Home > Software design >  Several TreeViewItem in one DataTemplate
Several TreeViewItem in one DataTemplate

Time:02-02

I'm struggling with a TreeView in my WinUI app where I would like to have several TreeViewItems in a single DataTemplate.

I have tried several things but I would imagine I could do something like in my example. But in my running code I can only see the TextBlocks and the TreeViewItem headers but with now arrow at the TreeViewItems.

<Window
    x:Class="SimpleTreeViewExample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SimpleTreeViewExample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="PersonTemplate" x:DataType="local:Person">
                <StackPanel>
                    <TextBlock Text="{x:Bind FirstName}" />
                    <TextBlock Text="{x:Bind LastName}" />

                    <TreeViewItem ItemsSource="{x:Bind Books}" IsExpanded="False" Content="Books"/>
                </StackPanel>
            </DataTemplate>

            <DataTemplate x:Key="BookTemplate" x:DataType="local:Book">
                <StackPanel>
                    <TextBlock Text="{x:Bind Writer}" />
                    <TextBlock Text="{x:Bind Title}" />
                </StackPanel>
            </DataTemplate>

            <local:TemplateSelector x:Key="TemplateSelector"
                PersonTemplate="{StaticResource PersonTemplate}"
                BookTemplate="{StaticResource BookTemplate}">
                
            </local:TemplateSelector>

        </Grid.Resources>

        <StackPanel>
            <TreeView x:Name="PackageReferenceTree"
                            ItemsSource="{x:Bind Persons}" 
                            ItemTemplateSelector="{StaticResource TemplateSelector}" />
        </StackPanel>
    </Grid>
</Window>

Here is my code behind:

    

public sealed partial class MainWindow : Window
{
    public ObservableCollection<Person> Persons = new();
        
    public MainWindow()
    {
        this.InitializeComponent();
        Person person1 = new Person("John", "Doe");
        person1.Books.Add(new Book("Stephen King", "The Shining"));
        Persons.Add(person1);
    }
}

public partial class Person : ObservableObject
{
    [ObservableProperty]
    private string firstName;

    [ObservableProperty]
    private string lastName;

    public ObservableCollection<Book> Books = new();

    public Person(string firstName, string lastName)
    {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

public partial class Book : ObservableObject
{
    [ObservableProperty]
    private string writer;

    [ObservableProperty]
    private string title;

    public Book(string writer, string title)
    {
        this.writer = writer;
        this.title = title;
    }
}

public class TemplateSelector : DataTemplateSelector
{
    public DataTemplate PersonTemplate { get; set; }

    public DataTemplate BookTemplate { get; set; }

    protected override DataTemplate SelectTemplateCore(object item)
    {
        if (item.GetType() == typeof(Person))
        {
            return PersonTemplate;
        }
        else if (item.GetType() == typeof(Book))
        {
            return BookTemplate;
        }
 
        throw new NotSupportedException($"The item type: {item.GetType()} wasn't known ");
    }
}

If I remove the StackPanel and only keep a single TreeViewItem it works fine. I'm having a template selector which isn't hit when I'm having a StackPanel but it is hit without the StackPanel so I assume the issue is related to that.

So what I would like to obtain is that I have a list of persons and each of them can be expanded. When expanded they contain a firstname and lastname and a list with books and a list with movies. Books and Movies lists can also be expanded and are not the same types.

So it looks something like:

TreeView example

CodePudding user response:

This code below works. The key point is to use TreeViews inside the template.

TreeViewDataTemplateSelector.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;

namespace TreeViewTests;

public class TreeViewDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate? PersonCollectionTemplate { get; set; }

    public DataTemplate? PersonTemplate { get; set; }

    public DataTemplate? BookCollectionTemplate { get; set; }

    public DataTemplate? MovieCollectionTemplate { get; set; }

    public DataTemplate? BookTemplate { get; set; }

    public DataTemplate? MovieTemplate { get; set; }

    public TreeViewDataTemplateSelector()
    {
    }

    protected override DataTemplate? SelectTemplateCore(object item)
    {
        return item switch
        {
            PersonCollection => PersonCollectionTemplate,
            Person => PersonTemplate,
            BookCollection => BookCollectionTemplate,
            Book => BookTemplate,
            MovieCollection => MovieCollectionTemplate,
            Movie => MovieTemplate,
            _ => throw new NotSupportedException(),
        };
    }
}

MainPageViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;

namespace TreeViewTests;

public partial class PersonCollection : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection<Person> persons = new();
}

public partial class Person : ObservableObject
{
    [ObservableProperty]
    private string firstName = string.Empty;

    [ObservableProperty]
    private string lastName = string.Empty;

    [ObservableProperty]
    private ObservableCollection<BookCollection> bookCollections = new();

    [ObservableProperty]
    private ObservableCollection<MovieCollection> movieCollections = new();
}

public partial class ItemCollection<T> : ObservableObject where T : class
{
    [ObservableProperty]
    private string name = string.Empty;

    [ObservableProperty]
    private ObservableCollection<T> items = new();
}

public class BookCollection : ItemCollection<Book>
{
}

public class MovieCollection : ItemCollection<Movie>
{
}

public record Book(string Title, string Writer);

public record Movie(string Production, int Year, double Score);

public partial class MainPageViewModel : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection<PersonCollection> personCollections = new();

    public MainPageViewModel()
    {
        this.PersonCollections.Add(new PersonCollection()
        {
            Persons = new ObservableCollection<Person>()
            {
                new Person()
                {
                    FirstName = "First A",
                    LastName = "Last A",
                    BookCollections = new ObservableCollection<BookCollection>()
                    {
                        new BookCollection()
                        {
                            Name = "Books",
                            Items = new ObservableCollection<Book>()
                            {
                                new Book(
                                    Title: "Book A-1",
                                    Writer: "Writer A-1"),
                            }
                        }
                    },
                    MovieCollections = new ObservableCollection<MovieCollection>()
                    {
                        new MovieCollection()
                        {
                            Name = "Movies",
                            Items = new ObservableCollection<Movie>()
                            {
                                new Movie(
                                    Production: "Production A-1",
                                    Year: 2018,
                                    Score: 10.0),
                                new Movie(
                                    Production: "Production A-2",
                                    Year: 2019,
                                    Score: 10.0),
                                new Movie(
                                    Production: "Production A-3",
                                    Year: 2020,
                                    Score: 10.0),
                            }
                        }
                    },
                },
                new Person()
                {
                    FirstName = "First B",
                    LastName = "Last B",
                    BookCollections = new ObservableCollection<BookCollection>()
                    {
                        new BookCollection()
                        {
                            Name = "Books",
                            Items = new ObservableCollection<Book>()
                            {
                                new Book(
                                    Title: "Book B-1",
                                    Writer: "Writer B-1"),
                                new Book(
                                    Title: "Book B-2",
                                    Writer: "Writer B-2"),
                            }
                        }
                    },
                    MovieCollections = new ObservableCollection<MovieCollection>()
                    {
                        new MovieCollection()
                        {
                            Name = "Movies",
                            Items = new ObservableCollection<Movie>()
                            {
                                new Movie(
                                    Production: "Production B-1",
                                    Year: 2021,
                                    Score: 10.0),
                                new Movie(
                                    Production: "Production B-2",
                                    Year: 2022,
                                    Score: 10.0),
                            }
                        }
                    },
                },
                new Person()
                {
                    FirstName = "First C",
                    LastName = "Last C",
                    BookCollections = new ObservableCollection<BookCollection>()
                    {
                        new BookCollection()
                        {
                            Name = "Books",
                            Items = new ObservableCollection<Book>()
                            {
                                new Book(
                                    Title: "Book C-1",
                                    Writer : "Writer C-1"),
                                new Book(
                                    Title: "Book C-2",
                                    Writer: "Writer C-2"),
                                new Book(
                                    Title: "Book C-3",
                                    Writer: "Writer C-3"),
                            }
                        }
                    },
                    MovieCollections = new ObservableCollection<MovieCollection>()
                    {
                        new MovieCollection()
                        {
                            Name = "Movies",
                            Items = new ObservableCollection<Movie>()
                            {
                                new Movie(
                                    Production: "Production C-1",
                                    Year: 2023,
                                    Score: 10.0),
                            }
                        }
                    },
                },
            }
        });
    }
}

MainPage.xaml

<Page
    x:Class="TreeViewTests.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:TreeViewTests"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">

    <Page.Resources>
        <DataTemplate
            x:Key="PersonCollectionTemplate"
            x:DataType="local:PersonCollection">
            <TreeViewItem
                HasUnrealizedChildren="True"
                ItemsSource="{x:Bind Persons, Mode=OneWay}">
                <TextBlock Text="Persons" />
            </TreeViewItem>
        </DataTemplate>

        <DataTemplate
            x:Key="PersonTemplate"
            x:DataType="local:Person">
            <StackPanel>
                <TextBlock Text="{x:Bind FirstName, Mode=OneWay}" />
                <TextBlock Text="{x:Bind LastName, Mode=OneWay}" />
                <TreeView
                    ItemTemplateSelector="{StaticResource TreeViewDataTemplateSelector}"
                    ItemsSource="{x:Bind BookCollections, Mode=OneWay}" />
                <TreeView
                    ItemTemplateSelector="{StaticResource TreeViewDataTemplateSelector}"
                    ItemsSource="{x:Bind MovieCollections, Mode=OneWay}" />
            </StackPanel>
        </DataTemplate>

        <DataTemplate
            x:Key="BookCollectionTemplate"
            x:DataType="local:BookCollection">
            <TreeViewItem
                HasUnrealizedChildren="True"
                ItemsSource="{x:Bind Items, Mode=OneWay}">
                <TextBlock Text="{x:Bind Name, Mode=OneWay}" />
            </TreeViewItem>
        </DataTemplate>

        <DataTemplate
            x:Key="BookTemplate"
            x:DataType="local:Book">
            <StackPanel>
                <TextBlock Text="{x:Bind Title, Mode=OneWay}" />
                <TextBlock Text="{x:Bind Writer, Mode=OneWay}" />
            </StackPanel>
        </DataTemplate>

        <DataTemplate
            x:Key="MovieCollectionTemplate"
            x:DataType="local:MovieCollection">
            <TreeViewItem
                HasUnrealizedChildren="True"
                ItemsSource="{x:Bind Items, Mode=OneWay}">
                <TextBlock Text="{x:Bind Name, Mode=OneWay}" />
            </TreeViewItem>
        </DataTemplate>

        <DataTemplate
            x:Key="MovieTemplate"
            x:DataType="local:Movie">
            <StackPanel>
                <TextBlock Text="{x:Bind Production, Mode=OneWay}" />
                <TextBlock Text="{x:Bind Year, Mode=OneWay}" />
                <TextBlock Text="{x:Bind Score, Mode=OneWay}" />
            </StackPanel>
        </DataTemplate>

        <local:TreeViewDataTemplateSelector
            x:Key="TreeViewDataTemplateSelector"
            BookTemplate="{StaticResource BookTemplate}"
            MovieCollectionTemplate="{StaticResource MovieCollectionTemplate}"
            BookCollectionTemplate="{StaticResource BookCollectionTemplate}"
            MovieTemplate="{StaticResource MovieTemplate}"
            PersonCollectionTemplate="{StaticResource PersonCollectionTemplate}"
            PersonTemplate="{StaticResource PersonTemplate}" />
    </Page.Resources>

    <Grid>
        <TreeView
            x:Name="TreeViewControl"
            ItemTemplateSelector="{StaticResource TreeViewDataTemplateSelector}"
            ItemsSource="{x:Bind ViewModel.PersonCollections, Mode=OneWay}" />
    </Grid>

</Page>

CodePudding user response:

Here is a sample for your reference, you need change the value of Name in code behind. The code is from enter image description here

MainWindow.xaml

<Grid>
    <TreeView ItemsSource="{x:Bind DataSource}">
        <TreeView.ItemTemplate>
            <DataTemplate x:DataType="local:ExplorerItem">
                <TreeViewItem ItemsSource="{x:Bind Children}" Content="{x:Bind Name}">
                </TreeViewItem>
            </DataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
</Grid>

MainWindow.xaml.cs

public sealed partial class MainWindow : Window
{
    TreeViewNode personalFolder;
    TreeViewNode personalFolder2;
    private ObservableCollection<ExplorerItem> DataSource;
    public MainWindow()
    {
        this.InitializeComponent();
        DataSource = GetData();
        
    }

    private ObservableCollection<ExplorerItem> GetData()
    {
        var list = new ObservableCollection<ExplorerItem>();
        ExplorerItem folder1 = new ExplorerItem()
        {
            Name = "Persons",
            Type = ExplorerItem.ExplorerItemType.Folder,
            Children =
            {
                new ExplorerItem()
                {
                    Name = "John",
                    Type = ExplorerItem.ExplorerItemType.File,
                },
                new ExplorerItem()
                {
                    Name = "Doe",
                    Type = ExplorerItem.ExplorerItemType.File,
                },
                new ExplorerItem()
                {
                    Name = "Books",
                    Type = ExplorerItem.ExplorerItemType.Folder,
                    Children =
                    {
                        new ExplorerItem()
                        {
                            Name = "Title",
                            Type = ExplorerItem.ExplorerItemType.File,
                        },
                        new ExplorerItem()
                        {
                            Name = "Writer",
                            Type = ExplorerItem.ExplorerItemType.File,
                        }

                    }
                },
                new ExplorerItem()
                {
                    Name = "Movies",
                    Type = ExplorerItem.ExplorerItemType.Folder,
                    Children =
                    {
                        new ExplorerItem()
                        {
                            Name = "Production",
                            Type = ExplorerItem.ExplorerItemType.File,
                        },
                        new ExplorerItem()
                        {
                            Name = "Year",
                            Type = ExplorerItem.ExplorerItemType.File,
                        },
                        new ExplorerItem()
                        {
                            Name = "Score",
                            Type = ExplorerItem.ExplorerItemType.File,
                        }

                    }
                }

            }
        };
        ExplorerItem folder2 = new ExplorerItem()
        {
            Name = "Personal Folder",
            Type = ExplorerItem.ExplorerItemType.Folder,
            Children =
                    {
                        new ExplorerItem()
                        {
                            Name = "Home Remodel Folder",
                            Type = ExplorerItem.ExplorerItemType.Folder,
                            Children =
                            {
                                new ExplorerItem()
                                {
                                    Name = "Contractor Contact Info",
                                    Type = ExplorerItem.ExplorerItemType.File,
                                },
                                new ExplorerItem()
                                {
                                    Name = "Paint Color Scheme",
                                    Type = ExplorerItem.ExplorerItemType.File,
                                },
                                new ExplorerItem()
                                {
                                    Name = "Flooring Woodgrain type",
                                    Type = ExplorerItem.ExplorerItemType.File,
                                },
                                new ExplorerItem()
                                {
                                    Name = "Kitchen Cabinet Style",
                                    Type = ExplorerItem.ExplorerItemType.File,
                                }
                            }
                        }
                    }
        };

        list.Add(folder1);
        list.Add(folder2);
        return list;
    }

}

public class ExplorerItem : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public enum ExplorerItemType { Folder, File };
    public string Name { get; set; }
    public ExplorerItemType Type { get; set; }
    private ObservableCollection<ExplorerItem> m_children;
    public ObservableCollection<ExplorerItem> Children
    {
        get
        {
            if (m_children == null)
            {
                m_children = new ObservableCollection<ExplorerItem>();
            }
            return m_children;
        }
        set
        {
            m_children = value;
        }
    }

    private bool m_isExpanded;
    public bool IsExpanded
    {
        get { return m_isExpanded; }
        set
        {
            if (m_isExpanded != value)
            {
                m_isExpanded = value;
                NotifyPropertyChanged("IsExpanded");
            }
        }
    }

    private void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

class ExplorerItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate FolderTemplate { get; set; }
    public DataTemplate FileTemplate { get; set; }

    protected override DataTemplate SelectTemplateCore(object item)
    {
        var explorerItem = (ExplorerItem)item;
        return explorerItem.Type == ExplorerItem.ExplorerItemType.Folder ? FolderTemplate : FileTemplate;
    }
}
  • Related