在WPF UserControl上使用Rx over事件,为什么当窗口最大化时控件会收到mousedown和mousemove?

这个让我有点困惑。

我在UIElement上编写了一些扩展方法,为一些鼠标事件提供Observable。 以下是相关的:

public static IObservable<EventPattern> ObserveMouseLeftButtonDown(this UIElement element) { return Observable.FromEventPattern(element, "MouseLeftButtonDown"); } public static IObservable<EventPattern> ObserveMouseMove(this UIElement element) { return Observable.FromEventPattern(element, "MouseMove"); } 

到目前为止,这简直令人费解。 然后我创建一个复合事件来检测用户何时开始拖动UIElement(这全部由我的自定义控件使用,其确切性质并不特别相关)。 首先,这是一个小辅助函数,用于查看用户是否拖动了最小拖动距离:

 private static bool MinimumDragSeen(Point start, Point end) { return Math.Abs(end.X - start.X) >= SystemParameters.MinimumHorizontalDragDistance || Math.Abs(end.Y - start.Y) >= SystemParameters.MinimumVerticalDragDistance; } 

然后复合观察本身:

 public static IObservable<EventPattern> ObserveMouseDrag(this UIElement element) { return Observable.CombineLatest( element.ObserveMouseLeftButtonDown().Select(ep => ep.EventArgs.GetPosition(element)), element.ObserveMouseMove().Where(ep => ep.EventArgs.LeftButton == MouseButtonState.Pressed), (md, mm) => new { Down = md, Move = mm }) .Where(i => MinimumDragSeen(i.Down, i.Move.EventArgs.GetPosition(element))) .Select(i => i.Move); } 

这一切都运行得很好,经过大量的咬牙切齿和烦恼之后,没有任何体面的基于Rx的拖放与WPF控件的实例(大多数是在canvas中拖动图像的天真简单的重复)。

当窗口最大化时,问题就出现了,特别是双击标题栏。 如果在最大化的布局中,我订阅了自己的ObserveMouseDrag()的一个控件最终在鼠标光标下接收了一个MouseLeftButtonDown和一个MouseMove,其中一个显然在最大化之前,另一个在之后。 最终结果是它启动了一个拖动事件,然后在释放鼠标按钮时立即停止,然后往往导致控件掉落到自身上,这在这个应用程序的某些情况下实际上做了一些事情。

这是非常奇怪的,因为我不明白为什么我应该接收由双击最大化引起的MouseDown和MouseMove。 当然,所有涉及的鼠标事件都应由Windows处理,因为我没有使用自定义窗口边框或类似的东西。

那么,有没有人有任何想法?

第二天……

固定它! (在Lee的回答下面有一些帮助,这个问题: 使用Rx确定鼠标拖动结束的正确方法是什么? )

代码现在看起来像这样:

 public static IObservable<EventPattern> ObserveMouseDrag(this UIElement element) { var mouseDown = element.ObserveMouseLeftButtonDown().Select(ep => ep.EventArgs.GetPosition(element)); var mouseMove = element.ObserveMouseMove(); var stop = Observable.Merge( element.ObserveMouseUp(), element.ObserveMouseLeave().Where(ep => ep.EventArgs.LeftButton == MouseButtonState.Pressed) ); return mouseDown.SelectMany( md => mouseMove .Where(ep => ep.EventArgs.LeftButton == MouseButtonState.Pressed) .Where(ep => MinimumDragSeen(md, ep.EventArgs.GetPosition(element))) .TakeUntil(stop) ); } 

并处理最小拖动距离,鼠标移动事件以及拖动完全正常工作所需的各种事物。 最大化的bug也消失了(虽然我仍然不完全理解那个,我怀疑鼠标处理可能与它有关)。

这里的关键是使用SelectMany处理来自mouseMove的多个事件流,直到控件中的mouseUp或鼠标指针离开鼠标按钮。

听起来它可能是一个错误,因为我不确定为什么当你最大化窗口时鼠标会先移动。

我会使用selectMany + takeuntil而不是最新的组合。 即:

 var mouseDown = element.ObserveMouseLeftButtonDown().Select(ep => ep.EventArgs.GetPosition(element)); var mouseUp = element.ObserveMouseLeftButtonUp(); var mouseMove = element.ObserveMouseMove(); var from md in mouseDown from mm in mouseMove.TakeUntil(mouseUp) where MinimumDragSeen(md, mm.EventArgs.GetPosition(element))) select mm; 

现在这只会在鼠标停止时开始移动,并在鼠标启动时立即将其杀死。 我不认为你的代码中有MouseUp的function。

我还会考虑尽量避免存储元素并将其用作状态,因为我认为这可以通过事件Args获得。 我还会考虑在MouseMove上使用Zip,这样你就可以得到最后一次鼠标移动变化的增量(如果合适的话)。

我真的很想知道你在做什么。 我正在写一本关于Rx的书,在工作大楼里我刚刚完成了一些使用Drag Drop的WPF控件。

希望有所帮助

背风处