web-dev-qa-db-fra.com

WPF TreeView HierarchicalDataTemplate - liaison à un objet avec plusieurs collections enfants

J'essaie d'obtenir un TreeView pour lier ma collection afin que tous les groupes affichent des groupes imbriqués et que chaque groupe affiche l'entrée.

Comment puis-je utiliser le HierarchicalDataTemplate pour que le TreeView traite à la fois les sous-groupes et la collection d'entrées?

Les groupes affichent des sous-groupes et des entrées:

Example:
Group1
--Entry
--Entry
Group2
--Group4
----Group1
------Entry
------Entry
----Entry
----Entry
--Entry
--Entry
Group3
--Entry
--Entry


Objets:


namespace TaskManager.Domain
{
    public class Entry
    {
        public int Key { get; set; }
        public string Name { get; set; }
    }
}

namespace TaskManager.Domain
{
    public class Group
    {
        public int Key { get; set; }
        public string Name { get; set; }

        public IList<Group> SubGroups { get; set; }
        public IList<Entry> Entries { get; set; }
    }
}

Données de test:


namespace DrillDownView
{
    public class TestData
    {

        public IList<Group> Groups = new List<Group>();

        public void Load()
        {
            Group grp1 = new Group() { Key = 1, Name = "Group 1", SubGroups = new List<Group>(), Entries = new List<Entry>() };
            Group grp2 = new Group() { Key = 2, Name = "Group 2", SubGroups = new List<Group>(), Entries = new List<Entry>() };
            Group grp3 = new Group() { Key = 3, Name = "Group 3", SubGroups = new List<Group>(), Entries = new List<Entry>() };
            Group grp4 = new Group() { Key = 4, Name = "Group 4", SubGroups = new List<Group>(), Entries = new List<Entry>() };

            //grp1
            grp1.Entries.Add(new Entry() { Key=1, Name="Entry number 1" });
            grp1.Entries.Add(new Entry() { Key=2, Name="Entry number 2" });
            grp1.Entries.Add(new Entry() { Key=3,Name="Entry number 3" });

            //grp2
            grp2.Entries.Add(new Entry(){ Key=4, Name = "Entry number 4"});
            grp2.Entries.Add(new Entry(){ Key=5, Name = "Entry number 5"});
            grp2.Entries.Add(new Entry(){ Key=6, Name = "Entry number 6"});

            //grp3
            grp3.Entries.Add(new Entry(){ Key=7, Name = "Entry number 7"});
            grp3.Entries.Add(new Entry(){ Key=8, Name = "Entry number 8"});
            grp3.Entries.Add(new Entry(){ Key=9, Name = "Entry number 9"});

            //grp4
            grp4.Entries.Add(new Entry(){ Key=10, Name = "Entry number 10"});
            grp4.Entries.Add(new Entry(){ Key=11, Name = "Entry number 11"});
            grp4.Entries.Add(new Entry(){ Key=12, Name = "Entry number 12"});

            grp4.SubGroups.Add(grp1);
            grp2.SubGroups.Add(grp4);

            Groups.Add(grp1);
            Groups.Add(grp2);
            Groups.Add(grp3);
        }
    }
}

XAML:


<Window x:Class="DrillDownView.Window2"
        xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TaskManager.Domain;Assembly=TaskManager.Domain"
        Title="Window2" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <TreeView Name="GroupView" Grid.Row="0" Grid.Column="0" ItemsSource="{Binding}">
            <TreeView.Resources>
                <HierarchicalDataTemplate DataType="{x:Type local:Group}" ItemsSource="{Binding SubGroups}">
                    <TextBlock Text="{Binding Path=Name}" />
                </HierarchicalDataTemplate>
                <HierarchicalDataTemplate DataType="{x:Type local:Entry}" ItemsSource="{Binding Entries}">
                    <TextBlock Text="{Binding Path=Name}" />
                </HierarchicalDataTemplate>
            </TreeView.Resources>
        </TreeView>
    </Grid>
</Window>

XAML.CS:


public partial class Window2 : Window
{
    public Window2()
    {
        InitializeComponent();
        LoadView();
    }

    private void LoadView()
    {
        TestData data = new TestData();
        data.Load();
        GroupView.ItemsSource = data.Groups;
    }
}
53
longday

Un HierarchicalDataTemplate est une façon de dire 'c'est ainsi que vous rendez ce type d'objet et voici une propriété qui peut être sondée pour trouver les nœuds enfants sous cet objet'

Par conséquent, vous avez besoin d'une seule propriété qui renvoie les "enfants" de ce nœud. par exemple. (Si vous ne pouvez pas faire dériver à la fois le groupe et l'entrée d'un type Node type)

public class Group{ ....
        public IList<object> Items
        {
            get
            {
                IList<object> childNodes = new List<object>();
                foreach (var group in this.SubGroups)
                    childNodes.Add(group);
                foreach (var entry in this.Entries)
                    childNodes.Add(entry);

                return childNodes;
            }
        }

Ensuite, vous n'avez pas besoin d'un HierarchicalDataTemplate pour l'entrée car une entrée n'a pas d'enfants. Le XAML doit donc être modifié pour utiliser la nouvelle propriété Items et un DataTemplate pour Entry:

<TreeView Name="GroupView" Grid.Row="0" Grid.Column="0" ItemsSource="{Binding}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:Group}" ItemsSource="{Binding Items}">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
        <DataTemplate DataType="{x:Type local:Entry}" >
            <TextBlock Text="{Binding Path=Name}" />
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

Et voici à quoi ça ressemble. Screenshot of Output

99
Gishu

Je pense que vous êtes la plupart du temps là-bas ... avec un tout petit peu de retouches, vous devriez faire en sorte que cela fonctionne assez facilement ...

Je vous suggère de créer une classe abstraite de base (ou une interface, selon votre préférence) et de l'hériter/l'implémenter pour la classe Group et Entry ...

De cette façon, vous pouvez exposer une propriété dans votre objet Group

public ObservableCollection<ITreeViewItem> Children { get; set; }

^ à ce stade, vous pouvez prendre une décision si cela remplace vos listes de sous-groupes et d'entrées, ou simplement les ajoute ensemble et les renvoie dans le getter de propriété ...

Maintenant, tout ce dont vous avez besoin est de remplir la collection Children avec des objets Group ou Entry, et le HierarchicalDataTemplate s'affichera correctement lorsque les objets seront placés dans TreeView.

Une dernière pensée, si Entry est toujours le "niveau inférieur" de l'arborescence (c'est-à-dire qu'il n'a pas d'enfants), alors vous n'avez pas besoin de définir un HierarchicalDataTemplate pour l'objet Entry, un DataTemplate suffira .

J'espère que cela t'aides :)

11
kiwipom

Voici une implémentation alternative de la réponse de Gishu qui retourne un IEnumerable plutôt qu'un IList, et utilise le mot clé yield pour simplifier le code:

public class Group
{
    ...

    public IEnumerable<object> Items
    {
        get
        {
            foreach (var group in this.SubGroups)
                yield return group;
            foreach (var entry in this.Entries)
                yield return entry;
        }
    }
}
9
Matthew

Ce message m'a aidé lors de la recherche d'une solution pour le même problème: http://blog.pmunin.com/2012/02/xaml-binding-to-compositecollection.html

en utilisant MultiBinding et CompositeCollectionConverter ..

/ Cordialement Anders

3
Anders