まぐらぼ

Unity/Android、Microsoft系のWPFをやってます。

MVVMのお勉強

~20代の好奇心が旺盛なときは新しい知識を得ると自分で工夫してコードを記述していましたが、年を取ると技術記事や他人のコードをみても「ふーん」で済ます事が多くなります。自分で考えてコードを書かなくないので直ぐに忘れてしまいます。インプットよりアウトプットのトレーニングが重要です。いままでプログラミング自体はすぐに習得できていたので写経とかアホらしいと思っていましたが今後は写経をすることにします。なお写経終了後に自分でプロジェクト新規に一から作成するとより効果的でした。

★重要
他人のコードコピペ→なんとなくわかった気になる→終了
何も見ずにコード書く→分らない場所で脳みそ動く→理解する

本日はMVVM Light toolkitを習得します。その前にMVVM基本形の復習と写経を行います。
題材:MVVM入門 その1「シンプル四則演算アプリケーションの作成」WPF/VS2012)

(1) エントリポイントと画面
App.xamlで指定したエントリポイントApplication_Startup()から new MainView();
MainViewがこの画面です。

(2)
MVVMと言いながら図を描くとV-VM-M図になってる事が多いです。
V ... View
VM ... ViewModel
M ... Model

VMからVへ通知。
VからコマンドでViewModelへ返答。
MはUIに依存しないロジックを記述します。

(3) クラス定義
ViewModelBaseはINotifyPropertyChangedから継承します。
このサンプルではViewModelが二つあります。
public class CalculateTypeViewModel : ViewModelBase
public class MainViewModel : ViewModelBase

CalculateTypeViewModelは計算方法を選択するComboBoxに表示する項目を表すクラスです。MainViewModelがCalculateTypeViewModelを持ちます。has-a関係です。ainViewModelのコンストラクタで全種類のCalculateTypeViewModelインスタンスを返しています。(*1)種類とはADD,SUB,MUL,DIV,NONEの5種類です。

(*1)CalculateTypeViewModel.Create()ファクトリメソッドを呼び、foreach(){ .. yield return Create(e); }

(4.1)MainView.xaml

MainView.xamlによると、MainView(Window)のDataContextに設定されています。...(あ)計算実行ボタンはMainViewModel.CalculateCommandコマンドにBINDされています。..(い)

<Window.DataContext>
    <vm:MainViewModel /> ... (あ)
</Window.DataContext>
<Button Content="計算実行" Command="{Binding Path=CalculateCommand}" ... (い)
Grid.Row="3" Grid.ColumnSpan="2" />

<ComboBox ItemsSource="{Binding Path=CalculateTypes}" 
	SelectedItem="{Binding Path=SelectedCalculateType}" ... (う)
	SelectedIndex="0" Grid.Row="1" Grid.Column="1" />

(う) ComboBoxのSelectedItemとViewModelのSelectedCalculateTypeプロパティをバインドしている。ComboBoxの選択肢が計算種になる。

(4.2)起動時のコマンド生成
アプリ起動時にpublic DelegateCommand CalculateCommandプロパティのgetが呼び出されて、DelegateCommandが生成されます。this.calculateCommand = new DelegateCommand(CalculateExecute, CanCalculateExecute); CanCalculateExecuteは起動時にもインスタンス数だけ呼び出されます。

(5) コマンドの呼び出し

クラス定義 public DelegateCommand (Action execute, Func canExecute)

  • CanCalculateExecute ... コマンドが実行できるか判定するための関数

計算方法がNONE以外なら計算できるよ!という答えを返します。

  • CalculateExecute .. コマンドが実行する関数

計算方法を足し算にしてボタン押下するとコマンド実行されます。「(4)起動時のコマンド生成」で生成されてMainViewModelが実体をもちます。

CalculateExecute()
 var calc = new Calculator();
 this.Answer = calc.Execute(this.Lhs, this.Rhs, this.SelectedCalculateType.CalculateType);

これはenumラムダ式をペアリングした配列を準備して、enumで配列のテーブルを引いているだけです。this.LhsとRhsはXAML定義でTextBoxにバインドされています。

5.2) UIへの入力
画面から値が入力されたら、public double Lhsのsetが呼ばれます。
a) メンバの更新  this.lhs = value;
b) UIからプロパティ変更要求。
 RaisePropertyChanged()が呼ばれて、PropertyChangedハンドラが呼ばれます。
 public event PropertyChangedEventHandler PropertyChanged;はXAMLでUIとBIND済です。(V->VMに通知が行って、VM->Vに通知が戻っている?)

(6) コマンドクラス
ICommand インターフェースを継承したDelegateCommandの生成。

(7)写経
 クラス単位で削除して何も見ずに自分で記述して復活させる方法でやってます。

  • DelegateCommand

 写経の結果、気が付いた事。
 1.CommandManagerを見逃していた。
 2.下記の2メソッドは冗長
public void Execute() {
this.execute();
}
public bool CanExecute() {
return this.canExecute();
}

  • ViewModelBase

INotifyPropertyChangedの名前空間はSystem.ComponentModel

  • CalculateTypeViewModel
  • MainViewModel

・コメントない方がカシュっとコンパクトにコードまとまるので分かり易い(VSがCtrl+M-m,oでコメントの開閉できるのは知ってるけども)
・CalculateTypeを色んな所につけるとかえってややこしくなる。
 まったく関係ない物でユニークな名前を付けた方が分かりやすい。