第 18 章の概要。 MVVM
Note
この本は 2016 年春に発行されて以降、改訂されていません。 多くの情報はまだ価値がありますが、一部の資料は古くなっており、トピックの中にはまったく正しくないものまたは不完全なものもあります。
アプリケーションを設計する最良の方法の 1 つは、基になるコード ("ビジネス ロジック" とも呼ばれます) からユーザー インターフェイスを分離することです。 いくつかの手法がありますが、XAML ベースの環境に合った手法は Model-View-ViewModel または MVVM と呼ばれます。
MVVM の相関関係
MVVM アプリケーションには、3 つのレイヤーがあります。
- Model によって、基になるデータが提供されます。ファイルや Web アクセスが使用される場合があります
- View はユーザー インターフェイス レイヤーまたはプレゼンテーション層です。通常は XAML で実装されます
- ViewModel によって、Model と View が接続されます
Model は ViewModel について何も知らず、ViewModel は View について何も知りません。 これら 3 つのレイヤーは、通常、次のメカニズムを使用して相互に接続されます。
多くの小規模なプログラムでは (より大規模なプログラムでも)、Model が存在しないか、その機能が ViewModel に統合されていることがよくあります。
ViewModel とデータ バインディング
データ バインディングを使用するために、ViewModel では、ViewModel のプロパティが変更されたときに View に通知できる必要があります。 ViewModel では、System.ComponentModel
名前空間の INotifyPropertyChanged
インターフェイスを実装することによってこれを行います。 これは、Xamarin.Forms ではなく .NET の一部です。 (通常、ViewModel はプラットフォームに依存しないようにします。)
INotifyPropertyChanged
インターフェイスでは、変更されたプロパティを示す PropertyChanged
という名前の単一のイベントが宣言されます。
ViewModel の時計
Xamarin.FormsBook.Toolkit ライブラリの DateTimeViewModel
によって、タイマーに基づいて変化する DateTime
型のプロパティが定義されます。 このクラスでは INotifyPropertyChanged
が実装され、DateTime
プロパティが変更されるたびに PropertyChanged
イベントが発生します。
MvvmClock サンプルでは、この ViewModel をインスタンス化し、その ViewModel に対してデータ バインディングを使用して、更新された日付と時刻の情報を表示します。
ViewModel の対話型プロパティ
SimpleMultiplier サンプルに含まれている SimpleMultiplierViewModel
クラスが示しているように、ViewModel のプロパティはより対話的にすることができます。 データ バインディングによって、2 つの Slider
要素から被乗数と乗数の値が提供され、Label
でその積が表示されます。 ただし、XAML でこのユーザー インターフェイスに広範な変更を加えつつ、ViewModel や分離コード ファイルにそれに伴う変更を生じさせないようにすることができます。
カラー ViewModel
Xamarin.FormsBook.Toolkit ライブラリの ColorViewModel
では、RGB と HSL のカラー モデルが統合されています。 これは HslSliders サンプルで示されています。
ViewModel の合理化
ViewModel のコードは、呼び出し元のプロパティ名を自動的に取得する CallerMemberName
属性を使用して OnPropertyChanged
メソッドを定義することで、合理化できます。 Xamarin.FormsBook.Toolkit ライブラリの ViewModelBase
クラスでは、これが行われ、ViewModel の基底クラスが提供されます。
コマンド インターフェイス
MVVM ではデータ バインディングが使用され、データ バインディングではプロパティが使用されます。そのため、MVVM は、Button
の Clicked
イベントや TapGestureRecognizer
の Tapped
イベントを処理する場合には不完全であるように見えます。 ViewModel でこのようなイベントを処理できるようにするために、Xamarin.Forms では "コマンド インターフェイス" がサポートされています。
コマンド インターフェイスは、Button
内で次の 2 つのパブリック プロパティを使用して明示されます。
ICommand
型 (System.Windows.Input
名前空間で定義されています) のCommand
CommandParameter
(Object
型)
コマンド インターフェイスをサポートするために、ViewModel では、後で Button
の Command
プロパティにデータ バインディングされる ICommand
型のプロパティを定義する必要があります。 ICommand
インターフェイスでは、2 つのメソッドと 1 つのイベントが宣言されています。
object
型の引数を持つExecute
メソッドobject
型の引数を持ち、bool
を返すCanExecute
メソッドCanExecuteChanged
イベント
ViewModel では、内部的に、ICommand
型の各プロパティが、ICommand
インターフェイスを実装するクラスのインスタンスに設定されます。 データ バインディングを通じて、Button
では最初に CanExecute
メソッドが呼び出され、そのメソッドが false
を返す場合はそれ自体が無効になります。 また、CanExecuteChanged
イベントのハンドラーも設定され、そのイベントが発生するたびに CanExecute
が呼び出されます。 Button
が有効になっている場合は、Button
がクリックされるたびに Execute
メソッドが呼び出されます。
Xamarin.Forms よりも前から存在する ViewModel を利用している場合があり、それらで既に、コマンド インターフェイスがサポートされていることがあります。 Xamarin.Forms とだけ使用することを想定された新しい ViewModel のために、Xamarin.Forms には、ICommand
インターフェイスを実装する Command
クラスと Command<T>
クラスが用意されています。 ジェネリック型は、Execute
および CanExecute
メソッドに対する引数の型です。
シンプルなメソッドの実行
PowersOfThree サンプルでは、ViewModel でコマンド インターフェイスを使用する方法が示されています。 PowersViewModel
クラスでは、ICommand
型の 2 つのプロパティと、最もシンプルな Command
コンストラクターに渡される 2 つのプライベート プロパティが定義されています。 このプログラムには、この ViewModel から 2 つの Button
要素の Command
プロパティへのデータ バインディングが含まれています。
Button
要素は、コードを変更せずに、XAML で TapGestureRecognizer
オブジェクトに簡単に置き換えることができます。
(ほぼ) 電卓
AddingMachine サンプルでは、ICommand
の Execute
メソッドと CanExecute
メソッドの両方が使用されています。 Xamarin.FormsBook.Toolkit ライブラリの AdderViewModel
クラスが使用されています。 ViewModel には、ICommand
型のプロパティが 6 つ含まれています。 これらは、Command
の Command
コンストラクターおよび Command
コンストラクターと、Command<T>
の Command<T>
コンストラクターから初期化されます。 足し算プログラムの数値キーは、すべて Command<T>
で初期化されるプロパティにバインドされます。また、Execute
と CanExecute
に対する string
引数によって、特定のキーが識別されます。
ViewModel とアプリケーションのライフサイクル
AddingMachine サンプルで使用されている AdderViewModel
では、SaveState
と RestoreState
という名前の 2 つのメソッドも定義されています。 これらのメソッドは、アプリケーションがスリープ状態になるときと再び起動するときに呼び出されます。