MVVM List Detail パターン


コード

ListDetail

今まで、ListとDetailをその都度、ああでもない、こうでもないと試行錯誤して書いていたので、パターンにまとめてみました。

開発環境

.NET Core 3.1 + ReactiveProperty で構築します。

ListViewModelとDetailViewModel

ListViewModel.cs
this.InfoList = this.Model.InfoList.ToReadOnlyReactiveCollection(x => new DetailViewModel(x)).AddTo(Disposable);

で、DataGridのそれぞれの行に対応する、それぞれのDetailViewModelを作成しています。
MVVMを学び始めた当初、DataGridやListViewではこうする方法があることが分からなかったですね。

Show

ShowボタンのXAMLは次です。
Commandは、DataGridのDataContextつまり、WindowのDataContext(=ListViewModel)のButtonShowを呼び出します。
パラメータは、CommandParameter="{Binding}"によって、この行のDetailViewModelになります。

ListView.xaml
<Button Command="{Binding DataContext.ButtonShow, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding}">Show</Button>
ListViewModel.cs
public ReactiveCommand<DetailViewModel> ButtonShow { get; } = new ReactiveCommand<DetailViewModel>();

その行のDetailViewModel自体をパラメータとしてうけとり(ここではxが、パラメータ)

ListViewModel.cs
ButtonShow.Subscribe(x => ShowDetail(x)).AddTo(Disposable);

private void ShowDetail(DetailViewModel infoVM)
{
    Model.ShowDetail(infoVM.Model);
}

DetailViewModelが保持しているModel(infoVM.Model)を、ListManagerに渡して、Detailを表示します。

従って、DataGridの行でもDetailでも、Modelは同じものを参照しますが、VMはそれぞれ異なります。

DataGridの行で利用している、同じDetailViewModelを利用して、Detailを表示したいなら、

ListViewModel.cs
private void ShowDetail(DetailViewModel infoVM)

の段階で、infoVMを利用して、ListViewModel.csから、DetailViewを表示してもいいかもしれません。そうすると、DataGridの行と新しく表示したDetailViewでのDataContextは、同じDetailViewModelを利用することになるでしょう。

Remove

Showとほぼ同じです。

newの順序

Model → ViewModel  → View の順序でnewしています。

Main.cs
var list = new ListManager();
ViewController.ShowListView(list);

ViewとViewModelのnewは、ViewControllerpublic staticのクラスで処理することにしました。

ViewController.cs
public static void ShowListView(ListManager model)
{
    var viewModel = new ListViewModel(model);
    var view = new ListView(viewModel);
    view.Show();
}

View  → ViewModel → Model の方法もありますが、ここではModelが先です。
バックグラウンドでModelが動いていて、ある条件でViewを表示するなら、Modelを先にせざる得ないでしょうし、Modelが無くてVとVMだけあることはMVVMでは考えにくいので、Modelを先にnewするのがいいのではという考えです。

Disposeのタイミング

ReactivePropertyで、よく分からないのはDisposeを呼び出すタイミングです。

DataGridのViewModel

DataGridのClear・Removeは、ListManager.csObservableCollection<Info> InfoListを、ClearしたりRemoveしています。
すると、ListViewModelの、ReadOnlyReactiveCollection<DetailViewModel> InfoListが変更されます。このコードでは、すぐに、DetailViewModelDisposeされたので、ここでは明示的にはDetailViewModelDisposeを呼び出していません。(これで良いのか?がよくわかりません。)

DetailのViewModel

DetailViewCloseした時は、Disposeされるのかどうかが分からないので、明示的にDiposeしています。

DetailViewModel.cs
private void Close()
{
    CloseAction();
    Dispose();
}

感想

処理の流れがはっきりせず、デバッグしつつ追わないと、コードを見るだけでは分かりませんね。この点が、MVVMの難しいところだと思います。
ただ、パターン化しておくと、処理の流れの見当が少しつきやすくなりますね。

MVVMの記事