2011-06-08 102 views
16

我的场景简化了:我有一个包含Employees行的ListView,并且在每个Employee行中都有按钮“Increase”和“Decrease”来调整他的薪水。MouseDoubleClick事件不会冒泡

假装在我的程序中,双击一个Employee行意味着“激发这个人”。

问题是当我点击“增加”时,这会触发ListViewItem上的双击事件。当然,我只是在增加薪水时不想解雇人员。

根据所有其他事件的工作方式,我希望能够通过设置Handled=true来解决这个问题。但是,这不起作用。在我看来,WPF会生成两个单独的,完全不关联的双击事件。

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

<ListView> 
    <ListViewItem MouseDoubleClick="ListViewItem_MouseDoubleClick"> 
      <Button MouseDoubleClick="Button_MouseDoubleClick"/> 
    </ListViewItem> 
</ListView> 

而且处理代码:

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做到了这一点,并采用了优雅的惯用方法来避免这个问题?

回答

10

我想你会发现MouseDoubleClick事件是MouseDown事件之上的抽象事件。也就是说,如果两个MouseDown事件发生的速度足够快,MouseDoubleClick事件也将被提出。 ButtonListViewItem似乎都有这个逻辑,所以这就解释了为什么你会看到两个不同的事件MouseDoubleClick

MSDN

虽然这个路由事件似乎 遵循冒泡的路线,通过一个 元素树,它实际上是由每个的UIElement沿 元素树提出了一个直接 路由事件。 如果您在 MouseDoubleClick事件处理程序 中将Handled属性设置为true,则沿路线的后续MouseDoubleClick事件 将与 一起发生设置为false。

你可以尝试在Button处理MouseDown和设置,要处理,使得它不会传播到ListViewItem

希望我可以自己验证一下,但我现在没有.NET。

+0

谢谢,但我无法完全符合这个要求。 Button上的MouseDown/MouseLeftButtonDown处理函数(或Button上的容器包装函数)永远不会被触发,所以看起来事件已经被按钮处理了。另外,我无法从ListViewItem的MouseDown/MouseLeftButtonDown中推断出双击,因为这些事件是由ListViewItem内容(如文本框)中的其他可视组件处理和吞食的。 – Deestan 2011-06-08 14:22:47

3

由于没有出现过明确回答这个问题,这是我最后使用的解决方法:

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."); 
} 

的类名在这里用不必要的文档目的全命名空间类型。

4

那么它可能不是优雅或习惯,但你可能会喜欢它比你目前的解决方法更好:

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,延迟有多长时间并不重要,它们将具有相同的时间戳。我不确定这是为什么,我不确定这是否是您可以依赖的记录行为。

+0

不漂亮,但有趣。 :)由于这两个点击事件都来自完全相同的MouseDown操作系统窗口消息,因此时间戳可能会与此绑定。如果这在某个地方确实是有记录的行为,这是一个很好的解决方案。可悲的是,MSDN在这里并没有多少亮点。 – Deestan 2011-06-14 07:05:57

+0

测试过了。它通常工作,但是随时某些代码由于某种原因触及事件处理队列时,它会中断。我不会推荐使用这个。 – Deestan 2011-06-18 22:59:47

+0

感谢您采取这种解决方法 - 最终使用了一个变体。正如Deestan指出的那样,在某些情况下,时间戳是不同的(除了未标记为已处理的情况之外)。将连续事件之间的时间间隔设置为几百毫秒,对我来说工作得很好。 – alexei 2012-06-15 16:55:43

8

MSDN documentation为MouseDoubleClick确实就如何冒泡保持MouseDoubleClick事件的建议:谁想要处理 鼠标双击应使用 MouseLeftButtonDown事件时 ClickCount的是

控制作者等于两个。这将 导致处理状态 在 树中处理该事件的元素 中的情况下适当地传播。

因此,如果ClickCount为2,则可以处理MouseLeftButtonDown事件并将挂起设置为true。但是这在按钮上失败,因为它们已经处理了MouseLeftButtonDown并且不会引发该事件。

但仍然存在PreviewMouseLeftButtonDown事件。请使用您的按钮时ClickCount的等于如下两集处理,以真:

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

“但是仍然存在PreviewMouseLeftButtonDown事件。在按钮上使用它来处理为true” - 这不起作用,因为Preview-events首先通过ListViewItem * *。 – Deestan 2011-06-14 06:55:27

+0

@Deestan,是的,但PreviewMouseLeftButtonDown将在PreviewMouseDoubleClick之前。 ListViewItem只是寻找后者,所以你可以处理前者来阻止它。 – 2011-06-17 19:21:24

+0

@Robert Levy:啊,我明白了。是的,那种工作。不过,它会产生视觉故障,因为如果在双击后按住按钮,则按钮不会熄灭。此外,它只会偶然地运作* - 我们没有遵循任何记录的行为。 – Deestan 2011-06-18 22:49:06

2

Control.MouseDoubleClick是不是泡沫事件,而是一个直接的事件。

由于检查与Snoop这个问题,这是用于浏览视觉树和路由事件的工具,我看到“ListView的”和“一个ListBoxItem”的Control.MouseDoubleClick事件在同一时间发射。你可以用这个Snoop工具来检查。


首先,找到一个答案,它是需要检查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的事件参数。它将返回检查相同实例的真实情况。

<ListView> 
     <ListViewItem ButtonBase.Click="ListViewItem_MouseDoubleClick"> 
      <Button Click="Button_MouseDoubleClick" Content="click"/> 
     </ListViewItem> 
    </ListView> 

如果您只关注如上所述的执行过程,将会有很多解决方案。如上所述,我认为使用国旗(_eventArg)也是不错的选择。

-1
  1. 您不能轻易改变双击事件被触发的方式,因为它们取决于用户设置,并且延迟是在控制面板中自定义的。
  2. 您应该结账RepeatButton,它允许您按下按钮并在按下按钮时按常规顺序生成多个点击事件。
  3. 如果你想自定义事件冒泡,那么你应该搜索预览事件,它允许你阻止事件的传播。 What are WPF Preview Events?
+1

不回答这个问题 – 2011-06-17 19:19:29

1

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

这里是点击双重如何通过控制提出....

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对孩子控制MouseDoubleClick设置e.Handled = true将不会触发父控件。