概要
ReactiveProeprty 5.2.0の新機能AwaitableなReactivePropertyを使ってみました。
ReactivePropertyの説明自体は下記を参照ください。
https://blog.okazuki.jp/entry/2015/12/05/221154
AwaitableなReactivePropertyとは
ReactivePropertyがAwaitable パターンを実装したことで、await
で値の変更を待機することができるようになりました。
今までもSubscribe
で値の変更を購読することはできましたが、
- 1度だけ購読して何かしたい
- 値の変更を待機して、次の処理を続けて書きたい
といった時には少々手間でした。
Awaitableパターンが実装されたことにより、下記のような形で値の変更を待機することができます。
var newValue = await MyReactiveProperty;
他にもSubscribe
との相違点としては、ReactivePropertyをSubscribe
するとMode
がRaiseLatestValueOnSubscribe
ならばすぐに値が返ってきますが、
await
した場合は、Mode
に関係なく変更するまで待機します。
なお、Awaitableになったのは狭義のReactivePropertyだけでなく、
(ReadOnly)ReactiveProperty(Slim)と(Async)ReactiveCommand(Generic)もです。
つまりだいたい全部です。
ReactiveCommandの場合はCanExcute
の変化ではなく、Excute
呼び出しを待機して、返り値はExcute呼び出し時の引数です。
注意点として、今回のバージョンには破壊的変更が含まれています。
あまり実用的ではないのですが、5.1以前でもawaitで待機することは可能でした。
この場合はReactivePropertyがDispose
されるまで待機して、最後の値が返ってきます。
これはReactivePropertyというよりも、IObsevable<T>
の機能です。
デモ
デモプログラムを作ってみました。
WPFでファイルの変更を監視して、それをTextBoxに表示するアプリケーションを作ります。
ボタン押下後に監視を開始して、変更されたら1度だけTextBoxに反映させます。
デモ実行結果
Button押下後、awaitで待機中はボタンが無効になっています。
TextBoxに入力された文字が大文字化されて入ります。以降はファイルを変更しても反映されません。
ViewModel
ViewModelではAsyncReactiveCommand
を用意して、コマンドのSubscribe
内でmodelのReactivePropertySlim
の変更をawait
で待機しています。
待機している間、コマンドのCanExcute
はfalseになります。
ReactivePropertySlim
が変更されたら、その値を大文字化して、VM内のReactiveProperty
に反映させます。
class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Model model = new Model();
public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>("TARO");
public AsyncReactiveCommand UpdateWaitLoadCommand { get; } = new AsyncReactiveCommand();
public MainWindowViewModel()
{
UpdateWaitLoadCommand.Subscribe(DoSomeAsync);
}
private async Task DoSomeAsync()
{
model.StartLoad();
string loadText = await model.LoadedText; //ReactivePropertyの変更を待つ
Name.Value = loadText.ToUpper();
}
}
Model
Modelでは一定時間ごとにファイルを見て、Model内のReactivePropertySlim
に反映しています。
class Model
{
public ReactivePropertySlim<string> LoadedText { get; } = new ReactivePropertySlim<string>("");
private const string inputPath = "inputText.txt";
private Timer timerLoad = new Timer();
public Model()
{
timerLoad.Elapsed += TimerLoad_Elapsed;
}
private void TimerLoad_Elapsed(object sender, ElapsedEventArgs e)
{
string loadedText = File.ReadAllText(inputPath);
LoadedText.Value = loadedText;
}
/// <summary>
/// ファイル監視の開始
/// </summary>
internal void StartLoad()
{
using (File.Create(inputPath)) { }
timerLoad.Start();
}
}
View
ViewではVMのReactiveProperty
とAsyncReactiveCommand
にBindingしています。
<Window
x:Class="TestReactiveProperty52.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestReactiveProperty52"
Width="300"
Height="150">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Name.Value, UpdateSourceTrigger=PropertyChanged}" />
<Button Command="{Binding UpdateWaitLoadCommand}" Content="ファイルが変更されるのを待って反映" />
</StackPanel>
</Window>
参考
https://runceel.github.io/ReactiveProperty/advanced/awaitable/#awaitable
https://ufcpp.net/study/csharp/sp5_awaitable.html
環境
VisualStudio2017
.NET Framework 4.7.1
C#7.1
ReactiveProperty 5.2.0