[]
        
(Showing Draft Content)

Tree Grid

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.


Tree Grid

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 DataSource property of the C1FlexGrid 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 Level 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 GridTree.Column 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 RowCollection class which inserts a new node row at a specified index. This is the 'low-level' way of inserting totals and building outlines.


Create node in bound mode


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 IsNode 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 Node 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 Subtotal 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.


Create node in bound mode using Subtotal method


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.


Create node in unbound mode using subtotal method


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.


Create node in unbound mode using IsNode property


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        

See Also

Documentation


Summary


Group


Blog


Creating Outlines and Trees with the C1FlexGrid Control