События MouseDoubleClick не всплывают - PullRequest
17 голосов
/ 08 июня 2011

Мой сценарий упрощен: у меня есть ListView, содержащий строки Employees, и в каждой строке Employee есть кнопки «Увеличить» и «Уменьшить», регулирующие его зарплату.

Представьте, что в моей программе двойное значение- щелчок строки 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;
}

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

В качестве «исправления» I can в обработчике ListViewItem проверяет визуальное дерево, присоединенное к событию, и проверяетчто "там где-то есть кнопка" и, таким образом, отбрасывают событие, но это последнее средство.Я хочу, по крайней мере, понять проблему, прежде чем кодировать такой ключ.

Кто-нибудь знает почему WPF делает это, и элегантный идиоматический способ избежать проблемы?

Ответы [ 7 ]

10 голосов
/ 08 июня 2011

Я думаю, вы обнаружите, что событие MouseDoubleClick является абстракцией поверх события MouseDown.То есть, если два MouseDown события происходят достаточно быстро, событие MouseDoubleClick также будет вызвано.И Button, и ListViewItem, похоже, обладают этой логикой, поэтому это объясняет, почему вы видите два отдельных MouseDoubleClick события.

Согласно MSDN :

Хотя это перенаправленное событие, по-видимому, следует по пузырчатому маршруту через дерево элементов, на самом деле оно является перенаправленным событием, которое генерируется вдоль дерева элементов каждым элементом UIElement. Если установить для свойства Handled значение true в обработчике события MouseDoubleClick, последующие события MouseDoubleClick вдоль маршрута будут происходить, а для параметра Handled установлено значение false.

Вы можете попробовать обработать MouseDownна Button и настройте его так, чтобы он не распространялся на ListViewItem.

Хотел бы я сам это проверить, но на данный момент у меня нет .NET.

8 голосов
/ 13 июня 2011

Документация MSDN для MouseDoubleClick содержит рекомендации о том, как предотвратить всплывание события MouseDoubleClick:

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

Таким образом, вы можете передать событие MouseLeftButtonDown и установить значение true, если ClickCount равен двум. Но это не работает для кнопок, потому что они уже обрабатывают MouseLeftButtonDown и не вызывают это событие.

Но все равно есть событие PreviewMouseLeftButtonDown. Используйте это на своих кнопках, чтобы установить для handled значение true, когда ClickCount равен двум, как показано ниже:

 private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)   {
     if (e.ClickCount == 2)
         e.Handled = true;
 }
4 голосов
/ 13 июня 2011

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

    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 и не установите точку останова или Sleep в обработчике кнопки, вы увидите задержку в обработчике ListView.(Даже без явной задержки все равно будет небольшая задержка, достаточная, чтобы ее сломать.) Но как только вы установите e.Handled, не имеет значения, как долго будет задержка, они будут иметь одинаковую метку времени.Я не уверен, почему это так, и я не уверен, документировано ли это поведение, на которое вы можете положиться.

4 голосов
/ 10 июня 2011

Поскольку не было определенных ответов на этот вопрос, это обходной путь, который я в итоге использовал:

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's 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.");
}

Имена классов здесь излишне набираются с полными пространствами имен для целей документации.

2 голосов
/ 16 июня 2011

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) также является хорошим выбором.

1 голос
/ 29 июля 2012

У меня только что была такая же проблема. Есть простое, но неочевидное решение.

Вот как Двойной щелчок поднимается Control ...

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 не сработает на родительском элементе управления.

0 голосов
/ 16 июня 2011
  1. Вы не можете легко изменить способ срабатывания событий двойного щелчка, поскольку они зависят от пользовательских настроек и эта задержка настраивается на панели управления.
  2. Вы должны оформить RepeatButton, который позволяет вам нажимать кнопку и, пока она нажата, генерирует несколько событий щелчка в регулярной последовательности.
  3. В случае, если вы хотите настроить всплытие событий, вам следует искать события предварительного просмотра, которые позволяют блокировать распространение событий. Что такое предварительные события WPF?
...