[ Download Code ]
Much has been written about the sorting and paging capabilities of the ASP.NET DataGrid. This article examines bringing together all the aspects of sorting on multiple columns and paging while presenting an easier, intuitive interface.
The goals of the code sample that accompanies this article are to:
Before we delve into the code take a look at the screenshot of the DataGrid below.
Figure 1: The DataGrid in its final form
As you can see, the sort direction is displayed by way of a simple image next to the sorted column in the DataGrid header. The number next to the image represents the sort sequence number for that column. The user can click on any sort-enabled column multiple times to toggle between one of three possible states: ascending, descending, or no sort order. The code sample that accompanies this article also writes the sort expression to the browser's status bar.
Now let us examine the code in a little detail. The declarative portion of the DataGrid is shown below. Setting the AllowSorting and AllowPaging attributes to true enables the sorting and paging functionality of the DataGrid.
<asp:datagrid id="dgEmployee" runat="server" AllowSorting="True" AllowPaging="True" AutoGenerateColumns="False"Width="100%" PageSize="20" Font-Size="10" Font-Name="verdana"> <AlternatingItemStyle BackColor="#CCCC99"></AlternatingItemStyle> <HeaderStyle Font-Bold="True" BackColor="#FFCC00"></HeaderStyle> <Columns> <asp:BoundColumn DataField="FirstName" SortExpression="FirstName" HeaderText="First Name"></asp:BoundColumn> <asp:BoundColumn DataField="LastName" SortExpression="LastName" HeaderText="Last Name"></asp:BoundColumn> <asp:BoundColumn DataField="HireDate" SortExpression="HireDate" HeaderText="Hire Date"></asp:BoundColumn> <asp:BoundColumn DataField="Job" SortExpression="Job" HeaderText="Job Description"></asp:BoundColumn> <asp:BoundColumn DataField="Publisher" SortExpression="Publisher" HeaderText="Publisher"></asp:BoundColumn> <asp:BoundColumn DataField="City" SortExpression="City" HeaderText="City"></asp:BoundColumn> <asp:BoundColumn DataField="State" SortExpression="State" HeaderText="State"></asp:BoundColumn> <asp:BoundColumn DataField="Country" HeaderText="Country"></asp:BoundColumn> </Columns> <PagerStyle PageButtonCount="20" Mode="NumericPages"></PagerStyle> </asp:datagrid>
To keep the demonstration simple, the DataGrid is bound to an XML data source, as shown below.
<?xml version="1.0" encoding="utf-8" ?> <EMPLOYEE> <EmpData FirstName="Paolo" LastName="Accorti" HireDate="1992-08-27" Job="Sales Representative" Publisher="Binnet Hardley" City="Washington" State="DC" Country="USA" /> <EmpData FirstName="Pedro" LastName="Afonso" HireDate="1990-12-24" Job="Designer" Publisher="Algodata Infosystems" City="Berkeley" State="CA" Country="USA" /> <EmpData FirstName="Victoria" LastName="Ashworth" HireDate="1990-09-13" Job="Managing Editor" Publisher="Binnet Hardley" City="Washington" State="DC" Country="USA" /> </EMPLOYEE>
When a sort expression of a bound column is set, the DataGrid renders the header of the column as a link button. Upon clicking such a link, the DataGrid’s SortCommand event fires and supplies the sort expression as part of the event argument.
The code for the SortCommand event handler is shown below. In a nutshell, the event handler determines a combined sort expression, sets up a DataView on the current data source with that sort expression, and binds the DataGrid to the DataView.
/// <summary> /// Datagrid Sort Event Handler -> Determine a combined sort expression and apply. /// </summary> private void dgEmployee_SortCommand(object source, System.Web.UI.WebControls.DataGridSortCommandEventArgs e) { DisplaySortedData(GetSortExpression(dgEmployee,e)); } /// <summary> /// Apply the Sort Expression to the retrieved data and bind to datagrid /// </summary> private void DisplaySortedData(string sortExpression) { sortExpression = sortExpression.TrimEnd(",".ToCharArray()); DataSet dsEmployees = new DataSet(); dsProjects.ReadXml(Server.MapPath("Employee.xml")); DataView dvEmployee = new DataView(dsEmployees.Tables[0]); dvEmployee.Sort = sortExpression; dgEmployee.DataSource = dvEmployee; dgEmployee.DataBind(); }
The key to implementing the multi-column sorting is to remember the previously clicked sort columns, and to establish the new sort criteria whenever a sort column header is clicked.
In this example the sort expression is persisted between requests in a custom DataGrid attribute named SortExpression. When the page is rendered to the browser this attribute becomes the attribute of the TABLE HTML element and is simply ignored by the browsers.
Another way of persisting the sort expression would be to keep it in a ViewState variable. This method is recommended if the DataGrid itself is not kept in ViewState due to large amounts of data.
The GetSortExpression method shown below implements the functionality to evaluate a new sort expression each time a sortable column header is clicked. It starts by examining the custom SortExpression attribute of the DataGrid to see if a previous sort expression exists. If the current sort column exists within this expression, then its sort order is toggled appropriately or the sort column is removed from the sort expression. Otherwise, the sort column is added to the SortExpression attribute.
private string GetSortExpression(DataGrid dg,DataGridSortCommandEventArgs e) { string[] sortColumns = null; string sortAttribute = dg.Attributes["SortExpression"]; //Check to See if we have an existing Sort Order already in the Grid. //If so get the Sort Columns into an array if ( sortAttribute != String.Empty) { sortColumns = sortAttribute.Split(",".ToCharArray()); } //if User clicked on the columns in the existing sort sequence. //Toggle the sort order or remove the column from sort appropriately if (sortAttribute.IndexOf(e.SortExpression) > 0 || sortAttribute.StartsWith(e.SortExpression)) sortAttribute= ModifySortExpression(sortColumns,e.SortExpression); else sortAttribute += e.SortExpression+" ASC,"; dg.Attributes["SortExpression"] = sortAttribute; return sortAttribute; } private string ModifySortExpression(string[] sortColumns,string sortExpression) { string ascSortExpression = String.Concat(sortExpression," ASC"); string descSortExpression = String.Concat(sortExpression," DESC"); for (int i=0; i< sortColumns.Length; i++) { if (ascSortExpression.Equals(sortColumns[i])) { sortColumns[i] = descSortExpression; } else if (descSortExpression.Equals(sortColumns[i])) { Array.Clear(sortColumns,i,1); } } return String.Join(",",sortColumns).Replace(",,",",").TrimStart(",".ToCharArray()); }
The paging event handler calls upon the same method to display the data in sorted order, after setting up the current page index.
/// <summary> /// Datagrid Page Changed Event Handler -> Apply the Currently Selected sort expression. /// </summary> private void dgEmployee_PageIndexChanged(object source, System.Web.UI.WebControls.DataGridPageChangedEventArgs e) { dgEmployee.CurrentPageIndex = e.NewPageIndex; DisplaySortedData(dgEmployee.Attributes["SortExpression"]); }
One more important aspect of this example is to display the current sorting order in the DataGrid header in a manner that is easy to recognize visually. This is accomplished by inserting an image and number next to the sorted column.
This is implemented in the ItemDataBound event handler of the DataGrid when binding the header item.
private void dgEmployee_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e) { if (e.Item.ItemType == ListItemType.Header) { if (dgEmployee.Attributes["SortExpression"] != String.Empty) DisplaySortOrderImages(dgEmployee.Attributes["SortExpression"] ,e.Item); } }
The method DisplaySortOrderImages loops through the cells of the header item. If any of the cells contain a link button, it then looks up its command argument (which is same as that columns sort expression) within the DataGrid’s SortExpression attribute and inserts an image and a literal (for the sequence number) next to the cell.
private void DisplaySortOrderImages(string sortExpression,DataGridItem dgItem) { string[] sortColumns = sortExpression.Split(",".ToCharArray()); for (int i=0; i< dgItem.Cells.Count; i++) { if(dgItem.Cells[i].Controls.Count > 0 && dgItem.Cells[i].Controls[0] is LinkButton) { string sortOrder; int sortOrderNo; string column = ((LinkButton)dgItem.Cells[i].Controls[0]).CommandArgument; SearchSortExpression(sortColumns,column,out sortOrder,out sortOrderNo); if (sortOrderNo > 0) { if (sortOrder.Equals("ASC")) { Image imgUp = new Image(); imgUp.ImageUrl = "~/images/sortascending.gif"; dgItem.Cells[i].Controls.Add(imgUp); Literal litOrder = new Literal(); litOrder.Text = sortOrderNo.ToString(); dgItem.Cells[i].Controls.Add(litOrder); } else if (sortOrder.Equals("DESC")) { Image imgDown = new Image(); imgDown.ImageUrl = "~/images/sortdescending.gif"; dgItem.Cells[i].Controls.Add(imgDown); Literal litOrder = new Literal(); litOrder.Text = sortOrderNo.ToString(); dgItem.Cells[i].Controls.Add(litOrder); } } } } } private void SearchSortExpression(string[] sortColumns,string sortColumn, out string sortOrder,out int sortOrderNo) { sortOrder = ""; sortOrderNo = -1; for (int i=0; i< sortColumns.Length; i++) { if (sortColumns[i].StartsWith(sortColumn)) { sortOrderNo = i+1; sortOrder = sortColumns[i].Substring(sortColumn.Length).Trim(); } } }
Conclusion
To summarize, we've taken advantage of the DataView's sorting capabilities to implement multi-column sorting on the DataGrid. Full source code is included in the downloadable sample. It also shows how to write the sort expression to the browser status bar by registering a client-side Javascript function. Enjoy, and be sure to let me know if you find this article applicable to your project scenario.
User Comments