現在、Microsoft MVVM テンプレートを使用していますが、詳細な例が不足していることに不満を感じています。付属の ContactBook の例にはコマンド処理がほとんど示されておらず、他に見つけた例は MSDN マガジンの記事のみです。この記事では概念は似ていますが、わずかに異なるアプローチが使用されており、複雑さがまったくありません。少なくとも基本的な CRUD 操作とダイアログ/コンテンツの切り替えを示す適切な MVVM の例はありますか?
皆さんの提案は本当に役立ちました。良いリソースのリストを作成し始めます。
フレームワーク/テンプレート
役に立つ記事
- モデル・ビュー・ビューモデル設計パターンを使用した WPF アプリ
- .NET 3.5 でのデータ検証
- ViewModel を使用して意味のある検証エラー メッセージを提供する
- アクションベースのViewModelとモデルの検証
- ダイアログ
- MVVM のコマンドバインディング
- WPF 向けの MVC 以上のもの
- MVVM + メディエーターのサンプルアプリケーション
スクリーンキャスト
追加ライブラリ
- WPF Disciples の改良された Mediator パターン実装(より複雑なナビゲーションを持つアプリケーションにはこれを強くお勧めします)
- MVVM ライト ツールキット メッセンジャー
ベストアンサー1
残念ながら、あらゆることを実行する優れた MVVM サンプル アプリは存在せず、さまざまなアプローチが存在します。まず、アプリ フレームワークの 1 つ (Prism は適切な選択肢です) に慣れておくことをお勧めします。これらのフレームワークには、依存性注入、コマンド、イベント集約などの便利なツールが用意されており、自分に合ったさまざまなパターンを簡単に試すことができます。
プリズムリリース:
http://www.codeplex.com/CompositeWPF
かなり良いサンプル アプリ (株式トレーダー) と、多数の小さなサンプルおよびハウツーが含まれています。少なくとも、MVVM を実際に動作させるために人々が使用するいくつかの一般的なサブパターンの良いデモンストレーションです。CRUD とダイアログの両方のサンプルがあると思います。
Prism は必ずしもすべてのプロジェクトに適しているわけではありませんが、慣れておくと良いでしょう。
CRUD:この部分は非常に簡単です。WPF の双方向バインディングにより、ほとんどのデータの編集が非常に簡単になります。本当の秘訣は、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 をモデルのプロパティに直接バインドできるようになります。
Item
ビューは次のようになります (ViewModel には、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 が基本クラスから継承されているかどうかを確認し、継承されている場合は、関連する閉じるイベントをサブスクライブします (ハンドラーがダイアログの結果を割り当てます)。代替のユニバーサル閉じる機能 (たとえば、X ボタン) を提供する場合は、関連する閉じるコマンドを ViewModel でも実行する必要があります。
ViewModel のデータ テンプレートを提供する必要がある場合もありますが、各ダイアログのビューが個別のコントロールにカプセル化されている可能性が高いため、データ テンプレートは非常にシンプルになります。ViewModel の既定のデータ テンプレートは次のようになります。
<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
<views:AddressEditView DataContext="{Binding}" />
</DataTemplate>
ダイアログ ビューはこれらにアクセスできる必要があります。そうしないと、ViewModel を表示する方法がわかりません。共有ダイアログ UI を除いて、その内容は基本的に次のようになります。
<ContentControl Content="{Binding}" />
暗黙的なデータ テンプレートはビューをモデルにマップしますが、誰がそれを起動するのでしょうか?
これはあまり mvvm 的ではない部分です。これを行う 1 つの方法は、グローバル イベントを使用することです。私が思うに、それよりも良い方法は、依存性注入によって提供されるイベント アグリゲータ タイプのセットアップを使用することです。この方法では、イベントはアプリケーション全体ではなく、コンテナーに対してグローバルになります。Prism はコンテナー セマンティクスと依存性注入に Unity フレームワークを使用しており、全体的に私は Unity がとても気に入っています。
通常、ルート ウィンドウがこのイベントをサブスクライブするのは理にかなっています。ルート ウィンドウはダイアログを開き、発生したイベントで渡される ViewModel にデータ コンテキストを設定できます。
このように設定すると、ViewModel はアプリケーションにダイアログを開くように要求し、UI について何も知らなくてもそこでユーザーのアクションに応答できるため、大部分で 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
の return プロパティを使用しShowDialog()
たり、ダイアログが閉じられるまでスレッドがブロックされることは想定していません。非標準のモーダル ダイアログは常にそのように動作するとは限らず、複合環境では、イベント ハンドラーがそのようにブロックされることは望ましくないことがよくあります。私は ViewModel にこれを処理させるようにしています。ViewModel の作成者は、関連するイベントをサブスクライブしたり、コミット/キャンセル メソッドを設定したりできるため、この UI メカニズムに依存する必要はありません。
したがって、このフローの代わりに:
// 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
私のダイアログのほとんどは非ブロッキング疑似モーダル コントロールであり、この方法の方が回避策よりも簡単なので、この方法を好みます。単体テストも簡単です。