http://www.developer.com/

Back to article

Managing Complex Layout and Nesting DataGrids in ASP.NET


June 18, 2003

This article follows up on the article, Using Nested DataGrids in ASP.NET.

Introduction

The DataList, DataGrid, Repeater, and plain old HTML can be used to create some advanced presentations in ASP.NET. In this article—a continuation of a three-part article—I will demonstrate how to intersperse HTML table rows and cells across DataList templates to intimately manage the appearance of a DataList. In addition, I will demonstrate how to nest a DataGrid in a DataList, and the best way to manage the presentation and code-behind for control nesting by using a UserControl.

All of the skills in the previous article and this article are beneficial precursors for creating presentation layers that easily model complex data relationships. After you have read the first two articles, you will be prepared for the third, and for implementing the most complex graphical user interfaces for the Web. All that you will need to bring to the table is a modicum of visual good taste. (Unfortunately, this is something that my left brain is not prepared to teach.)

Spanning an HTML Table Across Template Sections

A DataGrid is approximately an advanced form of an HTML table that coordinates the vertical and horizontal alignment of data, including data that plays the role of header and footer information with data that comprises the content. A symmetric appearance, while not especially artistic, does lend itself to good order. There are times that you may not necessarily want to use a DataGrid but want to constrain the appearance of data. For example, the Repeater is a faster, read-only control that displays collections of data like the DataGrid, and the DataList is a read-write control that behaves like the Repeater; it repeats template-described content.

If you choose to use the DataList or Repeater, but want grid-like organization, you can intersperse the rows and cells of an HTML table across the template regions of a DataList or Repeater. The visual result is that values in the header and footer sections can be made to align themselves with related values in the selected, item, and edit sections of the DataList or Repeater. (We'll demonstrate with a DataList. Simply keep in mind that the demonstrated technique can be applied to the Repeater control too.)

The Road Less Taken

New developers may likely be more comfortable with the very visually oriented aspect of ASP.NET design. Slightly older commandos may be more comfortable off the visual designer path, writing HTML. If you are comfortable writing HTML, you will be right at home here. If you are unfamiliar or uncomfortable writing HTML, this may be a good opportunity to broaden your skills.

The scenario for our example is to use a DataList and HTML table, and the DataList's <HeaderTemplate>, <ItemTemplate>, <SelectedItemTemplate>, and <FooterTemplate> to create a coordinated grid-like appearance. Header information will be conformed to vertically align with associated data in the item and selected-item rows, and the footer will be employed to create a simple visual effect. The result will look like a grid. The code is shown in Listing 1, and the result is shown in Figure 1. The listing is followed by an explanation.

Listing 1: Demonstrating an HTML table interwoven across template sections of a DataList to create a grid-like appearance.

1:  <%@ Page Language="vb" AutoEventWireup="false"
                           Codebehind="WebForm1.aspx.vb"
2:    Inherits="HTMLSpan.WebForm1"%>
3:  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
4:  <HTML>
5:    <HEAD>
6:      <title>WebForm1</title>
7:      <meta content="Microsoft Visual Studio .NET 7.1"
              name=GENERATOR>
8:      <meta content="Visual Basic .NET 7.1" name=CODE_LANGUAGE>
9:      <meta content=JavaScript name=vs_defaultClientScript>
10:     <meta content=http://schemas.microsoft.com/intellisense/
                             ie5 name=vs_targetSchema>
11:   </HEAD>
12:   <body MS_POSITIONING="GridLayout">
13:     <form id=Form1 method=post runat="server">
14:       <table>
15:         <asp:datalist id=DataList1
16:           style="Z-INDEX: 101; LEFT: 6px; POSITION: absolute;
                              TOP: 8px"
17:           runat="server" Width="100%">
18:           <HeaderTemplate>
19:         <tr bgcolor="navy" style="FONT-WEIGHT: bold;
                               COLOR: white;
20:           FONT-FAMILY: 'Courier New'">
21:           <td style="FONT-WEIGHT: bold">ID</td>
22:           <td style="FONT-WEIGHT: bold">First Name</td>
23:           <td style="FONT-WEIGHT: bold">Last Name</td>
24:           <td style="FONT-WEIGHT: bold">Email</td>
25:           <td style="FONT-WEIGHT: bold">Picture</td>
26:         </tr>
27:           </HeaderTemplate>
28:
29:           <SelectedItemTemplate>
30:         <tr bgcolor="aliceblue" height="45px">
31:           <td style="BORDER-TOP: blue thin solid;
BORDER-LEFT: blue thin solid"> 32: <asp:LinkButton id=LinkButton1 CommandName="Select" runat="server"> 33: <%# DataBinder.Eval(Container.DataItem, "ID") %></asp:LinkButton>
</td>
34:           <td style="BORDER-TOP: blue thin solid">
35: <asp:Label id="Label2" runat="server"> 36: <%# DataBinder.Eval(Container.DataItem, "FirstName") %> 37: </asp:Label>
</td>
38:           <td style="BORDER-TOP: blue thin solid ">
39: <asp:Label id="Label3" runat="server"> 40: <%# DataBinder.Eval(Container.DataItem, "LastName") %> 41: </asp:Label>
</td>
42:           <td style="BORDER-TOP: blue thin solid">
43: <asp:Label id="Label4" runat="server"> 44: <%# DataBinder.Eval(Container.DataItem, "Email") %></asp:Label>
</td>
45:           <td align="center" valign="center" rowspan="2"
46:               style="BORDER-TOP: blue thin solid;
                  BORDER-RIGHT: blue thin solid;
                  BORDER-BOTTOM: blue thin solid">
47: <asp:Image id="Image2" Height="90px" Width="75px" 48: ImageUrl='<%# DataBinder.Eval(Container.DataItem, "Picture") %>' 49: runat="server"></asp:Image>
50:           </td>
51:         </tr>
52:         <tr bgcolor="aliceblue" height="45px">
53:           <td style=
54:             "BORDER-LEFT: blue thin solid;
                 BORDER-BOTTOM: blue thin solid"></td>
55:           <td style="BORDER-BOTTOM: blue thin solid"></td>
56:           <td style="BORDER-BOTTOM: blue thin solid"></td>
57:           <td style="BORDER-BOTTOM: blue thin solid"></td>
58:         </tr>
59:           </SelectedItemTemplate>
60:           <FooterTemplate>
61:         <tr bgcolor="silver">
62:           <td colspan="5"> </td>
63:         </tr>
64:           </FooterTemplate>
65:           <ItemTemplate>
66:         <tr height="30px">
67:           <td>
68: <asp:LinkButton id="Linkbutton2" CommandName="Select" runat="server"> 69: <%# DataBinder.Eval(Container.DataItem, "ID") %></asp:LinkButton></td>
70:            <td>
71: <asp:Label id="Label6" runat="server"> 72: <%# DataBinder.Eval(Container.DataItem, "FirstName") %> 73: </asp:Label>
</td>
74:            <td>
75: <asp:Label id="Label7" runat="server"> 76: <%# DataBinder.Eval(Container.DataItem, "LastName") %> 77: </asp:Label>
</td>
78:            <td>
79: <asp:Label id="Label8" runat="server"> 80: <%# DataBinder.Eval(Container.DataItem, "Email") %></asp:Label>
</td>
81:            <td align="center" valign="center" rowspan="2">
82: <asp:Image id="Image1" Height="60px" Width="40px" 83: ImageUrl= 84: '<%# DataBinder.Eval(Container.DataItem, "Picture") %>'
85:                  runat="server"></asp:Image>
86:            </td>
87:          </tr>
88:          <tr height="30px">
89:            <td></td>
90:            <td></td>
91:            <td></td>
92:            <td></td>
93:          </tr>
94:            </ItemTemplate>
95:         </asp:DataList>
96:       </TABLE>
</FORM> 97: </body> 98: </HTML>

Listing 1 uses highlighting colors (color being one of the benefits of Web publishing) to illustrate how the HTML table in yellow is intertwined with the DataList template sections in green. The green sections include the template regions, such as the <ItemTemplate></ItemTemplate> beginning on line 65 and ending on line 94. The yellow sections show HTML tags, such as the table row beginning on line 66 and ending on line 87. (Lines 66 to 87 and 88 to 93 create the unselected data rows (see Figure 1.) The total effect is illustrated in Figure 1. The biggest drawback of combining an HTML table and DataList is that you must create the result by writing HTML. The designer seems incapable of rendering the effect at design time.



Click here for a larger image.

Figure 1: A DataList whose appearance is constrained by an HTML table.

By using an HTML table and a DataList, I was able to use one column and two rows for the image and just one row and column for each of the remaining data values, while keeping column headers lined up with column and row data. With a little effort, we could create a style sheet and (getting some artistic feedback), improve the total effect.

One might consider defining a UserControl with the HTML table and placing the UserControl in the DataList's ItemTemplate. The drawback with this approach is that the whole table is repeated for each row of data. By melding the HTML table and DataList controls together, the header is displayed once and just the data rows are repeated, as shown in Figure 1.

Putting a DataGrid in a DataList's Item Template

Another approach is to repeat an entire block of data. For example, if we define a relationship where the Customer has a list of contact information, for each customer we may want to show both the customer information and the nested, repeating contact information. Nesting a DataGrid inside of a DataList is a relatively low-impact way to achieve this result.

Figure 2 shows the results of nesting a DataGrid within our DataList. Before we talk about how to create this effect, let's take a moment to clear up some details. The first detail is that the DataGrid border was left in place to make it clear where the DataGrid was located. The second detail is that all of the contact information was intentionally initialized to the same value (e.g. Number=411 and Description=Information); displaying other values is simply a matter of modifying the relevant fields with code or user input. Finally, I used a strongly typed collection of customer objects and each Customer object contained a list of contact information. One could use any data structure that can be used as a DataSource for the DataList and DataGrid, such as a DataSet.



Click here for a larger image.

Figure 2: The nested DataGrid in the DataList. The DataGrid border was left in for clarity.

We already know how to create the ID, First Name, Last Name, and Picture columns; I re-used the code provided in Listing 1. The new part is the nested contact information. Let's talk about a convenient means of creating this effect.

To create the nested effect, do this:

  1. Drop a DataGrid onto the Web Page containing the HTML table/DataList, but not intermixed with those controls.
  2. Use the Properties window to modify the properties of the DataGrid. For example, set AutoGenerateColumns to False and define the (DataBindings) property as shown in Figure 3. (Basically, the DataSource property was set to DataBinder.Eval(Container.DataItem, "ContactInformationList") where the Container.DataItem will be a Customer object.)
  3. Then, switch to the HTML view and add a new column to the ItemTemplate and SelectedItemTemplate using the data cell tag-pair <td></td>.
  4. In the HTML, find the definition of the DataGrid—it will begin with the tag <DataGrid> and end with a matching </DataGrid>. Paste the DataGrid definition in between the new <td></td> pair defined in the ItemTemplate and SelectedItemTemplate.
  5. Add the code that supplies the DataSource and calls DataBind in the code-behind, and you're ready to run the example.



Click here for a larger image.

Figure 3: Use the DataBindings dialog to define the DataSource for the DataGrid.

For your perusal, the ItemTemplate containing the DataGrid—now playing the role of template—is provided in Listing 2 and the complete implementation of the Customer and ContactInformation is provided without comment in Listing 3.

Listing 2: The revised ItemTemplate, including the nested DataGrid definition (highlighted), section from Listing 1.

1:    <ItemTemplate>
2:  <tr height="30px">
3:    <td>
4:      <asp:LinkButton id="Linkbutton2" CommandName="Select"
                        runat="server">
5:        <%# DataBinder.Eval(Container.DataItem, "ID") %>
          </asp:LinkButton></td>
6:    <td>
7:      <asp:Label id="Label6" runat="server">
8:      <%# DataBinder.Eval(Container.DataItem, "FirstName") %>
        </asp:Label></td>
9:    <td>
10:     <asp:Label id="Label7" runat="server">
11:       <%# DataBinder.Eval(Container.DataItem, "LastName") %>
          </asp:Label></td>
12:   <td>
13:     <asp:Label id="Label8" runat="server">
14:       <%# DataBinder.Eval(Container.DataItem, "Email") %>
          </asp:Label></td>
15:   <td>
16:     <asp:DataGrid id=DataGrid1
17:       DataSource=
18:       '<%# DataBinder.Eval(Container.DataItem,
                               "ContactInformationList") %>'
19:       runat="server" AutoGenerateColumns="False">
20:       <Columns>
21:         <asp:BoundColumn DataField="ID" ReadOnly="True" 
22:           HeaderText="ID"></asp:BoundColumn>
23:         <asp:BoundColumn DataField="Number"
                             HeaderText="Number">
            </asp:BoundColumn>
24:         <asp:BoundColumn DataField="Description"
25:           HeaderText="Description"></asp:BoundColumn>
26:       </Columns>
27:     </asp:DataGrid>
28: </td> 29: <td align="center" valign="center" rowspan="2"> 30: <asp:Image id="Image1" Height="60px" Width="40px" 31: ImageUrl='<%# DataBinder.Eval(Container.DataItem, "Picture") %>' 32: runat="server"></asp:Image> 33: </td> 34: </tr> 35: <tr height="30px"> 36: <td></td> 37: <td></td> 38: <td></td> 39: <td></td> 40: </tr> 41: </ItemTemplate>

Listing 3: The strongly typed Customer and ContactInformation collections used for the samples.

Public Class CustomerList
  Inherits CollectionBase

  Public Shared Function GetCustomerList() As CustomerList
    Dim obj As CustomerList = New CustomerList
    obj.Add(New Customer(1, "Paul", "Kimmel", _
                            "pkimmel@softconcept.com", _
                            "~/Images/PaulKimmel.jpg"))
    obj.Add(New Customer(2, "Brad", "Jones", ""))
    obj.Add(New Customer(3, "Robert", "Golieb", ""))
    obj.Add(New Customer(4, "Geoff", "Caylor", ""))
    obj.Add(New Customer(5, "Joe 'Bilbo'", "Shook", ""))
    Return obj
  End Function

  Default Public Property Item(ByVal Index As Integer) As Customer
  Get
    Return CType(List(Index), Customer)
  End Get
  Set(ByVal Value As Customer)
    List(Index) = Value
  End Set
  End Property

  Public Function Add(ByVal Value As Customer) As Integer
    Return List.Add(Value)
  End Function

End Class

Public Class Customer
  Private FID As Integer
  Private FFirstName As String
  Private FLastName As String
  Private FEmail As String
  Private FPicture As String
  Private FContactInformationList As ContactInformationList

  Public Sub New()
  End Sub

  Public Sub New(ByVal ID As Integer, ByVal FirstName As String, _
    ByVal LastName As String, ByVal EMail As String, _
    Optional ByVal Picture As String = "~/Images/Unavailable.jpg")

    FID = ID
    FFirstName = FirstName
    FLastName = LastName
    FEmail = EMail
    FPicture = Picture

    FContactInformationList = _
     ContactInformationList.GetContactInformationList(ID)
  End Sub


  Public Property ID() As Integer
  Get
    Return FID
  End Get
  Set(ByVal Value As Integer)
    FID = Value
  End Set
  End Property

  Public Property FirstName() As String
  Get
    Return FFirstName
  End Get
  Set(ByVal Value As String)
    FFirstName = Value
  End Set
  End Property

  Public Property LastName() As String
  Get
    Return FLastName
  End Get
  Set(ByVal Value As String)
    FLastName = Value
  End Set
  End Property

  Public Property Email() As String
  Get
    Return FEmail
  End Get
  Set(ByVal Value As String)
    FEmail = Value
  End Set
  End Property

  Public Property Picture() As String
  Get
    Return FPicture
  End Get
  Set(ByVal Value As String)
    FPicture = Value
  End Set
  End Property

  Public ReadOnly Property ContactInformationList() _
         As ContactInformationList
  Get
    Return FContactInformationList
  End Get
  End Property

End Class

Public Class ContactInformationList
    Inherits CollectionBase

  Public Shared Function GetContactInformationList(ByVal ID _
         As Integer) As ContactInformationList
    Dim obj As ContactInformationList = New ContactInformationList
    obj.Add(New ContactInformation(ID, "411", "Information"))
    obj.Add(New ContactInformation(ID, "www.yahoo.com", _
                                   "People Search"))
    Return obj
  End Function

  Default Public Property Item(ByVal Index As Integer) _
          As ContactInformation
  Get
    Return CType(List(Index), ContactInformation)
  End Get
  Set(ByVal Value As ContactInformation)
    List(Index) = Value
  End Set
  End Property

  Public Function Add(ByVal Value As ContactInformation) As Integer
    Return List.Add(Value)
  End Function

End Class

Public Class ContactInformation
  Private FID As Integer
  Private FNumber As String
  Private FDescription As String

  Public Sub New(ByVal ID As Integer, ByVal Number As String, _
    ByVal Description As String)
      FID = ID
      FNumber = Number
      FDescription = Description
  End Sub

  Public Property ID() As Integer
  Get
    Return FID
  End Get
  Set(ByVal Value As Integer)
    FID = Value
  End Set
  End Property

  Public Property Number() As String
  Get
    Return FNumber
  End Get
  Set(ByVal Value As String)
    FNumber = Value
  End Set
  End Property

  Public Property Description() As String
  Get
    Return FDescription
  End Get
  Set(ByVal Value As String)
    FDescription = Value
  End Set
  End Property
End Class

It is worth noting that if we don't use the HTML table interspersed with the DataList, much, if not all, of the editing of the DataList and DataGrid can occur visually. As with a lot of programming, unfortunately, the most advanced things still have to be done by hand.

Templating with a UserControl

An excellent way to separate tasks and make managing the code-behind easier is to heavily employ UserControls. For example, what if we wanted to programmatically interact with the nested DataGrid? Unfortunately, DataGrid1 doesn't actually exist in the code-behind. When we cut the DataGrid from the page and pasted it in the template, we have a template definition for the grid, but each row creates a grid dynamically. Getting to those templated, dynamic grids is messy and time consuming. We have to implement events for the DataList and invoke FindControl or rely on the literal column position. This results in complicated code, dynamic binding of events, and slower performance; calling FindControl incurs some overhead.

There is a better way. If we place the DataGrid on its own UserControl, we can modify the definition of the DataGrid and add extra code at any time. By placing the UserControl containing the DataGrid in the DataList, we get the same visual effect as if we placed the grid in the DataList directly, yet any modifications to the separate UserControl-with-DataGrid are easier to effect and show up immediately in the DataList. The net benefit is that our DataList's HTML is much simpler, the same visual result can be affected, the code-behind for the DataGrid can be implemented at design time much more easily, and the UserControl can be used in other contexts. The DataList, then, simply devolves to a mechanism for repeating the template section.

There is a drawback using the DataList with a nested DataGrid approach: editing nested DataGrid seems to be broken. If you invoke an edit behavior on a DataGrid, your code will receive the Edit behavior but not subsequent events, such as Update. This is a problem. Fortunately, there is a solution and the clue resides in the role that our DataList has been relegated too in the last paragraph.

Summary

In this article, I demonstrated how to control layout by using an HTML table across DataList template regions. I also demonstrated how to nest a DataGrid within a DataList. Many of us are aware that displaying data using nested DataLists and DataGrids work fine, but editing behavior seems to break down. In the third installment of this article, I will help you solve that problem.

In Part III, I will demonstrate how to implement a repeated DataGrid behavior that will permit you to add, edit, update, and delete data. The approach demonstrated requires a bit of cleverness, but will work sufficiently until the DataGrid and DataList support complex nesting in conjunction with editing behavior.

About the Author

Paul Kimmel is a software architect, writer, and columnist for codeguru.com. Look for his recent book, Visual Basic .NET Power Coding from Addison-Wesley on Amazon.com. Paul Kimmel is available to help design and build your .NET solutions and can be contacted at pkimmel@softconcepts.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date