Blazor | ComponentOne
Controls / FlexGrid / TreeGrid
In This Topic
    TreeGrid
    In This Topic

    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.

    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.

    FlexGrid provides ChildItemsPath property that allows you to create a hierarchical structure of data to create a collapsible tree structure. It also provides the following properties based on TreeView object model, which can be used to manage TreeGrid.

    Properties Description
    TreeExpandMode Indicates how the tree is loaded and displayed in the grid. By default, this property has the GridTreeExpandMode.OnDemand value, meaning the tree is initially collapsed, and the subtrees are loaded on demand as the parent’s get expanded. This is the fastest mode to load, and is very useful for scenarios where the ChildItemsPath property is pointing to a late-evaluated property or huge trees. For other scenarios it can be useful or required the tree to be expanded from scratch. To make this you can simply set TreeExpandMode to Expanded mode and the whole tree is evaluated at loading time and all its nodes are expanded by default. Similarly, there is the Collapsed mode, which evaluates the whole tree and creates all the rows at loading time, but it shows all the nodes of the tree collapsed.
    TreeColumnIndex Allows you to specify the index of the column that shows the toggle buttons. By default it will be zero, meaning the first column will show the buttons and -1 can be set to avoid showing the buttons.
    TreeIndent Enables you to specify the length of the indentation displayed in the cells whose columns correspond to the TreeColumnIndex.
    TreeIndentMode Lets you specify how cells are indented and which cells are indented in the deepest level. This property uses the GridTreeIndentMode enumeration to specify if only the toggle cells are indented or all the cells are indented, including the ones not having toggle buttons.
    TreeLinesMode Determines how the treelines are displayed to connect parent-children rows using the GridTreeLinesMode enumeration. This allows you to choose whether you want to display the treelines as a series of connected lines between the connected nodes, display the treelines as vertical lines showing how far a group reaches or do not display the treelines,

    Now, let us create a hierarchical structure of tasks, where each task can have sub-tasks, allowing for a detailed breakdown of project work. This kind of organization is common in project management and software development to manage complex projects effectively. To implement TreeGrid in FlexGrid control, perform the following steps:

    1. Create a class named ProjectTask.cs class and define the following properties for the grid columns.
      ProjectTask.cs
      Copy Code
      public class ProjectTask : INotifyPropertyChanged
      {
          private bool _isExpanded = true;
          public ProjectTask()
          {
              SubTasks = new List<ProjectTask>();
              IsExpanded = true;
          }
      
          public string WBS { get; set; }
      
          [Display(Name = "Task Name")]
          public string Name { get; set; }
      
          public TimeSpan Duration { get; set; }
      
          public DateTime Start { get; set; }
      
          public DateTime Finish
          {
              get
              {
                  return Start + Duration;
              }
          }
      
          [Display(AutoGenerateField = false)]
          public ProjectTask ParentTask { get; set; }
      
          [Display(AutoGenerateField = false)]
          public List<ProjectTask> SubTasks { get; set; }
      
          [Display(AutoGenerateField = false)]
          public bool IsExpanded
          {
              get
              {
                  return _isExpanded;
              }
              set
              {
                  _isExpanded = value;
                  OnPropertyChanged();
                  OnIsVisibleChanged();
              }
          }
      
          private void OnIsVisibleChanged()
          {
              OnPropertyChanged("IsVisible");
              foreach (var subTask in SubTasks)
              {
                  subTask.OnIsVisibleChanged();
              }
          }
      
          [Display(AutoGenerateField = false)]
          public bool IsVisible
          {
              get
              {
                  if (ParentTask == null)
                  {
                      return true;
                  }
                  else if (!ParentTask.IsExpanded)
                  {
                      return false;
                  }
                  else
                  {
                      return ParentTask.IsVisible;
                  }
              }
          }
      
          [Display(AutoGenerateField = false)]
          public int Level
          {
              get
              {
                  if (ParentTask == null)
                  {
                      return 0;
                  }
                  else
                  {
                      return ParentTask.Level + 1;
                  }
              }
          }
          
          private void OnPropertyChanged([CallerMemberName] string propertyName = null)
          {
              if (PropertyChanged != null)
              {
                  PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
              }
          }
      
          public event PropertyChangedEventHandler PropertyChanged;
      
         
      }
      
    2. Create a collection with the name tasks which contains objects of type ProjectTask class. In the collection, task1 represents the top-level or parent task in the project, focusing on requirements and task11 represents a sub-task of task1. Similarly, task111, task112, task113, task114, task115 tasks further represent sub-tasks of task11. You can relate these sub tasks to their immediate parent task using the SubTasks property. Finally, relate all the sub tasks created with top level parent task, i.e., tasks, to complete the tree format and display these tasks in the FlexGrid control. Then, bind tasks with FlexGird using its ItemsSource property as shown in the following code snippet.
      Home.razor
      Copy Code
      @code {
          FlexGrid grid;
          List<ProjectTask> tasks;
      
          protected override async Task OnInitializedAsync()
          {
              //create tasks
              var task1 = new ProjectTask() { WBS = "1", Name = "Requirements", Duration = new TimeSpan(50, 0, 0, 0), Start = new DateTime(2009, 12, 4) };
              var task11 = new ProjectTask() { WBS = "1.1", Name = "Analysis", Duration = new TimeSpan(38, 3, 0, 0), Start = new DateTime(2009, 12, 4), ParentTask = task1 };
              var task111 = new ProjectTask() { WBS = "1.1.1", Name = "Analyze online reservations", Duration = new TimeSpan(12, 12, 0, 0), Start = new DateTime(2009, 12, 4), ParentTask = task11 };
              var task112 = new ProjectTask() { WBS = "1.1.2", Name = "Analyze query processes", Duration = new TimeSpan(12, 12, 0, 0), Start = new DateTime(2009, 12, 4), ParentTask = task11 };
              var task113 = new ProjectTask() { WBS = "1.1.3", Name = "Analyze multimedia enhancements", Duration = new TimeSpan(12, 12, 0, 0), Start = new DateTime(2010, 1, 4), ParentTask = task11 };
              var task114 = new ProjectTask() { WBS = "1.1.4", Name = "Draft Preliminary requirements", Duration = new TimeSpan(5, 0, 0, 0), Start = new DateTime(2010, 1, 14), ParentTask = task11 };
              var task115 = new ProjectTask() { WBS = "1.1.5", Name = "Review preliminary requirements", Duration = new TimeSpan(2, 12, 0, 0), Start = new DateTime(2010, 1, 14), ParentTask = task11 };
              task11.SubTasks = new List<ProjectTask> { task111, task112, task113, task114, task115 };
              var task12 = new ProjectTask() { WBS = "1.2", Name = "Acceptance Test Plan", Duration = new TimeSpan(12, 3, 0, 0), Start = new DateTime(2010, 6, 23), ParentTask = task1 };
              var task121 = new ProjectTask() { WBS = "1.2.1", Name = "Write acceptance test plans", Duration = new TimeSpan(5, 2, 0, 0), Start = new DateTime(2010, 6, 23), ParentTask = task12 };
              var task122 = new ProjectTask() { WBS = "1.2.2", Name = "Draft acceptance test plan", Duration = new TimeSpan(5, 0, 0, 0), Start = new DateTime(2010, 6, 23), ParentTask = task12 };
              task12.SubTasks = new List<ProjectTask> { task121, task122 };
              task1.SubTasks = new List<ProjectTask> { task11, task12 };
              var task2 = new ProjectTask() { WBS = "2", Name = "Design", Duration = new TimeSpan(55, 0, 0, 0), Start = new DateTime(2010, 8, 12) };
              var task21 = new ProjectTask() { WBS = "2.1", Name = "Top-level Design", Duration = new TimeSpan(27, 12, 0, 0), Start = new DateTime(2010, 8, 12), ParentTask = task2 };
              var task211 = new ProjectTask() { WBS = "2.1.1", Name = "Design online reservations", Duration = new TimeSpan(10, 0, 0, 0), Start = new DateTime(2010, 9, 7), ParentTask = task21 };
              var task212 = new ProjectTask() { WBS = "2.1.2", Name = "Design query processes", Duration = new TimeSpan(10, 12, 0, 0), Start = new DateTime(2010, 9, 14), ParentTask = task21 };
              var task213 = new ProjectTask() { WBS = "2.1.3", Name = "Design multimedia enhancements", Duration = new TimeSpan(10, 6, 0, 0), Start = new DateTime(2010, 10, 4), ParentTask = task21 };
              var task214 = new ProjectTask() { WBS = "2.1.4", Name = "Review design specification", Duration = new TimeSpan(5, 12, 0, 0), Start = new DateTime(2010, 10, 9), ParentTask = task21 };
              task21.SubTasks = new List<ProjectTask> { task211, task212, task213, task214 };
              var task22 = new ProjectTask() { WBS = "2.2", Name = "Detailed Design", Duration = new TimeSpan(23, 0, 0, 0), Start = new DateTime(2010, 12, 4), ParentTask = task2 };
              var task221 = new ProjectTask() { WBS = "2.2.1", Name = "Draft design specifications", Duration = new TimeSpan(17, 12, 0, 0), Start = new DateTime(2010, 12, 4), ParentTask = task22 };
              var task222 = new ProjectTask() { WBS = "2.2.2", Name = "Review design specifications", Duration = new TimeSpan(17, 0, 0, 0), Start = new DateTime(2010, 12, 8), ParentTask = task22 };
              var task223 = new ProjectTask() { WBS = "2.2.3", Name = "Incorporate feedback on design specifications", Duration = new TimeSpan(17, 6, 0, 0), Start = new DateTime(2010, 12, 14), ParentTask = task22 };
              task22.SubTasks = new List<ProjectTask> { task221, task222, task223 };
              task2.SubTasks = new List<ProjectTask> { task21, task22 };
              var task3 = new ProjectTask() { WBS = "3", Name = "Code and Unit Test", Duration = new TimeSpan(32, 4, 0, 0), Start = new DateTime(2010, 12, 4) };
              var task31 = new ProjectTask() { WBS = "3.1", Name = "Assign development staff", Duration = new TimeSpan(2, 3, 0, 0), Start = new DateTime(2010, 12, 4), ParentTask = task3 };
              var task32 = new ProjectTask() { WBS = "3.2", Name = "Develop Code", Duration = new TimeSpan(10, 3, 0, 0), Start = new DateTime(2010, 12, 4), ParentTask = task3 };
              var task321 = new ProjectTask() { WBS = "3.2.1", Name = "Develop online reservations", Duration = new TimeSpan(10, 2, 0, 0), Start = new DateTime(2010, 12, 4), ParentTask = task32 };
              var task322 = new ProjectTask() { WBS = "3.2.2", Name = "Test online reservations", Duration = new TimeSpan(1, 11, 0, 0), Start = new DateTime(2010, 12, 4), ParentTask = task32 };
              var task323 = new ProjectTask() { WBS = "3.2.3", Name = "Develop query processes", Duration = new TimeSpan(10, 0, 0, 0), Start = new DateTime(2010, 12, 4), ParentTask = task32 };
              var task324 = new ProjectTask() { WBS = "3.2.4", Name = "Test query processes", Duration = new TimeSpan(1, 4, 0, 0), Start = new DateTime(2010, 12, 4), ParentTask = task32 };
              task32.SubTasks = new List<ProjectTask> { task321, task322, task323, task324 };
              task3.SubTasks = new List<ProjectTask> { task31, task32 };
              tasks = new List<ProjectTask> { task1, task2, task3 };
          }
      
    3. Create a method with the name OnAutoGeneratingColumn to handle the configuration of columns in a grid used for displaying ProjectTask data. For example, the first property configured in the following code is WBS, which sets the width of the column to 90. Next, we configures the property named Duration, which sets up a value converter for the column. This converter formats TimeSpan values into a string representation of days with one decimal place and adds a question mark. It also converts back from strings to TimeSpan values. Finally, we add the column of type GridDateTimeColumn, which sets the date format to "ddd d/M/yyyy", for displaying dates in a specific format.
      Home.razor
      Copy Code
          private void OnAutoGeneratingColumn(object sender, C1.Blazor.Grid.GridAutoGeneratingColumnEventArgs e)
          {
              if (e.Property.Name == nameof(ProjectTask.Name))
              {
                  e.Column.AllowResizing = false;
                  e.Column.MinWidth = 300;
                  e.Column.Width = new GridLength(1, GridUnitType.Star);
              }
              if (e.Property.Name == nameof(ProjectTask.WBS))
                  e.Column.Width = 90;
              if (e.Property.Name == nameof(ProjectTask.Duration))
                  e.Column.ValueConverter = DelegateConverter.Create(
                      (value, type, parameter, culture) => string.Format("{0:N1} days?", ((TimeSpan)value).TotalDays),
                      (value, type, parameter, culture) =>
                      {
                          var str = value?.ToString() ?? "";
                          TimeSpan timeSpan;
                          if (TimeSpan.TryParse(str, out timeSpan))
                              return timeSpan;
                          if (str.EndsWith(" days?"))
                              str = str.Substring(0, str.Length - " days?".Length);
                          double totalDays;
                          if (double.TryParse(str, out totalDays))
                              return TimeSpan.FromDays(totalDays);
                          return TimeSpan.Zero;
                      });
              if (e.Column is GridDateTimeColumn)
                  e.Column.Format = "ddd d/M/yyyy";
          }      
      }
      
    4. Add the following code on the Home.razor page to bind the FlexGrid control with tasks collection using the ChildItemsPath and ItemsSource properties for displaying tasks in the hierarchical format:
      Home.razor
      Copy Code
      @using C1.Blazor.Grid
      @using C1.Blazor.Core
      @using FlexGrid_Tree.Data
      
      <FlexGrid @ref="grid" ChildItemsPath="SubTasks" TreeIndent="14" TreeColumnIndex="1"
                TreeExpandMode="GridTreeExpandMode.OnDemand" AutoGeneratingColumn="@OnAutoGeneratingColumn"
                ItemsSource="tasks" TreeIndentMode="GridTreeIndentMode.AllCells" TreeLinesMode="GridTreeLinesMode.Connected" Style="@("max-height:150vh")" />
      

    Sort and Filter

    Similar to FlexGrid, TreeGrid supports various operations at data level such as sorting and filtering. It uses the built-in column header menu of FlexGrid to perform sorting and filtering operations. Additionally, it allows you to sort the tree columns simply by clicking the column header cells. In TreeGrid, when you perform sorting operation, all the branches of the tree, i.e., all the parent and child nodes are sorted. Similarly, when you perform filtering operation on tree columns, the parent node remains visible if a data item or one of the child matches the filter.

    Sorting in TreeGrid Filtering in TreeGrid

    Style and Appearance

    FlexGrid offers different ways of styling and customizing the UI appearance of the TreeGrid control. It allows you to use different properties available in the FlexGrid class which lets you easily style visual aspects of the control and enhance its appearance.

    FlexGrid provides the following properties to customize the appearance and style the TreeLines of the TreeGrid control:

    The following image showcases how the TreeGrid appears after changing the color, thickness and pattern of the treelines.

    TreeGrid with styled treelines

    The following code demonstrates how to change the color, thickness and pattern of the treelines in the TreeGrid showcased above.

    Home.Razor
    Copy Code
    @using C1.Blazor.Grid
    @using C1.Blazor.Core
    @using FlexGrid_Tree.Data
    
    <FlexGrid @ref="grid" ChildItemsPath="SubTasks" TreeIndent="14" TreeColumnIndex="1"
              TreeExpandMode="GridTreeExpandMode.OnDemand" AutoGeneratingColumn="@OnAutoGeneratingColumn"
              ItemsSource="tasks" TreeIndentMode="GridTreeIndentMode.AllCells" TreeLinesMode="GridTreeLinesMode.Connected"
              TreeLinesBrush="C1Color.Blue" TreeLinesThickness="1" Style="@("max-height:150vh")" />