Goto Tree List 树列表
Tree Traversal 树遍历
TreeList 控件以树结构排列数据记录 — 一个节点可以有子节点。要遍历树,可以使用递归方法或迭代器。
Recursive Method 递归方法
您可以使用递归方法枚举树中的节点 — 一种在其主体中调用自身的方法。通常,您最初在开始枚举节点的节点上调用递归方法,然后根据方向在子节点或父节点上递归调用该方法。要访问树结构,请使用以下属性:
- TreeList.Nodes — 提供对根节点的访问。
- TreeListNode.Nodes — 提供对节点的子节点的访问。您还可以使用 TreeListNode.HasChildren 属性来检查节点是否具有子节点。
- TreeListNode.ParentNode — 提供对节点的父节点的访问。
在下面的示例中,只有叶节点(没有子节点的节点)在 Budget 列中具有值。下面的代码显示了如何在加载控件时计算父节点值。当单元格值发生更改时,该代码还会更新父节点。
下面的代码还显示了当 Location 列中的单元格值发生更改时如何更新子节点。
using System;
using DevExpress.XtraTreeList;
using DevExpress.XtraTreeList.Nodes;
private void treeList1_Load(object sender, EventArgs e) {
UpdateNodes(treeList1.Nodes, "BUDGET");
}
int lockChanged = 0;
int UpdateNodes(TreeListNodes nodes, string fieldName) {
lockChanged++;
int sum = 0;
foreach (TreeListNode treeListNode in nodes) {
if (treeListNode.HasChildren)
treeListNode.SetValue(fieldName, UpdateNodes(treeListNode.Nodes, fieldName));
sum += Convert.ToInt32(treeListNode.GetValue(fieldName));
}
lockChanged--;
return sum;
}
private void treeList1_CellValueChanged(object sender, CellValueChangedEventArgs e) {
if (lockChanged == 0) {
if(e.Column.FieldName == "BUDGET")
UpdateParentNodes(e.Node, e.Column.FieldName);
if (e.Column.FieldName == "LOCATION")
UpdateChildNodes(e.Node, e.Column.FieldName);
}
}
void UpdateParentNodes(TreeListNode node, string fieldName) {
TreeListNode parent = node.ParentNode;
if (parent == null || String.IsNullOrEmpty(fieldName))
return;
lockChanged++;
int sum = 0;
foreach (TreeListNode childNode in parent.Nodes) {
sum += Convert.ToInt32(childNode.GetValue(fieldName));
}
// The following SetValue method call does not raise the CellValueChanged event,
// since it is called in a CellValueChanged event handler.
parent.SetValue(fieldName, sum);
// Call the method recursively to update the current node's parent nodes.
UpdateParentNodes(parent, fieldName);
lockChanged--;
}
void UpdateChildNodes(TreeListNode node, string fieldName) {
if (!node.HasChildren || String.IsNullOrEmpty(fieldName))
return;
lockChanged++;
foreach(TreeListNode childNode in node.Nodes) {
childNode.SetValue(fieldName, Convert.ToString(node.GetValue(fieldName)));
UpdateChildNodes(childNode, fieldName);
}
lockChanged--;
}
Node Iterator 节点迭代器
TreeList.NodesIterator 属性提供对 TreeListNodesIterator 对象的访问,该对象允许您在不使用递归方法的情况下枚举节点。
Enumerate All Nodes 枚举所有节点
按照以下步骤枚举所有节点并在每个节点上执行自定义操作。
- 指定在节点上执行的操作。
- 继承自 TreeListOperation 抽象类。
- 重写 TreeListOperation.Execute 方法。每次枚举节点时都会调用此方法,并接受该节点作为参数。
public class MyOperation : TreeListOperation {
public override void Execute(TreeListNode node) {
//Do something.
}
}
您还可以指定应枚举的节点。请参阅下面的枚举特定节点。
- 枚举节点并对每一行执行指定的操作。
- 使用 TreeList.NodesIterator 属性获取迭代器。
- 将创建的操作传递给 TreeListNodesIterator.DoOperation 或 TreeListNodesIterator.DoLocalOperation 方法。
treeList1.NodesIterator.DoOperation(new MyOperation());
您还可以使用 lambda 表达式。下面的代码显示了如何对节点进行计数。
int count = 0;
treeList1.NodesIterator.DoOperation((n) => count++);
如何:对特定级别的节点进行计数
以下示例显示如何使用 Nodes Iterator 获取驻留在指定嵌套级别的节点数。
在该示例中,创建了一个 CustomNodeOperation 对象,用于计算此数字。操作类包含一个内部计数器,每次访问位于指定嵌套级别的节点时,该计数器都会递增 1。
要获取驻留在指定嵌套级别的节点总数,创建一个 CustomNodeOperation 实例并将其传递给 TreeListNodesIterator.DoLocalOperation 方法。执行该方法后,将读取 CustomNodeOperation.NodeCount 属性以获取节点数。
using DevExpress.XtraTreeList.Nodes;
using DevExpress.XtraTreeList.Nodes.Operations;
// Declaring the custom operation class.
class CustomNodeOperation : TreeListOperation {
int level;
int nodeCount;
public CustomNodeOperation(int level) : base() {
this.level = level;
this.nodeCount = 0;
}
public override void Execute(TreeListNode node) {
if(node.Level == level)
nodeCount++;
}
public int NodeCount {
get { return nodeCount; }
}
}
// ...
CustomNodeOperation operation = new CustomNodeOperation(2);
treeList1.NodesIterator.DoLocalOperation(operation, treeList1.Nodes);
int totalNodesAtSecondLevel = operation.NodeCount;
Enumerate Specific Nodes 枚举特定节点
您可以覆盖操作的以下属性和方法,以仅枚举特定节点:
-
CanExecute(TreeListNode) 方法 — 返回是否可以在已处理的节点上执行操作。如果未覆盖,则返回 true。
-
CanContinueIteration(TreeListNode) 方法 — 在已处理的节点上执行操作之前和之后调用,并返回一个值,该值指定是否继续枚举节点。如果未覆盖,则返回 true。
使用此方法可提高性能。例如,如果您需要查找节点。 -
NeedsVisitChildren(TreeListNode) 方法 — 返回一个值,该值指定是否枚举已处理节点的子节点。如果未覆盖,则返回 true。
使用此方法可提高性能。例如,如果只需要枚举根节点。 -
NeedsFullIteration 属性 — 获取或设置是否枚举叶节点(没有子节点的节点)。如果未覆盖,则返回 true。
使用此属性可提高性能。例如,如果该操作展开了折叠的节点,则无需枚举叶节点。 -
TreeListOperation.FinalizeOperation 方法 — 在流程完成时调用,并允许您释放分配的资源。
如何:折叠特定节点
以下示例演示如何创建一个操作类,该类将折叠所有不包含指定节点作为其子节点的节点。因此,只有节点的父节点才会被扩展。
操作类接受节点作为其构造函数参数,并将其存储在内部变量中。Execute 方法检查处理的节点是否包含指定的节点作为子节点。否则,已处理的节点将折叠。操作类还重写 TreeListOperation.NeedsFullIteration 属性,以仅处理具有子节点的节点。这在处理大型复杂节点结构时提供了性能优势。
下图为执行示例代码前后的 Tree List:
using DevExpress.XtraTreeList.Nodes;
using DevExpress.XtraTreeList.Nodes.Operations;
public class CollapseExceptSpecifiedOperation : TreeListOperation {
TreeListNode visibleNode;
public CollapseExceptSpecifiedOperation(TreeListNode visibleNode) : base() {
this.visibleNode = visibleNode;
}
public override void Execute(TreeListNode node) {
if (!visibleNode.HasAsParent(node))
node.Expanded = false;
}
public override bool NeedsFullIteration { get { return false; } }
}
//...
treeList1.NodesIterator.DoOperation(new CollapseExceptSpecifiedOperation(treeList1.FocusedNode));