web-dev-qa-db-fra.com

Comment sélectionner un élément TreeView à partir du code

J'ai un treeview à trois niveaux. Comment sélectionner un élément du troisième niveau du code? J'ai essayé une méthode mentionnée dans de nombreux blogs et sur stackoverflow mais elle semble fonctionner uniquement pour le premier niveau (dbObject est null pour les éléments situés au-dessous du premier niveau). 

Voici le code que j'utilise pour sélectionner TreeViewItem. Est-ce que quelque chose me manque? 

public static void SetSelectedItem(this TreeView control, object item)
{
    try
    {
        var dObject = control.ItemContainerGenerator.ContainerFromItem(item);

        //uncomment the following line if UI updates are unnecessary
        ((TreeViewItem)dObject).IsSelected = true;

        MethodInfo selectMethod = typeof(TreeViewItem).GetMethod("Select",
            BindingFlags.NonPublic | BindingFlags.Instance);

        selectMethod.Invoke(dObject, new object[] { true });
    }
    catch { }
}
23
Sergej Andrejev

Une autre option serait d'utiliser la liaison. Si vous utilisez une liaison avec un objet pour obtenir le texte de chaque TreeViewItem (par exemple), vous pouvez créer un style qui lie également la propriété IsSelected:

<TreeView>
    <TreeView.Resources>
        <Style TargetType="TreeViewItem">
            <Setter Property="IsSelected"
                    Value="{Binding Path=IsSelected, Mode=TwoWay}" />
        </Style>
    </TreeView.Resources>
</TreeView>

Cela suppose que l'objet lié possède une propriété IsSelected de type bool. Vous pouvez ensuite sélectionner TreeViewItem en définissant IsSelected sur true pour son objet correspondant.

La même approche peut être utilisée avec la propriété IsExpanded pour contrôler le moment où une TreeViewItem est développée ou réduite.

31
Andy

Vous pouvez utiliser l'extension TreeView suivante, qui est une solution plus simple:

public static class TreeViewExtension
{
    public static bool SetSelectedItem(this TreeView treeView, object item)
    {
        return SetSelected(treeView, item);
    }

    private static bool SetSelected(ItemsControl parent, object child)
    {
       if (parent == null || child == null)
          return false;

       TreeViewItem childNode = parent.ItemContainerGenerator
       .ContainerFromItem(child) as TreeViewItem;

       if (childNode != null)
       {
          childNode.Focus();
          return childNode.IsSelected = true;
       }

       if (parent.Items.Count > 0) 
       {
          foreach (object childItem in parent.Items)
          {
             ItemsControl childControl = parent
               .ItemContainerGenerator
               .ContainerFromItem(childItem) 
               as ItemsControl;

             if (SetSelected(childControl, child))
               return true;
          }
       }

      return false;
   }
}

Pour plus d'informations, lisez cet article de blog; http://decompile.fr/blog/2008/12/11/selecting-an-item-in-a-treeview-in-wpf/

5
kbisang

Après avoir essayé différentes solutions, je suis venu sur this site. Zhou Yong montre comment développer par programmation tous les nœuds de TreeView. Il y a deux idées principales dans sa méthode:

  • ContainerFromItem ne retournera le conteneur que si item est l'enfant direct de l'élément. Dans TreeView, cela signifie que seul le conteneur enfant de premier niveau sera renvoyé et que vous devez appeler ContainerFromItem sur l'enfant TreeViewItem pour obtenir le conteneur du niveau suivant.
  • Pour que ContainerFromItem fonctionne, des enfants visuels TreeViewItem doivent être créés, ce qui ne se produit que lorsque TreeViewItem est développé. Cela signifie que pour sélectionner TreeViewItem, tous les éléments précédents doivent être développés. En pratique, cela signifie que nous devrons fournir le chemin d'accès à l'élément que nous voulons sélectionner plutôt qu'à l'élément.

Voici le code que j'ai fini avec 

public static void SelectItem(this ItemsControl parentContainer, List<object> path)
{
    var head = path.First();
    var tail = path.GetRange(1, path.Count - 1);
    var itemContainer = parentContainer.ItemContainerGenerator.ContainerFromItem(head) as TreeViewItem;

    if (itemContainer != null && itemContainer.Items.Count == 0)
    {
        itemContainer.IsSelected = true;

        var selectMethod = typeof(TreeViewItem).GetMethod("Select", BindingFlags.NonPublic | BindingFlags.Instance);
        selectMethod.Invoke(itemContainer, new object[] { true });
    }
    else if (itemContainer != null)
    {
        itemContainer.IsExpanded = true;

        if (itemContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
        {
            itemContainer.ItemContainerGenerator.StatusChanged += delegate
            {
                SelectItem(itemContainer, tail);
            };
        }
        else
        {
            SelectItem(itemContainer, tail);
        }
    }
}
4
Sergej Andrejev

Dans mon cas (j'avais le même problème) mais il était inapproprié d'utiliser la liaison à la propriété IsSelected de l'objet Data et je ne pouvais pas non plus obtenir facilement le chemin d'accès à l'élément de l'arbre.

  private void SelectTreeViewItem(object item)
    {
        try
        {
            var tvi = GetContainerFromItem(this.MainRegion, item);

            tvi.Focus();
            tvi.IsSelected = true;

            var selectMethod =
                typeof(TreeViewItem).GetMethod("Select",
                System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

            selectMethod.Invoke(tvi, new object[] { true });
        }
        catch { }
    }

  private TreeViewItem GetContainerFromItem(ItemsControl parent, object item)
    {
        var found = parent.ItemContainerGenerator.ContainerFromItem(item);
        if (found == null)
        {
            for (int i = 0; i < parent.Items.Count; i++)
            {
                var childContainer = parent.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl;
                TreeViewItem childFound = null;
                if (childContainer != null && childContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
                {
                    childContainer.ItemContainerGenerator.StatusChanged += (o, e) =>
                        {
                             childFound = GetContainerFromItem(childContainer, item);
                        };
                }
                else
                {
                     childFound = GetContainerFromItem(childContainer, item);                            
                }
                if (childFound != null)
                    return childFound;                 
            }
        }
        return found as TreeViewItem;
    }
1
Mladen Nikolov

Très tard dans la soirée avec ma réponse, mais pour ceux qui souhaitent une solution MVVM pure, ceci peut être fait avec un déclencheur d’événement (pour mettre à jour la liaison lorsque l’utilisateur sélectionne un nouvel élément) et un déclencheur de données (pour mettre à jour l’élément sélectionné lorsque la des modifications de liaison).

Pour que cela fonctionne, ViewModel principal a besoin des éléments, d'une propriété pour l'élément actuellement sélectionné et d'une propriété de commande qui sera appelée lorsque l'élément sélectionné sera modifié:

public class MainViewModel : ViewModelBase
{
    // the currently selected node, can be changed programmatically
    private Node _CurrentNode;
    public Node CurrentNode
    {
        get { return this._CurrentNode; }
        set { this._CurrentNode = value; RaisePropertyChanged(() => this.CurrentNode); }
    }

    // called when the user selects a new node in the tree view
    public ICommand SelectedNodeChangedCommand { get { return new RelayCommand<Node>(OnSelectedNodeChanged); } }
    private void OnSelectedNodeChanged(Node node)
    {
        this.CurrentNode = node;
    }

    // list of items to display in the tree view
    private ObservableCollection<Node> _Items;
    public ObservableCollection<Node> Items
    {
        get { return this._Items; }
        set { this._Items = value; RaisePropertyChanged(() => this.Items); }
    }
}

TreeView a besoin d'un déclencheur d'événement pour appeler SelectedNodeChangedCommand lorsque la sélection est modifiée et d'un DataTrigger dans le style TreeViewItem afin que les éléments de contrôle soient sélectionnés lorsque la valeur de CurrentNode est modifiée de manière programmable dans le code:

<TreeView x:Name="treeView" ItemsSource="{Binding Items}"
            xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"
            xmlns:cmd ="http://www.galasoft.ch/mvvmlight">
        <TreeView.Resources>

            <conv:EqualityConverter x:Key="EqualityConverter" />

            <Style TargetType="TreeViewItem">
                <Setter Property="IsExpanded" Value="True" />
                <Setter Property="IsSelected" Value="False" />
                <Style.Triggers>
                    <!-- DataTrigger updates TreeViewItem selection when vm code changes CurrentNode -->
                    <DataTrigger Value="True">
                        <DataTrigger.Binding>
                            <MultiBinding Converter="{StaticResource EqualityConverter}">
                                <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type TreeView}}" Path="DataContext.CurrentNode" />
                                <Binding />
                            </MultiBinding>
                        </DataTrigger.Binding>
                        <Setter Property="IsSelected" Value="True" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>


            <!-- *** HierarchicalDataTemplates go here ***  -->

        </TreeView.Resources>

        <!-- EventTrigger invokes SelectedNodeChangedCommand when selection is changed by user interaction -->
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectedItemChanged">
                <cmd:EventToCommand Command="{Binding SelectedNodeChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=SelectedItem}"  />
            </i:EventTrigger>
        </i:Interaction.Triggers>

    </TreeView>

Le DataTrigger détecte quand la valeur de CurrentNode correspond au nœud pour l'élément de la liste en cours. Malheureusement, DataTriggers ne peut pas lier sa valeur. Il doit donc effectuer un test avec un convertisseur EqualityConverter qui effectue simplement une comparaison:

    public class EqualityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return values[0] == values[1];
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
0
Mark Feldman

Oui, la méthode ContainerFromItem ne restitue rien, même si vous l'appelez à partir de TreeViewItem parent direct.

Vous devrez peut-être un peu de refonte. Si vous créez tout en tant que TreeViewItem explicite, vous devriez pouvoir y garder une référence et y définir IsSelected.

0
RandomEngy