In my article "DataGridMagic" I discussed a few quick ways to improve the user interface of the DataGrid control. I firmly believe that the possibilities offered by the control are virtually endless and limited only by need and imagination. Each day, in fact, I find a few questions in my inbox that revolve around extensions to the user interface and the functionality of the DataGrid control. For more information on DataGrids, see " Customize DataGrid Behavior " and " Master the DataGrid Control. "

The DataGrid is the chief data-bound control in the ASP.NET 1.x arsenal. It is not the most flexible control (a Repeater, for instance, gives you much more freedom when it comes to the design of the user interface), but it is certainly the most versatile. In addition, the DataGrid control is by far the list control with the most advantageous ratio of features to implementation costs. If a given feature can be coded with both a Repeater and a DataGrid, then the solution based on the grid is easiest to develop.

Beyond its wealth of properties and styles, the DataGrid control's power is in the eventing model, which fires events to the host code for any internal step performed on the way to the output. Become familiar with the grid's lifecycle; this makes the road ahead to the implementation of complex functions much smoother. Having a full understanding of events such as ItemCreated and ItemDataBound is essential for getting the most out of the control.

In this article, I'll demonstrate how using these events safely and effectively leads to a better and more powerful control. Before going any further, I need to say that all the features and tips I'll discuss here assume that you work with an instance of the base DataGrid control. Most of the tricks can be incorporated in a new derived control and easily reused across pages. If you derive a custom control, you gain full access to a bunch of protected virtual methods and have a new world of opportunities open up.

This article is a collection of 10 tips in response to frequently asked how-to questions about DataGrids. Look at the "Advanced DataGrid Links" sidebar for references to other more complex topics.

In ASP.NET 2.0, the DataGrid control is fully supported and all this code and these tricks work perfectly. However, a new grid control makes its debut with ASP.NET 2.0 - the GridView control. The "GridView: The Successor to DataGrid" sidebar provides a brief introduction.

1. Give the Grid a Fixed Height

The DataGrid control's output is a plain HTML table. The height of a table depends on a variety of factors, including the number of child rows, the font in use, and the wrap mode of the cells. However, a pageable grid has an additional issue: The last page might have a smaller number of rows than the other pages. When this happens, the grid takes up less space than expected and the rest of the host page shifts up. Sometimes this may not be acceptable from the user-interface perspective.

At first sight, this issue has an easy fix: Set the Height property of the control to a fixed value in pixels. The Height property instructs the browser to reserve a fixed area for the control's display and removes the possibility of changes to the page layout at the root. If you do this though, the grid is effectively rendered as high as the specified height, but each row is rendered proportionally. For example, if you set the height to 100 pixels and the grid's page contains only two rows, then each row is 50 pixels high, no matter what the required height for the text is.

To work around this issue, you can place the DataGrid within a table and set the height on the table. Here's an example:

<table><tr>

<td vAlign="top">

<asp:datagrid width="100%" ...>

:

</asp:datagrid>

</td>

</tr></table>

To make the table blend in graphically, you might want to use the same background color for the table and the grid. In addition, any border setting should be defined on the table instead of the grid. Setting the grid's GridLines attribute to None normally results in a better looking grid (see Figure 1).


Figure 1. The DataGrid control occupies the same space no matter the number of displayed rows.

2. Make HyperLink Columns Accept Any Number of Parameters

You use the hyperlink column type in a DataGrid control to create a hyperlink for each row in the bound data. If the same text must appear for each row, you set the Text property to the desired caption text and make the NavigateUrl property point to the URL. More often than not though, the text and the URL must vary with each row. In this case, you can use the DataTextField and DataNavigateUrlField properties to bind the displayed content to fields in the underlying data source. This binding model works great if you are going to have a completely different URL for each row. There might be situations where the URL is the same for all rows but each row requires a distinct query string. In this case, you use the DataNavigateUrlField in conjunction with the DataNavigateUrlFormatString property. Here's an example:

grid.DataNavigateUrlField = "productid"

grid.DataNavigateUrlFormatString = "page.aspx?id={0}"

The final URL for each row includes the product ID as the value of the id parameter. By default, only one variable parameter is supported. So what if you need to pass a page with more than one data-bound parameter? You replace the hyperlink column with a template column:

<asp:templatecolumn runat="server">

<itemtemplate>

  <a href='<%# "page.aspx?" +

     "?id=" + DataBinder.Eval(Container.DataItem, ...) +

     "parm1=" + DataBinder.Eval(Container.DataItem, ...) +

     "parm2=" + DataBinder.Eval(Container.DataItem, ...) +

      %>'>

  Visit us

  </a>

</itemtemplate>

</asp:templatecolumn>

The structure of the template is pretty simple and just builds up an anchor tag <a> in which the href attribute is data bound to as many fields as needed.

3. Attach Code to HyperLink Columns

A hyperlink column renders its output through an anchor tag <a> whose clicking is completely handled by the browser. No server page other than the page bound to the link is invoked when the user clicks on a hyperlink column. This fact marks an important difference with command columns. When clicked on, a command column posts back to the same ASP.NET page and fires a server-side event. By handling this event - the ItemCommand event - you can execute some code in response to the user's clicking.

Using a command column, you can simulate a hyperlink column. It suffices that in the ItemCommand handler you eventually use redirects to a given URL. The Response.Redirect method does the job for you.

In Whidbey, a similar trick is employed to implement site counter functionality. Some server controls, such as HyperLink, Button, and LinkButton, come with built-in support for counters. When configured to support the site counter service, a Hyperlink control doesn't directly point to the specified URL. Instead, it points to a system URL - an HTTP handler named counters.axd - that registers the event and then redirects to the original URL. The handler receives information about counters and URLs in an encoded form for security reasons.

4. Change Styles in Edit Mode

The DataGrid supports in-place editing; this means that users can edit the content of the cells in a given row and have the new values stored back to the data source. In ASP.NET 1.x this process is not entirely automatic (developers must write the update code), but this doesn't change the essence of the function greatly. Only one row at a time is in edit mode, meaning that it is rendered using an edit template as opposed to the default item template. Templated columns have total control over the cell layout in both view and edit mode. Bound columns render their cells using label controls in view mode and textboxes in edit mode. Other column types just don't support the edit mode. The edit mode is activated setting the EditItemIndex property to a value greater than -1.

Fonts, colors, borders, and the size of the textboxes may not be appropriate and consistent with the look-and-feel of the site. Templates give you full control over what's displayed in edit mode, but there's a lot you can do to make a grid look better - even with bound columns.

When the grid is about to render any row, it fires the ItemCreated event. The event contains information about the type of item being created. If the grid item corresponds to the edit item, you retrieve all the textboxes in the row and dynamically change their styles (see Figure 2).

Sub ItemCreated(sender As Object, e

 As DataGridItemEventArgs)

  Dim itemType As ListItemType = e.Item.ItemType

  Select Case itemType

    Case ListItemType.Header

      SetupHeader(e)

    Case ListItemType.Footer

      SetupFooter(e)

    Case ListItemType.EditItem

      SetupEditItem(e)

  End Select

End Sub

 

Sub SetupEditItem(e As DataGridItemEventArgs)

  Dim cell As TableCell

  For Each cell In e.Item.Cells

      If cell.Controls.Count >0 Then

         Dim ctl As Control = cell.Controls(0)

         If TypeOf(ctl) Is TextBox Then

              Dim t As TextBox = CType(ctl, TextBox)

     t.BackColor = Color.Yellow

            t.BorderStyle = BorderStyle.Outset

            t.BorderColor = Color.BlueViolet

            t.BorderWidth = Unit.Point(1)

            t.Width = Unit.Percentage(100)

            t.Font.Name = "verdana"

            t.Font.Size = FontUnit.Point(8)

       End If

   End If

  Next

End Sub

Figure 2. Change the style of the controls in the DataGrid item being edited.

The ItemType property of the DataGridItem object being processed indicates the type of the item. If it is EditItem, then you loop through all the cells in the item and grab a reference to the first control in the cell:

Dim cell As TableCell

For Each cell As TableCell In e.Item.Cells

    Dim ctl As Control = cell.Controls(0)

    If TypeOf(ctl) Is TextBox Then

       Dim t As TextBox = CType(ctl, TextBox)

       :

    End If

Next

By design, the DataGridItem object inherits TableCell. Furthermore, the default edit template is made of a single TextBox control. For this reason, the code I've just shown you can only retrieve the cell's textbox object. Figure 3 compares the default user interface to the modified user interface.


Figure 3. The grid on the left shows the default user interface for editable items; the grid on the right implements style changes discussed in this article.

5. Modify the Header Dynamically

In most situations, the header text you statically set in the DataGrid's <columns> section remains valid for the entire lifetime of the session. However, suppose you're using the DataGrid to render a timesheet and have a column for each day of the week. Also suppose that instead of a generic caption such as Monday or Tuesday, you want date-specific text representing the actual date. It's rather unlikely that your database contains columns named after a date, so you typically need to bind the column to a fixed database column and then adjust the header text dynamically. Once again, the ItemCreated event handler does the trick.

You write a piece of code that gets involved if the type of the item being created is Header, as in Figure 2:

Sub SetupHeader(e As DataGridItemEventArgs)

    e.Item.Cells(0).Text = "..."

    e.Item.Cells(1).Text = "..."

End Sub

It is important to note that you must work directly on the cells. At the time the ItemCreated event fires, the DataGrid column objects have been processed, so any changes to the HeaderText property of any column object are ignored. Within the same handler, you can also retrieve the cell of a particular column and add extra controls. For example, you can add a little bitmap to denote that the displayed data is sorted by the content of that column. Here's how to modify the layout of the header row, grouping more columns under the same header (you can view the results; see Figure 4):

Sub SetupHeader(e As DataGridItemEventArgs)

  e.Item.Cells.RemoveAt(0)

  e.Item.Cells(0).HorizontalAlign = HorizontalAlign.Center

  e.Item.Cells(0).ColumnSpan = 2

  e.Item.Cells(0).Text = "Employees"

End Sub


Figure 4. The header and footer in the DataGrid are dynamically modified during the ItemCreated event.

6. Compute Footer Expressions

Each DataGrid column has a FooterText property that matches the aforementioned HeaderText property. Unlike HeaderText, though, FooterText means little if it's set to a fixed and static text. The real added value of a footer text is its capability to express a summary of the column's contents. The DataGrid control provides no facilities to compute aggregate functions such as Sum, Count, or Average. However, as long as you can figure out the correct value to display, the ItemCreated event handler gives you a chance to place the right value in the right place.

When the grid is about to render its final row, the type of the item being created is Footer. At this point, you retrieve the right-column footer and display any static or dynamically computed text. The value to display depends on the expression to compute (and also on the data source). If the data source is a DataTable object, you can use the Compute method to easily calculate functions on a particular column. Here's an example that sums up the values in the Price column and displays the result in the footer of the third column formatted as a currency:

Dim price As Decimal

price = Convert.ToDecimal(data.Compute("sum(price)", ""))

e.Item.Cells(2).HorizontalAlign = HorizontalAlign.Right

e.Item.Cells(2).Text = String.Format("{0:c}", price)

The DataGrid in Figure 4 contains a footer that counts the total of rows displayed through the grid. Also, you can delete or add cells as well as modify the layout dynamically in the footer row.

Although the ItemCreated and ItemDataBound events are different events that occur at different times in the lifecycle of a DataGrid, they look similar and fire one after the other. The former fires immediately after the grid item has been created. The latter fires only when the item has been successfully bound to its data item. It goes without saying that for dataless items such as the header and footer, the ItemDataBound event is never fired.

7. Add First/Last Buttons to the Pager

The DataGrid supports paging and two navigation models: sequential and random. A sequential, next/previous navigation model can be made even richer by using a pair of extra links for the first and last pages of the data source. This feature comes natively with Whidbey but it must be manually coded in ASP.NET 1.x. You must modify the pager bar on the fly to accommodate for the extra buttons, add a handler for the Click event, and update the view. This code shows how you to modify the pager bar by adding the links for the first and last pages. The key procedure is the ItemCreated event handler:

If e.Item.ItemType = ListItemType.Pager Then

   If grid.PagerStyle.Mode = PagerMode.NextPrev Then

      Dim pager As TableCell = e.Item.Cells(0)

      AddLinkFirstPage(pager)

      AddLinkLastPage(pager)

   End If

End If

The pager is represented as a row with a single cell. All paging elements are added as free markup text within the space reserved for a single cell.

The First and Last buttons are added as link buttons or labels, depending on the page index. If the link can be clicked on to go to the first or last page, you render the button as a link; otherwise, you render it as a label. Let's tackle adding the First button with no significant changes expected for the Last button.

The First button must be rendered as a link button only if the Previous button is a link. The Previous button is the first in the Controls collection when the helper subroutine AddLinkFirstPage runs. As you can see, you check the type of the Previous link and then create a LinkButton or Label control accordingly (see Figure 5).

Sub AddLinkFirstPage(ByVal pager As TableCell)

    If TypeOf(pager.Controls(0)) Is LinkButton Then

        Dim btnFirst As LinkButton = New LinkButton()

        btnFirst.Font.Name = "webdings"

        btnFirst.Font.Size = FontUnit.Larger

        btnFirst.ForeColor = grid.PagerStyle.ForeColor

        btnFirst.Text = "3"

        AddHandler btnFirst.Click, AddressOf GoToFirstPage

        pager.Controls.AddAt(0, btnFirst)

    Else

        Dim btnFirst As Label = New Label()

        btnFirst.Font.Name = "webdings"

        btnFirst.Font.Size = FontUnit.Larger

        btnFirst.ForeColor = grid.PagerStyle.ForeColor

        btnFirst.Text = "3"

        pager.Controls.AddAt(0, btnFirst)

    End If

End Sub

 

Sub GoToFirstPage(ByVal sender As Object, ByVal e As EventArgs)

    grid.CurrentPageIndex = 0

    BindData()

End Sub

Figure 5. Add a First button to the new pager to go to the first page of the bound data source.

You should ensure that the font and color used for the First and Last buttons is identical to that used for the default buttons in the pager. You can read the default settings from the DataGrid's PagerStyle object. Take a look at an example of this technique (see Figure 6). The First and Last buttons are rendered using Webdings glyphs. In this case, it is a good practice to slightly enlarge the font size.


Figure 6. Add a pair of First/Last page buttons to the DataGrid's pager.

8. Control the Selection Process

Whenever users click on selectable DataGrid items, the SelectedIndexChanged event is fired. A selectable item is any text displayed within a command column characterized by the Select command name. Each command column is associated with a command name that is passed as an argument to the ItemCommand event. Each client click results in the ItemCommand server event where the command name helps to distinguish between multiple command columns. A command name is just a verb. A few of them, such as Select, Delete, and Edit, are prebuilt into the DataGrid control.

Once you have a Select command column, any click results in a pair of events: ItemCommand and SelectIndexChanged. The former event is generic and raised because the select command is a command column first and foremost. The latter event is fired a little later, when the DataGrid realizes that the name of the command column is Select. In this case, the row is rendered applying all the styles defined by the SelectedItemStyle property. This sequence of events can be exploited to implement a toggling mechanism for the row selection. If users click on the Select button, the row is rendered as selected; if the users click on the button in a selected row, the same row is deselected. Here is the code that implements this feature:

Private m_ currentSelectedIndex As Integer

Sub ItemCommand(sender As Object, _

    e As DataGridCommandEventArgs)

  Select Case e.CommandName

     Case "select"

         m_currentSelectedIndex = grid.SelectedIndex

  End Select

End Sub

 

Sub SelectedIndexChanged(sender As Object, e As EventArgs)

   If m_currentSelectedIndex = grid.SelectedIndex Then

      grid.SelectedIndex = -1

      Return

   End If

   SelectItem()

End Sub

The index of the selected item is stored in the SelectedIndex property. This property is not updated yet to reflect the selection when the ItemCommand event fires. In light of this, you cache the old index during ItemCommand and then compare that to SelectedIndex when the SelectedIndexChanged event arrives. If the two values coincide, then the user clicked on a selected row and you deselect it. Note that command names are case-sensitive.

To select a DataGrid row, you must click on the Select button. Can you get the same by clicking anywhere in the row? The idea is that you add the postback code to the entire DataGrid row during the ItemDataBound event:

Dim gridRow As DataGridItem

gridRow = e.Item

e.Item.Attributes("onclick") = _

   GetPostBackClientHyperlink(selectButton, "")

The selectButton in this code snippet indicates the grid's Select link button. In the "Advanced DataGrid Links" sidebar, you'll find a reference to an MSDN article that explains the trick in greater detail. 

9. Associate Grid Buttons and Popup Windows

Buttons are common in the layout of a DataGrid. They represent actions that the user can execute on the data shown in the grid. Quite reasonably, some of these actions can modify data and alter the state of the application. Asking the user for a confirmation is not a far-fetched idea. But how do you implement that?

The simplest way is to add some JavaScript code that runs when the button is clicked - all buttons are always rendered as INPUT tags on the browser. For items and alternating items, you handle the ItemCreated event, locate the button, and add the name of a global JavaScript code to the OnClick attribute of the control:

Command column is the third one (cell index 2)

Dim ctl As WebControl

ctl = CType(e.Item.Cells(2).Controls(0), WebControl)

ctl.Attributes("onclick") =

   "return confirm('Do you want to edit this item?');"

The code associated with the OnClick client event is a simple JavaScript snippet. This technique can be further refined to make the popup window context sensitive. To obtain this, move the code in the ItemDataBound event:

Sub ItemDataBound(sender As Object, _

     e As DataGridItemEventArgs)

   Dim itemType As ListItemType

   itemType = e.Item.ItemType

   Select Case itemType

      Case ListItemType.Item

           SetupItem(e)

      Case ListItemType.AlternatingItem

           SetupItem(e)

   End Select

End Sub

When the ItemDataBound event fires, the data element for the row being drawn is stored in the DataItem property of the DataGridItem object. This is not true when the ItemCreated event arrives:

Sub SetupItem(e As DataGridItemEventArgs)

   Dim ctl As WebControl

   ctl = CType(e.Item.Cells(2).Controls(0), WebControl)

   Dim row As DataRowView

   row = CType(e.Item.DataItem, DataRowView)

 

   Dim code As String

   code = "return confirm('Do you want to edit [{0}]?');"

   ctl.Attributes("onclick") = String.Format(code,

       row("lastname"))

End Sub

You can view the results (see Figure 7).


Figure 7. A context-sensitive popup window asks for confirmation before a critical operation.

10. Do More With Custom Controls

All the features examined in this article can be implemented on top of existing DataGrid controls in existing pages. To apply the tricks I've discussed here you don't need to rework your pages; you can simply enhance them with a limited effort. By handling events and combining properties together, you primarily improve the grid's user interface and add simple extra functionality. To accomplish more advanced things, configuration is not enough; you must resort to creating your own DataGrid control.

The first step consists of deriving a new control and making it inherit from the base DataGrid class. All the tricks described here can be incorporated into a new control to enhance the user interface. By deriving a new control, though, you have access to all the protected members - properties and methods. This gives you unprecedented power because you can interact with the underpinnings of the base control and can modify internal aspects such as the generation of the markup, the order of columns, and the structure of the control.

Deriving a new control from DataGrid shouldn't break any existing code because all the base features are automatically inherited and, hence, preserved. However, the more you drill down into the nitty-gritty details of the control and alter built-in behaviors, the more you put backward compatibility at risk.

The bottom line is that the DataGrid control is highly configurable and feature rich. You can easily go beyond the base set of features in two ways: through the public interface of the control or through inheritance. The second approach is more complex and requires familiarity with object-oriented programming concepts, the ASP.NET framework, and DataGrid plumbing. The first approach is far simpler and pays the bill in most cases. Advanced features such as representing hierarchical data and scrolling items require a custom control.

The sample code in this article is available for download.

 

Dino Esposito is a trainer and consultant who specializes in ASP.NET, ADO.NET, and XML. He is the author of Programming Microsoft ASP.NET (Microsoft Press, 2003) and is the cofounder of www.VB2TheMax.com. Write to him at mailto:dinoesp@hotmail.com or join the blog at http://weblogs.asp.net/despos.

 

Advanced DataGrid Links

Nested Grids. "Nested Grids for Hierarchical Data" by Dino Esposito, MSDN Magazine, October 2003 (http://msdn.microsoft.com/msdnmag/issues/03/10/CuttingEdge/default.aspx).

 

The DataGrid control is optimized to contain tabular data, but with some work you can adapt it to show hierarchical, related data (such as related tables stored in a DataSet). This article discusses how to define a custom column to expand and collapse a grid's row. When expanded, the template of the row is changed dynamically and a child DataGrid appears below the standard row to list child records.

 

Client-side Behaviors. "Extend the ASP.NET DataGrid with Client-side Behaviors" by Dino Esposito, MSDN Magazine, January 2004 (http://msdn.microsoft.com/msdnmag/issues/04/01/CuttingEdge/default.aspx).

 

Once on the client, a DataGrid is a plain table. This means that you can enrich its client-side functionalities with any DHTML behavior you have available. In this article, the author demonstrates dragging and dropping columns. Interestingly, the new order of column is reflected on the server when the page posts back.

 

Hierarchical Data Display. "Hierarchical Data Binding in ASP.NET" by Fritz Onion, MSDN Online (http://msdn.microsoft.com/asp.net/archive/default.aspx?pull=/library/en-us/dnaspp/html/aspn-hierdatabinding.asp).

 

Although it's not specifically targeted to DataGrid controls, this article provides an excellent overview of the techniques for performing ASP.NET data binding to data sources that are more than two dimensions and are hierarchical in nature.

 

Scrollable Grids. Freeze the Header, Scroll the Grid by Dino Esposito; Freeze the Header, Scroll the Grid: Part 2 by Dino Esposito.

 

A pageable DataGrid requires a postback whenever the user selects to view a different block of records. When the number of viewable records is not particularly large, a scrollable grid may be an option that is worth trying. This two-part article goes beyond the basics of making controls scrollable. It covers how to scroll only the body of the DataGrid, leaving the header and footer stationary.

 

Logical Navigation. Build a DataGrid With Tabs by Dino Esposito, asp.netPRO, August 2002.

 

This article presents a DataGrid control with the special capability to group and page data according to more sophisticated criteria than the simple order position in the source. The code presented modifies the layout of the page so that it looks like a tabstrip and contains any text that logically groups the displayed data (for example, groups it by the initials of customers or the months of the year).

 

Assorted Great Tips. "Top Questions about the DataGrid Web Server Control" by Mike Pope and Nikhil Kothari, MSDN Online (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dv_vstechart/html/vbtchTopQuestionsAboutASPNETDataGridServerControl.asp).

 

This article provides an illuminating list of answers to frequently asked questions about the DataGrid. Written by two members of the team, it addresses tough points not covered anywhere else.

 

GridView: The Successor to DataGrid

The ASP.NET 2.0 GridView control represents a major upgrade to the ASP.NET 1.x DataGrid control. It provides the same set of capabilities, plus a long list of extensions and improvements. The DataGrid - fully supported in ASP.NET 2.0 - remains an extremely powerful and versatile control but suffers from one main drawback: It requires a developer to write a lot of custom code, even to handle relatively simple operations such as paging, sorting, editing, or deleting data. The GridView control has been redesigned from the ground up to work around this limitation and make two-way data binding happen with the least amount of code possible. The control is tightly connected to the family of new data source controls and can automatically handle direct data source updates as long as the underlying data source object supports these capabilities.

 

This virtually codeless two-way data binding is by far the most notable feature of the new GridView control. The list of enhancements doesn't end here, however. The control is an improvement over the DataGrid in many aspects, including its capability to define multiple primary key fields, new column types, and style and templating options. Finally, the GridView comes with an extended eventing model that allows developers to handle or cancel events.