3

这让我有点困惑。

我在 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);
}

这一切都很好,经过很多咬牙切齿和烦恼,没有任何像样的基于 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)
        );
    }

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

这里的关键是使用 SelectMany 处理从 mouseMove 到控件中的 mouseUp 或鼠标指针离开它并按下鼠标按钮的多个事件流。

4

1 回答 1

2

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

我会使用 selectMany + takeuntil 而不是 combine latest。IE:

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 获得。我还会考虑在 MouseMove 上使用 Zip,这样您就可以获得最后一次鼠标移动更改的增量(如果合适)。

我真的很想听听你在做什么。我正在写一本关于 Rx 的书,在工作中我刚刚完成了一些使用拖放的 WPF 控件的构建。

希望有帮助

于 2012-02-13T17:33:38.000 回答