翼度科技»论坛 编程开发 .net 查看内容

WPF中非递归(无后台代码)动态实现TreeView

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
在UI界面中,树形视图是比较常用的表示层级结构的方式,WPF中提供了TreeView控件。对于TreeView控件的基本使用已经有很多文章。大都是介绍如何在XAML中使用硬编码的固定信息填充Treeview控件,或者是后台代码递归遍历数据源,动态创建TreeView。这里我想介绍一下如何只通过XAML标记,不用一行后台代码遍历数据实现TreeView。
技术要点与实现

本文的技术关键点是层级式数据模板HierarchicalDataTemplate。HierarchicalDataTemplate是一个特殊的DataTemplate,它能够包装第二层模板。通过ItemsSource属性查找下一层级的数据集合,并将它提供给第二层模板。这样描述可能有点晦涩。接下来举例进行描述。
首先假设一个应用场景。用树形结构展现一个地区所有的学校->年级->班级->学生。首先定义几个Model
  1. public class School : ObservableObject
  2. {
  3.     private bool _isOpen;
  4.     /// <summary>
  5.     /// 获取或设置是否展开
  6.     /// </summary>
  7.     [System.Xml.Serialization.XmlIgnore]
  8.     public bool IsOpen { get { return _isOpen; } set { Set(ref _isOpen, value); } }
  9.     private bool _isSelected;
  10.     /// <summary>
  11.     /// 获取或设置是否被选中
  12.     /// </summary>
  13.     [System.Xml.Serialization.XmlIgnore]
  14.     public bool IsSelected { get { return _isSelected; } set { Set(ref _isSelected, value); } }
  15.    
  16.     public string SchoolID { get; set; }
  17.     public string SchoolName { get; set; }
  18.     public ObservableCollection<Grade> listGrade { get; set; }=new ObservableCollection<Grade>() { };
  19. }
  20. public class Grade : ObservableObject
  21. {
  22.     private bool _isOpen;
  23.     [System.Xml.Serialization.XmlIgnore]
  24.     public bool IsOpen { get { return _isOpen; } set { Set(ref _isOpen, value); } }
  25.     private bool _isSelected;
  26.     [System.Xml.Serialization.XmlIgnore]
  27.     public bool IsSelected { get { return _isSelected; } set { Set(ref _isSelected, value); } }
  28.    
  29.     public string GradeID { get; set; }
  30.     public string GradeName { get; set; }
  31.     public ObservableCollection<ClassInfo> ListClass { get; set; }=new ObservableCollection<ClassInfo>() { };
  32. }
  33. public class ClassInfo : ObservableObject
  34. {
  35.     private bool _isOpen;
  36.     [System.Xml.Serialization.XmlIgnore]
  37.     public bool IsOpen { get { return _isOpen; } set { Set(ref _isOpen, value); } }
  38.     private bool _isSelected;
  39.     [System.Xml.Serialization.XmlIgnore]
  40.     public bool IsSelected { get { return _isSelected; } set { Set(ref _isSelected, value); } }
  41.     public string ClassID { get; set; }
  42.     public string ClassName { get; set; }
  43.     public ObservableCollection<Student> Students { get; set; }= new ObservableCollection<Student>() { };
  44. }
  45. public class Student : ObservableObject
  46. {
  47.     private bool _isSelected;
  48.     [System.Xml.Serialization.XmlIgnore]
  49.     public bool IsSelected { get { return _isSelected; } set { Set(ref _isSelected, value); } }
  50.     public string Id { get; set; }
  51.     public string Name { get; set; }
  52. }
复制代码
接下来根据定义好的Model定义层级式数据模板HierarchicalDataTemplate。
  1. <HierarchicalDataTemplate DataType="{x:Type local:School}" ItemsSource="{Binding Path=listGrade}">
  2.     <TextBlock Text="{Binding Path=SchoolName}" />
  3. </HierarchicalDataTemplate>
  4. <HierarchicalDataTemplate DataType="{x:Type local:Grade}" ItemsSource="{Binding Path=ListClass}">
  5.     <TextBlock Text="{Binding Path=GradeName}" />
  6. </HierarchicalDataTemplate>
  7. <HierarchicalDataTemplate DataType="{x:Type local:ClassInfo}" ItemsSource="{Binding Path=Students}">
  8.     <TextBlock Text="{Binding Path=ClassName}" />
  9. </HierarchicalDataTemplate>
  10. <HierarchicalDataTemplate DataType="{x:Type local:Student}">
  11.     <CheckBox Command="{Binding SelectChangeCommand, ElementName=self}" CommandParameter="{Binding}" IsChecked="{Binding IsSelected}">
  12.         <TextBlock Text="{Binding Path=Name}" />
  13.     </CheckBox>
  14. </HierarchicalDataTemplate>
复制代码
其中最外层数据类型是School,它的下一层数据集合是ObservableCollection listGrade,因此HierarchicalDataTemplate中的ItemsSource赋值为listGrade,这里我们再属性控件中只显示学校的名称,因此数据模板只是包含绑定了学校名称SchoolName的TextBlock,如果需要显示其他信息(比如学校年级数量或者学校图标),只需增加相应XAML元素即可。紧接着按照这个方式定义好数据类型Grade,ClassInfo,Student的层级式数据模板即可。
定义好了数据模型和相应的层级式数据模板HierarchicalDataTemplate后,就可以直接把数据元绑定到TreeView上了。假设要绑定的数据源实例是ObservableCollection schools。只需如下调用即可。
[code][/code]这样使用TreeView是不是特别方便简洁。不用为了展示树形结构,特地定义一个递归类型的数据结构,UI展示全部交给XAML就行。JSON数据反序列化后直接绑定即可(XML或者DateSet也是类似的方法)。避免了递归遍历数据源的操作,也不用考虑递归带来的性能问题。
性能

前边提到不用考虑递归带来的性能问题。那本文介绍的方法对于大量数据的情况下性能到底怎样呢?接下来做一个测试,模拟100W的数据量,具体为240个学校,每个学校3个年级,每个年级20个班,每个班70个学生,总共数据量是240x3x20x70=1008000个。以下是测试结果:

从图中可以看到模拟100w数据耗时1.5s,内存增加了160M左右,数据渲染到界面不到1s,内存增加20M左右。结果还是令人满意的。这是因为TreeView支持开启虚拟化(默认是关闭的,设置 VirtualizingPanel.IsVirtualizing="True"开启虚拟化),渲染界面是不会一次把所有UI元素全部创建好,而是根据屏幕上可见区域计算需要渲染的元素个数,创建少量的UI元素,从而减少内存和CPU资源的使用。例如本例中又100w条数据,可见区能显示20条,TreeView只创建了41个UI元素。为什么不是创建20个呢?这是由于为了确保良好的滚动性能,实际会多创建一些UI元素。
TreeView 默认关闭虚拟化,是因为早期的WPF发布版本中的VirtualizingStackPanel不支持层次化数据,虽然现在已支持,但是TreeView默认关闭虚拟化确保兼容性。

来源:https://www.cnblogs.com/czwy/archive/2023/08/14/17623497.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具