32

私はかなりの時間を費やして、次の課題に対する洗練された解決策を見つけようとしました。問題をハックする以上の解決策を見つけることができませんでした。

View、ViewModel、および Model の簡単な設定ができました。説明のために非常に簡単にします。

  • には、タイプ StringModelと呼ばれる単一のプロパティがあります。Title
  • はのModelDataContext ですView
  • にはView、モデルにTextBlockデータバインドされたものがあります。Title
  • には、を保存するメソッドが呼び出されますViewModelSave()ModelServer
  • は、Server変更をプッシュできます。Model

ここまでは順調ですね。モデルとServer. サーバーのタイプは重要ではありません。モデルSave()Server.

調整 1:

  • によって に加えられた変更を に変換するには、プロパティModel.Titleを呼び出す必要があります。は の DataContext であるため、これはうまく機能します。RaisePropertyChanged()ModelServerViewModelView

悪くない。

調整 2:

  • 次のステップは、 を呼び出して からにSave()加えた変更を に保存することです。これは私が立ち往生するところです。モデルが変更されたときに Save() を呼び出すイベントを処理できますが、これにより、サーバーによって行われた変更が反映されます。ViewModelServerModel.PropertyChangedViewModel

私はエレガントで論理的なソリューションを探しており、それが理にかなっていればアーキテクチャを変更したいと思っています。

4

5 に答える 5

75

過去に、複数の場所からのデータ オブジェクトの「ライブ」編集をサポートするアプリケーションを作成しました。アプリの多くのインスタンスが同じオブジェクトを同時に編集でき、誰かが変更をサーバーにプッシュすると、他の全員が通知を受け、 (最も単純なシナリオでは)これらの変更をすぐに確認できます。どのように設計されたのかをまとめました。

設定

  1. ビューは常にViewModel にバインドされます。定型文がたくさんあることは知っていますが、モデルに直接バインドすることは、最も単純なシナリオ以外では受け入れられません。また、MVVM の精神にも当てはまりません。

  2. ViewModel は、変更をプッシュする責任を単独で負います。これには明らかにサーバーへの変更のプッシュが含まれますが、アプリケーションの他のコンポーネントへの変更のプッシュも含まれる場合があります。

    これを行うために、ViewModel はラップするモデルを複製して、サーバーに提供するときにアプリケーションの残りの部分にトランザクション セマンティクスを提供できるようにする必要がある場合があります (つまり、アプリケーションの残りの部分に変更をプッシュするタイミングを選択できます。全員が同じ Model インスタンスに直接バインドされている場合は実行できません)。このように変更を分離するには、さらに多くの作業が必要ですが、強力な可能性も開きます (たとえば、変更を元に戻すのは簡単です。変更をプッシュしないでください)。

  3. ViewModel は、ある種のデータ サービスに依存しています。データ サービスは、データ ストアとコンシューマーの間に位置し、それらの間のすべての通信を処理するアプリケーション コンポーネントです。ViewModel がそのモデルのクローンを作成するときは常に、データ サービスが公開する適切な「データ ストアの変更」イベントもサブスクライブします。

    これにより、ViewModel は、他の ViewModel がデータ ストアにプッシュした "自分の" モデルへの変更を通知され、適切に反応することができます。適切に抽象化すれば、データ ストアは何でも構いません (たとえば、その特定のアプリケーションの WCF サービス)。

ワークフロー

  1. ViewModel が作成され、Model の所有権が割り当てられます。すぐにモデルのクローンを作成し、このクローンをビューに公開します。Data Service に依存しているため、この特定のモデルの更新に関する通知をサブスクライブすることを DS に伝えます。ViewModel は、そのモデル (「主キー」) を識別するものを認識していませんが、それは DS の責任であるため、その必要はありません。

  2. ユーザーが編集を終了すると、VM でコマンドを呼び出すビューと対話します。その後、VM は DS を呼び出し、複製されたモデルに加えられた変更をプッシュします。

  3. DS は変更を保持し、Model X への変更が行われたことを他のすべての関心のある VM に通知するイベントをさらに発生させます。モデルの新しいバージョンは、イベント引数の一部として提供されます。

  4. 同じモデルの所有権が割り当てられた他の VM は、外部の変更が到着したことを認識します。これで、パズルのすべてのピース (複製されたモデルの「前の」バージョン、複製された「ダーティ」バージョン、および複製された「現在の」バージョン) を使用してビューを更新する方法を決定できます。イベント引数の一部としてプッシュされました)。

ノート

  • モデルINotifyPropertyChangedはビューでのみ使用されます。ViewModel が Model が「ダーティ」であるかどうかを知りたい場合は、いつでもクローンを元のバージョンと比較できます (保持されている場合、可能であればお勧めします)。
  • ViewModel は変更をサーバーにアトミックにプッシュします。これは、データ ストアが常に一貫した状態に保たれるため、優れています。これは設計上の選択であり、別の方法で処理したい場合は、別の設計の方が適切です。
  • thisViewModel がパラメーターとして「変更のプッシュ」呼び出しに渡された場合、サーバーは、この変更の原因となった ViewModel の「Model changed」イベントを発生させないことを選択できます。そうでない場合でも、モデルの「現在の」バージョンが自身のクローンと同一であることがわかった場合、ViewModel は何もしないことを選択できます。
  • 十分な抽象化があれば、シェル内の他のビューに変更をプッシュするのと同じくらい簡単に、他のマシンで実行されている他のプロセスに変更をプッシュできます。

お役に立てれば; 必要に応じて、さらに説明を提供できます。

于 2012-05-04T08:34:03.533 に答える
7

コントローラーを MVVM ミックス (MVCVM?) に追加して、更新パターンを簡素化することをお勧めします。

コントローラーは、より高いレベルで変更をリッスンし、モデルとビューモデルの間で変更を伝達します。

物事をきれいに保つための基本的なルールは次のとおりです。

  • ViewModel は、特定の形状のデータを保持する単なるダム コンテナーです。彼らは、データがどこから来たのか、どこに表示されているのかを知りません。
  • ビューは、(ビュー モデルへのバインディングを介して) 特定の形式のデータを表示します。 彼らはデータがどこから来たのかを知りません。それを表示する方法だけです。
  • モデルは実際のデータを提供します。彼らはそれがどこで消費されるかを知りません。
  • コントローラーはロジックを実装します。VM で ICommands のコードを提供する、データの変更をリッスンするなどのことです。モデルから VM を設定します。VM の変更をリッスンしてモデルを更新することは理にかなっています。

別の回答で述べたようにDataContext、モデルではなく、VM (またはそのプロパティ) である必要があります。DataModel を指すと、懸念事項を分離するのが難しくなります (たとえば、テスト駆動開発の場合)。

他のほとんどのソリューションは、「正しくない」ViewModels にロジックを配置していますが、コントローラーの利点は常に見過ごされています。そのMVVMの頭字語をくそ!:)

于 2012-05-04T08:52:28.203 に答える
1

モデルをビューに直接バインドすることは、モデルが INotifyPropertyChanged インターフェイスを実装している場合にのみ機能します。(例: Entity Framework によって生成されたモデル)

モデル実装 INotifyPropertyChanged

あなたはこれを行うことができます。

public interface IModel : INotifyPropertyChanged //just sample model
{
    public string Title { get; set; }
}

public class ViewModel : NotificationObject //prism's ViewModel
{
    private IModel model;

    //construct
    public ViewModel(IModel model)
    {
        this.model = model;
        this.model.PropertyChanged += new PropertyChangedEventHandler(model_PropertyChanged);
    }

    private void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Title")
        {
            //Do something if model has changed by external service.
            RaisePropertyChanged(e.PropertyName);
        }
    }
    //....more properties
}

ViewModel を DTO として

Model が INotifyPropertyChanged を実装している場合 (場合によって異なります)、ほとんどの場合、それを DataContext として使用できます。しかし、DDD では、ほとんどの MVVM モデルは、実際のドメインのモデルではなく、EntityObject と見なされます。

より効率的な方法は、ViewModel を DTO として使用することです

//Option 1.ViewModel act as DTO / expose some Model's property and responsible for UI logic.
public string Title
{
    get 
    {
        // some getter logic
        return string.Format("{0}", this.model.Title); 
    }
    set
    {
        // if(Validate(value)) add some setter logic
        this.model.Title = value;
        RaisePropertyChanged(() => Title);
    }
}

//Option 2.expose the Model (have self validation and implement INotifyPropertyChanged).
public IModel Model
{
    get { return this.model; }
    set
    {
        this.model = value;
        RaisePropertyChanged(() => Model);
    }
}

上記のViewModelのプロパティは両方ともバインディングに使用できますが、MVVMパターン(パターン!=ルール)を壊すことはありません。

もう1つ..ViewModelはModelに依存しています。モデルが外部サービス/環境によって変更できる場合。物事を複雑にしているのは「グローバルな状態」です。

于 2012-05-04T06:49:09.317 に答える
0

この問題が発生する理由は、モデルが汚れているかどうかをモデルが認識していないためです。

string Title {
  set {
    this._title = value;
    this._isDirty = true; // ??!!
  }
}}

解決策は、別の方法でサーバーの変更をコピーすることです。

public void CopyFromServer(Model serverCopy)
{
  this._title = serverCopy.Title;
}
于 2012-05-04T05:52:35.940 に答える
0

サーバーからの変更がすぐに再保存されることが唯一の問題である場合は、次のようなことをしてみませんか。

//WARNING: typed in SO window
public class ViewModel
{
    private string _title;
    public string Title
    {
        get { return _title; }
        set
        {
            if (value != _title) 
            {
                _title = value;
                this.OnPropertyChanged("Title");
                this.BeginSaveToServer();
            }
        }
    }

    public void UpdateTitleFromServer(string newTitle)
    {
        _title = newTitle;
        this.OnPropertyChanged("Title"); //alert the view of the change
    }
}

このコードは、プロパティ セッターを経由せずにサーバーからのプロパティ変更のビューを手動で警告するため、「サーバーに保存」コードを呼び出す必要はありません。

于 2012-05-03T18:29:34.653 に答える