为嵌套属性实现INotifyPropertyChanged

时间:2012-08-08 17:37:22

标签: c# wpf mvvm binding inotifypropertychanged

我有一个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
}

我有一个地址类:

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
}

我有一个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" );
           }
     }

}

我有一个View,其中包含以下几行:

<TextBox  Text="{Binding Person.Name, Mode=TwoWay,   
    UpdateSourceTrigger=PropertyChanged />

<TextBox  Text="{Binding Person.Address.StreetOne, Mode=TwoWay,   
    UpdateSourceTrigger=PropertyChanged />

视图加载时,两个值都显示在文本框中。

对第一个文本框的更改将在MyViewModel中触发OnPropertyChanged( "Person" )。大。

对第二个文本框("Person.Address.StreetOne")的更改不会在MyViewModel中触发OnPropertyChanged( "Person" )。这意味着它不会调用Person对象的SET方法。不是很好。有趣的是,调用了Address类中的StreetOne的SET方法。

Person.Address.StreetOne发生变化时,如何在ViewModel中获取Person对象的SET方法?

我是否需要展平我的数据,以便SteetOne在Person内部而不是Address ??

谢谢!

5 个答案:

答案 0 :(得分:12)

添加&#39;传递&#39; ViewModel的属性是一个很好的解决方案,很快就会变得站不住脚。标准的替代方案是传播如下变化:

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

现在,更改通知会从“地址”传播到“人”。

编辑:将处理程序移至c#7本地函数。

答案 1 :(得分:8)

如果您想要调用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

<TextBox  Text="{Binding Street, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged />

但这种解决方案有其缺点。我在我的项目中使用Reeds答案

答案 2 :(得分:5)

  

当Person.Address.StreetOne发生变化时,如何在ViewModel中获取Person对象的SET方法?

你为什么要这样做?它不应该是必需的 - 您只需要触发StreetOne属性更改事件。

  

我是否需要展平我的数据,以便SteetOne在Person内部而不是Address ??

如果你想真正导致它被触发,你不需要压扁它(虽然这是一个选项)。您可以在Person类中订阅Address的{​​{1}}事件,并在PropertyChanged更改时为“地址”引发事件。但是,这不是必要的。

答案 3 :(得分:1)

由于我无法找到即用型解决方案,我已经完成了基于Pieters(和Marks)建议的自定义实现(谢谢!)。

使用这些类,您将收到有关深层对象树中任何更改的通知,这适用于任何INotifyPropertyChanged实现类型和INotifyCollectionChanged *实现集合(显然,我正在使用{{ 1}}为了那个)。

我希望这是一个非常干净和优雅的解决方案,虽然没有经过全面测试,但仍有增强空间。它非常易于使用,只需使用静态ObservableCollection方法创建ChangeListener实例并传递Create

INotifyPropertyChanged

var listener = ChangeListener.Create(myViewModel); listener.PropertyChanged += new PropertyChangedEventHandler(listener_PropertyChanged); 提供PropertyChangedEventArgs,它始终是对象的完整“路径”。例如,如果您更改人员的“BestFriend”名称,PropertyName将为“BestFriend.Name”,如果PropertyName有一个子集合并且您更改了它的年龄,则值将为“ BestFriend.Children []。年龄“等等。当您的对象被销毁时,不要忘记BestFriend,然后它(希望)完全取消订阅所有事件监听器。

它在.NET(测试4)和Silverlight(测试4)中编译。由于代码分为三个类,我已将代码发布到 gist 705450 ,您可以将其全部抓取:https://gist.github.com/705450 **

*)代码工作的一个原因是Dispose也实现了ObservableCollection,否则它将无法正常工作,这是一个众所周知的警告

**)免费使用,在MIT License下发布

答案 4 :(得分:0)

您的财产更改通知中存在拼写错误:

OnPropertyChanged( "SteetOne" );

应该是

OnPropertyChanged( "StreetOne" );