Implementing INotifyPropertyChanged for nested properties

旧街凉风 提交于 2020-01-12 06:39:29

问题


I have a Person class:

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

Changes to the second text box ("Person.Address.StreetOne") does NOT fire OnPropertyChanged( "Person" ) inside MyViewModel. Meaning it doesn't call the Person object's SET method. Not great. Interestingly the SET method of StreetOne inside the Address class is called.

How do I get the SET method of the Person object inside the ViewModel to be called when Person.Address.StreetOne is changed???

Do I need to flatten my data so SteetOne is inside Person and not Address??

Thanks!


回答1:


if you want the viewmodel SET to be called you could create a street property

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

but this solution has its drawbacks. i go with Reeds answer in my projects




回答2:


While adding 'pass-through' properties to your ViewModel is a fine solution, it can quickly become untenable. 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.




回答3:


How do I get the SET method of the Person object inside the ViewModel to be called when Person.Address.StreetOne is changed???

Why do you want to do this? It should not be required - you only need the StreetOne property changed event to fire.

Do I need to flatten my data so SteetOne is inside Person and not 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. This shouldn't be necessary, however.




回答4:


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!).

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

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:

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. 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. Don't forget to Dispose when your object is destroyed, then it will (hopefully) completely unsubscribe from all event listeners.

It compiles in .NET (Tested in 4) and Silverlight (Tested in 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 **

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

**) Use for free, released under MIT License




回答5:


There is a spelling mistake in your property change notification:

OnPropertyChanged( "SteetOne" );

should be

OnPropertyChanged( "StreetOne" );



来源:https://stackoverflow.com/questions/11870069/implementing-inotifypropertychanged-for-nested-properties

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!