1. ホーム
  2. c#

[解決済み] WPFのMVVM - ViewModelにモデルの変更を警告する方法...すべき?

2022-08-13 12:11:08

質問

私はMVVMの記事をいくつか見ていますが、主に この この .

具体的な質問ですが モデルの変更をモデルからViewModelに伝えるにはどうしたらよいでしょうか?

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

モデルがモデルプロパティへの変更を ViewModel に警告する例はどこにも見当たりません。 これは、おそらく何らかの理由で行われていないのではと心配しています。 モデルの変更をViewModelに警告するためのパターンはありますか? (1) 各モデルには1つ以上のViewModelがあると考えられるし、(2) ViewModelが1つであっても、モデルに対する何らかのアクションによって他のプロパティが変更されるかもしれないので、必要だと思われる。

なぜそんなことをしたいんだ」というような回答やコメントがあるかと思いますので、ここで私のプログラムの説明をします。 私はMVVMの初心者なので、もしかしたら私の全体の設計に欠陥があるかもしれません。 簡単に説明します。

私は、quot;Customer" や "Product" クラスよりも面白い(少なくとも、私にとって!)ものをプログラミングしています。 私はBlackJackをプログラミングしています。

私は、背後にコードを持たず、ViewModelのプロパティとコマンドへのバインディングにのみ依存するViewを持っています(Josh Smithの記事を参照ください)。

良くも悪くも、私はモデルには以下のようなクラスだけでなく PlayingCard , Deck のみならず BlackJackGame クラスはゲーム全体の状態を保持し、プレイヤーがバストになったとき、ディーラーがカードを引かなければならないとき、そしてプレイヤーとディーラーの現在のスコア(21未満、21、バストなど)を知っています。

から BlackJackGame DrawCard" のようなメソッドを公開していますが、カードが引かれたときに、次のようなプロパティが表示されることを思いつきました。 CardScore とか IsBust は更新され、これらの新しい値が ViewModel に伝達される必要があります。おそらくそれは誤った考えなのでしょうか?

ViewModel が DrawCard() メソッドを呼び出したので、彼は更新されたスコアを要求し、バストであるかどうかを見つけるために知っているはずです。 意見は?

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

どのように解決するのですか?

ViewModelsに変更を通知する場合、Modelsに実装された INotifyPropertyChanged を実装し、ViewModels は 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 イベントをアタッチするために、実際にモデルのプロパティへの参照を持っていないようなケースがあれば、Prism の EventAggregator や MVVM Light の Messenger .

私の場合は メッセージングシステムの簡単な概要 に書いていますが、要約すると、どんなオブジェクトでもメッセージをブロードキャストすることができ、どんなオブジェクトでも特定のメッセージをリスニングするためにサブスクライブすることができる、ということです。つまり、例えば PlayerScoreHasChangedMessage をブロードキャストすると、別のオブジェクトがそのようなメッセージを受信するようにサブスクライブして、そのオブジェクトの PlayerScore プロパティを更新します。

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

理想的なMVVMの世界では、アプリケーションはViewModelsで構成され、Modelはアプリケーションを構築するために使用されるブロックに過ぎません。モデルは通常、データのみを含むので、以下のようなメソッドを持ちません。 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 を実装する必要がありますが、簡略化のため省きました)