web-dev-qa-db-fra.com

Comment obtenir TreeViewItem à partir d'un élément HierarchicalDataTemplate?

J'ai un TreeView qui utilise un HierarchicalDataTemplate pour lier ses données. 

Cela ressemble à ceci:

<TreeView x:Name="mainTreeList" ItemsSource="{Binding MyCollection}>
  <TreeView.Resources>
    <HierarchicalDataTemplate 
     DataType="{x:Type local:MyTreeViewItemViewModel}" 
     ItemsSource="{Binding Children}">
      <!-- code code code -->
    </HierarchicalDataTemplate>
  </TreeView.Resources>
</TreeView>

Maintenant, à partir du code derrière la fenêtre principale, je veux obtenir le TreeViewItem actuel sélectionné. Cependant, si j'utilise:

this.mainTreeList.SelectedItem;

L'élément selectedItem est de type MyTreeViewItemViewModel. Mais je veux obtenir le 'parent' ou 'lié' TreeViewItem. Je ne passe pas cela à mon objet TreeViewItemModel (je ne saurais même pas comment). 

Comment puis-je faire ceci?

31
Razzie

À partir de l'entrée de blog de Bea Stollnitz à ce sujet, essayez

TreeViewItem item = (TreeViewItem)(mainTreeList
    .ItemContainerGenerator
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition));
20
TreeViewItem item = (TreeViewItem)(mainTreeList
    .ItemContainerGenerator
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition));

NE FONCTIONNE PAS (pour moi) en tant que mainTreeList.Items.CurrentPosition dans un arborescence en utilisant un HierarchicalDataTemplate sera toujours -1.

NIITHERES ci-dessous en tant que mainTreeList.Items.CurrentItem dans un arbre utilisant un HierarchicalDataTemplate sera toujours null.

TreeViewItem item = (TreeViewItem)mainTreeList
    .ItemContainerGenerator
    .ContainerFromItem(mainTreeList.Items.CurrentItem);

&AGRAVE; LA PLACEJe devais définir le dernier TreeViewItem sélectionné dans l'événement TreeViewItem.Selected routé qui déborde dans la vue arborescente (les TreeViewItem n'existent pas à la conception car nous utilisons un HierarchicalDataTemplate).

L'événement peut être capturé en XAML de la manière suivante:

<TreeView TreeViewItem.Selected="TreeViewItemSelected" .../> 

Ensuite, le dernier TreeViewItem sélectionné peut être défini dans l'événement de la manière suivante:

    private void TreeViewItemSelected(object sender, RoutedEventArgs e)
    {
        TreeViewItem tvi = e.OriginalSource as TreeViewItem;

        // set the last tree view item selected variable which may be used elsewhere as there is no other way I have found to obtain the TreeViewItem container (may be null)
        this.lastSelectedTreeViewItem = tvi;

        ...
     }
31
markmnl

J'ai rencontré ce même problème… .. Il me fallait accéder à TreeViewItem pour pouvoir le sélectionner… .. Je me suis alors rendu compte que je pouvais simplement ajouter une propriété IsSelected à mon ViewModel, que je liais ensuite à la TreeViewItems IsSelectedProperty . Ceci peut être réalisé avec ItemContainerStyle:

<TreeView>
            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                </Style>
            </TreeView.ItemContainerStyle>
</TreeView>

Maintenant, si je veux qu'un élément de l'arborescence soit sélectionné, j'appelle directement IsSelected sur ma classe ViewModel.

J'espère que ça aide quelqu'un.

7
santiagoIT
TreeViewItem item = (TreeViewItem)(mainTreeList
    .ItemContainerGenerator
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition)); gives first item in the TreeView because CurrentPosition is always 0.

Que diriez-vous

TreeViewItem item = (TreeViewItem)(mainTreeList
    .ItemContainerGenerator
    .ContainerFromItem(mainTreeList.SelectedItem)));

Cela fonctionne mieux pour moi.

5
kitndirangu

Inspiré par la réponse de Fëanor, j’ai essayé de rendre la TreeViewItem facilement accessible pour chaque élément de données pour lequel une TreeViewItem a été créée.

L'idée est d'ajouter un champ de type TreeViewItem- au modèle de vue, également exposé via une interface, et de le remplir automatiquement par TreeView chaque fois que le conteneur TreeViewItem est créé.

Ceci est fait en sous-classant TreeView et en attachant un événement à ItemContainerGenerator, qui enregistre la TreeViewItem chaque fois qu'il est créé. Les pièges incluent le fait que les TreeViewItems sont créées paresseusement, de sorte qu'il pourrait ne pas y en avoir vraiment un disponible à certains moments.

Depuis que j'ai posté cette réponse, je l'ai développée plus avant et je l'ai utilisée pendant longtemps dans un projet. Aucun problème jusqu'à présent, mis à part le fait que cela viole MVVM (mais vous permet également d'économiser une tonne de passe-partout pour les cas simples). Source ici .

Utilisation

Sélectionnez le parent de l'élément sélectionné et réduisez-le en vous assurant qu'il est dans la vue:

...
var selected = myTreeView.SelectedItem as MyItem;
selected.Parent.TreeViewItem.IsSelected = true;
selected.Parent.TreeViewItem.IsExpanded = false;
selected.Parent.TreeViewItem.BringIntoView();
...

Déclarations:

<Window ...
        xmlns:tvi="clr-namespace:TreeViewItems"
        ...>
    ...
    <tvi:TreeViewWithItem x:Name="myTreeView">
        <HierarchicalDataTemplate DataType = "{x:Type src:MyItem}"
                                  ItemsSource = "{Binding Children}">
            <TextBlock Text="{Binding Path=Name}"/>
        </HierarchicalDataTemplate>
    </tvi:TreeViewWithItem>
    ...
</Window>
class MyItem : IHasTreeViewItem
{
    public string Name { get; set; }
    public ObservableCollection<MyItem> Children { get; set; }
    public MyItem Parent;
    public TreeViewItem TreeViewItem { get; set; }
    ...
}

Code

public class TreeViewWithItem : TreeView
{
    public TreeViewWithItem()
    {
        ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
    }

    private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
    {
        var generator = sender as ItemContainerGenerator;
        if (generator.Status == GeneratorStatus.ContainersGenerated)
        {
            int i = 0;
            while (true)
            {
                var container = generator.ContainerFromIndex(i);
                if (container == null)
                    break;

                var tvi = container as TreeViewItem;
                if (tvi != null)
                    tvi.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;

                var item = generator.ItemFromContainer(container) as IHasTreeViewItem;
                if (item != null)
                    item.TreeViewItem = tvi;

                i++;
            }
        }
    }
}

interface IHasTreeViewItem
{
    TreeViewItem TreeViewItem { get; set; }
}
4
Roman Starkov

Essayez quelque chose comme ça:

    public bool UpdateSelectedTreeViewItem(PropertyNode dateItem, ItemsControl itemsControl)
    {
        if (itemsControl == null || itemsControl.Items == null || itemsControl.Items.Count == 0)
        {
            return false;
        }
        foreach (var item in itemsControl.Items.Cast<PropertyNode>())
        {
            var treeViewItem = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
            if (treeViewItem == null)
            {
                continue;
            }
            if (item == dateItem)
            {
                treeViewItem.IsSelected = true;
                return true;
            }
            if (treeViewItem.Items.Count > 0 && UpdateSelectedTreeViewItem(dateItem, treeViewItem))
            {
                return true;
            }
        }
        return false;
    }
2
Kevin

Un seul appel 'ItemContainerGenerator.ContainerFromItem' ou 'ItemContainerGenerator.ItemContainerGenerator' ne peut pas trouver l'objet TreeViewItem associé à un objet de la vue arborescente, car il est nécessaire de créer une fonction récursive. utilisez 'ItemContainerGenerator.ContainerFromItem' et 'ItemContainerGenerator.ItemContainerGenerator' pour localiser TreeViewItem dans l'arborescence . Une fonction récursive pourrait ressembler à:

private TreeViewItem GetTreeViewItemFromObject(ItemContainerGenerator container, object tvio)
{
    var item = container.ContainerFromItem(tvio) as TreeViewItem;
    if (item != null)
    {
        return item;
    }

    for (int i = 0; i < container.Items.Count; i++)
    {
        var subContainer = (TreeViewItem)container.ContainerFromIndex(i);
        if (subContainer != null)
        {
            item = GetTreeViewItemFromObject(subContainer.ItemContainerGenerator, tvio);
            if (item != null)
            {
                return item;
            }
        }
    }

    return null;
}

appelé par var target = GetTreeViewItemFromObject (treeView.ItemContainerGenerator, item);

La fonction récursive ne fonctionne qu'après que 'ItemContainerGenerator.Status' de l'arborescence est 'ContainersGenerated'. Ainsi, pendant la période d'affichage de l'initialisation, 'GetTreeViewItemFromObject' ne fonctionne pas.

1
William

J'ai modifié la recherche récursive de William en une version plus compacte:

public TreeViewItem GetTreeViewItemFromObject(ItemContainerGenerator container, object targetObject) {
    if (container.ContainerFromItem(targetObject) is TreeViewItem target) return target;
    for (int i = 0; i < container.Items.Count; i++)
        if ((container.ContainerFromIndex(i) as TreeViewItem)?.ItemContainerGenerator is ItemContainerGenerator childContainer)
            if (GetTreeViewItemFromObject(childContainer, targetObject) is TreeViewItem childTarget) return childTarget;
    return null;
}

On l'appellerait en fournissant ItemContainerGenerator de l'instance TreeView et l'objet de données cible:

TreeViewItem tvi = GetTreeViewItemFromObject(treeView.ItemContainerGenerator, targetDataObject);
1
Mike DeMauro

Voici la solution . RtvEsa est un arbre . HierarchicalDataTemplate est un modèle d'arborescence Une balise rend la consommation réelle de l'élément actuel. Cet élément n'est pas sélectionné, il s'agit d'un élément actuel du contrôle d'arborescence qui utilise HierarchicalDataTemplate.

Items.CurrentItem fait partie de la collection d'arborescence interne . Vous ne pouvez pas obtenir de nombreuses et différentes données. Par exemple, Items.ParenItem aussi.

  <HierarchicalDataTemplate ItemsSource="{Binding ChildItems}">

  <TextBox Tag="{Binding ElementName=rtvEsa, Path=Items.CurrentItem }" />

0
Petar

Avez-vous besoin de TreeViewItem parce que vous allez modifier ce qui est affiché? Si tel est le cas, je vous recommande d'utiliser un style pour modifier le mode d'affichage de l'élément au lieu d'utiliser code-behind au lieu de modifier directement TreeViewItem. Espérons que cela soit plus propre.

0
viggity

Si vous devez trouver un objet chez des enfants d’enfants, vous devrez peut-être utiliser la récursivité comme ceci 

public bool Select(TreeViewItem item, object select) // recursive function to set item selection in treeview
{
    if (item == null)
        return false;
    TreeViewItem child = item.ItemContainerGenerator.ContainerFromItem(select) as TreeViewItem;
    if (child != null)
    {
        child.IsSelected = true;
        return true;
    }
    foreach (object c in item.Items)
    {
        bool result = Select(item.ItemContainerGenerator.ContainerFromItem(c) as TreeViewItem, select);
        if (result == true)
            return true;
    }
    return false;
}
0
purvin