This tutorial will teach you how to place and bind a DataGrid inside another DataGrid. The binding of the inner DataGrid will be done depending on the row of the parent DataGrid.
In this tutorial we’re going to make use of ASP.NET 2.0 (and Visual Studio 2005), however the code works with ASP.NET 1.1 without any changes.
Below you can see an example of a DataGrid that has another DataGrid in one of its columns; also called intrinsic or inner DataGrids. We’re going to build something similar in this ASP.NET tutorial.
Start by creating the ASPX code needed for the DataGrids. In our example the first DataGrid (the container) is named dgParents while the child DataGrid is named dgChildren. The second DataGrid (dgChildren) will reside in one of DataGrid’s columns. Let’s see the code:
<asp:DataGrid ID="dgParents" runat="server" CellPadding="6" BorderWidth="0" AutoGenerateColumns="False" OnItemDataBound="dgParents_ItemDataBound">
<Columns>
<asp:BoundColumn DataField="CatID" HeaderText="Category ID"></asp:BoundColumn>
<asp:BoundColumn DataField="CatName" HeaderText="Category Name"></asp:BoundColumn>
<asp:TemplateColumn HeaderText="Children Categories">
<ItemTemplate>
<asp:DataGrid ID="dgChildren" runat="server" CellPadding="6" BorderWidth="0" AutoGenerateColumns="False">
<Columns>
<asp:BoundColumn DataField="CatID" HeaderText="Category ID "></asp:BoundColumn>
<asp:BoundColumn DataField="CatName" HeaderText="Category Name"></asp:BoundColumn>
</Columns>
</asp:DataGrid>
</ItemTemplate>
</asp:TemplateColumn>
</Columns>
</asp:DataGrid>
As you can see, we are first creating one DataGrid with 3 columns. The first two columns show two fields from the database, while the third is more special – it is a TemplateColumn column which contains the second DataGrid inside it, dgChildren.
Now you are probably wondering how can we access dgChildren and DataBind it. Well, when the parent DataGrid gets bounded to a data source, each time a new row is added, the OnItemDataBound event fires. That’s when we can access that row’s instance of dgChildren. If you look closely you will notice that we already defined the OnItemDataBound attribute (in bold) to point to the event in our source code.
But first, let’s switch to code view and bind the first DataGrid. You probably already have your own code for this, but here is something you could use as a sample:
SqlConnection SqlCon1 = new SqlConnection("Data Source=localhost;Initial Catalog=MyDatabase;Persist Security Info=True;User ID=sa;Password=1234");
SqlCommand SqlCom1 = new SqlCommand("SELECT CatID, CatName FROM Categories", SqlCon1);
if (SqlCon1.State != ConnectionState.Open)
{
SqlCon1.Open();
}
dgParents.DataSource = SqlCom1.ExecuteReader();
dgParents.DataBind();
SqlCon1.Close();
Nothing magic here, simply databinding the DataGrid control to the content of a SQL table. The magic happens when we go to the OnItemDataBound event of the parent DataGrid.
It would be very simple to just bind the inner DataGrid to a random data source in our tutorial, however in real-life you’ll probably not want that. Most of the time you’ll want to bind the inner DataGrid depending on the content of the container DataGrid’s row. For example if you are listing the categories of a directory in the main DataGrid, you’ll want to display subcategories (child categories) of each main category in the inner DataGrids. If this is stored in a database, you’ll want the inner DataGrid to be aware of what row it is in. For example if it is in the row displaying “Financial Services”, you’ll want to retrieve subcategories of “Financial Services” and display them using the inner DataGrid. To do that in our current sample we’re going to retrieve the CatID value from the parent DataGrid.
protected void dgParents_ItemDataBound(object sender, DataGridItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
SqlConnection SqlCon2 = new SqlConnection("Data Source=localhost;Initial Catalog=MyDatabase;Persist Security Info=True;User ID=sa;Password=1234");
SqlCommand SqlCom2;
int CatID = Convert.ToInt32(DataBinder.Eval(e.Item.DataItem, "CatID"));
DataGrid dgChildren = (DataGrid)e.Item.FindControl("dgChildren");
SqlCom2 = new SqlCommand("SELECT CatID, CatName FROM Categories WHERE CatParent = " + CatID, SqlCon2);
if (SqlCon2.State != ConnectionState.Open)
{
SqlCon2.Open();
}
dgChildren.DataSource = SqlCom2.ExecuteReader();
dgChildren.DataBind();
SqlCon2.Close();
}
}
The first thing we do inside ItemDataBound (event which fires each time a new row is added to the DataGrid) is to create a new SQL connection and SQL command object. However, please note that for the sake of simplicity, we’ve done that here, however in a real-life application you’ll probably look to optimize this, at least by declaring these objects outside of the event.
Then we check to see if the row is actually a row (ListItemType.Item) or alternating row (ListItemType.AlternatingItem), otherwise we’re not interested in the content of that row. Inside CatID we store the CatID value from the container (parent) DataGrid; as we discussed above, this will help us retrieve the subcategories, since we’ll pass this value into the SQL query.
On line number 9, another interesting thing happens. Since we can’t access the inner DataGrid directly, we need to declare it first and assign it to the inner DataGrid that we created inside the container DataGrid, and we do this by using the FindControl() method.
The next lines are typical for every data binding process: the query is executed and the parent category ID is passed so that children of these category are retrieved, the SQL connection is opened, and the command is executed.
This is all you need know for creating inner DataGrids. If you experience problems with adapting this with your own data source, look carefully through the code and after you completely grasp the code, using inner DataGrids will become a routine task. Furthermore, this code can be applied to other controls, such as the Repeater control.