RelayCommands
RelayCommand
と RelayCommand<T>
は ICommand
の実装で、メソッドやデリゲートを view に公開することができます。これらの型は viewmodel と UI 要素の間でコマンドを binding する手段として機能します。
APIs:
- RelayCommand
- RelayCommand
- IRelayCommand
- IRelayCommand
どのように機能するか (How it works)
RelayCommand
と RelayCommand<T>
の特徴は、つぎのとおりです:
- これらは
ICommand
インターフェースの base 実装を提供しています。 IRelayCommand
orIRelayCommand<T>
インターフェースを実装して CanExecuteChanged イベントを発生させる NotifyCanExecuteChanged メソッドを公開しています。- コンストラクタは、標準的なメソッドの Action や Func
のラムダ式などのラッピングを可能とします。
ICommand の働き (Working with ICommand)
単純な command をセットアップする例は、つぎのとおりです:
public class MyViewModel : ObservableObject { public MyViewModel() { IncrementCounterCommand = new RelayCommand(IncrementCounter); } private int counter; public int Counter { get => counter; private set => SetProperty(ref counter, value); } public ICommand IncrementCounterCommand { get; } private void IncrementCounter() => Counter++; }
XAML はつぎのとおり:
<Page x:Class="MyApp.Views.MyPage" xmlns:viewModels="using:MyApp.ViewModels"> <Page.DataContext> <viewModels:MyViewModel x:Name="ViewModel"/> </Page.DataContext> <StackPanel Spacing="8"> <TextBlock Text="{x:Bind ViewModel.Counter, Mode=OneWay}"/> <Button Content="Click me!" Command="{x:Bind ViewModel.IncrementCounterCommand}"/> </StackPanel> </Page>
Button
は viewmodel の ICommand
にバインドされて private メソッドの IncrementCounter
をラップします。TextBlock は Counter プロパティの値を表示して、プロパティの値が更新されるたびに更新します。
Sample
public class MyViewModel : ObservableObject { public MyViewModel() { IncrementCounterCommand = new RelayCommand(IncrementCounter); } /// <summary> /// Gets the <see cref="ICommand"/> responsible for incrementing <see cref="Counter"/>. /// </summary> public ICommand IncrementCounterCommand { get; } private int counter; /// <summary> /// Gets the current value of the counter. /// </summary> public int Counter { get => counter; private set => SetProperty(ref counter, value); } /// <summary> /// Increments <see cref="Counter"/>. /// </summary> private void IncrementCounter() => Counter++; }
<Page x:Class="MyApp.Views.MyPage" xmlns:viewModels="[viewModels]using:MyApp.ViewModels"> <Page.DataContext> <viewModels:MyViewModel x:Name="ViewModel"/> </Page.DataContext> <StackPanel Spacing="8"> <TextBlock Text="{x:Bind ViewModel.Counter, Mode=OneWay}"/> <Button Content="Click me!" Command="{x:Bind ViewModel.IncrementCounterCommand}"/> </StackPanel> </Page>
AsyncCommands
AsyncRelayCommand
と AsyncRelayCommand<T>
は RelayCommand
の機能を拡張して、非同期操作をサポートする ICommand
の実装です。
APIs:
- AsyncRelayCommand
- AsyncRelayCommand
- RelayCommand
- IAsyncRelayCommand
- IAsyncRelayCommand
どのように機能するか (How it works)
AsyncRelayCommand
と AsyncRelayCommand<T>
の主な特徴はつぎのとおり:
- これらは、ライブラリーに含まれる同期 Commandの機能を拡張して
Task
を返却する delegate をサポートします。 - キャンセルをサポートするために
CancellationToken
引数を追加して非同期関数をラップすることができる。CancellationToken
プロパティ、CanBeCanceled
プロパティ、IsCancellationRequested
メソッドを公開しています。 - 保留中の操作の進行状況を監視するために使用できる
ExecutionTask
プロパティと、操作の完了を確認するために使用できるIsRunning
プロパティを公開しています。これは特に loading のインジケーターなどの UI 要素を binding する際に便利です。 IAsyncRelayCommand
とIAsyncRelayCommand<T>
インターフェースを実装しており viewmodel はこれらを使用して command を公開して、(view-viewmodel 間の)密結合を減らすことができます。例えば、同じ public API サーフェイスを公開するカスタム実装で command を置き換えることが簡単になります。(必要であれば)
非同期コマンドの働き (Working with asynchronous commands)
RelayCommand
のサンプルで説明した内容で、(非同期化した)同サンプルはつぎのとおり:
public class MyViewModel : ObservableObject { public MyViewModel() { DownloadTextCommand = new AsyncRelayCommand(DownloadText); } public IAsyncRelayCommand DownloadTextCommand { get; } private Task<string> DownloadText() { return WebService.LoadMyTextAsync(); } }
UI はつぎのとおり:
<Page x:Class="MyApp.Views.MyPage" xmlns:viewModels="using:MyApp.ViewModels" xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters"> <Page.DataContext> <viewModels:MyViewModel x:Name="ViewModel"/> </Page.DataContext> <Page.Resources> <converters:TaskResultConverter x:Key="TaskResultConverter"/> </Page.Resources> <StackPanel Spacing="8" xml:space="default"> <TextBlock> <Run Text="Task status:"/> <Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask.Status, Mode=OneWay}"/> <LineBreak/> <Run Text="Result:"/> <Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask, Converter={StaticResource TaskResultConverter}, Mode=OneWay}"/> </TextBlock> <Button Content="Click me!" Command="{x:Bind ViewModel.DownloadTextCommand}"/> <ProgressRing HorizontalAlignment="Left" IsActive="{x:Bind ViewModel.DownloadTextCommand.IsRunning, Mode=OneWay}"/> </StackPanel> </Page>
Button
をクリックすると Command が実行されて ExecutionTask
が実行されます。作業が完了するとプロパティは通知を発生させるので UI に反映されます。この場合、タスクのステータスと現在の結果の両方が表示されます。タスクの結果を表示するには TaskExtensions.GetResultOrDefault
メソッドを使用する必要があることに注意してください。これによって、スレッドをロックすることなく(デッドロックが発生する恐れもあるので)、まだ完了していないタスクの結果にアクセスできます。
Sample
public MyViewModel() { DownloadTextCommand = new AsyncRelayCommand(DownloadTextAsync); } public IAsyncRelayCommand DownloadTextCommand { get; } private async Task<string> DownloadTextAsync() { await Task.Delay(3000); // Simulate a web request return "Hello world!"; }
<Page.Resources> <converters:TaskResultConverter x:Key="TaskResultConverter"/> </Page.Resources> <StackPanel Spacing="8"> <TextBlock> <Run Text="Task status:"/> <Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask.Status, Mode=OneWay}"/> <LineBreak/> <Run Text="Result:"/> <Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask, Converter={StaticResource TaskResultConverter}, Mode=OneWay}"/> </TextBlock> <Button Content="Click me!" Command="{x:Bind ViewModel.DownloadTextCommand}"/> <muxc:ProgressRing HorizontalAlignment="Left" IsActive="{x:Bind ViewModel.DownloadTextCommand.IsRunning, Mode=OneWay}"/> </StackPanel>