My scenario, simplified: I have a ListView containing rows of Employees, and in each Employee row, there are buttons "Increase" and "Decrease" adjusting his salary.
Pretend that in my program, double-clicking an Employee row means "fire this person".
The problem is that while I'm clicking "Increase" rapidly, this triggers a double click event on the ListViewItem. Naturally, I don't want to fire people when I'm just increasing their salary.
According to how all other events work, I expect to be able to solve this by setting Handled=true
on the event. This, however, doesn't work. It appears to me that WPF generates two separate, completely unlinked, double click events.
The following is a minimal example to reproduce my issue. The visible components:
<ListView>
<ListViewItem MouseDoubleClick="ListViewItem_MouseDoubleClick">
<Button MouseDoubleClick="Button_MouseDoubleClick"/>
</ListViewItem>
</ListView>
And the handler code:
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;
}
After firing up this program and double-clicking the listed button, both messageboxes show up in sequence. (Also, the button is stuck in the down position after this.)
As a "fix" I can, on the ListViewItem handler, inspect the visual tree attached to the event and check that "there is a button there somewhere" and thus discard the event, but this is a last resort. I want to at least understand the issue before coding such a kludge.
Does anyone know why WPF does this, and an elegant idiomatic way to avoid the problem?
Control.MouseDoubleClick is not a bubble event but a direct event.
Since checking this question with Snoop, which is a tool for browsing visual trees and routed events, I see that
Control.MouseDoubleClick
events of 'ListView' and 'ListBoxItem' are fired at one time. You could check with this Snoop tool.First, to find an answer, it is needed to check that both event arguments of the
MouseDoublClick
are same objects. You would expect they are same objects. If it is true, it is very strange as your question, but they are not same instances. We can check it with following codes.It means that the event argument of the
MouseDoublClick
is created newly at somewhere, but I don't understand deeply why it is.To be clearer, let's check for the event argument of the
BottonBase.Click
. It will be return the true about checking same instances.If you only focus on the execution as you mentioned there'll be lots of solutions. As above, I think that using the flag(
_eventArg
) is also good choice.The MSDN documentation for the MouseDoubleClick does give a suggestion on how to keep the MouseDoubleClick event from bubbling up:
So you could hanlde the MouseLeftButtonDown event and set hanged to true if ClickCount is two. But this fails on Buttons because they already handle the MouseLeftButtonDown and don't raise that event.
But there is still the PreviewMouseLeftButtonDown event. Use that on your buttons to set handled to true when ClickCount equals two as below:
I've just had this same problem. There is a simple but non-obvious solution.
Here is how double click is raised by Control ....
So if you handle
PreviewMouseDoubleClick
settinge.Handled = true
on the child controlMouseDoubleClick
won't fire on the parent control.I think you'll find that the
MouseDoubleClick
event is an abstraction on top of theMouseDown
event. That is, if twoMouseDown
events occur in quick enough succession, theMouseDoubleClick
event will also be raised. Both theButton
andListViewItem
appear to have this logic, so that explains why you're seeing two distinctMouseDoubleClick
events.As per MSDN:
You could try handling
MouseDown
on theButton
and setting that to handled so that it doesn't propagate to theListViewItem
.Wish I could verify this myself but I'm .NET-less at the moment.
Well it may not be elegant or idiomatic, but you might like it better than your current workaround:
The weird thing is this doesn't work if you don't set
e.Handled = true
. If you don't sete.Handled
and put a breakpoint or a Sleep into the button's handler, you will see the delay in the ListView's handler. (Even without an explicit delay there will still be some small delay, enough to break it.) But once you sete.Handled
it doesn't matter how long of a delay there is, they will have the same timestamp. I'm not sure why this is, and I'm not sure if this is documented behavior that you can rely on.