web-dev-qa-db-ja.com

WPFのMVVM-モデルの変更をViewModelに警告する方法...または私はどうすればよいですか?

主に this および this のMVVMの記事をいくつか読んでいます。

私の特定の質問は次のとおりです:モデルの変更をモデルからViewModelに伝えるにはどうすればよいですか?

Joshの記事では、彼がこれを行うことはわかりません。 ViewModelは常にモデルにプロパティを要求します。 Rachelの例では、モデルにINotifyPropertyChangedを実装させ、モデルからイベントを発生させますが、それらはビュー自体が消費するためのものです(これを行う理由の詳細については、彼女の記事/コードを参照)。

モデルがViewModelにモデルプロパティの変更を警告する例はどこにもありません。これは、おそらく何らかの理由で行われていないのではないかと心配しています。 ViewModelにモデルの変更を警告するパターンはありますか?(1)おそらく1つ以上のViewModelが各モデル、および(2)ViewModelが1つだけの場合でも、モデルに対する何らかのアクションにより、他のプロパティが変更される可能性があります。

「どうしてそんなことをしたいのか」という形式の回答/コメントがあるのではないかと思います。コメントなので、これが私のプログラムの説明です。私はMVVMが初めてなので、おそらく私の設計全体に欠陥があります。簡単に説明します。

「Customer」または「Product」クラスよりも(少なくとも、私にとっては)より興味深いものをプログラミングしています。 BlackJackをプログラミングしています。

コードビハインドがなく、ViewModelのプロパティとコマンドへのバインドに依存しているビューがあります(Josh Smithの記事を参照)。

良くも悪くも、モデルにはPlayingCardDeckなどのクラスだけでなく、ゲーム全体の状態を保持するBlackJackGameクラスも含める必要があるという態度を取りました。プレーヤーがバストになったとき、ディーラーはカードを引く必要があり、プレーヤーとディーラーの現在のスコアは何ですか(21、21、バストなど)。

BlackJackGameから「DrawCard」などのメソッドを公開し、カードが描かれたときに、CardScoreIsBustなどのプロパティを更新し、これらの新しい値をViewModel。おそらくそれは間違った考えですか?

ViewModelがDrawCard()メソッドを呼び出したという態度を取ることができるので、更新されたスコアを要求し、バストかどうかを調べる必要があります。ご意見?

ViewModelには、トランプの実際の画像(スーツ、ランクに基づいて)を取得し、ビューで使用できるようにするロジックがあります。モデルはこれに関係するべきではありません(おそらく、他のViewModelはカード画像を再生する代わりに数字を使用するだけでしょう)。もちろん、モデルにはBlackJackゲームの概念さえ持たせてはならず、それをViewModelで処理すべきだと言う人がいるかもしれません。

99
Dave

モデルがViewModelに変更を警告するようにしたい場合は、 INotifyPropertyChanged を実装する必要があり、ViewModelはPropertyChange通知を受信するようにサブスクライブする必要があります。

コードは次のようになります。

// Attach EventHandler
PlayerModel.PropertyChanged += PlayerModel_PropertyChanged;

...

// When property gets changed in the Model, raise the PropertyChanged 
// event of the ViewModel copy of the property
PlayerModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "SomeProperty")
        RaisePropertyChanged("ViewModelCopyOfSomeProperty");
}

しかし通常、これは、複数のオブジェクトがモデルのデータを変更する場合にのみ必要です。通常はそうではありません。

PropertyChangedイベントをアタッチするModelプロパティへの参照が実際にない場合は、PrismのEventAggregatorやMVVM LightのMessengerなどのメッセージングシステムを使用できます。 。

私のブログには メッセージングシステムの概要 がありますが、要約すると、すべてのオブジェクトがメッセージをブロードキャストでき、すべてのオブジェクトが特定のメッセージを聞くためにサブスクライブできます。したがって、あるオブジェクトからPlayerScoreHasChangedMessageをブロードキャストすると、別のオブジェクトがこれらのタイプのメッセージをリッスンするようにサブスクライブし、PlayerScoreプロパティを聞いたときにそれを更新できます。

しかし、これはあなたが説明したシステムには必要ないと思います。

理想的なMVVMの世界では、アプリケーションはViewModelで構成され、モデルはアプリケーションの構築に使用されるブロックにすぎません。通常、データのみが含まれているため、DrawCard()(ViewModel内にある)などのメソッドはありません。

したがって、おそらく次のような単純なModelデータオブジェクトがあります。

class CardModel
{
    int Score;
    SuitEnum Suit;
    CardEnum CardValue;
}

class PlayerModel 
{
    ObservableCollection<Card> FaceUpCards;
    ObservableCollection<Card> FaceDownCards;
    int CurrentScore;

    bool IsBust
    {
        get
        {
            return Score > 21;
        }
    }
}

そして、あなたは次のようなViewModelオブジェクトを持っているでしょう

public class GameViewModel
{
    ObservableCollection<CardModel> Deck;
    PlayerModel Dealer;
    PlayerModel Player;

    ICommand DrawCardCommand;

    void DrawCard(Player currentPlayer)
    {
        var nextCard = Deck.First();
        currentPlayer.FaceUpCards.Add(nextCard);

        if (currentPlayer.IsBust)
            // Process next player turn

        Deck.Remove(nextCard);
    }
}

(上記のオブジェクトはすべてINotifyPropertyChangedを実装する必要がありますが、簡単にするために省略しました)

58
Rachel

簡単な答え:詳細に依存します。

あなたの例では、モデルは「独自に」更新されており、これらの変更はもちろん何らかの形でビューに伝播する必要があります。ビューは直接ビューモデルにしかアクセスできないため、モデルはこれらの変更を対応するビューモデルに伝える必要があります。そうするための確立されたメカニズムは、もちろんINotifyPropertyChangedです。つまり、次のようなワークフローが得られます。

  1. ビューモデルが作成され、モデルをラップします
  2. ViewmodelはモデルのPropertyChangedイベントにサブスクライブします
  3. ViewmodelはビューのDataContextとして設定され、プロパティはバインドされますなど
  4. ビューモデルのビュートリガーアクション
  5. Viewmodelはモデルのメソッドを呼び出します
  6. モデル自体の更新
  7. ViewmodelはモデルのPropertyChangedを処理し、応答で独自のPropertyChangedを発生させます
  8. ビューはバインディングの変更を反映し、フィードバックループを閉じます

一方、モデルにビジネスロジックがほとんど含まれていない(またはまったく含まれていない)場合、または他の理由(トランザクション機能を取得するなど)で各ビューモデルがラップされたモデルを「所有」することにした場合、モデルに対するすべての変更はパススルーされますそのような配置が必要ないようにビューモデル。

このような設計については、別のMVVMの質問 ここ で説明します。

21
Jon

あなたの選択:

  • INotifyPropertyChangedを実装する
  • イベント
  • プロキシマニピュレーターを使用するPOCO

ご覧のとおり、INotifyPropertyChangedは.Netの基本的な部分です。つまり、System.dllにあります。 「モデル」に実装することは、イベント構造を実装することに似ています。

純粋なPOCOが必要な場合は、プロキシ/サービスを介してオブジェクトを効果的に操作する必要があり、プロキシをリッスンすることでViewModelに変更が通知されます。

個人的にはINotifyPropertyChangedを大まかに実装してから FODY を使用して汚い仕事をしています。見た目も感じもPOCOです。

例(ILにFODYを使用してPropertyChangedレイザーを織り込む):

public class NearlyPOCO: INotifyPropertyChanged
{
     public string ValueA {get;set;}
     public string ValueB {get;set;}

     public event PropertyChangedEventHandler PropertyChanged;
}

次に、ViewModelにPropertyChangedの変更をリッスンさせることができます。またはプロパティ固有の変更。

INotifyPropertyChangedルートの美しさは、 Extended ObservableCollection でチェーン化されていることです。そのため、近くのpocoオブジェクトをコレクションにダンプし、コレクションを聴きます...どこかで何かが変わった場合、それについて学びます。

正直に言うと、これは「コンパイラによってINotifyPropertyChangedが自動的に処理されなかった理由」の議論に参加できます。議論は次のとおりです。つまり、デフォルトでINotifyPropertyChangedを実装します。しかし、そうではなく、最も少ない労力で最適なルートはIL Weavingを使用することです(具体的には FODY )。

3
Meirion Hughes

かなり古いスレッドですが、多くの検索をした後、私は自分の解決策を思いつきました:A PropertyChangedProxy

このクラスを使用すると、他の誰かのNotifyPropertyChangedに簡単に登録し、登録されたプロパティに対して起動された場合に適切なアクションを実行できます。

これは、独自に変更できるモデルプロパティ「ステータス」があり、ビューにも通知されるように、「ステータス」プロパティで独自のPropertyChangedを起動するように自動的にViewModelに通知する必要がある場合の例です。 )

public class MyModel : INotifyPropertyChanged
{
    private string _status;
    public string Status
    {
        get { return _status; }
        set { _status = value; OnPropertyChanged(); }
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class MyViewModel : INotifyPropertyChanged
{
    public string Status
    {
        get { return _model.Status; }
    }

    private PropertyChangedProxy<MyModel, string> _statusPropertyChangedProxy;
    private MyModel _model;
    public MyViewModel(MyModel model)
    {
        _model = model;
        _statusPropertyChangedProxy = new PropertyChangedProxy<MyModel, string>(
            _model, myModel => myModel.Status, s => OnPropertyChanged("Status")
        );
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

ここにクラス自体があります:

/// <summary>
/// Proxy class to easily take actions when a specific property in the "source" changed
/// </summary>
/// Last updated: 20.01.2015
/// <typeparam name="TSource">Type of the source</typeparam>
/// <typeparam name="TPropType">Type of the property</typeparam>
public class PropertyChangedProxy<TSource, TPropType> where TSource : INotifyPropertyChanged
{
    private readonly Func<TSource, TPropType> _getValueFunc;
    private readonly TSource _source;
    private readonly Action<TPropType> _onPropertyChanged;
    private readonly string _modelPropertyname;

    /// <summary>
    /// Constructor for a property changed proxy
    /// </summary>
    /// <param name="source">The source object to listen for property changes</param>
    /// <param name="selectorExpression">Expression to the property of the source</param>
    /// <param name="onPropertyChanged">Action to take when a property changed was fired</param>
    public PropertyChangedProxy(TSource source, Expression<Func<TSource, TPropType>> selectorExpression, Action<TPropType> onPropertyChanged)
    {
        _source = source;
        _onPropertyChanged = onPropertyChanged;
        // Property "getter" to get the value
        _getValueFunc = selectorExpression.Compile();
        // Name of the property
        var body = (MemberExpression)selectorExpression.Body;
        _modelPropertyname = body.Member.Name;
        // Changed event
        _source.PropertyChanged += SourcePropertyChanged;
    }

    private void SourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _modelPropertyname)
        {
            _onPropertyChanged(_getValueFunc(_source));
        }
    }
}
3
Roemer

この記事は役に立ちました: http://social.msdn.Microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of- the-?forum = wpf

私の要約:

MVVM組織の背後にある考え方は、ビューとモデルの再利用を容易にし、分離したテストを可能にすることです。ビューモデルはビューエンティティを表すモデルであり、モデルはビジネスエンティティを表します。

後でポーカーゲームを作りたい場合はどうしますか? UIの多くは再利用可能でなければなりません。ゲームロジックがビューモデルにバインドされている場合、ビューモデルを再プログラミングせずにこれらの要素を再利用することは非常に困難です。ユーザーインターフェイスを変更する場合はどうしますか?ゲームロジックがビューモデルロジックに結合されている場合、ゲームがまだ動作することを再確認する必要があります。デスクトップとWebアプリを作成したい場合はどうしますか?ビューモデルにゲームロジックが含まれている場合、アプリケーションロジックは必然的にビューモデルのビジネスロジックと結び付けられるため、これら2つのアプリケーションを並べて維持しようとすると複雑になります。

データ変更通知とデータ検証は、すべてのレイヤー(ビュー、ビューモデル、モデル)で行われます。

モデルには、データ表現(エンティティ)とそれらのエンティティに固有のビジネスロジックが含まれます。カードのデッキは、固有のプロパティを持つ論理的な「もの」です。良いデッキには、重複したカードを入れることはできません。トップカードを取得する方法を公開する必要があります。残っているよりも多くのカードを配らないことを知る必要があります。このようなデッキの動作は、カードのデッキに固有であるため、モデルの一部です。ディーラーモデル、プレーヤーモデル、ハンドモデルなどもあります。これらのモデルは相互作用が可能であり、相互作用します。

ビューモデルは、プレゼンテーションとアプリケーションロジックで構成されます。ゲームの表示に関連するすべての作業は、ゲームのロジックとは別個のものです。これには、画像としての手の表示、ディーラーモデルへのカードのリクエスト、ユーザーの表示設定などが含まれます。

記事の本質:

基本的に、これを説明したいのは、ビジネスロジックとエンティティがモデルを構成するということです。これは特定のアプリケーションが使用しているものですが、多くのアプリケーションで共有できます。

ビューはプレゼンテーション層です-実際にユーザーと直接やり取りすることに関連するもの。

ViewModelは基本的に、2つをリンクするアプリケーションに固有の「接着剤」です。

ここに、それらがどのようにインターフェースするかを示す素敵な図があります:

http://reedcopsey.com/2010/01/06/better-user-and-developer-experiences-from-windows-forms-to-wpf-with-mvvm-part-7-mvvm/ =

あなたの場合-詳細のいくつかに取り組むことができます...

検証:通常、これには2つの形式があります。ユーザー入力に関連する検証は、ViewModel(主に)およびビューで行われます(つまり、テキストの入力を妨げる「数値」TextBoxは、ビューなどで処理されます)。そのため、ユーザーからの入力の検証は通常VMの懸念事項です。とはいえ、多くの場合、検証の2番目の「レイヤー」があります。これは、使用されているデータがビジネスルールと一致することの検証です。多くの場合、これはモデル自体の一部です。データをモデルにプッシュすると、検証エラーが発生する場合があります。 VMは、この情報をビューにマップし直す必要があります。

「DBへの書き込み、電子メールの送信など、ビューのない舞台裏」の操作:これは実際に私の図の「ドメイン固有の操作」の一部であり、実際には純粋にモデルの一部です。これは、アプリケーションを介して公開しようとしているものです。 ViewModelはこの情報を公開するブリッジとして機能しますが、操作は純粋なモデルです。

ViewModelの操作:ViewModelにはINPC以外にも必要なものがあります。設定やユーザー状態の保存など、アプリケーション(ビジネスロジックではない)に固有の操作も必要です。これにより、アプリが異なります。同じ「モデル」をインターフェースする場合でも、アプリによって。

それについて考える良い方法-注文システムの2つのバージョンを作りたいとしましょう。 1つ目はWPFにあり、2つ目はWebインターフェイスです。

注文自体を処理する共有ロジック(電子メールの送信、DBへの入力など)はモデルです。アプリケーションはこれらの操作とデータをユーザーに公開していますが、2つの方法で実行しています。

WPFアプリケーションでは、ユーザーインターフェイス(ビューアーが操作するもの)は「ビュー」です。Webアプリケーションでは、これは基本的にクライアントで(少なくとも最終的に)javascript + html + cssに変換されるコードです。

ViewModelは、使用している特定のビューテクノロジー/レイヤーで動作させるためにモデルを調整するために必要な残りの「接着剤」です(順序付けに関連するこれらの操作)。

2
VoteCoffee

INotifyPropertyChangedおよびINotifyCollectionChangedに基づく通知は、まさに必要なものです。プロパティ変更のサブスクリプション、プロパティ名のコンパイル時検証、メモリリークを回避して生活を簡素化するには、PropertyObserverfrom-を使用することをお勧めします Josh SmithのMVVM Foundation 。このプロジェクトはオープンソースなので、ソースからプロジェクトにそのクラスだけを追加できます。

PropertyObserverの使用方法を理解するには、 この記事 を読んでください。

また、 Reactive Extensions(Rx) を詳しく見てください。モデルからIObserver <T>を公開し、ビューモデルでサブスクライブできます。

2

Model内にINotifyPropertyChangedを実装し、ViewModel内でリッスンすることは何も問題はありません。実際、XAMLでモデルのプロパティにドットを挿入することさえできます:{Binding Model.ModelProperty}

依存/計算された読み取り専用プロパティについては、これまでのところ、これより優れたシンプルなものはありません: https://github.com/StephenCleary/CalculatedProperties 。非常にシンプルですが非常に便利で、本当に「MVVMのExcel数式」です。Excelが数式セルに変更を伝播するのと同じように機能します。

1
KolA

みんなはこれに答えて素晴らしい仕事をしましたが、このような状況ではMVVMパターンが苦痛だと感じているので、Supervising ControllerまたはPassive Viewアプローチを使用して、少なくともモデルオブジェクトのバインディングシステム独自に変更を生成します。

1
Ibrahim Najjar

変更の流れでわかるように、私は長い間、方向性モデル->モデルの表示->変更の流れの表示を提唱してきました私の MVVM記事 2008年からのセクション。これには、モデルにINotifyPropertyChangedを実装する必要があります。私が知る限り、それはそれ以来一般的な習慣になっています。

ジョシュ・スミスに言及したので、 彼のPropertyChangedクラス を見てください。これは、モデルのINotifyPropertyChanged.PropertyChangedイベントにサブスクライブするためのヘルパークラスです。

my PropertiesUpdater class を作成することで最近私が持っているように、実際にこのアプローチをさらに進めることができます。ビューモデルのプロパティは、モデルの1つ以上のプロパティを含む複雑な式として計算されます。

1
HappyNomad

ビューモデルがサブスクライブする必要があるモデルからイベントを発生させることができます。

たとえば、最近ツリービューを生成する必要があるプロジェクトに取り組みました(当然、モデルには階層的な性質がありました)。モデルには、ChildElementsというobservablecollectionがありました。

ビューモデルでは、モデル内のオブジェクトへの参照を保存し、observablecollectionのCollectionChangedイベントにサブスクライブしていました。たとえば、ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)...

その後、モデルに変更が発生すると、ビューモデルに自動的に通知されます。 PropertyChangedを使用して同じ概念に従うことができますが、それを機能させるには、モデルからプロパティ変更イベントを明示的に発生させる必要があります。

0
Mash

これは本当に重要な質問のように思えます-それをするプレッシャーがなくても。 TreeViewを含むテストプロジェクトに取り組んでいます。削除などのコマンドにマップされるメニュー項目などがあります。現在、ビューモデル内からモデルとビューモデルの両方を更新しています。

例えば、

public void DeleteItemExecute ()
{
    DesignObjectViewModel node = this.SelectedNode;    // Action is on selected item
    DocStructureManagement.DeleteNode(node.DesignObject); // Remove from application
    node.Remove();                                // Remove from view model
    Controller.UpdateDocument();                  // Signal document has changed
}

これは簡単ですが、非常に基本的な欠陥があるようです。典型的な単体テストはコマンドを実行し、ビューモデルで結果を確認します。しかし、2つが同時に更新されるため、モデルの更新が正しいことをテストしません。

そのため、おそらくPropertyObserverなどの手法を使用して、モデルの更新でモデルの更新をトリガーする方が適切です。同じユニットテストは、両方のアクションが成功した場合にのみ機能するようになりました。

これは潜在的な答えではない、と私は理解しているが、そこに出す価値はあるようだ。

0
Art