本文へスキップ
BecomeCoder

C# MVVMコース · 第6章 ViewModel ― 画面の状態と操作 · レッスン22

Command ― ICommandとRelayCommand

ブラウザで完結

導入

「追加」ボタンを押したら何が起きるべきか——それをViewModelに伝える仕組みがCommandです。Viewのボタンにイベントハンドラを書く(コードビハインド)のではなく、ViewModelが持つICommandにボタンをバインドします。こうすればViewModelはUIフレームワークを知らずに済み、テストも書けます。ただしICommandを毎回1から実装するのは大変なので、汎用のヘルパーRelayCommandを作ります。

図解

flowchart LR
    B["Button(View)<br/>Command={Binding AddCommand}"] -->|クリック| C["ICommand<br/>(ViewModelが持つ)"]
    C -->|Execute| L["ViewModelの処理本体"]
    style C fill:#e8f5e9

サンプル

using System.Windows.Input;

// RelayCommand: Action(実行本体)とFunc<bool>(実行可否)をコンストラクタで受け取る汎用ヘルパー
var greetCommand = new RelayCommand(() => Console.WriteLine("こんにちは"));
Console.WriteLine(greetCommand.CanExecute(null));   // True(canExecute未指定なら常に実行可)
greetCommand.Execute(null);                         // こんにちは

class RelayCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func<bool>? _canExecute;

    public RelayCommand(Action execute, Func<bool>? canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object? parameter) => _canExecute?.Invoke() ?? true;
    public void Execute(object? parameter) => _execute();
    public event EventHandler? CanExecuteChanged;
}
  • ICommandExecute(実行)・CanExecute(実行可能か)・CanExecuteChanged(実行可否が変わったことの通知)の3つを持つ
  • RelayCommandはラムダ式を受け取るだけでICommandを満たせる汎用実装。ViewModelごとに専用クラスを書かずに済む
  • canExecuteを渡さなければ「常に実行可能」(?? true

演習

using System.Windows.Input;

int executedCount = 0;
var command = new RelayCommand(() => executedCount++);

Assert(command.CanExecute(null) == true, "canExecuteが未指定ならtrue");
command.Execute(null);
command.Execute(null);
Assert(executedCount == 2, "Executeのたびに実行本体が呼ばれる");

bool allowed = false;
var guarded = new RelayCommand(() => executedCount++, () => allowed);
Assert(guarded.CanExecute(null) == false, "canExecuteがfalseを返す間は不可");
allowed = true;
Assert(guarded.CanExecute(null) == true, "trueになれば実行可能になる");

Console.WriteLine("全テスト成功");

// TODO: ICommand を実装する RelayCommand を完成させてください
//       ・コンストラクタで実行本体(Action)と、任意で実行可否(Func<bool>)を受け取る
//       ・CanExecute は canExecute が未指定なら常にtrue、指定されていればその結果を返す
//       ・Execute は実行本体を呼ぶ
//       ・CanExecuteChanged イベントを持つ(ICommandの要件。このテストでは発火させなくてよい)
class RelayCommand : ICommand
{
    ___
}

void Assert(bool condition, string name)
{
    if (!condition) throw new Exception($"FAIL: {name}");
}
  • 期待される出力: 全テスト成功
ヒント1を見る

コンストラクタで_execute_canExecuteをフィールドに保存します

ヒント2を見る

public bool CanExecute(object? parameter) => _canExecute?.Invoke() ?? true; public void Execute(object? parameter) => _execute(); public event EventHandler? CanExecuteChanged;

まとめ

  • ICommandはViewからの「実行」をViewModelへ橋渡しするインターフェース
  • RelayCommandはラムダを渡すだけで使える汎用実装
  • ViewModelはUIフレームワークに依存せず、単体テストが書ける

次回: 「今は実行できない」をボタンの活性/非活性に反映するCanExecuteです。

実際に動かしてみよう

本文のサンプルや演習のコードは、コードブロック右上の「コピー」ボタンでコピーして、下のエディタに貼り付ければそのまま実行できます。

C# — ブラウザ内で実行

ブラウザ内でC#を動かす環境を読み込みます(初回のみ数秒)。
スクロールして表示された時点でも自動で読み込まれます。