在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());
}
答案 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.CaptureMouse
和this.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();