这个让我有点困惑。
我编写了一些UIElement的扩展方法,以提供某些鼠标事件的可观察对象。以下是相关方法:
到目前为止,一切都非常简单。然后我创建了一个复合事件来检测当用户开始在 UIElement 上拖动时(这全部是由我的自定义控件使用的,其确切的性质并不特别相关)。首先,这里有一个小助手函数来查看用户是否已经拖动了最小拖动距离:
这一切都很好地运作,经过了长时间的努力和烦恼,因为没有关于基于 Rx 的 WPF 控件拖放的像样示例(它们中的大多数都是简单地在画布中拖动图像的复制)。
问题出现在窗口最大化时,特别是通过双击标题栏。如果在最大化布局中,我的一个控件订阅了自己的 ObserveMouseDrag(),并且它正好在鼠标光标下面,那么它会接收到一个 MouseLeftButtonDown 和一个 MouseMove,其中一个显然在最大化之前,另一个在之后。结果就是它开始了一个拖动事件,但由于鼠标按钮已释放,它立即停止,然后往往会导致控件落在自身上,在此应用程序的某些情况下实际上会有所作为。
这非常奇怪,因为我不明白为什么双击最大化会引发MouseDown和MouseMove。毕竟,所有与此相关的鼠标事件都应该由Windows处理,因为我没有使用自定义窗口边框或类似的东西。
那么,有人有任何想法吗?
第二天...
我编写了一些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或鼠标指针带着鼠标按钮离开为止。