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;
if ( e.Column == lvwColumnSorter.SortColumn )
{
if (lvwColumnSorter.Order == SortOrder.Ascending)
{
lvwColumnSorter.Order = SortOrder.Descending;
}
else
{
lvwColumnSorter.Order = SortOrder.Ascending;
}
}
else
{
lvwColumnSorter.SortColumn = e.Column;
lvwColumnSorter.Order = SortOrder.Ascending;
}
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
{
public class ListViewColumnSorter : IComparer
{
public enum SortModifiers
{
SortByImage,
SortByCheckbox,
SortByText
}
public int ColumnToSort;
public SortOrder OrderOfSort;
private NumberCaseInsensitiveComparer ObjectCompare;
private ImageTextComparer FirstObjectCompare;
private CheckboxTextComparer FirstObjectCompare2;
private SortModifiers mySortModifier = SortModifiers.SortByText;
public SortModifiers _SortModifier
{
set
{
mySortModifier = value;
}
get
{
return mySortModifier;
}
}
public ListViewColumnSorter()
{
ColumnToSort = 0;
ObjectCompare = new NumberCaseInsensitiveComparer();
FirstObjectCompare = new ImageTextComparer();
FirstObjectCompare2 = new CheckboxTextComparer();
}
public int Compare(object x, object y)
{
int compareResult = 0;
ListViewItem listviewX, listviewY;
listviewX = (ListViewItem)x;
listviewY = (ListViewItem)y;
ListView listViewMain = listviewX.ListView;
if (listViewMain.Sorting != SortOrder.Ascending &&
listViewMain.Sorting != SortOrder.Descending)
{
return compareResult;
}
if (mySortModifier.Equals(SortModifiers.SortByText) || ColumnToSort > 0)
{
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;
}
}
if (OrderOfSort == SortOrder.Ascending)
{
return compareResult;
}
else if (OrderOfSort == SortOrder.Descending)
{
return (-compareResult);
}
else
{
return 0;
}
}
public int SortColumn
{
set
{
ColumnToSort = value;
}
get
{
return ColumnToSort;
}
}
public SortOrder Order
{
set
{
OrderOfSort = value;
}
get
{
return OrderOfSort;
}
}
}
public class ImageTextComparer : IComparer
{
private NumberCaseInsensitiveComparer ObjectCompare;
public ImageTextComparer()
{
ObjectCompare = new NumberCaseInsensitiveComparer();
}
public int Compare(object x, object y)
{
int image1, image2;
ListViewItem listviewX, listviewY;
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()
{
ObjectCompare = new NumberCaseInsensitiveComparer();
}
public int Compare(object x, object y)
{
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);
}
}
private string GetNumberDecimalSeparator()
{
return System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
}
private bool IsDecimalNumber(string strNumber)
{
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