如何在WPF/XAML中正确使用INotifyPropertyChanged [英] How to use INotifyPropertyChanged correctly in WPF/XAML

查看:93
本文介绍了如何在WPF/XAML中正确使用INotifyPropertyChanged的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个试图绑定到控件的自定义对象.在该自定义对象上,我实现了INotifyPropertyChanged接口.我已经成功绑定到我的对象和该对象的属性.

I have a custom object that I am trying to bind to a control. On that custom object I have implemented the INotifyPropertyChanged interface. I have successfully bound to my object and a property on that object.

我不知道怎么去那里.我已经为此工作了2天,但仍然无法正常工作.

What I can't figure out is how to go from there. I've been working on this for 2 days now and I still cannot get it working.

我的假设是,当我更改绑定到控件的属性时,在该属性中设置的值将显示在控件中.但是,无论我更改了多少属性,UI都不会超出其初始值进行更新.

My assumption was that when I would change the property bound to the control that the value set in that property would then show up in the control. However, no matter how much I change the property, the UI is never updated beyond it's initial value.

我以这种方式实现了INotifyPropertyChanged: 实现INotifyPropertyChanged的基类

I have implemented INotifyPropertyChanged in this manner: 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
}

我从上面的类继承,在派生类中,我在自己的媒体资源上执行此操作:

I inherit from that class above and in my derived class I do this on my property:

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

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

My XAML looks like this (abbreviated version):

<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事件处理程序.

You can see that the window's data context is a property called SizingProject. SizingProject is of the derived type (derived from BindableObject) and has the Name property in it and Raises the PropertyChanged event handler.

在窗口的构造函数中,填充SizingProject,并设置其Name属性.

In my window's constructor I populate SizingProject and it's Name property is set.

要对此进行测试,我在窗口上还具有一个按钮,该按钮会触发一个事件,该事件会将Name属性设置为原始属性之外的其他内容.但是,当name属性更改时,什么也不会发生.我已经追溯到BindableObject,并且PropertyChanged事件始终设置为null,因此没有设置和运行任何处理程序.为什么会这样?

To test this I also have a button on the window that fires an event that sets the Name property to something other than what it is originally. However when the name property is changed, nothing ever happens. I have traced back to the BindableObject and the PropertyChanged event is always set to null, so no handler is ever set and run. Why is this?

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

I thought by implementing INotifyPropertyChanged and using that type in a binding forces WPF to set that event handler automatically and then the correct behavior happens? For me, I never see that behavior.

我发现了问题.我需要做的是为我的属性SizingProject创建一个DependencyProperty.完成之后,一切正常.

I figured out the issue. What I needed to do was to create a DependencyProperty for my property SizingProject. After I did that, everything worked fine.

        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);
        }
    }

推荐答案

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

Works on my machine. Although, the cache is kind of wacky. I'd either create static readonly versions per type or forget about caching until required (premature optimization and all).

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

I created a sample project. The main window looks like this:

<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>

我将绑定到我在xaml中创建的MyObject实例(如果您对此并不熟悉,则可以在后面的代码中完成此操作).

I'm binding to an instance of MyObject, which I create in the xaml (you can do this in the codebehind if this isn't a familiar thing to you).

这是MyObject的代码:

Here's the code for 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");
        }
    }
}

它们如何共同发挥作用:

How it all works together is:

  1. 创建窗口
  2. MyObject 的实例被实例化并设置为Window.DataContext
  3. TextBlock绑定到 MyOutProperty
  4. 文本框绑定到 MyInProperty
  5. 用户在文本框中输入"X"
  6. MyInProperty 设置为'X'
  7. MyOutProperty 设置为下面的文本框显示:"X""
  8. MyOutProperty的 Set方法调用 RaisePropertyChanged 传入"MyOutProperty"
  9. TextBlock已按预期更新.
  1. Window is created
  2. Instance of MyObject is instantiated and set to Window.DataContext
  3. TextBlock is bound to MyOutProperty
  4. TextBox is bound to MyInProperty
  5. User types 'X' in the Textbox
  6. MyInProperty is set with 'X'
  7. MyOutProperty is set with 'The textbox below says: "X"'
  8. MyOutProperty's Set method calls RaisePropertyChanged passing in "MyOutProperty"
  9. The TextBlock gets updated as expected.

我怀疑您的问题与您的基类无关,或者与您的子类的实现有关,或者与您的绑定无关.

I suspect your issue isn't with your base class, it is either with the implementation of your child classes OR in your bindings.

要帮助调试绑定,请按照此链接中的信息进行操作将Visual Studio配置为进行详细的绑定跟踪输出(如果已配置,则显示在输出"窗口或立即"窗口中).

To help debug your bindings, follow the information at this link to configure visual studio for verbose binding trace output (ends up in the Output window or Immediate window, if you've configured that).

这篇关于如何在WPF/XAML中正确使用INotifyPropertyChanged的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆