[英]Implementing INotifyPropertyChanged for nested properties
I have a Person class:我有一个 Person 类:
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name{
get { return _name; }
set {
if ( _name != value ) {
_name = value;
OnPropertyChanged( "Name" );
}
}
private Address _primaryAddress;
public Address PrimaryAddress {
get { return _primaryAddress; }
set {
if ( _primaryAddress != value ) {
_primaryAddress = value;
OnPropertyChanged( "PrimaryAddress" );
}
}
//OnPropertyChanged code goes here
}
I have an Address class:我有一个地址类:
public class Address : INotifyPropertyChanged
{
private string _streetone;
public string StreetOne{
get { return _streetone; }
set {
if ( _streetone != value ) {
_streetone = value;
OnPropertyChanged( "StreetOne" );
}
}
//Other fields here
//OnPropertyChanged code goes here
}
I have a ViewModel:我有一个视图模型:
public class MyViewModel
{
//constructor and other stuff here
private Person _person;
public Person Person{
get { return _person; }
set {
if ( _person != value ) {
_person = value;
OnPropertyChanged( "Person" );
}
}
}
I have a View which has the following lines:我有一个视图,其中包含以下几行:
<TextBox Text="{Binding Person.Name, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged />
<TextBox Text="{Binding Person.Address.StreetOne, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged />
Both values show up in the text box ok when the view loads.当视图加载时,这两个值都显示在文本框中。
Changes to the first text box will fire OnPropertyChanged( "Person" )
in MyViewModel.对第一个文本框的更改将在 MyViewModel 中触发 OnPropertyChanged
OnPropertyChanged( "Person" )
。 Great.伟大的。
Changes to the second text box ("Person.Address.StreetOne")
does NOT fire OnPropertyChanged( "Person" )
inside MyViewModel.对第二个文本框
("Person.Address.StreetOne")
的更改不会在 MyViewModel 中触发 OnPropertyChanged OnPropertyChanged( "Person" )
。 Meaning it doesn't call the Person object's SET method.这意味着它不调用 Person 对象的 SET 方法。 Not great.
不是很好。 Interestingly the SET method of StreetOne inside the Address class is called.
有趣的是,Address 类中 StreetOne 的 SET 方法被调用了。
How do I get the SET method of the Person object inside the ViewModel to be called when Person.Address.StreetOne
is changed???当
Person.Address.StreetOne
更改时,如何获取 ViewModel 中 Person 对象的 SET 方法?
Do I need to flatten my data so SteetOne is inside Person and not Address??我是否需要展平我的数据,以便 SteetOne 在 Person 而不是 Address?
Thanks!谢谢!
While adding 'pass-through' properties to your ViewModel is a fine solution, it can quickly become untenable.虽然向 ViewModel 添加“传递”属性是一个很好的解决方案,但它很快就会变得站不住脚。 The standard alternative is to propagate changes as below:
标准的替代方法是传播更改,如下所示:
public Address PrimaryAddress {
get => _primaryAddress;
set {
if ( _primaryAddress != value )
{
//Clean-up old event handler:
if(_primaryAddress != null)
_primaryAddress.PropertyChanged -= AddressChanged;
_primaryAddress = value;
if (_primaryAddress != null)
_primaryAddress.PropertyChanged += AddressChanged;
OnPropertyChanged( "PrimaryAddress" );
}
void AddressChanged(object sender, PropertyChangedEventArgs args)
=> OnPropertyChanged("PrimaryAddress");
}
}
Now change notifications are propagated from Address to person.现在更改通知从地址传播到人员。
Edit: Moved handler to c# 7 local function.编辑:将处理程序移动到 c# 7 本地函数。
if you want the viewmodel SET to be called you could create a street property如果你想调用 viewmodel SET 你可以创建一个街道属性
public class MyViewModel
{
//constructor and other stuff here
public string Street{
get { return this.Person.PrimaryAddress.StreetOne; }
set {
if ( this.Person.PrimaryAddress.StreetOne!= value ) {
this.Person.PrimaryAddress.StreetOne = value;
OnPropertyChanged( "Street" );
}
}
}
xaml xml
<TextBox Text="{Binding Street, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged />
but this solution has its drawbacks.但这种解决方案有其缺点。 i go with Reeds answer in my projects
我在我的项目中选择 Reeds 答案
How do I get the SET method of the Person object inside the ViewModel to be called when Person.Address.StreetOne is changed???
当 Person.Address.StreetOne 更改时,如何获取 ViewModel 中 Person 对象的 SET 方法?
Why do you want to do this?你为什么要这样做? It should not be required - you only need the
StreetOne
property changed event to fire.它不应该是必需的 - 您只需要
StreetOne
属性更改事件。
Do I need to flatten my data so SteetOne is inside Person and not Address??
我是否需要展平我的数据,以便 SteetOne 在 Person 而不是 Address?
If you want to actually cause this to trigger, you don't need to flatten it (though that is an option).如果您想真正触发它,则不需要将其展平(尽管这是一个选项)。 You can subscribe to the
Address
's PropertyChanged
event within your Person class, and raise the event for "Address" within Person
when it changes.您可以在 Person 类中订阅
Address
的PropertyChanged
事件,并在Person
更改时引发“Address”事件。 This shouldn't be necessary, however.然而,这不应该是必要的。
Since I wasn't able to find a ready-to-use solution, I've done a custom implementation based on Pieters (and Marks) suggestions (thanks!).由于我无法找到现成的解决方案,因此我根据 Pieters(和 Marks)的建议(谢谢!)进行了自定义实现。
Using the classes, you will be notified about any change in a deep object tree, this works for any INotifyPropertyChanged
implementing Types and INotifyCollectionChanged
* implementing collections (Obviously, I'm using the ObservableCollection
for that).使用这些类,您将收到有关深层对象树中任何更改的通知,这适用于任何
INotifyPropertyChanged
实现类型和INotifyCollectionChanged
* 实现集合(显然,我为此使用了ObservableCollection
)。
I hope this turned out to be a quite clean and elegant solution, it's not fully tested though and there is room for enhancements.我希望这是一个非常干净和优雅的解决方案,虽然它没有经过全面测试并且还有改进的空间。 It's pretty easy to use, just create an instance of
ChangeListener
using it's static Create
method and passing your INotifyPropertyChanged
:它非常易于使用,只需使用它的静态
Create
方法创建一个ChangeListener
实例并传递您的INotifyPropertyChanged
:
var listener = ChangeListener.Create(myViewModel);
listener.PropertyChanged +=
new PropertyChangedEventHandler(listener_PropertyChanged);
the PropertyChangedEventArgs
provide a PropertyName
which will be always the full "path" of your Objects. PropertyChangedEventArgs
提供一个PropertyName
,它将始终是您的对象的完整“路径”。 For example, if you change your Persons's "BestFriend" Name, the PropertyName
will be "BestFriend.Name", if the BestFriend
has a collection of Children and you change it's Age, the value will be "BestFriend.Children[].Age" and so on.例如,如果您更改 Persons 的“BestFriend”名称,
PropertyName
将为“BestFriend.Name”,如果BestFriend
有一个 Children 集合并且您更改它的 Age,则该值将为“BestFriend.Children[].Age”等等。 Don't forget to Dispose
when your object is destroyed, then it will (hopefully) completely unsubscribe from all event listeners.当您的对象被销毁时,不要忘记
Dispose
,然后它将(希望)完全取消订阅所有事件侦听器。
It compiles in .NET (Tested in 4) and Silverlight (Tested in 4).它在 .NET(在 4 中测试)和 Silverlight(在 4 中测试)编译。 Because the code in seperated in three classes, I've posted the code to gist 705450 where you can grab it all: https://gist.github.com/705450 **
因为代码分为三个类,我已将代码发布到gist 705450 ,您可以在其中获取所有内容: https ://gist.github.com/705450 **
*) One reason that the code is working is that the ObservableCollection
also implements INotifyPropertyChanged
, else it wouldn't work as desired, this is a known caveat *) 代码工作的一个原因是
ObservableCollection
还实现了INotifyPropertyChanged
,否则它不会按预期工作,这是一个已知的警告
**) Use for free, released under MIT License **) 免费使用,在MIT 许可下发布
There is a spelling mistake in your property change notification:您的属性更改通知中有拼写错误:
OnPropertyChanged( "SteetOne" );
should be应该
OnPropertyChanged( "StreetOne" );
Please take a look at EverCodo.ChangesMonitoring .请查看EverCodo.ChangesMonitoring 。 This is a framework to handle PropertyChanged and CollectionChanged events on arbitrary hierarchy of nested objects and collections.
这是一个框架,用于处理嵌套对象和集合的任意层次结构上的 PropertyChanged 和 CollectionChanged 事件。
Create a monitor to handle all change events of the object tree:创建一个监视器来处理对象树的所有更改事件:
_ChangesMonitor = ChangesMonitor.Create(Root);
_ChangesMonitor.Changed += ChangesMonitor_Changed;
Do arbitrary modifications on the object tree (all of them will be handled):对对象树进行任意修改(所有这些都将被处理):
Root.Children[5].Children[3].Children[1].Metadata.Tags.Add("Some tag");
Root.Children[5].Children[3].Metadata = new Metadata();
Root.Children[5].Children[3].Metadata.Description = "Some description";
Root.Children[5].Name = "Some name";
Root.Children[5].Children = new ObservableCollection<Entity>();
Handle all events in one place:在一个地方处理所有事件:
private void ChangesMonitor_Changed(object sender, MonitoredObjectChangedEventArgs args)
{
// inspect args parameter for detailed information about the event
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.