wpf - ¿Por qué el evento MouseDoubleClick del TreeViewItem se genera varias veces por doble clic?
mouseevent (9)
XAML
<TreeView Name="GroupView" ItemsSource="{Binding Documents}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="OnTreeNodeDoubleClick"/>
</Style>
</TreeView.ItemContainerStyle>
....
</TreeView>
Código detrás
private void OnTreeNodeDoubleClick(object sender, MouseButtonEventArgs mouseEvtArgs)
{
Console.WriteLine("{3} MouseDoubleClick Clicks={0} ChangedButton={1} Source={2} Handled={4} ButtonState={5}",
mouseEvtArgs.ClickCount, mouseEvtArgs.ChangedButton, mouseEvtArgs.OriginalSource,
mouseEvtArgs.Timestamp, mouseEvtArgs.Handled, mouseEvtArgs.ButtonState);
}
Me parece que para un doble clic, el controlador de eventos se llama varias veces. Estoy tratando de abrir un documento en una pestaña haciendo doble clic en el nodo del árbol correspondiente; así que tendría que filtrar las llamadas adicionales.
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
En mi aplicación un poco complicada, se plantea 4 veces por doble clic. En una aplicación de reproducción simple, se genera 2 veces por doble clic. También todos los parámetros de argumento de evento son los mismos, así que no puedo distinguir el último de un conjunto.
¿Alguna idea de por qué esto es así?
Cuando se hace doble clic en un TreeViewItem
, ese elemento se selecciona como parte del comportamiento de control. Dependiendo de la situación particular podría ser posible decir:
...
TreeViewItem tviSender = sender as TreeViewItem;
if (tviSender.IsSelected)
DoAction();
...
Este es el maravilloso mundo del evento burbujeante. El evento está burbujeando la jerarquía de nodos de su TreeView y se llama a su manejador una vez para cada nodo en la ruta de la jerarquía.
Solo usa algo como
// ...
if (sender != this)
{
return;
}
// Your handler code goes here ...
args.Handled = true;
// ...
en su código de controlador.
Esto no es realmente un problema burbujeante. He visto esto antes. Incluso cuando le dices al evento que lo manejaste, continúa haciéndolo. Excepto que no creo que en realidad se esté formando burbujas, sino que se activa el evento de doble clic del nodo anterior. Podría estar totalmente equivocado en eso. Pero en cualquier caso, es importante saber que dice:
e.handled = true;
No hace nada para evitar que esto suceda.
Una forma de evitar este comportamiento es tener en cuenta que cuando haces doble clic, primero haces un solo clic y que el evento seleccionado debería dispararse primero. Por lo tanto, si bien no puede evitar que se produzcan los eventos de doble clic, debe poder verificar dentro del controlador para ver si la lógica del evento debería ejecutarse. Este ejemplo aprovecha que:
TreeViewItem selectedNode;
private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e)
{
if(selectedNode = e.Source)
{
//do event logic
}
}
private void TreeViewSelectedEventHandler(object sender, RoutedEventArgs e)
{
selectedNode = (TreeViewItem)e.Source;
}
A veces, sin embargo, hay situaciones en las que los nodos están siendo seleccionados por otros beans que a través del evento TreeView SelectedItemChanged. En ese caso puedes hacer algo como esto. Si tiene un TreeView con un único nodo superior declarado, puede darle a ese nodo un nombre específico y luego hacer algo como esto:
bool TreeViewItemDoubleClickhandled;
private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e)
{
if (!TreeViewItemDoubleClickhandled)
{
//do logic here
TreeViewItemDoubleClickhandled = true;
}
if (e.Source == tviLoadTreeTop)
{
TreeViewItemDoubleClickhandled = false;
}
e.Handled = true;
}
Independientemente del método que utilice, lo importante es tener en cuenta que, por el motivo que sea, al hacer doble clic en TreeViewItem, no puede evitar que los eventos activen el árbol. Al menos no he encontrado una manera.
Hay algunos problemas muy importantes con esta solución, pero podría funcionar en caso de que alguien necesite resolver este problema en múltiples lugares y encontré un escenario en el que la solución aceptada no funciona (hacer doble clic en el botón de alternar que abre un emergente, donde el botón de alternar está dentro de otro elemento que controla el doble clic.)
public class DoubleClickEventHandlingTool
{private const string DoubleClickEventHandled = "DoubleClickEventHandled";
public static void HandleDoubleClickEvent()
{
Application.Current.Properties[DoubleClickEventHandled] = DateTime.Now.AddSeconds(1);
}
public static bool IsDoubleClickEventHandled()
{
var doubleClickWasHandled = Application.Current.Properties[DoubleClickEventHandled] as DateTime?;
return doubleClickWasHandled.HasValue && !IsDateTimeExpired(doubleClickWasHandled.Value);
}
private static bool IsDateTimeExpired(DateTime value)
{
return value < DateTime.Now;
}
public static void EnableDoubleClickHandling()
{
Application.Current.Properties[DoubleClickEventHandled] = null;
}
public static bool IsDoubleClickEventHandledAndEnableHandling()
{
var handled = IsDoubleClickEventHandled();
EnableDoubleClickHandling();
return handled;
}
}
Use DoubleClickEventHandlingTool.HandleDoubleClickEvent () dentro del elemento de nivel interno / bajo, por ejemplo:
private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{if (e.ClickCount == 2) DoubleClickEventHandlingTool.HandleDoubleClickEvent();}
El evento de doble clic de alto nivel solo realiza su acción cuando:
if (!DoubleClickEventHandlingTool.IsDoubleClickEventHandledAndEnableHandling())
He hecho un poco de depuración y parece ser un error en WPF. La mayoría de las respuestas ya dadas son correctas, y la solución es verificar si el elemento de vista de árbol está seleccionado.
La respuesta de @ ristogod es la más cercana al problema raíz: menciona que la configuración de e.Handled = true
la primera vez que se invoca el controlador no tiene el efecto deseado y el evento continúa aumentando, al llamar a los controladores padre TreeViewItem
s (donde e.Handled
es false
otra vez).
El error parece estar en este código en WPF: http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,5ed30e0aec6a58b2
Recibe el evento MouseLeftButtonDown
(que ya es manejado por el control secundario), pero no comprueba si e.Handled
ya está configurado como verdadero. Luego se procede a crear un nuevo MouseDoubleClick
evento MouseDoubleClick
(con e.Handled == false
) y se invoca siempre.
La pregunta también sigue siendo: ¿por qué, después de configurarlo para que se maneje la primera vez que el evento sigue burbujeando? Porque en esta línea, cuando registramos el controlador Control.HandleDoubleClick
: http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,40
pasamos verdadero como el último argumento para RegisterClassHandler
: http://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/EventManager.cs,161 que se handledEventsToo
.
Así que el comportamiento desafortunado es una confluencia de dos factores:
-
Control.HandleDoubleClick
se llama siempre (para eventos manejados también), y -
Control.HandleDoubleClick
no puede verificar si el evento ya se ha manejado
Notificaré al equipo de WPF, pero no estoy seguro de que valga la pena corregir este error porque podría dañar las aplicaciones existentes (que dependen del comportamiento actual de los controladores de eventos que se llaman, incluso si Handled
estaba configurado como verdadero por un controlador anterior).
La razón más probable es que el controlador de doble clic se instale varias veces, por lo que cada instancia del controlador se llama una vez por cada clic.
Sé que esta es una pregunta antigua, pero cuando la encontré en mis búsquedas de la solución, ¡aquí están mis conclusiones para futuros visitantes!
TreeViewItem
s están contenidos recursivamente uno dentro del otro. TreeViewItem
es un HeaderedContentControl
(ver msdn ), con los nodos secundarios como el Content
. Por lo tanto, los límites de cada TreeViewItem
incluyen todos sus elementos secundarios. Esto se puede verificar usando el excelente WPF Inspector seleccionando un TreeViewItem
en el árbol visual, que resaltará los límites de TreeViewItem
.
En el ejemplo del OP, el evento MouseDoubleClick
se registra en cada TreeViewItem
usando el estilo. Por lo tanto, el evento se generará para los TreeViewItem
los que hizo doble clic, y en cada uno de sus elementos principales, por separado. Esto se puede verificar en su depurador colocando un punto de interrupción en su controlador de eventos de doble clic y vigilando la propiedad Origen de los argumentos del evento. Notará que cambia cada vez que se llama al controlador de eventos. Por cierto, como se puede esperar, la OriginalSource
del evento sigue siendo la misma.
Para contrarrestar este comportamiento inesperado, verificando si la fuente TreeViewItem
está seleccionada, como lo sugirió Pablo en su respuesta, ha funcionado mejor para mí.
Tengo una solución un poco más elegante que comprobar la selección o crear marcas:
Un método de ayuda:
public static object GetParent(this DependencyObject obj, Type expectedType) {
var parent = VisualTreeHelper.GetParent(obj);
while (parent != null && parent.GetType() != expectedType)
parent = VisualTreeHelper.GetParent(parent);
return parent;
}
Y luego su manejador:
public void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is DependencyObject)
if (sender == (e.OriginalSource as DependencyObject).GetParent(typeof(TreeViewItem)))
{
// sender is the node, which was directly doubleclicked
}
}
private void TreeView_OnItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.Source is TreeViewItem
&& (e.Source as TreeViewItem).IsSelected)
{
// your code
e.Handled = true;
}
}