In this article I will explain with an example, how to develop a multilevel nested GridView i.e. GridView with inner GridView and the inner GridView with another child GridView, thus creating a multilevel hierarchy of GridViews.
For this example I making use of Three (3) Levels of GridView.
Database
I’ll make use of Customers, Orders and Products Table of Microsoft’s Northwind Database which you can easily download and install using the link provided below.
HTML Markup
The HTML Markup contains a simple ASP.Net GridView with a child ASP.Net GridView of Orders consisting of another Child GridView of Products in the ItemTemplate of TemplateField of ASP.Net GridViews.
Note: I am storing CustomerID and OrderId in the DataKeys property of the GridView as that will be required to populate the Child or Nested GridView of Orders as well as Products.
<asp:GridView ID="gvCustomers" runat="server" AutoGenerateColumns="false" CssClass="Grid"
DataKeyNames="CustomerID">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:ImageButton ID="imgOrdersShow" runat="server" OnClick="Show_Hide_OrdersGrid" ImageUrl="~/images/plus.png"
CommandArgument="Show" />
<asp:Panel ID="pnlOrders" runat="server" Visible="false" Style="position: relative">
<asp:GridView ID="gvOrders" runat="server" AutoGenerateColumns="false" PageSize="5"
AllowPaging="true" OnPageIndexChanging="OnOrdersGrid_PageIndexChanging" CssClass="ChildGrid"
DataKeyNames="OrderId">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:ImageButton ID="imgProductsShow" runat="server" OnClick="Show_Hide_ProductsGrid" ImageUrl="~/images/plus.png"
CommandArgument="Show" />
<asp:Panel ID="pnlProducts" runat="server" Visible="false" Style="position: relative">
<asp:GridView ID="gvProducts" runat="server" AutoGenerateColumns="false" PageSize="2"
AllowPaging="true" OnPageIndexChanging="OnProductsGrid_PageIndexChanging" CssClass="Nested_ChildGrid">
<Columns>
<asp:BoundField ItemStyle-Width="150px" DataField="ProductId" HeaderText="Product Id" />
<asp:BoundField ItemStyle-Width="150px" DataField="ProductName" HeaderText="Product Name" />
</Columns>
</asp:GridView>
</asp:Panel>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField ItemStyle-Width="150px" DataField="OrderId" HeaderText="Order Id" />
<asp:BoundField ItemStyle-Width="150px" DataField="OrderDate" HeaderText="Date" />
</Columns>
</asp:GridView>
</asp:Panel>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField ItemStyle-Width="150px" DataField="ContactName" HeaderText="Contact Name" />
<asp:BoundField ItemStyle-Width="150px" DataField="City" HeaderText="City" />
</Columns>
</asp:GridView>
Namespaces
You will need to import the following namespaces.
C#
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
VB.Net
Imports System.Data
Imports System.Data.SqlClient
Imports System.Configuration
Binding the Customers records to the Level 1 GridView
Below is the code to bind the parent Level 1 ASP.Net GridView with the records of Customers table from the Northwind Database.
C#
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
gvCustomers.DataSource = GetData("select top 10 * from Customers");
gvCustomers.DataBind();
}
}
private static DataTable GetData(string query)
{
string constr = ConfigurationManager.ConnectionStrings["constr"].ConnectionString;
using (SqlConnection con = new SqlConnection(constr))
{
using (SqlCommand cmd = new SqlCommand())
{
cmd.CommandText = query;
using (SqlDataAdapter sda = new SqlDataAdapter())
{
cmd.Connection = con;
sda.SelectCommand = cmd;
using (DataSet ds = new DataSet())
{
DataTable dt = new DataTable();
sda.Fill(dt);
return dt;
}
}
}
}
}
VB.Net
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
If Not IsPostBack Then
gvCustomers.DataSource = GetData("select top 10 * from Customers")
gvCustomers.DataBind()
End If
End Sub
Private Shared Function GetData(query As String) As DataTable
Dim constr As String = ConfigurationManager.ConnectionStrings("constr").ConnectionString
Using con As New SqlConnection(constr)
Using cmd As New SqlCommand()
cmd.CommandText = query
Using sda As New SqlDataAdapter()
cmd.Connection = con
sda.SelectCommand = cmd
Using ds As New DataSet()
Dim dt As New DataTable()
sda.Fill(dt)
Return dt
End Using
End Using
End Using
End Using
End Function
Displaying the Child (Level 2) GridView with the Orders for each Customer in the Parent (Level 1) GridView
On the Click event of the Expand ImageButton of the Parent GridView, I am first searching the Child GridView in the corresponding GridView Row and then populating it with the records from the Orders table of the Northwind Database based on CustomerId stored in the DataKey property for the row to which the ImageButton belongs.
I have made use of the CommandArgument property of the ImageButton to determine whether the operation is of Expand or Collapse.
Also I have implemented Paging for the Child GridView using the OnOrdersGrid_PageIndexChanging event.
Note: GetData is a generic function and the same function discussed above is used here.
C#
protected void Show_Hide_OrdersGrid(object sender, EventArgs e)
{
ImageButton imgShowHide = (sender as ImageButton);
GridViewRow row = (imgShowHide.NamingContainer as GridViewRow);
if (imgShowHide.CommandArgument == "Show")
{
row.FindControl("pnlOrders").Visible = true;
imgShowHide.CommandArgument = "Hide";
imgShowHide.ImageUrl = "~/images/minus.png";
string customerId = gvCustomers.DataKeys[row.RowIndex].Value.ToString();
GridView gvOrders = row.FindControl("gvOrders") as GridView;
BindOrders(customerId, gvOrders);
}
else
{
row.FindControl("pnlOrders").Visible = false;
imgShowHide.CommandArgument = "Show";
imgShowHide.ImageUrl = "~/images/plus.png";
}
}
private void BindOrders(string customerId, GridView gvOrders)
{
gvOrders.ToolTip = customerId;
gvOrders.DataSource = GetData(string.Format("select * from Orders where CustomerId='{0}'", customerId));
gvOrders.DataBind();
}
protected void OnOrdersGrid_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
GridView gvOrders = (sender as GridView);
gvOrders.PageIndex = e.NewPageIndex;
BindOrders(gvOrders.ToolTip, gvOrders);
}
VB.Net
Protected Sub Show_Hide_OrdersGrid(sender As Object, e As EventArgs)
Dim imgShowHide As ImageButton = TryCast(sender, ImageButton)
Dim row As GridViewRow = TryCast(imgShowHide.NamingContainer, GridViewRow)
If imgShowHide.CommandArgument = "Show" Then
row.FindControl("pnlOrders").Visible = True
imgShowHide.CommandArgument = "Hide"
imgShowHide.ImageUrl = "~/images/minus.png"
Dim customerId As String = gvCustomers.DataKeys(row.RowIndex).Value.ToString()
Dim gvOrders As GridView = TryCast(row.FindControl("gvOrders"), GridView)
BindOrders(customerId, gvOrders)
Else
row.FindControl("pnlOrders").Visible = False
imgShowHide.CommandArgument = "Show"
imgShowHide.ImageUrl = "~/images/plus.png"
End If
End Sub
Private Sub BindOrders(customerId As String, gvOrders As GridView)
gvOrders.ToolTip = customerId
gvOrders.DataSource = GetData(String.Format("select * from Orders where CustomerId='{0}'", customerId))
gvOrders.DataBind()
End Sub
Protected Sub OnOrdersGrid_PageIndexChanging(sender As Object, e As GridViewPageEventArgs)
Dim gvOrders As GridView = TryCast(sender, GridView)
gvOrders.PageIndex = e.NewPageIndex
BindOrders(gvOrders.ToolTip, gvOrders)
End Sub
Displaying the Child (Level 3) GridView with the Products for each Order placed by a Customer in the Products Grid (Level 3) GridView
On the Click event of the Expand ImageButton of the Orders GridView, I am first searching the Products GridView in the corresponding GridView Row and then populating it with the records from the Products table of the Northwind Database based on OrderId stored in the DataKey property for the row to which the ImageButton belongs.
Same as above, I have made use of the CommandArgument property of the ImageButton to determine whether the operation is of Expand or Collapse.
Also as above I have implemented Paging for the Products GridView using the OnProductsGrid_PageIndexChanging event.
C#
protected void Show_Hide_ProductsGrid(object sender, EventArgs e)
{
ImageButton imgShowHide = (sender as ImageButton);
GridViewRow row = (imgShowHide.NamingContainer as GridViewRow);
if (imgShowHide.CommandArgument == "Show")
{
row.FindControl("pnlProducts").Visible = true;
imgShowHide.CommandArgument = "Hide";
imgShowHide.ImageUrl = "~/images/minus.png";
int orderId = Convert.ToInt32( (row.NamingContainer as GridView).DataKeys[row.RowIndex].Value);
GridView gvProducts = row.FindControl("gvProducts") as GridView;
BindProducts(orderId, gvProducts);
}
else
{
row.FindControl("pnlProducts").Visible = false;
imgShowHide.CommandArgument = "Show";
imgShowHide.ImageUrl = "~/images/plus.png";
}
}
private void BindProducts(int orderId, GridView gvProducts)
{
gvProducts.ToolTip = orderId.ToString();
gvProducts.DataSource = GetData(string.Format("SELECT ProductId, ProductName FROM Products WHERE ProductId IN (SELECT ProductId FROM [Order Details] WHERE OrderId = '{0}')", orderId));
gvProducts.DataBind();
}
protected void OnProductsGrid_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
GridView gvProducts = (sender as GridView);
gvProducts.PageIndex = e.NewPageIndex;
BindProducts(int.Parse(gvProducts.ToolTip), gvProducts);
}
VB.Net
Protected Sub Show_Hide_ProductsGrid(sender As Object, e As EventArgs)
Dim imgShowHide As ImageButton = TryCast(sender, ImageButton)
Dim row As GridViewRow = TryCast(imgShowHide.NamingContainer, GridViewRow)
If imgShowHide.CommandArgument = "Show" Then
row.FindControl("pnlProducts").Visible = True
imgShowHide.CommandArgument = "Hide"
imgShowHide.ImageUrl = "~/images/minus.png"
Dim orderId As Integer = Convert.ToInt32(TryCast(row.NamingContainer, GridView).DataKeys(row.RowIndex).Value)
Dim gvProducts As GridView = TryCast(row.FindControl("gvProducts"), GridView)
BindProducts(orderId, gvProducts)
Else
row.FindControl("pnlProducts").Visible = False
imgShowHide.CommandArgument = "Show"
imgShowHide.ImageUrl = "~/images/plus.png"
End If
End Sub
Private Sub BindProducts(orderId As Integer, gvProducts As GridView)
gvProducts.ToolTip = orderId.ToString()
gvProducts.DataSource = GetData(String.Format("SELECT ProductId, ProductName FROM Products WHERE ProductId IN (SELECT ProductId FROM [Order Details] WHERE OrderId = '{0}')", orderId))
gvProducts.DataBind()
End Sub
Protected Sub OnProductsGrid_PageIndexChanging(sender As Object, e As GridViewPageEventArgs)
Dim gvProducts As GridView = TryCast(sender, GridView)
gvProducts.PageIndex = e.NewPageIndex
BindProducts(Integer.Parse(gvProducts.ToolTip), gvProducts)
End Sub
Client Side Scripting
Finally some client side scripting using jQuery is required in order to create insert an HTML Row for displaying the Nested or Child GridView.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$("[id*=imgOrdersShow]").each(function () {
if ($(this)[0].src.indexOf("minus") != -1) {
$(this).closest("tr").after("<tr><td></td><td colspan = '999'>" + $(this).next().html() + "</td></tr>");
$(this).next().remove();
}
});
$("[id*=imgProductsShow]").each(function () {
if ($(this)[0].src.indexOf("minus") != -1) {
$(this).closest("tr").after("<tr><td></td><td colspan = '999'>" + $(this).next().html() + "</td></tr>");
$(this).next().remove();
}
});
});
</script>
CSS Classes for styling the GridViews
You can either place the following CSS classes on the page itself or in some CSS Class file.
<style type="text/css">
.Grid td
{
background-color: #A1DCF2;
color: black;
font-size: 10pt;
line-height: 200%;
}
.Grid th
{
background-color: #3AC0F2;
color: White;
font-size: 10pt;
line-height: 200%;
}
.ChildGrid td
{
background-color: #eee !important;
color: black;
font-size: 10pt;
line-height: 200%;
}
.ChildGrid th
{
background-color: #6C6C6C !important;
color: White;
font-size: 10pt;
line-height: 200%;
}
.Nested_ChildGrid td
{
background-color: #fff !important;
color: black;
font-size: 10pt;
line-height: 200%;
}
.Nested_ChildGrid th
{
background-color: #2B579A !important;
color: White;
font-size: 10pt;
line-height: 200%;
}
</style>
Screenshot
Demo
Downloads