1. ホーム
  2. wpf

[解決済み] MVVMテンプレートの好例

2022-05-03 15:15:24

質問

現在、Microsoft MVVM テンプレートを使用していますが、詳細な例がないことに不満を感じています。 付属のContactBookの例では、Commandの処理はほとんど見られず、私が見つけた唯一の例はMSDN Magazineの記事で、コンセプトは似ていますが、少し異なるアプローチを使用しており、依然として複雑さに欠けています。 少なくとも基本的なCRUD操作とダイアログ/コンテンツ切り替えを示す、まともなMVVMの例はないのでしょうか?


皆さんの提案が本当に役に立ったので、良いリソースのリストを作り始めます

フレームワーク/テンプレート

お役立ち記事

スクリーンキャスト

その他のライブラリ

解決するには?

残念ながら、すべてを実現する優れたMVVMサンプルアプリは存在しませんし、さまざまなアプローチがあります。なぜなら、依存性注入、コマンド実行、イベント集約などの便利なツールが提供されており、自分に合ったさまざまなパターンを簡単に試すことができるからです。

プリズムのリリースです。

http://www.codeplex.com/CompositeWPF

この本には、かなりまともなサンプルアプリ(株式トレーダー)と、たくさんの小さなサンプルやHow toが含まれています。少なくとも、MVVMを実際に動作させるためによく使われるいくつかのサブパターンの良いデモンストレーションになります。CRUDとダイアログの両方の例を持っていると思います。

Prismは必ずしもすべてのプロジェクトに必要なものではありませんが、慣れておくとよいでしょう。

CRUDです。 この部分はとても簡単で、WPFの2ウェイバインディングによって、ほとんどのデータを本当に簡単に編集することができます。本当のトリックは、UIを簡単にセットアップできるようなモデルを提供することです。少なくとも、ViewModel (またはビジネスオブジェクト) が以下の機能を実装していることを確認したいものです。 INotifyPropertyChanged を実装することで、バインディングをサポートし、プロパティをそのままUIコントロールにバインドすることができますが、同時に IDataErrorInfo を使用することで、検証を行うことができます。一般的に、ある種のORMソリューションを使用している場合、CRUDのセットアップは簡単です。

この記事では、簡単なCrud操作のデモンストレーションを行います。 http://dotnetslackers.com/articles/wpf/WPFDataBindingWithLINQ.aspx

これは LinqToSql 上に構築されていますが、この例には関係ありません。重要なのは、ビジネス・オブジェクトに INotifyPropertyChanged (LinqToSqlによって生成されたクラスがそうである)。MVVMはその例のポイントではありませんが、この場合は問題ないと思います。

この記事は、データバリデーションのデモです

http://blogs.msdn.com/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx

繰り返しになりますが、ほとんどのORMソリューションでは、すでに実装されている IDataErrorInfo また、通常、カスタムの検証ルールを簡単に追加するためのメカニズムも提供されています。

ほとんどの場合、ORM で作成したオブジェクト (モデル) を ViewModel でラップし、そのオブジェクトと保存/削除のコマンドを保持すれば、モデルのプロパティに直接 UI をバインドする準備ができます。

ビューは次のようなものになります(ViewModelはプロパティ Item は、ORM で作成されたクラスのように、モデルを保持します)。

<StackPanel>
   <StackPanel DataContext=Item>
      <TextBox Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
      <TextBox Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
   </StackPanel>
   <Button Command="{Binding SaveCommand}" />
   <Button Command="{Binding CancelCommand}" />
</StackPanel>

ダイアログです。 ダイアログとMVVMは少しトリッキーです。私はダイアログでMediatorアプローチの味を使うことを好みます。それについてはこのStackOverflowの質問でもう少し読むことができます。

WPF MVVMダイアログの例

古典的なMVVMとまではいかないが、私のいつものやり方は、次のようにまとめられる。

ダイアログ ViewModel の基本クラスで、コミットやキャンセルのコマンド、ダイアログを閉じる準備ができたことを知らせるイベント、その他すべてのダイアログで必要となるものを公開します。

ダイアログの一般的なビュー - これはウィンドウ、またはカスタム"モーダル"オーバーレイタイプのコントロールにすることができます。例えば、データコンテキストの変更時に、新しい ViewModel がベースクラスから継承されているかどうかを確認し、継承されている場合は、関連する close イベントを購読します (ハンドラはダイアログの結果を割り当てます)。代替の普遍的な閉じる機能 (例えば X ボタン) を提供する場合、関連する閉じるコマンドを ViewModel 上でも実行することを確認する必要があります。

ViewModels のデータテンプレートを提供する必要がある場合もありますが、特に、各ダイアログのビューを個別のコントロールにカプセル化している場合は、非常にシンプルにすることができます。ViewModel のデフォルトのデータテンプレートはこのようなものになります。

<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
   <views:AddressEditView DataContext="{Binding}" />
</DataTemplate>

ダイアログビューはこれらにアクセスする必要があります。そうでなければ ViewModel をどのように表示すればよいのかが分からないからです。

<ContentControl Content="{Binding}" />

暗黙のデータテンプレートは、ビューをモデルにマッピングしますが、誰がそれを起動するのでしょうか?

ここがmvvmらしくないところです。一つの方法として、グローバルイベントを使用することができます。私がより良いと思うのは、依存性注入によって提供されるイベントアグリゲータタイプのセットアップを使用することです。この方法では、イベントはアプリ全体ではなく、コンテナに対してグローバルです。Prismはコンテナセマンティクスと依存性注入にunityフレームワークを使用しており、全体として私はUnityが非常に気に入っています。

通常、ルートウィンドウがこのイベントを購読することは理にかなっています。ダイアログを開き、そのデータコンテキストを、発生したイベントとともに渡される ViewModel に設定することができます。

このように設定することで、UIについて何も知らなくても、ViewModelsがアプリケーションにダイアログを開くように要求し、そこでユーザーのアクションに応答することができるので、ほとんどの場合、MVVMらしさは完全なままです。

しかし、UIがダイアログを立ち上げる必要がある場合もあり、その場合は少し厄介なことになります。例えば、ダイアログの位置が、それを開くボタンの位置に依存する場合を考えてみましょう。この場合、ダイアログを開くように要求するときに、UI固有の情報を持つ必要があります。私は通常、ViewModelといくつかの関連するUI情報を保持する別のクラスを作成します。残念ながら、そこではいくつかのカップリングが避けられないようです。

要素の位置データを必要とするダイアログを発生させるボタンハンドラの擬似コードです。

ButtonClickHandler(sender, args){
    var vm = DataContext as ISomeDialogProvider; // check for null
    var ui_vm = new ViewModelContainer();
    // assign margin, width, or anything else that your custom dialog might require
    ...
    ui_vm.ViewModel = vm.SomeDialogViewModel; // or .GetSomeDialogViewModel()
    // raise the dialog show event
}

ダイアログビューは位置データにバインドし、含まれる ViewModel を内側の ContentControl . ViewModel自身はまだUIについて何も知りません。

一般的に、私は DialogResult のリターンプロパティは ShowDialog() メソッドを呼び出すか、ダイアログが閉じられるまでスレッドがブロックされることを期待します。非標準のモーダルダイアログは、必ずしもそのように動作しませんし、複合環境では、イベントハンドラがそのようにブロックされることを望まないことがよくあります。ViewModel の作成者は、関連するイベントのサブスクライブ、コミット/キャンセル メソッドの設定などを行うことができます。

だから、この流れの代わりに

// in code behind
var result = somedialog.ShowDialog();
if (result == ...

使っています。

// in view model
var vm = new SomeDialogViewModel(); // child view model
vm.CommitAction = delegate { this.DoSomething(vm); } // what happens on commit 
vm.CancelAction = delegate { this.DoNothing(vm); } // what happens on cancel/close (optional)
// raise dialog request event on the container

私のダイアログのほとんどはノンブロッキングの疑似モーダルコントロールで、この方法で行う方が回避策を講じるよりも簡単だと思えるからです。ユニットテストも簡単です。