用多级层级数据填充 WPF TreeView






4.33/5 (5投票s)
演示如何迭代填充 WPF TreeView 的代码,
引言
此代码使用多层级分层数据填充 WPF TreeView
。它遍历分层结构的各个层级,从上到下,并将每个项目的子项集合添加到其中。数据源是一个自引用项目集合,包含以下字段:项目 Id、项目名称、父级 Id。该代码支持具有一个根节点的父/子分层结构。每个父级可以有多个子级,但一个子级只能有一个父级。
背景
提供的代码编写目的是使用来自数据库的分层数据填充 TreeView
UI。 为了简化示例,数据库和相关的实体框架类被泛型列表替换。 主要目标是使用基本的、完全可控的语句创建 TreeView
UI。
Using the Code
声明的类和对象描述
- 创建
GIER_Hierarchy
类和GIER_Hierarchy_List
泛型列表仅用于定义一些分层数据的示例,在本例中,是虚构的组织公司结构。它将是我们填充TreeView
的数据源。 假设“GIER_Hierarchy_List
”泛型列表是数据库表,因此其内容将被加载到datatable
对象中,以便在内存中进行进一步处理。 - 在下一步中,此泛型列表中的所有数据都将使用
foreach
语句加载到 "InputDT
"datatable
对象中。 - "
InputDT
"datatable
对象反映了与 "GIER_Hierarchy_List
" 列表相同的结构,并用于在内存中处理数据。 在单个迭代过程中,"InputDT
"datatable
与 "sub
"datatable
结合使用 LINQ 语句来选择在后续迭代中具有特定分层级别的子项的行。"sub
" datatable 是一个辅助对象,其结构与 "InputDT
"datatable
类似,但包含额外的 "Level
" 列。 它允许仅在相关迭代中选择特定的分层级别。 - 有两个特殊的类来定义树的节点:
TreeViewModel
和NodeViewModel
。TreeViewModel
包含 "Items
" 字段,用于保存NodeViewModel
对象的集合。NodeViewModel
对象包含以下 4 个字段:元素 ID(数字类型)、元素名称(字符串类型)、展开属性(布尔类型,用于定义节点在启动时应展开还是折叠)以及第四个字段是项目的子项集合。 - "
NodesFactory
" 是一个递归函数,它接受三个参数:"InputDT
"datatable
、"sub
"datatable
、级别编号。 此函数包含迭代遍历所有分层级别以查找子项集合并创建TreeViewModel
项目的整个逻辑。
代码工作方式的总体描述
- 首先,使用 LINQ 语句查询 "
InputDT
"datatable
对象以查找根项。 结果将加载到 "sub
"datatable
对象,同时分配分层级别编号 0。 进一步的处理发生在 "NodesFactory
" 函数内部。 与 "sub
"datatable
联接并筛选相关分层级别编号,可以找到在先前迭代中创建的节点的子项。 使用递归函数、LINQ 语句、与 "sub
"datatable
联接并分配相关分层级别,然后按相关级别进行筛选,从而迭代查询 "InputDT
"datatable
以查找特定分层级别的所有项目。 在每个单独的迭代中,处理一个分层级别。 在单个迭代期间,使用foreach
循环,为特定级别内的所有项目创建NodeViewModel
对象。 首先使用空的Children
字段创建这些节点。 该字段将在下一次迭代中填充。 在当前迭代中,创建的节点将添加到先前迭代中创建的节点的子项集合字段中。 创建 "List
<nodeviewmodel>nodesList
" 列表是为了保存NodeViewModel
对象,以便在下一次迭代中添加其children
集合。 - 最后,
TreeViewModel
对象与datatreeView
组件绑定。
C# 代码
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows;
namespace TreeView_DataHierarchy
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public void NodesFactory(DataTable InptDT, DataTable subDT, long level)
{
level = level + 1;
var querryLevel =
from itemsInputDT in InptDT.AsEnumerable()
join itemSub in subDT.AsEnumerable() on
itemsInputDT.Field<long>("GparentId") equals itemSub.Field<long>("Gid")
where itemSub.Field<long>("level") == level - 1
select new { Gid = itemsInputDT.Field<long>("Gid"),
Gname = itemsInputDT.Field<string>("Gname"),
GparentId = itemsInputDT.Field<long>("GparentId"), level = level };
foreach (var x in querryLevel)
{
DataRow rowSub = subDT.NewRow();
rowSub["Gid"] = x.Gid;
rowSub["Gname"] = x.Gname;
rowSub["GparentId"] = x.GparentId;
rowSub["level"] = x.level;
subDT.Rows.Add(rowSub);
nodesList.Add(new NodeViewModel { Id = x.Gid, Name = x.Gname,
Expand = true, Children = new ObservableCollection<NodeViewModel>() });
nodesList.Find(gNode => gNode.Id == x.GparentId).Children.Add
(nodesList.Last());
}
if (querryLevel.Count() > 0)
{ NodesFactory(InptDT, subDT, level); }
}
public event PropertyChangedEventHandler PropertyChanged;
List<NodeViewModel> nodesList = new List<NodeViewModel>();
TreeViewModel MyTreeModel = new TreeViewModel();
public MainWindow()
{
//generic list comprising hierarchical data- source to fill TreeViewModel
List<GIER_Hierarchy> GIER_Hierarchy_List = new List<GIER_Hierarchy>();
GIER_Hierarchy_List.Add(new GIER_Hierarchy(1, "root", 0));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(2, "Production B", 7));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(12, "Document Control", 17));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(17, "Engineering", 7));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(16, "Executive", 1));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(14, "Facilities and Maintenance", 7));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(10, "Finance", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(9, "Human Resources", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(11, "Information Services", 4));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(4, "Marketing", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(7, "Production", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(8, "Production Control", 7));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(5, "Purchasing", 18));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(13, "Quality Assurance", 7));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(6, "Research and Development", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(3, "Sales", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(15, "Shipping and Receiving", 18));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(19, "Tool Design", 2));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(18, "Logistic", 16));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(20, "Logistic A", 18));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(21, "Logistic A1", 20));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(22, "Logistic A1", 21));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(23, "Logistic A1", 22));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(24, "Logistic A1", 23));
GIER_Hierarchy_List.Add(new GIER_Hierarchy(25, "Logistic A1", 24));
if (GIER_Hierarchy_List.Count > 0)
{
//datatable to load data from GIER_Hierarchy_List
DataTable InputDT = new DataTable();
InputDT.Columns.Add("Gid", typeof(long));
InputDT.Columns.Add("Gname", typeof(string));
InputDT.Columns.Add("GparentId", typeof(long));
//loading the data from to datable
foreach (var x in GIER_Hierarchy_List)
{
DataRow rowInput = InputDT.NewRow();
rowInput["Gid"] = x.Gid;
rowInput["Gname"] = x.Gname;
rowInput["GparentId"] = x.GparentId;
InputDT.Rows.Add(rowInput);
}
//querying the datable to find the row with root as first level of hierarchy
var queryRoot =
from itemsInputDT in InputDT.AsEnumerable()
where itemsInputDT.Field<long>("GparentId") == 0
select new { Gid = itemsInputDT.Field<long>("Gid"),
Gname = itemsInputDT.Field<string>("Gname"),
GparentId = itemsInputDT.Field<long>("GparentId"), level = 1 };
//creating auxiliary datatable for iteratively querying
//in the subsequent levels of hierarchy
DataTable sub = new DataTable();
sub.Columns.Add("Gid", typeof(long));
sub.Columns.Add("Gname", typeof(string));
sub.Columns.Add("GparentId", typeof(long));
sub.Columns.Add("level", typeof(long));
//looping through the result of the query to load the
//result of the query to auxiliary datable and adding nodes to list of nodes
foreach (var x in queryRoot)
{
DataRow rowSub = sub.NewRow();
rowSub["Gid"] = x.Gid;
rowSub["Gname"] = x.Gname;
rowSub["GparentId"] = x.GparentId;
rowSub["level"] = 0;
sub.Rows.Add(rowSub);
//creating NodeViewModel and adding to the list of NodeViewModel object list
nodesList.Add(new NodeViewModel
{
Id = x.Gid,
Name = x.Gname,
Expand = true,
Children =
new ObservableCollection<NodeViewModel>()
});
}
//adding Collection of NodeViewModel object with the root to TreeModel
MyTreeModel.Items =
new ObservableCollection<NodeViewModel> { nodesList.Last() };
long level = 0;
NodesFactory(InputDT, sub,level);
}
else { MessageBox.Show("List of Directory is empty"); }
InitializeComponent();
// binding TreeView component with TreeModel
GIER_catalogHierarchy.ItemsSource = MyTreeModel.Items;
}
}
public class GIER_Hierarchy
{
public long Gid { get; set; }
public string Gname { get; set; }
public long GparentId { get; set; }
public GIER_Hierarchy(long xGid, string xGname, long xGparentId)
{
this.Gid = xGid;
this.Gname = xGname;
this.GparentId = xGparentId;
}
}
public class TreeViewModel
{
public ObservableCollection<NodeViewModel> Items { get; set; }
}
public class NodeViewModel : INotifyPropertyChanged
{
public long Id { get; set; }
public string _Name;
public bool _Expand;
public string Name
{
get { return _Name; }
set
{
if (_Name != value)
{
_Name = value;
NotifyPropertyChanged("Name");
}
}
}
public bool Expand
{
get { return _Expand; }
set
{
if (_Expand != value)
{
_Expand = value;
NotifyPropertyChanged("Expand");
}
}
}
public ObservableCollection<NodeViewModel> Children { get; set; }
#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
WPF 代码
<xmp>
<Window x:Class="TreeView_DataHierarchy.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeView_DataHierarchy"
xmlns:src="clr-namespace:TreeView_DataHierarchy"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView x:Name="GIER_catalogHierarchy" BorderThickness="1"
DataContext="{Binding TreeModel}" SelectedValuePath="Id"
MinWidth="300" MinHeight="440" Width="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
Margin="1,1,1,1" Height="Auto">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType= "{x:Type local:NodeViewModel}" ItemsSource="{Binding Children}" >
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="Line" Width="20" />
<ColumnDefinition x:Name="Rectangle" />
<ColumnDefinition x:Name="Empty1" Width="10" />
<ColumnDefinition x:Name="Department" />
</Grid.ColumnDefinitions>
<!--drawing Connecting Lines -->
<!-- Horizontal Lines -->
<Border Grid.Column="0" x:Name="HorLn" Margin="9,0,0,2"
HorizontalAlignment="Stretch" Height="1"
BorderThickness="0,0,0,1" VerticalAlignment="Bottom">
<Border.BorderBrush>
<LinearGradientBrush StartPoint="0,0"
EndPoint="2,0" SpreadMethod="Repeat" MappingMode="Absolute">
<GradientStop Color="Transparent" Offset="0" />
<GradientStop Color="Transparent" Offset="0.499" />
<GradientStop Color="#999" Offset="0.5" />
</LinearGradientBrush>
</Border.BorderBrush>
</Border>
<!-- Vertical Lines -->
<Border Grid.Column="0" x:Name="VerLn" Margin="0,0,1,2"
Grid.RowSpan="2" VerticalAlignment="Stretch"
Width="1" BorderThickness="0,0,1,0" >
<Border.BorderBrush>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,2"
SpreadMethod="Repeat" MappingMode="Absolute">
<GradientStop Color="Transparent" Offset="0" />
<GradientStop Color="Transparent" Offset="0.499" />
<GradientStop Color="#999" Offset="0.5" />
</LinearGradientBrush>
</Border.BorderBrush>
</Border>
<!--drawing catalog shapes-->
<Rectangle Grid.Column="1" Width="14" Height="10"
Stroke="Gold" SnapsToDevicePixels="true" VerticalAlignment="Center">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,2" StartPoint="0.5,0">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="Gold" Offset="0.5"/>
<GradientStop Color="Honeydew" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Column="1" Width="7" Height="4"
Stroke="Gold" SnapsToDevicePixels="true" VerticalAlignment="Top"
HorizontalAlignment="Left" Fill="White" />
<!--textblock to display the names of catalogs-->
<TextBlock x:Name="textBlockHeader" Grid.Column="3"
Grid.Row="1" Text="{Binding Name}" FontSize="9" />
</Grid>
</HierarchicalDataTemplate>
<!--triger to expand nodes-->
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Expand}" Value="True">
<Setter Property="IsExpanded" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
</xmp>
历史
- 2019 年 8 月 24 日:初始版本