前提
- MVVMについての知識。
-
PrismLibraryについての知識。
(
BindableBase
が分かれば…)
背景
デスクトップアプリケーションにおいて複数のウィンドウを開いたり閉じたりする要請がよくある。
問題
MVVMを前提とした複数のウィンドウを扱うようなWPFアプリケーションでは、ViewModelがViewを参照した状態でCloseすることは禁忌である。
例えば Sample.cs
と SampleViewModel.cs
があったとして、以下のようにViewModelからViewの参照を持ち、そこからClose()
メソッドを叩くのはだめ。
// ダメな例
// SampleViewModel.cs
public class SampleViewModel : BindableBase {
private Sample view;
public MainWindowViewModel(Sample view) {
this.view = view;
}
public void CloseWindow() {
view.Close();
}
}
方法
考え方のフローは1から5までをたどっていく。
- ViewからViewModelへ委譲するための
Action型
のプロパティをViewModelに用意する必要がある。 - 委譲するためのインターフェースを用意する。
- ViewModelにインターフェースを持つ。
- ViewでViewModelの持つインターフェースのプロパティにメソッドを登録。
- ViewModelでコール。
実際に用意する必要があるのは、ViewとViewModelを仲介するインターフェースである。今回の例では、IClosedWindow
と名付けることにする。
// IClosedWindow.cs
interface IClosedWindow {
Action Close { get; set; }
}
次に、このインターフェース(IClosedWindow
)をViewModelに実装する。
// SampleViewModel.cs
public class SampleViewModel : BindableBase, IClosedWindow {
public SampleViewModel {}
public Action Close { get; set; }
private DelegateCommand closeCommand;
public DelegateCommand CloseCommand =>
closeCommand ??= new DelegateCommand(ExecuteCloseCommand);
private void ExecuteCloseCommand() {
Close?.Invoke();
}
}
そして、ViewはSample
の継承元であるWindow
クラスの持つLoaded
イベントが発火されたタイミングでDataContext
プロパティに登録されたViewModelを参照し、IClosedWindow
にキャストする。
// Sample.xaml.cs
public partial class Sample : Window {
public Sample {
InitializeComponent();
Loaded += SampleLoaded;
}
private void SampleLoaded(object sender, RoutedEventArgs e) {
// vmがインターフェースを持っていない場合の処理
if (DataContext is not IClosedWindow vm)
return;
// ここで、Viewを閉じる処理の委譲
vm.Close += () => {
this.Close();
};
}
}
キャストされたvm
のClose()
に委譲することによって、ViewModel側でコマンドを呼ぶとViewが閉じる。