Ask the DotNetJunkies : Building a Master/Detail DataGrid
By Doug Seven
Published: 10/4/2001
Reader Level: Intermediate
Rated: 5.00 by 6 member(s).
Tell a Friend
Rate this Article
Printable Version
Discuss in the Forums

Sample code Download

Question:

I have a question regarding DataGrid. Can I bind a column of a DataGrid to another DataGrid? I have a situation where I have to display a few details in the same DataGrid on click of a HyperLink column.
Thanks in advance.
Regards,
Prathibha Sharangapani

The Answer:

Prathibha, this can certainly be done. You are describing a master/detail DataGrid. For example, a DataGrid of customers with a column in each row that has an embdedded DataGrid of all the orders placed by that customer. The real challenge here is, how do we data bind a DataGrid that isn't created until run-time. You see, we cannot set the DataGrid's DataSource property, or invoke the DataGrid's DataBind() method, because the DataGrid doesn't exist at desing-time.

Every data bound control exposes an ItemDataBound event that we can handle. This event fires with each item that is data bound. In the case of a DataGrid, the ItemDataBound event fires for each DataGridItem (row) in the DataGrid. Since we can capture the ItemDataBound event for each row, we can capture values from the row, such as the CustomerID for the specified row, and dynamically create and add controls to the Controls collection of one of the columns (cells).

Let's Get It On

We will demonstrate building a master/detail DataGrid using the Northwind Customers and Orders tables. The sample code download also contains an orders to order detail master/detail DataGrid, and all of the sample code is in C# and Visual Basic .NET.

First, lets build the Web form user interface. For this we will use a DataGrid with BoundColumns and TemplateColumns. The result will look like this:

[Run Sample]

From the screenshot you can see that we have three columns, but actually there is acolumn that is hidden as the first column.

The first column is a BoundColumn with its Visible property set to False (to hide it). This column is bound to the CustomerID field in the data source. We will use this later to bind the orders DataGrid. The second column is a HyperLinkColumn, also bound to the CustomerID field, and set to link to the OrderDetailDataGrid.aspx page (see the code sample download). The third and fourth columns are TemplateColumns used to create a custom layout.

Here is the Web Form code:

<%@ Page language="c#" Inherits="MasterDetail.CustomerOrderDataGrid" EnableViewState="False" %>
<HTML>
<body
style="font: x-small Verdana, Arial, sans-serif;">
<!-- Begin Web Form -->
<form id="CustomerOrderDataGrid" method="post" runat="server">
  <p><a href="/DayOfDotNet/">Parent Directory</a></p>

  <!-- Begin DataGrid -->
  <asp:DataGrid id="CustomerDataGrid" runat="server"
    AutoGenerateColumns="False"
    CellPadding="2" CellSpacing="0"
    Font-Names="Verdana, Arial, sans-serif"
    BorderColor="Black" BorderWidth="1"
    GridLines="Horizontal"
    OnItemDataBound="CustomerDataGrid_OnItemDataBound">

    <HeaderStyle
      Font-Bold="True" Font-Size="small"
      Font-Name="Arial"
      BackColor="Maroon" ForeColor="White" />

    <ItemStyle Font-Size="x-small" />

    <AlternatingItemStyle BackColor="Tan" />

    <Columns>
      <asp:BoundColumn
        DataField="CustomerID" Visible="False" />

      <asp:HyperLinkColumn
        DataTextField="CustomerID"
        DataNavigateUrlField="CustomerID"
        DataNavigateUrlFormatString="OrderDetailDataGrid.aspx?customerid={0}"
        HeaderText="ID"
        ItemStyle-VerticalAlign="Top" />

      <asp:TemplateColumn ItemStyle-VerticalAlign="Top" HeaderText="Customer">
        <ItemTemplate>
          <b><%# DataBinder.Eval(Container.DataItem, "CompanyName") %></b><br>
          <%# DataBinder.Eval(Container.DataItem, "Address" ) %><br>
          <%# DataBinder.Eval(Container.DataItem, "City" ) %>,
          <%# DataBinder.Eval(Container.DataItem, "Region") %>
          <%# DataBinder.Eval(Container.DataItem, "PostalCode" ) %><br>
          <br>
          <%# DataBinder.Eval(Container.DataItem, "ContactName" ) %><br>
          <%# DataBinder.Eval(Container.DataItem, "ContactTitle" ) %><br>
          <%# DataBinder.Eval(Container.DataItem, "Phone" ) %>
        </ItemTemplate>
      </asp:TemplateColumn>

      <asp:TemplateColumn ItemStyle-VerticalAlign="Top"
        HeaderText="Orders">
          <%-- Embedded DataGrid will go here --%>
      </asp:TemplateColumn>
    </Columns>
  </asp:DataGrid>
<!-- End DataGrid -->
</form>
<!-- End Web Form -->
</body>
</HTML>

You can see, in the forth column I have added a server-side comment indicating where the embedded DataGrid will be placed.

Break It Down

In the code behind class for the Web Form we will do a couple of things: bind the CustomerDataGrid server control to the Customers table from the Northwind database; handle the CustomerDataGrid's ItemDataBound event, and dynamically create an OrdersDataGrid and load it into the fourth column Controls collection.

Lets start by retreiving the data for both the customers and orders in the Page_Load() event handler.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Configuration;

namespace
MasterDetail
{
  public class CustomerOrderDataGrid : System.Web.UI.Page
  {
    protected DataGrid CustomerDataGrid;
    private DataSet ds = new DataSet();
  
    private void Page_Load(object sender, System.EventArgs e)
    {
      string sqlStmt = "SELECT * FROM Customers; SELECT * FROM Orders";
      string conString = "server=localhost;database=Northwind;uid=sa;pwd=;";
  
      SqlDataAdapter sda = new SqlDataAdapter(sqlStmt, conString);
  
      sda.Fill(ds);
      ds.Tables[0].TableName = "Customers";
      ds.Tables[1].TableName = "Orders";
  
      CustomerDataGrid.DataSource = ds.Tables["Customers"];
      CustomerDataGrid.DataBind();
    }
  }
}

In the SQL statement we select two result sets. When we invoke the data adapter's Fill() method this will create two DataTables, one for each result set. For organizational (and anal retentive) purposes, we set the TableName property of each of the DataTables, and bind the CustomerDataGrid.

Notice, we declared the DataSet (ds) at the class-level. This will give us access to the DataSet from the OnItemDataBound event handler. In the OnItemDataBound event handler we need to dynamically construct a DataGrid, and bind it to only records in the Orders DataTable that have the same CustomerID value as the CustomerID for the current row (the value in the first column, which is hidden for display).

Lets take a look at the OnItemDataBound() event handler:

    //Use the OnItemDataBound event handler to dynamically add an embedded DataGrid
    protected void CustomerDataGrid_OnItemDataBound(object sender, DataGridItemEventArgs e)
    {
      //When each row is created in the DataGrid, eval the ItemType
      if(e.Item.ItemType == ListItemType.Item ||
        e.Item.ItemType == ListItemType.AlternatingItem)
      {
        //If the ItemType is Item or AlternatingItem,
        //Create a new DataGrid object named OrdersDataGrid

        DataGrid OrdersDataGrid = new DataGrid();
  
        //Format the DataGrid to look cool.
        OrdersDataGrid.BorderWidth = (Unit)1;
        OrdersDataGrid.CellPadding = 4;
        OrdersDataGrid.CellSpacing = 0;
        OrdersDataGrid.GridLines = GridLines.Horizontal;
        OrdersDataGrid.BorderColor = Color.FromName("Black");
  
        OrdersDataGrid.ItemStyle.Font.Name = "Verdana";
        OrdersDataGrid.ItemStyle.Font.Size = FontUnit.XSmall;
  
        OrdersDataGrid.AlternatingItemStyle.BackColor = Color.FromName("LightGray");
  
        OrdersDataGrid.ShowHeader = true;
        OrdersDataGrid.HeaderStyle.BackColor = Color.FromName("Black");
        OrdersDataGrid.HeaderStyle.ForeColor = Color.FromName("White");
        OrdersDataGrid.HeaderStyle.Font.Bold = true;
        OrdersDataGrid.HeaderStyle.Font.Size = FontUnit.XSmall;
  
        //Do not autogenerate columns.
        OrdersDataGrid.AutoGenerateColumns = false;

        //****Add a series of BoundColumns****//
        //***Order ID***//
        BoundColumn bc = new BoundColumn();
        //Set the BoundColumn Values
        bc.HeaderText = "Order ID";
        bc.DataField = "OrderID";
        bc.ItemStyle.Wrap = false;
        //Add the BoundColumn to the OrdersDataGrid.
        OrdersDataGrid.Columns.Add(bc);
  
        //***Order Date***//
        bc = new BoundColumn();
        bc.HeaderText = "Order Date";
        bc.DataField = "OrderDate";
        bc.DataFormatString="{0:d}";
        bc.ItemStyle.Wrap = false;
        OrdersDataGrid.Columns.Add(bc);
  
        //***Required Date***//
        bc = new BoundColumn();
        bc.HeaderText = "Required Date";
        bc.DataField = "RequiredDate";
        bc.DataFormatString="{0:d}";
        bc.ItemStyle.Wrap = false;
        OrdersDataGrid.Columns.Add(bc);
  
        //***Shipped Date***//
        bc = new BoundColumn();
        bc.HeaderText = "Shipped Date";
        bc.DataField = "ShippedDate";
        bc.DataFormatString="{0:d}";
        bc.ItemStyle.Wrap = false;
        OrdersDataGrid.Columns.Add(bc);
  
        //****End BoundColumns****//
  
        //Get the Authors DataView and filter it for the current ISBN
        DataView _orders = ds.Tables["Orders"].DefaultView;
        _orders.RowFilter = "CustomerID='" + e.Item.Cells[0].Text + "'";
  
        //Bind the DataGrid.
        OrdersDataGrid.DataSource = _orders;
        OrdersDataGrid.DataBind();
  
        //Add the OrdersDataGrid to the BooksDataGrid.
        e.Item.Cells[3].Controls.Add(OrdersDataGrid);
      }
    }

In this event handler, we construct a DataGrid in code, setting all of the properties we need to. Next we construct and add any columns we need. In this example, all of the columns are BoundColumn objects, but they could just as easily be HyperLinkColumns, ButtonColumns, or any of the other column data types. Once we define all of the columns and add them to the DataGrid, we bind the DataGrid to a filtered DataView. The DataView is the Orders DataTable, with a RowFilter set to the current CustomerID value. This will filter out any rows that don't have a matching CustomerID. Once we filter the DataView, we set the OrdersDataGrid's DataSource property, and data bind it. Now we have a populated DataGrid that can be added to the CustomerDataGrid's fourth column, in the Controls collection.

Summary

The DataGrid is probably the single most powerful server control in the ASP.NET tool box. Its flexibility enables granular control over the rendered layout, and the ability to handler the events of the DataGrid enable easy manipulation of the output, such as embedding other data bound controls.



Marketplace
(Sponsored Links)
What are the green links?
   



 
Copyright © 2007 CMP Tech LLC |
Privacy Policy (4/10/06) | Your California Privacy Rights (4/10/06) | Terms of Service | Advertising Info | About Us | Help