web-dev-qa-db-ja.com

MVVM:変更されたモデル、ViewModelおよびViewを正しく更新する方法は?

場合

Personクラス、PersonViewModelおよびPersonViewがあるとします。

プロパティをPersonViewからPersonモデルに更新するのは簡単です。 PersonViewModelにはPersonオブジェクトが含まれ、Personモデルを更新するためにPersonViewがバインドするパブリックプロパティがあります。

しかしながら。

PersonモデルがServiceによって更新されることを想像してください。次に、プロパティの変更をPersonViewModelに伝達し、次にPersonViewに伝達する必要があります。

これは私がそれを修正する方法です:

Personモデルの各プロパティについて、PropertyChangedイベントを発生させます。 PersonViewModelPersonのPropertyChangedイベントをサブスクライブします。次に、PersonViewModelは、PersonViewを更新するために別のPropertyChangedを発生させます。

これは私には最も明白な方法のようですが、誰かが私にもっと良い方法を見せてくれることを期待して、この質問を投げ捨てたいと思います。本当にこれは単純ですか、それともモデルを変更済みとしてマークし、ViewModelのそれぞれのプロパティを更新するより良い方法がありますか?

追加

PersonViewのDataContextはPersonViewModelです。 PersonはJSONから読み込まれ、その存続期間中に何度も更新されます。

私の特定のケースについて、アーキテクチャの変更を自由に提案してください。

回答

Aqwertは、既に提案したソリューションの代替手段を提供するため、質問の回答としてマークしました。

27
ndsc

ビューがモデルに直接バインドされている場合、サービスが同じインスタンスを使用している限り、モデルプロパティへの変更はビューに反映されます。

ただし、サービスで新しいモデルを再作成する場合は、viewmodelに新しいモデルを指定します。モデルはビューモデルのプロパティとして表示されるので、そのプロパティを設定すると、すべてのバインディングに変更が通知されます。

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

編集:

特定の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プロパティの両方のみが、データを複製せずにビューにバインドされます。

プロパティの変更をモデルからビューモデルにリンクするヘルパーを作成したり、マッピングディクショナリを使用したりできます。

 _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);
      } 
 }
7
aqwert

ビューがモデルに直接バインドする場合(ViewModelがモデルを公開する場合も同様)、UIコードとデータコードを混在させます。 MVVMの目標は、これら2つのコードドメインを分離することです。それが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がお互いを分離するため、UIはそれに応じてフィットする必要はありません。

さてあなたの質問に。上記の例でわかるように、汎用の_ValueViewModel<T>_を使用しました。このクラスはINotifyPropertyChanged(および他のいくつかのもの)を実装します。新しいPersonインスタンスを受け取ったら、ViewModelでReadFromModel(newPerson)を呼び出すだけでUIが更新されます。これは、ViewがバインドするValueViewModelが値が変更されたときにUIに通知するためです。

以下は、ValueViewModelの内部構造の非常に簡略化された例です。

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

これは、MVVMライブラリで使用したアプローチです。これには、開発者がコードを設計者の懸念から明確に分離する必要があるという利点があります。また、副作用として、すべてのビューとビューモデルに標準化されたコードレイアウトを生成し、コードの品質を向上させます。

28
PVitt