如何在WPF/XAML中正确使用INotifyPropertyChanged
我有一个自定义对象,我试图将其绑定到控件。在该自定义对象上,我实现了 INotifyPropertyChanged 接口。我已成功绑定到我的对象以及该对象上的属性。
我不知道如何从那里走下去。我已经为此工作了 2 天,但仍然无法让它工作。
我的假设是,当我更改绑定到控件的属性时,该属性中设置的值将显示在控件中。但是,无论我如何更改属性,UI 的更新都不会超出其初始值。
我以这种方式实现了 INotifyPropertyChanged: 实现 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 属性设置为不同于原来的属性。但是,当名称属性更改时,什么也不会发生。我已追溯到 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);
}
}
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.
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.
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.
I have implemented INotifyPropertyChanged in this manner:
A base class which implements INotifyPropertyChanged
So my base class is this:
[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");
}
}
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>
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.
In my window's constructor I populate SizingProject and it's Name property is set.
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?
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.
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);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
在我的机器上工作。不过,缓存有点古怪。我要么为每种类型创建静态只读版本,要么在需要之前忘记缓存(过早优化等)。
我创建了一个示例项目。主窗口如下所示:
我正在绑定到 MyObject 的实例,该实例是我在 xaml 中创建的(如果您不熟悉的话,您可以在代码隐藏中执行此操作)。
下面是 MyObject 的代码:
它是如何协同工作的:
Window.DataContext
我怀疑您的问题不在于您的基类,而在于您的子类的实现或您的绑定。
为了帮助调试您的绑定,按照此链接中的信息进行配置Visual Studio 用于详细绑定跟踪输出(最终出现在“输出”窗口或“立即”窗口中,如果您已配置)。
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:
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).
Here's the code for MyObject:
How it all works together is:
Window.DataContext
I suspect your issue isn't with your base class, it is either with the implementation of your child classes OR in your bindings.
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).