The ListView control provides the Sort method, which sorts the list’s items, and the Sorting property, which determines how the items will be sorted. The Sort method sorts the items in the first column alphabetically. Each item may contain any number of subitems, and you should be able to sort the list according to any column. The values stored in the subitems can represent different data types (numeric values, strings, dates, and so on), but the control doesn’t provide a default sorting mechanism for all data types. Instead, it uses a custom comparer object, which you supply, to sort the items. (The topic of building custom comparers is discussed in detail in Chapter, “Storing Data in Collections.”) A custom comparer is a function that compares two items and returns an integer value (–1, 0, or 1) that indicates the order of the two items. After this function is in place, the control uses it to sort its items.
The ListView control’s ListViewItemSorter property accepts the name of a custom comparer, and the items on the control are sorted according to the custom comparer as soon as you call the Sort method. You can provide several custom comparers and sort the items in many different ways. If you plan to display subitems along with your items in Details view, you should make the list sortable by any column. It’s customary for a ListView control to sort its items according to the values in a specific column each time the header of this column is clicked. And this is exactly the type of functionality you’ll add to the ListView Sample project in this section.
The ListView Sample control displays contact information. The items are company names, and the first subitem under each item is the name of a contact. We’ll create two custom comparers to sort the list according to either company name or contact. The two methods are identical because they compare strings, but it’s not any more complicated to compare dates, distances, and so on. Let’s start with the two custom comparers. Each comparer must be implemented in its own class, and you assign the name of the custom comparer to the ListViewItem property of the control. Listing 4.48 shows the ListCompanyComparer and ListContactComparer classes.
Listing 4.48: The Two Custom Comparers for the ListView Sample Project
Class ListCompanySorter
Implements IComparer
Public Function CompareTo(ByVal o1 As Object, _
ByVal o2 As Object) As Integer _
Implements System.Collections.IComparer.Compare
Dim item1, item2 As ListViewItem
item1 = CType(o1, ListViewItem)
item2 = CType(o2, ListViewItem)
If item1.ToString.ToUpper > item2.ToString.ToUpper Then
Return 1
Else
If item1.ToString.ToUpper < item2.ToString.ToUpper Then
Return -1
Else
Return 0
End If
End If
End Function
End Class
Class ListContactSorter
Implements IComparer
Public Function CompareTo(ByVal o1 As Object, _
ByVal o2 As Object) As Integer _
Implements System.Collections.IComparer.Compare
Dim item1, item2 As ListViewItem
item1 = CType(o1, ListViewItem)
item2 = CType(o2, ListViewItem)
If item1.SubItems(1).ToString.ToUpper > item2.SubItems(1).ToString.ToUpper Then
Return 1
Else
If item1.SubItems(1).ToString.ToUpper < item2.SubItems(1).ToString.ToUpper Then
Return -1
Else
Return 0
End If
End If
End Function
End Class
Code language: VB.NET (vbnet)
The code is straightforward. If you need additional information, see the discussion of the IComparer interface in Chapter “Storing Data in Collections”. The two functions are identical, except that the first one sorts according to the item, and the second one sorts according to the first subitem.
To test the custom comparers, you simply assign their names to the ListViewItemSorter property of the ListView control. To take advantage of our custom comparers, we must write some code that intercepts the clicks on the control’s headers and calls the appropriate comparer. The ListView control fires the ColumnClick event each time a column header is clicked. This event handler reports the index of the column that was clicked through the e.Column property, and we can use this argument in our code to sort the items accordingly. Listing 4.49 shows the event handler for the ColumnClick event.
Listing 4.49: The ListView Control’s ColumnClick Event Handler
Private Sub ListView1_ColumnClick(...) _
Handles ListView1.ColumnClick
Select Case e.Column
Case 0
ListView1.ListViewItemSorter = New ListCompanySorter
ListView1.Sorting = SortOrder.Ascending
Case 1
ListView1.ListViewItemSorter = New ListContactSorter
ListView1.Sorting = SortOrder.Ascending
End Select
End Sub
Code language: VB.NET (vbnet)
Processing Selected Items
The user can select multiple items froma ListView control by default. Even though you can display a check mark in front of each item, it’s not customary. Multiple items in a ListView control are selected with the mouse while holding down the Ctrl or Shift key.
The selected items form the SelectedListItemCollection, which is a property of the control. You can iterate through this collection with a For. . .Next loop or through the enumerator object exposed by the collection. In the following example, I use a For Each. . .Next loop. Listing 4.50 is the code behind the Selected Items button of the ListViewDemo project. It goes through the selected items and displays each one of them, along with its subitems, in the Output window. Notice that you can select multiple items in any view, even when the subitems are not visible. They’re still there, however, and they can be retrieved through the SubItems collection.
Listing 4.50: Iterating the Selected Items on a ListView Control
Private Sub bttnIterate Click(...) _
Handles bttnIterate.Click
Dim LItem As ListViewItem
Dim LItems As ListView.SelectedListViewItemCollection
LItems = ListView1.SelectedItems
For Each LItem In LItems
Debug.Write(LItem.Text & vbTab)
Debug.Write(LItem.SubItems(0).ToString & vbTab)
Debug.Write(LItem.SubItems(1).ToString & vbTab)
Debug.WriteLine(LItem.SubItems(2).ToString & vbTab)
Next
End Sub
Code language: VB.NET (vbnet)
Fitting More Data into a ListView Control
A fairly common problem in designing practical user interfaces with the ListView control is how to display more columns than can be viewed in a reasonably sized window. This is especially true for accounting applications, which may have several debit/credit/balance columns. It’s typical to display these values for the previous period, the current period, and then the totals, or to display the period values along with the corresponding values of the previous year, year-to-date values, and so on.
The first approach is to use a smaller font, but this won’t take you far. A more-practical approach is to use two (or even more) rows on the control for displaying a single row of data. For example, you can display credit and debit data in two rows, as shown in the following figure. This arrangement saves you the space of one column on the screen. You could even display the balance on a third row and use different colors. The auxiliary rows, which are introduced to accommodate more data on the control, could have a different background color too.
Adding auxiliary columns is straightforward; just add an empty string for the cells that don’t change values, because all rows must have the same structure. The first two rows of the ListView control in the preceding screen capture were added by using the following statements:
Dim LI As ListViewItem
LI = ListView1.Items.Add("Customer 1")
LI.SubItems.Add("Paul Levin")
LI.SubItems.Add("NE")
LI.SubItems.Add("12,100.90")
LI = ListView1.Items.Add("")
LI.SubItems.Add("")
LI.SubItems.Add("")
LI.SubItems.Add("7,489.30")
LI.SubItems.Add((12100.9 - 7489.3).ToString("#,###.00"))
Code language: VB.NET (vbnet)
If your code reacts to the selection of an item with the mouse, or the double-click event, you must take into consideration that users may click an auxiliary row. The following If structure in the control’s SelectedIndexChanged event handler prints the item’s text, no matter which of the two rows of an item are selected on the control:
If ListView1.SelectedItems.Count = 0 Then Exit Sub
Dim idx As Integer
If ListView1.SelectedItems(0).Index Mod 2 <> 0 Then
idx = ListView1.SelectedItems(0).Index - 1
Else
idx = ListView1.SelectedItems(0).Index
End If
Debug.WriteLine(ListView1.Items(idx).Text)
Code language: VB.NET (vbnet)