使用响应式扩展来模拟Click on UIElement



在WPF中,我希望能够使用带有响应式扩展的鼠标事件来为一个像Click事件一样工作的UIElement创建一个可观察对象。有很多使用它来创建拖放行为的例子,但我找不到任何用于简单单击的例子。

我预计它将涉及到MouseLeftButtonDown, MouseLeftButtonUp, MouseLeave和MouseEnter上的可观察对象。但我不确定合并,SelectMany, TakeUntil或TakeWhile我需要使用什么组合。在尝试将其全部包装在一个扩展中,以下是我到目前为止所做的:

public static IDisposable GetClick(this UIElement item, Action clickAction)
        {
            var obs1 = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
                    h => (s, e) => h(s, e),
                    h => item.MouseLeftButtonDown += h,
                    h => item.MouseLeftButtonDown -= h);
            var obs2 = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
                    h => (s, e) => h(s, e),
                    h => item.MouseLeftButtonUp += h,
                    h => item.MouseLeftButtonUp -= h);
            var obs3 = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
                    h => (s, e) => h(s, e),
                    h => item.MouseLeave += h,
                    h => item.MouseLeave -= h);
            var obs4 = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
                    h => (s, e) => h(s, e),
                    h => item.MouseEnter += h,
                    h => item.MouseEnter -= h);
             var finalObs = ???
             return finalObs.Subscribe(x => clickAction.Invoke());

}

下面的代码似乎可以工作,但是我怀疑是否可以用一种更简洁的方式来完成。

var click = mouseEnter
    .SelectMany(_ => mouseDown.TakeUntil(mouseLeave))
    .SelectMany(_ => mouseUp.TakeUntil(mouseLeave).Take(1));

我已经将finalObs重命名为click, obs1重命名为mouseDown, obs2重命名为mouseUp

编辑:添加Take(1)来修复Enigmativity指出的缺陷

编辑(2):

这是另一个我更喜欢的解决方案。

你需要添加一个.Select(_ => "U")到mouseUp的定义,.Select(_ => "D")到mouseDown…

var click = Observable.Merge(mouseDown, mouseUp, mouseLeave, mouseEnter)
    .Scan((s, c) => c == "L" ? "" : s + c)  // Create a string of the events, reset on mouseLeave
    .Where(s => s.Length >= 2 && s.Substring(s.Length - 2) == "DU");

仔细想想,在用户将鼠标放在项目上,然后移到项目外,然后移回并抬起鼠标的情况下,不可能获得完全正确的行为。这是因为当你不在项目上方时,你不会让鼠标向上,所以你不能确定他们没有鼠标向上,然后鼠标向下,而在外面。

正确的方法是使用this.CaptureMousethis.ReleaseMouseCapture,它们解决了在检测鼠标离开和返回的公认答案中的一些问题。使用ReactiveUI绑定事件的完整(未经测试的)解决方案如下:

// Create a factory for capturing the mouse and and releasing it as an 
// IDisposable compatible with Observable.Using
Func<IDisposable> captureDisposable = () => {
        this.CaptureMouse(); 
        return Disposable.Create(()=>this.ReleaseMouseCapture());
};
// Capture the mouse and then release it on mouse up
var up = Observable.Using
    ( captureDisposable
    , capture => this.Events().PreviewMouseUp.Take(1)
    );
// Create the click event
var click = this.Events().PreviewMouseDown.Select(e=>up).Switch();

相关内容

  • 没有找到相关文章

最新更新