在WPF UserControl上使用Rx-over事件,为什么当窗口最大化时,控件会收到mousedown和mouse
本文关键字:控件 最大化 mouse mousedown 窗口 UserControl WPF 为什么 事件 Rx-over | 更新日期: 2023-09-27 18:24:14
这个让我有点困惑。
我已经在UIElement上编写了一些扩展方法,以便在一些鼠标事件上提供Observables。以下是相关的:
public static IObservable<EventPattern<MouseEventArgs>> ObserveMouseLeftButtonDown(this UIElement element)
{
return Observable.FromEventPattern<MouseEventArgs>(element, "MouseLeftButtonDown");
}
public static IObservable<EventPattern<MouseEventArgs>> ObserveMouseMove(this UIElement element)
{
return Observable.FromEventPattern<MouseEventArgs>(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<MouseEventArgs>> 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);
}
这一切都很好,在经历了很多咬牙切齿和烦恼之后,没有任何像样的WPF控件基于Rx的拖放示例(其中大多数都是在画布中拖动图像的天真简单的复制品)。
当窗口最大化时,问题就出现了,特别是双击标题栏。如果在最大化布局中,我的一个订阅了自己的ObserveMouseDrag()的控件最终位于鼠标光标下,它会收到一个MouseLeftButtonDown和MouseMove,其中一个显然在最大化之前,另一个在最大化之后。最终结果是,它启动了一个拖动事件,然后在释放鼠标按钮时立即停止,然后往往会导致控件掉落到自己身上,在某些情况下,这个应用程序实际上会起到一些作用。
这非常奇怪,因为我不明白为什么我应该接收双击最大化引起的MouseDown和MouseMove。当然,所有涉及的鼠标事件都应该由Windows处理,因为我没有使用自定义窗口边框或类似的东西。
那么,有人有什么想法吗?
第二天
修复它!(在李下面的回答和这个问题的帮助下:使用Rx确定鼠标拖动结束的正确方法是什么?)
代码现在看起来是这样的:
public static IObservable<EventPattern<MouseEventArgs>> 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)
);
}
并处理最小拖动距离、鼠标向上事件以及拖动完全正常工作所需的各种事情。最大化错误也已经消失了(尽管我仍然不完全理解这个错误,但我怀疑鼠标向上的处理可能与此有关)。
这里的关键是使用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的功能。
我也会考虑尽量避免存储元素并将其用作状态,因为我认为这可以通过事件Args获得。我也会考虑在鼠标移动上使用Zip,这样你就可以只得到最后一次鼠标移动变化的增量(如果合适的话)。
我真的很想听听你在做什么。我正在写一本关于Rx的书,在工作大楼里,我刚刚完成了一些使用Drag-Drop的WPF控件的构建。
希望能帮助
Lee