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