はじめに
WindowsフォームアプリからWPFアプリに移行すると
MVVMの呪縛にかかり、コードビハインドに何も書けなくなる (※ 個人差があります)
たとえば、フォーム(WPFではウィンドウ)を読み込んだ直後に何かの初期化処理をしたい場合など・・
- フォームアプリでは、フォームをダブルクリックすると自動的に
Form_Load()
メソッドが作られるので、そこに処理を書けばよい - 一方、WPFアプリでは・・・Windowをいくら高速クリックしてもメソッドは生成されない😣
これは困ったことなので、どうしたらいいかを書く
目次のようなもの
<< ゴール >>
WPFアプリにおいて、ウィンドウが読み込まれた直後になんらかの初期化メソッドを呼ぶ
(ただし、コードビハインドには何も追記しない)
流れとしては、まずフォームアプリで所望の動作をさせてみる。
そのあと、WPFアプリで同じことをする
フォームアプリ
おなじみのWindowsフォームアプリ
プロジェクトを立ち上げ直後は、まっさらなフォームが表示される
フォームの上でマウスを左ダブルクリックすると、メソッドが生成される
// フォームをダブルクリックすると、このメソッドが生成される
private void Form1_Load(object sender, EventArgs e)
{
// フォームをロードし終えたときの動作
}
あとはこの関数の中に、初期化などの処理を書けばいい
フォームアプリについては以上
WPFアプリ
つづいてWPFアプリの2パターン
コードビハインドを使う場合と、使わない場合
Case1: MVVMの呪縛に囚われていない場合
Window
タグにLoaded="Window_Loaded"
を追加する
<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>
コードビハインドに同名のメソッドを追加し、中身を書けば終わり
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ // ここに処理を書く
+ }
}
Tips) IDEの補助機能を有効につかう
ViewでLoaded="
まで入力すると、「新しいイベントハンドラー」ポップアップが表示される
このポップアップをクリックすると、コードビハインドにメソッドが自動で追加される
おまけ)ほかのイベントについて
ウィンドウのプロパティを開いて右上の⚡を押せば、使えるであろうたくさんのイベントが表示される
Case2: MVVMの呪縛でコードビハインドに何も書けない場合(本題)
MVVMに従う場合、何はともあれViewModel
のファイルを作成する
今回はMainWindowViewModel.cs
をプロジェクトに追加するものとする
NuGetで必要なライブラリをインストールする
Xaml.Behaviors.Wpf
をインストールする
これはViewのイベントとViewModelに定義したコマンドを紐づけるためにつかう
詳しくは・・・System.Windows.Interactivity.dll から Xaml.Behaviors.Wpf へ
ReactiveProperty
をインストールする
これはコマンドを簡単に書くためのライブラリである
View (XAML) を編集する
さきほどのLoaded=...
イベントは削除する。
代わりにi
に関わるものを追加する。
新たに追加した行を見ると
- イベントトリガーという名前からして、なにかのイベントに連動して処理が行われそうだとわかる
-
Loaded
と書かれており、きっとウィンドウがロードされたイベントを指定していそうだとわかる -
Command="{Binding ...}"
でViewModelのコマンドに連動しそうだとわかる
<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要らないんじゃなかったっけ?
-
ReactiveCommand
クラスのLoaded_Command
がViewにバインドされる-
.Subscribe
の引数(ラムダ式)に書かれた処理が実行される
-
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
を指定する
<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に対しては、なにかこの先大きなことをしてくれるのではないかと期待してしまう
というまとめ。