Click here to Skip to main content
11,721,385 members (75,013 online)
Click here to Skip to main content

ListView Column Sorter

, 21 Jul 2015 CPOL 200K 4.6K 60
Rate this:
Please Sign up or sign in to vote.
A column sorter that sorts columns with strings and numbers (also decimals), but also the first column on image and then on string.

Introduction

Long time ago I started using the ListView. After a while, I was in a desperate need of a column sorter. I just wanted to sort on each column. A while later, I thought maybe it would be good to sort on the images as well. Of course in this case it should sort on image and then on text in the first column. Yesterday I had the problem, that one column contained numbers in string form. Which of course again brought me to the next version, which should find out, if the string is actually a number and then sort on numbers. I have created an example project that utilizes all this for you. Have a look.

How to use it

If you just want to use it, it's so simple. Just download the source zip from the link on the top of the page. In your forms class, just declare this:

private ListViewColumnSorter lvwColumnSorter;

Then within the constructor of your form, you simply add:

private void MyForm()
{
  lvwColumnSorter = new ListViewColumnSorter();
  this.listView1.ListViewItemSorter = lvwColumnSorter;
}

Of course, now you want to be able to sort, when you click on the column title. Now in Visual Studio, you select your ListView and go to properties, select events and double-click on ColumnClick. What will be created is this:

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

Which means, we will need another method that will steer the sorting for us:

private void listView1_ColumnClick(object sender, System.Windows.Forms.ColumnClickEventArgs e)
{
   ListView myListView = (ListView)sender;

   // Determine if clicked column is already the column that is being sorted.
   if ( e.Column == lvwColumnSorter.SortColumn )
   {
     // Reverse the current sort direction for this column.
     if (lvwColumnSorter.Order == SortOrder.Ascending)
     {
      lvwColumnSorter.Order = SortOrder.Descending;
     }
     else
     {
      lvwColumnSorter.Order = SortOrder.Ascending;
     }
   }
   else
   {
    // Set the column number that is to be sorted; default to ascending.
    lvwColumnSorter.SortColumn = e.Column;
    lvwColumnSorter.Order = SortOrder.Ascending;
   }

   // Perform the sort with these new sort options.
   myListView.Sort();
}

Now you are done...

Code basics

using System;
using System.Collections;
using System.Text.RegularExpressions;    
using System.Windows.Forms;
using System.Globalization;

namespace ListViewSorter
{
    /// <summary>
    /// This class is an implementation of the 'IComparer' interface.
    /// </summary>
    public class ListViewColumnSorter : IComparer
    {
        public enum SortModifiers
        {
            SortByImage,
            SortByCheckbox,
            SortByText
        }

        /// <summary>
        /// Specifies the column to be sorted
        /// </summary>
        public int ColumnToSort;
        /// <summary>
        /// Specifies the order in which to sort (i.e. 'Ascending').
        /// </summary>
        public SortOrder OrderOfSort;
        /// <summary>
        /// Case insensitive comparer object
        /// </summary>
        
        private NumberCaseInsensitiveComparer ObjectCompare;
        private ImageTextComparer FirstObjectCompare;
        private CheckboxTextComparer FirstObjectCompare2;

        private SortModifiers mySortModifier = SortModifiers.SortByText;
        public SortModifiers _SortModifier
        {
            set
            {
                mySortModifier = value;
            }
            get
            {
                return mySortModifier;
            }
        }

        /// <summary>
        /// Class constructor.  Initializes various elements
        /// </summary>
        public ListViewColumnSorter()
        {
            // Initialize the column to '0'
            ColumnToSort = 0;

            // Initialize the CaseInsensitiveComparer object
            ObjectCompare = new NumberCaseInsensitiveComparer();
            FirstObjectCompare = new ImageTextComparer();
            FirstObjectCompare2 = new CheckboxTextComparer();
        }

        /// <summary>
        /// This method is inherited from the IComparer interface.  It compares the two objects passed using a case insensitive 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 int Compare(object x, object y)
        {
            int compareResult = 0;
            ListViewItem listviewX, listviewY;

            // Cast the objects to be compared to ListViewItem objects
            listviewX = (ListViewItem)x;
            listviewY = (ListViewItem)y;

            ListView listViewMain = listviewX.ListView;

            // Calculate correct return value based on object comparison
            if (listViewMain.Sorting != SortOrder.Ascending &&
                listViewMain.Sorting != SortOrder.Descending)
            {
                // Return '0' to indicate they are equal
                return compareResult;
            }

            if (mySortModifier.Equals(SortModifiers.SortByText) || ColumnToSort > 0)
            {
                // Compare the two items

                if (listviewX.SubItems.Count <= ColumnToSort &&
                    listviewY.SubItems.Count <= ColumnToSort)
                {
                    compareResult = ObjectCompare.Compare(null, null);
                }
                else if (listviewX.SubItems.Count <= ColumnToSort &&
                         listviewY.SubItems.Count > ColumnToSort)
                {
                    compareResult = ObjectCompare.Compare(null, listviewY.SubItems[ColumnToSort].Text.Trim());
                }
                else if (listviewX.SubItems.Count > ColumnToSort && listviewY.SubItems.Count <= ColumnToSort)
                {
                    compareResult = ObjectCompare.Compare(listviewX.SubItems[ColumnToSort].Text.Trim(), null);
                }
                else
                {
                    compareResult = ObjectCompare.Compare(listviewX.SubItems[ColumnToSort].Text.Trim(), listviewY.SubItems[ColumnToSort].Text.Trim());
                }
            }
            else
            {
                switch (mySortModifier)
                {
                    case SortModifiers.SortByCheckbox:
                        compareResult = FirstObjectCompare2.Compare(x, y);
                        break;
                    case SortModifiers.SortByImage:
                        compareResult = FirstObjectCompare.Compare(x, y);
                        break;
                    default:
                        compareResult = FirstObjectCompare.Compare(x, y);
                        break;
                }
            }

            // Calculate correct return value based on object comparison
            if (OrderOfSort == SortOrder.Ascending)
            {
                // Ascending sort is selected, return normal result of compare operation
                return compareResult;
            }
            else if (OrderOfSort == SortOrder.Descending)
            {
                // Descending sort is selected, return negative result of compare operation
                return (-compareResult);
            }
            else
            {
                // Return '0' to indicate they are equal
                return 0;
            }
        }
    
        /// <summary>
        /// Gets or sets the number of the column to which to apply the sorting operation (Defaults to '0').
        /// </summary>
        public int SortColumn
        {
            set
            {
                ColumnToSort = value;
            }
            get
            {
                return ColumnToSort;
            }
        }

        /// <summary>
        /// Gets or sets the order of sorting to apply (for example, 'Ascending' or 'Descending').
        /// </summary>
        public SortOrder Order
        {
            set
            {
                OrderOfSort = value;
            }
            get
            {
                return OrderOfSort;
            }
        }
    
    }

    public class ImageTextComparer : IComparer
    {
        //private CaseInsensitiveComparer ObjectCompare;
        private NumberCaseInsensitiveComparer ObjectCompare;
        
        public ImageTextComparer()
        {
            // Initialize the CaseInsensitiveComparer object
            ObjectCompare = new NumberCaseInsensitiveComparer();
        }

        public int Compare(object x, object y)
        {
            //int compareResult;
            int image1, image2;
            ListViewItem listviewX, listviewY;

            // Cast the objects to be compared to ListViewItem objects
            listviewX = (ListViewItem)x;
            image1 = listviewX.ImageIndex;
            listviewY = (ListViewItem)y;
            image2 = listviewY.ImageIndex;

            if (image1 < image2)
            {
                return -1;
            }
            else if (image1 == image2)
            {
                return ObjectCompare.Compare(listviewX.Text.Trim(), listviewY.Text.Trim());
            }
            else
            {
                return 1;
            }
        }
    }

    public class CheckboxTextComparer : IComparer
    {
        private NumberCaseInsensitiveComparer ObjectCompare;

        public CheckboxTextComparer()
        {
            // Initialize the CaseInsensitiveComparer object
            ObjectCompare = new NumberCaseInsensitiveComparer();
        }

        public int Compare(object x, object y)
        {
            // Cast the objects to be compared to ListViewItem objects
            ListViewItem listviewX = (ListViewItem)x;
            ListViewItem listviewY = (ListViewItem)y;

            if (listviewX.Checked && !listviewY.Checked)
            {
                return -1;
            }
            else if (listviewX.Checked.Equals(listviewY.Checked))
            {
                if (listviewX.ImageIndex < listviewY.ImageIndex)
                {
                    return -1;
                }
                else if (listviewX.ImageIndex == listviewY.ImageIndex)
                {
                    return ObjectCompare.Compare(listviewX.Text.Trim(), listviewY.Text.Trim());
                }
                else
                {
                    return 1;
                }
            }
            else
            {
                return 1;
            }
        }
    }

    public class NumberCaseInsensitiveComparer : CaseInsensitiveComparer
    {
        public NumberCaseInsensitiveComparer ()
        {
            
        }
        
        public new int Compare(object x, object y)
        {
            if (x == null && y == null)
            {
                return 0;
            }
            else if (x == null && y != null)
            {
                return -1;
            }
            else if (x != null && y == null)
            {
                return 1;
            }

            if ((x is System.String) && IsDecimalNumber((string)x) && (y is System.String) && IsDecimalNumber((string)y))
            {
                try
                {
                    decimal xx= Decimal.Parse(((string)x).Trim());
                    decimal yy= Decimal.Parse(((string)y).Trim());

                    return base.Compare(xx,yy);
                }
                catch
                {
                    return -1;
                }
            }
            else
            {
                return base.Compare(x,y);
            }
        }

        // deprecated
        //private bool IsWholeNumber(string strNumber)
        //{
        //    Regex wholePattern = new Regex(@"^\d+$");
        //    return wholePattern.IsMatch(strNumber);
        //}

        private string GetNumberDecimalSeparator()
        {
            return System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
        }

        // http://stackoverflow.com/questions/4246077/matching-numbers-with-regular-expressions-only-digits-and-commas/4247184#4247184
        // https://www.debuggex.com/r/Lyx0F0y1LORvNhwA
        private bool IsDecimalNumber(string strNumber)
        {
            //@"^-?(\d+|(\d{1,3}((,|\.)\d{3})*))((,|\.)\d+)?$"

            //string regex = @"^-?(?:(?:0|[1-9][0-9]*)(?:" + GetNumberDecimalSeparator() + @"[0-9]+)?|[1-9][0-9]{1,2}(?:,[0-9]{3})+)$";

            string regex = @"^-?(\d+|(\d{1,3}((,|\.)\d{3})*))((,|\.)\d+)?$";

            Regex wholePattern = new Regex(regex);
            return wholePattern.IsMatch(strNumber);
        }
    }

}

History

2003-11-04 - Have added "How to use it" to description.

2013-08-16 - Added some bugfixes (number sort, int64 sorting, null subitem) and added checkbox sort

2015-07-21 - Added decimal sorting, fixed possible namespace bug with "SortOrder" conflicting with another class

License

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

Share

About the Author

Sven So.
Software Developer (Senior) biggest furniture dealer in the world
Germany Germany
No Biography provided

You may also be interested in...

Comments and Discussions

 
QuestionExcellent Pin
surfersat10-Aug-13 11:59
membersurfersat10-Aug-13 11:59 
GeneralRe: Excellent Pin
Sven So.11-Aug-13 22:37
memberSven So.11-Aug-13 22:37 
BugObject reference not set to an instance of an object. Pin
Infinity82724-Aug-11 2:51
memberInfinity82724-Aug-11 2:51 
QuestionRe: Object reference not set to an instance of an object. Pin
Sven So.24-Aug-11 2:58
memberSven So.24-Aug-11 2:58 
GeneralRe: Object reference not set to an instance of an object. Pin
Infinity82724-Aug-11 2:58
memberInfinity82724-Aug-11 2:58 
GeneralRe: Object reference not set to an instance of an object. Pin
Sven So.24-Aug-11 3:00
memberSven So.24-Aug-11 3:00 
Questionwhy does the compare methode occure when a new item gets added? Pin
Emanuel Steininger7-May-11 9:12
memberEmanuel Steininger7-May-11 9:12 
QuestionRe: why does the compare methode occure when a new item gets added? Pin
Sven So.9-May-11 22:23
memberSven So.9-May-11 22:23 
Can you post a code snippet, so I can see how you add the new item?
GeneralMy vote of 2 Pin
Charith Jayasundara27-Apr-11 15:24
memberCharith Jayasundara27-Apr-11 15:24 
GeneralMy vote of 3 Pin
shelby673-Apr-11 15:43
membershelby673-Apr-11 15:43 
GeneralIsWholeNumber fix Pin
Twinity10-Nov-09 0:37
memberTwinity10-Nov-09 0:37 
AnswerRe: IsWholeNumber fix Pin
Sven So.16-Nov-09 2:14
memberSven So.16-Nov-09 2:14 
GeneralObjectCompare.Compare throws Exception index out of range Pin
irafN78621-May-08 5:18
memberirafN78621-May-08 5:18 
AnswerRe: ObjectCompare.Compare throws Exception index out of range Pin
Member 78050263-Jul-11 22:42
memberMember 78050263-Jul-11 22:42 
GeneralWorks great for short lists Pin
scotlfs11-Apr-08 7:04
memberscotlfs11-Apr-08 7:04 
GeneralRe: Works great for short lists Pin
stotti_no113-Apr-08 23:37
memberstotti_no113-Apr-08 23:37 
GeneralRe: Works great for short lists - remove sorter during population Pin
SoerenD21-Aug-08 4:51
memberSoerenD21-Aug-08 4:51 
AnswerRe: Works great for short lists - remove sorter during population Pin
stotti_no125-Aug-08 7:13
memberstotti_no125-Aug-08 7:13 
GeneralThx hero Pin
Cptkli19-Nov-07 16:14
memberCptkli19-Nov-07 16:14 
QuestionCannot sort empty rows? Pin
MJay16-Oct-06 18:58
memberMJay16-Oct-06 18:58 
QuestionRe: Cannot sort empty rows? Pin
stotti_no116-Oct-06 20:56
memberstotti_no116-Oct-06 20:56 
AnswerRe: Cannot sort empty rows? Pin
MJay17-Oct-06 16:57
memberMJay17-Oct-06 16:57 
AnswerRe: Cannot sort empty rows? Pin
stotti_no117-Oct-06 20:57
memberstotti_no117-Oct-06 20:57 
GeneralRe: Cannot sort empty rows? Pin
MJay19-Oct-06 21:54
memberMJay19-Oct-06 21:54 
QuestionDont work!? Pin
Andy.C5-Apr-06 22:25
memberAndy.C5-Apr-06 22:25 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150901.1 | Last Updated 21 Jul 2015
Article Copyright 2003 by Sven So.
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid