読者です 読者をやめる 読者になる 読者になる

C# + WPFで、WindowDisplayServiceを使ってWindowを表示して画面遷移する

C# WPF

引き続き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
    • Microsoft.TeamFoundation.MVVM名前空間を使ったMVVMのWPFアプリ
    • ViewModelは、Microsoft.TeamFoundation.MVVM.ViewModelBaseを継承したもの
  • 作るもの:親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名前空間を使って、

ができたため、名前空間名が気になるものの、手元の環境ではこれで十分な気もしました。

 

ソースコード

GitHubに上げました(プロジェクト名は「ShowWindowMVVM」)。
CSharp-Sample/MVVMApp/ShowWindowMVVM at master - thinkAmi/CSharp-Sample

スタートアッププロジェクトやApp.xamlStartupUriを切り替えれば、それぞれのパターンが試せるかと思います。

 
なお、上記でうまくできなかった、子WindowのViewModelにstaticメソッドを用意しWindowDisplayServiceを使うパターンについては、WindowDisplayServiceErrorView.xaml などに書いてあります。

WindowDisplayServiceParentView.xamlの「ShowErrorDialog」ボタンを押せば再現できるため、解決方法が分かる方はコメントなどをしてくださるとありがたいです。