MouseDoubleClick事件不会冒泡

时间:2011-06-08 13:47:14

标签: c# wpf mouseevent routed-events

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

假设在我的程序中,双击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会这样做,以及一种优雅的惯用方法来避免这个问题?

7 个答案:

答案 0 :(得分:10)

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

根据MSDN

  

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

您可以尝试在MouseDown上处理Button并设置要处理的内容,以便它不会传播到ListViewItem

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

答案 1 :(得分:8)

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

  

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

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

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

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

答案 2 :(得分:4)

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

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

为了文档目的,类名在这里不必要地用完整名称空间键入。

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

答案 4 :(得分: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)也是不错的选择。

答案 5 :(得分:1)

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

以下是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,则不会触发父控件。

答案 6 :(得分:-1)

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