An article describing a recursive version of the DataGrid






4.42/5 (14 votes)
Oct 13, 2005
4 min read

59073

560
This article explores yet another aspect of the versatile asp:DataGrid and shows how to use it to display and select recursive data.
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.
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 andEmployee::Team
denotes the ‘collection’ property. - The
RecursiveGrid
is able to consume this recursive data through itsDataSource
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 aLabel
(to be bound to item-names) and a newRecursiveGrid
(to be bound to item-collection) in the current cell. - The two bound controls – the
Label
and the newRecursiveGrid
use theirDataItem
property and use Reflection to invoke theNamePropertyName
andCollectionPropertyName
:- The
Label
binds itself toInvokeMember(NamePropertyName)
: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 toInvokeMember(CollectionPropertyName)
: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); } }
- The
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 ofRecursiveGrid
– it has aCheckBox
cell with eachRecursiveColumnTemplate
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 calledCheckBoxColumnTemplate
and the other calledRecursiveColumnTemplate
(the one with aLabel
and aRecursiveCheckedGrid
).
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, Schildren of Grid-n} (if Grid-n has children) Sn = {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.).
- I used this formula in two methods:
SetSelectedIndexes
andGetSelectedIndexes
.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)))); } } }
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
andGetSelectedIndexes
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 theRecursiveCheckedGrid
.
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
andGetSelectedIndexes
.