Sorting ListView Items by Column Using Windows Forms

 

Shannon Dunn
Microsoft Corporation

July 2002

Applies to:
   Microsoft® .NET Framework
   Microsoft® Visual Studio® .NET

Summary: Describes how to provide sorting of items in a ListView control in Microsoft .NET based on the column that is clicked. (13 printed pages)

Contents

Introduction
Custom Sorting Features of the ListView Control
Sorting in Ascending Order
Sorting in Ascending or Descending Order
Sorting Dates
Conclusion

Introduction

The ListView control is a great way to display file system information and data from an XML file or database. The ListView control is typically used to display a graphical icon that represents the item, as well as the item text. In addition, the ListView control can be used to display additional information about an item in a subitem. For example, if the ListView control is displaying a list of files, you can configure the ListView control to display details such as file size and attributes as subitems. To display subitem information in the ListView control, you must set the View property to View.Details. In addition, you must create ColumnHeader objects and assign them to the Columns property of the ListView control. Once these properties are set, items are displayed in a row and column format that is similar to a DataGrid control. The ability to display items in this way makes the ListView control a quick and easy solution for displaying data from any type of data source.

Sorting for the ListView control is provided by using the Sorting property of the ListView. This enables you define the type of sorting to apply to the items. This is a great feature if you want to sort only by items. If you want to sort by subitems, you must use the custom sorting features of the ListView control. This document will demonstrate how to perform custom sorting in the ListView control and how to handle special data-type conditions when sorting.

Custom Sorting Features of the ListView Control

The ListView control provides features that enable you to use sorting other than that provided by the Sorting property. When the ListView control sorts items using the Sorting property, it uses a class that implements the System.Collections.IComparer interface. This class provides the sorting features used to sort each item. In order to sort by subitems, you must create your own class that implements the IComparer interface that in turn implements the sorting your ListView control needs. The class is defined with a constructor that specifies the column by which the ListView control is sorted. Once you have created this class, typically as a nested class of your form, you create an instance of this class and assign it to the ListViewItemSorter property of the ListView. This identifies the custom sorting class that the ListView control will use when the Sort method is called. The Sort method performs the actual sorting of the ListView items.

Sorting in Ascending Order

The following sections provide an example of the basics of sorting in a ListView control, based on its subitems. This example demonstrates sorting items in the ListView control in ascending order. Sorting in ascending or descending order will be covered in a later section in this document. The goal here is to demonstrate the basic requirements for custom sorting in a ListView control.

Initializing the Control

To begin, create an instance of a ListView control and add it to a form. After the control is on the form, add items to the ListView control using the Items property. You can add as many items as you want; just be sure that each item's text is unique. While you are creating the items, add two subitems for each. The first subitem should contain numerical information and the second one date information. The following table is an example of how this information might look in the ListView control.

Item Subitem 1 Subitem 2
Alpha 1.0 4/5/1945
Charlie 3.5 1/9/1920
Bravo 2.4 12/8/1930

Create two ColumnHeader objects and assign them to the Columns property of the ListView control. Set the View property to View.Details.

Handling the ColumnClick Event

In order to determine which set of subitems to sort by, you need to know when the user clicks a column heading for a subitem. To do this, you need to create an event-handling method for the ColumnClick event of the ListView. Place the event-handling method as a member of your form and ensure that it contains a signature similar to the one shown in the following code example.

'Visual Basic 
Private Sub listView1_ColumnClick(sender As Object, e As System.Windows.Forms.ColumnClickEventArgs)
End Sub

//C#
private void listView1_ColumnClick(object sender, System.Windows.Forms.ColumnClickEventArgs e)
{
}

Connect the event-handling method to the ListView control by adding code to the constructor of your form, as shown in the following example.

'Visual Basic
AddHandler listView1.ColumnClick, AddressOf Me.listView1_ColumnClick

//C#
this.listView1.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.listView1_ColumnClick);

Add the following code to the event-handling method for the ColumnClick event.

'Visual Basic 
' Set the ListViewItemSorter property to a new ListViewItemComparer
' object.
Me.listView1.ListViewItemSorter = New ListViewItemComparer(e.Column)
' Call the sort method to manually sort.
listView1.Sort()

//C#
// Set the ListViewItemSorter property to a new ListViewItemComparer
// object.
this.listView1.ListViewItemSorter = new ListViewItemComparer(e.Column);
// Call the sort method to manually sort.
listView1.Sort();

The code added to the event-handling method sets the ListViewItemSorter property of the ListView control with a new instance of the ListViewItemComparer class (which is defined in the next section), and assigns the column that is being clicked. The column being clicked is passed as part of the event arguments. After the ListViewItemSorter property is set, the Sort method is called to perform the manual sort.

Creating the ListViewItemComparer Class

As mentioned earlier, the key to custom sorting in the ListView control is creating a class that implements the System.Collections.IComparer interface. It is in this class that you provide the sorting for items. For this example, a named ListViewItemComparer is defined and added as a nested class of your form. The ListViewItemComparer performs a basic ascending sort of the specified column that is passed to its constructor. Add the following class definition to your Form class and ensure that it is nested properly inside your form.

'Visual Basic 
' Implements the manual sorting of items by column.
Class ListViewItemComparer
    Implements IComparer 
    Private col As Integer
    
    Public Sub New()
        col = 0
    End Sub
    
    Public Sub New(column As Integer)
        col = column
    End Sub
    
    Public Function Compare(x As Object, y As Object) As Integer _ 
                            Implements System.Collections.IComparer.Compare
        Dim returnVal as Integer = -1
        returnVal = [String].Compare(CType(x, _
                        ListViewItem).SubItems(col).Text, _
                        CType(y, ListViewItem).SubItems(col).Text)
        Return returnVal
    End Function
End Class

//C#
// Implements the manual sorting of items by column.
class ListViewItemComparer : IComparer {
    private int col;
    public ListViewItemComparer() {
        col=0;
    }
    public ListViewItemComparer(int column) 
    {
        col=column;
    }
    public int Compare(object x, object y) 
    {
        int returnVal = -1;
        returnVal = String.Compare(((ListViewItem)x).SubItems[col].Text,
        ((ListViewItem)y).SubItems[col].Text);
        return returnVal;
    }
}

The sorting is performed in a required method of the IComparer interface called Compare. This method takes two objects as parameters, which will contain the two items being compared. When the Sort method is called in the ColumnClick event-handling method of the ListView control, the Sort method uses the ListViewItemComparer object that was defined and assigned to the ListViewItemSorter property and calls its Compare method. When the ListViewItemComparer object is created, it is assigned the index of the column that was clicked. This column index is used to access subitems from the column that needs to be sorted. The subitems are then passed to the String.Compare method, which compares the items and returns one of three results. If the item in the x parameter is less than the item in the y parameter, a value less than zero is returned. If the items are identical, a zero is returned. Finally, if the item in the x parameter is greater than the item in the y parameter, a value greater than zero is returned. The value returned by the Compare method is passed back to the Sort method, which determines the location in the column of each item being compared. The Sort method makes as many calls to the Compare method as needed to sort all subitems in the selected column.

The preceding example is complete. If you run the example and click the column headings of the ListView control, the items will sort appropriately in alphabetical or numerical order. The only column that will not sort properly is the column that contains date information. Sorting the date column is covered later in this document.

This example demonstrates the basic components needed to perform a basic manual sorting of items in the ListView control. The next section extends this example to provide ascending and descending sort capabilities.

Sorting in Ascending or Descending Order

Users of the ListView control will expect to have the ability to sort items in both ascending and descending order. In order to facilitate this ability, some changes will need to be made to the previous example to enable the Compare method to identify the items to sort.

Changes to the Form

Typically, to switch between ascending and descending sort order, you click the column heading more than once. Users expect that if they click the column heading, the sort will occur and a subsequent click will change the sort order. The previous code example needs to be able to determine when a column has been clicked more than once. In order to do this, you add a private integer variable to the Form class. This variable stores the last column that was clicked. The ColumnClick event-handling method will use this variable to compare the last column to the column currently being clicked, and determine if they are the same. Add the following member definition to the Form class.

'Visual Basic 
Dim sortColumn as Integer = -1

//C#
private int sortColumn = -1;

Changes to the ColumnClick Event Handling Method

The ColumnClick event-handling method defined in the previous example needs to be modified to track the column that was clicked and the current sorting order. Add the following code to replace the code in the ColumnClick event-handling method created in the previous example.

'Visual Basic 
Private Sub listView1_ColumnClick(sender As Object, e As
                System.Windows.Forms.ColumnClickEventArgs)
    ' Determine whether the column is the same as the last column clicked.
    If e.Column <> sortColumn Then
        ' Set the sort column to the new column.
        sortColumn = e.Column
        ' Set the sort order to ascending by default.
        listView1.Sorting = SortOrder.Ascending
    Else
        ' Determine what the last sort order was and change it.
        If listView1.Sorting = SortOrder.Ascending Then
            listView1.Sorting = SortOrder.Descending
        Else
            listView1.Sorting = SortOrder.Ascending
        End If
    End If 
    ' Call the sort method to manually sort.
    listView1.Sort()
    ' Set the ListViewItemSorter property to a new ListViewItemComparer
    ' object.
    listView1.ListViewItemSorter = New ListViewItemComparer(e.Column, _
                                                     listView1.Sorting)
End Sub

//C#
private void listView1_ColumnClick(object sender,
                           System.Windows.Forms.ColumnClickEventArgs e)
{
    // Determine whether the column is the same as the last column clicked.
    if (e.Column != sortColumn)
    {
        // Set the sort column to the new column.
        sortColumn = e.Column;
        // Set the sort order to ascending by default.
        listView1.Sorting = SortOrder.Ascending;
    }
    else
    {
        // Determine what the last sort order was and change it.
        if (listView1.Sorting == SortOrder.Ascending)
            listView1.Sorting = SortOrder.Descending;
        else
            listView1.Sorting = SortOrder.Ascending;
    }

    // Call the sort method to manually sort.
    listView1.Sort();
    // Set the ListViewItemSorter property to a new ListViewItemComparer
    // object.
    this.listView1.ListViewItemSorter = new ListViewItemComparer(e.Column,
                                                      listView1.Sorting);
}

This code adds some logic before setting the ListViewItemSorter property and calling the Sort method. The added code determines whether the item being clicked is the same as the last column that was clicked. If they are different, the sortColumn variable is set and the Sorting property is assigned the SortOrder.Ascending value. If the sortColumn variable and the column being clicked are identical, the Sorting property is changed to the opposite sort order. In this example, the Sorting property is used as a way to define the sort order for items. Because you are using a custom comparer class to sort items, setting the Sorting property has no effect on the sorting operation. It is used simply as a variable in the example.

Once the sorting order is specified, the only other change to the ColumnClick event-handling method code is to add an additional parameter value to the creation of the ListViewItemComparer object that you assign to the ListViewItemSorter property. As shown later in this example, the ListViewItemComparer class contains a new parameter that specifies the sorting order. You use the value of the ListView.Sorting property to assign the value to the parameter.

Changes to the ListViewItemComparer Class

The last set of changes to enable this example to sort in either ascending or descending order is to the ListViewItemComparer class. The added code will perform the logic required to compare items in either sort mode. Add the following code to replace the code defined for the ListViewItemComparer in the previous example.

'Visual Basic 
' Implements the manual sorting of items by columns.
Class ListViewItemComparer
    Implements IComparer 
    Private col As Integer
    Private order as SortOrder
    
    Public Sub New()
        col = 0
        order = SortOrder.Ascending
    End Sub
    
    Public Sub New(column As Integer, order as SortOrder)
        col = column
        Me.order = order
    End Sub
    
    Public Function Compare(x As Object, y As Object) As Integer _
                        Implements System.Collections.IComparer.Compare
        Dim returnVal as Integer = -1
        returnVal = [String].Compare(CType(x, _
                        ListViewItem).SubItems(col).Text, _
                        CType(y, ListViewItem).SubItems(col).Text)
        ' Determine whether the sort order is descending.
        If order = SortOrder.Descending Then
            ' Invert the value returned by String.Compare.
            returnVal *= -1
        End If

        Return returnVal
    End Function
End Class

//C#
// Implements the manual sorting of items by columns.
class ListViewItemComparer : IComparer {
    private int col;
    private SortOrder order;
    public ListViewItemComparer() {
        col=0;
        order = SortOrder.Ascending;
    }
    public ListViewItemComparer(int column, SortOrder order) 
    {
        col=column;
        this.order = order;
    }
    public int Compare(object x, object y) 
    {
        int returnVal= -1;
        returnVal = String.Compare(((ListViewItem)x).SubItems[col].Text,
                                ((ListViewItem)y).SubItems[col].Text);
        // Determine whether the sort order is descending.
        if(order == SortOrder.Descending)
            // Invert the value returned by String.Compare.
            returnVal *= -1
        return returnVal;
    }
}33

The code adds the sort order parameter to the constructor and creates a private variable to store the value. The code for the Compare method is changed to determine whether the sort order is descending. If it is, the value returned by the String.Compare method is multiplied by -1 to change the value so that the value returned by the Compare method is the opposite of what String.Compare returned.

Run the code and click a column. The column is sorted in ascending order. Click the same column and the column is sorted in descending order. Again, the date column is not sorted properly because it is sorted as a string and not as a date. In the next section, you complete the example by adding functionality to sort by date or string depending on the type of data.

Sorting Dates

Data that is placed into the ListView control as an item is displayed as text and stored as text. This makes it easy to sort using the String.Compare method in an IComparer class. String.Compare sorts both alphabetical characters and numbers. However, certain data types do not sort correctly using String.Compare, such as date and time information. For this reason, the System.DateTime structure has a Compare method just as the String class does. This method can be used to perform the same type of sorting based on chronological order. In this section, you modify only the Compare method to allow for dates to be sorted properly.

Add the following code to replace the code defined for the Compare method of the ListViewItemComparer class defined in the previous example.

'Visual Basic 
    Public Function Compare(ByVal x As Object, ByVal y As Object) As 
                Integer Implements System.Collections.IComparer.Compare
        Dim returnVal As Integer
        ' Determine whether the type being compared is a date type.
        Try
            ' Parse the two objects passed as a parameter as a DateTime.
            Dim firstDate As System.DateTime = DateTime.Parse(CType(x, _
                                    ListViewItem).SubItems(col).Text)
            Dim secondDate As System.DateTime = DateTime.Parse(CType(y, _ 
                                      ListViewItem).SubItems(col).Text)
            ' Compare the two dates.
            returnVal = DateTime.Compare(firstDate, secondDate)
            ' If neither compared object has a valid date format, 
            ' compare as a string.
        Catch
            ' Compare the two items as a string.
            returnVal = [String].Compare(CType(x, _ 
                              ListViewItem).SubItems(col).Text, CType(y,ListViewItem).SubItems(col).Text)
        End Try
        
        ' Determine whether the sort order is descending.
        If order = SortOrder.Descending Then
            ' Invert the value returned by String.Compare.
            returnVal *= -1
        End If
        Return returnVal
    End Function

//C#
public int Compare(object x, object y) 
{
    int returnVal;
    // Determine whether the type being compared is a date type.
    try
    {
        // Parse the two objects passed as a parameter as a DateTime.
        System.DateTime firstDate = 
                DateTime.Parse(((ListViewItem)x).SubItems[col].Text);
        System.DateTime secondDate = 
                DateTime.Parse(((ListViewItem)y).SubItems[col].Text);
        // Compare the two dates.
        returnVal = DateTime.Compare(firstDate, secondDate);
    }
    // If neither compared object has a valid date format, compare
    // as a string.
    catch 
    {
        // Compare the two items as a string.
        returnVal = String.Compare(((ListViewItem)x).SubItems[col].Text,
                    ((ListViewItem)y).SubItems[col].Text);
    }
    // Determine whether the sort order is descending.
    if (order == SortOrder.Descending)
    // Invert the value returned by String.Compare.
        returnVal *= -1;
    return returnVal;
}

The code that was added to replace the previous version of the Compare method starts by casting the x and y parameters to DateTime objects. This extraction is performed in a try/catch block to catch exceptions that might occur by forcing the casting of the two items being compared into DateTime objects. If an exception does occur, it signals to the code that the type being converted is not a valid date or time and can be sorted by the String.Compare method. If the two types are dates, they are sorted using the DateTime.Compare method.

Run this new version of the example code and click any of the columns. You will notice that they properly sort the subitems, including the date column. The ListView control in the example now can properly handle all of the types of data it is displaying.

Conclusion

The ListView control can provide the ability to display data in a number of ways. It can be used to display single items as well as items that contain subitem information. Using the sorting features provided by the ListView control, you can also enable users to sort items in the ListView control based on those subitems, regardless of the type of data being presented. This ability to sort items and their subitems enables your application to behave in ways that are familiar to users of Microsoft® Windows® Explorer and other applications that provide a ListView display of data and the ability to sort its contents.