You can download the demo application with detailed comments from here ListViewSortDemo.zip (47.56 kb)
A little while ago I was working on a project where I needed to display the contents of a given folder. When sorting the columns it was requested that the application behaved similar to windows explorer. Sorting on the name column in ascending order would group the folders followed by the files, and sorting in descending order would list the files first then folders.
Ascending Sorting in Windows Explorer
Descending Sorting in Windows Explorer
I realised it was worth investing some time in creating a reusable lisvtiew sorting class which can sort multiple columns.
Sorting a listview can be achieved by creating a class that inherits from the ICompare interface. I'm not going to go into the technicalities of ICompare here, rather I am going to present the features of the class I have written. It is sufficient to say though that the comparison of string values is not case sensitive, so 'hello world' will be considered the same as 'HeLlO WorLd'.
While looking through the code below bear in mind that this is only one example of how it can be done, the code is open to scrutiny and opinion of all.
Listiview Sorter Features
Multiple column sort
A sort can be performed containing upto three columns (personally I have not yet found the need to sort on more than a couple of columns as yet). This is achieved by three properties; SortAppendColumn, SortColumn and SortPrependColumn.
- SortColumn. This is column clicked on the listview by the user.
- SortAppendColumn. When a column index is specified for the SortAppendColumn the listview is sorted by this column before the actual clicked column. An index of -1 indicates no column
- SortPrependColumn. When a column index is specified for the SortPrependColumn the listview is sorted by this column after the actual clicked column. An index of -1 indicates no column
So the idea is, if the user clicks on the header of column 1 you can specify a sortprependcolumn, for example column 3. What the class will do while sorting is simply append the contents of the relevant row in column 3 to column 1 before a compare. This is assuming a textual comparison is being made. This is all made clearer in the accompanying demo application.
For those of you more interested in the technical here is the code from the Compare method that analyses the properties and sets up the text to be compared;
1: ListViewItem listviewX, listviewY;
2:
3: // Cast the objects to be compared to ListViewItem objects
4: listviewX = (ListViewItem)x;
5: listviewY = (ListViewItem)y;
6:
7: StringBuilder XCompare = new StringBuilder();
8: StringBuilder YCompare = new StringBuilder();
9:
10: // Compare the two items
11: if (this.SortPrependColumn> -1 )
12: { 13: XCompare.Append(listviewX.SubItems[this.SortPrependColumn].Text);
14: YCompare.Append(listviewY.SubItems[this.SortPrependColumn].Text);
15: }
16:
17: XCompare.Append (listviewX.SubItems[MainSortColumn].Text);
18: YCompare.Append(listviewY.SubItems[MainSortColumn].Text);
19:
20: if (this.SortAppendColumn > -1)
21: { 22: XCompare.Append(listviewX.SubItems[this.SortAppendColumn].Text);
23: YCompare.Append(listviewY.SubItems[this.SortAppendColumn].Text);
24: }
Textual or Numeric Comparison
When numbers are sorted in text format the result looks something like this;
1
14
2
26
3
37
4
...
Some developers tend to work around this feature by padding the number to a specified length with zeros like so;
0001
0002
0010
0014
...
and putting the padded numbers into an extra hidden column. When the column containing the original numbers is clicked, the sorting is actually performed on the hidden column.
One of the properties you can set on the sorter class is whether a textual or numeric sort should be performed. A numeric sort will avoid the above issues and sort number values in the expected logical way. This was actually a new feature I implemented while writing this article, you never stop learning.
Using the Class
At the top of your form declare an instance of the ListviewColumnSorter class.
1: ListViewColumnSorter lvwColumnSorter = new ListViewColumnSorter();
Inside your form constructor assign the class to the listview.
1: // Assign the column sorter
2: listView1.ListViewItemSorter = lvwColumnSorter;
In the ColumnClick event set the class properties and call the listview sort method.
First we check if we are changing the sort order on the previously selected column or on a different column.
Next, if the clicked column is index 2 we tell it to sort on column index 3 then column index 2. This mimics the windows explorer functionality mentioned earlier of grouping the folders and files when sorting.
Lastly, if column index 4 was clicked then we are sorting a number column so the compare type is set to numeric.
1: private void listView1_ColumnClick(object sender, ColumnClickEventArgs e)
2:
3:
4: { 5: // Determine if clicked column is already the column that is being sorted.
6: if (e.Column == lvwColumnSorter.SortColumn)
7: { 8: // Reverse the current sort direction for this column.
9: if (lvwColumnSorter.Order == SortOrder.Ascending)
10: { 11: lvwColumnSorter.Order = SortOrder.Descending;
12: }
13: else
14: { 15: lvwColumnSorter.Order = SortOrder.Ascending;
16: }
17: }
18: else
19: { 20: // Set the column number that is to be sorted; default to ascending.
21: lvwColumnSorter.SortColumn = e.Column;
22: lvwColumnSorter.Order = SortOrder.Ascending;
23: }
24:
25: if (lvwColumnSorter.SortColumn == 2)
26: lvwColumnSorter.SortPrependColumn = 3;
27:
28: if (lvwColumnSorter.SortColumn == 4)
29: lvwColumnSorter.CompareAs = SorterCompareType.Numeric;
30: else
31: lvwColumnSorter.CompareAs = SorterCompareType.Textual ;
32:
33: // Perform the sort with these new sort options.
34: listView1.Sort();
35: }
You can download the demo application with detailed comments from here ListViewSortDemo.zip (47.56 kb)