RelayCommand 属性
RelayCommand
型は、注釈付きメソッドのリレー コマンド プロパティを生成できる属性です。 その目的は、ビューモデルでプライベート メソッドをラップするコマンドを定義するために必要な定型句を完全に排除することです。
Note
注釈付きメソッドが機能するためには、部分クラス内にある必要があります。 型が入れ子になっている場合は、宣言構文ツリー内のすべての型にも部分として注釈を付ける必要があります。 そうしないとコンパイル エラーが発生します。ジェネレーターは、要求されたコマンドを使用してその型の別の部分宣言を生成できないためです。
Platform API:
RelayCommand
、ICommand
、IRelayCommand
、IRelayCommand<T>
、IAsyncRelayCommand
、IAsyncRelayCommand<T>
、Task
、CancellationToken
しくみ
RelayCommand
属性は、次のように、部分型のメソッドに注釈を付けるために使用できます。
[RelayCommand]
private void GreetUser()
{
Console.WriteLine("Hello!");
}
そして、次のようなコマンドが生成されます。
private RelayCommand? greetUserCommand;
public IRelayCommand GreetUserCommand => greetUserCommand ??= new RelayCommand(GreetUser);
Note
生成されたコマンドの名前は、メソッド名に基づいて作成されます。 ジェネレーターはメソッド名を使用し、末尾に "Command" を追加し、"On" プレフィックス (存在する場合) を削除します。 さらに、非同期メソッドの場合は、"Command" が追加される前に "Async" サフィックスも削除されます。
コマンド パラメーター
[RelayCommand]
属性は、パラメーターを持つメソッドのコマンドの作成をサポートしています。 その場合、生成されたコマンドは自動的に IRelayCommand<T>
に変更され、代わりに同じ型のパラメーターが受け入れられます。
[RelayCommand]
private void GreetUser(User user)
{
Console.WriteLine($"Hello {user.Name}!");
}
これにより、次のコードが生成されます。
private RelayCommand<User>? greetUserCommand;
public IRelayCommand<User> GreetUserCommand => greetUserCommand ??= new RelayCommand<User>(GreetUser);
結果のコマンドでは、引数の型が型引数として自動的に使用されます。
非同期コマンド
[RelayCommand]
コマンドは、IAsyncRelayCommand
と IAsyncRelayCommand<T>
インターフェイスを介した非同期メソッドのラップもサポートしています。 これは、メソッドが Task
型を返すたびに自動的に処理されます。 次に例を示します。
[RelayCommand]
private async Task GreetUserAsync()
{
User user = await userService.GetCurrentUserAsync();
Console.WriteLine($"Hello {user.Name}!");
}
結果は、次のコードのようになります。
private AsyncRelayCommand? greetUserCommand;
public IAsyncRelayCommand GreetUserCommand => greetUserCommand ??= new AsyncRelayCommand(GreetUserAsync);
メソッドがパラメーターを受け取る場合、結果のコマンドもジェネリックになります。
メソッドに CancellationToken
がある場合は、キャンセルを有効にするためにコマンドに伝達されるため、特別なケースがあります。 つまり、次のようなメソッドです。
[RelayCommand]
private async Task GreetUserAsync(CancellationToken token)
{
try
{
User user = await userService.GetCurrentUserAsync(token);
Console.WriteLine($"Hello {user.Name}!");
}
catch (OperationCanceledException)
{
}
}
生成されたコマンドは、ラップされたメソッドにトークンを渡すことになります。 これにより、コンシューマーは IAsyncRelayCommand.Cancel
を呼び出してトークンに通知し、保留中の操作を正しく停止できます。
コマンドの有効化と無効化
多くの場合、コマンドを無効にし、後でその状態を無効にし、実行できるかどうかを再度確認すると便利です。 これをサポートするために、RelayCommand
属性は CanExecute
プロパティを公開します。これは、コマンドを実行できるかどうかを評価するために使用するターゲット プロパティまたはメソッドを示すために使用できます。
[RelayCommand(CanExecute = nameof(CanGreetUser))]
private void GreetUser(User? user)
{
Console.WriteLine($"Hello {user!.Name}!");
}
private bool CanGreetUser(User? user)
{
return user is not null;
}
こうすると、CanGreetUser
ボタンが最初に UI (ボタンなど) にバインドされたときに呼び出され、IRelayCommand.NotifyCanExecuteChanged
がコマンドで呼び出されるたびに再び呼び出されます。
たとえば、コマンドをプロパティにバインドして状態を制御する方法は次のとおりです。
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]
private User? selectedUser;
<!-- Note: this example uses traditional XAML binding syntax -->
<Button
Content="Greet user"
Command="{Binding GreetUserCommand}"
CommandParameter="{Binding SelectedUser}"/>
この例では、生成された SelectedUser
プロパティは、値が変更されるたびに GreetUserCommand.NotifyCanExecuteChanged()
メソッドを呼び出します。 この UI には、GreetUserCommand
への Button
コントロール バインドがあります。つまり、CanExecuteChanged
イベントが発生するたびに、その CanExecute
メソッドが再び呼び出されます。 これにより、ラップされた CanGreetUser
メソッドが評価され、入力 User
インスタンス (UI で SelectedUser
プロパティにバインドされている) が null
かどうかに基づいて、ボタンの新しい状態が返されます。 つまり、SelectedUser
が変更されるたびに、そのプロパティに値があるかどうかに基づいて、GreetUserCommand
が有効になるかならないかが決まります。これは、このシナリオでの望ましい動作です。
Note
このコマンドは、CanExecute
メソッドまたはプロパティの戻り値がいつ変更されたかを自動的に認識しません。 IRelayCommand.NotifyCanExecuteChanged
を呼び出してコマンドを無効にし、リンクされた CanExecute
メソッドの再評価を要求して、コマンドにバインドされているコントロールの視覚的な状態を更新するのは、開発者の責任です。
同時実行の処理
コマンドが非同期の場合は常に、同時実行を許可するかどうかを決定するように構成できます。 RelayCommand
属性を使用する場合は、AllowConcurrentExecutions
プロパティを使用して設定できます。 既定値は false
であり、実行が保留中になるまで、コマンドはその状態を無効として通知することを意味します。 代わりに true
に設定されている場合は、任意の数の同時呼び出しをキューに登録できます。
コマンドがキャンセル トークンを受け入れる場合、同時実行が要求されるとトークンも取り消されることに注意してください。 主な違いは、同時実行が許可されている場合、コマンドは有効なままになり、前の実行が実際に完了するのを待たずに新しい要求された実行を開始することです。
非同期例外の処理
非同期リレー コマンドが例外を処理する方法は 2 つあります。
- 待機と再スロー (既定): コマンドが呼び出しの完了を待機すると、同じ同期コンテキストで例外が自然にスローされます。 これは通常、スローされる例外がアプリをクラッシュさせるだけであることを意味します。これは同期コマンドの動作と一致する動作です (例外がスローされると、アプリもクラッシュします)。
- タスク スケジューラに対する例外のフロー: コマンドがタスク スケジューラに例外をフローするように構成されている場合、スローされる例外はアプリをクラッシュさせるのではなく、公開された
IAsyncRelayCommand.ExecutionTask
を介して使用できるようになり、TaskScheduler.UnobservedTaskException
に引き渡されます。 これにより、より高度なシナリオ (UIコンポーネントがタスクにバインドされ、操作の結果に応じて異なる結果を表示するなど) が可能になりますが、正しく使用するのがより複雑になります。
既定の動作は、コマンドに例外を待機させて再スローさせます。 これは、FlowExceptionsToTaskScheduler
プロパティを使用して構成できます。
[RelayCommand(FlowExceptionsToTaskScheduler = true)]
private async Task GreetUserAsync(CancellationToken token)
{
User user = await userService.GetCurrentUserAsync(token);
Console.WriteLine($"Hello {user.Name}!");
}
この場合、例外によってアプリがクラッシュすることはないので、try/catch
は必要ありません。 これにより、他の関連のない例外も自動的に再スローされないため、個々のシナリオにアプローチする方法を慎重に決定し、残りのコードを適切に構成する必要があることに注意してください。
非同期操作のキャンセル コマンド
非同期コマンドの最後のオプションの 1 つは、cancel コマンドの生成を要求する機能です。 これは、操作の取り消しを要求するために使用できる非同期リレー コマンドをラップする ICommand
です。 このコマンドは、任意の時点で使用できるかどうかを反映するようにその状態を自動的に通知します。 たとえば、リンクされたコマンドが実行されていない場合、その状態も実行可能でないと報告されます。 これは次のように使用できます。
[RelayCommand(IncludeCancelCommand = true)]
private async Task DoWorkAsync(CancellationToken token)
{
// Do some long running work...
}
これにより、 DoWorkCancelCommand
プロパティも生成されます。 これにより、ユーザーが保留中の非同期操作を簡単にキャンセルできるように、他の UI コンポーネントにバインドできます。
カスタム属性の追加
観測可能なプロパティと同様に、生成されるプロパティのカスタム属性のサポートも RelayCommand
ジェネレーターに含まれています。 これを利用するには、注釈付きメソッドに対して属性リストで [property: ]
ターゲットを使用するだけで、MVVM ツールキットによってこれらの属性が生成されたコマンド プロパティに転送されます。
たとえば、次のようなメソッドを考えてみましょう。
[RelayCommand]
[property: JsonIgnore]
private void GreetUser(User user)
{
Console.WriteLine($"Hello {user.Name}!");
}
これにより、[JsonIgnore]
属性を持つ GreetUserCommand
プロパティが生成されます。 メソッドをターゲットとする属性リストはいくつでも使用でき、そのすべてが生成されたプロパティに転送されます。
例
MVVM Toolkit