しらとりのブログ

社会人ひよこプログラマのtil

WPFで画面とロジックを分離する(ICommand 理論編)

ICommandとは何者か。という覚書きメモです。下記の実装編のコードがなぜ動くかの補足です。

silatori.hatenablog.com

ICommandインターフェース

ICommandは標準ライブラリにあるインターフェースです。このICommand型のプロパティをViewModelで作成、Viewに公開することで従来のイベントハンドラのような動作をさせることができます。

ポイントは、ICommand型プロパティに設定されるインスタンスです。実装編のコードではコンストラクタでRelayCommand型のインスタンスを生成してセットしています。

public MainWindowViewModel()
{
    this.HogeCommand = new RelayCommand(hogeCommand);
}

RelayCommandのコンストラクタは引数にメソッド名をとっています。C#ではメソッド名を文字列ではなくそのまま指定するとメソッドグループ変換が行われ、汎用デリゲートに変換されます。用語が難しくなってきましたが、意味していることは単純でRelayCommandはとあるメソッド1つを受け取り、代わりに実行してくれるものです。当然ながら、RelayCommandはICommandを実装しています。次はその実装内容を見てみます。

RelayCommandクラス

以下のコードはPrismのDelegateCommandクラスのコンストラクタの抜粋です。

public class DelegateCommand : DelegateCommandBase
{
    Action _executeMethod;
    Func<bool> _canExecuteMethod;

    public DelegateCommand(Action executeMethod)
        : this(executeMethod, () => true)
    {

    }

    public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
        : base()
    {
        if (executeMethod == null || canExecuteMethod == null)
            throw new ArgumentNullException(nameof(executeMethod), Resources.DelegateCommandDelegatesCannotBeNull);

        _executeMethod = executeMethod;
        _canExecuteMethod = canExecuteMethod;
    }

Actionデリゲート(引数無し、戻り値無しの汎用デリゲート)を引数にとっているのが分かると思います。これをフィールドに保存しているだけですね。
もう1つ、オーバーロードでFunc<bool>デリゲート(引数無し、戻り値がboolのデリゲート)をとっているコンストラクタがあります。この2つ目のデリゲートを渡した場合は、その条件によってコマンドの実行可否を決めることができます。ボタンの場合はここがfalseになる条件の場合非活性になります。Actionしか渡さない場合は、常にtrueを返すデリゲートがセットされます。

そして、WPFのバインディング機能によって下記メソッドが参照されます。これがインターフェースとなってコマンドを定義しています。

    public void Execute()
    {
        _executeMethod();
    }

    public bool CanExecute()
    {
        return _canExecuteMethod();
    }

以上、コマンド呼び出しの流れでした。RelayCommandのようなICommandを実装したヘルパークラスを使用すると簡単にViewModelのメソッドをViewから使用できるようになります。こういったヘルパークラスは通常MVVMインフラに含まれていますが、標準ライブラリにはありません。ライブラリ無しでWPFにとりあえず触れてみたい人も、RelayCommandにあたるクラスはどこからか持ってきましょう。