nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Development Technologies。

WPFやXamarinのICommandを改めて整理する

ICommandとはなにか

XAMLで発生した何らかの操作に基づき、処理を実行する「コマンド」を表します。
例えば、ButtonのCommandプロパティにICommandの実態をバインドすることで、ボタンをクリックされた際に処理を実行する事が出来ます。
メソッドと何が違うの?というと、以下の点が異なります。

  1. 処理の実行可否を状態として持つ事ができ、また状態の変更を通知できる
  2. 標準の仕組みではXAMLからメソッドの呼び出しはXAMLと同一クラス内のメソッド(コードビハインド)しか利用できないが、ICommandであればバインドが利用できる=別クラス(主にViewModel)の処理を呼び出すことができる

といった特徴があります。
この為、XAMLアーキテクチャで開発する上で、最も重要で基本的な概念の一つと言っても、まぁ誰も怒りはしないと思います。

ICommandの概要

さて、それではICommandをもう少し掘り下げてみてみましょう。

    public interface ICommand
    {
        void Execute(object parameter);
        bool CanExecute(object parameter);
        event EventHandler CanExecuteChanged;
    }

ICommandには3つのメンバが存在します。
順番に説明していきます。

void Execute(object parameter)

コマンドで実行する処理の実体です。
parameterにはXAML側でCommandParameterというのを指定すると渡されてきます。
ただ、これはあくまで私個人の考えですが、ちゃんと必要な値がViewModelにバインドされていれば、どうしてもparameterで渡したいというケースはあまり多くなく、利用頻度は低いです。
CommandParameterがXAML側で指定されていないと、nullが渡ってきます。

bool CanExecute(object parameter)

コマンドが実行可能な状態かどうか判定します。 例えばコマンドがButtonのCommandプロパティにバインドされていた場合、この戻り値がfalseを返却すると、Buttonが非活性状態となります。 parameterには以下略

event EventHandler CanExecuteChanged;

コマンドの状態が変更されたことに伴い、CanExecuteの返す値が変わった場合、このイベントで変更があったことを通知します。
イベントを受信した側は、改めてCanExecuteを実行し、状態の変更に対応します。

例えば、入力フォームがあって、登録ボタンがあったとします。
その際に、氏名が必須だった場合に、氏名が入力されていたら登録ボタンを有効化するといったことを、容易に実現することが可能になるわけです。
慣れると便利ですよ!

RelayCommand・DelegateCommandとはなにか

さて、ここからが本題といえば本題です。
ICommand、非常に便利かつ強力な仕組みなのですが、WPFでもXamarinでも標準では実装が存在しません。
かといって、毎度用途に合わせて実装するのも大変です。

2016.07.10 改定 XamarinにはXamarin.Forms.Commandクラスという同様のデフォルト実装がありましたので、他のMVVMフレームワークを使わない場合はこちらを利用するとよいと思います。

そこでRelayCommandまたはDelegateCommandの登場です。
名前は異なりますが、実際にはほぼ同じものです。
私が見た範囲では当初MSの文献にはRelayCommandという名称で出ていましたが、現在のPrismなどのMVVMフレームワークではDelegateCommandという名称で提供されています。
まあ、本エントリー内ではDelegateCommandという名称で以後は統一しようと思います。

ではDelegateCommandとは何者か?端的に言うと
「コマンドの実行処理をActionとして、実行可否の判定をboolを返却するFuncを渡すことでインスタンス化できるICommand」
です。

通常はPrismとかMVVMLightとかLivetとか、何らかのMVVMフレームワークを使うと思いますが、どのフレームワークでも類する実装が含まれています。
なかなか、口頭では伝えにくいものがありますので、ここからは実際の使い方を見てもらいましょう。

DelegateCommandの簡単な利用方法

では、実際のDelegateCommandの利用方法を見ていきましょう。
作るものは簡単です。

  • 氏名と住所がある登録フォーム
  • 氏名が必須入力で、氏名が入力されないと登録ボタンは押せない

というだけの、しょぼいアプリになります。
実際の動作イメージは以下の通りです。


WPFやXamarinのICommandを改めて整理する

なお、今回のサンプルではPrismのDelegateCommandの実装を利用します。
WPFで試される場合はNuGetから「Pris.WPF」を検索して追加してください。

まずXAMLです。

氏名・住所のテキストボックスがあって、その下に登録ボタンがあります。
登録ボタンの下には、登録結果を表示するラベルがあります。

よく見ると以下にバインディングが設定されているのが見て取れると思います。

  • 氏名TextBoxのTextプロパティ
  • 登録ButtonのCommandプロパティ
  • 結果LabelのContentプロパティ

ポイントは登録ButtonのCommandプロパティにバインドされているコマンドです。
なお、住所テキストボックスは「在るだけ」ですが、氏名TextBoxからフォーカスアウトしないと、入力値がVMに渡らない為、一身上の都合で無駄に存在しています。

ではViewModelの方を見てみましょう。
全部見ると大きいので、順番に。以下がポイントのDelegateCommandの部分です。

DelegateCommandをプロパティとして公開し、コンストラクタの中でインスタンス化しています。

引数は二つあります。

一番目の引数はコマンドのExecuteメソッドを呼ばれた場合に呼び出されるActionです。
実際に実行したい処理の内容を記述します。
ここでは、結果ラベルにメッセージを表示しているだけですね。

二番目の引数はコマンドのCanExecuteに該当するFuncを渡します。
ここではコマンドが実行可能な状態かどうかをboolで返してあげます。
ここではメソッドを渡していますが、もちろんラムダ式で記述しても問題ありません。

二番目の引数は省略することも可能で、省略した場合、常に実行可能な状態となります。

ViewModelでもう一つ重要な点が以下です。

氏名のTextBoxにバインディングしているプロパティです。
Nameプロパティが変更された場合に、先ほどのExecuteCommandに対して、RaiseCanExecuteChangedメソッドを呼び出すことによって、コマンドの実行可能状態が変わったことを通知してあげます。
すると、対象のコマンドをバインドしているコントロールが、改めてコマンドのCanExecuteを呼び、実行可能状態を取得し、結果、Buttonが有効になります。

と言う分けで、今日はここまで。
それではまた!