WPF之TreeView🔥

2/1/2024 WPF

# 效果演示

mixureSecure

# 什么是TreeView

TreeView 是 Windows Presentation Foundation (WPF) 提供的一个控件,用于以树形结构展示层级化的数据。它允许用户以展开和折叠的方式浏览数据层级,常用于文件浏览器、组织结构图、菜单导航等场景。

# 优点

# 1、层级数据展示

直观的层级结构:TreeView 能够清晰地展示多层次的数据,使用户能够直观地理解数据之间的父子关系。 无限嵌套:支持无限层级的嵌套,适用于各种复杂的数据结构,如文件系统、组织架构等。

# 2、性能优化

虚拟化:支持 UI 虚拟化(UI Virtualization),仅渲染可见部分的节点,提升大量数据时的渲染性能。 延迟加载:可以实现按需加载子节点,减少初始加载时间,优化资源使用。

# 完整代码

mixureSecure

# 先在Nuget上面安装上面两个类库

public class ClassModel: ObservableObject
{
    private string grade;

    public string Grade
    {
        get { return grade; }
        set { SetProperty(ref grade, value); }
    }

    private string name;

    public string Name
    {
        get { return name; }
        set { SetProperty(ref name, value); }
    }

    private ObservableCollection<StudentModel> students;

    public ObservableCollection<StudentModel> Students
    {
        get { return students; }
        set { SetProperty(ref students, value); }
    }

    public ClassModel()
    {
        students = new ObservableCollection<StudentModel>();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class GradeModel:ObservableObject
{
	private string name;

	public string Name
	{
		get { return name; }
		set { SetProperty(ref name, value); }
	}

	private ObservableCollection<ClassModel> classes	;

	public ObservableCollection<ClassModel> Classes
	{
		get { return classes; }
		set { SetProperty(ref classes,value); }
	}

    public GradeModel()
    {
		classes = new ObservableCollection<ClassModel>();
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class StudentModel : ObservableObject
{
	private string grade;

	public string Grade
	{
		get { return grade; }
		set { SetProperty(ref grade, value); }
	}

	private string className;

	public string ClassName
	{
		get { return className; }
		set { SetProperty(ref className, value); }
	}

	private string name;

	public string Name
	{
		get { return name; }
		set { SetProperty(ref name, value); }
	}

	private string subject;
	public string Subject
	{
		get { return subject; }
		set { SetProperty(ref subject, value); }
	}

	private double score;

	public double Score
	{
		get { return score; }
		set { SetProperty(ref score, value); }
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class TreeNode:ObservableObject
{
	private string name;

	public string Name
	{
		get { return name; }
		set { SetProperty(ref name,value); }
	}

	private ObservableCollection<TreeNode> children;

	public ObservableCollection<TreeNode> Children
	{
		get { return children; }
		set { SetProperty(ref children,value); }
	}

    public TreeNode()
    {
		children = new ObservableCollection<TreeNode>();
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<UserControl x:Class="TreeViewDataDemo.View.StudentGradeTabelView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:TreeViewDataDemo.View"
             xmlns:viewmodel="clr-namespace:TreeViewDataDemo.ViewModel"
             xmlns:model="clr-namespace:TreeViewDataDemo.Model"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="4*"/>
            <ColumnDefinition Width="8*"/>
        </Grid.ColumnDefinitions>
        <TreeView ItemsSource="{Binding Grades}"
                  SelectedItemChanged="TreeView_SelectedItemChanged"
          Grid.Column="0"
          Margin="10">
            <TreeView.Resources>
                <!-- 学生节点模板 -->
                <DataTemplate x:Key="StudentTemplate">
                    <TextBlock Text="{Binding Name}" />
                </DataTemplate>

                <!-- 班级节点模板 -->
                <HierarchicalDataTemplate DataType="{x:Type model:ClassModel}" ItemsSource="{Binding Students}">
                    <TextBlock Text="{Binding Name}" />
                    <HierarchicalDataTemplate.ItemTemplate>
                        <StaticResource ResourceKey="StudentTemplate" />
                    </HierarchicalDataTemplate.ItemTemplate>
                </HierarchicalDataTemplate>
                
                <!-- 班级模板资源键 -->
                <HierarchicalDataTemplate x:Key="ClassTemplate" DataType="{x:Type model:ClassModel}" ItemsSource="{Binding Students}">
                    <TextBlock Text="{Binding Name}" />
                    <HierarchicalDataTemplate.ItemTemplate>
                        <StaticResource ResourceKey="StudentTemplate" />
                    </HierarchicalDataTemplate.ItemTemplate>
                </HierarchicalDataTemplate>
                <!-- 年级节点模板 -->
                
                <HierarchicalDataTemplate DataType="{x:Type model:GradeModel}" ItemsSource="{Binding Classes}">
                    <TextBlock Text="{Binding Name}" />
                    <HierarchicalDataTemplate.ItemTemplate>
                        <StaticResource ResourceKey="ClassTemplate" />
                    </HierarchicalDataTemplate.ItemTemplate>
                </HierarchicalDataTemplate>
            </TreeView.Resources>
        </TreeView>
        <DataGrid ItemsSource="{Binding Students}"
          AutoGenerateColumns="False"
          Grid.Column="1"
          Margin="10"
          IsReadOnly="True">
            <DataGrid.Columns>
                <DataGridTextColumn Header="姓名" Binding="{Binding Name}" Width="*" />
                <!-- 动态列,根据数据类型显示不同的信息 -->
                <DataGridTextColumn Header="科目" Binding="{Binding Subject}" Width="*" />
                <DataGridTextColumn Header="成绩" Binding="{Binding Score}" Width="*" />
                <!-- 如果需要显示学生姓名或班级名称,可以添加更多列 -->
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</UserControl>

public partial class StudentGradeTabelView : UserControl
{ 
	private StudentGradeTabelViewModel viewModel = new StudentGradeTabelViewModel();
	public StudentGradeTabelView()
	{
		InitializeComponent();
		this.DataContext = viewModel;
	}

	private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
	{
		viewModel.GetModels(e);
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class StudentGradeTabelViewModel:ObservableObject
{
	public ObservableCollection<GradeModel> Grades { get; set; } = new ObservableCollection<GradeModel>();

	public ObservableCollection<StudentModel> Students { get; set; } = new ObservableCollection<StudentModel>();

	private ClassModel selectedClass;
	public ClassModel SelectedClass
	{
		get => selectedClass;
		set => SetProperty(ref selectedClass,value);
	}

    public StudentGradeTabelViewModel()
    {
		Grades = new ObservableCollection<GradeModel>();
		Students = new ObservableCollection<StudentModel>();

		// 自动生成全校成绩数据
		GenerateSchoolGrades();
	}
    private string GetChineseNumber(int number)
	{
		string[] chineseNumbers = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" };
		if (number <= 10)
			return chineseNumbers[number];
		else
			return number.ToString(); // 超过10的数字直接使用阿拉伯数字
	}
	public void GenerateSchoolGrades(int numberOfGrades = 6, int classesPerGrade = 3, int studentsPerClass = 30)
	{
		var random = new Random();
		var subjects = new string[] { "语文"};
		//var subjects = new string[] { "语文", "数学", "英语", "科学", "历史", "地理" };
		Grades.Clear();

		for (int gradeIndex = 1; gradeIndex <= numberOfGrades; gradeIndex++)
		{
			var grade = new GradeModel
			{
				Name = $"{gradeIndex}年级"
			};

			for (int classIndex = 1; classIndex <= classesPerGrade; classIndex++)
			{
				var className = $"{GetChineseNumber(classIndex)}班";
				var classObj = new ClassModel
				{
					Name = className,
					Grade = grade.Name
				};

				for (int studentIndex = 1; studentIndex <= studentsPerClass; studentIndex++)
				{
					var studentName = $"学生{GetChineseNumber(studentIndex)}";
					foreach (var subject in subjects)
					{
						var score = Math.Round(random.NextDouble() * 40 + 60, 1); // 60 ~ 100 分
						var studentGrade = new StudentModel
						{
							Grade = grade.Name,
							ClassName = className,
							Name = studentName,
							Subject = subject,
							Score = score
						};
						classObj.Students.Add(studentGrade);
					}
				}

				grade.Classes.Add(classObj);
			}

			Grades.Add(grade);
		}
	}

	public void GetModels(RoutedPropertyChangedEventArgs<object> e)
	{
		Students.Clear();
		if (e.NewValue is ClassModel classmodel)
		{
			var grade = Grades.Where(it => it.Name == classmodel.Grade).First();
			var clas = grade.Classes.Where(it=>it.Name == classmodel.Name).First();
			foreach (var item in clas.Students)
			{
				Students.Add(item);
			}
		}
			
		if (e.NewValue is StudentModel student)
			Students.Add(student);
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<Window x:Class="TreeViewDataDemo.MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TreeViewDataDemo"
        xmlns:view="clr-namespace:TreeViewDataDemo.View"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <view:StudentGradeTabelView/>
    </Grid>
</Window>
1
2
3
4
5
6
7
8
9
10
11
12
13
<Application x:Class="TreeViewDataDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:TreeViewDataDemo"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
        
    </Application.Resources>
</Application>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16