用多级层级数据填充 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 日:初始版本


