Professional Documents
Culture Documents
Page 1 of 9
Average Rating:
Views (Total / Last 10 Days): 206744/ 767
The ASP.NET DataGrid is a popular control for displaying tabular data, with editing,
paging and sorting capabilities. However, this is only suitable for displaying single
DataTables - there is no support for master-detail views using parent-child relations.
I have created a custom control called HierarGrid (that derives from the standard
ASP.NET DataGrid) and a custom DataGridColumn called HierarColumn.
The HierarGrid takes a DataSet containing relations between tables as its DataSource.
While iterating over the parent table, it checks the related tables for child rows, and if
one is found it dynamically loads a template for the child row(s).
The template is rendered invisibly into the custom HierarColumn and when the user
clicks the plus icon, the template's content is copied via JavaScript into a newly created
TableRow.
http://aspalliance.com/articleViewer.aspx?aId=21&pId=-1 11/26/2009
HierarGrid: A Hierarchical DataGrid that Displays Master-Detail Relationships: ASP Alli... Page 2 of 9
Please also note the demo that I created, which can be downloaded from the same
location. The following explanations are mainly based on, and refer to this demo.
The .NET Framework offers multiple ways to load data into a DataSet. One possible
way is to use a SQLCommand to run a SQL query against a table in MS SQL Server. In
my example, I used the "Pubs" database, a sample Database of Microsoft SQL Server. I
created three SELECT statements that returned a list of publications, the associated
authors and the sales in separate tables. The resulting DataSet can be written to a XML
file using the method ds.WriteXml. The XML file is contained in the sample application
as Testdata.XML, which can be downloaded from the link above.
To load the sample data yourself add two Views and a stored procedure into the "Pubs"
database with the following three SQL statements:
http://aspalliance.com/articleViewer.aspx?aId=21&pId=-1 11/26/2009
HierarGrid: A Hierarchical DataGrid that Displays Master-Detail Relationships: ASP Alli... Page 3 of 9
SELECT *
FROM VwAuthors
WHERE exists (Select * from Titles where au_id = VwAuthors.au_id)
ORDER BY title_id
SELECT *
FROM VwSales
WHERE exists (Select * from Titles where title_id = VwSales.title_id)
To load the data into a DataSet create a new WebApplication and add the following
code into the Page_Load event:
[VB]
Dim sqlConnection As SqlConnection = New SqlConnection()
Dim sqlCommand As SqlCommand = New SqlCommand()
sqlConnection.ConnectionString =
"data source=(local);initial catalog=pubs;integrated security=SSPI;"
sqlCommand.CommandText = "[TestData]"
sqlCommand.CommandType = System.Data.CommandType.StoredProcedure
sqlCommand.Connection = sqlConnection
Dim ds As DataSet = New DataSet()
sqlConnection.Open()
Dim adap As SqlDataAdapter = New SqlDataAdapter(sqlCommand)
adap.Fill(ds)
sqlConnection.Close()
ds.Tables(0).TableName = "Titles"
ds.Tables(1).TableName = "Authors"
ds.Tables(2).TableName = "Sales"
[C#]
SqlConnection sqlConnection = new SqlConnection();
SqlCommand sqlCommand = new SqlCommand();
sqlConnection.ConnectionString =
"data source=(local);initial catalog=pubs;integrated security=SSPI;";
sqlCommand.CommandText = "[TestData]";
sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
sqlCommand.Connection = sqlConnection;
DataSet ds = new DataSet();
sqlConnection.Open();
SqlDataAdapter adap = new SqlDataAdapter(sqlCommand);
adap.Fill(ds);
sqlConnection.Close();
ds.Tables[0].TableName = "Titles";
ds.Tables[1].TableName = "Authors";
ds.Tables[2].TableName = "Sales";
Alternatively to the steps mentioned in the section above, you can download the
http://aspalliance.com/articleViewer.aspx?aId=21&pId=-1 11/26/2009
HierarGrid: A Hierarchical DataGrid that Displays Master-Detail Relationships: ASP Alli... Page 4 of 9
sample project and use the Testdata.XML file. This file can be loaded into a DataSet
and bound to a standard DataGrid with only a few lines of code.
If you had not already done in step 3.1 create a new WebApplication and drop a new
DataGrid from the toolbar onto the webform and call it DG1. Otherwise skip the next
few lines and continue with binding your retrieved DataSet to the DataGrid.
The first step is to load the data from the sample file into a DataSet or to use the
DataSet generated with the code above:
[VB]
Dim ds as DataSet = new DataSet()
ds.ReadXml(AppDomain.CurrentDomain.BaseDirectory + "\\Testdata.xml")
[C#]
DataSet ds = new DataSet();
ds.ReadXml(AppDomain.CurrentDomain.BaseDirectory + @"\Testdata.xml");
Then the DataSet can be bound to a standard DataGrid that has been dropped on a
webform:
[VB]
DG1.DataSource = ds
DG1.DataMember = "Titles"
DG1.DataBind()
[C#]
DG1.DataSource = ds;
DG1.DataMember = "Titles";
DG1.DataBind();
The procedure so far is the normal way to bind relational data from a DataSet to an
ASP.NET DataGrid and can be looked up in the .NET Framework documentation and
Quickstart tutorials. Following are the changes described the HierarGrid requires to
display parent-child relations.
One goal when developing the HierarGrid was to enable developers to switch
seamlessly between the standard DataGrid view without parent-child relations and the
detailed view with the HierarGrid.
Second, add the following declaration to the top of the ASPX file:
Now, you just have to change the declaration in the ASPX file from
to
http://aspalliance.com/articleViewer.aspx?aId=21&pId=-1 11/26/2009
HierarGrid: A Hierarchical DataGrid that Displays Master-Detail Relationships: ASP Alli... Page 5 of 9
[VB]
Protected WithEvents HG1 As DBauer.Web.UI.WebControls.HierarGrid
[C#]
protected DBauer.Web.UI.WebControls.HierarGrid HG1;
Restarting your application should show that nothing has changed so far. The
HierarGrid behaves just like the normal DataGrid does.
Using the HierarGrid - Part II
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)
[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);
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
http://aspalliance.com/articleViewer.aspx?aId=21&pId=-1 11/26/2009
HierarGrid: A Hierarchical DataGrid that Displays Master-Detail Relationships: ASP Alli... Page 6 of 9
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.
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.
http://aspalliance.com/articleViewer.aspx?aId=21&pId=-1 11/26/2009
HierarGrid: A Hierarchical DataGrid that Displays Master-Detail Relationships: ASP Alli... Page 7 of 9
[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 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.
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
http://aspalliance.com/articleViewer.aspx?aId=21&pId=-1 11/26/2009
HierarGrid: A Hierarchical DataGrid that Displays Master-Detail Relationships: ASP Alli... Page 8 of 9
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.
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.
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();
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
http://aspalliance.com/articleViewer.aspx?aId=21&pId=-1 11/26/2009
HierarGrid: A Hierarchical DataGrid that Displays Master-Detail Relationships: ASP Alli... Page 9 of 9
Product Spotlight
Kindle 2: New Wireless Reading Device
Lighter than a paperback, but holds over 1500 books. 3G wireless - no fees or
service plans. Improved display reads like real paper. Learn more
My SQL Problems?
Find bad queries and tables. Improve
performance. Free download.
www.jetprofiler.com
Exceptioneer
A new service to handle .NET exceptions
www.exceptioneer.com
COBOL Modernization
Maintain and modernize COBOL while
reducting TCO
veryant.com
http://aspalliance.com/articleViewer.aspx?aId=21&pId=-1 11/26/2009