Click here to Skip to main content
15,885,954 members
Articles / Programming Languages / C#
Tip/Trick

Sort listview Columns and Set Sort Arrow Icon on Column Header

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
21 Mar 2014CPOL2 min read 51.4K   8   5
Listview column sort and set sort column icon (ascending/desceding)

Image 1 Image 2

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 strings.

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:

C++
//Create listview
ListView lstIdenticalResources=new Listview();
lstIdenticalResources.Items.Clear();  

//Add item to list 
ListViewItem item = new ListViewItem(new[] { fileName, CheckoutDate.ToString(), filesize});
item.Tag = file.LinkedFileId;
 
//Here get the column header name and index and assign tag property to actual value without converting it to string

item.SubItems[lstIdenticalResources.Columns.OfType<ColumnHeader>().First(col=>col.Text=="Date modified").Index].Tag = file.CheckoutDate;//Datetime value

item.SubItems[lstIdenticalResources.Columns.OfType<ColumnHeader>().First(col => col.Text == "File Size").Index].Tag = Convert.ToDouble((float)file.LinkedFileValues.FirstOrDefault().FileSize / (float)1024);

//Add item to list
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:

C#
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);
       }

       /// <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)
       {
           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.

C#
ColumnSorter m_lstColumnSorter = new ColumnSorter(); 
//Specify the listviewcolumnsorter 
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.

C#
 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;         // List messages
        const Int32 LVM_GETHEADER = LVM_FIRST + 31;
        const Int32 HDM_FIRST = 0x1200;         // Header messages
        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);


//This method used to set arrow icon
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:

C#
private void lstIdenticalResources_ColumnClick(object sender, ColumnClickEventArgs e)
       {
           ListView myListView = (ListView)sender;

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

           // Perform the sort with these new sort options.
           myListView.Sort();
           myListView.SetSortIcon(m_lstColumnSorter.SortColumn, m_lstColumnSorter.Order);
       }

That's all!! Now you can sort your listview <img align="top" alt="Smile | :)" src="http://www.codeproject.com/script/Forums/Images/smiley_smile.gif" /> .

License

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


Written By
Software Developer (Senior)
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralSome Invalid Information Pin
nestor428720-Apr-17 5:42
nestor428720-Apr-17 5:42 
QuestionVery helpful. Thank you. Pin
Tom Clement11-Mar-16 13:24
professionalTom Clement11-Mar-16 13:24 
QuestionRegarding the position of arrows Pin
iamshiva22-Nov-15 18:24
iamshiva22-Nov-15 18:24 
QuestionSample upload? Pin
Member 107804211-Feb-15 3:25
Member 107804211-Feb-15 3:25 
QuestionSlight improvement (?) Pin
Jens Madsen, Højby23-Dec-14 11:19
Jens Madsen, Højby23-Dec-14 11:19 
If you have a lot of columns and/or frequent sorts, you can speed the process up by updating columns with glyphs only:

(Save the last column clicked before a new columnclick event)

C#
public static void SetSortIcon(this ListView listView, int columnIndex, SortOrder order,int lastColumn)
      {
          IntPtr columnHeader = SendMessage(listView.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);

          int[] cols = new int[] { columnIndex };
          if (lastColumn != columnIndex && lastColumn > -1 && lastColumn < listView.Columns.Count)
          {
              cols = new int[] { columnIndex ,lastColumn };
          }


          //for (int columnNumber = 0; columnNumber <= listView.Columns.Count - 1; columnNumber++)
         foreach( int columnNumber in cols ) etc.etc.

QuestionGood stuff Pin
Volynsky Alex22-Mar-14 12:23
professionalVolynsky Alex22-Mar-14 12:23 
QuestionScreenshot? Pin
Ravi Bhavnani21-Mar-14 2:37
professionalRavi Bhavnani21-Mar-14 2:37 

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.