C#开发WinForm之DevExpress框架里TreeList使用

C#开发WinForm之DevExpress框架里TreeList使用

前言

winForm里树型结构是比较常见的,不论何种前端开发,树型都需要学会怎么用。
而DexExpress里的TreeList是比较特殊的使用方式 ,因为它的数据结构是“平级”的,从结构上讲,数据没有上下级关系。都放在DataRowCollection这个Map集合里,它通过指定KeyFieldNameParentFieldName来标明上面级关系。换句话说,数据是平等的,关键在逻辑上来处理父子关系。

新建一下windows 窗体,拖拽一下DevExpress.XtraTreeList.TreeList到窗体上,命名为workSpaceTree
如下图方式打开列的设计面板
在这里插入图片描述
在这里可以指定展示的列,每一个属性都必需指定列,但我们可以设置列是否显示,所以我们同样可以控制列的展示。

以后台返回的如下json格式数据为例

{
	"uid": "480304801154859009",
	"objectName": "鬼六村族谱",
	"children": [
		{
			"uid": "515117932112510976",
			"objectName": "张三",
			"releaseStatus": "Working",
			"children": [
				{
					"uid": "518088753315577856",
					"objectName": "张三大儿子",
					"children": []
				},
				{
					"uid": "504962126524186624",
					"objectName": "张三二儿子",
					"children": [
						{
							"uid": "504962126557741056",
							"objectName": "张三大孙子",
							"children": []
						}
					]
				}
			]
		},
		{
			"uid": "518079417910558720",
			"objectName": "张四",
			"children": []
		}
}

简单的数据结构,有父子关系。

初始化树结构


        /// <summary>
        /// 初始化树信息
        /// </summary>
        private void LoadData(WorkspaceRootVo workspaceRootVo)
        {
            this.workSpaceTree.Nodes.Clear();
            this.workSpaceTree.BeginUpdate();

            DataTable dt = new DataTable();
            dt.Columns.Add("uid");
            dt.Columns.Add("index");
            dt.Columns.Add("parentUid");
            dt.Columns.Add("parentIndex");
            dt.Columns.Add("objectName"); 

            int index = 0;
            ParseDataTable(dt, workspaceRootVo.data, null, ref index, 0);

            //TreeList绑定数据
            this.workSpaceTree.DataSource = dt;
            
            this.workSpaceTree.KeyFieldName = "index";
            this.workSpaceTree.ParentFieldName = "parentIndex";
            this.workSpaceTree.EndUpdate();
            this.workSpaceTree.ExpandAll();
        }

LoadData方法的参数WorkspaceRootVo 是上面json数据对象,如果不懂怎么解析,可百度Newtonsoft.Json,DataTable是树的数据源,指定给this.workSpaceTree.DataSource即可。 this.workSpaceTree.KeyFieldNamethis.workSpaceTree.ParentFieldName指定父子关系。

ParseDataTable方法如下:

private void ParseDataTable(DataTable dt, WorkspaceVo childVo, WorkspaceVo parentVo, ref int childIndex, int parentIndex)
        {
            DataRow dataRow = dt.NewRow();
            dataRow["uid"] = childVo.uid;
            int index = ++childIndex;
            dataRow["index"] = index;
            dataRow["objectName"] = childVo.objectName;
            if (null != parentVo)
            {
                dataRow["parentUid"] = parentVo.uid;
                dataRow["parentIndex"] = parentIndex;
            }
            else
            {
                dataRow["parentUid"] = null;
                dataRow["parentIndex"] = null;
            }
            dt.Rows.Add(dataRow);
            if (null != childVo.children && childVo.children.Count > 0)
            {
                foreach (WorkspaceVo itemVo in childVo.children)
                {
                    ParseDataTable(dt, itemVo, childVo, ref childIndex, index);
                }
            }
        }

通过递归调用,构建数据,每一个DataRow都通过dt.Rows.Add(dataRow);添加到Map集合里,本身没有父子关系,通过index和parentIndex,在逻辑上处理成父子关系。
按如上方式即可渲染出一个树。

TreeList选中事件

给TreeList增加FocusedNodeChanged获得焦点事件。增加的方法如下

this.workSpaceTree.FocusedNodeChanged += new DevExpress.XtraTreeList.FocusedNodeChangedEventHandler(this.WorkSpaceTree_FocusedNodeChanged);

当然我们可以通过属性面板里的事件面板自动增加事件处理。
在这里插入图片描述

在事件处理函数里有2种方式取数据。


        /**
         * 工作区选中事件
         * */
        private void WorkSpaceTree_FocusedNodeChanged(object sender, DevExpress.XtraTreeList.FocusedNodeChangedEventArgs e)
        {
            TreeList workSpaceTree = sender as TreeList;
            TreeListNode node = workSpaceTree.FocusedNode;
            if (null != node)
            {
                //方法一:通过GetDataRecordByNode获取结点数据集
                DataRowView drv = workSpaceTree.GetDataRecordByNode(node) as DataRowView;
                string uid = (string)drv["uid"];
                string objectName= (string)drv["objectName"];

                //方法二:通过GetDisplayText取得显示文件
                workspaceSelectedUid = node.GetDisplayText("uid");
                workspaceSelectedCtype = node.GetDisplayText("objectName");
            }
        }

获得行的数据集

  		DataRowView drv = workSpaceTree.GetDataRecordByNode(node) as DataRowView;
                string uid = (string)drv["uid"];
                string objectName= (string)drv["objectName"];

刷新数据以及控制选中

有时候我们需要从后台重新请求数据并显示,或者我们主动控制哪个结点被选中。
假如WorkspaceRootVo result = HttpUtil.GetResponse(UrlFactory.getWorkspaceUrl(GlobalData.ini.ReadValue(“home”)));是我们请求后台的接口方法。

刷新树型方法如下:

private void refreshTree()
        {
            workSpaceTree.ClearNodes();
            WorkspaceRootVo result = HttpUtil.GetResponse<WorkspaceRootVo>(UrlFactory.getWorkspaceUrl(GlobalData.ini.ReadValue("home")));
            LoadData(result);
        }

控制选中方法如下:

//调用testSetTreeSelect方法
private void  test(){
	testSetTreeSelect(this.workSpaceTree.Nodes, "515117932112510976");
}


//selectedUid是选中的结点Uid
private Boolean testSetTreeSelect(TreeListNodes treeListNodes, string selectedUid)
        {
            if (!string.IsNullOrEmpty(selectedUid))
            {
                for(int i = 0;i< treeListNodes.Count; i++)
                {
                    TreeListNode treeListNode = treeListNodes[i];
                    string uid = treeListNode.GetDisplayText("uid");
                    if (!string.IsNullOrEmpty(uid))
                    {
                        if(uid == selectedUid)
                        {
                            treeListNode.Selected = true;
                            return true;
                        }
                        else
                        {
                            treeListNode.Selected = false;
                        }
                    }
                    if (treeListNode.Nodes.Count > 0)
                    {
                        Boolean re = testSetTreeSelect(treeListNode.Nodes, selectedUid);
                        if (re)
                        {
                            return true;
                        }
                    }
                }               
            }
            return false;
        }

我这里使用Uid做唯一标识,如果有其它参数唯一,也可以用来判断。通过遍历树下所有结点,通过唯一的uid比较,选中使用treeListNode.Selected = true;,不选中treeListNode.Selected = false;

CheckState与Selected

这2属性意思不一样。CheckState是在有复杂框情况下选中是否,事实上,它的“选中”和Selected的“选中”不是一个选中,Selected是唯一的,表示当前某个结点被“选中”,而CheckState表达的是“选择”的意思。
TreeList带有CheckBox,并且节点要有三种状态(所有的子节点都选中,所有的子节点都没选择,一部分子节点选中)。
TreeList.OptionsView.ShowCheckBoxes = true :是否显示CheckBox
TreeList.OptionsBehavior.AllowIndeterminateCheckState = true :设置节点是否有中间状态,即一部分子节点选中,一部分子节点没有选中
设置这两个属性之后就实现了TreeList带有CheckBox,并且节点有三种状态。
复选框的子节点与父节点统一的规则有:

    1.  选择某一节点时,该节点的子节点全部选择
    2.  取消某一节点时,该节点的子节点全部取消选择
    3. 某节点的子节点全部选择时,该节点选择
    4. 某节点的子节点未全部选择时,该节点不选择

代码如下

//节点选中前事件,通过上一次选中状态计算这次状态
 private void treeList1_BeforeCheckNode(object sender, DevExpress.XtraTreeList.CheckNodeEventArgs e)  
 {  
     if (e.PrevState == CheckState.Checked)  
     {  
         e.State = CheckState.Unchecked;  
     }  
     else  
     {  
         e.State = CheckState.Checked;  
     }  
 }  
 
 //region 节点选中后事件 ,计算子结点和父结点状态
 private void treeList1_AfterCheckNode(object sender, DevExpress.XtraTreeList.NodeEventArgs e)  
 {  
     SetCheckedChildNodes(e.Node, e.Node.CheckState);  
     SetCheckedParentNodes(e.Node, e.Node.CheckState);  
 }  
 
 //region 设置子节点状态跟随父结点
 private void SetCheckedChildNodes(DevExpress.XtraTreeList.Nodes.TreeListNode node, CheckState check)  
 {  
     for (int i = 0; i < node.Nodes.Count; i++)  
     {  
         node.Nodes[i].CheckState = check;  
         SetCheckedChildNodes(node.Nodes[i], check);  
     }  
 }  
 
 //region 设置父节点状态,如果它的孩子都被选中,则状态是check(由上下文件环境传递),否则是CheckState.Indeterminate
 private void SetCheckedParentNodes(DevExpress.XtraTreeList.Nodes.TreeListNode node, CheckState check)  
 {  
     if (node.ParentNode != null)  
     {  
         bool b = false;  
         CheckState state;  
         for (int i = 0; i < node.ParentNode.Nodes.Count; i++)  
         {  
             state = (CheckState)node.ParentNode.Nodes[i].CheckState;  
             if (!check.Equals(state))  
             {  
                 b = !b;  
                 break;  
             }  
         }  
         if (b)  
         {  
             node.ParentNode.CheckState = CheckState.Indeterminate;  
         }  
         else  
         {  
             node.ParentNode.CheckState = check;  
         }  
         SetCheckedParentNodes(node.ParentNode, check);  
     }  
 }  

树型结构展示

默认展示形势是TreeList,效果
在这里插入图片描述

如果想换成树型,则需要设置ViewStyle=TreeView。或者如下操作
打开Run Designer找到Options,设置ViewStyle
在这里插入图片描述

展开和关闭图标

默认的展开和关闭图标是箭头,如果想换成+-。则如下操作
打开Run Designer找到Options,设置LookAndFeel
在这里插入图片描述

LookAndFeel.UseDefaultLookAndFeel=false
LookAndFeel.UseWindowsXPTheme=false

增加过滤显示功能

TreeList自身是提供了一个数据过滤功能,但这个功能有一个缺点。就是如果目录树是多级目录树,并且父节点不符合过滤条件时,即使里面的子节点符合过滤条件,也不会显示对应的节点。所以我们使用DevExpress.XtraEditors.SearchControl控件,
拖拽一个SearchControl到面板上,指定属性Client为需要过滤的树即可,其它不用设置。
有些人说给树添加FilterNode事件,根据不需要,而且DevExpress17.2.7版本下TreeList也没有FilterNode事件。

©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页