Почему событие MouseDoubleClick TreeViewItem вызывается несколько раз за двойной щелчок? - PullRequest
20 голосов
/ 17 февраля 2010

1001 * 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);
       }

Я обнаружил, что для одного двойного щелчка обработчик события вызывается несколько раз. Я пытаюсь открыть документ на вкладке при двойном щелчке на соответствующем узле дерева; поэтому мне нужно отфильтровать лишние звонки.

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 раза за двойной клик. Также все параметры аргумента события одинаковы, поэтому я не могу отличить последний из набора.

Есть идеи, почему это так?

Ответы [ 9 ]

24 голосов
/ 03 февраля 2012

Я знаю, что это старый вопрос, но когда я наткнулся на него в своих поисках решения, вот мои выводы для любых будущих посетителей!

TreeViewItem s рекурсивно содержатся внутри друг друга. TreeViewItem - это HeaderedContentControl (см. msdn ) с дочерними узлами в качестве Content. Таким образом, в границы каждого TreeViewItem входят все его дочерние элементы. Это можно проверить с помощью превосходного WPF Inspector , выбрав TreeViewItem в визуальном дереве, которое выделит границы TreeViewItem.

В примере OP событие MouseDoubleClick регистрируется на каждом TreeViewItem с использованием стиля. Следовательно, событие будет вызываться для TreeViewItem, которые вы дважды щелкнули - и для каждого из его родительских элементов - отдельно. Это можно проверить в своем отладчике, установив точку останова в свой обработчик событий двойного щелчка и установив наблюдение за свойством Source аргументов событий - вы заметите, что оно меняется при каждом вызове обработчика событий. Кстати, как и следовало ожидать, OriginalSource события остается прежним.

Чтобы противостоять этому неожиданному поведению, проверка того, выбран ли источник TreeViewItem, как было предложено Пабло в его ответе, сработала для меня лучше всего.

18 голосов
/ 13 октября 2010

При двойном щелчке TreeViewItem этот элемент выбирается как часть поведения элемента управления. В зависимости от конкретного сценария можно сказать:

...
TreeViewItem tviSender = sender as TreeViewItem;

if (tviSender.IsSelected)
    DoAction();
...
7 голосов
/ 27 марта 2016

Я сделал некоторую отладку, и это похоже на ошибку в WPF. Большинство ответов, которые уже даны, являются правильными, и обходной путь должен проверить, выбран ли элемент представления дерева.

@ ristogod ответ наиболее близок к корневой проблеме - он упоминает, что установка e.Handled = true при первом вызове обработчика не дает желаемого эффекта, и событие продолжает всплывать, вызывая обработчики родительского TreeViewItem s (где e.Handled снова false).

Ошибка, кажется, в этом коде в WPF: http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,5ed30e0aec6a58b2

Он получает событие MouseLeftButtonDown (которое уже обрабатывается дочерним элементом управления), но не может проверить, установлено ли для e.Handled значение true. Затем он создает новые аргументы MouseDoubleClicke.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/System/Windows/EventManager.cs,161 что handledEventsToo.

Итак, неудачное поведение - это слияние двух факторов:

  1. Control.HandleDoubleClick вызывается всегда (также для обработанных событий), а
  2. Control.HandleDoubleClick не может проверить, было ли событие уже обработано

Я сообщу команде WPF, но я не уверен, что эту ошибку стоит исправлять, поскольку она может сломать существующие приложения (которые полагаются на текущее поведение вызываемых обработчиков событий, даже если предыдущий Handled был установлен в значение true обработчик).

6 голосов
/ 30 октября 2012
private void TreeView_OnItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    if (e.Source is TreeViewItem
        && (e.Source as TreeViewItem).IsSelected)
    {
        // your code
        e.Handled = true;
   }
}
4 голосов
/ 07 октября 2010

На самом деле это не пузырящаяся проблема. Я видел это раньше. Даже когда вы сообщаете событию, что обрабатывали его, оно продолжает пузыриться. За исключением того, что я не думаю, что это на самом деле пузырится, а скорее запускает узел над собственным событием двойного щелчка. Я могу быть совершенно неправ в этом. Но в любом случае важно знать, что поговорка:

e.handled = true;

Ничего не делает, чтобы это не произошло.

Один из способов предотвратить такое поведение - заметить, что при двойном щелчке вы сначала нажимаете один раз, и что выбранное событие должно запускаться первым. Таким образом, хотя вы не можете остановить события двойного щелчка, вы должны иметь возможность проверить внутри обработчика, чтобы увидеть, должна ли выполняться логика событий. Этот пример использует это:

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

Иногда, однако, возникают ситуации, когда узлы выбираются другими компонентами, а не посредством события 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 вы не можете остановить запуск событий по дереву. По крайней мере, я не нашел способ.

1 голос
/ 28 ноября 2013

У меня есть немного более элегантное решение, чем проверка выбора или создание флагов:

Вспомогательный метод:

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
    }
}
0 голосов
/ 08 июня 2016

Есть некоторые довольно серьезные проблемы с этим решением, но оно может работать, если кому-то нужно решить эту проблему в нескольких местах, и я нашел сценарий, где принятое решение не работает (двойной щелчок по кнопке переключения, который открывает всплывающее окно, где кнопка переключения находится внутри другого элемента, который обрабатывает двойной щелчок.)

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())
0 голосов
/ 22 сентября 2010

Это чудесный мир бурлящих событий. Событие поднимает иерархию узлов вашего TreeView, и ваш обработчик вызывается один раз для каждого узла в пути иерархии.

Просто используйте что-то вроде

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

в коде вашего обработчика.

0 голосов
/ 17 февраля 2010

Наиболее вероятная причина заключается в том, что обработчик двойного щелчка устанавливается несколько раз, поэтому каждый экземпляр обработчика вызывается один раз для каждого щелчка.

...