为什么每次双击时 TreeViewItem 的 MouseDoubleClick 事件都会引发多次?
XAML
<TreeView Name="GroupView" ItemsSource="{Binding Documents}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="OnTreeNodeDoubleClick"/>
</Style>
</TreeView.ItemContainerStyle>
....
</TreeView>
代码隐藏
private void OnTreeNodeDoubleClick(object sender, MouseButtonEventArgs mouseEvtArgs)
{
Console.WriteLine("{3} MouseDoubleClick Clicks={0} ChangedButton={1} Source={2} Handled={4} ButtonState={5}",
mouseEvtArgs.ClickCount, mouseEvtArgs.ChangedButton, mouseEvtArgs.OriginalSource,
mouseEvtArgs.Timestamp, mouseEvtArgs.Handled, mouseEvtArgs.ButtonState);
}
我发现对于一次双击,事件处理程序会被调用多次。我试图通过双击相应的树节点来打开选项卡中的文档;所以我需要过滤掉额外的电话。
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
在我的稍微复杂的应用程序中,每次双击它都会被提升 4 次。在一个简单的重现应用程序上,每次双击它都会被提升 2 次。此外,所有事件参数参数也相同,因此我无法区分一组中的最后一个。
有什么想法为什么会这样吗?
XAML
<TreeView Name="GroupView" ItemsSource="{Binding Documents}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="OnTreeNodeDoubleClick"/>
</Style>
</TreeView.ItemContainerStyle>
....
</TreeView>
Code-Behind
private void OnTreeNodeDoubleClick(object sender, MouseButtonEventArgs mouseEvtArgs)
{
Console.WriteLine("{3} MouseDoubleClick Clicks={0} ChangedButton={1} Source={2} Handled={4} ButtonState={5}",
mouseEvtArgs.ClickCount, mouseEvtArgs.ChangedButton, mouseEvtArgs.OriginalSource,
mouseEvtArgs.Timestamp, mouseEvtArgs.Handled, mouseEvtArgs.ButtonState);
}
I find that for one double click, the event handler is called multiple times. I'm trying to open up a document in tab on a double-click on the corresponding tree node; so I'd need to filter out the extra calls.
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
In my slightly-complicated app, it is being raised 4 times per double-click. On a simple repro-app, it is being raised 2 times per double click. Also all the event argument parameters are the same too, so I can't distinguish the last one of a set.
Any ideas why this is the way it is?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
我知道这是一个老问题,但当我在寻找解决方案时遇到它时,以下是我的发现,供未来的访问者参考!
TreeViewItem
递归地相互包含。TreeViewItem
是一个HeaderedContentControl
(请参阅msdn),子节点作为Content
。因此,每个TreeViewItem
的边界包括其所有子项。这可以使用优秀的 WPF Inspector 进行验证,方法是在可视化树中选择一个TreeViewItem
,这将突出显示TreeViewItem
的边界。在OP的示例中,使用该样式在每个
TreeViewItem
上注册MouseDoubleClick
事件。因此,将分别为您双击的TreeViewItem
及其每个父项引发该事件。这可以在调试器中进行验证,方法是在双击事件处理程序中放置一个断点,并在事件参数的 Source 属性上放置一个监视 - 您会注意到每次调用事件处理程序时它都会发生变化。顺便说一句,正如预期的那样,事件的OriginalSource
保持不变。为了应对这种意外行为,检查源
TreeViewItem
是否被选择,正如 Pablo 在他的回答中所建议的那样,对我来说效果最好。I know this is an old question, but as I came across it in my searches for the solution, here are my findings for any future visitors!
TreeViewItem
s are recursively contained within each other.TreeViewItem
is aHeaderedContentControl
(see msdn), with the child nodes as theContent
. So, eachTreeViewItem
's bounds include all of its child items. This can be verified using the excellent WPF Inspector by selecting aTreeViewItem
in the visual tree, which will highlights the bounds of theTreeViewItem
.In the OP's example, the
MouseDoubleClick
event is registered on eachTreeViewItem
using the style. Therefore, the event will be raised for theTreeViewItem
s that you double-clicked on - and each of its parent items - separately. This can be verified in your debugger by putting a breakpoint in your double-click event handler and putting a watch on the event args' Source property - you will notice that it changes each time the event handler is called. Incidentally, as can be expected, theOriginalSource
of the event stays the same.To counter this unexpected behaviour, checking whether the source
TreeViewItem
is selected, as suggested by Pablo in his answer, has worked the best for me.双击
TreeViewItem
时,将选择该项目作为控件行为的一部分。根据具体情况,可以这样说:When a
TreeViewItem
is double clicked, that item is selected as part of the control behavior. Depending on the particular scenario it could be possible to say:我已经做了一些调试,这似乎是 WPF 中的一个错误。已经给出的大多数答案都是正确的,解决方法是检查是否选择了树视图项。
@ristogod 的答案最接近根本问题 - 它提到第一次调用处理程序时设置
e.Handled = true
没有达到预期的效果,并且事件继续冒泡,调用父级TreeViewItem
的处理程序(其中e.Handled
再次为false
)。该错误似乎在 WPF 中的这段代码中:
http://referencesource.microsoft.com/#PresentationFramework/src/ Framework/System/Windows/Controls/Control.cs,5ed30e0aec6a58b2
它接收
MouseLeftButtonDown
事件(已由子控件处理),但无法检查e .Handled
已设置为 true。然后,它继续创建一个新的MouseDoubleClick
事件参数(使用e.Handled == false
)并始终调用它。问题还在于,为什么在将其设置为第一次处理后,事件继续冒泡?因为在这一行中,当我们注册处理程序
Control.HandleDoubleClick
时:http://referencesource.microsoft.com/#PresentationFramework/src/ Framework/System/Windows/Controls/Control.cs,40
我们将 true 作为最后一个参数传递给
RegisterClassHandler
:http://referencesource.microsoft.com/#PresentationCore/Core/CSharp/系统/Windows/EventManager.cs,161
这是
handledEventsToo
。因此,不幸的行为是两个因素共同作用的结果:
Control.HandleDoubleClick
始终被调用(对于已处理的事件也是如此),并且Control.HandleDoubleClick
无法检查事件是否已经发生 我将通知 WPF 团队,但我不确定这个错误是否值得修复,因为它可能会破坏现有应用程序(这些应用程序依赖于正在调用的事件处理程序的当前行为,即使
Handled
设置为前一个处理程序为 true)。I've done some debugging and it appears to be a bug in WPF. Most answers already given are correct, and the workaround is to check if the tree view item is selected.
@ristogod's answer is the closest to the root problem - it mentions that setting
e.Handled = true
the first time handler is invoked doesn't have the desired effect and the event continues to bubble up, calling parentTreeViewItem
s' handlers (wheree.Handled
isfalse
again).The bug seems to be in this code in WPF:
http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,5ed30e0aec6a58b2
It receives the
MouseLeftButtonDown
event (which is handled by the child control already), but it fails to check ife.Handled
is already set to true. Then it proceeds to create a newMouseDoubleClick
event args (withe.Handled == false
) and invokes that always.The question also remains why after having set it to handled the first time the event continues to bubble? Because in this line, when we register the handler
Control.HandleDoubleClick
:http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,40
we pass true as the last argument to
RegisterClassHandler
:http://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/EventManager.cs,161
which is
handledEventsToo
.So the unfortunate behavior is a confluence of two factors:
Control.HandleDoubleClick
is called always (for handled events too), andControl.HandleDoubleClick
fails to check if the event had already been handledI will notify the WPF team but I'm not sure this bug is worth fixing because it might break existing apps (who rely on the current behavior of event handlers being called even if
Handled
was set to true by a previous handler).这实际上不是一个冒泡问题。我以前见过这个。即使您告诉该事件您已经处理了它,它仍然会继续冒泡。只是我不认为它实际上是冒泡的,而是触发了上面的节点自己的双击事件。我可能完全错了。但无论哪种情况,重要的是要知道这句话:
无法阻止这种情况的发生。
防止这种行为的一种方法是注意,当您双击时,您首先单击,并且应首先触发所选事件。因此,虽然您无法阻止双击事件的发生,但您应该能够检查处理程序内部以查看事件逻辑是否应该运行。此示例利用了这一点:
然而,有时您会遇到这样的情况:节点是由其他 bean 选择的,而不是通过 TreeView SelectedItemChanged 事件选择的。在这种情况下你可以做这样的事情。如果您碰巧有一个具有单个声明的顶部节点的 TreeView,您可以为该节点指定一个特定名称,然后执行如下操作:
无论您使用哪种方法,重要的是要注意,无论出于何种原因,双击 TreeViewItem你无法阻止事件激发树。至少我还没有找到方法。
This is not actually a bubbling issue. I've seen this before. Even when you tell the event that you handled it, it continues to keep bubbling up. Except that I don't think that it's actually bubbling up, but rather firing the node above's own double click event. I could be totally wrong on that. But in either case, it's important to know that saying:
Does nothing to stop this from happening.
One way to prevent this behavior is to note that when you are double clicking, you are first single clicking and that the selected event should fire first. So while you can't stop the Double Click events from occurring, you should be able to check inside the handler to see whether the event logic should run. This example leverages that:
Sometimes however you have situations where the nodes are being selected by other beans than through the TreeView SelectedItemChanged event. In that case you can do something like this. If you happen to have a TreeView with a single declared top node, you can give that node a specific name and then do something like this:
Regardless of the method you use, the important thing is to note that for whatever reason with TreeViewItem double clicking that you can't stop the events from firing up the tree. At least I haven't found a way.
我有一个比检查选择或创建标志更优雅的解决方案:
一个辅助方法:
然后是你的处理程序:
I have a little bit more elegant solution than checking for selection or creating flags:
A helper method:
And then your handler:
这就是事件冒泡的奇妙世界。该事件在 TreeView 的节点层次结构中向上冒泡,并且为层次结构路径中的每个节点调用一次处理程序。
使用类似的内容即可。
只需在处理程序代码中
This is the wonderful world of event bubbling. The event is bubbling up the node hierarchy of your TreeView and your handler is called once for every node in the hierarchy path.
Just use something like
in your handler code.
这个解决方案存在一些相当大的问题,但如果有人需要在多个地方解决这个问题,它可能会起作用,而且我确实发现了一个接受的解决方案不起作用的场景(双击一个切换按钮,打开一个弹出窗口,其中切换按钮位于另一个处理双击的元素内。)
{
私有 const string DoubleClickEventHandled = "DoubleClickEventHandled";
使用
DoubleClickEventHandlingTool.HandleDoubleClickEvent()
内部/低级别元素内部,例如:
高级双击事件仅在以下情况下执行其操作:
There are some pretty major problems with this solution, but it could work in case someone needs to solve this problem in multiple places and I did find a scenario where the accepted solution doesn't work (double clicking on a toggle button that opens up a popup, where the toggle button is inside another element that handles double click.)
{
private const string DoubleClickEventHandled = "DoubleClickEventHandled";
}
Use DoubleClickEventHandlingTool.HandleDoubleClickEvent()
inside the inner/low level element eg:
High level double click event then only performs it's action when:
最可能的原因是双击处理程序安装了多次,因此每次单击都会调用处理程序的每个实例一次。
The most likely reason is that the doubleclick handler is installed multiple times, so each instance of the handler is being called once for each click.