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

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

4b9b3361

Ответ 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?