Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

An article describing a recursive version of the DataGrid

4.42/5 (14 votes)
28 Oct 20054 min read 1   560  
This article explores yet another aspect of the versatile asp:DataGrid and shows how to use it to display and select recursive data.

The expected output in a webpage

Introduction

This article explores yet another aspect of the versatile asp:DataGrid and shows how to use it to display and select recursive data. The UI presented here is kinda close to that of a TreeView, but until the current version (1.1), the TreeView is not a standard control shipped with ASP.NET. Moreover I am not sure if the coming standard TreeView (ASP.NET 2.0) allows for selection of data.

The Problem

How often have you come across a hierarchical data that needs to be presented to the user and be selected by them? I recently stumbled upon such a requirement and in finding the solution to it, I explored a recursive flavor of the DataGrid. If simplified, the three objectives of this article are:

  • Explore how to set recursive data to a control so that it can be displayed in UI,
  • How to let the users Select/Deselect these data elements from UI, and,
  • How to get/set the selected status of these data elements programmatically.

The Data

The solution presented here is for recursive data only – where objects contain collection of objects of their own type.

The example I used here is the classic Employee->Manager one. An Employee may have a Team, which is nothing but a collection of one or more Employee objects.

The data

Other examples may be Item-Categories in a web-portal. A Category may have a Subcategories collection of one or more Category objects.

Solution - The ‘Recursive’ Grid

Since the very nature of the data is recursive, the solution that presents this data to the user has to be - well, recursive! Let's see how we come about meeting all the above mentioned objectives:

Set recursive data to this control

  • Refer to our definition of ‘recursive’ data: the objects contain collection of objects of their own type. The grid can be bound to this recursive-data using a simple pattern of ‘name’ and ‘collection’ properties. ‘name’ is the property that you want to be displayed with each selectable item. ‘collection’ is the property of the object that represents the collection of objects of their own type. In our example data above, Employee::Name denotes the ‘name’ property and Employee::Team denotes the ‘collection’ property.
  • The RecursiveGrid is able to consume this recursive data through its DataSource property.

Display the data recursively

  • The overridden ‘DataBind’ method is used to display the data recursively.
  • The Grid has a Template-column (RecursiveColumnTemplate) that adds a Label (to be bound to item-names) and a new RecursiveGrid (to be bound to item-collection) in the current cell.
  • The two bound controls – the Label and the new RecursiveGrid use their DataItem property and use Reflection to invoke the NamePropertyName and CollectionPropertyName:
    • The Label binds itself to InvokeMember(NamePropertyName):
      C#
      void BindLabelData(Object sender, EventArgs e)
      {
          if (_namePropertyName != string.Empty)
          {
              Label label = (Label)sender;
              object dataGridItem = 
                ((DataGridItem)(label).NamingContainer).DataItem;
              Type dataGridItemType = dataGridItem.GetType();
      
              label.Text = (string) dataGridItemType.InvokeMember(
                  _namePropertyName, 
                  System.Reflection.BindingFlags.GetProperty,
                  null,
                  dataGridItem,
                  null);
          }
      }
    • Similarly the RecursiveGrid control binds itself to InvokeMember(CollectionPropertyName):
      C#
      void BindGridData(Object sender, EventArgs e)
      {
          if (_collectionPropertyName != string.Empty)
          {
              RecursiveCheckedGrid grid = (RecursiveCheckedGrid)sender;
              object dataGridItem = 
                ((DataGridItem)(grid).NamingContainer).DataItem;
              Type dataGridItemType = dataGridItem.GetType();
      
              grid.DataSource = dataGridItemType.InvokeMember(
                  _collectionPropertyName, 
                  System.Reflection.BindingFlags.GetProperty,
                  null,
                  dataGridItem,
                  null);
          }
      }

Select/de-select these data elements from UI

  • To meet this requirement, I came up with RecursiveCheckedGrid. This (in addition to being a fancy term!) is only an extension of the concept of RecursiveGrid – it has a CheckBox cell with each RecursiveColumnTemplate column. This checkbox is meant to let users select/deselect items from the UI.
  • As you must have guessed, this new column is a Template too. It's called CheckBoxColumnTemplate.
  • So, our RecursivecheckedGrid has two template columns – one called CheckBoxColumnTemplate and the other called RecursiveColumnTemplate (the one with a Label and a RecursiveCheckedGrid).

Get/set the selected status of these data elements programmatically

  • What good is letting the user select from the grid if you can’t tell what they selected??
  • I have a recursive solution to this too (don’t you just love recursion?). If Sn denotes the list of selected elements in a grid (n) Sn can be derived by the following recursive formula:
    n = {n, S<SUB>children of Grid-n</SUB>}
    (if Grid-n has children)
    
    S<SUB>n</SUB> = {n}
    (if Grid-n has no children)
  • {} here denotes the list of numbers (n). Note that ‘n’ is an outlined number (0, 0.1, 1, 1.1.1, 2 etc.).

    The expected output in a webpage

  • I used this formula in two methods: SetSelectedIndexes and GetSelectedIndexes.
    C#
    public void SetSelectedIndexes(string indices)
    {
        ArrayList indicesArray = new ArrayList(indices.Split('.'));
    
        //find the Nth item.
        //N is represented by 0th position in the passed string
        DataGridItem item = Items[Convert.ToInt32(indicesArray[0])];
    
        if (item != null)
        {
            if (indicesArray.Count == 1)
            {
                Control foundControl = item.FindControl("SelectorCheckBox");
                CheckBox checkBox = foundControl as CheckBox;
                if ( checkBox != null )  
                    checkBox.Checked = true;
            }
            else
            {
                indicesArray.RemoveAt(0);
    
                Control foundControl = item.FindControl("RecursiveGrid");
                RecursiveCheckedGrid grid = foundControl as RecursiveCheckedGrid;
                if ( grid != null ) 
                    grid.SetSelectedIndexes(String.Join(".", 
                        (string[])indicesArray.ToArray(typeof(string))));
            }
        }
    }
    C#
    private string[] GetSelectedIndexes(string previousLevel)
    {
        ArrayList selectedIndexList = new ArrayList();
        foreach( DataGridItem item in Items ) 
        {
            Control foundControl;
    
            //check whether the current item is checked
            foundControl = item.FindControl("SelectorCheckBox");
            CheckBox checkBox = foundControl as CheckBox;
            if ( checkBox != null && checkBox.Checked )  
                selectedIndexList.Add( previousLevel + item.ItemIndex.ToString() );
    
                //recursively, check if the children are checked
                foundControl = item.FindControl("RecursiveGrid");
                RecursiveCheckedGrid grid = foundControl as RecursiveCheckedGrid;
                if ( grid != null ) 
                    selectedIndexList.AddRange(grid.GetSelectedIndexes
                        (previousLevel + item.ItemIndex.ToString() + "."));
        }
    
        return (string[])selectedIndexList.ToArray(typeof( string ) );
    }

To explore the sample...

  • Download the Code.zip (from the link above).
  • Copy the contents of the "Code" folder onto your local wwwroot location. Rename it is you want.
  • Make an IIS virtual-folder pointing to the folder you just created in wwwroot.
  • From the Visual Studio IDE, open the web-project from the wwwroot location.
  • Run!

To use the solution in your project...

All you'll need to use is the RecursiveCheckedGrid class. The three properties that you need to set to the RecursiveCheckedGrid are:

  • DataSource: this is the root data object.
  • NamePropertyName: this is the name of the property that denotes the ‘name’ property (that should be used to display with each data element).
  • CollectionPropertyName: this is the name of the property that denotes the ‘collection’ property.

Some extensions/modifications that can be done to this solution:

  • The SetSelectedIndexes and GetSelectedIndexes methods could be modified to return/take the objects that are selected, instead of their indexes.
  • In some situations it is desirable to have the child-objects selected automatically if the parent is selected. This can be achieved by using client-side scripting on the CheckBox column in the RecursiveCheckedGrid.

Epilogue

With this introduction to the solution, I hope you'll be able to use this grid in your projects. I welcome all your suggestions/feedback on this subject.

History

  • 13-Oct-2005: Created.
  • 20-Oct-2005: Added code for methods SetSelectedIndexes and GetSelectedIndexes.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here