Introduction
This article explains how to make an ASP.NET editable nested GridView. Here I am going to explain how to provide all the features of Edit/Add/Delete/Update/Page/Sort of a GridView, and not only one GridView, but I am going to explain how to extend these features to even nested grids (GridView inside GridView). I have provided the fully functional source code, which is self-explanatory.
Background
My previous article explains about the features of nested editable DataGrids. This article provides all those features in a GridView (Visual Studio 2005, .NET 2.0). With the combination of GridView and DataSource controls, it's very easy to create an editable GridView.
Using the Code
This is a web application with a virtual directory named EditNestedGridView. You may create the same virtual directory to run this application with Visual Studio .NET 2005, or create any other virtual directory and map the path to this directory to access it from the browser. As I have used Access 2003 as my database, you need to have Microsoft Access installed on your machine. I have used the NorthWind database with some modifications. I have also included this in the code bundle under the App_Data folder. I have used C# for the code-behind files. In Visual Studio 2005, File --> Open --> Web Site, and navigate to the folder EditNestedGridView.
Step-by-Step Procedure
The code attached is self-explanatory, but I will try to explain it as much as possible.
- Since here I am going to use the
DataSource
control, let's first create an AccessDataSource
control as below:
<asp:AccessDataSource ID="AccessDataSource1" runat="server"
DataFile="App_Data/Northwind.mdb"
SelectCommand="SELECT [Customers].[CustomerID],
[Customers].[CompanyName],[Customers].[ContactName],
[Customers].[ContactTitle],[Customers].[Address] FROM [Customers]
ORDER BY [Customers].[CustomerID]"></asp:AccessDataSource>
The main advantage of the DataSource
control is that it simplifies the amount of custom code that needs to be written to retrieve and bind data, and even to sort, page through, or edit data.
- Now, let's create a simple
GridView
control and attach the previously created DataSource
control as shown below:
<asp:GridView ID="GridView1" AllowPaging="True" BackColor="#f1f1f1"
AutoGenerateColumns=false DataSourceID="AccessDataSource1"
DataKeyNames="CustomerID"
style="Z-INDEX: 101; LEFT: 8px; POSITION: absolute; TOP: 32px"
ShowFooter=true Font-Size=Small
Font-Names="Verdana" runat="server" GridLines=None BorderStyle=Outset>
GridView
is very similar to DataGrid
and it has all those column features of DataGrid
like TemplateColumn
, BoundColumn
to populate data as columns. EditItemTemplate
and FooterTemplate
can be used for editing and adding purposes. The following code shows the columns of the parent grid:
<asp:TemplateField HeaderText="Customer ID" SortExpression="CustomerID">
<ItemTemplate>
<asp:Label ID="lblCustomerID" Text='<%# Eval("CustomerID") %>'
runat="server"></asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:Label ID="lblCustomerID" Text='<%# Eval("CustomerID") %>'
runat="server"></asp:Label>
</EditItemTemplate>
<FooterTemplate>
<asp:TextBox ID="txtCustomerID" Text='' runat="server"></asp:TextBox>
</FooterTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Company Name" SortExpression="CompanyName">
<ItemTemplate><%# Eval("CompanyName") %></ItemTemplate>
<EditItemTemplate>
<asp:TextBox ID="txtCompanyName" Text='<%# Eval("CompanyName") %>'
runat="server"></asp:TextBox>
</EditItemTemplate>
<FooterTemplate>
<asp:TextBox ID="txtCompanyName" Text='' runat="server"></asp:TextBox>
</FooterTemplate>
</asp:TemplateField>
..........................
..........................
<asp:CommandField HeaderText="Edit" ShowEditButton="True" />
<asp:TemplateField HeaderText="Delete">
<ItemTemplate>
<asp:LinkButton ID="linkDeleteCust" CommandName="Delete"
runat="server">Delete</asp:LinkButton>
</ItemTemplate>
<FooterTemplate>
<asp:LinkButton ID="linkAddCust" CommandName="AddCustomer"
runat="server">Add</asp:LinkButton>
</FooterTemplate>
</asp:TemplateField>
Now that we have all the columns in place, we need the event handlers to take care of the Add/Edit/Delete actions. Handling these events is very straightforward compared to that for the DataGrid
. The DataSource
control takes care of the paging and sorting actions. Here is the final parent GridView
control with all these events.
<asp:GridView ID="GridView1" AllowPaging="True" BackColor="#f1f1f1"
AutoGenerateColumns=false DataSourceID="AccessDataSource1"
DataKeyNames="CustomerID"
style="Z-INDEX: 101; LEFT: 8px; POSITION: absolute; TOP: 32px"
ShowFooter=true Font-Size=Small
Font-Names="Verdana" runat="server" GridLines=None
OnRowDataBound="GridView1_RowDataBound"
OnRowCommand = "GridView1_RowCommand"
OnRowUpdating = "GridView1_RowUpdating" BorderStyle=Outset
OnRowDeleting = "GridView1_RowDeleting"
OnRowDeleted = "GridView1_RowDeleted"
OnRowUpdated = "GridView1_RowUpdated" AllowSorting=true>
With all the above options, the GridView
looks like:
- Until now, we have created a parent
GridView
. Now we are going to extend these features in a child GridView
as well. Before adding another GridView
, we must understand how GridView
emits its content as HTML tags. For an Internet Explorer browser, GridView
is like a regular Table
with TR
and TD
tags. So, if we can manipulate the parent GridView
to forcibly close a row and emit a child GridView
as another row, we are done with it.
What is displayed is some HTML table cell and row tags that effectively intercept the current output that will be generated by the GridView
with our own special implementation. Namely, we are telling the table (when it's drawn) to close the current cell, the current row, and now add a new row with one blank column (for spacing) and another column (spanning all columns) that we'll use to display a second GridView
. Here is the piece of code which explains this:
<asp:TemplateField>
<ItemTemplate>
<tr>
<td colspan="100%">
<div id="div<%# Eval("CustomerID") %>"
style="display:none;position:relative;left:15px;OVERFLOW: auto;WIDTH:97%" >
Here, I have created a division id="div Eval("CustomerID")
with an ID of Customer (parent row) which holds the child GridView
so that we can dynamically hide and expand it by using the DIV
ID. Also, we need a column in the main GridView
which holds the image to expand and collapse the child GridView
as below:
<asp:TemplateField>
<ItemTemplate>
<a href="javascript:expandcollapse('div<%# Eval("CustomerID") %>', 'one');">
Here, the JavaScript function expandcollapse
will take care of the expand and collapse action.
- In order to bind the child
GridView
, we can't use the static DataSource
control as we have used for the parent GridView
. We will have to dynamically prepare the query based on the customer ID of the corresponding parent row, and that we can do it in the RowDataBound
event of the parent grid as below:
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
GridViewRow row = e.Row;
string strSort = string.Empty;
if (row.DataItem == null)
{
return;
}
GridView gv = new GridView();
gv = (GridView)row.FindControl("GridView2");
gv.DataSource =
ChildDataSource(((DataRowView)e.Row.DataItem)["CustomerID"].ToString(),
strSort);
gv.DataBind();
}
Here, the ChildDataSource
function forms the query using the passed customer ID and returns the AccessDataSource
.
- Now, we have data in the child grid as well, with dynamic expand and collapse. Let's add the effects one by one as we did for the parent grid. However, before going further, there is a little tricky part involved. In the case of the parent grid, there is only a unique ID (
GridView1
), and in the case of the child, there will be several unique IDs that get generated at run time. If we can identify the one which we need, that will solve this problem.
Paging and sorting were taken care of by the DataSource
control in the case of the parent grid. For the child grid, we have to take care of it manually using the corresponding events PageIndexChanging
and Sorting
. A sample code of the paging event looks like:
protected void GridView2_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
GridView gvTemp = (GridView)sender;
gvUniqueID = gvTemp.UniqueID;
gvNewPageIndex = e.NewPageIndex;
GridView1.DataBind();
}
Here, we have identified the run time unique ID of the child grid which needs paging along with the new page number. We are going to use these variable values in the RowDataBound
event of the parent grid, as below:
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
........................
GridView gv = new GridView();
gv = (GridView)row.FindControl("GridView2");
if (gv.UniqueID == gvUniqueID)
{
gv.PageIndex = gvNewPageIndex;
gv.EditIndex = gvEditIndex;
if (gvSortExpr != string.Empty)
{
GetSortDirection();
strSort = " ORDER BY " + string.Format("{0} {1}", gvSortExpr, gvSortDir);
}
ClientScript.RegisterStartupScript(GetType(), "Expand",
"<script language="javascript"></script>");
}
..........................
}
The remaining actions (Sorting/Edit/Update/Delete) can also be handled similarly to paging. Refer to the source code attached above.
Conclusion
The GridView
has more features compared to the DataGrid
, which will make it easier to code, and also, we can include HTML tags in between for more flexibility.