Je voudrais sélectionner un nœud WPF TreeView sur un clic droit, juste avant le ContextMenu affiché.
Pour WinForms, je pourrais utiliser un code tel que ce nœud de recherche cliqué dans le menu contextuel , quelles sont les alternatives WPF?
Je voudrais sélectionner un nœud WPF TreeView sur un clic droit, juste avant le ContextMenu affiché.
Pour WinForms, je pourrais utiliser un code tel que ce nœud de recherche cliqué dans le menu contextuel , quelles sont les alternatives WPF?
Réponses:
Selon la façon dont l'arborescence a été peuplée, les valeurs de l'expéditeur et de l'e.Source peuvent varier .
L'une des solutions possibles consiste à utiliser e.OriginalSource et à rechercher TreeViewItem à l'aide de 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;
}
if (treeViewItem == null) treeView.SelectedIndex = -1
ou treeView.SelectedItem = null
. Je pense que l'un ou l'autre devrait fonctionner.
Si vous souhaitez une solution XAML uniquement, vous pouvez utiliser Blend Interactivity.
Supposons que les TreeView
données soient liées à une collection hiérarchique de modèles de vue ayant une Boolean
propriété IsSelected
et une String
propriété Name
ainsi qu'une collection d'éléments enfants nommés 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>
Il y a deux parties intéressantes:
La TreeViewItem.IsSelected
propriété est liée à la IsSelected
propriété sur le modèle de vue. La définition de la IsSelected
propriété sur le modèle de vue sur true sélectionnera le nœud correspondant dans l'arborescence.
Lorsque se PreviewMouseRightButtonDown
déclenche sur la partie visuelle du nœud (dans cet exemple a TextBlock
), la IsSelected
propriété du modèle de vue est définie sur true. En revenant à 1. vous pouvez voir que le nœud correspondant sur lequel on a cliqué dans l'arborescence devient le nœud sélectionné.
Une façon d'obtenir l'interactivité de fusion dans votre projet consiste à utiliser le package NuGet Unofficial.Blend.Interactivity .
i
et ei
résolvent et dans quels assemblys ils peuvent être trouvés. Je suppose: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
et xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
, qui se trouvent respectivement dans les assemblys System.Windows.Interactivity et Microsoft.Expression.Interactions.
ChangePropertyAction
tente de définir une IsSelected
propriété de l'objet de données lié, qui ne fait pas partie de l'interface utilisateur, donc il n'a pas de IsSelected
propriété. Est-ce que je fais quelque chose de mal?
IsSelected
propriété comme indiqué dans le deuxième paragraphe de ma réponse: Supposons que les TreeView
données soient liées à une collection hiérarchique de modèles de vue ayant une propriété booléenneIsSelected
... (je souligne).
En XAML, ajoutez un gestionnaire 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>
Puis gérez l'événement comme ceci:
private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
{
TreeViewItem item = sender as TreeViewItem;
if ( item != null )
{
item.Focus( );
e.Handled = true;
}
}
En utilisant l'idée originale de alex2k8, en gérant correctement les non-visuels de Wieser Software Ltd, le XAML de Stefan, le IsSelected d'Erlend et ma contribution à vraiment rendre la méthode statique générique:
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>
Code C # derrière:
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;
}
Edit: Le code précédent fonctionnait toujours très bien pour ce scénario, mais dans un autre scénario, VisualTreeHelper.GetParent retournait null lorsque LogicalTreeHelper renvoyait une valeur, donc corrigé cela.
Presque à droite , mais vous devez faire attention aux éléments non visuels dans l'arborescence (comme un Run
, par exemple).
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;
}
Je pense que l'enregistrement d'un gestionnaire de classe devrait faire l'affaire. Enregistrez simplement un gestionnaire d'événements routés sur le PreviewMouseRightButtonDownEvent de TreeViewItem dans votre fichier de code app.xaml.cs comme ceci:
/// <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;
}
}
Une autre façon de le résoudre à l'aide de MVVM est la commande de liaison pour un clic droit sur votre modèle de vue. Là, vous pouvez spécifier une autre logique ainsi que source.IsSelected = true
. Cela utilise uniquement à xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
partir de System.Windows.Interactivity
.
XAML pour la vue:
<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>
Voir le modèle:
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;
}
}
J'avais un problème avec la sélection d'enfants avec une méthode HierarchicalDataTemplate. Si je sélectionnais l'enfant d'un nœud, cela sélectionnerait en quelque sorte le parent racine de cet enfant. J'ai découvert que l'événement MouseRightButtonDown serait appelé pour chaque niveau de l'enfant. Par exemple, si vous avez un arbre quelque chose comme ceci:
Rubrique 1
- Enfant 1
- Enfant 2
- Sous
-élément1 - Sous -élément2
Si je sélectionnais Subitem2, l'événement se déclencherait trois fois et l'élément 1 serait sélectionné. J'ai résolu cela avec un appel booléen et asynchrone.
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; });
}
Cela semble un peu brouillon, mais en gros, je règle le booléen sur true lors du premier passage et le réinitialise sur un autre thread en quelques secondes (3 dans ce cas). Cela signifie que le prochain passage par où il essaierait de monter dans l'arborescence sera ignoré, vous laissant avec le bon nœud sélectionné. Cela semble fonctionner jusqu'à présent :-)
MouseButtonEventArgs.Handled
sur true
. Puisque l'enfant est le premier à être appelé. Si cette propriété est définie sur true, les autres appels au parent seront désactivés.
Vous pouvez le sélectionner avec l'événement on mouse down. Cela déclenchera la sélection avant que le menu contextuel ne démarre.
Si vous souhaitez rester dans le modèle MVVM, vous pouvez effectuer les opérations suivantes:
Vue:
<TreeView x:Name="trvName" ItemsSource="{Binding RootElementListView}" Tag="{Binding ClickedTreeElement, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:YourTreeElementClass}" ItemsSource="{Binding Path=Subreports}">
<TextBlock Text="{Binding YourTreeElementDisplayProperty}" PreviewMouseRightButtonDown="TreeView_PreviewMouseRightButtonDown"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code derrière:
private void TreeView_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is TextBlock tb && tb.DataContext is YourTreeElementClass te)
{
trvName.Tag = te;
}
}
VoirModèle:
private YourTreeElementClass _clickedTreeElement;
public YourTreeElementClass ClickedTreeElement
{
get => _clickedTreeElement;
set => SetProperty(ref _clickedTreeElement, value);
}
Vous pouvez maintenant réagir à la modification de propriété ClickedTreeElement ou utiliser une commande qui fonctionne en interne avec ClickedTreeElement.
Vue étendue:
<UserControl ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<TreeView x:Name="trvName" ItemsSource="{Binding RootElementListView}" Tag="{Binding ClickedTreeElement, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonUp">
<i:InvokeCommandAction Command="{Binding HandleRightClickCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:YourTreeElementClass}" ItemsSource="{Binding Path=Subreports}">
<TextBlock Text="{Binding YourTreeElementDisplayProperty}" PreviewMouseRightButtonDown="TreeView_PreviewMouseRightButtonDown"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</UserControl>