【问题标题】:How to use INotifyPropertyChanged correctly in WPF/XAML如何在 WPF/XAML 中正确使用 INotifyPropertyChanged
【发布时间】:2011-03-31 03:52:39
【问题描述】:

我有一个想要绑定到控件的自定义对象。在那个自定义对象上,我实现了 INotifyPropertyChanged 接口。我已成功绑定到我的对象和该对象上的属性。

我不知道如何从那里开始。我已经为此工作了 2 天,但仍然无法正常工作。

我的假设是,当我更改绑定到控件的属性时,该属性中设置的值将显示在控件中。但是,无论我如何更改属性,UI 都不会超过其初始值。

我以这种方式实现了 INotifyPropertyChanged: A base class which implements INotifyPropertyChanged

所以我的基类是这样的:

[Serializable]
public abstract class BindableObject : INotifyPropertyChanged
{
    #region Data

    private static readonly Dictionary<string, PropertyChangedEventArgs> eventArgCache;
    private const string ERROR_MSG = "{0} is not a public property of {1}";

    #endregion // Data

    #region Constructors

    static BindableObject()
    {
        eventArgCache = new Dictionary<string, PropertyChangedEventArgs>();
    }

    protected BindableObject()
    {
    }

    #endregion // Constructors

    #region Public Members

    /// <summary>
    /// Raised when a public property of this object is set.
    /// </summary>
    [field: NonSerialized]
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Returns an instance of PropertyChangedEventArgs for 
    /// the specified property name.
    /// </summary>
    /// <param name="propertyName">
    /// The name of the property to create event args for.
    /// </param>  
    public static PropertyChangedEventArgs
        GetPropertyChangedEventArgs(string propertyName)
    {
        if (String.IsNullOrEmpty(propertyName))
            throw new ArgumentException(
                "propertyName cannot be null or empty.");

        PropertyChangedEventArgs args;

        // Get the event args from the cache, creating them
        // and adding to the cache if necessary.
        lock (typeof(BindableObject))
        {
            bool isCached = eventArgCache.ContainsKey(propertyName);
            if (!isCached)
            {
                eventArgCache.Add(
                    propertyName,
                    new PropertyChangedEventArgs(propertyName));
            }

            args = eventArgCache[propertyName];
        }

        return args;
    }

    #endregion // Public Members

    #region Protected Members

    /// <summary>
    /// Derived classes can override this method to
    /// execute logic after a property is set. The 
    /// base implementation does nothing.
    /// </summary>
    /// <param name="propertyName">
    /// The property which was changed.
    /// </param>
    protected virtual void AfterPropertyChanged(string propertyName)
    {
    }

    /// <summary>
    /// Attempts to raise the PropertyChanged event, and 
    /// invokes the virtual AfterPropertyChanged method, 
    /// regardless of whether the event was raised or not.
    /// </summary>
    /// <param name="propertyName">
    /// The property which was changed.
    /// </param>
    protected void RaisePropertyChanged(string propertyName)
    {
        this.VerifyProperty(propertyName);

        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            // Get the cached event args.
            PropertyChangedEventArgs args =
                GetPropertyChangedEventArgs(propertyName);

            // Raise the PropertyChanged event.
            handler(this, args);
        }

        this.AfterPropertyChanged(propertyName);
    }

    #endregion // Protected Members

    #region Private Helpers

    [Conditional("DEBUG")]
    private void VerifyProperty(string propertyName)
    {
        Type type = this.GetType();

        // Look for a public property with the specified name.
        PropertyInfo propInfo = type.GetProperty(propertyName);

        if (propInfo == null)
        {
            // The property could not be found,
            // so alert the developer of the problem.

            string msg = string.Format(
                ERROR_MSG,
                propertyName,
                type.FullName);

            Debug.Fail(msg);
        }
    }

    #endregion // Private Helpers
}

我从上面的那个类继承,在我的派生类中我在我的财产上这样做:

    public virtual string Name
    {
        get
        {
            return m_strName;
        }
        set
        {
            m_strName = value;
            RaisePropertyChanged("Name");
        }
    }

我的 XAML 看起来像这样(缩略版):

<Window x:Class="PSSPECApplication.Windows.Project"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:System="clr-namespace:System;assembly=mscorlib"
    DataContext="{Binding SizingProject, RelativeSource={RelativeSource Self}}">
        <StackPanel VerticalAlignment="Center">
            <TextBox Name="txtProjectName" Text="{Binding Name}" />
        </StackPanel>

您可以看到窗口的数据上下文是一个名为 SizingProject 的属性。 SizingProject 是派生类型(派生自 BindableObject),其中包含 Name 属性并引发 PropertyChanged 事件处理程序。

在我的窗口的构造函数中,我填充了 SizingProject 并设置了它的 Name 属性。

为了测试这一点,我在窗口上还有一个按钮,它触发一个事件,将 Name 属性设置为不同于原来的值。但是,当更改 name 属性时,什么也没有发生。我已经追溯到 BindableObject 并且 PropertyChanged 事件始终设置为 null,因此没有设置和运行任何处理程序。这是为什么呢?

我认为通过实现 INotifyPropertyChanged 并在绑定中使用该类型会强制 WPF 自动设置该事件处理程序,然后发生正确的行为?对我来说,我从未见过这种行为。


我发现了这个问题。我需要做的是为我的属性 SizingProject 创建一个 DependencyProperty。在我这样做之后,一切正常。

        public static readonly DependencyProperty SizingProjectProperty =
        DependencyProperty.Register("SizingProject", typeof(Sizing.Project), typeof(Project), new UIPropertyMetadata());

    public Sizing.Project SizingProject
    {
        get
        {
            return (Sizing.Project)GetValue(Project.SizingProjectProperty);
        }
        set
        {
            SetValue(Project.SizingProjectProperty, value);
        }
    }

【问题讨论】:

标签: wpf xaml binding inotifypropertychanged propertychanged


【解决方案1】:

在我的机器上工作。虽然,缓存有点古怪。我要么为每种类型创建静态只读版本,要么在需要之前忘记缓存(过早的优化和所有)。

我创建了一个示例项目。主窗口如下所示:

<Window x:Class="INPCTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:this="clr-namespace:INPCTest"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <this:MyObject />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBlock
            Text="{Binding MyOutProperty}" />
        <TextBox
            Grid.Row="1"
            Text="{Binding MyInProperty, UpdateSourceTrigger=PropertyChanged}" />
    </Grid>
</Window>

我正在绑定到 MyObject 的一个实例,该实例是在 xaml 中创建的(如果您不熟悉,可以在代码隐藏中执行此操作)。

这是 MyObject 的代码:

class MyObject : BindableObject
{
    private string _in;
    private string _out;
    public string MyOutProperty
    {
        get { return _out; }
        set { _out = value; this.RaisePropertyChanged("MyOutProperty"); }
    }
    public string MyInProperty
    {
        get { return _in; }
        set
        {
            _in = value;
            MyOutProperty = "The textbox below says: \"" + value + "\"";
            this.RaisePropertyChanged("MyInProperty");
        }
    }
}

这一切是如何协同工作的:

  1. 窗口已创建
  2. MyObject 的实例被实例化并设置为Window.DataContext
  3. TextBlock 绑定到 MyOutProperty
  4. TextBox 绑定到 MyInProperty
  5. 用户在文本框中输入“X”
  6. MyInProperty 设置为“X”
  7. MyOutProperty 设置为“下面的文本框显示:“X””
  8. MyOutProperty 的 Set 方法调用 RaisePropertyChanged 传入“MyOutProperty”
  9. TextBlock 按预期更新。

我怀疑您的问题不在于您的基类,而在于您的子类的实现或您的绑定。

为了帮助调试您的绑定,follow the information at this link 为详细绑定跟踪输出配置 Visual Studio(如果您已配置,则在“输出”窗口或“立即”窗口中结束)。

【讨论】:

  • 我正在尝试将已实例化的对象绑定到属性。实例化的对象在 Window 后面的代码中的一个属性中被引用。我尝试了您的一些语法,但它在 DataContext 部分抱怨说我正在绑定到一个没有不带参数的构造函数的对象。我不想绑定到新对象。我正在尝试绑定到一个已经存在的。
  • 虽然我发现了这个问题,但我还是给了你信任,因为你是唯一一个试图提供帮助的人。谢谢。
  • 问题最终是我必须公开我在 DataContext 中使用的属性以及相应的 DependencyProperty。在我这样做之后,它工作得很好。然而不幸的是,我很快就遇到了 INotifyPropertyChanged 的​​其他问题。我的对象非常复杂,并且在我的自定义对象中存在许多依赖属性,维护 PropertyChanged 事件是维护的噩梦。我最终使用这种技术来解决我所有的问题。 pochet.net/blog/2010/07/02/…
猜你喜欢
  • 1970-01-01
  • 2015-04-07
  • 2020-02-25
  • 2011-08-12
  • 1970-01-01
  • 2019-04-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多