C#开发WinForm之DevExpress框架里TreeList使用
文章目录
前言
winForm里树型结构是比较常见的,不论何种前端开发,树型都需要学会怎么用。
而DexExpress里的TreeList是比较特殊的使用方式 ,因为它的数据结构是“平级”的,从结构上讲,数据没有上下级关系。都放在DataRowCollection这个Map集合里,它通过指定KeyFieldName和ParentFieldName来标明上面级关系。换句话说,数据是平等的,关键在逻辑上来处理父子关系。
树
新建一下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.KeyFieldName和this.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事件。