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:
CodePudding user response:
This code below works. The key point is to use TreeView
s 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
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;
}
}