ListViewColumnSorter

On my previous blog(the one on ‘that’ site that just went AWOL…) I had an article and download link to a class that I often used for making sorting easier in a ListView control. It drew some attention even with some people at Microsoft themselves.

Of course times have passed and even MS these days seems to be moving away from Winforms but I have yet to make any real effort to start moving to WPF – there are reasons for this… Anyway,  I’m sure there are people that still use Winform development and a class like this one could be very useful.

Functionality

Since the original I have made some changes (hopefully improvements). For those that don’t remember or missed it originally, here is a list of (the original) functionality that it provides:

  • It implements the standard IComparer interface that is required for making a custom class for sorting a ListView control (in detail view).
  • It defines some standard types of sorting (plain string, number, date, byte sizes, inverted strings and number/string mix)
  • It can automatically ‘bind’ to the column clicking event meaning you don’t have to write a single line of code to do the sorting stuff yourself (provided you use one of the provided standard sorting types)
  • It adds the little triangle icons to the clicked column header indicating sort direction.
  • It allows for disabling/enabling of sorting. This helps while you are loading the control and do not want the sorting/comparing routines firing for each insert!
  • Using it is very easy. All it requires to get started is a declaration to it and instantiation. That is 2 lines of code you have to write to use and bind it!

Since the original version I now have added the following functionality:

  • Added 2 new column ‘types’ that are not actually types but a way to add behavior. These are ‘Override’ and Deferred. More on the details on what they do later.
  • The ListView control has the ability to use ‘Groups’ so new functionality has been added to make use of it.

Usage

Like mentioned above to use it is really easy. All you need are ‘2’ lines of code like this:

private ListViewColumnSorter lvwSorter;

private void MainForm_Load(object sender, EventArgs e)
{

//The following lines are actually all one line wrapped for easier reading

lvwSorter = new ListViewColumnSorter(lvwMyListView, true,
SortColumnTypes.StringType,
SortColumnTypes.NumberType,
SortColumnTypes.DateType,
SortColumnTypes.ByteSize,
SortColumnTypes.Override,
SortColumnTypes.Deferred);

}

That is all that is required to get it up and running.

New functionality

Except for the ListView grouping functionality (which I’m not going to cover for now) there are two new related things that have been added to to the control – Override and Deferred sorting.

Deferred sorting

Sometimes you need to sort the ListView based on some other criteria than the displayed column that the user has clicked on. It could be that the displayed values are incomplete or that values in other columns might have to be taken in consideration as well. You may even want to do some external (to the ListView control) look-up in the sorting process. Deferred sorting is the solution to this problem.

The way this functionality work is by using delegates. This way the ListViewColumnSorter control effectively allows you to provide your own custom function that it will call during the sorting process. Keep in mind it will do this for every row in the ListView in the sorting process!

The delegate requires your method to accept 3 paramerters – column number, list view item 1 and list view item 2. As you can guess the column number is the column clicked on and the 2 list view items the items (rows) that must/can be compared. I often attach a custom class to the tag property of the list view item that contains more properties that what is displayed in the ListView. This way you can use the custom class as part of the comparison process. Yes, the ‘unboxing’ of the classes from type object like stored in the tag property does add some overhead so you’re not going to be using this on huge lists (not if you are doing some kind of complex sorting anyway!)

Example code

This example sample simply shows how the deferred method works. It must return the same -1/0/1 return value that the CompareTo method expect.

Somewhere (e.g. in the Form_Load event you can set:

lvwSorter = new ListViewColumnSorter(lvwListView, true, SortColumnTypes.Deferred, …);

lvwSorter.DeferredCompare = DeferredCompare;

Then somewhere else you can define the following method

private int DeferredCompare(int columnToSort, ListViewItem x, ListViewItem y)
{

if (columnToSort == 0)

{

CustomClass customClassX = (CustomClass)x.Tag;

CustomClass customClassY = (CustomClass)y.Tag;

return customClassX.SomeProperty.CompareTo(customClassY.SomeProperty);

}

else

return 0; //or whatever

}

Override

This is actually not really a way to sort the control at all but really to capture the column click event and call a custom method you provide and do anything else (including sorting or reloading the ListView control). This could be useful if you need to do some kind functionality that is too complex for sorting or got nothing to do with sorting at all.

As with deferred sorting this is done by delegates (actually an event in this case). You provide a method that the control will call when that column is clicked.

Example code

Somewhere in the Form_Load event you can set:

lvwSorter = new ListViewColumnSorter(lvwListView, true, SortColumnTypes.Override, …);

lvwSorter.OverrideColumnClicked += new OverrideColumnClickedDelegate(lvwSorter_OverrideColumnClicked);

Then you can define a method like this:

private void lvwSorter_OverrideColumnClicked(int column, SortOrder sortOrder)
{

if (column == 0)

{

DoSomethingElse();

}

}

Download

You can find a copy of the source code here.

 

  1. I am having an issue where the sorter crashes the application after I sort the list and then try to add an new entry. It is giving an invalid ‘index’. I am hoping you can advise how to bypass the sorter when updating the list. When I step through it goes through the sorter for each entry.

    Thanks,
    Mark
    l.mark.hart@gmail.com

    • Can you perhaps specify a bit more one how/what you are adding? Have you looked at the Enable/Disable methods?
      Generally I call the Disable method when adding list view items to avoid it trying to sort the whole listview each item I add. Also, try using the AddRange() method of the ListView control if you can – it help alot when there are a large number of rows to add.

  2. Thank you for the quick response. I did not realize that I was using an older version of the code. I updated to your current version and now do not seem to have any crashes when sorting and adding.

    One additional question. Is their a way to speed up the sort? I currently only am sorting 50 records and it takes a noticable amount of time and I am concerned that as the number of records grow so will the sort time. The previous version sorted much faster but also did not have the features of the newer version. Any suggestions?

    Thanks again.

    Mark
    L.Mark.Hart@gmail.com

    • I’ve noticed that if you specify a wrong data/column type or wrong number of columns it seems to be very slow – this happens because it then basically does a ‘default’ generic sort after first trying the wrong type (which can throws an exception which might be ignored internally) which is really slow.
      If set up properly it can sort lists with thousands of rows.
      The easiest place to set up the column types is on the constructor .e.g.

      lvSorter = new ListViewColumnSorter(lvwFiles, true, SortColumnTypes.StringType, SortColumnTypes.NumberType, SortColumnTypes.DateType, SortColumnTypes.ByteSize, …);

  3. Thanks I made the changes to the constructor and now it flies.

  4. Rudolf,
    I have a few more questions.

    How can I get it to look as if their were no sort at all. So the listview looks as if it were just loaded, with out having to reload the listview.

    Is it possible to sort both asc and desc. So that one click sorts ASC and a second click sorts desc.

    Thanks,
    Mark

    • For the first question I doubt the current implementation can do it – you’ll have to have the control repaint itself not showing the sorting triangles showing etc.
      For the second – that is the default behavior. If you click a column the first time the control should sort asc and if you click the same column again it will swap around for desc.

  5. thanks. I am having issues with the sort now. It does not seem to want to sort in the reverse order. Will you please point me to the code that controls the sort so that I can troubleshoot against my application.

    Thanks

    • Best is to set a breakpoint in the Compare method of ListViewColumnSorter.cs to see what/how it is ordering.

Leave a Reply

%d bloggers like this: