WPF-TreeView unterschiedliche Subknoten – ein erweitertes Beispiel

Veröffentlicht von

Die meisten Beispiele von TreeViews und WPF sind einfache Beispiele in denen die Knoten stets vom gleichen Typ sind. Ich hatte aber das Problem, dass meine Objekte unterschiedliche Unterlisten hatten, welche im TreeView dargestellt werden sollten.

Geholfen hat mir das sehr gute Beispiel hier: Organizing Heterogeneous Data on a WPF TreeView. Dieses Beispiel möchte ich mit einem eigenen Artikel aufgreifen.

Den gesamten Code gibt es am Ende zum Download. Ok legen wir los. Im Beispiel haben wir das folgende Modell:

Jede Person hat zwei Unterlisten: Lieblingsbiere und Lieblingsbücher. Wir wollen nun die Personen und die beiden Unterlisten im Baum darstellen. Die Unterlisten sollen unterhalb von Person in eigenen Unterknoten dargestellt werden:

Die Herausforderung ist die Darstellung der beiden Unterlisten. Legen wir zuerst mit den Hauptelementen los, dazu legen wir ein “HierarchicalDataTemplate” an, welches uns den Namen und das Alter ausgibt.

<TreeView x:Name="TreeView"
      ItemsSource="{Binding Persons}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type model:Person}">
            <TextBlock>
                <Run Text="{Binding Name}"></Run>
                <Run Text="("></Run>
                <Run Text="{Binding Age}"></Run>
                <Run Text=")"></Run>
            </TextBlock>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>

Das Ergebnis:

Soweit so unspannend. Nun geht es darum die beiden Unterlisten anzuzeigen und in Ordner einzusortieren. Um mehrere verschiedene Unterlisten anzubinden, hilft uns ein MultiBinding:

<HierarchicalDataTemplate.ItemsSource>
    <MultiBinding Converter="{StaticResource PersonSubitemConverter}">
        <Binding Path="FavoriteBeers"></Binding>
        <Binding Path="FavoriteBooks"></Binding>
    </MultiBinding>
</HierarchicalDataTemplate.ItemsSource>

Dieses fügen wir unserem “HierarchicalDataTemplate” hinzu:

Für ein Multibinding benötigen wir zwingend einen IMultiValueConverter, in diesem Fall habe ich bereits den “PersonSubItemConverter” angegeben. In diesem werden wir die Unterlisten Listen entsprechend “konvertieren” für die Darstellung:

public class PersonSubitemConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        ObservableCollection<Beer> beers = (ObservableCollection<Beer>)values[0];
        ObservableCollection<Book> books = (ObservableCollection<Book>)values[1];
        List<object> items = new List<object>();

        FolderItem folderItemThen = new FolderItem() { Name = "Lieblingsbiere", Items = beers };
        FolderItem folderItemElse = new FolderItem() { Name = "Lieblingsbücher", Items = books };

        items.Add(folderItemThen);
        items.Add(folderItemElse);

        return items;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException("Cannot be done!");
    }
}

Hier nehmen wir die beiden Listen, erstellen ein “FolderItem“-Objekt, welches einen Titel und die Liste zugewiesen bekommt. Anschließend geben wir eine Liste von FolderItems zurück. Das FolderItem sieht so aus:

public class FolderItem : INotifyPropertyChanged
{
    #region Name
    private string _name;


    /// 
    /// The name will be shown as the title of the folder
    /// in the tree
    /// 
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                _name = value;
                NotifyPropertyChanged();
            }
        }
    }
    #endregion

    #region Items

    private IEnumerable _items;

    /// 
    /// The child items of the folder.
    /// 
    public IEnumerable Items
    {
        get { return _items; }
        set
        {
            if (_items != value)
            {
                _items = value;
                NotifyPropertyChanged();
            }
        }
    }
    #endregion

    #region INotifyPropertyChanged

    protected void NotifyPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }

    public event PropertyChangedEventHandler PropertyChanged;
    #endregion
}

Das “FolderItem” dient der Einsortierung der Listenelemente in einen Unterknoten in den TreeView. Dazu müssen wir nun noch ein Template im Xaml-Code einfügen.

<HierarchicalDataTemplate DataType="{x:Type model:FolderItem}"
                     ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>

Das Ergebnis zeigt uns nun bereits die Unterordner an:

Damit sind wir fast am Ziel, es fehlen nur noch die DataTemplates für die Darstellung der Unterknoten.

<HierarchicalDataTemplate DataType="{x:Type model:Beer}">
    <TextBlock>
        <Run Text="{Binding Name}"></Run>
        <Run Text=" - "></Run>
        <Run Text="{Binding Alcolhol}"></Run>"
    </TextBlock>
</HierarchicalDataTemplate>

<HierarchicalDataTemplate DataType="{x:Type model:Book}">
    <TextBlock>
        <Run Text="{Binding Name}"></Run>
        <Run Text=" - "></Run>
        <Run Text="{Binding Isbn}"></Run>"
    </TextBlock>
</HierarchicalDataTemplate>

Damit ist die Darstellung nun vollständig:

Das gesamte Beispiel gibt es hier zum Download.

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert