简体   繁体   中英

Sorting a ListView by multiple columns in C#

With ListView controls, you can specify a column to sort by, and there's a method to sort() whenever you want.

However, this only allows for single column sorting.

I'm keen to sort by say, Column A first, and then by Column F for when they are the same.

I've found a few custom compare classes written online, but wondered if stackoverflow could show a cleaner way. Plus having this here may help others looking for it in future :)

Any suggestions or examples on how to go about this appreciated.

So, after playing around, the answer I came up with was to write a ListViewItemComparer class through the IComparer interface.

I then overwrote the Compare() method, and could now return -1, 0, or 1 depending on the comparison between first the primary column, and then when equal, the secondary column.

Quite tidy in the end, I think.

As with almost all tasks, ObjectListView (an open source wrapper around .NET WinForms ListView) makes living with a ListView much easier.

ObjectListView has SecondarySortColumn and SecondarySortOrder properties to do exactly what you are asking.

If you want to do even fancier sorting, you can install a CustomSorter . Have a look at this recipe

@MarkMayo, I had created my own sorter class ListViewItemComparer through the IComparer interface which support secondary/priority column sorting.

I overwrite Compare() method to support numeric, date & case-insensitive string comparison.

It will first sort the column you wish, if both compared values are the same, it will take the second column as reference for sorting, hence secondary sorting.

You just need to include this sorter class and modify the Form's Listview ColumnClick event with the following example VB.Net code:

ListViewItemComparer Class

Imports System.Collections

''' <summary>
''' This class is an implementation of the 'IComparer' interface.
''' </summary>
Public Class ListViewColumnSorter
    Implements IComparer
    ''' <summary>
    ''' Specifies the column to be sorted
    ''' </summary>
    Private ColumnToSort As Integer
    ''' <summary>
    ''' Specifies the secondary column to be sorted
    ''' </summary>
    Private SecondaryColumnToSort As Integer = -1
    ''' <summary>
    ''' Specifies the order in which to sort (i.e. 'Ascending').
    ''' </summary>
    Private OrderOfSort As SortOrder
    ''' <summary>
    ''' Class constructor. Initializes various elements
    ''' </summary>
    Public Sub New(ByVal column_number As Integer, ByVal sort_order As SortOrder)
        ColumnToSort = column_number
        OrderOfSort = sort_order
    End Sub
    ''' <summary>
    ''' Class constructor. Initializes various elements
    ''' </summary>
    Public Sub New(ByVal column_number As Integer, ByVal sort_order As SortOrder, ByVal secondary_column_number As Integer)
        ColumnToSort = column_number
        SecondaryColumnToSort = secondary_column_number
        OrderOfSort = sort_order
    End Sub

    ''' <summary>
    ''' This method is inherited from the IComparer interface. It compares the two objects passed and support secondary column comparison
    ''' </summary>
    ''' <param name="x">First object to be compared</param>
    ''' <param name="y">Second object to be compared</param>
    ''' <returns>The result of the comparison. "0" if equal, negative if 'x' is less than 'y' and positive if 'x' is greater than 'y'</returns>
    Public Function Compare(x As Object, y As Object) As Integer Implements IComparer.Compare
        Dim compareResult As Integer
        Dim listviewX As ListViewItem, listviewY As ListViewItem

        ' Cast the objects to be compared to ListViewItem objects
        listviewX = DirectCast(x, ListViewItem)
        listviewY = DirectCast(y, ListViewItem)

        ' Compare the two items
        Dim x1 As Object = listviewX.SubItems(ColumnToSort)
        Dim y1 As Object = listviewY.SubItems(ColumnToSort)

        ' Use .tag for comparison if not empty
        If (x1.Tag IsNot vbNullString) And (y1.Tag IsNot vbNullString) Then
            compareResult = ObjectComparer(x1.Tag, y1.Tag)
        Else
            compareResult = ObjectComparer(x1.Text, y1.Text)
        End If

        'require secondary column compare?
        If (compareResult = 0 And SecondaryColumnToSort >= 0 And SecondaryColumnToSort <> ColumnToSort) Then
            ' Compare the two items
            Dim x2 As Object = listviewX.SubItems(SecondaryColumnToSort)
            Dim y2 As Object = listviewY.SubItems(SecondaryColumnToSort)

            ' Use .tag for comparison if not empty
            If (x2.Tag IsNot vbNullString) And (y2.Tag IsNot vbNullString) Then
                compareResult = ObjectComparer(x2.Tag, y2.Tag)
            Else
                compareResult = ObjectComparer(x2.Text, y2.Text)
            End If
        End If

        ' Calculate correct return value based on object comparison
        If OrderOfSort = SortOrder.Ascending Then
            ' Ascending sort is selected, return normal result of compare operation
            Return compareResult
        ElseIf OrderOfSort = SortOrder.Descending Then
            ' Descending sort is selected, return negative result of compare operation
            Return (-compareResult)
        Else
            ' Return '0' to indicate they are equal
            Return 0
        End If
    End Function

    ''' <summary>
    ''' This method compares the two objects passed. Object supported are numeric, dates and string
    ''' </summary>
    ''' <param name="x">First object to be compared</param>
    ''' <param name="y">Second object to be compared</param>
    ''' <returns>The result of the comparison. "0" if equal, negative if 'x' is less than 'y' and positive if 'x' is greater than 'y'</returns>
    Private Function ObjectComparer(x As Object, y As Object) As Integer
        Dim compareResult As Integer

        If IsNumeric(x) And IsNumeric(y) Then 'comparing numbers
            compareResult = Val(x).CompareTo(Val(y))
        ElseIf IsDate(x) And IsDate(y) Then 'comparing dates
            compareResult = DateTime.Parse(x).CompareTo(DateTime.Parse(y))
        Else 'comparing string
            Dim ObjectCompare As New CaseInsensitiveComparer
            compareResult = ObjectCompare.Compare(x.ToString, y.ToString)
        End If
        Return compareResult
    End Function

End Class

Windows Form's Listview ColumnClick

Private prevColumnClick As Integer 'to store previous sorted column number
Private secondary_column_to_sort As Integer = 0 'column 0

Private Sub lvLog_ColumnClick(sender As Object, e As ColumnClickEventArgs) Handles lvLog.ColumnClick
    Dim myListView As ListView = DirectCast(sender, ListView)
    Dim sort_order As System.Windows.Forms.SortOrder

    If myListView.Columns(e.Column).Tag Is Nothing Then
        sort_order = SortOrder.Ascending
    Else
        ' Get previous sort order information from columns .tag
        sort_order = DirectCast(myListView.Columns(e.Column).Tag, System.Windows.Forms.SortOrder)
    End If

    If (prevColumnClick = e.Column) Then
        If sort_order = SortOrder.Ascending Then
            sort_order = SortOrder.Descending
        Else
            sort_order = SortOrder.Ascending
        End If
    End If

    ' Initialize ColumnSorter class
    myListView.ListViewItemSorter = New ListViewColumnSorter(e.Column, sort_order, secondary_column_to_sort)

    ' Perform the sort with these new sort options.
    'myListView.Sort()

    ' Store current column sorting order
    myListView.Columns(e.Column).Tag = sort_order

    ' Store previous column number clicked 
    prevColumnClick = e.Column

End Sub

It's probably not the most efficient way, but you can just do the following:

listView.Sort(5);    // Column F, then
listView.Sort(0);    // Column A

Note the reverse ordering.

Is this on the web or winform? On the web you can put together an expression that has the columns, comma separated, and pass it to the sort() method of the listview

Framework 3.5 and up though...

Well, if you just want the columns to be sorted, try using List of List; for instance like following:

List<List<string>> lstColumns = new List<List<string>>();

Haven't tried it, but just thinking a quick solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM