web-dev-qa-db-fra.com

Sélectionnez TreeView Node sur un clic droit avant d'afficher ContextMenu

Je voudrais sélectionner un WPF TreeView Node sur clic droit, juste avant le ContextMenu affiché.

Pour WinForms, je pourrais utiliser du code comme celui-ci Cliquez sur le nœud de recherche sous le menu contextuel , quelles sont les alternatives WPF?

94
alex2k8

Selon la façon dont l'arborescence a été remplie, l'expéditeur et les valeurs e.Source peuvent varier .

L'une des solutions possibles consiste à utiliser e.OriginalSource et à trouver 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;
}
126
alex2k8

Si vous souhaitez une solution XAML uniquement, vous pouvez utiliser Blend Interactivity.

Supposons que TreeView est des données liées à une collection hiérarchique de modèles de vues ayant une propriété BooleanIsSelected et une propriété StringName en tant que 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:

  1. La propriété TreeViewItem.IsSelected Est liée à la propriété IsSelected sur le modèle de vue. La définition de la propriété IsSelected sur le modèle de vue sur true sélectionne le nœud correspondant dans l'arborescence.

  2. Lorsque PreviewMouseRightButtonDown se déclenche sur la partie visuelle du nœud (dans cet exemple un TextBlock), la propriété IsSelected sur le modèle de vue est définie sur true. En revenant à 1. vous pouvez voir que le nœud correspondant sur lequel vous avez cliqué dans l'arborescence devient le nœud sélectionné.

Une façon d'obtenir l'interaction Blend dans votre projet consiste à utiliser le package NuGet nofficial.Blend.Interactivity .

20
Martin Liversage

Utilisation de "item.Focus ();" ne semble pas fonctionner à 100%, en utilisant "item.IsSelected = true;" Est-ce que.

16
Erlend

En utilisant l'idée originale de alex2k8, en gérant correctement les éléments non visuels de Wieser Software Ltd, le XAML de Stefan, le IsSelected d'Erlend, et ma contribution à rendre vraiment générique la méthode statique:

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 bien pour ce scénario, mais dans un autre scénario VisualTreeHelper.GetParent a retourné null lorsque LogicalTreeHelper a renvoyé une valeur, donc corrigé cela.

12
Sean Hall

Dans XAML, ajoutez un gestionnaire PreviewMouseRightButtonDown dans 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>

Ensuite, 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;
        }
    }
11
Stefan

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

Je pense que l'enregistrement d'un gestionnaire de classe devrait faire l'affaire. Enregistrez simplement un gestionnaire d'événements routé sur 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;
    }
}
6
Nathan Swannet

Une autre façon de le résoudre à l'aide de MVVM est la commande bind 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/intera‌​ctivity" 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;
        }
    }
1
benderto

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, il 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:

Objet 1
- Enfant 1
- Enfant 2
- Sous-élément1
- Sous-élément2

Si j'ai sélectionné 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 lourd, mais en gros, j'ai défini le booléen sur true lors du premier passage et je l'ai réinitialisé sur un autre thread en quelques secondes (3 dans ce cas). Cela signifie que le prochain passage à travers lequel il essaiera de se déplacer vers le haut de l'arborescence sera ignoré, vous laissant avec le nœud correct sélectionné. Cela semble fonctionner jusqu'à présent :-)

1
Zoey

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.

0
Scott Thurlow