2011-06-08 14 views
16

Mi situación, simplificada: tengo un ListView que contiene filas de empleados, y en cada fila Empleado, hay botones "Aumentar" y "Disminuir" para ajustar su salario.Los eventos MouseDoubleClick no hacen burbujas

Supongamos que en mi programa, al hacer doble clic en una fila de empleado significa "despedir a esta persona".

El problema es que mientras hago clic en "Aumentar" rápidamente, esto desencadena un evento de doble clic en ListViewItem. Naturalmente, no quiero despedir personas cuando solo estoy aumentando su salario.

De acuerdo con el funcionamiento de todos los demás eventos, espero poder resolver esto configurando Handled=true en el evento. Esto, sin embargo, no funciona. Me parece que WPF genera dos eventos de doble clic separados y completamente desvinculados.

El siguiente es un ejemplo mínimo para reproducir mi problema. Los componentes visibles:

<ListView> 
    <ListViewItem MouseDoubleClick="ListViewItem_MouseDoubleClick"> 
      <Button MouseDoubleClick="Button_MouseDoubleClick"/> 
    </ListViewItem> 
</ListView> 

Y el código del controlador:

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

Después de disparar este programa y haga doble clic en el botón de la lista, ambos cuadros de mensajes aparecen en secuencia. (Además, el botón está atascado en la posición hacia abajo después de esto.)

como una "solución" Me puedo, en el manejador de ListViewItem, inspeccionar el árbol visual adjunta al evento y comprobar que "hay un botón allí en algún lugar "y así descartar el evento, pero este es un último recurso. Al menos quiero entender el problema antes de codificar tal kludge.

¿Alguien sabe por qué WPF hace esto, y una elegante manera idiomática para evitar el problema?

Respuesta

10

Creo que encontrará que el evento MouseDoubleClick es una abstracción en la parte superior del evento MouseDown. Es decir, si se producen dos eventos MouseDown en una sucesión suficientemente rápida, también se generará el evento MouseDoubleClick. Tanto Button como ListViewItem parecen tener esta lógica, por lo que esto explica por qué está viendo dos eventos distintos MouseDoubleClick.

Según MSDN:

Aunque este evento enrutado parece seguir una ruta que burbujea a través de un árbol de elementos , que en realidad es un evento enrutado directo que se alza a lo largo del árbol elemento por cada UIElement . Si establece la propiedad Handled en verdad en un controlador de eventos MouseDoubleClick, subsiguientes eventos MouseDoubleClick a lo largo de la ruta se producirá con conjunto Handled en falso.

Usted podría intentar manejar MouseDown en el Button y establecer que a manejado de modo que no se propaga a la ListViewItem.

Desearía poder verificar esto yo mismo, pero estoy .NET-less en este momento.

+0

Gracias, pero tampoco puedo hacer que encaje bien. Un controlador para MouseDown/MouseLeftButtonDown en Button (o en un contenedor contenedor alrededor de Button) nunca se desencadena, por lo que parece que el evento ya está 'Handled' by Button. Además, no puedo inferir clics dobles de MouseDown/MouseLeftButtonDown en ListViewItem, ya que estos eventos son manejados y tragados por otros componentes visuales en los contenidos ListViewItem (como cuadros de texto). – Deestan

3

Dado que no ha habido respuestas definitivas a esta pregunta, esta es la solución que terminé usando:

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

Los nombres de clase están aquí innecesariamente mecanografiadas con espacios de nombres completos para fines de documentación.

4

Bueno, no puede ser elegante o idiomática, pero se les puede gustar mejor que su solución actual:

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

Lo extraño es que esto no funciona si no se establece e.Handled = true. Si no establece e.Handled y pone un punto de interrupción o un reposo en el controlador del botón, verá el retraso en el controlador de ListView. (Incluso sin un retraso explícito todavía habrá un pequeño retraso, suficiente para romperlo.) Pero una vez que establezca e.Handled, no importa cuánto tiempo haya, tendrán la misma marca de tiempo. No estoy seguro de por qué esto es así, y no estoy seguro si este es un comportamiento documentado en el que puede confiar.

+0

No es hermoso, pero es interesante. :) Dado que los dos eventos de clic surgen exactamente del mismo mensaje de la ventana del sistema operativo MouseDown, la marca de tiempo puede estar vinculada a eso. Si este es un comportamiento documentado en alguna parte, es una buena solución. Lamentablemente, MSDN no arroja mucha luz aquí. – Deestan

+0

Probado esto. Funciona en general, pero cada vez que algún código toca la cola de procesamiento de eventos por alguna razón u otra, se rompe. No recomendaría usar esto. – Deestan

+0

Gracias por esta solución: hemos terminado usando una variación. Como señaló Deestan, las marcas de tiempo son diferentes en algunas circunstancias (aparte de la circunstancia de no marcar como se maneja). Solo cuadrar el intervalo entre sucesos sucesivos a unos pocos cientos de ms funcionó bien para mí. – alexei

8

El MSDN documentation para la MouseDoubleClick da una sugerencia sobre cómo mantener el evento MouseDoubleClick de burbujeando:

autores de control que quieren manejar hace doble clic ratón debe utilizar el evento MouseLeftButtonDown cuando ClickCount es igual a dos. Esto hará que provoque que el estado de Handled a se propague apropiadamente en el caso donde otro elemento en el árbol del elemento maneja el evento.

Así que podría tener el evento MouseLeftButtonDown y establecer colgado en verdadero si ClickCount es dos. Pero esto falla en los botones porque ya manejan MouseLeftButtonDown y no provocan ese evento.

Pero aún existe el evento PreviewMouseLeftButtonDown. El uso que en sus botones para establecer manejado a cierto cuando ClickCount es igual a dos de la siguiente manera:

private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { 
    if (e.ClickCount == 2) 
     e.Handled = true; 
} 
+0

"Pero aún existe el evento PreviewMouseLeftButtonDown. Use eso en sus botones para configurar manejado a verdadero" - Esto no funciona, ya que los eventos de vista previa pasan a través de ListViewItem * primero *. – Deestan

+0

@Deestan, sí, pero PreviewMouseLeftButtonDown estará antes que PreviewMouseDoubleClick. ListViewItem solo está buscando lo último para que pueda manejar el anterior para detenerlo. –

+0

@Robert Levy: Ah, ya veo. Sí, eso realmente funciona. Sin embargo, da una falla visual, ya que si presiona hacia abajo luego de hacer doble clic, el botón no baja. Además, solo funciona * accidentalmente * - no estamos siguiendo ningún comportamiento documentado. – Deestan

2

Control.MouseDoubleClick no es un evento de burbuja, pero un evento directa.

Dado que la comprobación a esta pregunta con Snoop, que es una herramienta para navegar por los árboles visuales y eventos enrutados, veo que Control.MouseDoubleClick eventos de 'ListView' y 'ListBoxItem' se disparan al mismo tiempo. Puede consultar con esta herramienta Snoop.


En primer lugar, para encontrar una respuesta, que es necesaria para comprobar que ambos argumentos del evento de la MouseDoublClick son los mismos objetos. Es de esperar que sean los mismos objetos. Si es cierto, es muy extraño como su pregunta, pero no son las mismas instancias. Podemos verificarlo con los siguientes códigos.

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

significa que el argumento de evento de la MouseDoublClick se crea de nuevo en alguna parte, pero no entiendo por qué es profundamente.

Para ser más claros, vamos a verificar el argumento del evento BottonBase.Click. Se devolverá la verdad sobre la comprobación de las mismas instancias.

<ListView> 
     <ListViewItem ButtonBase.Click="ListViewItem_MouseDoubleClick"> 
      <Button Click="Button_MouseDoubleClick" Content="click"/> 
     </ListViewItem> 
    </ListView> 

Si solo te enfocas en la ejecución como mencionaste, habrá muchas soluciones. Como arriba, creo que usar la bandera (_eventArg) también es una buena opción.

-1
  1. No se puede cambiar fácilmente la forma en que se activan los eventos de doble clic porque dependen de la configuración del usuario y ese retraso se personaliza en el panel de control.
  2. Debe verificar RepeatButton que le permite presionar el botón y mientras está presionado genera múltiples eventos de clic en secuencia regular.
  3. En caso de que desee personalizar el burbujeo de eventos, debe buscar eventos de vista previa que le permitan bloquear la propagación de eventos. What are WPF Preview Events?
+1

no responde la pregunta –

1

Acabo de tener este mismo problema. Hay una solución simple pero no obvia.

Así es como el doble clic se eleva por el 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; 
     } 
    } 
} 

Así que si usted maneja PreviewMouseDoubleClick establecer e.Handled = true en el control secundario MouseDoubleClick no se disparará en el control de los padres.

Cuestiones relacionadas