65.9K
CodeProject 正在变化。 阅读更多。
Home

用多级层级数据填充 WPF TreeView

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (5投票s)

2019年8月24日

CPOL

3分钟阅读

viewsIcon

18322

downloadIcon

1190

演示如何迭代填充 WPF TreeView 的代码, 其中包含多级层级数据

Sample Image - maximum width is 600 pixels

引言

此代码使用多层级分层数据填充 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" 列。 它允许仅在相关迭代中选择特定的分层级别。
  • 有两个特殊的类来定义树的节点:TreeViewModelNodeViewModelTreeViewModel 包含 "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 日:初始版本
© . All rights reserved.