17

我的场景,简化:我有一个包含员工行的 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为什么要这样做,以及避免该问题的优雅惯用方式?

4

7 回答 7

10

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

根据MSDN

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

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

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

于 2011-06-08T14:12:00.493 回答
8

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

想要处理鼠标双击的控件作者应该在 ClickCount 等于 2 时使用 MouseLeftButtonDown 事件。这将导致 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;
 }
于 2011-06-13T02:31:17.330 回答
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.");
}

出于文档目的,这里不必要地使用完整的命名空间键入类名。

于 2011-06-10T11:45:32.360 回答
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了延迟多长时间,它们将具有相同的时间戳。我不确定这是为什么,我不确定这是否是您可以依赖的记录在案的行为。

于 2011-06-13T01:54:28.550 回答
2

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

由于使用Snoop(一种用于浏览可视化树和路由事件的工具)检查了这个问题,我看到Control.MouseDoubleClick“ListView”和“ListBoxItem”事件同时被触发。您可以使用此 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);
    }
}

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

为了更清楚,让我们检查BottonBase.Click. 它将返回关于检查相同实例的 true。

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

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

于 2011-06-16T03:30:42.923 回答
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 = trueMouseDoubleClick

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