Получатель и сеттер в ViewModel для примитивных свойств в модели INotifyPropertyChanged

В настоящее время я работаю над проектом, который использует шаблон MVVM, и наткнулся на этот сценарий.

Предполагаемый класс Model: INotifyPropertyChanged содержит некоторые свойства, например

public class Model : INotifyPropertyChanged
{
    private string rabbit;
    public string Rabbit
    {
        get { return rabbit; }
        setter { rabbit = value; OnPropertyChanged("Rabbit"); }
    }
}

Теперь я хочу написать ViewModel, который обтекает модель, раскрывая свойства модели вместе с дополнительными свойствами (командами и т. Д.).

Проблема возникает при написании геттеров и сеттеров для свойств, уже существующих в модели. Я закомментировал конкретную часть кода.

public class ViewModel : INotifyPropertyChanged
{
    private Model modelObject;
    public string Rabbit
    {
        get { return modelObject.rabbit; }
        setter 
        { 
            modelObject.rabbit = value; 
            OnPropertyChanged("Rabbit"); // Should OnPropertyChanged be called here?
        }
    }

    public ViewModel(Model modelObject)
    {
         this.modelObject = modelObject;
    }
}

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

(P.S. Причина, по которой я сделал свою модель наблюдаемой, заключается в том, что та же модель будет использоваться в другом месте, кроме графического интерфейса, в том же пространстве памяти, где также необходимо получать уведомления об изменениях в модели.)


person Yiyuan Lee    schedule 17.05.2015    source источник


Ответы (4)


Это одна из основных проблем MVVM с несколькими разными подходами к ее решению.

Ваш текущий подход хорош и очень чистый MVVM. Недостаток - объем необходимой работы.

Я не уверен, следует ли мне снова вызывать OnPropertyChanged в ViewModel.

Вам придется, INPC модели не помогает привязке данных. Вам также нужно будет подписать вашу ViewModel на Model.PropertyChanged и повторить эти изменения в виртуальной машине.

Другой приемлемый подход - предоставить модель как общедоступное свойство (с INPC) и связать с <TextBlock Text="{Binding Model.Rabbit}" />

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

person Henk Holterman    schedule 17.05.2015

Есть два сценария возникновения этой проблемы:

1. View-Model, которая обтекает объект модели, что похоже на ваш сценарий.

и для этого есть два дополнительных варианта:

1a) объект модели уже реализует INotifyPropertyChanged для свойств, которые вы хотите использовать в представлении. В этом случае вы можете вывести модель.

public class ViewModel : Model 
{
   public ViewModel(){} //parameterless c-tor used for mock for the view.
   public ViewModel(Model model)
   {
      //Copy all model properties to view-model properties.
      //Assuming you get from "outside" a model or Data transfer object.
   } 
}

1b) объект модели не реализует INotifyPropertyChangedили не для всех свойств, которые вы хотите использовать в представлении. в этом случае вам необходимо содержать модель.

public class ViewModel : INotifyPropertyChanged
{
   protected Model model {get;} //C# 5 assumed..

   public ViewModel() //parameterless c-tor used for mock for the view.
   {
       model = new Model();
   } 

   public ViewModel(Model model)
   {
      this.model = model;
   }

   public string SomeProperty
   {
     get { return Model.SomeProperty; } 
     set
     { 
       Model.SomeProperty = value; 
       PropertyChanged?.Invoke(this, new PropertyChangedEventsArgs(this, "SomeProperty"));
     }
   }
}

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

2. Модель представления, которая обтекает представление.

Что ж, чтобы написать это - снова я смотрю на второй пример из предыдущего варианта, и все выглядит правильно и для этого сценария.

Вывод: View-Model обтекает представление - и в 99% случаев - конкретное представление. Поэтому используйте вариант 1b для каждой модели представления, которую вы пишете, и независимо от того, как кодируется ваша модель.

Чтобы завершить процесс понимания этой проблемы, рассмотрите этот пример модели представления, и вы быстро поймете, что не существует "хорошего" способа написать его с использованием шаблона из примера 1a:

public class ViewModel : INotifyPropertyChanged
{
   protected ModelType1 model1 {get;}
   protected ModelType2 model2 {get;}

   public ViewModel() //parameterless c-tor used for mock for the view.
   {
      model1= new ModelType1();
      model2= new ModelType2();
   }

   public ViewModel(ModelType1 model1, ModelType2 model2)
   {
      this.model1 = model1;
      this.model2 = model2;
   }

   public string SpecialProperty
   {
     get { return $"{model1.SomeProperty}.{model2.SomeOtherProperty}"; } 
     set
     { 
       var arr = value.Split('.');
       model1.SomeProperty = arr[0];
       model2.SomtOtherProperty = arr[1];
       PropertyChanged?.Invoke(this, new PropertyChangedEventsArgs(this, "SpecialProperty"));
     }
   }
}

Надеюсь, это поможет ;)

person G.Y    schedule 13.03.2016

Да, если вы собираетесь просто обернуть модель, вам также нужно будет снова вызвать OnPropertyChanged. Это связано с тем, что ваше представление привязано к модели представления, а не к модели, в этом случае ваша модель представления просто предлагает уровень перенаправления.

Если вы смотрите на это и думаете, что должен быть более простой способ, и думаете, что можете просто указать путь привязки, который проходит через свойство viewmodel и непосредственно к свойству в модели - это звучит просто и легко (и это так), но не делай этого. Это быстро приводит к антипаттерну, когда ваш проект начинает расти, и нарушает Закон Деметры ( и в основном сводит на нет цель модели просмотра).

person slugster    schedule 17.05.2015

Да, иначе View, привязанный к модели представления, не будет уведомлен о каких-либо изменениях.

Обратите внимание, что если Model используется совместно и обновляется в другом месте (возможно, в другом View), этот View не будет обновляться, поскольку модель будет выдавать уведомление. ViewModel нужно будет прослушивать изменения на Model, а затем вызывать соответствующие изменения свойств на ViewModel.

Более прагматичное решение часто состоит в том, чтобы разоблачить Model и напрямую привязаться к нему, хотя некоторые сочтут такой прагматизм ересью. Выбор за вами - быстрый гугл откроет различные мнения и некоторые плюсы / минусы. Даже другие ответы дают некоторые!

person Charles Mager    schedule 17.05.2015