3.4 Defining relations in the DataSet
DataSets offer the ability of adding relations between the different tables contained within it. The HierarGrid uses these relations to get from the parent table to its child data. Note that you have to define the relations prior to binding the DataSet to the HierarGrid.
The relations can be defined be specifying the key columns in both tables:
[VB]
Dim dc1 As DataColumn
Dim dc2 As DataColumn
'Relation Title => Author on Title_ID column
dc1 = ds.Tables(0).Columns("title_id")
dc2 = ds.Tables(1).Columns("title_id")
Dim dr As DataRelation = New DataRelation("Title_Author", dc1, dc2, False)
ds.Relations.Add(dr)
'Relation Title => Sales
dc1 = ds.Tables(0).Columns("title_id")
dc2 = ds.Tables(2).Columns("title_id")
dr = New DataRelation("Title_Sales", dc1, dc2, False)
ds.Relations.Add(dr)
[C#]
DataColumn dc1;
DataColumn dc2;
//Relation Title => Author on Title_ID column
dc1 = ds.Tables[0].Columns["title_id"];
dc2 = ds.Tables[1].Columns["title_id"];
DataRelation dr = new DataRelation("Title_Author", dc1, dc2, false);
ds.Relations.Add(dr);
//Relation Title => Sales
dc1 = ds.Tables[0].Columns["title_id"];
dc2 = ds.Tables[2].Columns["title_id"];
dr = new DataRelation("Title_Sales", dc1, dc2, false);
ds.Relations.Add(dr);
3.5 Adding the HierarColumn
The HierarGrid automatically adds a new column called HierarColumn to the grid at runtime if none is specified at design time. This column contains the image of the plus/minus icon and the content of the child templates invisibly.
This row can be created at design time similarly to how we define other BoundColumns or TemplateColumns without any further properties.
3.6 Creating a child template
Now comes the part of visually designing a template that is loaded to display the child row information. Please create a new UserControl by right-clicking the project and selection Add | Add new Item | Web User Control and give it the same name as the first child table (in my example it is called Authors.ascx and stored in the folder Templates underneath the project directory).
Drop a TextBox onto the form and choose the DataBindings property from the Property viewer. Select "Text" as the bindable property and choose to enter a custom databinding expression. Enter the following code:
[VB]
DataBinder.Eval(CType(Container, DataGridItem).DataItem, "au_lname")
[C#]
DataBinder.Eval(((DataGridItem)Container).DataItem, "au_lname")
This expression corresponds to the normal databinding syntax for one exception: The container object has to be cast to the type DataGridItem before the DataItem property can be accessed.
For each additional child table a new UserControl can be created.
3.7 Using the TemplateSelection event
Let’s take a look at what exactly happens internally when the DataBind method on the HierarGrid is called: As in the standard DataGrid, it is looped over the first table of the DataSource (or the one given in the DataMember attribute as shown in the sample code above). For each row in the table an object of the type DataGridItem is added to the Items collection of the DataGrid. When the DataGrid is rendered and sent to the client browser later in the page cycle (for further reference I recommend the Article Page cycle by Paul Wilson) each DataGridItem is converted into the HTML tag <TR>. This mechanism is extended in the HierarGrid so that after rendering one row of the parent table, the related tables are checked for child rows. If any child rows exist, their corresponding template is loaded and inserted into HierarGridColumn which is itself a child object of the DataGridItem.
To illustrate this, let’s take a look at the sample picture above (Fig. 1). In the standard DataGrid, separate rows are generated for the books with the IDs BU1032, BU1111 and BU2075. That is exactly the same with the HierarGrid except that after generating the row for book ID BU1032 the child tables Authors and Sales are checked for corresponding child rows. When we take a look at the book BU1111 we can see that two child rows in the Authors table and one entry in the Sales table were found that correspond to that book.
Now, to specify which UserControl should be loaded for each child row (the two in the Author table and the one in the Sales table), I decided to use a special event called TemplateSelection. This event is raised for each row in every child table. In the example mentioned above, this is three times for the book BU1111- and of course several more times for the other books as well.
In your sample main ASPX page, create an event handler as shown below.
[VB]
Private Sub HG1_TemplateSelection(ByVal sender As Object,
ByVal e As DBauer.Web.UI.WebControls.HierarGridTemplateSelectionEventArgs)
Handles HG1.TemplateSelection
e.TemplateFilename = "Templates\\" + e.Row.Table.TableName + ".ascx"
End Sub
[C#]
private void HG1_TemplateSelection(object sender,
HierarGridTemplateSelectionEventArgs e)
{
e.TemplateFilename = "Templates\\" + e.Row.Table.TableName + ".ascx";
}
In C# please add the following line to the InitializeComponent method to wire up the new event handler (note that this method is inside the region “Web Form Designer generated code”):
[C#]
this.HG1.TemplateSelection +=
new HierarGridTemplateSelectionEventHandler(this.HG1_TemplateSelection);
The TemplateSelection event has a parameter "e" which is of type HierarGridTemplateSelectionEventArgs. This type contains two properties that are important to understand.
The Row property contains the child row for which the event has been raised. Returning to the example, when the event is raised for the first time, the property contains exactly one row from the author table with data about Michael O’Leary.
Based on this data, the developer can decide which template to load. In this case the filename would be based on the name of the table i.e. "Templates\Authors.ascx".
This filename is sent back to the HierarGrid with the TemplateFilename property. This property initially doesn’t contain a value, and is reserved as the return value – it’s a workaround as event handlers do not support return values. The HierarGrid expects to find the filename of the template in this property which is then loaded internally using Page.LoadTemplate or Page.LoadControl (fur further reference please check the MSDN documentation).
This construct allows you to decide, based on any criteria, on the child row which the template should load. This can either be based on the table name as in the example, or could also be based on a value in a column or some other logical expression.
3.8 Changing the TemplateCachingBase property
Calling the TemplateSelection event for every single row of each child table would cause quite a performance hit. Therefore I decided to implement a caching option called TemplateCachingBase.
This enum property can either be configured to cache based on the tablename of the childrows. In the example above, the TemplateSelection event would be called once for the Authors table and once for the Sales table. The second option is to cache based on the value of a column which has to be specified in the TemplateCachingColumn property. This could be helpful if the subitems are not able to be distinguished by the table name but by a special column in the tables. For example if the Sales table contained a country column and for each country an individual template should be selected, the country column can be specified as the CachingColumn.The third option is to disable caching at all.
3.9 Displaying one template for all child rows
The sample described above relies on displaying a template for each child row. E.g. when a book has two authors and has been sold three times the total number of templates loaded is five. Imagine you want to change this to display a nested DataGrid for all the authors and a DataGrid for all the sales under book.
This can be achieved by setting the TemplateDataMode to "Table" instead of "SingleRow" (its default value). Doing so loads only one template per child table.
You could change the Authors.ASCX file described above by dropping a DataGrid instead of a TextBox. In the code-behind you can then use the normal DataBinding syntax to bind a DataSet to the grid. This DataSet of child rows can be taken from the BindingContainer property:
[VB]
Dim dgi as DataGridItem = CType(Me.BindingContainer, DataGridItem)
Dim ds as DataSet = CType(dgi.DataItem, DataSet)
DG1.DataSource = ds
DG1.DataMember = "Authors"
DG1.DataBind()
[C#]
DataSet ds = (DataSet) ((DataGridItem) this.BindingContainer).DataItem;
DG1.DataSource = ds;
DG1.DataMember = "Authors";
DG1.DataBind();
3.10 LoadTemplate vs LoadControl
I noticed slight differences with event processing between loading the template using Page.LoadTemplate and Page.LoadControl. This is why I decided to include a property called LoadControlMode to enable both possibilities. So, when experiencing problems with the one method try switching to the other.
4 Conclusion
This tutorial explains how to use the HierarGrid to display parent-child relations in a DataGrid-like control. It is important to remember to bind the data just like you would bind it to a standard datagrid and then use the TemplateSelection event to specify which template to load for which child row.
Additionally there are some properties in place to allow easy customization for the most common usage scenarios.
I hope this control will be a useful tool in one of your future web applications developed with ASP.NET, and that it will save you some time writing code.
If you have any feedback regarding the HierarGrid or this article, I’ll be happy if you let me know. My contact information is available at http://www.denisbauer.com. Updated information about the HierarGrid can also be found in my Weblog.
I want to thank Paolo Pignatelli for helping me write this article.
About the Author
|
|
Denis Bauer is a Software Design Engineer at Microsoft Germany’s Consulting Services where he is working on large-scale projects using leading-edge technology. Denis specializes on web application development with ASP.NET and the .NET Framework. Before joining Microsoft in 2001, he held leading positions in two new economy startup companies. |