在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,或者鼠标指针按下鼠标按钮。

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

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

我会使用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