Introduction
A long time ago (back in 2002), Eddie Velazquez published a little class to easily add column sorting to ListView
controls. The original article is here.
Several people (myself among them) requested some other features, especially the ability to sort by multiple columns at the same time. Although he fixed some bugs, Eddie did not have the time to add this bigger feature. One day I really needed it for a project and instead of waiting, I decided to roll up my sleeves and do it.
I had wanted to post it here for everyone else to benefit, but I had not given myself the time to write the article. But finally, here it is.
The most important feature I added to Eddie's version is multicolumn sorting. But I also added:
- Currency and IP address sorting. The IP sorter was taken from a comment by Eddie in the original article.
- Fixed a bug with the
SortEnabled
property
ListViewBottomItem
class for items that should always stay at the bottom (e.g. a total row).
To sort on several columns at once, click the first column, then hold down the Ctrl key and click on additional columns.
The demo project included with this article is for Visual Studio .NET 2003 and .NET 1.1, so it can reach a broader audience. I have used this class also with Visual Studio 2005 and .NET 2.0 and everything works fine as is.
Using the Code
I have tried to keep my version backward compatible with Eddie's, and I have succeeded in 99% of the cases. Using ListViewSortManager
is very easy. You just have to create an instance in your constructor, set the list view and column sorters, and that's it. In the example below, I have marked in bold the required changes:
using Intelectix.Windows.Forms;
class MyForm()
{
ListView lstNames;
ListViewSortManager sortManager;
public MyForm()
{
InitializeComponent();
sortManager = new ListViewSortManager(lstNames, new Type[]
{
typeof(ListViewTextSort),
typeof(ListViewTextSort),
typeof(ListViewInt32Sort),
typeof(ListViewDateSort)
});
}
}
Simple, isn't it? If you want to specify initial sorting to the list, specify the column index in the third parameter of the constructor, and the sort order in the fourth parameter:
sortManager = new ListViewSortManager(lstNames, new Type[]
{
typeof(ListViewTextSort),
typeof(ListViewTextSort),
typeof(ListViewInt32Sort),
typeof(ListViewDateSort)
}, 1, SortOrder.Descending);
Of course, you will have to add the ListViewSortManager.cs file to your project.
Adding Items to the ListView
If you are adding a large quantity of items to the listview
, either use ListView.Items.AddRange
or disable ListViewSortManager
. If you add one by one, the list view will be sorted after each item and you will see a performance hit.
lstNames.BeginUpdate();
lstNames.Items.Clear();
sortManager.SortEnabled = false;
for(int i = 0; i < 1000; i++)
{
ListViewItem item = new ListViewItem();
.
.
.
lstNames.Items.Add(item);
}
sortManager.SortEnabled = true;
lstNames.EndUpdate();
Creating your Own Comparers
I have provided comparers for:
- Text (
ListViewTextSort
)
- Case insensitive text (
ListViewTextCaseInsensitiveSort
)
- Date (
ListViewDateSort
)
- Integers (
ListViewInt32Sort
, ListViewInt64Sort
)
- Double (
ListViewDoubleSort
)
- Currency (
ListViewCurrencySort
)
- Percentage (
ListViewPercentSort
)
- IP addresses (
ListViewIPSort
)
If you want to sort on something different, implementing your own comparer is very easy (I've made it easier, but a little different than Eddie's version). You just need to implement the IListViewSorter
interface, and compare two items. Below you can see the code for ListViewCurrencySort
:
public class ListViewCurrencySort : IListViewSorter
{
public int Compare(String lhs, String rhs)
{
decimal result =
decimal.Parse(lhs, NumberStyles.Currency, CultureInfo.CurrentCulture) -
decimal.Parse(rhs, NumberStyles.Currency, CultureInfo.CurrentCulture);
if(result > 0)
return 1;
else if(result < 0)
return -1;
else
return 0;
}
}
Other Improvements
There is a ListViewBottomItem
, that when sorted, will always go at the very bottom. This is useful for rows containing totals. It is created and used like a regular ListViewItem
(some constructors are missing, though).
How Multicolumn Sorting Works
Basically, to support sorting by multiple columns, the ListViewSortManager
keeps a list of the indexes of sorted columns.
When the user clicks a column:
- If the Control key is not down, the
listView_ColumnClick
method checks to see if the same column is already the first in the sequence, and if so, just toggles the order. Otherwise, the whole sort list is cleared and then the column added.
- If the Control key is down, the clicked column is added at the end of the sort sequence. If it was at the end before, its sort order is toggled. If it appeared somewhere in the middle of the sequence, it is removed from there and added at the end.
The sequence contains the indexes of the columns in the order they should be sorted. If the index is negative, it means the column should be sorted in a descending manner.
Coming Soon
One thing I have always wanted to add is designer support. I visualize it like this:
- In the designer, add a component to your form.
- Each
listview
gets SortEnabled
, SortColumn
, and SortOrder
properties which you can set.
- In the column designer, each column gets a
Sorter
property.
I just hope the "Soon" in this section's title is soon enough. :)
If you have any doubts, comments, or improvements, feel free to use the message board below so everyone can benefit.