c# - MouseDoubleClick eventos no burbujean
wpf mouseevent (7)
Mi escenario, simplificado: tengo un ListView que contiene filas de Empleados, y en cada fila de Empleados, hay botones "Aumentar" y "Reducir" ajustando su salario.
Haga de cuenta que en mi programa, 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 activa un evento de doble clic en el ListViewItem. Naturalmente, no quiero despedir a las personas cuando solo estoy aumentando su salario.
De acuerdo con cómo funcionan 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, 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 manejador:
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 activar este programa y hacer doble clic en el botón de la lista, ambos cuadros de mensajes aparecen en secuencia. (Además, el botón se atasca en la posición hacia abajo después de esto).
Como "solución" puedo , en el controlador ListViewItem, inspeccionar el árbol visual adjunto al evento y verificar que "hay un botón en alguna parte" y, por lo tanto, descartar el evento, pero este es un último recurso. Quiero al menos entender el problema antes de codificar tal confusión.
¿Alguien sabe por qué WPF hace esto y una manera elegante e idiomática de evitar el problema?
- No 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.
- Debería retirar el botón RepeatButton que le permite presionar el botón y mientras se presiona, genera múltiples eventos de clic en una secuencia regular.
- 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. ¿Qué son los eventos de vista previa de WPF?
Acabo de tener este mismo problema. Hay una solución simple pero no obvia.
Aquí es cómo doble clic es levantado por 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;
}
}
}
Por lo tanto, si controla la configuración de PreviewMouseDoubleClick
e.Handled = true
en el control MouseDoubleClick
, MouseDoubleClick
no se MouseDoubleClick
en el control principal.
Creo que encontrarás que el evento MouseDoubleClick
es una abstracción sobre el evento MouseDown
. Es decir, si dos eventos MouseDown
ocurren en una sucesión lo suficientemente rápida, el evento MouseDoubleClick
también se activará. Tanto Button
como ListViewItem
parecen tener esta lógica, por lo que explica por qué estás viendo dos eventos distintos de MouseDoubleClick
.
Según MSDN :
Aunque este evento enrutado parece seguir una ruta de propagación a través de un árbol de elementos, en realidad es un evento enrutado directo que se eleva a lo largo del árbol de elementos por cada UIElement. Si establece la propiedad Handled en true en un controlador de eventos MouseDoubleClick, los eventos posteriores de MouseDoubleClick a lo largo de la ruta ocurrirán con Handled establecido en false.
Puede intentar controlar MouseDown
en el Button
y configurarlo para que no se propague a ListViewItem
.
Ojalá pudiera verificar esto, pero no tengo .NET en este momento.
La MSDN para el MouseDoubleClick da una sugerencia sobre cómo evitar que el evento MouseDoubleClick brille:
Los autores de control que desean manejar los clics dobles del mouse deben usar el evento MouseLeftButtonDown cuando ClickCount es igual a dos. Esto hará que el estado de Manejado se propague adecuadamente en el caso de que otro elemento del árbol de elementos maneje el evento.
Por lo tanto, podría usar el evento MouseLeftButtonDown y establecer colgado en verdadero si ClickCount es dos. Pero esto falla en los botones porque ya manejan el MouseLeftButtonDown y no provocan ese evento.
Pero todavía existe el evento PreviewMouseLeftButtonDown. Use eso en sus botones para establecer manejado en verdadero cuando ClickCount es igual a dos como se muestra a continuación:
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
if (e.ClickCount == 2)
e.Handled = true;
}
Puede que no sea elegante o idiomático, pero puede que te guste más que tu 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 establece e.Handled = true
. Si no configura e.Handled
y coloca un punto de interrupción o Sleep en el controlador del botón, verá la demora 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 configura e.Handled
, no importa cuánto tiempo de retraso haya, tendrán la misma marca de tiempo. No estoy seguro de por qué esto es así, y no estoy seguro de si se trata de un comportamiento documentado en el que pueda confiar.
Ya 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 se escriben aquí innecesariamente con espacios de nombres completos para fines de documentación.
MSDN no es un evento de burbuja sino un evento directo.
Desde que revisé esta pregunta con Snoop , que es una herramienta para explorar árboles visuales y eventos enrutados, veo que los eventos Control.MouseDoubleClick
de ''ListView'' y ''ListBoxItem'' se activan al mismo tiempo. Podrías consultar con esta herramienta Snoop.
Primero, para encontrar una respuesta, es necesario verificar que ambos argumentos de evento de MouseDoublClick
sean los mismos objetos. Usted esperaría que sean los mismos objetos. Si es verdad, es muy extraño como su pregunta, pero no son las mismas instancias. Podemos comprobarlo 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 del MouseDoublClick
se creó recientemente en algún lugar, pero no entiendo muy bien por qué lo es.
Para ser más claros, verifiquemos el argumento del evento de BottonBase.Click
. 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 se enfoca en la ejecución como mencionó, habrá muchas soluciones. Como arriba, creo que usar la bandera ( _eventArg
) también es una buena opción.