如何在WPF / XAML中正确使用INotifyPropertyChanged

时间:2010-08-17 18:32:38

标签: wpf xaml binding inotifypropertychanged propertychanged

我有一个自定义对象,我试图绑定到一个控件。在该自定义对象上,我实现了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);
        }
    }

1 个答案:

答案 0 :(得分:3)

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

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

<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的设置方法调用 RaisePropertyChanged 传入“MyOutProperty”
  9. TextBlock按预期更新。
  10. 我怀疑你的问题不在于你的基类,它与你的子类的实现或你的绑定有关。

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