本サイトからスピンオフした姉妹サイトです。Xamarin2015年3月27日MvvmCrossでは、画面でのイベント発生をViewModelに通知するためにコマンドバインディングを使用する。iOS/Androidにおける、その基本的な実装方法を説明する。→MvvmCrossを使ったiOS/Androidアプリ開発では、「ボタンが押された」「テキストが入力された」といった画面での各種イベントの発生をViewModelに通知するために、コマンドのバインディングを使用する。今回は、ViewModelにコマンドを定義して、iOS/Android各プラットフォームでのイベント発生をViewModelに伝達する方法を解説する。1コマンドバインディングについてMVVMでは、「ボタンがクリックされる」などのユーザーが行った操作をViewModelに伝える手段として、コマンドを使用する。コマンドオブジェクトは、「操作の実行」/「操作の可否判定」を担当する2つのメソッドと、「操作の可否が変更されたことを検知するイベント」を持っている。これらはWPFでは、ICommandインターフェース(System.Windows.Input名前空間)として定義されており、そのインターフェースを実装したコマンドオブジェクトを、コントロールのCommandプロパティにバインディングして使用する。MvvmCrossでは、コマンドはMvxCommandクラス(Cirrious.MvvmCross.ViewModels名前空間)を使用する。MvxCommandクラスもICommandインターフェースを実装しているため、WPFやWindowsストアアプリでMvvmCrossを使う場合は、CommandプロパティにMvxCommandクラスをバインディングして使用できる。iOSやAndroidの標準コントロールは(WPFと違って)Commandプロパティを持たないため、そのイベントにバインディングできる仕組みになっている。MvvmCrossで独自拡張されているコントロールにはイベントではなく、ICommand型のプロパティが実装されている場合がある。Androidでは、MvvmCrossが提供するリストビューウィジェットであるMvxListViewのItemClickプロパティの型はICommandである。この場合はMvxCommandオブジェクトを直接バインディングして使用する。独自にカスタムコントロールを実装し、ICommand型のプロパティを提供することもできる。2コマンドを定義する今回は例として、ボタンをタップすると、テキストの数字が1ずつカウントアップするプログラムを作成する。「Tips:MvvmCrossのプロジェクトをセットアップするには?」の手順で「CommandBindingSample」という名前のプロジェクトが用意されている状態で始める。ViewModel側にコマンドと、そのコマンド実行によって値が変化するプロパティを定義する。具体的には、CommandBindingSample.CoreプロジェクトのViewModelsフォルダー内にあるFirstViewModel.csファイルを次のように修正する。1{Count++;}));}}}}CountUpCommandプロパティ(=コマンド)と、そのコマンド実行によって値が変化するCountプロパティを定義したViewModelのコード例(CoreプロジェクトのFirstViewModel.cs)コマンドはMvxCommand型のプロパティとして定義する。MvxCommandクラスのコンストラクター第1引数に、コマンドが実行されるときの処理(このコード例ではCount++している)を指定する。1のように、??演算子を使って定義することにより、このプロパティが最初に使用される際に、コマンドオブジェクト(=MvxCommandオブジェクト)が生成される。使用されない場合、オブジェクトは生成されない。3コマンドをバインディングするバインディングの基本的な部分や手順は、「Tips:MvvmCrossでプロパティバインディングをするには?」とあまり変わらない。XMLファイル(.axml)の中で、local:MvxBind属性に記述できるが、iOSでイベントにバインディングする場合は、多少記述に制限がある。先ほど2で作ったコマンドとプロパティにバインディングするコードを記述してみよう。iOSでは、CommandBindingSample.TouchプロジェクトのViewsフォルダー内にあるFirstView.csファイルを開き、以下のように記述する。2set.Apply();}}}TouchUpInsideイベントにCountUpCommandプロパティ(=コマンド)をバインディングするコード(TouchプロジェクトのFirstView.cs)全体としては「Tips:MvvmCrossでプロパティバインディングをするには?」と変わらないが、1の部分ではボタンを作ってボタンのキャプションを設定している。実際にコマンドにバインディングしているのは、2の部分だ。プロパティではなく、イベントにバインディングする場合は、Forメソッドで対象となるイベントの名前(この例ではTouchUpInside)を文字列で指定する必要がある。MvvmCross独自拡張(前述したMvxListViewのItemClickプロパティなど)や自分で実装したICommand型のプロパティの場合は、このForメソッドで式木を使ったバインディングができる。先述の記事に記載のある通り、UIButtonやUIBarButtonItemはデフォルトのバインディング先がそれぞれタップ時のイベントとなっているため、Forメソッド自体を省略できる。AndroidはCommandBindingSample.DroidプロジェクトのResources/layoutフォルダー内にあるFirstView.axmlファイルを開き、以下のように記述する。/></LinearLayout>ClickイベントにCountUpCommandプロパティ(=コマンド)をバインディングするコード(DroidプロジェクトのFirstView.axml)1のlocal:MvxBind属性でコマンドにバインディングしているが、その前の<TextView>要素のlocal:MvxBind属性と変わりのない記述となっていることが分かる。このアプリを実行すると、iOS、Android共に数字とボタンが表示されたアプリが起動する(図1)。ボタンを押すたびに表示されている値が1加算される。基本的なコマンドバインディングを実装したアプリの実行結果(iOS/Android)4コマンドの実行可否を制御するWPFなどでは、ICommandインターフェースのCanExecuteメソッドで実行可否の状態を判断し、その判断結果がバインディング先のボタンの有効/無効にまで波及する。その状態変化はCanExecuteChangedイベントで監視されている。MvxCommandクラスにも同じインターフェースの実装メソッドが用意されており、それらのメソッドを簡便に利用するため、コンストラクターの第2引数でCanExecuteメソッドの内容を記述できたり、値変更イベントを発動させるRaiseCanExecuteChangedメソッドが用意されたりしている。iOSとAndroidには、現時点ではボタンの有効/無効にまで波及させるような実装は行われていない。このため、実行可否の状態を表すプロパティ(以下、状態プロパティ)を用意し、MvxCommandクラスのCanExecuteメソッドで、この状態プロパティの値を返すように実装する。iOS/Androidでは状態プロパティを、コントロールのEnabledプロパティにバインディングする(なお、WPFではCanExecuteメソッドが使用されるので、コマンドのバインディングのみでViewの実装が済む)。実行可否の状態を判断する例として、ボタンに表示されている数字が偶数の場合のみ、表示されている数字を3カウントアップするボタンを、先ほどのプログラムに追加してみよう。これには、CommandBindingSample.CoreプロジェクトのFirstViewModel.csファイルに以下のように追記する。--}}コマンドの実行可否を制御するコードの例(CoreプロジェクトのFirstViewModel.cs)このコード例では、3カウントアップのコマンドを新たに定義した(1)。MvxCommandクラスのコンストラクター第2引数に、このコマンドが使用できるかを判断してbool値を返すラムダ式が指定され、そこでThreeCountUpCanExecuteプロパティが使われている。このThreeCountUpCanExecuteプロパティ(2)は、Countプロパティが偶数ならtrue、奇数ならfalseを返すようにしている。ThreeCountUpCanExecuteプロパティの値は、Countプロパティの値が変わったときに変動するので、Countプロパティのセッターで、プロパティ値が変化したことをViewに通知するように実装する。これにはまず、ThreeCountUpCanExecuteプロパティ値が変わったことを通知するためのRaisePropertyChangedメソッドを呼ぶ(3)。これでこのプロパティを直接バインディングするiOS/Androidアプリはこのタイミングで表示が更新される。その次の行では、ThreeCountUpCommandプロパティ(つまりコマンド)のRaiseCanExecuteChangedメソッドを呼び出して(4)、コマンド自体のCanExecuteの変化を通知している。コマンドの機能が全て使えるWPFではこのタイミングで有効状態が反映される。次にiOSのViewに、このThreeCountUpCanExecuteプロパティの値を反映する。これにはCommandBindingSample.TouchプロジェクトのFirstView.csファイルに以下のように追加する。CountUp]ボタンに、先ほど作ったThreeCountUpCommandコマンドをバインディングしている(1)。先述の通り、UIButtonのTouchUpInsideプロパティはデフォルト・バインディング・プロパティなのでForメソッドは省略できる。その次の行(2)では、ボタンの有効状態を制御するEnabledプロパティに、有効状態を表すThreeCountUpCanExecuteプロパティをバインディングした。AndroidのViewはCommandBindingSample.DroidプロジェクトのFirstView.axmlファイルに以下のように追加する。--></LinearLayout>ViewにThreeCountUpCanExecuteプロパティ値を反映するためのバインディングのコード(DroidプロジェクトのFirstView.axml)ここでは、ボタンのClickイベントにViewModelのThreeCountUpCommandコマンドを、また同様に、ボタンのEnabledプロパティにViewModelのThreeCountUpCanExecuteプロパティをバインディングしている。(セミコロン)で区切ればよい。CountUp]ボタンが追加されたアプリが起動する(図2)。CountUp]ボタンは無効状態になりタップできなくなる。コマンドの実行可否を制御するアプリの実行結果5値を持つイベントにバインディングするコマンドを経由して発生されるイベントの中には、例えばMvxListViewで項目選択時に発生するItemClickイベントなどのように、コマンドパラメーターが指定される場合がある。その場合には、MvxCommand<T>クラスを使用して、渡されるコマンドパラメーターの型を明示的に指定したコマンドを定義できる。次のコードでは、Message型のコマンドパラメーターが指定されるOpenMessageCommandプロパティ(=コマンド)を定義している。message);}));}}MvxCommandクラスを使用して、渡されるコマンドパラメーターの型を明示的に指定したコマンドを定義するコード例(CoreプロジェクトのFirstViewModel.cs)6コマンドに静的にパラメーターを渡す状況によっては、「MvxCommandクラスのコンストラクター引数に、実行時に変わらない決まった文字列や列挙体の値を渡したい」という場合がある。iOSの場合は、Bindメソッドのメソッドチェーンの中に、CommandParameterメソッドを含めることで、そういったコマンドパラメーター値を指定できる(次のコードはその例)。vm.ButtonActionCommand).CommandParameter("Test");コマンドに静的にコマンドパラメーターを渡すコード例(TouchプロジェクトのFirstView.cs)Androidの場合は、local:MvxBind属性のバインディング式のオプションにCommandParameterプロパティを指定できる(次のコード)。/>コマンドに静的にコマンドパラメーターを渡すコード例(DroidプロジェクトのFirstView.axml)この場合、5で解説した方法で型を明示してパラメーターを受ける他に、MvxCommandクラスのコンストラクター第1引数のラムダ式に引数を取って、コマンドパラメーターをobject型として受け取ることもできる(次のコードはその例)。【コラム】UIViewController(iOS)やActivity(Android)のライフサイクルを監視する「画面表示時に値を更新したい」とか、「画面を離れる前に値を保存したい」という要求はよくある。MvxViewModelクラスには、StartやInitなどの「ViewModelの初期化」に関するメソッドはあるが、「画面が表示された」などのViewControllerやActivityのライフサイクルイベントを受け取って実行されるメソッドがない。MvxViewControllerクラスやMvxActivityクラスでは、それぞれの基底クラスであるMvxEventSourceViewControllerクラス/MvxEventSourceActivityクラスで、ライフサイクルイベントに応じたイベントが定義されている。本Tipsの3の要領で、それらのイベントにコマンドをバインディングすることで、ライフサイクルイベント発生時の処理をViewModelのコマンドとして記述するとよいだろう。ちなみにMvxViewControllerクラスやMvxActivityクラスには、それぞれ、以下のようにライフサイクルイベントと、それに対応するメソッドが定義されている。Transitions]という項で説明されている。対応するメソッドCreateWillBeCalledOnCreate(基底クラスを呼ぶ前)CreateCalledOnCreate(基底クラスを呼んだ後)StartCalledOnStartRestartCalledOnRestartResumeCalledOnResumePauseCalledOnPauseSaveInstanceStateCalledOnSaveInstanceStateCalledStopCalledOnStopDestroyCalledOnDestroyDisposeCalledDisposeActivityResultCalledOnActivityResultNewIntentCalledOnNewIntentStartActivityForResultCalledStartActivityForResultライフサイクルイベントに対応する、MvxActivityのメソッド特徴的なのは、OnCreateメソッドでは、基底クラスのOnCreateメソッドが呼ばれる前に発生する「CreateWillBeCalled」というイベントがあること。Lifecycle]という項で説明されている。※以下では、本稿の前後を合わせて5回分(第41回~第45回)のみ表示しています。INDEX]を参照してください。MvvmCrossでプロパティバインディングをするには?MvvmCrossでの画面表示に必要なプロパティバインディングについて、iOS/Androidの基本的な実装を説明する。Xamarin.Formsでビヘイビアーを使用するには?サブクラス化することなく、UIコントロールに機能を追加できる「ビヘイビアー」の基本的な使い方を説明する。MvvmCrossでコマンドバインディングをするには?MvvmCrossでは、画面でのイベント発生をViewModelに通知するためにコマンドバインディングを使用する。iOS/Androidにおける、その基本的な実装方法を説明する。Xamarin.FormsでListViewのコンテキストアクションを使用するには?リストの1つをスライド(iOS)もしくは長押し(Android)されたらメニューを表示する「コンテキストアクション」の基本的な使い方を説明する。Xamarin.Formsでトリガーを使用するには?イベントやプロパティの変化に応じたコントロールの外観の変更をXAMLだけで実装できるトリガーの基本的な使用方法を解説する。機械学習エンジニアを目指す人~初中級者のための技術情報サイトです。「機械学習&AI」「クラウド&ビッグデータ」「IoTデバイス」のネット記事情報を共有中。開発者のためのSlackチーム、作りました:「機械学習&AI」「クラウド&ビッグデータ」「IoTデバイス」のネット記事情報を共有中。2020最適なビューへの切り替えを推奨します:スマートフォン用表示に。