Xamarin.Forms での MVVM の基礎について、通称 Petzold's book の18章「MVVM」をざっくり試しています。
前回の続きです。
Overview
Petzold's bookの内容に沿ってにざっくり進めます。
- 1. MVVM interrelationships(chapter.18 その1)
- MVVMの概要説明
- 時計の表示をMVVMで行うサンプル
- スライダーの値を掛け算するサンプル
- 色の変化を楽しむ?サンプル
- 「ViewModelの簡素化」についてまとめました(...本文を要約)
- ICommandインターフェースの概要
- 3の累乗計算アプリのサンプル
- 簡易足し算アプリのサンプル
Environment
検証した環境は以下で前回同様2016-09-29時点で最新の状態にしています。
- Visual Studio 2015 (Enterprise update3)
- Xamarin.Forms
2. ViewModels and data binding(の続き)
513ページ、「Streamlining the ViewModel」...ViewModelの簡素化についてです。今回は、本文を私なりに要約してみました。それでは進めましょう。
Streamlining the ViewModel
INotifyPropertyChanged
インターフェースの典型的な実装では、例えば、クラスで定義されたすべてのパブリックプロパティに、以下のようなバッキングフィールドを持っています。
double number;
また、PropertyChanged
イベントを発火する責務を持ったOnPropertyChanged
メソッドも持っています。
public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } public double Number { set { if (number != value) { number = value; OnPropertyChanged("Number"); // Do something with the new value. } } get { return number; } }
OnpropertyChange
メソッドの引数に文字列を渡すということは潜在的な問題です。
バッキングフィールドは、1つのプロパティの中で3回も出現しますが、もしスペルを間違っても、エラーメッセージの出力はないしプロパティは正しく動作しません。
もし、類似のプロパティをいくつも持っていてコピペで書いた場合、1プロパティ毎に3つもあるバッキングフィールドの一つをコピペし忘れることはあるかもしれないし、そのバグをトレースするのは大変でしょう。
この問題は、C#5.0の機能で解決できます。
CallerMemberNameAttribute
クラスを使うと、呼び出し元のメソッド名やプロパティ名を、オプション引数の値と置換してくれます。この機能を使ってOnPropertyChanged
メソッドを再定義してみましょう。引数にCallerMemberName
属性をつけて、初期値NULLのオプション引数にします。名前空間には、 System.Runtime.CompilerServices
が必要です。
using System.Runtime.CompilerServices; ... protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
Number
プロパティは、OnPropertyChanged
メソッドを、プロパティ名の引数無しで呼ぶことができるようになりました。OnPropertyChanged
メソッドの引数には、呼び出し元のプロパティ名である"Number”が自動的にセットされます。
public double Number { set { if (number != value) { number = value; OnPropertyChanged(); // Do something with the new value. } } get { return number; } }
このアプローチによって、プロパティ名のスペルミスは回避できますし、プログラミング中のプロパティ名の変更の心配もなくなります。実際のところ、CallerMemberName
属性が実装された重要な理由のひとつは、INotifyPropertyChanged
インターフェースを実装するクラスを単純化するためでした。
しかしながら、これが機能するのは、値が変更されたプロパティがOnPropertyChagedメソッドを呼び出した場合のみです。(前回のサンプルで登場した)ColorViewModelの場合、Colorプロパティの値変更の場合を除き、明示的にプロパティ名の入力が必要です。
セッターの単純化について掘り下げてみます。以下のようなジェネリクスのメソッド(メソッド名は"SetProperty"、 またそんな感じの名称)を作る必要があります。以下のSetProperty
メソッドは、CallMemberName
属性を使って定義されています。
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (Object.Equals(storage, value)) return false; storage = value; OnPropertyChanged(propertyName); return true; } protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }
SetProperty
メソッドの最初の引数は、バッキングフィールドへの参照です。2つめの引数は、プロパティにセットする値です。このSetProperty
メソッドは、値のチェックとセットを自動化してくれます。
注意として、OnPropertyChanged
メソッドを呼ぶときに、propertyName を明示的に含めています。(そうしないと、propertyName引数の値はいつでも”SetProperty”になってしまいます。)
このSetProperty
メソッドは、プロパティの値が変更されたTrueを返します。それによって追加の処理を加えることもできます。
public double Number { set { if (SetProperty(ref number, value)) { // Do something with the new value. } } get { return number; } }
SetProperty はジェネリクスのメソッドなので、引数からC#コンパイラーは型を推論することができます。もし、プロパティのセッターでの新しい値を使って何か処理する必要がなければ、Stterを一行で書くこともできます。
public double Number { set{ SetProperty(ref number, value)); } get { return number; } }
この合理性を気に入ったら、独自のクラスでSetPropertyとOnPropertyChangedメソッドを作り、VieModelクラスを作るときに派生させたいと思うでしょう。 ViewModelBaseと呼ばれるこのようなクラスは、以下のXamarin.FormsBook.Toolkitライブラリに既にあります。
xamarin-forms-book-samples/ViewModelBase.cs at master · xamarin/xamarin-forms-book-samples · GitHub
これ以降の18章の残り2つのサンプルでは、このViewModelBase
クラスを使うということで、ようやく18章の半分を超えたくらいです(:.; ゚Д ゚;.:)=3ハァハァ。
次回に続きます。