引き続きMicrosoft.TeamFoundation.MVVM
名前空間を使って作る、WPFアプリの話です。
前回はMessageBoxServiceを使ってMessageBoxの表示ができ、コードが簡潔になりました。
ただ、Windowを表示して画面遷移するための定型コードがコードビハインドやその他の部分にまだ散らばっているため、これも何とかしたくなりました。
そこで、Microsoft.TeamFoundation.MVVM.ViewModelBase
のプロパティを見ると、WindowDisplayService
というgetアクセサのみのプロパティがあり、これが使えそうでした。
ViewModelBase.WindowDisplayService プロパティ (Microsoft.TeamFoundation.MVVM) - MSDN
ただ、MSDNなどでは使い方の情報がなかったため、DataContextChangedイベントハンドラーと比較したメモを残しておきます。
2015/12/10 追記 ここから
公式Blogに以下の記事が掲載されましたので、Microsoft.TeamFoundation.MVVM
の使用前に記事を確認してみてください。
Microsoft.TeamFoundation.MVVM 名前空間の利用について - Visual Studio サポート チーム blog - Site Home - MSDN Blogs
2015/12/10 追記 ここまで
環境
- Windows7
- .NET Framework 4.5
- 作るもの:親Windowでボタンを押したら、子Windowを表示する
Windowの表示 (DataContextChangedイベントハンドラー編)
親Window
View
XAMLにはボタン要素だけを定義します。
<Grid> <Button Margin="10" Width="100" Height="100" Content="ShowDialog" Command="{Binding Path=ShowDialogCommand}"></Button> </Grid>
ViewModel
ButtonのCommandをデータバインディングして、RelayCommandでExecuteShowDialogCommandメソッドを呼びます。
ExecuteShowDialogCommandメソッドでは、子WindowのViewModelにあるShowDialogメソッドを呼びます。
private void ExecuteShowDialogCommand(object x) { var r = DataContextChangedChildViewModel.ShowDialog(); }
子Window
View
ViewのXAMLでは、文字を表示するためのTextBlockだけを置いておきます。
Viewのコードビハインドでは、DataContextChangedイベントハンドラーで、ViewのShowDialogメソッドとViewModelのFuncデリゲートであるShowDialogBoxを紐付けます。
DataContextChanged += (sender, e) =>
{
var x = DataContext as DataContextChangedChildViewModel;
x.ShowDialogBox = () => ShowDialog();
};
ViewModel
親Windowから呼ばれるShowDialogメソッドをstaticなメソッドとして用意します。
ShowDialogメソッドではViewとViewModelを生成し、Funcデリゲートを介してViewのShowDialogメソッドを使って、Windowを表示します。
public Func<bool?> ShowDialogBox { get; set; } public static bool? ShowDialog() { var vm = new DataContextChangedChildViewModel(); var v = new DataContextChangedChildView(); v.DataContext = vm; return vm.ShowDialogBox(); }
Windowの表示 (WindowDisplayService編)
WindowDisplayServiceの使い方としては、おおまかには
- 親WindowのViewのXAMLに、Resourcesとして子WindowのViewを設定
- 親WindowのViewModelで、WindowDisplayService.ShowDialogメソッドを使って、子WindowのXAMLのResourcesとViewModelを渡す
- 子Windowでは、画面遷移用のコードは不要
となります。
なお、DataContextChangedイベントハンドラーの場合と同様な実装として、子WindowのViewModelにstaticメソッドを用意しWindowDisplayServiceを使うことも試してみましたが、実行時に
ServiceNotFoundExceptionはハンドルされませんでした
Service not found: Microsoft.TeamFoundation.MVVM.IWindowDisplayService.
Make sure that 'mvvm:MVVMSupport.ViewModel="{Binding}"' is in your .xaml file.
という例外が発生しました。
子WindowのXAMLにはmvvm:MVVMSupport.ViewModel="{Binding}"
という設定をしているにも関わらず例外が出てしまったため、情報が少ないこともあり、この方法で実装することは諦めました。
一応、後述のソースコードにはこの例外が発生するパターンも含めてあります。
親Window
View
ViewはXAMLのみで、コードビハインドはありません。
ViewのXAMLは
- Window要素に、
Microsoft.TeamFoundation.MVVM
名前空間と、mvvm:MVVMSupport.ViewModel="{Binding}"
を追加 - Resourcesとして、
<mvvm:RegisterWindow x:Key="ChildWindowKey" Type="local:WindowDisplayServiceChildView" />
のように、リソースのKeyと子WindowのViewの型を設定
となります。
全体は以下の通りとなります。
<Window ... xmlns:local="clr-namespace:ShowWindowMVVM" xmlns:mvvm="clr-namespace:Microsoft.TeamFoundation.MVVM;assembly=Microsoft.TeamFoundation.Controls" mvvm:MVVMSupport.ViewModel="{Binding}" ... <Window.Resources> <mvvm:RegisterWindow x:Key="ChildWindowKey" Type="local:WindowDisplayServiceChildView" /> </Window.Resources> <Grid> <Button Margin="10" Width="100" Height="100" Content="ShowDialog" Command="{Binding Path=ShowDialogCommand}"></Button> </Grid> </Window>
ViewModel
ButtonのCommandをデータバインディングして、RelayCommandでExecuteShowDialogCommandメソッドを呼び、Windowを表示します。
ViewModelのWindowDisplayServiceプロパティにはIWindowDisplayService
を持つオブジェクトが設定されています。そのため、ExecuteShowDialogCommandメソッドでは、そのオブジェクトのShowDialogメソッドを使って子Windowを表示します。
IWindowDisplayService.ShowDialog メソッド (Microsoft.TeamFoundation.MVVM) - MSDN
WindowDisplayService.ShowDialog("ChildWindowKey", new WindowDisplayServiceChildViewModel());
子Window
親Windowで子Windowの表示まで行っているため、画面遷移用のコードは不要です。
感想
DataContextChangedイベントハンドラーと比べてWindowDisplayServiceでは、
- Viewでは、XAMLの記述が増え子WindowのViewの型を指定する必要があるものの、コードビハインドがなくなった
- ViewModelでは、全体のコード量が減った
- ViewやViewModelの生成が不要になった
- Funcデリゲートやstaticなメソッドが不要になった
となり、より簡潔に書けそうです。
また、ここまででMicrosoft.TeamFoundation.MVVM
名前空間を使って、
INotifyDataErrorInfo
と合わせた、単票入力でのエラー検証INotifyDataErrorInfo
と合わせた、グリッド入力でのエラー検証- MessageBoxServiceを使った、MessageBoxの表示
- WindowDisplayServiceを使った、Windowの表示 (今回)
ができたため、名前空間名が気になるものの、手元の環境ではこれで十分な気もしました。
ソースコード
GitHubに上げました(プロジェクト名は「ShowWindowMVVM」)。
CSharp-Sample/MVVMApp/ShowWindowMVVM at master - thinkAmi/CSharp-Sample
スタートアッププロジェクトやApp.xamlのStartupUri
を切り替えれば、それぞれのパターンが試せるかと思います。
なお、上記でうまくできなかった、子WindowのViewModelにstaticメソッドを用意しWindowDisplayServiceを使うパターンについては、WindowDisplayServiceErrorView.xaml
などに書いてあります。
WindowDisplayServiceParentView.xaml
の「ShowErrorDialog」ボタンを押せば再現できるため、解決方法が分かる方はコメントなどをしてくださるとありがたいです。