なべひろBlog

プログラミングをメインに仕事に関するアレコレを発信しています。

WPFでボタンが押された時の処理が終わるまで簡単にボタンを押せないようにする

存在は知ってましたが他に優先順位の高い案件が多かったので後回しになってました。

結果から言えばもっと早くやるべきでしたね(汗

簡単に言えばボタンを押して処理が終わるまでボタンを押せないようにするのが楽にできます。

私のような装置系は特にボタンを押してから処理が終わるまでは時間がかかり、その間はボタンが押せないようにするのは当然のふるまいです。

(たまに手抜きでボタンが押せるようになっているアプリも見受けられますが...)

今まではReactiveCommandにSelectで条件を記述したりボタンのIsEnabledにboolをBindingしてましたが、AsyncReactiveCommandを使う事で余計な手間が省けます。

但し装置系でよくあるボタン構成で「リセット」と「スタート」のボタンがあり、装置立ち上げ直後は「リセット」だけ有効、リセット後は「スタート」だけ有効、動作中は「リセット」も「スタート」も無効といったパターンでは工夫が必要ですが、最低限そのボタンに該当する処理が行われている時はそのボタンの有効、無効は気にしなくてOKなのは手間が省けます。

Viewは何の変哲もない

<Button Content="Button1" Command="{Binding Button1Command}" />
<Button Content="Button2" Command="{Binding Button2Command}" />
<Button Content="Button3" Command="{Binding Button3Command}" />

今回のサンプルでは「Button1」は単独で処理中は押せなくなり、「Button2」と「Button3」はいずれかのボタンが押され処理中はどちらのボタンも押せなくしています。

そして処理の実行は非同期で行われ、処理中でも画面が固まる事はありません。

// どこかで宣言した変数
public AsyncReactiveCommand Button1Command { get; }
public AsyncReactiveCommand Button2Command { get; }
public AsyncReactiveCommand Button3Command { get; }
public ReactivePropertySlim<bool> IsButtonEnable = new ReactivePropertySlim<bool>(true);

コンストラクタに以下を記述

Button1Command = new AsyncReactiveCommand().WithSubscribe(Button1Processing);
Button2Command = IsButtonEnable.ToAsyncReactiveCommand().WithSubscribe(Button2Processing);
Button3Command = IsButtonEnable.ToAsyncReactiveCommand().WithSubscribe(Button3Processing);

「Button1」が押されると「Button1」だけが押せなくなりButton1Processingメソッドが非同期で実行されます。

「Button2」が押されると「Button2」と「Button3」が押せなくなりButton2Processingメソッドが非同期実行されます。

「Button3」が押されると「Button2」と「Button3」が押せなくなりButton3Processingメソッドが非同期実行されます。

Button2とButton3は同じReactivePropertySlimを共有する事でどちらかが実行している時はもう片方は実行できない(ボタンが無効状態になる)を簡単に実現しています。

余計なボタンを押されるのを抑制する制御は地味ならが煩わしい処理でこれが簡単に実現できるのはとても便利です。

参考例をシンプルにするためDisposeの処理は省略していますが、詳しくはサンプルを参照お願いします。

サンプルプログラムはこちら