use the ListView control a lot. Not to display large or small icon views, which are attractive but information-sparse. Instead, I tend to use it to display a detail view similar to the one shown on the right half of Windows Explorer when you choose "Details" from the View menu or the dropdown button on the toolbar.
In detail view, the ListView acts like an easy-to-use read-only grid. In code, it's easy to add, remove, and rearrange the control's items and sub-items. And at run time, the control can let the user select rows, rearrange columns, and even sort the rows it displays.
While the ListView provides the basic tools to do these things, however, you need to add some code to make things work. The SortableListView control described in this article adds that code. It inherits from the ListView class, so it not only has all the features of the ListView control, but also adds support for sorting by all columns and sorting by a selected column.
Selected Column Sorting
The ListView control contains an Items
collection that holds the items that it displays. Each item in the collection is an instance of a ListViewItem class and exposes a SubItems
collection that gives further detail for the item. In the Details view, the ListView lists the text for the items in the leftmost column and lists sub-items in the other columns.
The ListView control has a built-in Sort
method that makes it sort its data. By default, however, the control only sorts the itemsnot the sub-items. Fortunately the control's ListViewItemSorter
property allows you to change the way in which the control sorts its items.
To implement a customized sort, you must set the ListViewItemSorter
property to an object that implements the IComparer interface. That interface specifies a single method, Compare
, that takes two items as parameters and returns 1
, or 1
to indicate whether the first item should be considered less than, equal to, or greater than the second item in the sort order, respectively.
This control uses IComparer classes to provide two new sorting styles. The first sorts by a specific column, not necessarily the first column. Figure 1
shows the control sorted by its Pages column in ascending order (smallest values on top). Note that the row that contains no Pages data sorts to the top. The control displays an upward arrow on the currently sorted column as an indication that the column is sorted in ascending order (the arrow points towards the smallest item). If you click the column header, the control switches to a descending sort.
|Figure 1. Sorting by Page: Here the SortableListView control has sorted the data by the Pages column in ascending order.||
|Figure 2. Sorting by Title: Here the SortableListView control is sorting by the Title column in descending order.||
If you click on a different column header, the control sorts on that columnjust like Windows Explorer. Figure 2
shows the form after I clicked on the Title column twice, first to sort by that column and then again to change the sort order to descending.
The following code shows the SelectedColumnSorter class used by the SortableListView control to sort on a selected column. You'll find both this Visual Basic version and a C# version in the downloadable code
attached to this article:
' Sort the ListView items by the selected column.
Private Class SelectedColumnSorter
' Compare two ListViewItems.
Public Function Compare(ByVal x As Object, _
ByVal y As Object) As Integer _
' Get the items.
Dim itemx As ListViewItem = DirectCast(x, ListViewItem)
Dim itemy As ListViewItem = DirectCast(y, ListViewItem)
' Get the selected column index.
Dim slvw As SortableListView = itemx.ListView
Dim idx As Integer = slvw.m_SelectedColumn
If idx < 0 Then Return 0
' Compare the sub-items.
If itemx.ListView.Sorting = SortOrder.Ascending Then
Return String.Compare( _
ItemString(itemx, idx), _
Return -String.Compare( _
ItemString(itemx, idx), _
' Return a string representing this item's sub-item.
Private Function ItemString( _
ByVal listview_item As ListViewItem, ByVal idx As Integer) _
Dim slvw As SortableListView = listview_item.ListView
' Make sure the item has the needed sub-item.
Dim value As String = ""
If idx <= listview_item.SubItems.Count - 1 Then
value = listview_item.SubItems(idx).Text
' Return the sub-item's value.
If slvw.Columns(idx).TextAlign = _
' Pad so numeric values sort properly.
function converts its two parameters from generic Objects into ListViewItem objects. It uses the first item's ListView
property to get the SortableListView control that contains the item.
The code then uses the SortableListView control's m_SelectedColumn
variable to see which column it should use for sorting (this variable is described later). If no column is selected, the function simply exits.
Next the code checks the SortableListView control's Sorting
property to see whether it should sort the objects in ascending or descending order. The SelectedColumnSorter object calls its own ItemString
function to generate strings representing the selected column in each of the ListViewItem objects. It uses String.Compare
to compare the strings and returns the results. If the items should be sorted in descending order, the code negates the result of String.Compare
to give the proper result.
The SelectedColumnSorter class's ItemString
function returns a string representing the selected column for a ListViewItem. It first checks whether the ListViewItem has the needed column. For example, if a ListViewItem has only one sub-item but the control is sorting on its fifth column, the code uses a blank string for the column.
After finding the correct value for the column (either the sub-item's text or a blank string), the code checks the corresponding column's TextAlign
property. If the text in this column should be right-aligned, then the column probably contains numeric data that should not be sorted alphabetically. For example, the string "100"
comes alphabetically before "11:
but you probably want "11"
to appear first in the sorted list.
To sort right-aligned numbers properly, the ItemString
function pads those values on the left with spaces. Spaces come before digits alphabetically so the string " 11"
will appear before "100" as desired.
|Author's Note: You may want to make this test more elaborate depending on your data. For example, this code assumes right-aligned columns contain simple numbers with at most 20 digits. The code would not know that "1e10" is the same as "1E+10" or that "April 1" should come after "January 1." But the sample code demonstrates the general idea.