3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

長押しで発火するボタンを作る

Posted at

概要

一定時間長押しをして発火するようなボタンを作りたいということがよくあります。
ここではWPFでReactiveExtensionsを用いて次の3種類のボタンを作ります。

  • 単純に長押しをしたら発火
  • 長押しして発火するまでのProgressも表示して発火
  • 長押しを止めたらProgressが戻り、再度押したら再開するResumableなもの

下準備

こんな感じの拡張メソッドを先に用意します。

ButtonExtensions.cs
public static class ButtonExtensions
{
    public static IObservable<MouseButtonEventArgs> PreviewMouseDownAsObservable(this ButtonBase button)
            => Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
                h => (s, e) => h(e),
                h => button.PreviewMouseDown += h,
                h => button.PreviewMouseDown -= h);

    public static IObservable<MouseButtonEventArgs> PreviewMouseUpAsObservable(this ButtonBase button)
            => Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
                h => (s, e) => h(e),
                h => button.PreviewMouseUp += h,
                h => button.PreviewMouseUp -= h);
}

単純に長押ししたら発火

ボタン上でマウスダウンされたらタイマー開始、マウスアップがあったら中断ですね。

public static IObservable<long> LongPressAsObservable(this ButtonBase button, TimeSpan time)
{
    var down = button.PreviewMouseDownAsObservable();
    var up = button.PreviewMouseUpAsObservable();

    return down
        .Select(_ => Observable.Timer(time).TakeUntil(up))
        .Switch();
}

使う時はこんな感じです。
LongPressという名前のボタンがあると思って下さい。
この例では2秒で発火しています。

this.LongPress
    .LongPressAsObservable(TimeSpan.FromSeconds(2))
    .ObserveOn(SynchronizationContext.Current) //.NET5にはObserveOnDispatcherがないので
    .Subscribe(_ => this.TestMessage.Text += $"LongPressed!:{DateTime.Now}\n"); //実行したいメソッド

Progressも付ける

上ではシンプルなのを作りましたが、Progressが見えないといつまで押せばいいのか不安になりますね。
というわけでProgressがわかるようにしましょう。

public static IObservable<double> ProgressAsObservable(this ButtonBase button, TimeSpan time)
{
    var down = button.PreviewMouseDownAsObservable();
    var up = button.PreviewMouseUpAsObservable();

    //100になるまでGenerateしてもらいます。この100がトリガーです。
    var progress = down
                .Select(_ => Observable
                    .Generate(0d, i => i <= 100, i => ++i, i => i, i => time)
                    .TakeUntil(up));

    return progress.Switch();
}

//ProgressというボタンとProgressBarというプログレスバーがあったとします
this.Progress
    .ProgressAsObservable(TimeSpan.FromMilliseconds(1))
    .ObserveOn(SynchronizationContext.Current)
    .Subscribe(x =>
    {
        this.ProgressBar.Value = x;
        if (x.Equals(100d))
            this.TestMessage.Text += $"ProgressCompleted!:{DateTime.Now}\n";
    });

ProgressをResumableにする

途中でボタンを押すのを止めたらProgressを戻して欲しい時もあります。
一瞬で0に戻ったらびっくりしますしね。
ただ100%完了してるのに戻られても困るからその辺りも何とかしましょう。

public static IObservable<double> ResumableProgressAsObservable(this ButtonBase button, TimeSpan time)
{
    double value = 0;
    double limit = 100d;
    var down = button.PreviewMouseDownAsObservable();
    var up = button.PreviewMouseUpAsObservable();

    var increment = down
        .Do(_ => 
        {
            if (value.Equals(limit))
                value = 0;
        })
        .Select(_ => Observable.Generate(value, i => i <= 100, i => ++i, i => i, i => time).TakeUntil(up));

    var decrement = up
        .Where(_ => !value.Equals(limit))
        .Select(_ => Observable.Generate(value, i => i >= 0d, i => --i, i => i, i => time).TakeUntil(down));

    return Observable.Merge(increment, decrement).Switch().Do(x => value = x);
}

// Resumableという名前のボタンがあるとします。
this.Resumable
    .ResumableProgressAsObservable(TimeSpan.FromMilliseconds(1))
    .ObserveOn(SynchronizationContext.Current)
    .Subscribe(x =>
    {
        this.ProgressBar.Value = x;
        if (x.Equals(100d))
            this.TestMessage.Text += $"ResumableProgressCompleted!:{DateTime.Now}\n";
    });

もう少しスマートに書きたいですね。

補足

Tabキー等でフォーカスを失った際にもタイマーの進行を中止させたい場合は、LostFocusを監視しましょう。

ボタン上からマウスが外れた時は単純にMouseLeaveを監視すればいい、というわけにはならないのが辛いですね。
ボタンがマウスキャプチャをリリースしている必要があったりします。

ソースコード

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?