FlexGrid for WinForms | ComponentOne
In This Topic
    Tree Grid
    In This Topic

    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.

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