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 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)
:
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)
:
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.).
- I used this formula in two methods:
SetSelectedIndexes
and GetSelectedIndexes
.
public void SetSelectedIndexes(string indices)
{
ArrayList indicesArray = new ArrayList(indices.Split('.'));
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;
foundControl = item.FindControl("SelectorCheckBox");
CheckBox checkBox = foundControl as CheckBox;
if ( checkBox != null && checkBox.Checked )
selectedIndexList.Add( previousLevel + item.ItemIndex.ToString() );
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
.