How To: Improve Column Sort Presentation for an ASP.NET GridView
I was recently browsing through some of the samples on the Ajax Rain site. I came across what I thought was a nice presentation for an HTML Table that supports sorting by clicking on the column headers. There are a 3 visual features that I like about this presentation that I am not using in the current web application I am working on:
1. Support for 3 different state icons within the column headers (not sorted, sorted ascending and sorted descending)
2. Allowing the user to sort by clicking anywhere within the column header instead of just on the header text
3. Change the header cell's background color (light blue in the screen shot below)
[Update 9/17/2007] Download link was broken. It is now fixed. Thanks Diego for pointing this out.
[Update 9/5/2007] Dave Ward pointed out a bug in my css that prevents this from working in FireFox and Safari. In FireFox and Safari, the background-repeat attribute needs to be set for my .sortascheaderstyle and .sortdescheaderstyle classes. I have updated the posting code samples, the live demo and download with this fix. Thanks Dave!
Adding these features to an ASP.NET GridView is pretty easy to do. Here is how you can incorporate it into your application ...
Add the GridView and the DataSource to the Page
The first step is to add the GridView and its corresponding DataSource to the page. For my example, I am using an ObjectDataSource for fetching the customer rows. Additionally, I have placed my GridView inside an UpdatePanel so only the GridView is refreshed when the user sorts on the different column headers. Here is the markup for this piece. The definitions for the CssClasses are at the end of the listing.
<asp:UpdatePanel runat="server" UpdateMode="Conditional"> <ContentTemplate> <asp:GridView ID="gvCustomers" runat="server" CssClass="tablestyle" AllowSorting="true" DataSourceID="odsCustomers" OnRowDataBound="GvCustomers_RowDataBound" AutoGenerateColumns="false"> <AlternatingRowStyle CssClass="alternatingrowstyle" /> <HeaderStyle CssClass="headerstyle" /> <RowStyle CssClass="rowstyle" /> <Columns> <asp:BoundField HeaderText="ID" DataField="customerid" SortExpression="customerid" /> <asp:BoundField HeaderText="Company" DataField="companyname" SortExpression="companyname" /> <asp:BoundField HeaderText="Contact Name" DataField="contactname" SortExpression="contactname" /> <asp:BoundField HeaderText="Contact Title" DataField="contacttitle" SortExpression="contacttitle" /> <asp:BoundField HeaderText="Country" DataField="country" SortExpression="country" /> <asp:BoundField HeaderText="Phone" DataField="phone" SortExpression="phone" /> </Columns> </asp:GridView> </ContentTemplate> </asp:UpdatePanel>
Handle the RowDataBound GridView Event
Next, in order to apply the different sort icons within in the header cells, we need to handle the RowDataBound event from the GridView so we can add the appropriate styling to the cell before it is rendered. Within the event handler, we first determine which column is being sorted by by seeing which column's SortExpression property matches the SortExpression of the parent GridView. Once we find the column, we can index into the GridViewRow's Cell's collection to set the CssClass for the header cell. The code for the event handler is as follows ... .
protected void GvCustomers_RowDataBound(object sender, GridViewRowEventArgs e) { GridView gridView = (GridView)sender; if (e.Row.RowType == DataControlRowType.Header) { int cellIndex = -1; foreach (DataControlField field in gridView.Columns) { e.Row.Cells[gridView.Columns.IndexOf(field)].CssClass = "headerstyle"; if (field.SortExpression == gridView.SortExpression) { cellIndex = gridView.Columns.IndexOf(field); } } if (cellIndex > -1) { // this is a header row, // set the sort style e.Row.Cells[cellIndex].CssClass = gridView.SortDirection == SortDirection.Ascending ? "sortascheaderstyle" : "sortdescheaderstyle"; } } }
Define the Style Classes
Now that all of the markup and code is in place, we just need to define the appropriate style classes. To support the feature of allowing the user to click anywhere within the column header, we need to apply the display:block style to the anchor's that are rendered within the column headers (thanks Tim Mackey for this tip). Additionally, because I am applying the sortascheaderstyle and sortdescheaderstyle classes to the header cells, we will need to define these classes as well. Here are the style definitions. The sort icon references are defined as background-images ...
.tablestyle{
font-family:arial;
margin:10px 0pt 15px;
font-size: 8pt;
border-color: #CDCDCD;
width:850px;
color: #3D3D3D;
}
.tablestyle td, .tablestyle th{
border-color: #CDCDCD;
}
.alternatingrowstyle{
background-color:#F0F0F6;
}
.headerstyle {
background-color:#F0F0F6;
background-image: url(img/sort_none.gif);
background-repeat: no-repeat;
background-position: center left;
padding-left: 20px;
}
.headerstyle a{
text-decoration:none;
color:black;
display:block;
}
.rowstyle{
background-color: #FFF;
}
.rowstyle td, .alternatingrowstyle td {
padding: 4px;
}
.sortascheaderstyle{
background-image: url(img/sort_asc.gif);
background-color: #8dbdd8;
background-repeat: no-repeat;
background-position: center left;
}
.sortdescheaderstyle{
background-image: url(img/sort_desc.gif);
background-color: #8dbdd8;
background-repeat: no-repeat;
background-position: center left;
} That's it. Enjoy!
Comments
Hey Mark,
Your gridview stuff is great. Have you given any thought to putting it all together with this column sorting, the column resizing and the paging controls at the bottom?
Hi Matt,
Sorry for messing your name up in the previous post. I was on my first cup of coffee or whatever. Anyways, I really enjoy your blog because you focus on the making components more usable and that's what my customers want and expect. Great ideas, great implementation.
--Geri
hey matt, so great your gridview stuff. it will be really greater if you make all of these things all together like a custom gridview control... :P
Nice article, but the download link doesn't work. Thank you.
@Diego -
Thanks for the heads-up. It should be fixed now.
Matt.
@Geri/@jack-
I have started this, just haven't had time to complete it. Hopefully soon ...
Matt.
Great article! This is the cleanest approach I've seen yet!
One thing I added so that the individual column header styles don't get overwritten, was to replace the last line of actual code with:
e.Row.Cells[cellIndex].CssClass = gridView.Columns[cellIndex].HeaderStyle.CssClass + (gridView.SortDirection == SortDirection.Ascending ? " sortascheaderstyle" : " sortdescheaderstyle");
2. Allowing the user to sort by clicking anywhere within the column header instead of just on the header text
strangely that part doesn't work. but works on the link. if i add a filter in the header, its possible to keep the sort?
regards
Great solution to the problem of how to show the user which column they are sorting by.
The only thing I had to change was that I have the AutoGenerateSelectButton set to "True", adding a column to the grid that was throwing the references off. I just added a simple +1 anywhere you reference columns.indexof.
I'm sure there's a more elegant approach then my solution but it works.
Hi,
How about sorting for customized column? Say, I add a column which is bounded value though RowDataBound Event and not retrieved from same data source.
Regards,
Hans
Hi,
I am trying to implement this. Just one problem. If my data column's size in gridview is small, then the title and the image in the header overlaps. If the column size is large, everything works fine.
Please help.
Thanks
Hello All,
I am wondering how to get the cellIndex when the columns of the grid are autogenerated? The "gridView.Columns" returns an empty collection of columns. Any help?
Thanks.
How would you generalize this code so your not copying and pasting this code in every gridview? I guess you could create a base class for all your pages and then put this code in a method and call that method from each gridview's RowDataBound method. Any other ideas? How would you do it?