導入
「追加」ボタンを押したら何が起きるべきか——それを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;
}
ICommandはExecute(実行)・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です。