Introduction
Sorting Listview
is not a big task but what if you have items with different data types as subitems in the listview
, in such case when you sort the listview
, then sorting result will not be correct as every item is stored as string
in the listview
. Say for example, if your listview
contains item "file size" which appears like "128 KB", if you perform normal sort, then items will be sorted based on the string
s.
This tip explains how to sort the listview
columns and set sorting icon (Ascending/Descending) when column header is clicked.
Using the Code
If you have listview
with columns containing different values (data types) like column with FileSize:168kb, Date modified 12/05/2012 12:00:01 pm, then while filling the values in listview
, fill the values in tags with respective data types. For example, suppose your listview
has column Date Modified. When you fill the listview
, fill the tag property of its items and subitems to actual data type without converting it to string
.
Explaining the Code
I have created listview
with name lstIdenticalResources
and added one listViewItem
with different values/Columns like Filename(string)
, CheckoutDate(DateTime)
, FileSize(int)
.
Here is the code snippet for it:
ListView lstIdenticalResources=new Listview();
lstIdenticalResources.Items.Clear();
ListViewItem item = new ListViewItem(new[] { fileName, CheckoutDate.ToString(), filesize});
item.Tag = file.LinkedFileId;
item.SubItems[lstIdenticalResources.Columns.OfType<ColumnHeader>().First(col=>col.Text=="Date modified").Index].Tag = file.CheckoutDate;
item.SubItems[lstIdenticalResources.Columns.OfType<ColumnHeader>().First(col => col.Text == "File Size").Index].Tag = Convert.ToDouble((float)file.LinkedFileValues.FirstOrDefault().FileSize / (float)1024);
lstIdenticalResources.Items.Add(item);
Then, create a class columnSorter
in your project. Add new class file ColumnSorter.cs and paste the following code to it:
public class ColumnSorter : IComparer
{
private int sortColumn;
public int SortColumn
{
set { sortColumn = value; }
get { return sortColumn; }
}
private SortOrder sortOrder;
public SortOrder Order
{
set { sortOrder = value; }
get { return sortOrder; }
}
private Comparer listViewItemComparer;
public ColumnSorter()
{
sortColumn = 0;
sortOrder = SortOrder.None;
listViewItemComparer = new Comparer(CultureInfo.CurrentUICulture);
}
public int Compare(object x, object y)
{
try
{
ListViewItem lviX = (ListViewItem)x;
ListViewItem lviY = (ListViewItem)y;
int compareResult = 0;
if (lviX.SubItems[sortColumn].Tag != null && lviY.SubItems[sortColumn].Tag != null)
{
compareResult = listViewItemComparer.Compare(lviX.SubItems[sortColumn].Tag, lviY.SubItems[sortColumn].Tag);
}
else
{
compareResult = listViewItemComparer.Compare(lviX.SubItems[sortColumn].Text, lviY.SubItems[sortColumn].Text);
}
if (sortOrder == SortOrder.Ascending)
{
return compareResult;
}
else if (sortOrder == SortOrder.Descending)
{
return (-compareResult);
}
else
{
return 0;
}
}
catch
{
return 0;
}
}
}
Now you will need to create the columnsorter
object in your code that should be assigned to listview
just before filling the listview
or after creating listview
.
ColumnSorter m_lstColumnSorter = new ColumnSorter();
lstIdenticalResources.ListViewItemSorter = m_lstColumnSorter ;
Now you will be able to sort your listview
but what else if you want up/down sorting arrows on your column header.
Use the following class to make it work.
internal static class ListViewExtensions
{
[StructLayout(LayoutKind.Sequential)]
public struct LVCOLUMN
{
public Int32 mask;
public Int32 cx;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszText;
public IntPtr hbm;
public Int32 cchTextMax;
public Int32 fmt;
public Int32 iSubItem;
public Int32 iImage;
public Int32 iOrder;
}
const Int32 HDI_WIDTH = 0x0001;
const Int32 HDI_HEIGHT = HDI_WIDTH;
const Int32 HDI_TEXT = 0x0002;
const Int32 HDI_FORMAT = 0x0004;
const Int32 HDI_LPARAM = 0x0008;
const Int32 HDI_BITMAP = 0x0010;
const Int32 HDI_IMAGE = 0x0020;
const Int32 HDI_DI_SETITEM = 0x0040;
const Int32 HDI_ORDER = 0x0080;
const Int32 HDI_FILTER = 0x0100;
const Int32 HDF_LEFT = 0x0000;
const Int32 HDF_RIGHT = 0x0001;
const Int32 HDF_CENTER = 0x0002;
const Int32 HDF_JUSTIFYMASK = 0x0003;
const Int32 HDF_RTLREADING = 0x0004;
const Int32 HDF_OWNERDRAW = 0x8000;
const Int32 HDF_STRING = 0x4000;
const Int32 HDF_BITMAP = 0x2000;
const Int32 HDF_BITMAP_ON_RIGHT = 0x1000;
const Int32 HDF_IMAGE = 0x0800;
const Int32 HDF_SORTUP = 0x0400;
const Int32 HDF_SORTDOWN = 0x0200;
const Int32 LVM_FIRST = 0x1000; const Int32 LVM_GETHEADER = LVM_FIRST + 31;
const Int32 HDM_FIRST = 0x1200; const Int32 HDM_SETIMAGELIST = HDM_FIRST + 8;
const Int32 HDM_GETIMAGELIST = HDM_FIRST + 9;
const Int32 HDM_GETITEM = HDM_FIRST + 11;
const Int32 HDM_SETITEM = HDM_FIRST + 12;
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage")]
private static extern IntPtr SendMessageLVCOLUMN(IntPtr hWnd, Int32 Msg, IntPtr wParam, ref LVCOLUMN lPLVCOLUMN);
public static void SetSortIcon(this ListView listView, int columnIndex, SortOrder order)
{
IntPtr columnHeader = SendMessage(listView.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
for (int columnNumber = 0; columnNumber <= listView.Columns.Count - 1; columnNumber++)
{
IntPtr columnPtr = new IntPtr(columnNumber);
LVCOLUMN lvColumn = new LVCOLUMN();
lvColumn.mask = HDI_FORMAT;
SendMessageLVCOLUMN(columnHeader, HDM_GETITEM, columnPtr, ref lvColumn);
if (!(order == SortOrder.None) && columnNumber == columnIndex)
{
switch (order)
{
case System.Windows.Forms.SortOrder.Ascending:
lvColumn.fmt &= ~HDF_SORTDOWN;
lvColumn.fmt |= HDF_SORTUP;
break;
case System.Windows.Forms.SortOrder.Descending:
lvColumn.fmt &= ~HDF_SORTUP;
lvColumn.fmt |= HDF_SORTDOWN;
break;
}
lvColumn.fmt |= (HDF_LEFT | HDF_BITMAP_ON_RIGHT);
}
else
{
lvColumn.fmt &= ~HDF_SORTDOWN & ~HDF_SORTUP & ~HDF_BITMAP_ON_RIGHT;
}
SendMessageLVCOLUMN(columnHeader, HDM_SETITEM, columnPtr, ref lvColumn);
}
}
}
Now, we just need to use the above code in our listview ColumnClick
event such that when column click happens, it will check the current sort order and accordingly set sort icon.
Here is the snippet for column click event:
private void lstIdenticalResources_ColumnClick(object sender, ColumnClickEventArgs e)
{
ListView myListView = (ListView)sender;
if (e.Column == m_lstColumnSorter.SortColumn)
{
if (m_lstColumnSorter.Order == SortOrder.Ascending)
{
m_lstColumnSorter.Order = SortOrder.Descending;
}
else
{
m_lstColumnSorter.Order = SortOrder.Ascending;
}
}
else
{
m_lstColumnSorter.SortColumn = e.Column;
m_lstColumnSorter.Order = SortOrder.Ascending;
}
myListView.Sort();
myListView.SetSortIcon(m_lstColumnSorter.SortColumn, m_lstColumnSorter.Order);
}
That's all!! Now you can sort your listview
.