Click here to Skip to main content
15,881,852 members
Articles / Programming Languages / C#
Article

An extendable ListViewSorter

Rate me:
Please Sign up or sign in to vote.
4.65/5 (13 votes)
12 Oct 2006CPOL5 min read 62.8K   1.1K   52   6
This article describes how to sort a ListView ascending/descending with two lines of code, and how to extend its sorting capabilities.

Sample Image

Introduction

Recently, I had to face the exciting challenge of sorting a ListView. So I searched CodeProject and other sites, but what I found was mostly too sophisticated or complex for my simple mind. I did not want to write inherited controls or loads of lines of code in the Form - just to sort a ListView!

The ListView itself provides a sort function if an IComparer implementation is assigned to the property ListViewItemSorter. So there must be a simple way to use this function. Thus, I tried to find a design for an extendable, easy-to-use ListViewSorter by myself and, eventually, the article Extended ListView by Michiel van Eerd at CodeProject kicked off my inspiration.

Next, you will find a bunch of explanations and diagrams. If you just want to know how to use the code, jump to the Using the code-section - it's shorter. Finally, if you want to know why it works - come back here.

Warranty

This article and the provided code do not claim to be complete or perfect! Rather it is an attempt to solve the given problem.

Design

Until I found the mentioned article, I was not really aware of the functions Decimal.TryParse(String, Decimal) or DateTime.TryParse(String, DateTime). These functions allow you to try (!) to convert nearly everything into Decimal or DateTime without headaches. Even these methods are a bit dirty, they work sufficient for sorting purposes (in my opinion).

With a knowledge about these methods, I had the idea to write a group of ISortComparers which can be assigned to certain columns of a ListView at runtime. The ISortComparer interface extends the IComparer only by the property SortOrder.

Sample Image

The intention of the design was not to couple the comparer directly to the ListView, finally they shall be usable for all kinds of collections. So they stand independent from the System.Windows.Forms namespace, and an adapter mechanism is needed.

To make life easier, the ISortComparer implementations can inherit from the abstract class SortComparerBase which provides the common functionalities like holding the SortOrder property or the null-value handling. The abstract method Compare has to be implemented by the concrete implementations.

Image 3

As an example, a short commendation of the DateComparer:

C#
public class DateComparer : SortComparerBase{

  public override int Compare(object x, object y)
  {
    // 1.
    if (base.sortorder == SortOrder.None)
      return 0;

    // 2.
    if (base.HasEmptyValue(x, y))
      return base.CompareEmpty(x, y);

    // 3. 
    DateTime x1 = DateTime.MinValue;
    DateTime y1 = DateTime.MinValue;

    // 4.
    if (DateTime.TryParse(x.ToString(), out x1) && 
        DateTime.TryParse(y.ToString(), out y1))
    {
      // 5.
      if (base.sortorder == SortOrder.Ascending)
        return DateTime.Compare(x1, y1);
      else
        return DateTime.Compare(y1, x1);
    }
    else
    {
      return 0;
    }
  }
}

Comments to the code above:

  1. If there is nothing to sort, everything is equal.
  2. Handle the case if at least one of the values is null or empty (these methods are implemented in the SortComparerBase).
  3. The DateTime.TryParse method will write its (successful) results into this variables. In case of failure, the current values of this variable will not be overwritten. So set them to values which imply a position after sorting.
  4. If a TryParse method fails (e.g., because the value cannot be converted into the required Type) it returns false. If this happens, 0 (equal) will be returned.

This adapter to connect the ISortComparers to the ListView was realized by the class ListViewSorter which holds a reference to the ListView and handles the ListView's ColumnClick event. It also holds a Dictionary with ISortComparers for each column. As mentioned, the ISortComparers do not know a class called ListView or its items. Hence, a mediator or proxy was implemented which connects the ListView with specific ISortComparer implementations: the ListViewItemComparer. This class holds the to-be sorted column as well as the specified ISortComparer for this column. Since it implements the IComparer interface, it can be set as the ListView's ListViewItemSorter property.

Image 4

When the sort routine of the ListViewSorter is called by the TreeView's ColumnClick event, a new ListViewItemComparer instance is created, and its Comparer property is set by the columns comparer-dictionary entry. Then the ListViewItemComparer is set as the ListView's ListViewItemSorter property, and ListView.Sort() is called. That's it.

Using the code

After many words in theory, now less words in practice. Actually, only two lines of code are required to sort a ListView using arbitrary columns.

C#
public partial class Form1 : Form
{
  // Required line 1: A ListViewSorter member
  ListViewSorter listviewsorter = new ListViewSorter();
  
  //...

  private void Form1_Load(object sender, EventArgs e)
  {
    // Required line 2: Set the ListViewSorters ListView property
    listviewsorter.ListView = this.listView1;

    // Optional: Register specific comparers for each column
    listviewsorter.ColumnComparerCollection["Int"] = new NumericComparer();
    listviewsorter.ColumnComparerCollection["Decimal"] = new NumericComparer();
    listviewsorter.ColumnComparerCollection["Date"] = new DateComparer();    
  }

  //...
}

Note: In the above example, the comparer for the String-column is left out because the StringComparer is the default comparer.

If you want to display specific images on the column headers indicating the sort column and the order, the ListViewSorter provides the properties ImageKeyUp and ImageKeyDown to define the keys of the images of the ListView's SmallImageList property.

Extensibility example

If you are in need for a specific comparer, it should be no problem to write your own ISortComparer implementation and extend the functionality of the ListViewSorter. In the demo application, I implemented a GermanPostalCodeCityByCityComparer and a GermanPostalCodeCityByPostalCodeComparer (couldn't find shorter names) as examples.

In Germany, each area has a unique five digit postal code. It is common usage to display an address' city part as a combination of the postal code and the city's name, separated by a space (e.g., "22307 Hamburg"). Three meaningful scenarios to sort this combinations could be:

  1. alphanumeric by the complete token
  2. alphanumeric by the city's name
  3. numeric by the postal code

The GermanPostalCodeCityByCityComparer matches b. and sorts the items by the city's name ("Hamburg", "Berlin",...). The GermanPostalCodeCityByPostalCodeComparer matches c. and sorts the items numeric by the postal code ("01465", "22307", ...). For option c., the default StringComparer can be used.

I guess these comparers are not absolutely necessary for most of you, but they can give an idea of what ISortComparers can be useful for. If you read the code, you will see how easy it is to enhance the sorting capabilities.

Challenge: Not implemented are comparers which sort first by name and then by postal code or vice versa. Or comparers which can sort various columns in a certain order...

Points of Interest

SortOrder: There is an enumeration with the same name in the System.Windows.Forms namespace. Since the use of something like a sort-order should not force the import of that namespace, it became necessary to include a SortOrder enumeration in a more common namespace. Like Microsoft, it is my opinion that SortOrder describes its meaning perfectly, and I refrained from naming it SortOrderX.

Namespaces: As mentioned somewhere above, it was not my intention to couple the comparers directly to the ListView and System.Windows.Forms namespaces. Thus, I separated them and the ListViewSorter in their own namespaces.

A look ahead

The mechanism described in this article should be sufficient to sort Collections as well. Provided the necessary time, I will update this article or write a separate one regarding this subject.

History

  • 2006-10-12 - First published.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Germany Germany
I am working for a small software company in Hamburg, Germany, and my day-to-day distress is Java development.

Comments and Discussions

 
GeneralNice Pin
mig1624-Nov-09 21:26
mig1624-Nov-09 21:26 
GeneralMy vote of 2 Pin
BFSer13-Jan-09 4:58
BFSer13-Jan-09 4:58 
GeneralRe: My vote of 2 Pin
Marcus Deecke16-Oct-09 10:54
Marcus Deecke16-Oct-09 10:54 
AnswerColumn glyph bug Pin
Jonathan Laloz2-Jul-08 12:32
Jonathan Laloz2-Jul-08 12:32 
GeneralGood work Pin
Victor Ionescu15-Jan-07 9:28
Victor Ionescu15-Jan-07 9:28 
The only inconvenient is that the first column is displaced to the right by 16 pixels. I can't believe the API of .NET, the property ImageList of the column header is readonly and points to the SmallImage property of the ListView.
AnswerRe: Good work Pin
Marcus Deecke4-Feb-07 16:59
Marcus Deecke4-Feb-07 16:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.