【问题标题】:MouseDoubleClick events don't bubbleMouseDoubleClick 事件不会冒泡
【发布时间】:2012-07-29 19:40:08
【问题描述】:

我的场景,简化:我有一个包含员工行的 ListView,在每个员工行中,有“增加”和“减少”按钮调整他的薪水。

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

问题是当我快速单击“增加”时,这会触发 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 处理程序上检查附加到事件的可视化树并检查“某处有一个按钮”并因此丢弃该事件,但这是不得已而为之。在编写这样的代码之前,我至少想了解这个问题。

有谁知道为什么 WPF 这样做,以及避免问题的优雅惯用方式?

【问题讨论】:

    标签: c# wpf mouseevent routed-events


    【解决方案1】:

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

    根据MSDN

    虽然这个路由事件似乎 沿着冒泡的路线穿过 元素树,其实是直接的 沿着引发的路由事件 每个 UIElement 的元素树。 如果你 将 Handled 属性设置为 true MouseDoubleClick 事件处理程序, 随后的 MouseDoubleClick 事件 沿途会发生 处理设置为 false。

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

    希望我可以自己验证这一点,但我目前没有 .NET。

    【讨论】:

    • 谢谢,但我也无法完全适应。 Button 上的 MouseDown/MouseLeftButtonDown 处理程序(或 Button 周围的容器包装器上)永远不会被触发,因此该事件似乎已经是 Button 的Handled。此外,我无法从 ListViewItem 上的 MouseDown/MouseLeftButtonDown 推断出双击,因为这些事件由 ListViewItem 内容中的其他可视组件(如文本框)处理和吞噬。
    【解决方案2】:

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

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

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

    但仍有 PreviewMouseLeftButtonDown 事件。当 ClickCount 等于 2 时,在您的按钮上使用它来将处理设置为 true,如下所示:

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

    【讨论】:

    • “但仍然存在 PreviewMouseLeftButtonDown 事件。在您的按钮上使用该事件将处理设置为 true” - 这不起作用,因为预览事件通过 ListViewItem first i>.
    • @Deestan,是的,但 PreviewMouseLeftButtonDown 将在 PreviewMouseDoubleClick 之前。 ListViewItem 只寻找后者,因此您可以处理前者来阻止它。
    • @Robert Levy:啊,我明白了。是的,这有点工作。但是,它会产生视觉故障,因为如果您在双击后按住,则该按钮不会按下。此外,它只会意外起作用 - 我们没有遵循任何记录在案的行为。
    • 虽然这不是一个真正可以接受的答案,但这是最有效的建议解决方法。赏金给你!
    【解决方案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 并在按钮的处理程序中放置断点或睡眠,您将在 ListView 的处理程序中看到延迟。 (即使没有明确的延迟,仍然会有一些小的延迟,足以打破它。)但是一旦你设置e.Handled,延迟多长时间都没有关系,它们将具有相同的时间戳。我不确定这是为什么,也不确定这是否是您可以依赖的记录在案的行为。

      【讨论】:

      • 不漂亮,但很有趣。 :) 由于两个单击事件都来自完全相同的 MouseDown OS 窗口消息,因此时间戳可能会绑定到该消息。如果这确实是某处记录的行为,那么这是一个很好的解决方案。可悲的是,MSDN 在这里并没有提供太多启示。
      • 对此进行了测试。它通常可以工作,但是任何时候某些代码出于某种原因触及事件处理队列时,它都会中断。我不建议使用它。
      • 感谢您提供此解决方法——最终使用了一个变体。正如 Deestan 指出的那样,时间戳在某些情况下是不同的(除了未标记为已处理的情况)。将连续事件之间的间隔阈值设置为几百毫秒对我来说效果很好。
      【解决方案5】:

      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 的事件参数。它将返回关于检查相同实例的 true。

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

      如果您只关注您提到的执行,将会有很多解决方案。如上,我觉得使用flag(_eventArg)也是不错的选择。

      【讨论】:

        【解决方案6】:

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

        这是 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 将不会在父控件上触发。

        【讨论】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-05-02
            • 2019-07-11
            • 1970-01-01
            • 1970-01-01
            • 2011-02-22
            • 2015-01-30
            • 2011-10-20
            • 2011-08-23
            相关资源
            最近更新 更多