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);
}                

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();
}               

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.

You can also explore the outline structure using the following properties and methods:

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();
}

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();
}          

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);
     }
}                
See Also