使用Reactive Extensions模拟Click on UIElement

时间:2014-03-18 20:45:26

标签: c# wpf system.reactive

在WPF中,我希望能够使用带有反应式扩展的鼠标事件来创建一个像Click事件一样工作的UIElement的observable。有很多使用它来创建拖放行为的例子,但我只是简单的点击就找不到任何东西。

我预计它会涉及MouseLeftButtonDown,MouseLeftButtonUp,MouseLeave和MouseEnter上的observable。但我不确定我需要使用的Merge,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());

}

2 个答案:

答案 0 :(得分:3)

以下似乎有效,但我怀疑它可以以更整洁的方式进行。

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

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

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

EDIT(2):

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

您需要在mouseUp的定义中添加.Select(_ => "U"),向mouseDown添加.Select(_ => "D") ...

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");

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

答案 1 :(得分:0)

正确的方法是使用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();