Introduction
For those that don't know me, I run a website about the ASP.NET Datagrid control, http://www.datagridgirl.com/. Inevitably, my first article here at CodeProject also focuses on the Datagrid control. I also enjoy answering Datagrid questions here at CodeProject, and around the web. A common question that comes up involves grouping similar data, and providing a visual cue to the user when a repeating data element has changed.
The Standard Datagrid
In a typical Datagrid, all columns are treated "equally". For example, you may wish to display Sales by Date, and to meet that end, you might include an ORDER BY Date
clause. However, if the end user is primarily interested in seeing how these sales vary by date, then this standard implementation might not make that obvious enough to them, as shown:

The Separated Datagrid
A better approach might be to add a separator row between each new group of sales for a given day, as seen at the top of the article. To do that, take advantage of two Datagrid events, ItemDataBound
, and PreRender
. We'll also need two variables public to the page:
Public LastDateValue As DateTime = Convert.ToDateTime("1/1/1901")
Public NewValues(100) As String
ItemDataBound
During the grid's ItemDataBound
event, detect whether the current row has a SalesDate
that varies from the previous row. If it is different, store the value in the NewValues array, and update the public variable for the LastDateValue
. You could also do some other manipulation here during the ItemDataBound
event. For example, if you wanted to also display a total for each date, that would be calculated during this event.
Private Sub dgSales_ItemDataBound(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) _
Handles dgSales.ItemDataBound
If e.Item.ItemType = ListItemType.Item Or _
e.Item.ItemType = ListItemType.AlternatingItem Then
NewValues(e.Item.ItemIndex) = ""
If e.Item.DataItem("SalesDate") <> LastDateValue Then
LastDateValue = e.Item.DataItem("SalesDate")
NewValues(e.Item.ItemIndex) = String.Format("{0:D}", _
e.Item.DataItem("SalesDate"))
End If
End If
End Sub
PreRender
First off, I'm a fan of casting sender
back to a Datagrid
, and working with that rather than simply referring to the Datagrid
by its ID. That makes it easier if you ever copy/paste this code to another Datagrid---errr, I mean, if you move this code into a reusable component--yeah, that's what I meant. Anyway. You're already aware that the Datagrid
ultimately renders as a <table>
in HTML, but you may not have realized that you can access that table on the server-side using DG.Controls(0)
. This table object is useful for several last-minute formatting tricks, including this one.
Private Sub dgSales_PreRender(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles dgSales.PreRender
Dim DG As DataGrid = CType(sender, DataGrid)
Dim Tbl As Table = DG.Controls(0)
Dim DGI As DataGridItem
Dim Cell As TableCell
Dim i As Integer, iAdded As Integer = 0
For i = 0 To NewValues.GetUpperBound(0)
If NewValues(i) <> "" Then
DGI = New DataGridItem(0, 0, ListItemType.Header)
Cell = New TableCell
Cell.ColumnSpan = 3
Cell.Text = NewValues(i)
DGI.Cells.Add(Cell)
Tbl.Controls.AddAt(i + iAdded + 1, DGI)
iAdded = iAdded + 1
End If
Next
End Sub
Conclusion
With a few tricks, the Datagrid
can help provide rich information to your client user base. Don't by limited by the standard out-of-the-box output from this control - use the Datagrid's events to format the output to your heart's desire, and provide a more useful user interface.
An Alternate Approach
This problem can also be solved by nesting another Datagrid
within the main Datagrid, binding the outer grid to a list of dates, and the inner grid to the sales, filtered for that date. I may cover this technique in a future article.