Внедрение 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
}

У меня есть класс Address:

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

}

У меня есть вид, который имеет следующие строки:

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

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

Оба значения отображаются в текстовом поле ok при загрузке представления.

Изменения в первом текстовом поле срабатывают OnPropertyChanged( "Person" ) в MyViewModel. Отлично.

Изменения во втором текстовом поле ("Person.Address.StreetOne") НЕ запускают OnPropertyChanged( "Person" ) внутри MyViewModel. Это означает, что он не вызывает метод объекта объекта Person. Не хорошо. Интересно, что вызывается метод SET StreetOne внутри класса Address.

Как получить метод SET объекта Person внутри ViewModel, который будет вызываться при изменении Person.Address.StreetOne???

Нужно ли мне сглаживать мои данные, поэтому SteetOne находится внутри Person, а не Address?

Спасибо!

Ответ 1

если вы хотите, чтобы вызываемая модель 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 />

но это решение имеет свои недостатки. я иду с ответом Ридс в моих проектах

Ответ 2

Хотя добавление сквозных свойств в 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");
        }
  }

Теперь уведомления об изменениях распространяются от Адреса к человеку.

Редактировать: Перемещен обработчик в локальную функцию С# 7.

Ответ 3

Как получить метод SET объекта Person внутри ViewModel, который будет вызываться при изменении Person.Address.StreetOne???

Почему вы хотите это сделать? Это не требуется - вам нужно только событие с изменением свойства StreetOne для запуска.

Нужно ли мне сглаживать мои данные, поэтому SteetOne находится внутри Person, а не Address?

Если вы действительно хотите вызвать это, вам не нужно сгладить его (хотя это вариант). Вы можете подписаться на событие Address PropertyChanged в вашем классе Person и поднять событие для "Адрес" в Person, когда оно изменится. Однако это не обязательно.

Ответ 4

Поскольку я не смог найти готовое к использованию решение, я сделал пользовательскую реализацию на основе предложений Pieters (и Marks) (спасибо!).

Используя классы, вы будете уведомлены о любых изменениях в дереве глубоких объектов, это работает для любых INotifyPropertyChanged реализации типов и INotifyCollectionChanged * реализации коллекций (очевидно, я использую ObservableCollection для этого).

Я надеюсь, что это оказалось довольно чистым и элегантным решением, но оно не полностью протестировано, и есть возможности для усовершенствований. Он довольно прост в использовании, просто создайте экземпляр ChangeListener, используя его статический метод Create и передав INotifyPropertyChanged:

var listener = ChangeListener.Create(myViewModel);
listener.PropertyChanged += 
    new PropertyChangedEventHandler(listener_PropertyChanged);

PropertyChangedEventArgs укажите PropertyName, который всегда будет полным "путем" ваших объектов. Например, если вы измените имя "BestFriend" ваших лиц, PropertyName будет "BestFriend.Name", если BestFriend имеет коллекцию Children и вы меняете ее Age, значение будет "BestFriend.Children [ ].Age" и т.д. Не забывайте Dispose, когда ваш объект будет уничтожен, тогда он (надеюсь) полностью отменит подписку на все прослушиватели событий.

Он компилируется в .NET(протестирован в 4) и Silverlight (протестирован в 4). Поскольку код, разделенный на три класса, я отправил код gist 705450, где вы можете его захватить: https://gist.github.com/705450 **

*) Одна из причин, по которой работает код, заключается в том, что ObservableCollection также реализует INotifyPropertyChanged, иначе он не будет работать по желанию, это известная оговорка

**) Используйте бесплатно, выпущенный под Лицензия MIT

Ответ 5

В уведомлении об изменении свойства есть орфографическая ошибка:

OnPropertyChanged( "SteetOne" );

должен быть

OnPropertyChanged( "StreetOne" );