Improving the Sort Arrows GridView Control
By Scott Mitchell
Introduction
While the GridView control offer built-in, bi-directional sorting support, there is no visual feedback as to what column the data is sorted by.
To remedy this shortcoming, I showed how to create a custom server control that extended the GridView class and automatically added arrow up and down
images to the appropriate column header, thereby clearly showing what column the data was sorted by and whether the data was sorted in ascending or
descending order. Read Extending the GridView to Include Sort Arrows for more
information on this technique.
Since publishing that article, I have received a lot of great feedback and suggestions for improving the control. 4Guys reader Richard Deeming proposed
a better technique for adding the arrow images to the appropriate GridView header cell, one that removed the hacks that my approach required. Another
great suggestion came from Tom Mason, who proposed using a differing CSS class in lieu of <img> elements in the header. Tom's ideas
prompted me to add two new properties to the custom GridView control: SortAscendingStyle and SortDescendingStyle. Use these
properties to customize the appearance of the sorted header cell.
Read on to learn more about these enhancements!
Please Read Extending the GridView to Include Sort Arrows First
If you have not year read the original article, Extending the GridView to Include Sort Arrows,
please do so before continuing with this one. The rationale behind designing this control, as well as instructions on how to use the control, are
detailed in the original article. This article focuses on the enhancements made since then.
When to Add the Up/Down Arrow Images
When I first created the custom GridView control, I struggled with where to put the logic to dynamically add the sort arrow images to the appropriate
GridView header cell. As noted in Extending the GridView to Include Sort Arrows, my first attempt was to use the GridView's
InitializeRow method, but that
was not feasible for a number of reasons. I ended up settling on applying the logic in an override of the OnSorted method.
Additionally, the way I added the up or down arrow image was less than ideal. I injected the necessary HTML markup directly into the header cell's
HeaderText property. This has two disadvantages:
Since the HeaderText value is persisted in view state, the up/down arrow image markup was persisted in view state, as well. Not only
did this add a bit of extra bloat to the renderd page's size, but it also necessitated removing the markup. For instance, when a user sorts the results
by, say, ProductName, my code added an <img> tag to that column's HeaderText. If the user then sorts
the results by UnitPrice, not only does my code need to add an <img> tag to the UnitPrice column, but it
also needs to remove the <img> tag from the ProductName column's HeaderText.
For BoundFields, HTML markup in the HeaderText is escaped if the BoundField's HtmlEncode property is set to True (the default).
Consequently, by default the <img> tag would get escaped to <img> and show up in the header cell as text.
My "solution" was a bit of a hack: I created a custom BoundField control that did not HTML encode the HeaderText.
Fortunately, there is a better approach, as alert 4Guys reader Richard Deeming kindly pointed out to me. The GridView has a
PrepareControlHierarchy method
that runs every time the GridView is rendered (regardless of whether data has been bound to the control from a data source, or if it was reconstructed
from the control's view state) and executes after the GridView's control hierarchy has been built. The purpose of this method is to apply the various
styles - RowStyle, AlternatingRowStyle, HeaderStyle, and so on - to the Table and TableRows that makeup the GridView's
rendered output.
What Richard suggested was that I override this method instead of OnSorted; moreover, he suggested that instead of squishing in the <img>
markup, that I instead add an Image Web control to the appropriate header cell. What perfect advice! This solves the two problems noted above: by adding
the image as an Image Web control (instead of markup in the HeaderText property), the Image is not saved in view state, so there's no need
to remove "old" Image controls from other columns. Now, if an item is not saved in view state then it must be added back to the control hierarchy on
every postback. Because the PrepareControlHiearchy method executes on every postback, we're guaranteed to get the arrow image added
back into the appropriate cell. Furthermore, by using an Image Web control and adding it late in the GridView's lifecycle, we remove the need for a
special BoundField class to bypass HTML encoding the header.
From the Internet.com eBook Library: Navigating Your IT Career
A career in information technology usually has its share of
ups and downs. Download this Internet.com eBook to learn
where the jobs are in IT, how to negotiate a salary, and
helpful advice on job security and how to deal with a layoff.
Join Internet.com now to download! http://www.devx.com/ebook/Link/34938
Interested in placing your TEXT AD HERE? Click Here
To apply these changes, I removed the BoundField class from the skmControls2 assembly and commented out the overridden
OnSorted method. I then overrode the PrepareControlHierachy method and added the following code:
if (this.HasControls())
{
if (!string.IsNullOrEmpty(this.SortExpression))
{
Table table = this.Controls[0] as Table;
if (table != null && table.Rows.Count > 0)
{
GridViewRow firstRow = table.Rows[0] as GridViewRow;
if (firstRow.RowType == DataControlRowType.Header)
{
foreach (TableCell cell in firstRow.Cells)
{
DataControlFieldCell gridViewCell = cell as DataControlFieldCell;
if (gridViewCell != null)
{
DataControlField cellsField = gridViewCell.ContainingField;
if (cellsField != null && cellsField.SortExpression == this.SortExpression)
{
// Add the sort arrows for this cell
CreateSortArrows(cell);
// We're done!
break;
}
}
}
}
}
}
}
}
The method starts by calling the base class's PrepareControlHierarchy method. Next, if there are controls in the GridView's rendered
output and the first row in the Rows collection is a header row, then the cells in the row are enumerated. If a cell whose SortExpression
matches the GridView's current SortExpression is found, then the CreateSortArrows method is called, passing in the
TableCell that corresponds to the header row cell that the data is sorted by. The CreateSortArrows method, which we'll examine
in a moment, is responsible for adding the up or down arrow image.
After the arrows have been added, we can exit the foreach loop since the GridView can be sorted by at most one column.
Before we look at the CreateSortArrows method, let's discuss the final enhancement I made to the GridView class - two new
properties for specifying additional style information for the sorted header cell.
Specifying a Style for the Sorted Header Cell
Another suggestion I received for the GridView control came from Tom Mason, who noted that the arrow images can be added entirely using CSS classes
rather than <img> elements. Tom noted that in some of his projects he created the following two CSS classes:
A table cell with the CSS class GridViewHeaderSortA will display the arrow-up.gif image as a background. Using Tom's approach,
we could omit adding the up or down arrow and instead just set the cell's CssClass property to the appropriate CSS class, depending on
whether the column was sorted in ascending or descending order.
This suggestion got me thinking, "Why not allow the user to specify any style settings for the sorted header cell?" Granted, all style information
could (and probably should) be set through a CSS class, but you might want to add a quick and dirty style setting without having to create a CSS class.
To accommodate this, I added two new TableItemStyle properties to the GridView class: SortAscendingStyle and
SortDescendingStyle. The TableItemStyle
class has properties like BackColor, ForeColor, Font, and CssClass, and can be used to customize
the appearance of the sorted header cell.
The demo available at the end of this article includes an example that sets the BackColor and ForeColor
properties of the SortAscendingStyle and SortDescendingStyle properties so that the sorted header cell is shaded differently
from the other cells, and shaded differently if its sorted in ascending or descending order.
Adding the Image and Style to the Sorted Header Cell
As we already discussed, the PrepareControlHierarchy method is responsible for locating the header cell that the GridView's data is sorted
by (assuming it is sorted). It then passes off this TableCell to the CreateSortArrows method, which is responsible for adding the
sort arrow image and applying the appropriate style. It's code follows:
protected virtual void CreateSortArrows(TableCell sortedCell)
{
// Add the appropriate arrow image and apply the appropriate state, depending
// on whether we're sorting the results in ascending or descending order
TableItemStyle sortStyle = null;
string imgUrl = null;
if (sortStyle != null)
sortedCell.MergeStyle(sortStyle);
}
The method starts by determining whether the data is sorted in ascending or descending order. The arrow image URL and sort style settings depend on the
sort direction. The sort arrow image is added by creating a new Image Web control, settings its ImageUrl and BorderStyle
properties, and then adding it to the header cell's Controls collection. The style information, if specified, has merged in with the header
cell's existing style settings.
The download at the end of this article includes a demo of this custom GridView control in action. The following two screen shots illustrate the
use of the two new properties. Note that the sorted column's header cell's colors differ from the non-sorted columns. The header cell for
a column sorted in ascending order has a dark purple background.
A yellow background is used when the data is sorted in ascending order.
Conclusion
This article looked at enhancing the custom GridView control initially created in an earlier article,
Extending the GridView to Include Sort Arrows. Based on feedback from
Richard, I updated the control to use an Image Web control for the up and down arrow images (as opposed to <img> markup) and moved
the logic to add the image to the GridView's PrepareControlHierarchy method. Feedback from Tom prompted me to add two new properties to
the control - SortAscendingStyle and SortDescendingStyle - which can be used to specify additional style information for
the header cell of the sorted column. The complete source code, plus a working demo, are available for download at the end of this article.