Click here to Skip to main content
15,893,487 members
Articles / Desktop Programming / Windows Forms

Adding Multicolumn-sorting to ListViewSortManager

Rate me:
Please Sign up or sign in to vote.
4.50/5 (3 votes)
5 Dec 2007CPOL4 min read 60.8K   1.2K   31   16
Easily add multicolumn sorting to your ListView controls

Introduction

A long time ago (back in 2002), Eddie Velazquez published a little class to easily add column sorting to ListView controls. The original article is here.

Several people (myself among them) requested some other features, especially the ability to sort by multiple columns at the same time. Although he fixed some bugs, Eddie did not have the time to add this bigger feature. One day I really needed it for a project and instead of waiting, I decided to roll up my sleeves and do it.

I had wanted to post it here for everyone else to benefit, but I had not given myself the time to write the article. But finally, here it is.

The most important feature I added to Eddie's version is multicolumn sorting. But I also added:

  • Currency and IP address sorting. The IP sorter was taken from a comment by Eddie in the original article.
  • Fixed a bug with the SortEnabled property
  • ListViewBottomItem class for items that should always stay at the bottom (e.g. a total row).

To sort on several columns at once, click the first column, then hold down the Ctrl key and click on additional columns.

The demo project included with this article is for Visual Studio .NET 2003 and .NET 1.1, so it can reach a broader audience. I have used this class also with Visual Studio 2005 and .NET 2.0 and everything works fine as is.

Using the Code

I have tried to keep my version backward compatible with Eddie's, and I have succeeded in 99% of the cases. Using ListViewSortManager is very easy. You just have to create an instance in your constructor, set the list view and column sorters, and that's it. In the example below, I have marked in bold the required changes:

C#
using Intelectix.Windows.Forms;

class MyForm()
{
    ListView lstNames;
    ListViewSortManager sortManager;

    public MyForm()
    {
        InitializeComponent();

        sortManager = new ListViewSortManager(lstNames,  new Type[]
            {
                typeof(ListViewTextSort),
                typeof(ListViewTextSort),
                typeof(ListViewInt32Sort),
                typeof(ListViewDateSort)
            });
    }
}

Simple, isn't it? If you want to specify initial sorting to the list, specify the column index in the third parameter of the constructor, and the sort order in the fourth parameter:

C#
sortManager = new ListViewSortManager(lstNames,  new Type[]
    {
        typeof(ListViewTextSort),
        typeof(ListViewTextSort),
        typeof(ListViewInt32Sort),
        typeof(ListViewDateSort)
    }, 1, SortOrder.Descending);  // Sort descending by second column

Of course, you will have to add the ListViewSortManager.cs file to your project.

Adding Items to the ListView

If you are adding a large quantity of items to the listview, either use ListView.Items.AddRange or disable ListViewSortManager. If you add one by one, the list view will be sorted after each item and you will see a performance hit.

C#
lstNames.BeginUpdate();
lstNames.Items.Clear();
sortManager.SortEnabled = false;

for(int i = 0; i < 1000; i++)
{
    ListViewItem item = new ListViewItem();
        .
        .
        .
    lstNames.Items.Add(item);
}

sortManager.SortEnabled = true;
lstNames.EndUpdate();

Creating your Own Comparers

I have provided comparers for:

  • Text (ListViewTextSort)
  • Case insensitive text (ListViewTextCaseInsensitiveSort)
  • Date (ListViewDateSort)
  • Integers (ListViewInt32Sort, ListViewInt64Sort)
  • Double (ListViewDoubleSort)
  • Currency (ListViewCurrencySort)
  • Percentage (ListViewPercentSort)
  • IP addresses (ListViewIPSort)

If you want to sort on something different, implementing your own comparer is very easy (I've made it easier, but a little different than Eddie's version). You just need to implement the IListViewSorter interface, and compare two items. Below you can see the code for ListViewCurrencySort:

C#
public class ListViewCurrencySort : IListViewSorter
{
    public int Compare(String lhs, String rhs)
    {
        decimal result =
           decimal.Parse(lhs, NumberStyles.Currency, CultureInfo.CurrentCulture) -
           decimal.Parse(rhs, NumberStyles.Currency, CultureInfo.CurrentCulture);

        if(result > 0)
            return 1;
        else if(result < 0)
            return -1;
        else
            return 0;
    }
}

Other Improvements

There is a ListViewBottomItem, that when sorted, will always go at the very bottom. This is useful for rows containing totals. It is created and used like a regular ListViewItem (some constructors are missing, though).

How Multicolumn Sorting Works

Basically, to support sorting by multiple columns, the ListViewSortManager keeps a list of the indexes of sorted columns.

When the user clicks a column:

  • If the Control key is not down, the listView_ColumnClick method checks to see if the same column is already the first in the sequence, and if so, just toggles the order. Otherwise, the whole sort list is cleared and then the column added.
  • If the Control key is down, the clicked column is added at the end of the sort sequence. If it was at the end before, its sort order is toggled. If it appeared somewhere in the middle of the sequence, it is removed from there and added at the end.

The sequence contains the indexes of the columns in the order they should be sorted. If the index is negative, it means the column should be sorted in a descending manner.

Coming Soon

One thing I have always wanted to add is designer support. I visualize it like this:

  • In the designer, add a component to your form.
  • Each listview gets SortEnabled, SortColumn, and SortOrder properties which you can set.
  • In the column designer, each column gets a Sorter property.

I just hope the "Soon" in this section's title is soon enough. :)

If you have any doubts, comments, or improvements, feel free to use the message board below so everyone can benefit.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Mexico Mexico
I graduated from Monterrey Institute of Technology in December 2003 with a degree in Electrical Engineering, of which I mostly remember nothing!

I have been programming since I was 10 when I learned QuickBasic. I have experience in C/C++, MFC, Win32, Visual Basic (don't tell anyone!), HTML, ASP, and recently in C# (which I like very much BTW), ASP.NET, and ADO.NET/SQL Server.

Now that I have to work, I'm currently the president of Intelectix, my own little company, that I founded in June 2004, where we do contract work in software development (and a little web design and hosting), specializing in .NET. I even already have four other people working with me, very good all of them!

Having some big projects already, we've been doing very good, so good in fact that only recently we finished our web site (which currently is only in Spanish -- English version coming soon!)

I have special interests in cars (Audi, Formula 1) and golf -- I used to be good at it (at one point a +2, now a 6 handicap on the board, but playing much worse than that.)

Comments and Discussions

 
QuestionSorting multicolumns programmatically Pin
Yomodo28-Jul-11 3:56
Yomodo28-Jul-11 3:56 
AnswerRe: Sorting multicolumns programmatically Pin
Luis Alonso Ramos3-Aug-11 4:44
Luis Alonso Ramos3-Aug-11 4:44 
SuggestionRe: Sorting multicolumns programmatically Pin
Greg Koweiski26-Oct-11 19:39
Greg Koweiski26-Oct-11 19:39 
Luis, Thank you so much for you code. It was a life saver for a noob like me. I too needed it to be programmatic. I also had some trouble with the showheaders, so I modified the management of the clicks and came up with a way to pull it off using 95% of your code.

I used a couple classes to simplify management, but its conceptually the same as yours, but programmatically resolves the column clicks.

Added this to my UI Code:
VB
Private _sortmanager As New twol.ListViewSortManager
Private _columnsortsgplistview() As ColumnSort = {}
Friend Property ColumnSortStatesGPListView() As ColumnSort()
    Get
        Return _columnsortsgplistview
    End Get
    Set(ByVal value As ColumnSort())
        _columnsortsgplistview = value
    End Set
End Property
Public Property SortManager() As twol.ListViewSortManager
    Get
        Return _sortmanager
    End Get
    Set(ByVal value As twol.ListViewSortManager)
        _sortmanager = value
    End Set
End Property


The Call from the UI, (LV is already constructed.)

VB
Private Sub DragNDropListViewGP_ColumnClick(ByVal sender As System.Object, ByVal e As System.Windows.Forms.ColumnClickEventArgs) Handles DragNDropListViewGP.ColumnClick
     Try
         BAL_MainWindow.ManageSortStates(sender, e, Me.ColumnSortStatesGPListView, Me.DragNDropListViewGP, Me.SortManager)
     Catch ex As Exception
         MessageBox.Show(ex.Message, "Error message", MessageBoxButtons.OK, MessageBoxIcon.Error)
     End Try
 End Sub


Snippets of code in my BAL_MainWindow Class:

VB
Friend Class ColumnSort
    Private _columnnumber As Integer
    Private _currentsortstate As SortOrder
    Private _sortsequence As Short
    Public Property SortSequence() As Short
        Get
            Return _sortsequence
        End Get
        Set(ByVal value As Short)
            _sortsequence = value
        End Set
    End Property
    Public Property CurrentSortState() As SortOrder
        Get
            Return _currentsortstate
        End Get
        Set(ByVal value As SortOrder)
            _currentsortstate = value
        End Set
    End Property
    Public Property ColumnNumber() As Integer
        Get
            Return _columnnumber
        End Get
        Set(ByVal value As Integer)
            _columnnumber = value
        End Set
    End Property

End Class

#Region "GpListView"
    Friend Sub PopulateGPListview(ByVal nCheckBookID As String, ByVal sender As DragNDrop.DragAndDropListView, Optional ByVal repopulate As Boolean = False)
        Try
            Using gplv As New GPListView With {.ConnClass = Me.ConnClass.Clone}
                If Not repopulate Then
                    gplv.ConstructGPListview(sender)
                End If
                gplv.ClearListviewData(sender)
                gplv.PopulateGPListview(nCheckBookID, sender)
            End Using
        Catch ex As Exception
            Throw ex
        End Try
    End Sub
    Friend Function ClearSortStates(ByVal sender As ColumnSort(), ByVal listview As DragNDrop.DragAndDropListView) As ColumnSort()
        Try
            ReDim sender(listview.Columns.Count - 1)
            For c = 0 To sender.Length - 1
                sender(c) = New ColumnSort
                sender(c).ColumnNumber = c
                sender(c).CurrentSortState = SortOrder.None
                sender(c).SortSequence = 0
            Next
            Return sender.Clone
        Catch ex As Exception
            Throw ex
        End Try
    End Function
    Friend Sub ManageSortStates(ByRef sender As System.Object, _
                                ByVal e As System.Windows.Forms.ColumnClickEventArgs, _
                                ByVal columnSortState As ColumnSort(), _
                                ByRef listview As DragNDrop.DragAndDropListView,
                                ByRef sortmanager As twol.ListViewSortManager)
        Try
            If (Control.ModifierKeys And Keys.Control) > 0 Then   ' If Ctrl key is down, add this column to the sequence
                SetSortState(columnSortState, e)
            Else
                ' If only one column is in the sequence, and it is this colum, don't clear the sequence, but toggle the order
                If (From c In columnSortState Where c.SortSequence <> 0 And c.ColumnNumber <> e.Column Select c.ColumnNumber).Count >= 1 Then
                    columnSortState = ClearSortStates(columnSortState, listview)
                End If
                SetSortState(columnSortState, e)
            End If
            Using gpl As New GPListView With {.ConnClass = Me.ConnClass.Clone}
                gpl.SortListView(sortmanager, columnSortState, listview, e)
            End Using
        Catch ex As Exception
            Throw ex
        End Try
    End Sub
    Friend Sub SetSortState(ByRef sender As ColumnSort(), ByVal e As System.Windows.Forms.ColumnClickEventArgs)
        Try
            If (From ary In sender Where ary.ColumnNumber = e.Column _
                And ary.SortSequence <> 0 Select ary.ColumnNumber).Count = 0 Then 'Don't increase the sequence if this field is already selecto for sorts
                sender(e.Column).SortSequence = (From ary In sender Select ary.SortSequence).Max + 1
            End If

            If sender(e.Column).CurrentSortState = SortOrder.None Then
                sender(e.Column).CurrentSortState = SortOrder.Ascending
            ElseIf sender(e.Column).CurrentSortState = SortOrder.Ascending Then
                sender(e.Column).CurrentSortState = SortOrder.Descending
            Else
                sender(e.Column).CurrentSortState = SortOrder.Ascending
            End If
        Catch ex As Exception
            Throw ex
        End Try
    End Sub
#End Region


And, Finally, the hook into your code, (keep in mind I had to eliminate the click handling in your code):

VB
Friend Sub SortListView(ByRef sender As twol.ListViewSortManager, _
                            ByVal columnsSortStates As ColumnSort(), _
                            ByVal listview As DragNDrop.DragAndDropListView, _
                            ByVal e As System.EventArgs)

        Dim _columns = (From c In columnsSortStates Order By c.ColumnNumber Where c.SortSequence <> 0 Select c.ColumnNumber).ToArray
        Dim _sortorders = (From c In columnsSortStates Order By c.ColumnNumber Where c.SortSequence <> 0 Select c.CurrentSortState).ToArray

        sender = New twol.ListViewSortManager(listview, New Type() _
                                                       {GetType(twol.ListViewTextSort), _
                                                        GetType(twol.ListViewTextSort), _
                                                        GetType(twol.ListViewDateSort), _
                                                        GetType(twol.ListViewTextSort), _
                                                        GetType(twol.ListViewTextSort), _
                                                        GetType(twol.ListViewCurrencySort), _
                                                        GetType(twol.ListViewCurrencySort), _
                                                        GetType(twol.ListViewTextSort)}, _
                                                    _columns, _
                                                    _sortorders)

    End Sub


I sure hope this helps some other novice in the future. Plz, no trolling on my code from you bitter ppl out there, I KNOW its not the most efficient and organized. I'm LEARNING! Cool | :cool:
Greg Kowieski
The World On-Line, Inc.


modified 27-Oct-11 1:57am.

GeneralRe: Sorting multicolumns programmatically Pin
Luis Alonso Ramos28-Oct-11 13:11
Luis Alonso Ramos28-Oct-11 13:11 
Generalhelp me Pin
choti720-May-11 5:17
choti720-May-11 5:17 
GeneralRe: help me Pin
Luis Alonso Ramos20-May-11 6:04
Luis Alonso Ramos20-May-11 6:04 
GeneralRe: help me Pin
choti720-May-11 6:13
choti720-May-11 6:13 
GeneralMissing Images (in additional TreeView Control) Pin
KSchweiger3-Dec-08 9:55
KSchweiger3-Dec-08 9:55 
QuestionAlternatives? Pin
Gavin Roberts16-Sep-08 22:35
Gavin Roberts16-Sep-08 22:35 
AnswerRe: Alternatives? Pin
Luis Alonso Ramos17-Sep-08 3:55
Luis Alonso Ramos17-Sep-08 3:55 
GeneralRe: Alternatives? Pin
Gavin Roberts17-Sep-08 4:15
Gavin Roberts17-Sep-08 4:15 
GeneralRe: Alternatives? Pin
Luis Alonso Ramos17-Sep-08 5:42
Luis Alonso Ramos17-Sep-08 5:42 
GeneralRe: Alternatives? Pin
Gavin Roberts17-Sep-08 5:49
Gavin Roberts17-Sep-08 5:49 
GeneralQuestion on License Pin
Ryan Dunn6-Apr-08 13:02
Ryan Dunn6-Apr-08 13:02 
GeneralRe: Question on License Pin
Luis Alonso Ramos7-Apr-08 6:10
Luis Alonso Ramos7-Apr-08 6:10 
GeneralDesigner support available! Pin
Luis Alonso Ramos10-Feb-08 18:19
Luis Alonso Ramos10-Feb-08 18:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.