MVVM: измененная модель, как правильно обновить ViewModel и View?

Case

Скажем, у меня есть класс Person, PersonViewModel и PersonView.

Обновление свойств от PersonView до модели Person достаточно просто. PersonViewModel содержит объект Person и имеет общедоступные свойства, с которыми связывается PersonView, чтобы обновить модель Person.

Однако.

Представьте, что модель Person может быть обновлена ​​Service. Теперь изменение свойства должно быть передано в PersonViewModel, а затем в PersonView.

Вот как я это исправит:

Для каждого свойства модели Person я бы поднял событие PropertyChanged. PersonViewModel подписывается на событие PropertyChanged Person. PersonViewModel затем поднимет еще одно свойство PropertyChanged, чтобы обновить PersonView.

Это для меня кажется самым очевидным способом, но я как бы хочу бросить этот вопрос там в надежде, что кто-то покажет мне более хороший способ. Действительно ли это так просто или есть лучшие способы пометить модель как измененную и обновить соответствующие свойства в ViewModel?

Дополнения

PersonView DataContext PersonViewModel. Person заселяется из JSON и обновляется многократно за время его существования.

Не стесняйтесь предлагать архитектурные изменения для моего конкретного случая.

Ответ

Я назвал aqwert ответом на мой вопрос, так как он предоставил мне альтернативу решению, которое я уже предложил.

Ответ 1

Если представление напрямую связано с моделью, то пока служба использует тот же экземпляр, любые изменения свойств модели будут переданы в представление.

Однако, если вы воссоздаете новую модель в сервисе, вы дадите viewmodel новую модель. Я ожидаю увидеть модель как свойство в модели представления, поэтому, когда вы установите это свойство, все привязки должны быть предупреждены об изменении.

//in the ViewModel
public Person Model
{
   get { return _person; }
   set { _person = value; 
         RaisePropertyChanged("Model");  //<- this should tell the view to update
        }
}

EDIT:

Поскольку вы указываете, что существует конкретная логика ViewModel, тогда вы можете адаптировать эти свойства в ViewModel

 private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
 {
      if(e.PropertyName == "Prop1") RaisePropertyChanged("SpecicalProperty");
      ...
 }

  public string SpecicalProperty
  {
     get
     {
         reutrn Model.Prop1 + " some additional logic for the view"; 
     }
   }

В XAML

  <TextBlock Text="{Binding Model.PropertyDirect}" />  
  <TextBlock Text="{Binding SpecicalProperty}" />

Таким образом, только свойства Model и ViewModel привязаны к представлению без дублирования данных.

Вы можете заставить fancier создать помощника, чтобы связать изменения свойств с моделью с моделью просмотра или использовать словарь сопоставлений

 _mapping.Add("Prop1", new string[] { "SpecicalProperty", "SpecicalProperty2" });

а затем найдите свойства для обновления, получив список свойств

 private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
 {

      string[] props;
      if(_mapping.TryGetValue(e.PropertyName, out props))
      {
          foreach(var prop in props)
              RaisePropertyChanged(prop);
      } 
 }

Ответ 2

Когда представление связывается непосредственно с моделью (что также имеет место, когда ViewModel представляет модель), вы смешиваете код пользовательского интерфейса и код данных. Целью MVVM является разделение этих двух доменов кода. Вот для чего нужна ViewModel.

Модель представления должна иметь свои собственные свойства, с которыми может быть связано представление. Пример:

class PersonViewModel
{
    private Person OriginalModel { get; set; }

    public ValueViewModel<string> Name { get; set; }
    public ValueViewModel<int> Postcode { get; set; }

    protected void ReadFromModel(Person person)
    {
        OriginalModel = person;
        Name.Value = OriginalModel.Name;
        Postcode.Value = OriginalModel.Postcode;
    }

    protected Person WriteToModel()
    {
        OriginalModel.Name = Name.Value; //...
        return OriginalModel;
    }
}

Использование такого ViewModel-дизайна действительно отделяет ваши объекты данных от кода вашего пользовательского интерфейса. Когда структура класса Person изменяется, пользовательский интерфейс не должен соответствующим образом соответствовать, потому что ViewModel отделяет их друг от друга.

Теперь к вашему вопросу. Как вы можете видеть в приведенном выше примере, я использовал универсальный ValueViewModel<T>. Этот класс реализует INotifyPropertyChanged (и некоторые другие вещи). Когда вы получаете новый экземпляр Person, вам нужно только вызвать ReadFromModel(newPerson) в вашей ViewModel для обновления пользовательского интерфейса, потому что ValueViewModels, с которыми связывается представление, сообщит пользовательскому интерфейсу об изменении их значения.

Вот чрезвычайно упрощенный пример внутренней структуры ValueViewModel:

class ValueViewModel<T> : INotifyPropertyChanged
{
    private T _value;
    public T Value 
    {
        get { return _value;}
        set
        {
            _value = value;
            RaisePropertyChanged("Value");
        }
    }
}

Это подход, который мы использовали в нашей библиотеке MVVM. Он имеет то преимущество, что вынуждает разработчика четко отделять код от забот дизайнеров. И, как побочный эффект, он генерирует стандартизированное расположение кода во всех ваших представлениях и моделях представления и, таким образом, улучшает качество кода.