我的场景,简化:我有一个包含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会这样做,以及一种优雅的惯用方法来避免这个问题?
答案 0 :(得分:10)
我认为你会发现MouseDoubleClick
事件是MouseDown
事件之上的抽象。也就是说,如果两个MouseDown
事件以足够快的速度发生,则还会引发MouseDoubleClick
事件。 Button
和ListViewItem
似乎都有这个逻辑,这就解释了为什么你会看到两个不同的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)