HierarchicalDataTemplate

Dato che sul forum wpfitalia.it ci sono state molte domande riguardo il binding di wpf con i TreeView, ecco una semplice guida step-by-step.

Per prima cosa, se occorre eseguire un binding gerarchico, serve una sorgente dati gerarchica. Ora me ne vengono in mente due:

  1. Xml: per sua natura l’xml ha una struttura gerarchica e ben si adatta al treeview
  2. Oggetti (leggasi composite pattern): in pratica classi “Composite” che hanno una proprietà IEnumerable<Component> Children.

Per il poco che mi viene in mente, tutto può essere ricondotto al secondo caso (il primo incluso ma non ce ne è bisogno, si salti pure avanti).
OGGETTI + COSTRUZIONE GERARCHIA DA FLATHIERARCHY:

supponiamo di avere una lista di oggetti (che può essere benissimo una DataTable o qualsiasi altra cosa Smile):
public class FlatHierarchyNode
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Text { get; set; }
}

Andremo a creare la nostra struttura nel seguente modo (ho lasciato da parte un po’ di “eleganza” e “pulizia” per comprimere il codice):

public class Composite
{
    public FlatHierarchyNode Node { get; set; }
    private List<FlatHierarchyNode> _allNodes;
 
    public Composite(FlatHierarchyNode node, List<FlatHierarchyNode> allNodes) 
    {
        this.Node = node;
        this._allNodes = allNodes;
    }
 
    private IEnumerable<Composite> _children;
    public IEnumerable<Composite> Children //nodi figlio del corrente nodo
    {
        get {
            if(_children == null)
                _children = Composite.getChildren(Node.Id, _allNodes);
            return _children;
        }
    }
    public static IEnumerable<Composite> getChildren(int? parentId, List<FlatHierarchyNode> allNodes)
    {
        return from node in allNodes //da tutti i nodi
                where node.ParentId == parentId //prendo i nodi figli
                select new Composite(node, allNodes); //ritornando un Composite
    }
}

è un po’ lunghetto ma abbastanza semplice. La parte principale è il metodo getChildren.
Avendo una struttura del genere, potremmo benissimo fare:
treeView1.ItemsSource = Composite.getChildren(null, new List<FlatHierarchyNode>(… … …));
Come eseguire il binding ora? Easy:

<TreeView Name="treeView1" >
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
            <TextBlock Text="{Binding Path=Node.Text}" />
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

In pratica andiamo a sostinuire il template impostando un HierarchicalDataTemplate . Questo componente permette di ciclare sui figli ItemsSource=”{Binding Path=Children}”, riapplicando se stesso come datatemplate.
Del nostro oggetto andremo a mostrare come testo Text=”{Binding Path=Node.Text}”.

XML:

Detto che anche con Xml possiamo usare il codice qui sopra, tutto potrebbe risultare ancora più semplice senza neanche una riga di codice (detto che personalmente odio i datasource livello xaml):

<Window.Resources>
    <XmlDataProvider x:Key="provider" Source="XMLFile1.xml" XPath="Sezioni/*" />
</Window.Resources>

Poi il TreeView è così definito:

<TreeView Name="treeView1" ItemsSource="{Binding Source={StaticResource ResourceKey=provider}}"  >
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding XPath=*}">
            <TextBlock Text="{Binding XPath=@Name}" />
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

Supponendo di visualizzare tutti i nodi partendo da Sezioni/*, e mostrando, di tutti i nodi, l’attributo Name.

Cosa preferisco:

Beh… dipende molto dalle situazioni, però… decisamente il primo Smile
Il secondo non mi piace proprio, semplicemente per il fatto che ho un XmlDataProvider lato UI che non mi piace. Si può recuperare da codebehind ma storcio il naso.
Inoltre l’XML può essere caricato con un XDocument/XElement e ricondotto molto facilmente al primo caso.

Post Scriptum XML:
Una cosa che non ho detto… I LOVE CONVERTERS! In wpf esistono questi magnifici oggetti e sposerei chi li ha inventati!
Per gli XML preferisco di gran lunga un approccio del genere:

XDocument doc = XDocument.Load("XMLFile1.xml");
this.treeView1.ItemsSource = doc.Elements();

poi dichiarare la treeview come segue:

<TreeView Height="227" Name="treeView1" Width="294">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Path=., Converter={StaticResource NodeToChildren}}">
            <TextBlock Text="{Binding Path=., Converter={StaticResource NodeToTextConverter}}" />
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

E creare i converters:

public class NodeToChildren : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        XElement element = value as XElement;
        if (element != null)
            return element.Elements();
        return null;
    }
public class NodeToTextConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        XElement element = value as XElement;
        if (element != null)
            return element.Attributes("Name").FirstOrDefault();
        return null;
    }

Insomma… ad ognuno il suo!

, ,

  1. Lascia un commento

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

%d blogger hanno fatto clic su Mi Piace per questo: