Help us understand the problem. What is going on with this article?

AsyncReactiveCommandでWPFのお手軽ダブルクリック抑制

More than 1 year has passed since last update.

背景

世の中には特に意味はなくても、ボタンをダブルクリックする人種がいるのです。
「ボタンはダブルクリックするものだと思っていた」
「不安なのでダブルクリックした」
「せっかちだから」
理由はそれぞれですが、
当然クリックイベントに結び付けられた処理は2回行われてしまい、
(押した人からすると)意図しない動作をし、激怒します。
AsyncReactiveCommandを使用すると簡単にこれを防ぐことが出来ます。

あなたのプログラムに意図せず2回動作したらまずい処理(ex.課金アイテムの使用、発注書の送信など)が含まれていたら、AsyncReactiveCommandを検討する価値はあります。

ReactiveProperty v2.8以前

AsyncReactiveCommand登場前のV2.8以前ではCommandとは別にプロパティを作り、コマンドの実行可否を結びつける必要がありました。
参考:ReactiveProperty で2度押し防止(Using使ったやつ)
しかし複数のCommandを1つのプロパティに結びつけた場合、
全てのCommandが同時に押せなくなってしまうため、
それはそれで複数のCommand(ボタン)を連続で押したいせっかちな人は激怒します。

かといってダブルクリックを防止するためだけに、全てのCommandで
A. プロパティを同じ数用意する
B. RxのBufferなどで一定時間以内のイベントをまとめる
のもめんどうです。
Aの場合ですとかんな感じです。

v2.8以前
class MainWindowViewModel
{
    public ReactiveProperty<bool> IsBusy1 { get; }
        = new ReactiveProperty<bool>(false);
    public ReactiveProperty<bool> IsBusy2 { get; }
        = new ReactiveProperty<bool>(false);

    public ReactiveCommand ButtonClickAsyncCommand1 { get; }
    public ReactiveCommand ButtonClickAsyncCommand2 { get; }

    public MainWindowViewModel()
    {
        ButtonClickAsyncCommand1 = IsBusy1.Select(x => !x).ToReactiveCommand();
        ButtonClickAsyncCommand2 = IsBusy2.Select(x => !x).ToReactiveCommand();

        ButtonClickAsyncCommand1.Subscribe(async _ =>
        {
            IsBusy1.Value = true;
            Debug.WriteLine("Clicked ASync 1");
            await Task.Delay(500);
            IsBusy1.Value = false;

        });
        ButtonClickAsyncCommand2.Subscribe(async _ =>
        {
            IsBusy2.Value = true;
            Debug.WriteLine("Clicked ASync 2");
            await Task.Delay(500);
            IsBusy2.Value = false;

        });
    }
}

無駄に長いです。
上記リンク記事の最後にもあるように
「IsBusy プロパティを持った ReactiveCommand」
があればこれを解決できます。
そしてこれがまさにv2.9で登場したAsyncReactiveCommandです。

AsyncReactiveCommandを使用

AsyncReactiveCommandを使用してコードを書き直してみます。

AsyncReactiveCommand使用
class MainWindowViewModel
{
    public AsyncReactiveCommand ButtonClickAsyncCommand1 { get; }
        = new AsyncReactiveCommand();
    public AsyncReactiveCommand ButtonClickAsyncCommand2 { get; }
        = new AsyncReactiveCommand();

    public MainWindowViewModel()
    {
        ButtonClickAsyncCommand1.Subscribe(async _ =>
        {
            Debug.WriteLine("Clicked ASync 1");
            await Task.Delay(500);
        });
        ButtonClickAsyncCommand2.Subscribe(async _ =>
        {
            Debug.WriteLine("Clicked ASync 2");
            await Task.Delay(500);
        });
    }
}

短い。

動作は
スクリーンショット 2017-02-11 13.25.45.png
からボタンをクリックすると
スクリーンショット 2017-02-11 13.25.30.png
になり、0.5秒経過すると上の画面に戻ります。

それぞれのCommandは独立しているので、片方が押された後、別のCommandをすぐ押すこともできます。
なお、Commandが同時に動くのを防ぎたいのであれば、
ReactiveProperty v2.9.0とv3.0.0-pre5をリリースしました。 - かずきのBlog@hatena
のように共通プロパティを使用することも出来ます。

まとめ

AsyncReactiveCommandを使用することで簡単に意図しないダブルクリックを抑制できました。
上記リンクでもそうですが、AsyncReactiveCommandは重たい処理や同時に実行されたくない処理で使用する、というシーンで説明されることが多いです。
しかし重たい処理でなくとも、意図せず2回実行されては困る場合にも有用です。

またバックグラウンドで動く処理などの場合、UI側に処理したことを伝える必要があります。
ただ単に処理が動いたことを伝えたいのであれば、
上記コードのように処理後にAsyncReactiveCommandを0.5秒ほど待機させれば、その間ボタンがグレイアウトしますのでView側には何も書かずにすみます。

おまけ

AsyncReactiveCommandのコードスニペット
これ書いてから、そもそもAsyncReactiveCommandの記事自体がQiitaに無いことに気づきました。
これ使ってガンガンAsyncReactiveCommandを書いて下さい。

環境

VisualStudio2015
.NET Framework 4.6
C#6
ReactiveProperty

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした