MVVMに適した、new Windowの方法 まとめ

MVVMに適した、Windowをnewする方法は、大きく分けて、次の3種類になるでしょう。

1. Viewのコードビハインド。
2. DIの、Serviceで処理する。
3. フレームワークで処理する。

1と2は、次のコードを参照して下さい。

https://github.com/mikihiro-t/ShowWindowStudy

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132059/3535e4bc-8fad-68e0-b6ee-9085fceecb13.png)

## 1. Viewのコードビハインド。

```csharp
var view = new MainView(viewModel);
```
ただし、これは、Viewのコードビハインドでのみ利用します。
MainViewを、 Model, ViewModelで参照するのは、MVVMに適しません。

これを解消するには、例えば、Model, ViewModelから、Viewのメソッド(new Windowする)を呼び出すAction（実体は、delegateと同じ） を利用します。

留意点：Actionは、メモリーリークの原因になりやすいので、利用しなくなった時はnullにします。

##### MainViewModel
```csharp
public Action? ShowWindowAction { get; set; }
    
public void ShowWindow()
{
    ShowWindowAction?.Invoke();
}
```

##### MainView
```csharp
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をサービスにし、テストもする。](https://qiita.com/hiro_t/items/b6ef800050897bd01f1b)

### 特定のウインドウを表示するメソッド
```csharp
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

```csharp
public void ShowWindow<T>(object dataContext) where T : Window, new()
{
    T window = new T();
    window.DataContext = dataContext;
    window.Show();
}
```


```csharp
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](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/compiler-messages/cs0310)

参考：
- [Opening new window in MVVM WPF](https://stackoverflow.com/a/25845857)

##### 例2
[MVVMでViewModelから別ウィンドウ表示をするサンプル～シンプルなTODOリスト～](https://sourcechord.hatenablog.com/entry/2019/06/09/230316)

## 3. フレームワークで処理する。
新規ウインドウを表示する機能をもつフレームワークを利用します。

### Prism
PrismのIDialogServiceは、Viewの名前の文字列で、どのWindowを表示するか指定できます。

##### [App.xaml.cs](https://github.com/PrismLibrary/Prism-Samples-Wpf/blob/master/26-UsingDialogService/UsingDialogService/App.xaml.cs)
```csharp
containerRegistry.RegisterDialog<NotificationDialog, NotificationDialogViewModel>();
```

で登録

##### [MainWindowViewModel.cs](https://github.com/PrismLibrary/Prism-Samples-Wpf/blob/master/26-UsingDialogService/UsingDialogService/ViewModels/MainWindowViewModel.cs)
```csharp
_dialogService.ShowDialog("NotificationDialog", new DialogParameters($"message={message}")
```

"NotificationDialog" の文字列で指定

ちなみに、Prismは、Ver 9からライセンスが変更（有料版が登場）になっていますね。

### フレームワーク「KAMISHIBAI」
[WPF用Generic Host対応MVVMフレームワーク「KAMISHIBAI」](https://zenn.dev/nuits_jp/articles/2022-05-05-release-kamishibai-1_0_0)
```csharp
var builder = KamishibaiApplication<App, MainWindow>.CreateBuilder();
builder.Services.AddPresentation<SubView, SubViewModel>();
```

で、ViewとViewModelを登録

```csharp
_presentationService.OpenWindowAsync<SubViewModel>();
```

で、表示

## どれが良いか？
いずれもMVVMの要件は満たすので、プロジェクトに応じてどれを採用するか判断すれば良いでしょう。

#### 「1. Viewのコードビハインド」
- ある程度の規模のアプリで、多数の種類のウインドウがあるなら、コードを書くのが辛くなります。
例えば、Actionを使うと、処理の流れが複雑になり、デバッグ時にコードを追うのが大変です。
#### 「2. DIの、Serviceで処理する。」
- DIの理解を必要としますが、DIのメリットは大きいです。
#### 「3. フレームワークで処理する。」
- 新規ウインドウ表示以外にも便利な機能が多く、それらのメリットも大きいです。
- 保守が必要なアプリの場合、フレームワークに変更があれば、それに対応する必要があります。破壊的変更があると、変更に相当時間がかかります。