代码之家  ›  专栏  ›  技术社区  ›  Ristogod

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

  •  2
  • Ristogod  · 技术社区  · 15 年前

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

    我不明白的是如何从那里开始。我已经做了两天了,但还是不能用。

    我的假设是,当我更改绑定到控件的属性时,该属性中设置的值将显示在控件中。但是,无论我对属性做了多少更改,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创建一个dependencProperty。我做完之后,一切都很顺利。

            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 回复  |  直到 15 年前
        1
  •  3
  •   user1228 user1228    15 年前

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

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

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

    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. 实例 Window.DataContext
    3. TextBlock已绑定到 MyOutProperty公司
    4. 我的财产
    5. 我的财产
    6. MyOutProperty公司 设置为“下面的文本框显示:“X””
    7. 设置方法调用 RaiseProperty已更改 传递“MyOutProperty”
    8. TextBlock按预期更新。

    为了帮助调试绑定, follow the information at this link