Twitter で @amay077 さんが以下のような内容を呟いていたのを見て、頭の体操と思って Rx を使って解決してみました。
3秒間押し続けてないと onClick 発火しないボタン的なの作りたいけど何が手っ取り早いかなー?エフェクト考えなくてもまあまあめんどいな。
— あめいぱわーにおまかせろ! (@amay077) 2019年6月7日
ボタンのイベントが取れれば動作確認できるので、今回は雑に Windows Forms で試してみます。WPF でも Xamarin でも同様なことはできるでしょう。
追記
以下の実装でバグ報告があったので修正しました...w
ざっくり実装
まずマウスイベントを Rx 化します。イベントをハンドラのまま扱おうとするとだいぶ面倒ですが、シーケンスとして扱えるようにすると一気に楽になれます。
public static class ControlExtensions { public static IObservable<MouseEventArgs> MouseDownAsObservable(this Control control) => Observable.FromEvent<MouseEventHandler, MouseEventArgs> ( handler => (sender, e) => handler(e), handler => control.MouseDown += handler, handler => control.MouseDown -= handler ); public static IObservable<MouseEventArgs> MouseUpAsObservable(this Control control) => Observable.FromEvent<MouseEventHandler, MouseEventArgs> ( handler => (sender, e) => handler(e), handler => control.MouseUp += handler, handler => control.MouseUp -= handler ); }
この MouseDown
と MouseUp
のイベントを Zip
メソッドで対になるようにし、それぞれのイベントが発火された時間の差分を取ります。これが一定時間を超えていれば OK ですね!ちょっと汎用化して拡張メソッドとして切り出すと以下のような感じになるでしょう。
// メソッド名がこんなのでいいかはさておき... public static IObservable<Unit> ClickIf(this Control control, TimeSpan threshold) { var down = control.MouseDownAsObservable().Select(_ => DateTimeOffset.Now); var up = control.MouseUpAsObservable().Select(_ => DateTimeOffset.Now); return down .Zip(up, (downTime, upTime) => upTime - downTime) // 経過時間を算出 .Where(x => x >= threshold) // 一定時間を超えていたら発火 .Select(_ => Unit.Default); }
使ってみる
上記のように実装しておけば、こんなに簡単に実装できます。Rx を使えば押し続けないと発火しないボタンも簡単に作れちゃいますね!
public partial class MainForm : Form { public MainForm() { this.InitializeComponent(); this.button .ClickIf(TimeSpan.FromSeconds(3)) // 3 秒押し続けたら .Subscribe(_ => Debug.WriteLine("fire!!")); // 発火! } }