为什么每次双击都会多次引发TreeViewItem的MouseDoubleClick事件?

时间:2010-02-17 11:05:56

标签: wpf treeview mouseevent treeviewitem

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次。此外,所有的事件参数参数也是相同的,所以我无法区分集合中的最后一个参数。

任何想法为什么会这样?

9 个答案:

答案 0 :(得分:23)

我知道这是一个老问题,但正如我在搜索解决方案中遇到的那样,以下是我对未来访客的调查结果!

TreeViewItem以递归方式包含在彼此之内。 TreeViewItemHeaderedContentControl(请参阅msdn),子节点为Content。因此,每个TreeViewItem的边界都包含其所有子项。通过在可视化树中选择TreeViewItem,可以使用优秀的WPF Inspector验证这一点,这将突出显示TreeViewItem的范围。

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

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

答案 1 :(得分:17)

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

...
TreeViewItem tviSender = sender as TreeViewItem;

if (tviSender.IsSelected)
    DoAction();
...

答案 2 :(得分:7)

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

@ ristogod的答案是最接近根问题的 - 它提到在第一次调用处理程序时设置TreeViewItem没有达到预期效果并且事件继续冒泡,致电父母e.Handled s&#39;处理程序(false再次为MouseLeftButtonDown)。

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

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

问题还存在的原因仍然是为什么在第一次将事件设定为处理泡沫后进行处理?因为在这一行中,当我们注册处理程序RegisterClassHandler时: http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,40

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

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

    总是调用
  1. Control.HandleDoubleClick(对于处理过的事件),
  2. Handled无法检查事件是否已被处理
  3. 我会通知WPF团队,但我不确定这个bug是否值得修复,因为它可能会破坏现有的应用程序(即使{{1}}设置为{{1}},依赖于事件处理程序的当前行为以前的处理程序是真的。)

答案 3 :(得分:6)

private void TreeView_OnItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    if (e.Source is TreeViewItem
        && (e.Source as TreeViewItem).IsSelected)
    {
        // your code
        e.Handled = true;
   }
}

答案 4 :(得分: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双击,您无法阻止事件启动树。至少我还没找到办法。

答案 5 :(得分:1)

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

辅助方法:

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
    }
}

答案 6 :(得分:0)

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

只需使用

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

在你的处理程序代码中。

答案 7 :(得分:0)

这个解决方案存在一些相当大的问题,但是如果有人需要在多个地方解决这个问题并且我确实找到了一个可以接受解决方案不起作用的场景(双击切换)按钮打开一个弹出窗口,其中切换按钮位于另一个处理双击的元素内。)

public class DoubleClickEventHandlingTool

{     私有const字符串DoubleClickEventHandled =&#34; DoubleClickEventHandled&#34 ;;

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())

答案 8 :(得分:-1)

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