コマンドの有効・無効の切り替えは、
ReactiveProperty>bool< から ToReactiveCommand() を使用して作成する方法があります。
ただ、この方法だと ViewModel 内に bool 型のフラグが増えてしまいます。
そこで今回は、プロパティの変化を条件式として利用することで、
コマンドの有効・無効を切り替える方法を試してみたいと思います。
ViewModel
// プロパティ
public ReactivePropertySlim<string> TextBox1 { get; private set; }
= new("");
// コマンド
public ReactiveCommandSlim Button1Command { get; }
// コンストラクタ
public MainWindowViewModel()
{
//
TextBox1
.Skip(1)
.Subscribe(value =>
{
Debug.Print($"タイトルの値が{value}に変更された");
})
.AddTo(Disposable);
Button1Command = TextBox1
.Select(x => x != "")
.ToReactiveCommandSlim()
.WithSubscribe(()=>
{
Debug.Print("Button1 Click");
})
.AddTo(Disposable);
}
Button1Command = TextBox1
.Select(x => x != "")
TextBox1の変更で、文字列が空でないこと条件にしている。
XAML
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<TextBox Text="{Binding TextBox1.Value, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Button1" Command="{Binding Button1Command}" />
</StackPanel>
</Grid>
</Window>
サンプルコードでは
TextBoxとButtonだけで、Buttonが無効状態だと、
フォーカスの移動先が無いので、変更通知が行われない。
しかたが無いので、UpdateSourceTrigger=PropertyChangedをセットすることで、
1文字入力ごととに変更通知が行われるようにしています。
実行
初期はボタンが押せない

TextBoxに1文字でも入力があると、
ボタンが有効化

TextBoxの文字列が空になると
ボタンは無効化される。

ViewModelでUIの状態を IsXXX のようなプロパティとして定義するのは、
XAMLでバインドする場合や、他の処理から参照する必要がある場合に限られます。
それ以外の場合、IsXXX をプロパティとして用意しても、
単なるフラグのON/OFFを保持するだけになってしまいます。
そのため、ViewModelのプロパティとして公開する意味はあまりありません。
ReactivePropertyでは、必要な場面で無名の ReactiveProperty>bool< を生成することができます。
名前を持たないため、ViewModelのプロパティとして定義する必要がなく、余計なフラグを増やさずに済みます。
また、特別な意味を持つ状態として扱う必要もありません。
初めは、この連動の仕組みが少し面倒に感じました。
バインド(イベント)の管理に加えて、コマンドの状態も別途管理する必要があるからです。
しかし逆に考えると、バインディングはかなり大まかな仕組みです。
実際には「このタイミングではバインドしてほしくない」と感じる場面にも、しばしば遭遇します。
そのため、バインディングだけに頼るのではなく、
条件式を利用してUIの状態を制御する方法も有効だと思います。
ただ、感覚的にはイベントドリブンでプログラミングしているのと、それほど大きくは変わらないように感じます。
どちらの場合でも、適切なタイミングを管理する必要があります。
早すぎたり、遅すぎたりと、ちょうど良いタイミングを探す作業になる点は同じです。
Rxでは、そのタイミングを条件式として表現できますが、
その条件を成立させるための環境を整える必要がある点は、イベントドリブンと似ている部分だと感じました。

コメント