События MouseDoubleClick не пузырятся
Мой сценарий, упрощенный: у меня есть ListView, содержащий строки Employees, и в каждой строке Employee есть кнопки "Увеличить" и "Уменьшить", корректируя его зарплату.
Притворись, что в моей программе двойной щелчок по строке "Сотрудник" означает "Огонь этого человека".
Проблема заключается в том, что, хотя я быстро нажимаю "Увеличить", это вызывает событие двойного щелчка в элементе ListViewItem. Естественно, я не хочу увольнять людей, когда я просто увеличиваю их зарплату.
В соответствии с тем, как работают все другие события, я ожидаю, что смогу решить это, установив Handled=true
на событие. Это, однако, не работает. Мне кажется, что WPF генерирует два отдельных, полностью несвязанных события двойного щелчка.
Ниже приведен минимальный пример для воспроизведения моей проблемы. Видимые компоненты:
<ListView>
<ListViewItem MouseDoubleClick="ListViewItem_MouseDoubleClick">
<Button MouseDoubleClick="Button_MouseDoubleClick"/>
</ListViewItem>
</ListView>
И код обработчика:
private void Button_MouseDoubleClick(object s, MouseButtonEventArgs e) {
if (!e.Handled) MessageBox.Show("Button got unhandled doubleclick.");
e.Handled = true;
}
private void ListViewItem_MouseDoubleClick(object s, MouseButtonEventArgs e) {
if (!e.Handled) MessageBox.Show("ListViewItem got unhandled doubleclick.");
e.Handled = true;
}
После запуска этой программы и двойного щелчка по указанной кнопке оба окна сообщений отображаются последовательно. (Кроме того, после этого кнопка застревает в нижнем положении.)
Как "исправление", я могу в обработчике ListViewItem проверить визуальное дерево, прикрепленное к событию, и проверить, что "там есть какая-то кнопка" и, таким образом, отменить это событие, но это последнее средство. Я хочу, по крайней мере, понять эту проблему, прежде чем кодировать такой kludge.
Кто-нибудь знает, почему WPF делает это, и элегантный идиоматический способ избежать проблемы?
Ответы
Ответ 1
Я думаю, вы обнаружите, что событие MouseDoubleClick
является абстракцией поверх события MouseDown
. То есть, если два события MouseDown
происходят достаточно быстро, событие MouseDoubleClick
также будет поднято. Кажется, что у обоих Button
и ListViewItem
есть эта логика, поэтому объясняется, почему вы видите два разных события MouseDoubleClick
.
По MSDN:
Хотя это маршрутизируемое событие кажется следовать барботирующему маршруту через дерево элементов, это фактически событие, которое поднимается вдоль дерева элементов каждым элементом UIElement. Если вы установите для свойства Handled значение true в Обработчик событий MouseDoubleClick, последующие события MouseDoubleClick по маршруту будет происходить с Заданное значение false.
Вы можете попробовать обработать MouseDown
на Button
и настроить для обработки таким образом, чтобы он не распространялся на ListViewItem
.
Пожелаю, чтобы я мог убедиться в этом сам, но сейчас я .NET-меньше.
Ответ 2
документация MSDN для MouseDoubleClick дает подсказку о том, как предотвратить событие MouseDoubleClick:
Авторы контроля, которые хотят обрабатывать двойные клики мыши должны использовать Событие MouseLeftButtonDown, когда ClickCount равно двум. Это будет вызывают состояние надлежащим образом распространяться в случае где другой элемент в элементе дерево обрабатывает событие.
Таким образом, вы могли бы предотвратить событие MouseLeftButtonDown и установить повешенное значение true, если ClickCount равно двум. Но это не работает на кнопках, потому что они уже обрабатывают MouseLeftButtonDown и не поднимают это событие.
Но все еще есть событие PreviewMouseLeftButtonDown. Используйте это на ваших кнопках, чтобы установить значение true, когда ClickCount равно двум, как показано ниже:
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
if (e.ClickCount == 2)
e.Handled = true;
}
Ответ 3
Ну, это может быть не элегантно или идиоматично, но вам может понравиться это лучше, чем ваш текущий обходной путь:
int handledTimestamp = 0;
private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.Timestamp != handledTimestamp)
{
System.Diagnostics.Debug.WriteLine("ListView at " + e.Timestamp);
handledTimestamp = e.Timestamp;
}
e.Handled = true;
}
private void Button_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.Timestamp != handledTimestamp)
{
System.Diagnostics.Debug.WriteLine("Button at " + e.Timestamp);
handledTimestamp = e.Timestamp;
}
e.Handled = true;
}
Странно, что это не работает, если вы не устанавливаете e.Handled = true
. Если вы не установите e.Handled
и поместите контрольную точку или спящий режим в обработчик кнопки, вы увидите задержку в обработчике ListView. (Даже без явной задержки все равно будет небольшая задержка, достаточно, чтобы сломать ее.) Но как только вы установите e.Handled
, не имеет значения, сколько времени задержки существует, у них будет такая же метка времени. Я не уверен, почему это так, и я не уверен, что это документированное поведение, на которое вы можете положиться.
Ответ 4
Поскольку конкретных ответов на этот вопрос не было, это обходной путь, который я использовал:
protected override void ListViewItem_MouseDoubleClick(MouseButtonEventArgs e) {
var originalSource = e.OriginalSource as System.Windows.Media.Visual;
if (originalSource.IsDescendantOf(this)) {
// Test for IsDescendantOf because other event handlers can have changed
// the visual tree such that the actually clicked original source
// component is no longer in the tree.
// You may want to handle the "not" case differently, but for my
// application UI, this makes sense.
for (System.Windows.DependencyObject depObj = originalSource;
depObj != this;
depObj = System.Windows.Media.VisualTreeHelper.GetParent(depObj))
{
if (depObj is System.Windows.Controls.Primitives.ButtonBase) return;
}
}
MessageBox.Show("ListViewItem doubleclicked.");
}
Названия классов здесь без необходимости печатаются с полными пространствами имен для целей документации.
Ответ 5
Control.MouseDoubleClick не является событием с пузырьками, а прямым событием.
После проверки этого вопроса с помощью Snoop, который является инструментом просмотра визуальных деревьев и маршрутизируемых событий, я вижу, что Control.MouseDoubleClick
события из "ListView" и "ListBoxItem" запускаются одновременно. Вы можете проверить этот инструмент Snoop.
Во-первых, чтобы найти ответ, необходимо проверить, что оба аргумента событий MouseDoublClick
- это те же объекты. Вы ожидаете, что они будут такими же объектами. Если это правда, это очень странно, как ваш вопрос, но они не совпадают. Мы можем проверить его с помощью следующих кодов.
RoutedEventArgs _eventArg;
private void Button_MouseDoubleClick(object s, RoutedEventArgs e)
{
if (!e.Handled) Debug.WriteLine("Button got unhandled doubleclick.");
//e.Handled = true;
_eventArg = e;
}
private void ListViewItem_MouseDoubleClick(object s, RoutedEventArgs e)
{
if (!e.Handled) Debug.WriteLine("ListViewItem got unhandled doubleclick.");
e.Handled = true;
if (_eventArg != null)
{
var result = _eventArg.Equals(e);
Debug.WriteLine(result);
}
}
Это означает, что аргумент события MouseDoublClick
создается где-то недавно, но я не понимаю, почему это так.
Чтобы быть яснее, давайте проверим аргумент события BottonBase.Click
. Это вернет правду о проверке одинаковых экземпляров.
<ListView>
<ListViewItem ButtonBase.Click="ListViewItem_MouseDoubleClick">
<Button Click="Button_MouseDoubleClick" Content="click"/>
</ListViewItem>
</ListView>
Если вы только сосредоточитесь на выполнении, как вы упомянули, будет много решений. Как и выше, я считаю, что использование флага (_eventArg
) также является хорошим выбором.
Ответ 6
У меня была такая же проблема. Существует простое, но неочевидное решение.
Вот как дважды щелкнул элемент управления...
private static void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
Control control = (Control)sender;
MouseButtonEventArgs mouseButtonEventArgs = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton, e.StylusDevice);
if (e.RoutedEvent == UIElement.PreviewMouseLeftButtonDownEvent || e.RoutedEvent == UIElement.PreviewMouseRightButtonDownEvent)
{
mouseButtonEventArgs.RoutedEvent = Control.PreviewMouseDoubleClickEvent;
mouseButtonEventArgs.Source = e.OriginalSource;
mouseButtonEventArgs.OverrideSource(e.Source);
control.OnPreviewMouseDoubleClick(mouseButtonEventArgs);
}
else
{
mouseButtonEventArgs.RoutedEvent = Control.MouseDoubleClickEvent;
mouseButtonEventArgs.Source = e.OriginalSource;
mouseButtonEventArgs.OverrideSource(e.Source);
control.OnMouseDoubleClick(mouseButtonEventArgs);
}
if (mouseButtonEventArgs.Handled)
{
e.Handled = true;
}
}
}
Итак, если вы отредактируете PreviewMouseDoubleClick
параметр e.Handled = true
на дочернем элементе управления MouseDoubleClick
не будет срабатывать над родительским элементом управления.
Ответ 7
- Вы не можете легко изменить способ двойного щелчка на событиях, потому что они зависят от пользовательских настроек, и эта задержка настраивается на панели управления.
- Вы должны проверить RepeatButton, который позволяет вам нажимать кнопку, и во время ее нажатия он генерирует несколько событий щелчка в регулярной последовательности.
- Если вы хотите настроить буферизацию событий, вам необходимо выполнить поиск событий Preview, которые позволяют блокировать распространение событий. Что такое WPF Preview Events?