When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article
Jobs

ASP ASP.NET ASP FAQs Message Board Feedback ASP Jobs
 
Print this Page!
Published: Wednesday, February 6, 2008

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.

A down arrow image is shown in the Price column because the data is sorted in descending order, by price.

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!

- continued -

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 &lt;img&gt; 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.

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:

protected override void PrepareControlHierarchy()
{
   base.PrepareControlHierarchy();

   if (this.HasControls())
   {
      if (!string.IsNullOrEmpty(this.SortExpression) && this.ShowHeader)
      {
         Table table = this.Controls[0] as Table;

         if (table != null && table.Rows.Count > 0)
         {
            // Need to check first TWO rows because the first row may be a
            // pager row... Thanks for Barbaros Saglamtimur for this catch!
            GridViewRow headerRow = table.Rows[0] as GridViewRow;
            if (headerRow.RowType != DataControlRowType.Header && table.Rows.Count > 1)
               headerRow = table.Rows[1] as GridViewRow;

            if (headerRow.RowType == DataControlRowType.Header)
            {
               foreach (TableCell cell in headerRow.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:

GridViewHeaderSortA {background: url(/images/icons/arrow-up.gif) no-repeat 95% 50%; }
GridViewHeaderSortD {background: url(/images/icons/arrow-down.gif) no-repeat 95% 60%; }

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 (this.SortDirection == SortDirection.Ascending)
   {
      imgUrl = this.ArrowUpImageUrlInternal;
      sortStyle = _sortAscendingStyle;
   }
   else
   {
      imgUrl = this.ArrowDownImageUrlInternal;
      sortStyle = _sortDescendingStyle;
   }

   Image arrow = new Image();
   arrow.ImageUrl = imgUrl;
   arrow.BorderStyle = BorderStyle.None;
   sortedCell.Controls.Add(arrow);

   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.

An up arrow image is shown in the Price column because the data is sorted in ascending order, by price. Also, the header cell's background is a dark purple color.

A yellow background is used when the data is sorted in ascending order.

A down arrow image is shown in the Name column because the data is sorted in descending order. Also, the header cell's background color is yellow.

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.

Happy Programming!

  • By Scott Mitchell


    Attachments


  • Download the Complete Source Code (in ZIP format)
  • Further Readings:

  • Working with Data in ASP.NET 2.0
  • Extending the GridView to Include Sort Arrows


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article