Overview
One of the unique and popular features of the FlexGrid control is the ability to add hierarchical grouping to regular data grid and display it in a tree like structure called Tree Grid. The Tree Grid control is very similar to the one you see in a TreeView control. It shows an indented structure with collapse or expand icon next to each node row so the user can expand and collapse the outline to see the desired level of detail by clicking on the nodes.
Although, you can create simple outline trees using FlexGrid grouping, Tree Grid helps you implement more advanced use cases such as displaying customer and order details. In a usual grid showing such data, it is difficult to see the details of each customer and order. In such case, you can create a Tree Grid to group the data in a hierarchical structure for better view and accessibility of information.

Quick Binding
Loading data in a Tree Grid is exactly the same as loading in a regular grid. If the data source is available at design time, you can use the Visual Studio Property Window to set the grid's property of the class and bind the grid to the data without writing even a single line of code. For detailed steps, see Bound Mode.
You can also set the DataSource property through code. Following code shows how to use the DataSource property to populate data in the WinForms Tree Grid.
private void Bound_Node_Load(object sender, EventArgs e)
{
// Binding FlexGrid
BindGrid(_gridBound);
}
public void BindGrid(C1FlexGrid grid)
{
DataTable _dt = new DataTable();
// Get data
var fields = @" Country, City, SalesPerson, Quantity, ExtendedPrice";
var sql = string.Format("SELECT {0} FROM Invoices ORDER BY {0}", fields);
var da = new OleDbDataAdapter(sql, GetConnectionString());
da.Fill(_dt);
// Bind grid to data
grid.DataSource = _dt;
// Format ExtendedPrice column
grid.Cols["ExtendedPrice"].Format = "n2";
}
static string GetConnectionString()
{
string path = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + @"\ComponentOne Samples\Common";
string conn = @"provider=microsoft.jet.oledb.4.0;data source={0}\c1nwind.mdb;";
return string.Format(conn, path);
}
Private Sub Bound_Node_Load(ByVal sender As Object, ByVal e As EventArgs)
' Binding FlexGrid
BindGrid(_gridBound)
End Sub
Public Sub BindGrid(ByVal grid As C1FlexGrid)
Dim _dt As DataTable = New DataTable()
' Get data
Dim fields = " Country, City, SalesPerson, Quantity, ExtendedPrice"
Dim sql = String.Format("SELECT {0} FROM Invoices ORDER BY {0}", fields)
Dim da = New OleDbDataAdapter(sql, GetConnectionString())
da.Fill(_dt)
' Bind grid to data
grid.DataSource = _dt
' Format ExtendedPrice column
grid.Cols("ExtendedPrice").Format = "n2"
End Sub
Private Shared Function GetConnectionString() As String
Dim path As String = Environment.GetFolderPath(Environment.SpecialFolder.Personal) & "\ComponentOne Samples\Common"
Dim conn As String = "provider=microsoft.jet.oledb.4.0;data source={0}\c1nwind.mdb;"
Return String.Format(conn, path)
End Function
The code uses an OleDbDataAdapter to fill a DataTable with data, then assigns datatable to DataSource property of the grid. To turn this regular grid in a Tree grid, you need to insert the node rows which is discussed in the section below.
Create Nodes (Bound and Unbound Mode)
To create a Tree Grid, FlexGrid introduces a concept of Node rows. These rows do not contain regular data and are simply header rows under which similar data is grouped, just like nodes in a usual TreeView control. You can also define the hierarchy of these nodes by setting the property. These nodes can be collapsed and expanded to hide or show the data they contain. The Tree Grid can be displayed in any column, defined by the property. By default, this property is set to -1, which causes the tree not to be displayed at all.
Bound Mode (Using InsertNode Method)
You can create node rows using InsertNode method of the class which inserts a new node row at a specified index. This is the 'low-level' way of inserting totals and building outlines.

The GroupBy method used here inserts node rows by grouping identical values. To obtain a Node object, you can either use return value of the InsertNode method or retrieve the node for an existing row using the property.
Use the following code to create nodes using InsertNode method in the bound WinForms Tree Grid.
private void Bound_Node_Load(object sender, EventArgs e)
{
// Binding FlexGrid
BindGrid(_gridBound);
// Shows Tree in Bound FlexGrid
CreateTree(_gridBound);
GroupBy(_gridBound, "Country", 0);
GroupBy(_gridBound, "City", 1);
}
public void CreateTree(C1FlexGrid grid)
{
grid.Tree.Column = 0;
grid.Tree.Style = TreeStyleFlags.SimpleLeaf;
grid.Tree.Show(1);
}
void GroupBy(C1FlexGrid grid, string columnName, int level)
{
// Styles group at level 0
CellStyle s0 = grid.Styles.Add("Group0");
s0.BackColor = Color.Gray;
s0.ForeColor = Color.White;
s0.Font = new Font(grid.Font, FontStyle.Bold);
// Styles group at level 1
CellStyle s1 = grid.Styles.Add("Group1");
s1.BackColor = Color.LightGray;
s1.ForeColor = Color.Black;
object current = null;
for (int r = grid.Rows.Fixed; r < grid.Rows.Count; r++)
{
if (!grid.Rows[r].IsNode)
{
var value = grid[r, columnName];
if (!object.Equals(value, current))
{
// Value changed: insert node.
grid.Rows.InsertNode(r, level);
if (level == 0)
grid.Rows[r].Style = s0;
else if (level == 1)
grid.Rows[r].Style = s1;
// Show group name in first scrollable column.
grid[r, grid.Cols.Fixed] = value;
// Update current value.
current = value;
}
}
}
grid.AutoSizeCols();
}
Private Sub Bound_Node_Load(ByVal sender As Object, ByVal e As EventArgs)
' Binding FlexGrid
BindGrid(_gridBound)
' Shows Tree in Bound FlexGrid
CreateTree(_gridBound)
GroupBy(_gridBound, "Country", 0)
GroupBy(_gridBound, "City", 1)
End Sub
Public Sub CreateTree(ByVal grid As C1FlexGrid)
grid.Tree.Column = 0
grid.Tree.Style = TreeStyleFlags.SimpleLeaf
grid.Tree.Show(1)
End Sub
Private Sub GroupBy(ByVal grid As C1FlexGrid, ByVal columnName As String, ByVal level As Integer)
' Styles group at level 0
Dim s0 As CellStyle = grid.Styles.Add("Group0")
s0.BackColor = Color.Gray
s0.ForeColor = Color.White
s0.Font = New Font(grid.Font, FontStyle.Bold)
' Styles group at level 1
Dim s1 As CellStyle = grid.Styles.Add("Group1")
s1.BackColor = Color.LightGray
s1.ForeColor = Color.Black
Dim current As Object = Nothing
Dim r As Integer = grid.Rows.Fixed
While r And lt
grid.Rows.Count
End While
r += 1
If True Then
If Not grid.Rows(r).IsNode Then
Dim value = grid(r, columnName)
If Not Object.Equals(value, current) Then
' Value changed: insert node.
grid.Rows.InsertNode(r, level)
If level = 0 Then
grid.Rows(r).Style = s0
ElseIf level = 1 Then
grid.Rows(r).Style = s1
End If
' Show group name in first scrollable column.
grid(r, grid.Cols.Fixed) = value
' Update current value.
current = value
End If
End If
End If
grid.AutoSizeCols()
End Sub
The code also calls the AutoSizeCols method to ensure that the column is wide enough to accommodate the Tree Grid. Finally, it calls the GridTree.Show method to display the nodes.
Also, the class provides following methods and properties, based on TreeView object model, which can be used to manage the Tree Grid.
- Checked: Gets or sets the check state of cell defined by Node.Row and GridTree.Column.
- Collapsed/Expanded: Gets or sets collapsed or expanded state of the node.
- Data: Gets or sets value in the cell defined by Node.Row and GridTree.Column.
- Image: Gets or sets image in the cell defined by Node.Row and GridTree.Column.
- Level: Gets or sets node level in the Tree Grid.
You can also explore the outline structure using the following properties and methods:
- Children: Gets number of child nodes under this node.
- GetCellRange: Gets the CellRange object that described range of rows that belong to this node.
- GetNode: Gets the node that has a given relationship to this node (parent, first child, next sibling, and so on).
- Nodes: Gets a node array containing child nodes of this node.
Bound Mode (Using Subtotal Method)
In bound mode, another way to create nodes is using the method. To make the Tree Grid really useful, the node rows must include summary information for the data they contain.
If you create Tree Grid using the Subtotal method, the subtotals are added automatically. The method scans the entire grid and automatically inserts node rows with optional subtotals at locations where the grid data changes.
This is the 'high-level' way of inserting totals and building outlines.

The first parameter of the Subtotal method is AggregateEnum enumeration which calculates various types of aggregates like Sum, Average, Count, Max, Min, and others. In the code below, Subtotal method of the C1FlexGrid class is used for creating nodes in a bound WinForms Tree Grid.
private void SubtotalNode_Bound_Load(object sender, EventArgs e)
{
// Binding FlexGrid
BindGrid(_gridBound);
// shows Tree in Bound FlexGrid
CreateTree(_gridBound);
// Creates Subtotal(s) in Bound FlexGrid
CreateSubTotal(_gridBound);
}
public void BindGrid(C1FlexGrid grid)
{
DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
dt.Columns.Add("Name", typeof(string));
dt.Columns.Add("Course", typeof(string));
dt.Columns.Add("Score", typeof(int));
dt.Columns.Add("Attendance", typeof(int));
dt.Columns.Add("Country", typeof(string));
//Sample Data
dt.Rows.Add(1, "Helen Bennett", "ComputerScience", 79, 84, "Spain");
dt.Rows.Add(2, "Ana Trujillo", "Biology", 78, 87, "Mexico");
dt.Rows.Add(3, "Antonio Moreno", "Aeronautics", 71, 70, "Spain");
dt.Rows.Add(4, "Paolo Accorti", "Biology", 74, 63, "Spain");
dt.Rows.Add(5, "Elizabeth Brown", "ComputerScience", 80, 93, "Mexico");
dt.Rows.Add(6, "Jaime Yorres", "Biology", 61, 48, "Spain");
dt.Rows.Add(7, "Yvonne Moncada", "Aeronautics", 85, 78, "Mexico");
dt.Rows.Add(8, "Martine Rancé", "Aeronautics", 67, 81, "Spain");
dt.Rows.Add(9, "Sergio Gutiérrezy", "ComputerScience", 62, 58, "Mexico");
dt.Rows.Add(10, "Thomas Hardy", "Aeronautics", 94, 92, "Mexico");
dt.Rows.Add(11, "Patricio Simpson", "Aeronautics", 46, 52, "Spain");
dt.Rows.Add(12, "Maria Anders", "ComputerScience", 85, 73, "Spain");
grid.DataSource = dt;
grid.AutoSizeCols();
(grid.DataSource as DataTable).DefaultView.Sort = "Course";
}
// Creates Tree in FlexGrid
public void CreateTree(C1FlexGrid grid)
{
grid.Tree.Column = 1;
grid.Tree.Style = TreeStyleFlags.SimpleLeaf;
grid.Tree.Show(1);
grid.AutoSizeCols();
}
public void CreateSubTotal(C1FlexGrid grid)
{
// Clears any existing subtotal(s) present in grid
grid.Subtotal(AggregateEnum.Clear);
// Adds subtotal in grid with Level: 1, Group: 'Course' column, Aggregate(Average): Score, Attendance
grid.Subtotal(AggregateEnum.Average, 1, 3, 3, 4, "Average for {0}");
grid.AutoSizeCols();
}
Private Sub SubtotalNode_Bound_Load(ByVal sender As Object, ByVal e As EventArgs)
' Binding FlexGrid
BindGrid(_gridBound)
' Shows Tree in Bound FlexGrid
CreateTree(_gridBound)
' Creates Subtotal(s) in Bound FlexGrid
CreateSubTotal(_gridBound)
End Sub
Public Sub BindGrid(ByVal grid As C1FlexGrid)
Dim dt As DataTable = New DataTable()
dt.Columns.Add("ID", GetType(Integer))
dt.Columns.Add("Name", GetType(String))
dt.Columns.Add("Course", GetType(String))
dt.Columns.Add("Score", GetType(Integer))
dt.Columns.Add("Attendance", GetType(Integer))
dt.Columns.Add("Country", GetType(String))
'Sample Data
dt.Rows.Add(1, "Helen Bennett", "ComputerScience", 79, 84, "Spain")
dt.Rows.Add(2, "Ana Trujillo", "Biology", 78, 87, "Mexico")
dt.Rows.Add(3, "Antonio Moreno", "Aeronautics", 71, 70, "Spain")
dt.Rows.Add(4, "Paolo Accorti", "Biology", 74, 63, "Spain")
dt.Rows.Add(5, "Elizabeth Brown", "ComputerScience", 80, 93, "Mexico")
dt.Rows.Add(6, "Jaime Yorres", "Biology", 61, 48, "Spain")
dt.Rows.Add(7, "Yvonne Moncada", "Aeronautics", 85, 78, "Mexico")
dt.Rows.Add(8, "Martine Rancé", "Aeronautics", 67, 81, "Spain")
dt.Rows.Add(9, "Sergio Gutiérrezy", "ComputerScience", 62, 58, "Mexico")
dt.Rows.Add(10, "Thomas Hardy", "Aeronautics", 94, 92, "Mexico")
dt.Rows.Add(11, "Patricio Simpson", "Aeronautics", 46, 52, "Spain")
dt.Rows.Add(12, "Maria Anders", "ComputerScience", 85, 73, "Spain")
grid.DataSource = dt
grid.AutoSizeCols()
TryCast(grid.DataSource, DataTable).DefaultView.Sort = "Course"
End Sub
' Creates Tree in FlexGrid
Public Sub CreateTree(ByVal grid As C1FlexGrid)
grid.Tree.Column = 1
grid.Tree.Style = TreeStyleFlags.SimpleLeaf
grid.Tree.Show(1)
grid.AutoSizeCols()
End Sub
Public Sub CreateSubTotal(ByVal grid As C1FlexGrid)
' Clears any existing subtotal(s) present in grid
grid.Subtotal(AggregateEnum.Clear)
' Adds subtotal in grid with Level: 1, Group: 'Course' column, Aggregate(Average): Score, Attendance
grid.Subtotal(AggregateEnum.Average, 1, 3, 3, 4, "Average for {0}")
grid.AutoSizeCols()
End Sub
Unbound Mode (Using Subtotal Method)
The Subtotal method is a very convenient and flexible way to create a tree grid. It has a number of overloads that allow you to specify which columns are to be grouped on and totaled on by index or by name, whether to include a caption in the node rows that it inserts, how to perform the grouping, and so on.

In the code below, Subtotal method of the C1FlexGrid class is used for creating nodes in an unbound WinForms Tree Grid.
private void Unbound_Subtotal_Load(object sender, EventArgs e)
{
// Adding data to Unbound FlexGrid
PopulateGrid(_gridUnbound);
// Shows tree in Unbound FlexGrid
ShowTreeNode(_gridUnbound);
// Creates Subtotal(s) in Unbound grid
CreateSubTotal(_gridUnbound);
}
public void PopulateGrid(C1FlexGrid grid)
{
// Populate grid
Random rnd = new Random();
grid.Rows.Count = 14;
grid[0, 1] = "Direction";
grid[0, 2] = "Region";
CellRange rg = grid.GetCellRange(0, 3, 0, grid.Cols.Count - 1);
rg.Data = "Rnd";
for (int r = 1; r < grid.Rows.Count; r++)
{
grid[r, 0] = r;
grid[r, 1] = (r < 7) ? "Inbound" : "Outbound";
grid[r, 2] = (r < 3) ? "North" : (r < 7) ? "South" : (r < 10) ? "East" : "West";
for (int c = 3; c < grid.Cols.Count; c++)
{
grid[r, c] = rnd.Next(1000);
grid.Cols[c].Format = "#,###";
}
}
grid.AutoSizeCols();
}
// Creates Tree in FlexGrid
private void ShowTreeNode(C1FlexGrid grid)
{
// Creates tree
grid.Tree.Column = 1;
grid.Tree.Style = TreeStyleFlags.SimpleLeaf;
grid.AutoSizeCols();
}
// Creates Subtotal in FlexGrid
public void CreateSubTotal(C1FlexGrid grid)
{
// Clears any existing subtotal(s) present in grid
grid.Subtotal(AggregateEnum.Clear);
for (int c = 3; c < grid.Cols.Count; c++)
{
// Adds subtotals in grid
grid.Subtotal(AggregateEnum.Sum, 0, -1, c, "Grand Total");
grid.Subtotal(AggregateEnum.Sum, 2, 2, c, "Total for {0}");
}
grid.AutoSizeCols();
}
Private Sub Unbound_Subtotal_Load(ByVal sender As Object, ByVal e As EventArgs)
' Adding data to Unbound FlexGrid
PopulateGrid(_gridUnbound)
' Shows tree in Unbound FlexGrid
ShowTreeNode(_gridUnbound)
' Creates Subtotal(s) in Unbound grid
CreateSubTotal(_gridUnbound)
End Sub
Public Sub PopulateGrid(ByVal grid As C1FlexGrid)
' Populate grid
Dim rnd As Random = New Random()
grid.Rows.Count = 14
grid(0, 1) = "Direction"
grid(0, 2) = "Region"
Dim rg As CellRange = grid.GetCellRange(0, 3, 0, grid.Cols.Count - 1)
rg.Data = "Rnd"
Dim r As Integer = 1
While r And lt
grid.Rows.Count
End While
r += 1
If True Then
grid(r, 0) = r
grid(r, 1) = r And lt
7
"Inbound" "Outbound"
grid(r, 2) = r And lt
3
"North"
r And lt
7
"South"
r And lt
10
"East" "West"
Dim c As Integer = 3
While c And lt
grid.Cols.Count
End While
c += 1
If True Then
grid(r, c) = rnd.[Next](1000)
grid.Cols(c).Format = "#,###"
End If
End If
grid.AutoSizeCols()
End Sub
' Creates Tree in FlexGrid
Private Sub ShowTreeNode(ByVal grid As C1FlexGrid)
' Creates tree
grid.Tree.Column = 1
grid.Tree.Style = TreeStyleFlags.SimpleLeaf
grid.AutoSizeCols()
End Sub
' Creates Subtotal in FlexGrid
Public Sub CreateSubTotal(ByVal grid As C1FlexGrid)
' Clears any existing subtotal(s) present in grid
grid.Subtotal(AggregateEnum.Clear)
Dim c As Integer = 3
While c And lt
grid.Cols.Count
End While
c += 1
If True Then
' Adds subtotals in grid
grid.Subtotal(AggregateEnum.Sum, 0, -1, c, "Grand Total")
grid.Subtotal(AggregateEnum.Sum, 2, 2, c, "Total for {0}")
End If
grid.AutoSizeCols()
End Sub
Unbound Mode(Using IsNode Property)
In an unbound grid, you can turn regular rows into node rows by simply setting the IsNode property to true. If you try to turn a regular data bound row into a node, it causes the grid to throw an exception.

Use the following code to create nodes using IsNode property in an unbound WinForms Tree Grid.
private void Unbound_Row_Load(object sender, EventArgs e)
{
// Populates unbound FlexGrid
PopulateGrid(_gridUnbound);
// Shows tree in unbound FlexGrid
ShowTreeNode(_gridUnbound);
}
private void PopulateGrid(C1FlexGrid grid)
{
// Resets FlexGrid
grid.Rows.Count = 0;
grid.Cols.Count = 2;
string fileName = "../../test.xml";
// Loads xml document
XmlDocument xdoc = new XmlDocument();
xdoc.Load(fileName);
// Reads XML document and shows it as nodes in FlexGrid
ShowNode(_gridUnbound, xdoc.ChildNodes[1], 0);
grid.AutoSizeCols();
}
// Creates Tree in FlexGrid
private void ShowTreeNode(C1FlexGrid grid)
{
// Creates tree
grid.Tree.Column = 0;
grid.Tree.Style = TreeStyleFlags.SimpleLeaf;
grid.AutoSizeCols();
}
// Add xml node in FlexGrid
private void ShowNode(C1FlexGrid grid, XmlNode node, int level)
{
// Skips comment nodes
if (node.NodeType == XmlNodeType.Comment)
return;
// Adds new row for the read xml node
int row = grid.Rows.Count;
grid.Rows.Add();
// Assigns xml nodes data in grid's cell
grid[row, 0] = node.Name;
// Checks if the xml node has only one child
if (node.ChildNodes.Count == 1)
{
// Sets node value in grid's cell
grid[row, 1] = node.InnerText;
}
// Makes new row a node
grid.Rows[row].IsNode = true;
grid.Rows[row].Node.Level = level;
// If the node has children, get them as well
if (node.ChildNodes.Count > 1)
{
// Recurse to get children
foreach (XmlNode child in node.ChildNodes)
ShowNode(_gridUnbound, child, level + 1);
}
}
Private Sub Unbound_Row_Load(ByVal sender As Object, ByVal e As EventArgs)
' Populates unbound FlexGrid
PopulateGrid(_gridUnbound)
' Shows tree in unbound FlexGrid
ShowTreeNode(_gridUnbound)
End Sub
Private Sub PopulateGrid(ByVal grid As C1FlexGrid)
' Resets FlexGrid
grid.Rows.Count = 0
grid.Cols.Count = 2
Dim fileName As String = "../../test.xml"
' Loads xml document
Dim xdoc As XmlDocument = New XmlDocument()
xdoc.Load(fileName)
' Reads XML document and shows it as nodes in FlexGrid
ShowNode(_gridUnbound, xdoc.ChildNodes(1), 0)
grid.AutoSizeCols()
End Sub
' Creates Tree in FlexGrid
Private Sub ShowTreeNode(ByVal grid As C1FlexGrid)
' Creates tree
grid.Tree.Column = 0
grid.Tree.Style = TreeStyleFlags.SimpleLeaf
grid.AutoSizeCols()
End Sub
' Add xml node in FlexGrid
Private Sub ShowNode(ByVal grid As C1FlexGrid, ByVal node As XmlNode, ByVal level As Integer)
' Skips comment nodes
If node.NodeType Is XmlNodeType.Comment Then Return
' Adds new row for the read xml node
Dim row As Integer = grid.Rows.Count
grid.Rows.Add()
' Assigns xml nodes data in grid's cell
grid(row, 0) = node.Name
' Checks if the xml node has only one child
If node.ChildNodes.Count = 1 Then
' Sets node value in grid's cell
grid(row, 1) = node.InnerText
End If
' Makes new row a node
grid.Rows(row).IsNode = True
grid.Rows(row).Node.Level = level
' If the node has children, get them as well
If node.ChildNodes.Count And gt Then
End If
1
If True Then
' Recurse to get children
For Each child As XmlNode In node.ChildNodes
ShowNode(_gridUnbound, child, level + 1)
Next
End If
End Sub