Embedding a Detail GridView Inside a Master GridView...
This article shows how to display a row of a master GridView and then display one or more rows of a detail GridView on an ASP.NET page.
I was recently asked to demonstrate a way to display a master-detail GridView whereby a the master GridView data was presented as a row of data, with the detail GridView displayed immediately below it as one or more rows of detailed data from the detail datasource. This is my attempt to answer that need.
Since you cannot create an embedded GridView at design-time, it must be done at runtime by dynamically adding the control and setting its properties. The best way to demonstrate the technique is get right to the code. The .aspx file (GridMasterDetail.aspx) is presented first below. The Northwind database provides a nice set of tables to use for our example. The Customers table is tied to the Orders table by the common column "CustomerID", while the Orders table and the Order Details table are tied together by the common column "OrderID". In the .aspx file we have included a DropDownList containing the list of Customers from the Customers table. The DataValueField of the DropDownList is the CustomerID. Upon choosing a Customer and then clicking the "Get Orders Data" button an OnClick event is called to get the Orders and Order Details data for that Customer. The master GridView in this case is the Orders table. All columns are shown in an html table forming our first row of Master data. We have to have a way of triggering the selection of Order Details data for the runtime building of the details GridView. For that we use the OnItemDataBound event. Most of the remainder of the code is just to make the grid look presentable.
|
<%@ Page Language="vb" Src="GridMasterDetail.aspx.vb" Inherits="DotNetJohn.GridMasterDetail"%> <html> <head> <title>GridMasterDetail</title> <style rel="StyleSheet"> .tablehead { font-family: Verdana, Arial, Helvetica, Sans-Serif; background-color:Teal; color:White; font-size: 10pt; } .tablebody { font-family: Verdana, Arial, Helvetica, Sans-Serif; background-color:Gainsboro; color:Black; font-size: 10pt; } </style> </head> <body> <form id="OrderDetailGridView" method="post" runat="server"> <asp:Label ID="lblCustomer" Text="Choose Customer: " Font-Bold="True" Runat="server" /> <asp:DropDownList ID="ddlCustomers" Runat="server" /> <asp:Button ID="btnGetData" OnClick="Get_Data" Text="Get Orders Data" Runat="server" /> <p></p> <asp:GridView id="dtgOrders" runat="server" AutoGenerateColumns="False" CellPadding="4" CellSpacing="0" Font-Names="Verdana, Arial, Helvetica, Sans-Serif" BorderColor="Black" BorderWidth="1" GridLines="Horizontal" OnItemDataBound="dtgOrders_OnItemDataBound"> <HeaderStyle Font-Bold="True" Font-Size="x-small" Font-Name="Verdana" BackColor="AliceBlue" ForeColor="Black" /> <ItemStyle Font-Size="x-small" /> <AlternatingItemStyle BackColor="Ivory" /> <Columns> <asp:BoundColumn DataField="OrderID" visible="False" /> <asp:TemplateColumn ItemStyle-VerticalAlign="Top" HeaderText="Order Information" HeaderStyle-HorizontalAlign="Center"> <ItemTemplate> <table width="100%" cellpadding="4" cellspacing="0"> <tr class="tablehead"> <th align="left">Order ID</th> <th align="left">Customer ID</th> <th align="left">Order Date</th> <th align="left">Required Date</th> <th align="left">Shipped Date</th> </tr> <tr class="tablebody"> <td align="left" valign="top"> <%# DataBinder.Eval(Container.DataItem, "OrderID") %> </td> <td align="left" valign="top"> <%# DataBinder.Eval(Container.DataItem, "CustomerID") %> </td> <td align="left" valign="top"> <%# DataBinder.Eval(Container.DataItem, "OrderDate", "{0:d}") %> </td> <td align="left" valign="top"> <%# DataBinder.Eval(Container.DataItem, "RequiredDate", "{0:d}" ) %> </td> <td align="left" valign="top"> <%# DataBinder.Eval(Container.DataItem, "ShippedDate", "{0:d}") %> </td> </tr> </table> <!-- Order Details GridView goes here --> </ItemTemplate> </asp:TemplateColumn> </Columns> </asp:GridView> </form> </body> </html> |
As usual, the code-behind file contains most of the interesting code. It is displayed in two sections below. The only thing going on in the Page_Load sub is the loading of the DropDownList of customers from the Customers table. The Get_Data subroutine is our button click event from the .aspx page. It gets both the Orders table for the selected customer and the Order Details data and fills a DataSet with the two tables. Notice that all Order Details data is being retrieved. We will later have to filter that data so that it can be displayed for each Order table data row it belongs to. The master GridView's (dtgOrders) datasource is then set to the Orders table of the DataSet and the GridView is bound.
|
Imports System.Data Imports System.Data.SqlClient Imports System.Drawing Imports System.Web.UI.WebControls Imports System.Configuration Namespace DotNetJohn Public Class GridMasterDetail : Inherits System.Web.UI.Page Protected dtgOrders As GridView Protected ddlCustomers As DropDownList Private _dataSet As DataSet = New DataSet() Private strCustID As String Public Sub Page_Load(sender As System.Object, e As System.EventArgs) If Not IsPostBack Then Dim objConn As SqlConnection Dim objCmd As SqlCommand Dim dataReader As SqlDataReader Dim strSql As String objConn = New SqlConnection(ConfigurationSettings.AppSettings.Get("NorthwindConnection")) strSql = "SELECT CustomerID, CompanyName FROM Customers ORDER BY CompanyName" objCmd = New SqlCommand(strSql, objConn) Try objConn.Open() dataReader = objCmd.ExecuteReader() With ddlCustomers .DataSource = dataReader .DataTextField = "CompanyName" .DataValueField = "CustomerID" .DataBind() End With Catch End Try End If End Sub Public Sub Get_Data(sender As object, e As System.EventArgs) strCustID = ddlCustomers.SelectedItem.Value Dim strSql As String strSql = "SELECT * FROM Orders WHERE CustomerID='" & strCustID & "'; " _ & "SELECT OrderID, ProductID, UnitPrice, " _ & "Quantity, Discount, UnitPrice * " _ & "(1 - Discount) * Quantity AS SubTotal " _ & "FROM [Order Details]" _ Dim strConn As String = ConfigurationSettings.AppSettings.Get("NorthwindConnection") Dim _dataAdapter As SqlDataAdapter = New SqlDataAdapter(strSql, strConn) _dataAdapter.Fill(_dataSet) _dataSet.Tables(0).TableName = "Orders" _dataSet.Tables(1).TableName = "OrderDetails" dtgOrders.DataSource = _dataSet.Tables("Orders") dtgOrders.DataBind() End Sub |
The remainder of the code-behind file consists of code to create and embed the details GridView containing the Order Details table data. As stated previously we are using the OnItemDataBound event to create each details GridView. Each time (row) the master GridView is bound, the OnItemDataBound event is called and we create our details grid. In the third line of code below we create the details GridView. The next 15 or so lines of code set properties of the new grid. After that there is a long section of code where we create bound columns for each of the columns in the Order Details table. We then create a DataView using the OrderID as a filter. The OrderID comes from the invisible BoundColumn we included on the .aspx page. After that we set the details GridView's datasource to the dataview and bind it. Lastly we add the the details GridView as the second cell of the master and we are done.
|
Protected Sub dtgOrders_OnItemDataBound(sender As Object, e As GridViewItemEventArgs) If e.Item.ItemType = ListItemType.Item Or e.Item.ItemType = ListItemType.AlternatingItem Then Dim dtgOrderDetails As GridView = New GridView() dtgOrderDetails.Width = Unit.Pixel(720) dtgOrderDetails.BorderWidth = Unit.Pixel(1) dtgOrderDetails.CellPadding = 2 dtgOrderDetails.CellSpacing = 0 dtgOrderDetails.GridLines = GridLines.Horizontal dtgOrderDetails.BorderColor = Color.FromName("Black") dtgOrderDetails.HeaderStyle.BackColor = Color.FromName("Black") dtgOrderDetails.HeaderStyle.ForeColor = Color.FromName("White") dtgOrderDetails.HeaderStyle.Font.Bold = True dtgOrderDetails.HeaderStyle.Font.Size = FontUnit.XSmall dtgOrderDetails.ItemStyle.Font.Name = "Verdana" dtgOrderDetails.ItemStyle.Font.Size = FontUnit.XSmall dtgOrderDetails.AlternatingItemStyle.BackColor = Color.FromName("Gainsboro") dtgOrderDetails.AutoGenerateColumns = False Dim _boundColumn As BoundColumn = New BoundColumn() 'Order ID _boundColumn.HeaderText = "Order ID" _boundColumn.DataField = "OrderID" dtgOrderDetails.Columns.Add(_boundColumn) 'Product ID _boundColumn = New BoundColumn() _boundColumn.HeaderText = "Product ID" _boundColumn.DataField = "ProductID" dtgOrderDetails.Columns.Add(_boundColumn) 'Unit Price _boundColumn = New BoundColumn() _boundColumn.HeaderText = "Unit Price" _boundColumn.DataField = "UnitPrice" _boundColumn.DataFormatString="{0:c}" dtgOrderDetails.Columns.Add(_boundColumn) 'Quantity _boundColumn = New BoundColumn() _boundColumn.HeaderText = "Quantity" _boundColumn.DataField = "Quantity" dtgOrderDetails.Columns.Add(_boundColumn) 'Discount _boundColumn = New BoundColumn() _boundColumn.HeaderText = "Discount" _boundColumn.DataField = "Discount" _boundColumn.DataFormatString="{0:p}" dtgOrderDetails.Columns.Add(_boundColumn) 'SubTotal _boundColumn = New BoundColumn() _boundColumn.HeaderText = "SubTotal" _boundColumn.DataField = "SubTotal" _boundColumn.DataFormatString="{0:c}" _boundColumn.HeaderStyle.HorizontalAlign = HorizontalAlign.Right _boundColumn.ItemStyle.HorizontalAlign = HorizontalAlign.Right dtgOrderDetails.Columns.Add(_boundColumn) Dim _dataView As DataView = _dataSet.Tables("OrderDetails").DefaultView _dataView.RowFilter = "OrderID='" & e.Item.Cells(0).Text & "'" 'Bind the GridView. dtgOrderDetails.DataSource = _dataView dtgOrderDetails.DataBind() 'Add the dtgOrderDetails GridView e.Item.Cells(1).Controls.Add(dtgOrderDetails) End If End Sub End Class End Namespace |
You may download the code here.