WinUI TreeView单DataTemplate中放置多个TreeViewItem的实现问题
解决WinUI TreeView单个DataTemplate中嵌套多个可展开节点的问题
问题根源
你当前的写法存在两个核心问题:
- 在
PersonTemplate的StackPanel中直接放置TreeViewItem时,这个TreeViewItem是作为普通UI控件存在,而非外部TreeView的逻辑节点。外部TreeView不会为它生成展开箭头,也不会将ItemTemplateSelector应用到它的子项上。 - TreeView的
ItemTemplate是用来定义单个节点的内容,而非用来嵌套TreeViewItem控件,正确的层级节点展示需要依赖HierarchicalDataTemplate来关联子项数据源。
解决方案
通过调整数据结构+使用HierarchicalDataTemplate,实现Person节点展开后展示姓名、可展开的Books和Movies分组的需求:
1. 新增分组类与Movie类
先定义一个Group类来统一Books和Movies的分组逻辑,同时补充Movie类:
public class Group : ObservableObject { [ObservableProperty] private string name; [ObservableProperty] private ObservableCollection<object> items; public Group(string name, ObservableCollection<object> items) { Name = name; Items = items; } } public partial class Movie : ObservableObject { [ObservableProperty] private string director; [ObservableProperty] private string title; public Movie(string director, string title) { Director = director; Title = title; } }
2. 修改Person类,添加分组集合
让Person类包含Books、Movies分组,作为TreeView的子节点数据源:
public partial class Person : ObservableObject { [ObservableProperty] private string firstName; [ObservableProperty] private string lastName; public ObservableCollection<Book> Books { get; } = new(); public ObservableCollection<Movie> Movies { get; } = new(); // 用于TreeView的子节点分组 public ObservableCollection<Group> Groups { get; } = new(); public Person(string firstName, string lastName) { FirstName = firstName; LastName = lastName; // 初始化Books和Movies分组 Groups.Add(new Group("Books", new ObservableCollection<object>(Books))); Groups.Add(new Group("Movies", new ObservableCollection<object>(Movies))); } }
3. 更新XAML模板与选择器
使用HierarchicalDataTemplate定义各层级节点的模板,同时扩展模板选择器支持新的类型:
<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> <!-- Person节点模板:显示姓名,子节点为分组集合 --> <HierarchicalDataTemplate x:Key="PersonTemplate" x:DataType="local:Person" ItemsSource="{x:Bind Groups}"> <StackPanel> <TextBlock Text="{x:Bind FirstName}" /> <TextBlock Text="{x:Bind LastName}" /> </StackPanel> </HierarchicalDataTemplate> <!-- 分组节点模板:显示分组名称,子节点为对应书籍/电影列表 --> <HierarchicalDataTemplate x:Key="GroupTemplate" x:DataType="local:Group" ItemsSource="{x:Bind Items}"> <TextBlock Text="{x:Bind Name}" FontWeight="Bold"/> </HierarchicalDataTemplate> <!-- Book节点模板 --> <DataTemplate x:Key="BookTemplate" x:DataType="local:Book"> <StackPanel> <TextBlock Text="{x:Bind Writer}" /> <TextBlock Text="{x:Bind Title}" /> </StackPanel> </DataTemplate> <!-- Movie节点模板 --> <DataTemplate x:Key="MovieTemplate" x:DataType="local:Movie"> <StackPanel> <TextBlock Text="{x:Bind Director}" /> <TextBlock Text="{x:Bind Title}" /> </StackPanel> </DataTemplate> <!-- 扩展后的模板选择器 --> <local:TemplateSelector x:Key="TemplateSelector" PersonTemplate="{StaticResource PersonTemplate}" GroupTemplate="{StaticResource GroupTemplate}" BookTemplate="{StaticResource BookTemplate}" MovieTemplate="{StaticResource MovieTemplate}"/> </Grid.Resources> <TreeView x:Name="PackageReferenceTree" ItemsSource="{x:Bind Persons}" ItemTemplateSelector="{StaticResource TemplateSelector}" /> </Grid> </Window>
4. 更新模板选择器代码
让选择器支持Person、Group、Book、Movie四种类型:
public class TemplateSelector : DataTemplateSelector { public DataTemplate PersonTemplate { get; set; } public DataTemplate GroupTemplate { get; set; } public DataTemplate BookTemplate { get; set; } public DataTemplate MovieTemplate { get; set; } protected override DataTemplate SelectTemplateCore(object item) { return item switch { Person => PersonTemplate, Group => GroupTemplate, Book => BookTemplate, Movie => MovieTemplate, _ => throw new NotSupportedException($"不支持的类型: {item.GetType()}") }; } }
5. 测试数据示例
在MainWindow中添加Movie测试数据:
public sealed partial class MainWindow : Window { public ObservableCollection<Person> Persons { get; } = new(); public MainWindow() { this.InitializeComponent(); Person person1 = new Person("John", "Doe"); person1.Books.Add(new Book("Stephen King", "The Shining")); person1.Movies.Add(new Movie("Christopher Nolan", "Inception")); Persons.Add(person1); } }
效果说明
- 展开Person节点时,会显示姓名以及Books、Movies两个可展开的分组节点
- 展开Books/Movies分组时,会显示对应的书籍/电影列表,且模板选择器会自动匹配对应的子项模板
内容的提问来源于stack exchange,提问作者JerryA
相关产品推荐
相关产品推荐

