The need often arises to custom sort a DataGrid when working with displaying data in a sortable manner. You may for example wish to make your grid sortable in ascending or descending order by any column. I recently had to do this, and from my research found that there are really two ways to do this (depending on your needs). The first (and preferable, IMO) method is to use the IComparer interface. To do so, let's start with this grid:
<asp:DataGrid ID="dgMyGrid" runat="server" BorderWidth="0" Visible="True" CellPadding="0" Width="99%" AutoGenerateColumns="False" BorderStyle="None" GridLines="None" EnableViewState="False" Title="My Grid" OnItemDataBound="dgMyGrid_ItemDataBound" AllowSorting="true" OnSortCommand="dgMyGrid_Sort">
<HeaderStyle CssClass="dgHeader"></HeaderStyle>
<Columns>
<asp:BoundColumn HeaderStyle-HorizontalAlign="Left" ItemStyle-HorizontalAlign="Left" HeaderText="Date" DataFormatString="{0:MM/dd/yyyy}" HeaderStyle-CssClass="dgHeader" HeaderStyle-Width="70" ItemStyle-Width="70" SortExpression="DateCreated" />
<asp:BoundColumn HeaderStyle-HorizontalAlign="Center" ItemStyle-HorizontalAlign="Center" HeaderText="Account" HeaderStyle-CssClass="dgHeader" HeaderStyle-Width="100" ItemStyle-Width="100" SortExpression="AccountNumber" />
<asp:BoundColumn HeaderStyle-HorizontalAlign="Left" ItemStyle-HorizontalAlign="Left" HeaderText="CustomerName" HeaderStyle-CssClass="dgHeader" HeaderStyle-Width="250" ItemStyle-Width="250" SortExpression="CustomerName" />
</Columns>
</asp:DataGrid>
Now that we have a grid in place, we need to add an enumeration for the columns into our page. This enumeration will be used for both methods of sorting.
Public Enum dgMyGridColNames
Date = 0
Account
Customer
End Enum
You’ll also need a couple of constants for the sort order:
Public Const SORT_ORDER_ASCENDING As String = "Ascending"
Public Const SORT_ORDER_DESCENDING As String = "Descending"
Private Const SORT_ORDER_NONE As String = "None"
The dgMyGrid Sort method will recognize which column is clicked on and do the work that decides which column and which order we are sorting by. You’ll have to come up with your own method of keeping track of whether you’re sorting ascending or descending and by which column. In my case, used a label for each column in the grid which stored the word “ascending” or “descending” in it depending on which column the user clicks on. Here’s the general idea:
Protected Sub dgMyGrid_Sort(ByVal sender As Object, ByVal e As DataGridSortCommandEventArgs)
Select Case e.SortExpression
Case "DateCreated"
Select Case lblDateCreatedSortOrder.Text
Case SORT_ORDER_ASCENDING, SORT_ORDER_NONE
lblDateCreatedSortOrder.Text = SORT_ORDER_DESCENDING
lblAccountNumberSortOrder.Text = SORT_ORDER_NONE
lblCustomerSortOrder.Text = SORT_ORDER_NONE
SortGrid(dgMyGridColnames.Date, SORT_ORDER_DESCENDING)
Case SORT_ORDER_DESCENDING
lblDateCreatedSortOrder.Text = SORT_ORDER_ASCENDING
lblAccountNumberSortOrder.Text = SORT_ORDER_NONE
lblCustomerSortOrder.Text = SORT_ORDER_NONE
SortGrid(dgMyGridColnames.Date, SORT_ORDER_ASCENDING)
Case Else
End Select
Case "AccountNumber"
Select Case lblAccountNumberSortOrder.Text
Case SORT_ORDER_ASCENDING, SORT_ORDER_NONE
lblAccountNumberSortOrder.Text = SORT_ORDER_DESCENDING
lblDateCreatedSortOrder.Text = SORT_ORDER_NONE
lblCustomerSortOrder.Text = SORT_ORDER_NONE
SortGrid(dgMyGridColnames.Account, SORT_ORDER_DESCENDING)
Case SORT_ORDER_DESCENDING
lblAccountNumberSortOrder.Text = SORT_ORDER_ASCENDING
lblDateCreatedSortOrder.Text = SORT_ORDER_NONE
lblCustomerSortOrder.Text = SORT_ORDER_NONE
SortGrid(dgMyGridColnames.Account, SORT_ORDER_ASCENDING)
Case Else
End Select
Case "CustomerName"
Select Case lblCustomerSortOrder.Text
Case SORT_ORDER_ASCENDING, SORT_ORDER_NONE
lblCustomerSortOrder.Text = SORT_ORDER_DESCENDING
lblDateCreatedSortOrder.Text = SORT_ORDER_NONE
lblAccountNumberSortOrder.Text = SORT_ORDER_NONE
SortGrid(dgMyGridColnames.colSubj, SORT_ORDER_DESCENDING)
Case SORT_ORDER_DESCENDING
lblCustomerSortOrder.Text = SORT_ORDER_ASCENDING
lblDateCreatedSortOrder.Text = SORT_ORDER_NONE
lblAccountNumberSortOrder.Text = SORT_ORDER_NONE
SortGrid(dgMyGridColnames.Customer, SORT_ORDER_ASCENDING)
Case Else
End Select
Case Else
End Select
End Sub
And here is the SortGrid method:
Protected Sub SortGrid(ByVal column As dgMyGridColnames, ByVal sortOrder As String)
Dim arrayDS As ArrayList = GetMyDataSource()
Dim comparer As AlertListComparer = New AlertListComparer()
comparer.ColType = column
comparer.SortOrder = sortOrder
arrayDS.Sort(comparer)
dgMyGrid.DataSource = arrayDS
dgMyGrid.DataBind()
End Sub
Finally, this is how you would implement the IComparer class (the “comparer” object declared in the previous method that gets passed into the ArrayList.Sort() function):
Public Class AlertListComparer
Implements IComparer
Public SortOrder As String
Public ColType As MessageCenterRequestList.alertsDGColnames
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
Dim xDetail As <WHATEVER OBJECT TYPE IS IN YOUR ARRAYLIST DATASOURCE>
Dim yDetail As <WHATEVER OBJECT TYPE IS IN YOUR ARRAYLIST DATASOURCE>
xDetail = CType(x, <OBJECT TYPE IN YOUR ARRAYLIST DATASOURCE>)
yDetail = CType(y, <OBJECT TYPE IN YOUR ARRAYLIST DATASOURCE>)
' Less than zero x is less than y.
' Zero x equals y.
' Greater than zero x is greater than y.
Select Case ColType
Case MyClass.dgMyGridColnames.Account
Select Case SortOrder
Case MyClass.SORT_ORDER_ASCENDING
Return String.Compare(xDetail.AccountKey, yDetail.AccountKey)
Case MyClass.SORT_ORDER_DESCENDING
Return String.Compare(yDetail.AccountKey, xDetail.AccountKey)
End Select
Case MyClass.dgMyGridColnames.Date
Select Case SortOrder
Case MyClass.SORT_ORDER_ASCENDING
Return Date.Compare(xDetail.DateCreated, yDetail.DateCreated)
Case MyClass.SORT_ORDER_DESCENDING
Return Date.Compare(yDetail.DateCreated, xDetail.DateCreated)
End Select
Case MyClass.dgMyGridColnames.Customer
Select Case SortOrder
Case MyClass.SORT_ORDER_ASCENDING
Return String.Compare(xDetail.Subject, yDetail.Subject)
Case MyClass.SORT_ORDER_DESCENDING
Return String.Compare(yDetail.Subject, xDetail.Subject)
End Select
End Select
End Function
End Class
The idea here is that the Object.Compare(x, y) methods will return -1 if x < y, 0 if x = y, and 1 if x > y. The class simply compares the currently indexed object in the array to the next object (x is current, y is next).
You can even add logic to custom handle cases where two items in the same column are equal and you want to sort by the next column. This would be done like so:
Select Case ColType
Case MyClass.dgMyGridColnames.Account
Select Case SortOrder
Case MyClass.SORT_ORDER_ASCENDING
If (String.Compare(xDetail.AccountKey, yDetail.AccountKey) = 0) Then
If (xDetail.Date > yDetail.Date) Then
Return -1
ElseIf (xDetail.Date < yDetail.Date) Then
Return 1
Else
Return String.Compare(xDetail.AccountKey, yDetail.AccountKey)
Case MyClass.SORT_ORDER_DESCENDING
Return String.Compare(yDetail.AccountKey, xDetail.AccountKey)
End Select
With this logic if the AccountKeys are equal, that means we want to go ahead and sort by Date as a secondary sort order. The same logic should be implemented for descending order with the x and y elements reversed.
Another way custom sorting can be accomplished is by using a dataview:
Protected Sub SortGridWithDataTable(ByVal column As alertsDGColnames, ByVal sortOrder As String)
'Set up the DataTable and columns
Dim ds As DataSet = New DataSet("dsSort")
Dim dt As DataTable = New DataTable("tblSort")
dt.Columns.Add("Date")
dt.Columns.Add("Account")
dt.Columns.Add("Customer")
'Populate the DataTable
Dim array As ArrayList = GetMyDataSourceArray()
For Each item As <DataType In array> In array
Dim row As DataRow = dt.NewRow()
row("Date") = item.Date.ToString()
row("Account") = item.AccountNumber.ToString()
row("Subject") = item.Subject.ToString()
dt.Rows.Add(row)
Next
ds.Tables.Add(dt)
Dim dv As DataView = ds.Tables("tblSort").DefaultView
'Sort the DataTable
Select Case column
Case dgMyGridColnames.Account
Select Case sortOrder
Case SORT_ORDER_ASCENDING
dv.Sort = "Account ASC"
Case SORT_ORDER_DESCENDING
dv.Sort = "Account DESC"
End Select
Case dgMyGridColnames.Date
Select Case sortOrder
Case SORT_ORDER_ASCENDING
dv.Sort = "Date ASC"
Case SORT_ORDER_DESCENDING
dv.Sort = "Date DESC"
End Select
Case dgMyGridColnames.Customer
Select Case sortOrder
Case SORT_ORDER_ASCENDING
dv.Sort = "Customer ASC"
Case SORT_ORDER_DESCENDING
dv.Sort = "Customer DESC"
End Select
End Select
dgMyGrid.DataSource = dv
dgMyGrid.DataBind()
End Sub
Both of these methods of custom sorting work just fine, with the former (using IComparer) being more elegant and easy to use in my opinion. Bear in mind that your datagrid’s OnItemDatabound event will need to take into account what kind of sorting method you are using for when you bind the grid to the newly sorted datasource. One nice thing about using IComparer is that your datasource (in this case an ArrayList) retains the same object type throughout because you are not converting the source to a DataView. In my case, I had a lot of custom formatting to do based on my original datasource in the OnItemDataBound event, so it was a no-brainer for me to use IComparer.
Hopefully this was clear enough; now go play around with it!