Подтвердить что ты не робот

Почему событие 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>

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

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

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

Любые идеи, почему так оно и есть?

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

...
TreeViewItem tviSender = sender as TreeViewItem;

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

Ответ 3

Я проделал некоторую отладку и, похоже, это ошибка в 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.

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

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

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

Ответ 4

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

Ответ 5

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

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

Иногда возникают ситуации, когда узлы выбираются другими beans, чем через событие TreeView SelectedItemChanged. В этом случае вы можете сделать что-то подобное. Если у вас есть TreeView с одним объявленным верхним node, вы можете указать, что node определенное имя, а затем сделать что-то вроде этого:

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

Ответ 6

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

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

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

Ответ 7

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

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

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

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

Ответ 8

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

public class DoubleClickEventHandlingTool

{   private const string 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())

Ответ 9

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