wpf - Seleccione el nodo TreeView en el botón derecho del mouse antes de mostrar ContextMenu
(10)
Quisiera seleccionar un nodo TreeView de WPF con el botón derecho, justo antes de que se muestre ContextMenu.
Para WinForms podría usar un código como este nodo Buscar en el menú contextual , ¿cuáles son las alternativas de WPF?
Creo que registrar un controlador de clase debería ser el truco. Simplemente registre un manejador de eventos enrutados en PreviewMouseRightButtonDownEvent de TreeViewItem en su archivo de código app.xaml.cs como este:
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
EventManager.RegisterClassHandler(typeof(TreeViewItem), TreeViewItem.PreviewMouseRightButtonDownEvent, new RoutedEventHandler(TreeViewItem_PreviewMouseRightButtonDownEvent));
base.OnStartup(e);
}
private void TreeViewItem_PreviewMouseRightButtonDownEvent(object sender, RoutedEventArgs e)
{
(sender as TreeViewItem).IsSelected = true;
}
}
Dependiendo de la forma en que se haya rellenado el árbol, el emisor y los valores de e.Source pueden variar .
Una de las posibles soluciones es usar e.OriginalSource y encontrar TreeViewItem usando el VisualTreeHelper:
private void OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
TreeViewItem treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject);
if (treeViewItem != null)
{
treeViewItem.Focus();
e.Handled = true;
}
}
static TreeViewItem VisualUpwardSearch(DependencyObject source)
{
while (source != null && !(source is TreeViewItem))
source = VisualTreeHelper.GetParent(source);
return source as TreeViewItem;
}
En XAML, agregue un controlador PreviewMouseRightButtonDown en XAML:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- We have to select the item which is right-clicked on -->
<EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
</Style>
</TreeView.ItemContainerStyle>
Luego maneja el evento de esta manera:
private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
{
TreeViewItem item = sender as TreeViewItem;
if ( item != null )
{
item.Focus( );
e.Handled = true;
}
}
Estaba teniendo un problema al seleccionar niños con un método HierarchicalDataTemplate. Si seleccioné el elemento secundario de un nodo, de alguna manera seleccionaría el padre raíz de ese niño. Descubrí que se llamaría al evento MouseRightButtonDown para cada nivel que el niño era. Por ejemplo, si tienes un árbol, algo como esto:
Artículo 1
- Niño 1
- Niño 2
- Subitem1
- Subitem2
Si seleccioné Subitem2, el evento se dispararía tres veces y se seleccionaría el elemento 1. Lo resolví con una llamada booleana y una asíncrona.
private bool isFirstTime = false;
protected void TaskTreeView_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var item = sender as TreeViewItem;
if (item != null && isFirstTime == false)
{
item.Focus();
isFirstTime = true;
ResetRightClickAsync();
}
}
private async void ResetRightClickAsync()
{
isFirstTime = await SetFirstTimeToFalse();
}
private async Task<bool> SetFirstTimeToFalse()
{
return await Task.Factory.StartNew(() => { Thread.Sleep(3000); return false; });
}
Se siente un poco discordante, pero básicamente establecí el booleano en verdadero en el primer paso y lo restablecí en otro hilo en unos pocos segundos (3 en este caso). Esto significa que el próximo paso por donde intentaría moverse hacia arriba del árbol se omitirá, dejándolo con el nodo correcto seleccionado. Parece que funciona hasta ahora :-)
Otra forma de resolverlo usando MVVM es el comando de enlace para hacer clic con el botón derecho en su modelo de vista. Allí puede especificar otra lógica además de source.IsSelected = true
. Esto usa solo xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
de System.Windows.Interactivity
.
XAML para ver:
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseRightButtonDown">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.TreeViewItemRigthClickCommand}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Ver modelo:
public ICommand TreeViewItemRigthClickCommand
{
get
{
if (_treeViewItemRigthClickCommand == null)
{
_treeViewItemRigthClickCommand = new RelayCommand<object>(TreeViewItemRigthClick);
}
return _treeViewItemRigthClickCommand;
}
}
private RelayCommand<object> _treeViewItemRigthClickCommand;
private void TreeViewItemRigthClick(object sourceItem)
{
if (sourceItem is Item)
{
(sourceItem as Item).IsSelected = true;
}
}
Puede seleccionarlo con el evento en el mouse hacia abajo. Eso activará la selección antes de que el menú contextual entre en acción.
Si desea una solución exclusiva de XAML, puede usar Blend Interactivity.
Supongamos que TreeView
está vinculado a una colección jerárquica de modelos de vista que tienen una propiedad Boolean
IsSelected
y un Name
propiedad String
, así como una colección de elementos secundarios denominada Children
.
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseRightButtonDown">
<ei:ChangePropertyAction PropertyName="IsSelected" Value="true" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Hay dos partes interesantes:
La propiedad
TreeViewItem.IsSelected
está vinculada a la propiedadIsSelected
en el modelo de vista. Establecer la propiedadIsSelected
en el modelo de vista en true seleccionará el nodo correspondiente en el árbol.Cuando
PreviewMouseRightButtonDown
dispara en la parte visual del nodo (en este ejemplo, unTextBlock
), la propiedadIsSelected
en el modelo de vista se establece en verdadero. Volviendo a 1. puede ver que el nodo correspondiente en el que se hizo clic en el árbol se convierte en el nodo seleccionado.
Una forma de obtener Blend Interactivity en su proyecto es usar el paquete NuGet Unofficial.Blend.Interactivity .
Usando "item.Focus ();" no parece funcionar al 100%, usando "item.IsSelected = true;" hace.
Usando la idea original de alex2k8, manejando correctamente los elementos no visuales de Wieser Software Ltd, el XAML de Stefan, el IsSelected de Erlend, y mi contribución de hacer verdaderamente el método estático genérico:
XAML:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- We have to select the item which is right-clicked on -->
<EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown"
Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
</Style>
</TreeView.ItemContainerStyle>
C # código detrás:
void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
TreeViewItem treeViewItem =
VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject);
if(treeViewItem != null)
{
treeViewItem.IsSelected = true;
e.Handled = true;
}
}
static T VisualUpwardSearch<T>(DependencyObject source) where T : DependencyObject
{
DependencyObject returnVal = source;
while(returnVal != null && !(returnVal is T))
{
DependencyObject tempReturnVal = null;
if(returnVal is Visual || returnVal is Visual3D)
{
tempReturnVal = VisualTreeHelper.GetParent(returnVal);
}
if(tempReturnVal == null)
{
returnVal = LogicalTreeHelper.GetParent(returnVal);
}
else returnVal = tempReturnVal;
}
return returnVal as T;
}
Editar: El código anterior siempre funcionó bien para este escenario, pero en otro escenario, VisualTreeHelper.GetParent devolvió nulo cuando LogicalTreeHelper devolvió un valor, por lo que lo corrigió.
Casi correcto , pero debes tener cuidado con los elementos no visuales en el árbol (como, por ejemplo, Run
).
static DependencyObject VisualUpwardSearch<T>(DependencyObject source)
{
while (source != null && source.GetType() != typeof(T))
{
if (source is Visual || source is Visual3D)
{
source = VisualTreeHelper.GetParent(source);
}
else
{
source = LogicalTreeHelper.GetParent(source);
}
}
return source;
}