WindowsフォームのForm_Load()メソッドをWPFのMVVMで置き換える

# はじめに

WindowsフォームアプリからWPFアプリに移行すると
**MVVMの呪縛にかかり、コードビハインドに何も書けなくなる** （※ 個人差があります）

たとえば、フォーム（WPFではウィンドウ）を読み込んだ直後に何かの初期化処理をしたい場合など・・

- フォームアプリでは、フォームをダブルクリックすると自動的に`Form_Load()`メソッドが作られるので、そこに処理を書けばよい
- 一方、WPFアプリでは・・・**Windowをいくら高速クリックしてもメソッドは生成されない**😣 

これは困ったことなので、どうしたらいいかを書く

# 目次のようなもの

<< ゴール >>
WPFアプリにおいて、**ウィンドウが読み込まれた直後になんらかの初期化メソッドを呼ぶ**
（ただし、コードビハインドには何も追記しない）

流れとしては、まずフォームアプリで所望の動作をさせてみる。
そのあと、WPFアプリで同じことをする

# フォームアプリ

おなじみのWindowsフォームアプリ

プロジェクトを立ち上げ直後は、まっさらなフォームが表示される

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/3bc876a7-65fd-86f5-77e0-a10f510ddbc8.png)

フォームの上でマウスを左ダブルクリックすると、メソッドが生成される

```cs
// フォームをダブルクリックすると、このメソッドが生成される
private void Form1_Load(object sender, EventArgs e)
{
    // フォームをロードし終えたときの動作
}
```

あとはこの関数の中に、初期化などの処理を書けばいい

フォームアプリについては以上

# WPFアプリ

つづいてWPFアプリの2パターン
コードビハインドを使う場合と、使わない場合

## Case1: MVVMの呪縛に囚われていない場合

`Window`タグに`Loaded="Window_Loaded"`を追加する

```diff_xml:XAMLファイル
 <Window x:Class="MyWpfApp.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:MyWpfApp"
         mc:Ignorable="d"
         Title="MainWindow" Height="450" Width="800"
+        Loaded="Window_Loaded">
     <Grid>
 
     </Grid>
 </Window>
```

コードビハインドに同名のメソッドを追加し、中身を書けば終わり

```diff_c_sharp:コードビハインド
 public partial class MainWindow : Window
 {
     public MainWindow()
     {
         InitializeComponent();
     }
 
+    private void Window_Loaded(object sender, RoutedEventArgs e)
+    {
+        // ここに処理を書く
+    }
 }
```

### Tips） IDEの補助機能を有効につかう

Viewで`Loaded="`まで入力すると、「新しいイベントハンドラー」ポップアップが表示される
このポップアップをクリックすると、コードビハインドにメソッドが自動で追加される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/eb783e07-256a-648a-b835-fae1a0a942d6.png)

### おまけ）ほかのイベントについて

ウィンドウのプロパティを開いて右上の⚡を押せば、使えるであろうたくさんのイベントが表示される

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/1a93fd3d-17c8-81b0-ad5a-3d1f1346382f.png)

## Case2: MVVMの呪縛でコードビハインドに何も書けない場合（本題）

MVVMに従う場合、何はともあれ`ViewModel`のファイルを作成する
今回は`MainWindowViewModel.cs`をプロジェクトに追加するものとする

### NuGetで必要なライブラリをインストールする

`Xaml.Behaviors.Wpf`をインストールする
これはViewのイベントとViewModelに定義したコマンドを紐づけるためにつかう
詳しくは・・・[System.Windows.Interactivity.dll から Xaml.Behaviors.Wpf へ](https://elf-mission.net/programming/wpf/wpf-tips/xaml-behaviors-wpf-interactivity/)

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/9938d935-4fce-c673-eb18-c30835864a14.png)

`ReactiveProperty`をインストールする
これはコマンドを簡単に書くためのライブラリである

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/0dd1a16a-40bb-f22f-e839-3dd8866d5315.png)

### View (XAML) を編集する

さきほどの`Loaded=...`イベントは削除する。
代わりに`i`に関わるものを追加する。

新たに追加した行を見ると

- イベントトリガーという名前からして、**なにかのイベントに連動して処理が行われそう**だとわかる
- `Loaded`と書かれており、きっと**ウィンドウがロードされたイベントを指定していそう**だとわかる
- `Command="{Binding ...}"`でViewModelのコマンドに連動しそうだとわかる

```diff_xml:MainWindow.xaml

 <Window x:Class="MyWpfApp.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:MyWpfApp"
+        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
         mc:Ignorable="d"
         Title="MainWindow" Height="450" Width="800"
-        Loaded="Window_Loaded">
+     <i:Interaction.Triggers>
+         <i:EventTrigger EventName="Loaded">
+             <i:InvokeCommandAction Command="{Binding Loaded_Command}"/>
+         </i:EventTrigger>
+     </i:Interaction.Triggers>
     <Grid>
 
     </Grid>
 </Window>
```

### ViewModelを編集する

- ViewModelは` INotifyPropertyChanged`を実装する
 - なぜ必要か？　--->>> [Q. あれ、ReactivePropertyだとINotifyPropertyChanged要らないんじゃなかったっけ？](https://qiita.com/YSRKEN/items/5a36fb8071104a989fb8#q-%E3%81%82%E3%82%8Creactiveproperty%E3%81%A0%E3%81%A8inotifypropertychanged%E8%A6%81%E3%82%89%E3%81%AA%E3%81%84%E3%82%93%E3%81%98%E3%82%83%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%A3%E3%81%91)
- `ReactiveCommand`クラスの`Loaded_Command`がViewにバインドされる
    - `.Subscribe`の引数（ラムダ式）に書かれた処理が実行される

```cs:MainWindowViewModel.cs
using Reactive.Bindings;
using System.ComponentModel;

namespace MyWpfApp
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        // Viewにバインドするコマンド
        public ReactiveCommand Loaded_Command { get; } = new();

        public MainWindowViewModel() // コンストラクター内で
        {
            // ボタンが押された時の動作を定義する
            Loaded_Command.Subscribe(()=> System.Diagnostics.Debug.WriteLine("Loaded !!"));
        }

        // これはひとまず気にしないでいい
        public event PropertyChangedEventHandler PropertyChanged;
    }
}
```

このまま実行しても"Loaded !!"とは表示されない

### 最後にもう1度、View (XAML) を編集する

ViewとViewModelをつなぐために、Viewを編集する
`DataContext`に`MainWindowViewModel`を指定する

```diff_xml:MainWindow.xaml

 <Window x:Class="MyWpfApp.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:MyWpfApp"
         xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
         mc:Ignorable="d"
         Title="MainWindow" Height="450" Width="800">
+    <Window.DataContext>
+        <local:MainWindowViewModel/>
+    </Window.DataContext>
     <i:Interaction.Triggers>
         <i:EventTrigger EventName="Loaded">
             <i:InvokeCommandAction Command="{Binding Loaded_Command}"/>
         </i:EventTrigger>
     </i:Interaction.Triggers>
     <Grid>
 
     </Grid>
 </Window>
```

なお、MVVMの呪縛に囚われていない場合は
コードビハインドで`this.DataContext = new MainViewModel();`としてもよい。

# まとめ

こと`Loaded`イベントだけに関してみれば、WinFormのほうが何も考えずにできてよいかもしれない

しかし、ひるがえってみれば、`Loaded`のためだけにNuGetを通してわざわざ2つのライブラリをインストールするWPFに対しては、なにかこの先大きなことをしてくれるのではないかと期待してしまう

というまとめ。