22

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 次。在一个简单的 repro-app 上,每次双击它会提升 2 次。此外,所有事件参数参数也相同,所以我无法区分一组中的最后一个。

任何想法为什么会这样?

4

9 回答 9

26

我知道这是一个老问题,但是当我在寻找解决方案时遇到它时,这里是我为任何未来访问者的发现!

TreeViewItems 彼此递归地包含。 TreeViewItem是一个HeaderedContentControl(参见msdn),子节点为Content. 因此, eachTreeViewItem的边界包括其所有子项。这可以使用优秀的WPF 检查器通过在可视化树中选择 a来验证TreeViewItem,这将突出显示TreeViewItem.

在 OP 的示例中,使用样式MouseDoubleClick在每个事件上注册事件。因此,将为您双击TreeViewItem的 s 及其每个父项分别引发事件。TreeViewItem这可以通过在您的双击事件处理程序中放置一个断点并监视事件 args 的 Source 属性在您的调试器中进行验证 - 您会注意到每次调用事件处理程序时它都会更改。顺便说一句,正如可以预料的那样,OriginalSource事件的 保持不变。

为了应对这种意外行为,检查是否TreeViewItem选择了源,正如 Pablo 在他的回答中所建议的那样,对我来说效果最好。

于 2012-02-03T16:51:10.513 回答
22

双击a 时TreeViewItem,该项目被选择为控件行为的一部分。根据具体情况,可以说:

...
TreeViewItem tviSender = sender as TreeViewItem;

if (tviSender.IsSelected)
    DoAction();
...
于 2010-10-13T08:05:06.593 回答
9

我做了一些调试,它似乎是 WPF 中的一个错误。已经给出的大多数答案都是正确的,解决方法是检查是否选择了树视图项。

@ristogod 的答案是最接近根本问题的 - 它提到设置e.Handled = true第一次调用处理程序没有达到预期的效果,并且事件继续冒泡,调用 parent TreeViewItems' 处理程序(再次在哪里e.Handledfalse

该错误似乎在 WPF 中的此代码中: http ://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,5ed30e0aec6a58b2

它接收MouseLeftButtonDown事件(已由子控件处理),但无法检查是否e.Handled已设置为 true。然后它继续创建一个新MouseDoubleClick事件 args (with e.Handled == false) 并始终调用它。

问题仍然存在,为什么在事件第一次继续冒泡时将其设置为处理?因为在这一行中,当我们注册处理程序时Control.HandleDoubleClickhttp ://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,40

我们将 true 作为最后一个参数传递给RegisterClassHandlerhttp ://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/EventManager.cs,161 这是handledEventsToo.

所以不幸的行为是两个因素的汇合:

  1. Control.HandleDoubleClick总是被调用(也用于处理的事件),并且
  2. Control.HandleDoubleClick无法检查事件是否已被处理

我会通知 WPF 团队,但我不确定这个 bug 是否值得修复,因为它可能会破坏现有的应用程序(这些应用程序依赖于被调用的事件处理程序的当前行为,即使Handled之前的处理程序设置为 true)。

于 2016-03-27T05:42:18.917 回答
7
private void TreeView_OnItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    if (e.Source is TreeViewItem
        && (e.Source as TreeViewItem).IsSelected)
    {
        // your code
        e.Handled = true;
   }
}
于 2012-10-30T13:35:16.967 回答
4

这实际上不是一个冒泡问题。我以前见过这个。即使你告诉事件你处理了它,它仍然会继续冒泡。除了我不认为它实际上在冒泡,而是触发上面的节点自己的双击事件。我可能完全错了。但无论哪种情况,重要的是要知道这句话:

e.handled = true;

没有什么可以阻止这种情况的发生。

防止这种行为的一种方法是注意,当您双击时,您是第一次单击,并且应首先触发所选事件。因此,虽然您无法阻止 Double Click 事件的发生,但您应该能够检查处理程序内部以查看事件逻辑是否应该运行。这个例子利用了:

TreeViewItem selectedNode;

private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e)
{
    if(selectedNode = e.Source)
    {
        //do event logic
    }
}

private void TreeViewSelectedEventHandler(object sender, RoutedEventArgs e)
{
    selectedNode = (TreeViewItem)e.Source;
}

但是,有时您会遇到节点被其他 bean 选择的情况,而不是通过 TreeView SelectedItemChanged 事件。在这种情况下,你可以做这样的事情。如果您碰巧有一个带有单个声明顶部节点的 TreeView,您可以为该节点指定一个特定名称,然后执行以下操作:

bool TreeViewItemDoubleClickhandled;

private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e)
{
    if (!TreeViewItemDoubleClickhandled)
    {
        //do logic here

        TreeViewItemDoubleClickhandled = true;
    }

    if (e.Source == tviLoadTreeTop)
    {
        TreeViewItemDoubleClickhandled = false;
    }
    e.Handled = true;
}

无论您使用哪种方法,重要的是要注意,无论出于何种原因双击 TreeViewItem,您都无法阻止事件启动树。至少我还没有找到方法。

于 2010-10-07T17:56:42.727 回答
2

我有一个比检查选择或创建标志更优雅的解决方案:

辅助方法:

public static object GetParent(this DependencyObject obj, Type expectedType) {

    var parent = VisualTreeHelper.GetParent(obj);
    while (parent != null && parent.GetType() != expectedType)
        parent = VisualTreeHelper.GetParent(parent);

    return parent;
}

然后你的处理程序:

public void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
    if (e.OriginalSource is DependencyObject)
        if (sender == (e.OriginalSource as DependencyObject).GetParent(typeof(TreeViewItem))) 
    {
        // sender is the node, which was directly doubleclicked
    }
}
于 2013-11-28T12:47:16.830 回答
0

这是事件冒泡的美妙世界。该事件使 TreeView 的节点层次结构冒泡,并且为层次结构路径中的每个节点调用一次处理程序。

只需使用类似的东西

        // ...
        if (sender != this)
        {
            return;
        }
        // Your handler code goes here ...
        args.Handled = true;
        // ...

在您的处理程序代码中。

于 2010-09-22T13:09:22.720 回答
0

这个解决方案有一些非常大的问题,但如果有人需要在多个地方解决这个问题,它可能会起作用,而且我确实发现了一个已接受的解决方案不起作用的场景(双击打开一个切换按钮弹出窗口,其中切换按钮位于另一个处理双击的元素内。)

public class DoubleClickEventHandlingTool

{ 私人常量字符串 DoubleClickEventHandled = "DoubleClickEventHandled";

public static void HandleDoubleClickEvent()
{
    Application.Current.Properties[DoubleClickEventHandled] = DateTime.Now.AddSeconds(1);
}

public static bool IsDoubleClickEventHandled()
{
    var doubleClickWasHandled = Application.Current.Properties[DoubleClickEventHandled] as DateTime?;

    return doubleClickWasHandled.HasValue && !IsDateTimeExpired(doubleClickWasHandled.Value);
}

private static bool IsDateTimeExpired(DateTime value)
{
    return value < DateTime.Now;
}

public static void EnableDoubleClickHandling()
{
    Application.Current.Properties[DoubleClickEventHandled] = null;
}

public static bool IsDoubleClickEventHandledAndEnableHandling()
{
    var handled = IsDoubleClickEventHandled();
    EnableDoubleClickHandling();

    return handled;
}

}

在内部/低级元素中使用 DoubleClickEventHandlingTool.HandleDoubleClickEvent() 例如:

    private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{if (e.ClickCount == 2) DoubleClickEventHandlingTool.HandleDoubleClickEvent();}

高级双击事件仅在以下情况下执行它的操作:

if (!DoubleClickEventHandlingTool.IsDoubleClickEventHandledAndEnableHandling())
于 2016-06-07T21:38:05.877 回答
-1

最可能的原因是双击处理程序安装了多次,因此每次单击都会调用一次处理程序的每个实例。

于 2010-02-17T11:15:54.510 回答