MouseDoubleClick事件不会冒泡

我的场景,简化:我有一个包含Employees行的ListView,在每个Employee行中,有一些按钮“Increase”和“Decrease”调整他的工资。

假设在我的程序中,双击Employee行意味着“解雇此人”。

问题是,当我快速点击“增加”时,这会触发ListViewItem上的双击事件。 当然,当我只是增加工资时,我不想解雇人。

根据所有其他事件的工作原理,我希望能够通过在事件上设置Handled=true来解决这个问题。 但是,这不起作用。 在我看来,WPF生成两个独立的,完全未链接的双击事件。

以下是重现我的问题的最小示例。 可见组件:

   

和处理程序代码:

 private void Button_MouseDoubleClick(object s, MouseButtonEventArgs e) { if (!e.Handled) MessageBox.Show("Button got unhandled doubleclick."); e.Handled = true; } private void ListViewItem_MouseDoubleClick(object s, MouseButtonEventArgs e) { if (!e.Handled) MessageBox.Show("ListViewItem got unhandled doubleclick."); e.Handled = true; } 

启动此程序并双击列出的按钮后, 两个消息框按顺序显示。 (此外,此后按钮会卡在向下位置。)

作为一个“修复”,我可以在ListViewItem处理程序上检查附加到事件的可视树,并检查“那里有一个按钮”,从而丢弃该事件,但这是最后的手段。 我想在编写这样一个kludge之前至少要理解这个问题。

有谁知道为什么 WPF会这样做,以及一种优雅的惯用方法来避免这个问题?

我想你会发现MouseDoubleClick事件是MouseDown事件之上的抽象。 也就是说,如果两个MouseDown事件以足够快的速度连续发生,则还会引发MouseDoubleClick事件。 ButtonListViewItem似乎都具有此逻辑,因此可以解释为什么您会看到两个不同的MouseDoubleClick事件。

根据MSDN :

尽管此路由事件似乎遵循通过元素树的冒泡路径,但它实际上是每个UIElement沿元素树引发的直接路由事件。 如果在MouseDoubleClick事件处理程序中将Handled属性设置为true,则沿着路径的后续MouseDoubleClick事件将在Handled设置为false的情况下发生。

您可以尝试在Button上处理MouseDown并将其设置为处理,以便它不会传播到ListViewItem

希望我能亲自validation这一点,但此刻我还没有.NET。

MouseDoubleClick的MSDN文档确实提供了有关如何防止MouseDoubleClick事件冒泡的建议:

想要处理鼠标双击的控件作者应该在ClickCount等于2时使用MouseLeftButtonDown事件。 这将导致Handled的状态在元素树中的另一个元素处理事件的情况下适当地传播。

因此,如果ClickCount为2,您可以处理MouseLeftButtonDown事件并将hanged设置为true。 但是这在Buttons上失败了,因为它们已经处理了MouseLeftButtonDown并且没有引发该事件。

但仍有PreviewMouseLeftButtonDown事件。 当ClickCount等于2时,在按钮上使用它将handle设置为true:

  private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (e.ClickCount == 2) e.Handled = true; } 

好吧,它可能不是优雅或惯用,但你可能比你目前的解决方案更好:

  int handledTimestamp = 0; private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e) { if (e.Timestamp != handledTimestamp) { System.Diagnostics.Debug.WriteLine("ListView at " + e.Timestamp); handledTimestamp = e.Timestamp; } e.Handled = true; } private void Button_MouseDoubleClick(object sender, MouseButtonEventArgs e) { if (e.Timestamp != handledTimestamp) { System.Diagnostics.Debug.WriteLine("Button at " + e.Timestamp); handledTimestamp = e.Timestamp; } e.Handled = true; } 

奇怪的是,如果你没有设置e.Handled = true这不起作用。 如果您没有设置e.Handled并将断点或Sleep放入按钮的处理程序中,您将在ListView的处理程序中看到延迟。 (即使没有明确的延迟,仍然会有一些小的延迟,足以打破它。)但是一旦你设置了e.Handled ,延迟有多长并不重要,它们将具有相同的时间戳。 我不确定为什么会这样,而且我不确定这是否是您可以依赖的记录行为。

由于这个问题没有明确的答案,这是我最终使用的解决方法:

 protected override void ListViewItem_MouseDoubleClick(MouseButtonEventArgs e) { var originalSource = e.OriginalSource as System.Windows.Media.Visual; if (originalSource.IsDescendantOf(this)) { // Test for IsDescendantOf because other event handlers can have changed // the visual tree such that the actually clicked original source // component is no longer in the tree. // You may want to handle the "not" case differently, but for my // application's UI, this makes sense. for (System.Windows.DependencyObject depObj = originalSource; depObj != this; depObj = System.Windows.Media.VisualTreeHelper.GetParent(depObj)) { if (depObj is System.Windows.Controls.Primitives.ButtonBase) return; } } MessageBox.Show("ListViewItem doubleclicked."); } 

出于文档目的,类名在此处不必要地填充完整名称空间。

Control.MouseDoubleClick不是气泡事件,而是直接事件。

由于使用Snoop (浏览可视树和路由事件的工具)检查此问题,我看到“ListView”和“ListBoxItem”的Control.MouseDoubleClick事件一次被触发。 您可以使用此Snoop工具进行检查。


首先,要找到答案,需要检查MouseDoublClick两个事件参数MouseDoublClick是相同的对象。 你会期望它们是相同的对象。 如果是真的,那么你的问题就很奇怪了,但它们不是同一个例子。 我们可以使用以下代码进行检查。

 RoutedEventArgs _eventArg; private void Button_MouseDoubleClick(object s, RoutedEventArgs e) { if (!e.Handled) Debug.WriteLine("Button got unhandled doubleclick."); //e.Handled = true; _eventArg = e; } private void ListViewItem_MouseDoubleClick(object s, RoutedEventArgs e) { if (!e.Handled) Debug.WriteLine("ListViewItem got unhandled doubleclick."); e.Handled = true; if (_eventArg != null) { var result = _eventArg.Equals(e); Debug.WriteLine(result); } } 

这意味着MouseDoublClick的事件参数是在某个地方新创建的,但我不明白为什么会这样。

为了更清楚,让我们检查BottonBase.Click的事件参数。 它将返回检查相同实例的真实情况。

       

如果你只专注于你提到的执行,那么会有很多解决方案。 如上所述,我认为使用标志( _eventArg )也是不错的选择。

我刚遇到同样的问题。 有一个简单但不明显的解决方案。

以下是Control引发双击的方式….

 private static void HandleDoubleClick(object sender, MouseButtonEventArgs e) { if (e.ClickCount == 2) { Control control = (Control)sender; MouseButtonEventArgs mouseButtonEventArgs = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton, e.StylusDevice); if (e.RoutedEvent == UIElement.PreviewMouseLeftButtonDownEvent || e.RoutedEvent == UIElement.PreviewMouseRightButtonDownEvent) { mouseButtonEventArgs.RoutedEvent = Control.PreviewMouseDoubleClickEvent; mouseButtonEventArgs.Source = e.OriginalSource; mouseButtonEventArgs.OverrideSource(e.Source); control.OnPreviewMouseDoubleClick(mouseButtonEventArgs); } else { mouseButtonEventArgs.RoutedEvent = Control.MouseDoubleClickEvent; mouseButtonEventArgs.Source = e.OriginalSource; mouseButtonEventArgs.OverrideSource(e.Source); control.OnMouseDoubleClick(mouseButtonEventArgs); } if (mouseButtonEventArgs.Handled) { e.Handled = true; } } } 

因此,如果您在子控件上处理PreviewMouseDoubleClick设置e.Handled = true ,则不会在父控件上MouseDoubleClick

  1. 您无法轻易更改双击事件被触发的方式,因为它们取决于用户设置,并且该延迟是在控制面板中自定义的。
  2. 您应该检查RepeatButton,它允许您按下按钮,当按下它时,它会按正常顺序生成多个单击事件。
  3. 如果您想自定义事件冒泡,那么您应该搜索允许您阻止事件传播的预览事件。 什么是WPF预览事件?