Home > Windows Development > Reusable ListView Sorting – The Easy Way

Reusable ListView Sorting – The Easy Way

This week I finally decided to write the code to allow users to sort WPF ListView controls by clicking the headers. Of course, my first stop was Bing with WPF & ListView & sort as the search term. This yielded about 186,000 results. Three results on the first page were each useful in a unique way, but they each left something to be desired. They were not flexible enough, had too much code behind or required sub-classing controls. This situation led me to try to roll the good parts of each article into one piece of easy to maintain and easy to use code.

To give credit where due, my starting points were articles by Josh Twist, Michael Kuehl and Michael Brown.  I really liked Michael Brown’s concept of providing attached properties to control the sorting and the fact that his solution only had extra markup on the ListView itself instead of on every column in the associated GridView. I liked the use of the Adorner by Kuehl. Josh’s article was just a good introduction to the problem and an overview of the solution and the first to introduce me to the SortDescription class.

My static class, ListViewSorter, provides read/write and read-only attached properties to control and report on the current sort. In the default use it is invoked with a single piece of XAML, ListViewSorter.IsSortable=”true”. In most cases that is all that is required to properly sort on a single column and best of all, no extra code. You may also indicate that a given column is not sortable or you may override the default property for sorting.

The key to this ease of use is in the liberal use of attached properties. You should notice that some of the properties are attached to the ListView type and others are attached to the GridViewColumnHeader type.

The IsSortableProperty is the starting point. It is set on a ListView control to indicate that it allows sorting. The changed event for this property either adds or removes the Click event handler. Thanks to Michael Brown for this trick.

static private void OnIsSortableChanged (DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
  ListView         lv = obj as ListView;

  if (obj != null)
  {
    if (e.NewValue is bool)
      if ((bool)e.NewValue)
        lv.AddHandler(GridViewColumnHeader.ClickEvent,
          new RoutedEventHandler(OnColumnHeaderClick));
     else
       lv.RemoveHandler(GridViewColumnHeader.ClickEvent,
         new RoutedEventHandler(OnColumnHeaderClick));
  }
} /* method ListViewSorter OnIsSortableChanged */

The click event handler will find the associated ListView and only continue if the ListView is still marked as sortable.

static public void OnColumnHeaderClick (object sender, RoutedEventArgs e)
{
  GridViewColumnHeader   gvch = e.OriginalSource as GridViewColumnHeader;
  ListView               lv;

  if (gvch != null && GetAutoSort(gvch))
  {
/*
*   Make sure the ListView is still marked sortable
* */
  if (((lv = GetListViewFromColumnHeader(gvch)) != null) &&
    GetIsSortable(lv))
    SortByColumn(gvch, lv); }
} /* method ListViewSorter OnColumnHeaderClick */

 

The SortByColumn (GridViewColumnHeader, ListView) method (see the attached code) does all the actual work for setting up the adorner and sorting. It uses the read-only attached properties to track last column sorted and the sort direction of each column. It is intentionally designed to remember to toggle the sort direction on each click of a column instead of only on consecutive clicks of a column. In other words, if column A is sorted descending and then column B is clicked the next click of column A will sort it ascending.

The SortByColumn (GridViewColumnHeader) method (see the attached code) is supplied as a simple entry from external code to sort on a column. This method bypasses all the checks for allowing sorting.

An up and down arrow is placed into the column header by using an Adorner. This seems simpler than the switching column header templates to include the correct arrow display method shown in several articles. The template solution could be more flexible, but of course, the arrow could be put into a resource instead of being hardcoded as in my solution. Just in case you are wondering what I did, I got a little fancy and produced an arrow that fades in from the top to the bottom. I am not sure if it is a good user experience or not, but it was fun to do and it looks cute. The hardest part of the Adorner is doing the display.

protected override void OnRender (DrawingContext drawingContext)
{
  base.OnRender(drawingContext);
  drawingContext.PushTransform(new TranslateTransform(AdornedElement.RenderSize.Width-14,
    (AdornedElement.RenderSize.Height-18)/2.0));
  drawingContext.DrawGeometry(ArrowBrush, null, sda_render_geometry);
  drawingContext.Pop();
} /* method SortDirectionAdorner OnRender */

I cannot take too much credit for this code. Kuehl presented the basic idea. The transform moves the arrow to the right side of the column and the vertical center of the header. (Note that if the right of the column is not visible, the arrow will not be visible either.) The constants in the transform represent the fact that my arrow is an isosceles triangle with the base of four and a height of eight centered in a (non-displaying) rectangle 14 wide and 18 high.

I think I reached my goal of ease of use. I applied this code to six ListView controls in about 1 minute by simply copying and pasting ListViewSorter.IsSortable=”true” into each ListView in my project.

The Code is here

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: