MVVMに適した、Windowをnewする方法は、大きく分けて、次の3種類になるでしょう。
- Viewのコードビハインド。
- DIの、Serviceで処理する。
- フレームワークで処理する。
1と2は、次のコードを参照して下さい。
1. Viewのコードビハインド。
var view = new MainView(viewModel);
ただし、これは、Viewのコードビハインドでのみ利用します。
MainViewを、 Model, ViewModelで参照するのは、MVVMに適しません。
これを解消するには、例えば、Model, ViewModelから、Viewのメソッド(new Windowする)を呼び出すAction(実体は、delegateと同じ) を利用します。
留意点:Actionは、メモリーリークの原因になりやすいので、利用しなくなった時はnullにします。
MainViewModel
public Action? ShowWindowAction { get; set; }
public void ShowWindow()
{
ShowWindowAction?.Invoke();
}
MainView
public MainView(MainViewModel viewModel)
{
InitializeComponent();
vm = viewModel;
DataContext = vm;
vm.ShowWindowAction ??= new Action(ShowWindow);
}
/// <summary>
/// Actionから呼び出される。
/// </summary>
public void ShowWindow()
{
var model = new MainModel();
var viewModel = new MainViewModel(model);
var view = new MainView(viewModel);
view.Show();
}
2. DIの、Serviceで処理する。
WindowService
を、Dependency Injection(DI・依存性の注入・依存オブジェクト注入)で作成します。
そして、次のどちらかを利用します。
-
public void ShowMainView()
のように、メソッド内で特定のウインドウを表示する。 - ジェネリックメソッド
サービス化の方法は、次と同じです。
MVVMに適した方法で、DIでMessageBoxをサービスにし、テストもする。
特定のウインドウを表示するメソッド
public class WindowService : IWindowService
{
/// <summary>
/// Model, ViewModel, Viewの、いずれのレイヤーから呼び出し可能。
/// </summary>
public void ShowMainView()
{
var model = new MainModel();
var viewModel = new MainViewModel(model);
var view = new MainView(viewModel);
view.Show();
}
ジェネリックメソッド
ジェネリックメソッドの場合は、どのWindowを表示するかを、なんらかの形で呼び出す側が指定する必要があります。
例1
public void ShowWindow<T>(object dataContext) where T : Window, new()
{
T window = new T();
window.DataContext = dataContext;
window.Show();
}
var model = new SubModel();
var viewModel = new SubViewModel(model);
//ここに、SubViewへの参照があるので、MVVMに適した形にするには、Viewのレイヤーで処理する。
_windowService.ShowWindow<SubView>(viewModel);
- この例では、サービスを呼び出す時に、SubViewを指定しているので、Viewのコードビハインドで実行しないと、MVVMに適しません。
ViewModel, Model上で呼び出して、MVVMを満たすようにするには、Windowを文字列で指定し、サービス側で、指定したWindowの型を生成します。例えば、Prismでは、文字列で指定します。 -
where T : new()
制約で、「パラメーターなしのパブリック コンストラクター」が必要になります。
コンパイラ エラー CS0310
参考:
例2
MVVMでViewModelから別ウィンドウ表示をするサンプル~シンプルなTODOリスト~
3. フレームワークで処理する。
新規ウインドウを表示する機能をもつフレームワークを利用します。
Prism
PrismのIDialogServiceは、Viewの名前の文字列で、どのWindowを表示するか指定できます。
App.xaml.cs
containerRegistry.RegisterDialog<NotificationDialog, NotificationDialogViewModel>();
で登録
MainWindowViewModel.cs
_dialogService.ShowDialog("NotificationDialog", new DialogParameters($"message={message}")
"NotificationDialog" の文字列で指定
ちなみに、Prismは、Ver 9からライセンスが変更(有料版が登場)になっていますね。
フレームワーク「KAMISHIBAI」
WPF用Generic Host対応MVVMフレームワーク「KAMISHIBAI」
var builder = KamishibaiApplication<App, MainWindow>.CreateBuilder();
builder.Services.AddPresentation<SubView, SubViewModel>();
で、ViewとViewModelを登録
_presentationService.OpenWindowAsync<SubViewModel>();
で、表示
どれが良いか?
いずれもMVVMの要件は満たすので、プロジェクトに応じてどれを採用するか判断すれば良いでしょう。
「1. Viewのコードビハインド」
- ある程度の規模のアプリで、多数の種類のウインドウがあるなら、コードを書くのが辛くなります。
例えば、Actionを使うと、処理の流れが複雑になり、デバッグ時にコードを追うのが大変です。
「2. DIの、Serviceで処理する。」
- DIの理解を必要としますが、DIのメリットは大きいです。
「3. フレームワークで処理する。」
- 新規ウインドウ表示以外にも便利な機能が多く、それらのメリットも大きいです。
- 保守が必要なアプリの場合、フレームワークに変更があれば、それに対応する必要があります。破壊的変更があると、変更に相当時間がかかります。