ViewModelとはViewをモデル化したものである
ある人に教えてもらったのですが、その瞬間に色々なことがスッキリしました。ViewModelの役割って様々な主張があるけど、骨子はこれですね。
モデル化とは・・・
検索したら++C++がかかりました。さすが岩永さんは色々書いてますねー。
http://ufcpp.net/study/dsl/mdd.html
- モデル化とは、 現実の問題から、問題解決に必要な部分だけを抜き出して簡単化・抽象化することです。
- 「よいモデル化」というのは、 問題の要件を必要十分に、過不足なく表せるモデルを作ることです。 情報は多すぎても少なすぎてもダメ。
例
このViewをモデル化してみます。この画像は出来上がったもののキャプチャですが、こんな画面仕様書が来たと思ってください。コンボボックスの選択を変えたら、そのミュージシャンのアルバム一覧が表示されるというものです。
(※リリース日は年はあってますが、月日はわからなかったので嘘ものです)
この画面は具体的ですね。ComboBoxとかDataGridViewとか。
これを心の目で見るのですw
見えましたか?GUIの詳細はフィルタされてます。それから固定値のTextBlockもフィルタしました。そしたら、これをコードに落としましょう。
publicclass MainWindowVM { //ミュージシャンpublic ObservableCollection<Musician> Musicians { get; } = new ObservableCollection<Musician>(); //現在選択されているミュージシャンpublic ReactiveProperty<Musician> SelectedMusician { get; } = new ReactiveProperty<Musician>(); //アルバムpublic ObservableCollection<Album> Albums { get; } = new ObservableCollection<Album>(); }
次に、これを操作します。操作は・・・、WinFomrsでコードビハインドに実装しているイメージでいいんじゃないですかね。操作対象が具体的なGUIコントロールではなく抽象化されたオブジェクトを操作するように変わっただけです。
using Reactive.Bindings; using System.Collections.ObjectModel; using System.Reactive.Linq; using System.Linq; using System; namespace WpfApp { publicclass MainWindowVM { MusicInfo _info = new MusicInfo(); //ミュージシャンpublic ObservableCollection<Musician> Musicians { get; } = new ObservableCollection<Musician>(); //現在選択されているミュージシャンpublic ReactiveProperty<Musician> SelectedMusician { get; } = new ReactiveProperty<Musician>(); //アルバムpublic ObservableCollection<Album> Albums { get; } = new ObservableCollection<Album>(); public MainWindowVM() { //初期化 _info.GetMusicians().ToList().ForEach(e => Musicians.Add(e)); //選択ミュージシャン変更イベント SelectedMusician.Subscribe(_AppDomain => MusicianChanged()); //最初は0番目を選択 SelectedMusician.Value = Musicians[0]; } publicvoid MusicianChanged() { //変わったらそのミュージシャンのアルバム一覧を取得 Albums.Clear(); _info.GetAlbums(SelectedMusician.Value.Id).ToList().ForEach(e => Albums.Add(e)); } } }
↑*1
過不足なく抽象化されたViewModelのオブジェクトを操作するのであれば、ハマりポイントの多いGUIコントロールと違って素直に実装できます。しかもテストコードで確認しながら実装できるのです。(理論的には。みんなやってるんですよねw)
ポイントは、あくまでViewをモデル化したものなので、View起因で設計されるものです。決してモデル起因ではありません。
ViewMdoelは薄く
抽象化かかってるとはいえ、あくまでViewをモデル化しただけです。そこにロジックをモリモリ載せるのではなくモデル層でしっかり設計、実装したロジックを呼び出すだけにしましょう。でも、Viewとは違って抽象化かかってるから、コントローラを挟まず直接モデルを呼び出してもOKという判断なのでしょう。
Model
Modelは、まあMVCでやる場合とあんまり変わらない印象です。前回でも書きましたが、MVナントカ関係なくしっかり設計しましょう。
MVVM怖くない
こう考えるとMVVMは怖くなくなるのではないでしょうか。
- Modelはあんまり変わらない
- ViewModelはコードビハインドに書くより簡単
- Viewは・・・、Xamlが怖いかw
チームで導入する場合、デザイナとプログラマで仕事を分けるとかありますけど、現実的には先行でWPFの知識を持った人とその他のプログラマで分けたらいいかもしれないですね。そしたら他の人が慣れてくるまで、その人にひたすらXaml書いてもらえばよいのではないでしょうかw
短期集中連載終わり
2日間で7本は自分の中では記録ですね。また何か便利な書き方学んだり、書きたいことが出てきたらアウトプットしていきます。
連載で使ったMarkup拡張のコードはこちらにおいてます。
おまけ
サンプルのViewとModelのコードです。
<Window x:Class="WpfApp.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:l="clr-namespace:WpfApp"xmlns:c="clr-namespace:VVMConnection;assembly=VVMConnection"Title="MainWindow"Height="350"Width="525"><Window.DataContext><l:MainWindowVM/></Window.DataContext><Grid><Grid.RowDefinitions><RowDefinition Height="auto"/><RowDefinition Height="auto"/><RowDefinition Height="*"/></Grid.RowDefinitions><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="auto"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions><TextBlock Text="ミュージシャン"Grid.Column="0"Margin="10,0,0,0"/><ComboBox ItemsSource="{Binding Musicians}"SelectedItem="{Binding SelectedMusician.Value}"Margin="10,0,10,0"Grid.Column="1"DisplayMemberPath="Name"/></Grid><TextBlock Text="アルバム一覧"Margin="10,30,0,0"Grid.Row="1"/><DataGrid ItemsSource="{Binding Albums}"AutoGenerateColumns="False"CanUserAddRows="False"Grid.Row="2"><DataGrid.Columns><DataGridTextColumn Header="リリース日"Binding="{Binding ReleaseDate, StringFormat={}{0:yyyy年MM月dd日}}"Width="150"IsReadOnly="True"/><DataGridTextColumn Header="アルバムタイトル"Binding="{Binding Title}"Width="*"IsReadOnly="True"/></DataGrid.Columns></DataGrid></Grid></Window>
using System; namespace WpfApp { publicclass Musician { publicint Id { get; set; } publicstring Name { get; set; } } publicclass Album { publicstring Title { get; set; } public DateTime ReleaseDate { get; set; } } publicclass MusicInfo { public Musician[] GetMusicians() { returnnew Musician[] { new Musician() { Name = "Kenny Burrell", Id = 0}, new Musician() { Name = "Aerosmith", Id = 1}, new Musician() { Name = "BLANKEY JET CITY", Id = 2}, }; } public Album[] GetAlbums(int musicianId) { switch (musicianId) { case0: returnnew Album[] { new Album() { Title = "Introducing", ReleaseDate = new DateTime(1956, 1, 1)}, new Album() { Title = "Kenny Burrell", ReleaseDate = new DateTime(1957, 2, 2)}, new Album() { Title = "Blue Lights Vol.1", ReleaseDate = new DateTime(1958, 3, 3)} }; case1: returnnew Album[] { new Album() { Title = "Aerosmit", ReleaseDate = new DateTime(1973, 4, 4)}, new Album() { Title = "Get Your Wings", ReleaseDate = new DateTime(1974, 5, 5)}, new Album() { Title = "Toys in the Attic", ReleaseDate = new DateTime(1975, 6, 6)} }; case2: returnnew Album[] { new Album() { Title = "Red Guitar And The Truth", ReleaseDate = new DateTime(1991, 4, 12)}, new Album() { Title = "Bang!", ReleaseDate = new DateTime(1992, 1, 22)}, new Album() { Title = "C.B.Jim", ReleaseDate = new DateTime(1993, 2, 24)} }; } returnnew Album[0]; } } }
追記
@yone64さんに、「こう書いたらもっとReactiveだよ。」って教えていただきました。ありがとうございます!
using Reactive.Bindings; using System.Collections.ObjectModel; using System.Reactive.Linq; using System.Linq; namespace WpfApp { publicclass MainWindowVM { MusicInfo _info = new MusicInfo(); //ミュージシャンpublic ObservableCollection<Musician> Musicians { get; } = new ObservableCollection<Musician>(); //現在選択されているミュージシャンpublic ReactiveProperty<Musician> SelectedMusician { get; } = new ReactiveProperty<Musician>(); //アルバムpublic ReactiveProperty<ObservableCollection<Album>> Albums { get; } public MainWindowVM() { //初期化 _info.GetMusicians().ToList().ForEach(e => Musicians.Add(e)); //最初は0番目を選択 SelectedMusician.Value = Musicians[0]; //選択ミュージシャンとAlbumsをReactiveな感じでつなぐ Albums = SelectedMusician.Select(e => new ObservableCollection<Album>(_info.GetAlbums(SelectedMusician.Value.Id))).ToReactiveProperty(); } } }