Tree Traversal
- 8 minutes to read
The TreeList control arranges data records in a tree structure — a node can have child nodes. To traverse the tree you can use either a recursive method or an iterator.
Recursive Method
You can enumerate nodes in a tree with a recursive method — a method that calls itself in its body. Typically, you initially call a recursive method on a node from which you start to enumerate nodes, and then, you recursively call it on a child or parent node depending on the direction. To get access to the tree structure, use the following properties:
- TreeList.Nodes — provides access to root nodes.
- TreeListNode.Nodes — provides access to a node’s child nodes. You can also use the TreeListNode.HasChildren property to check if a node has child nodes.
- TreeListNode.ParentNode — provides access to a node’s parent node.
In the example below, only leaf nodes (nodes that have no children) have values in the Budget column. The code below shows how to calculate parent node values when the control is loaded. The code also updates parent nodes when a cell value changes.
The code below also shows how to update child nodes when a cell value in the Location column changes.
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
The TreeList.NodesIterator property provides access to a TreeListNodesIterator object that allows you to enumerate nodes without the use of a recursive method.
Enumerate All Nodes
Follow the steps below to enumerate all nodes and execute a custom operation on each node.
Specify the operation executed on nodes.
- Inherit from the TreeListOperation abstract class.
- Override the TreeListOperation.Execute method. This method is called each time a node is enumerated and accepts the node as a parameter.
public class MyOperation : TreeListOperation { public override void Execute(TreeListNode node) { //Do something. } }
You can also specify nodes that should be enumerated. See Enumerate Specific Nodes below.
Enumerate nodes and execute the specified operation on each row.
- Use the TreeList.NodesIterator property to get an iterator.
- Pass the created operation to the TreeListNodesIterator.DoOperation or TreeListNodesIterator.DoLocalOperation method.
You can also use lambda expressions. The code below shows how to count nodes.
How to: Count Nodes at Specific Level
The following example shows how to use the Nodes Iterator to obtain the number of nodes which reside on the specified nesting level.
In the example, a CustomNodeOperation object is created that is used to calculate this number. The operation class contains an internal counter that is incremented by one each time a node that resides at the specified nesting level is accessed.
To get the total number of the nodes which reside at the specified nesting level, a CustomNodeOperation instance is created and passed to the TreeListNodesIterator.DoLocalOperation method. After the method has been performed, the CustomNodeOperation.NodeCount property is read to get the number of nodes.
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
You can override an operation’s following properties and methods to enumerate specific nodes only:
The CanExecute(TreeListNode) method — returns whether the operation can be executed on the processed node. Returns true if not overridden.
The CanContinueIteration(TreeListNode) method — called before and after the operation is executed on the processed node and returns a value that specifies whether to continue to enumerate nodes. Returns true if not overridden.
Use this method to improve performance. For instance, if you need to find a node.
The NeedsVisitChildren(TreeListNode) method — returns a value that specifies whether to enumerate the processed node’s child nodes. Returns true if not overridden.
Use this method to improve performance. For instance, if you need to enumerate root nodes only.
The NeedsFullIteration property — gets or sets whether to enumerate leaf nodes (nodes without children). Returns true if not overridden.
Use this property to improve performance. If the operation expands collapsed nodes, for example, there is no need to enumerate leaf nodes.
Tip
You can also check if the processed node is a leaf and specify whether to execute the operation in the Execute(TreeListNode) method. Note that this approach can decrease performance.
Note
If this property is set to false, the CanExecute(TreeListNode) method returns false for leaf nodes.
The TreeListOperation.FinalizeOperation method — called when the process is finished and allows you to release allocated resources.
How to: Collapse Specific Nodes
The following example shows how to create an operation class that will collapse all the nodes which do not contain the specified node as their child. Only the node’s parents will be expanded as a result.
The operation class accepts the node as its constructor parameter and stores it in an internal variable. The Execute method checks whether the processed node contains the specified node as a child. If not, the processed node is collapsed. The operation class also overrides the TreeListOperation.NeedsFullIteration property to process only the nodes that have children. This provides performance benefits when working with large and complex node structures.
The image below shows the Tree List before and after executing the sample code:
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));