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

3
这个让我有点困惑。
我编写了一些UIElement的扩展方法,以提供某些鼠标事件的可观察对象。以下是相关方法:
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);
}

这一切都很好地运作,经过了长时间的努力和烦恼,因为没有关于基于 Rx 的 WPF 控件拖放的像样示例(它们中的大多数都是简单地在画布中拖动图像的复制)。
问题出现在窗口最大化时,特别是通过双击标题栏。如果在最大化布局中,我的一个控件订阅了自己的 ObserveMouseDrag(),并且它正好在鼠标光标下面,那么它会接收到一个 MouseLeftButtonDown 和一个 MouseMove,其中一个显然在最大化之前,另一个在之后。结果就是它开始了一个拖动事件,但由于鼠标按钮已释放,它立即停止,然后往往会导致控件落在自身上,在此应用程序的某些情况下实际上会有所作为。
这非常奇怪,因为我不明白为什么双击最大化会引发MouseDown和MouseMove。毕竟,所有与此相关的鼠标事件都应该由Windows处理,因为我没有使用自定义窗口边框或类似的东西。
那么,有人有任何想法吗?
第二天...

修复了!(在Lee的回答和这个问题的帮助下: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)
        );
    }

我会很好地处理最小拖动距离,鼠标弹起事件和所有必要的拖动功能。 最大化bug也已经消失了(虽然我仍然不完全理解那个问题,我怀疑鼠标弹起处理可能与其有关)。

关键在于使用SelectMany来处理来自mouseMove的多个事件流,直到控件中的任一mouseUp或鼠标指针带着鼠标按钮离开为止。


我的解决方案有些不同,因为我使用了DoDragDrop,但这个答案很有帮助。此外,我对MinimumDragSeen方法进行了一些调查,显然它并不那么简单。底层Windows API调用的规范表明这些数字可以是负数。最终,我找到了这篇文章http://blogs.msdn.com/b/oldnewthing/archive/2010/03/04/9972520.aspx,我认为这是相当明确的。我的C#版本:var rc = new Rect(start, start); rc.Inflate(SystemParameters.MinimumHorizontalDragDistance, SystemParameters.MinimumVerticalDragDistance); return !rc.Contains(now); - jdasilva
1个回答

2

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

我建议使用selectMany + takeuntil代替combine latest。例如:

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功能。我建议尝试避免存储元素并将其用作状态,因为我认为这可以通过事件参数获得。如果适当的话,我还建议在MouseMove上使用Zip,以便只获取最后一个鼠标移动更改的增量。我非常想听听您正在做什么。我正在撰写一本关于Rx的书,并在工作中构建一些使用拖放的WPF控件。希望这有所帮助。Lee

你说得对,我们不处理鼠标抬起事件,这会导致一个bug,我们可以演示一下。最好有一个修复方法,问题是元素本身的MouseLeftButtonUp是不够的,因为MouseLeftButtonUp可能在其他地方触发。最终,它需要在任何地方释放鼠标(即使在应用程序外部)时触发以正确重置拖动。 - Matthew Walton
您的代码存在一个有趣的 bug,即在第二次拖动操作时,似乎 where 子句总是为 true,在可观察对象触发之前没有观察到最小拖动距离,而第一次则有。我怀疑它实际上并没有完全重置,并仍在使用它看到的第一个 mouseDown。至于我们正在做什么——拖放重新排列类似于日程表的网格布局中的条目。 - Matthew Walton
我接受这个答案,因为它让我朝着正确的方向前进,尽管它并不完全是我所需要的。非常感谢!我也很想听听你关于 Rx 的书,特别是如果你要讲述比普遍的“拖动图像”示例更复杂的内容。 - Matthew Walton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接