Sorting A ListView By Column

Viewed 115339

Currently I use a custom sorter on the listview, and i can sort the listview each time i click on the FIRST column, but it won't sort by other columns.

SortStyle: Variable to determine whether it is Ascending Sort, or Descending.

if (e.Column == 0)
{
    if (SortStyle == 0)
    {
        List.ListViewItemSorter = customSortDsc;
        SortStyle = 1;
    }
    else
    {
        List.ListViewItemSorter = customSortAsc;
        SortStyle = 0;
    }
}

This works fine when sorting for the first column, but if you were to do it on any other column, it would just sort by the first column. Is there a way to sort by the column clicked?

15 Answers

i used this trick

private void lv_TavComEmpty_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            ListView lv = (ListView)sender;

            //propriety SortOrder make me some problem on graphic layout
            //i use this tag to set last order
            if (lv.Tag == null || (int)lv.Tag > 0)
            //if (lv.Sorting == SortOrder.Ascending)
            {
                ListViewItem[] tmp = lv.Items.Cast<ListViewItem>().OrderBy(t => t.SubItems[e.Column].Text).ToArray();
                lv.Items.Clear();
                lv.Items.AddRange(tmp);

                lv.Tag = -1;
                //lv.Sorting = SortOrder.Descending;
            }
            else
            {
                ListViewItem[] tmp = lv.Items.Cast<ListViewItem>().OrderByDescending(t => t.SubItems[e.Column].Text).ToArray();
                lv.Items.Clear();
                lv.Items.AddRange(tmp);

                lv.Tag = +1;
                //lv.Sorting = SortOrder.Ascending;
            }
        }

My solution is a class to sort listView items when you click on column header.

You can specify the type of each column.

listView.ListViewItemSorter = new ListViewColumnSorter();
listView.ListViewItemSorter.ColumnsTypeComparer.Add(0, DateTime);
listView.ListViewItemSorter.ColumnsTypeComparer.Add(1, int);

That's it !

The C# class :

using System.Collections;
using System.Collections.Generic;
using EDV;

namespace System.Windows.Forms
{
    /// <summary>
    /// Cette classe est une implémentation de l'interface 'IComparer' pour le tri des items de ListView. Adapté de http://support.microsoft.com/kb/319401.
    /// </summary>
    /// <remarks>Intégré par EDVariables.</remarks>
    public class ListViewColumnSorter : IComparer
    {
        /// <summary>
        /// Spécifie la colonne à trier
        /// </summary>
        private int ColumnToSort;
        /// <summary>
        /// Spécifie l'ordre de tri (en d'autres termes 'Croissant').
        /// </summary>
        private SortOrder OrderOfSort;
        /// <summary>
        /// Objet de comparaison ne respectant pas les majuscules et minuscules
        /// </summary>
        private CaseInsensitiveComparer ObjectCompare;

        /// <summary>
        /// Constructeur de classe.  Initialise la colonne sur '0' et aucun tri
        /// </summary>
        public ListViewColumnSorter()
            : this(0, SortOrder.None) { }

        /// <summary>
        /// Constructeur de classe.  Initializes various elements
        /// <param name="columnToSort">Spécifie la colonne à trier</param>
        /// <param name="orderOfSort">Spécifie l'ordre de tri</param>
        /// </summary>
        public ListViewColumnSorter(int columnToSort, SortOrder orderOfSort)
        {
            // Initialise la colonne
            ColumnToSort = columnToSort;

            // Initialise l'ordre de tri
            OrderOfSort = orderOfSort;

            // Initialise l'objet CaseInsensitiveComparer
            ObjectCompare = new CaseInsensitiveComparer();

            // Dictionnaire de comparateurs
            ColumnsComparer = new Dictionary<int, IComparer>();
            ColumnsTypeComparer = new Dictionary<int, Type>();

        }

        /// <summary>
        /// Cette méthode est héritée de l'interface IComparer.  Il compare les deux objets passés en effectuant une comparaison 
        ///qui ne tient pas compte des majuscules et des minuscules.
        /// <br/>Si le comparateur n'existe pas dans ColumnsComparer, CaseInsensitiveComparer est utilisé.
        /// </summary>
        /// <param name="x">Premier objet à comparer</param>
        /// <param name="x">Deuxième objet à comparer</param>
        /// <returns>Le résultat de la comparaison. "0" si équivalent, négatif si 'x' est inférieur à 'y' 
        ///et positif si 'x' est supérieur à 'y'</returns>
        public int Compare(object x, object y)
        {
            int compareResult;
            ListViewItem listviewX, listviewY;

            // Envoit les objets à comparer aux objets ListViewItem
            listviewX = (ListViewItem)x;
            listviewY = (ListViewItem)y;

            if (listviewX.SubItems.Count < ColumnToSort + 1 || listviewY.SubItems.Count < ColumnToSort + 1)
                return 0;

            IComparer objectComparer = null;
            Type comparableType = null;
            if (ColumnsComparer == null || !ColumnsComparer.TryGetValue(ColumnToSort, out objectComparer))
                if (ColumnsTypeComparer == null || !ColumnsTypeComparer.TryGetValue(ColumnToSort, out comparableType))
                    objectComparer = ObjectCompare;

            // Compare les deux éléments
            if (comparableType != null) {
                //Conversion du type
                object valueX = listviewX.SubItems[ColumnToSort].Text;
                object valueY = listviewY.SubItems[ColumnToSort].Text;
                if (!edvTools.TryParse(ref valueX, comparableType) || !edvTools.TryParse(ref valueY, comparableType))
                    return 0;
                compareResult = (valueX as IComparable).CompareTo(valueY);
            }
            else
                compareResult = objectComparer.Compare(listviewX.SubItems[ColumnToSort].Text, listviewY.SubItems[ColumnToSort].Text);

            // Calcule la valeur correcte d'après la comparaison d'objets
            if (OrderOfSort == SortOrder.Ascending) {
                // Le tri croissant est sélectionné, renvoie des résultats normaux de comparaison
                return compareResult;
            }
            else if (OrderOfSort == SortOrder.Descending) {
                // Le tri décroissant est sélectionné, renvoie des résultats négatifs de comparaison
                return (-compareResult);
            }
            else {
                // Renvoie '0' pour indiquer qu'ils sont égaux
                return 0;
            }
        }

        /// <summary>
        /// Obtient ou définit le numéro de la colonne à laquelle appliquer l'opération de tri (par défaut sur '0').
        /// </summary>
        public int SortColumn
        {
            set
            {
                ColumnToSort = value;
            }
            get
            {
                return ColumnToSort;
            }
        }

        /// <summary>
        /// Obtient ou définit l'ordre de tri à appliquer (par exemple, 'croissant' ou 'décroissant').
        /// </summary>
        public SortOrder Order
        {
            set
            {
                OrderOfSort = value;
            }
            get
            {
                return OrderOfSort;
            }
        }

        /// <summary>
        /// Dictionnaire de comparateurs par colonne.
        /// <br/>Pendant le tri, si le comparateur n'existe pas dans ColumnsComparer, CaseInsensitiveComparer est utilisé.
        /// </summary>
        public Dictionary<int, IComparer> ColumnsComparer { get; set; }

        /// <summary>
        /// Dictionnaire de comparateurs par colonne.
        /// <br/>Pendant le tri, si le comparateur n'existe pas dans ColumnsTypeComparer, CaseInsensitiveComparer est utilisé.
        /// </summary>
        public Dictionary<int, Type> ColumnsTypeComparer { get; set; }
    }
}

Initializing a ListView :

    <var>Visual.WIN.ctrlListView.OnShown</var>  : 
    eventSender.Columns.Clear();
    eventSender.SmallImageList = edvWinForm.ImageList16;
    eventSender.ListViewItemSorter = new ListViewColumnSorter();
    var col = eventSender.Columns.Add("Répertoire");
    col.Width = 160;
    col.ImageKey = "Domain";
    col = eventSender.Columns.Add("Fichier");
    col.Width = 180;
    col.ImageKey = "File";
    col = eventSender.Columns.Add("Date");
    col.Width = 120;
    col.ImageKey = "DateTime";
    eventSender.ListViewItemSorter.ColumnsTypeComparer.Add(col.Index, DateTime);
    col = eventSender.Columns.Add("Position");
    col.TextAlign = HorizontalAlignment.Right;
    col.Width = 80;
    col.ImageKey = "Num";
    eventSender.ListViewItemSorter.ColumnsTypeComparer.Add(col.Index, Int32);

Fill a ListView :

<var>Visual.WIN.cmdSearch.OnClick</var>  : 
//non récursif et sans fonction
    ..ctrlListView:Items.Clear();
    ..ctrlListView:Sorting = SortOrder.None;
    var group = ..ctrlListView:Groups.Add(DateTime.Now.ToString()
                , Path.Combine(..cboDir:Text, ..ctrlPattern1:Text) + " contenant " + ..ctrlSearch1:Text);
    var perf =  Environment.TickCount;

    var files = new DirectoryInfo(..cboDir:Text).GetFiles(..ctrlPattern1:Text)
    var search = ..ctrlSearch1:Text;
    var ignoreCase = ..Search.IgnoreCase;
    //var result = new StringBuilder();
    var dirLength : int = ..cboDir:Text.Length;
    var position : int;
    var added : int = 0;
    for(var i : int = 0; i &lt; files.Length; i++){
        var file = files[i];
        if(search == ""
        || (position = File.ReadAllText(file.FullName).IndexOf(String(search)
                            , StringComparison(ignoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture))) &gt; =0) {

        //  result.AppendLine(file.FullName.Substring(dirLength) + "\tPos : " + pkvFile.Value);
            var item = ..ctrlListView:Items.Add(file.FullName.Substring(dirLength));
            item.SubItems.Add(file.Name);
            item.SubItems.Add(File.GetLastWriteTime(file.FullName).ToString());
            item.SubItems.Add(position.ToString("# ### ##0"));
            item.Group = group;
            ++added;
        }
    }
    group.Header += " : " + added + "/" + files.Length + " fichier(s)"
                + "  en " + (Environment.TickCount - perf).ToString("# ##0 msec");

On ListView column click :

<var>Visual.WIN.ctrlListView.OnColumnClick</var>  : 
// Déterminer si la colonne sélectionnée est déjà la colonne triée.
var sorter = eventSender.ListViewItemSorter;
if ( eventArgs.Column == sorter .SortColumn )
{
    // Inverser le sens de tri en cours pour cette colonne.
    if (sorter.Order == SortOrder.Ascending)
    {
        sorter.Order = SortOrder.Descending;
    }
    else
    {
        sorter.Order = SortOrder.Ascending;
    }
}
else
{
    // Définir le numéro de colonne à trier ; par défaut sur croissant.
    sorter.SortColumn = eventArgs.Column;
    sorter.Order = SortOrder.Ascending;
}

// Procéder au tri avec les nouvelles options.
eventSender.Sort();

Function edvTools.TryParse used above

class edvTools {
    /// <summary>
    /// Tente la conversion d'une valeur suivant un type EDVType
    /// </summary>
    /// <param name="pValue">Référence de la valeur à convertir</param>
    /// <param name="pType">Type EDV en sortie</param>
    /// <returns></returns>
    public static bool TryParse(ref object pValue, System.Type pType)
    {
        int lIParsed;
        double lDParsed;
        string lsValue;
        if (pValue == null) return false;
        if (pType.Equals(typeof(bool))) {
            bool lBParsed;
            if (pValue is bool) return true;
            if (double.TryParse(pValue.ToString(), out lDParsed)) {
                pValue = lDParsed != 0D;
                return true;
            }
            if (bool.TryParse(pValue.ToString(), out lBParsed)) {
                pValue = lBParsed;
                return true;
            }
            else
                return false;
        }
        if (pType.Equals(typeof(Double))) {
            if (pValue is Double) return true;
            if (double.TryParse(pValue.ToString(), out lDParsed)
                || double.TryParse(pValue.ToString().Replace(NumberDecimalSeparatorNOT, NumberDecimalSeparator), out lDParsed)) {
                pValue = lDParsed;
                return true;
            }
            else
                return false;
        }
        if (pType.Equals(typeof(int))) {
            if (pValue is int) return true;
            if (Int32.TryParse(pValue.ToString(), out lIParsed)) {
                pValue = lIParsed;
                return true;
            }
            else if (double.TryParse(pValue.ToString(), out lDParsed)) {
                pValue = (int)lDParsed;
                return true;
            }
            else
                return false;
        }
        if (pType.Equals(typeof(Byte))) {
            if (pValue is byte) return true;
            byte lByte;
            if (Byte.TryParse(pValue.ToString(), out lByte)) {
                pValue = lByte;
                return true;
            }
            else if (double.TryParse(pValue.ToString(), out lDParsed)) {
                pValue = (byte)lDParsed;
                return true;
            }
            else
                return false;
        }
        if (pType.Equals(typeof(long))) {
            long lLParsed;
            if (pValue is long) return true;
            if (long.TryParse(pValue.ToString(), out lLParsed)) {
                pValue = lLParsed;
                return true;
            }
            else if (double.TryParse(pValue.ToString(), out lDParsed)) {
                pValue = (long)lDParsed;
                return true;
            }
            else
                return false;
        }
        if (pType.Equals(typeof(Single))) {
            if (pValue is float) return true;
            Single lSParsed;
            if (Single.TryParse(pValue.ToString(), out lSParsed)
                || Single.TryParse(pValue.ToString().Replace(NumberDecimalSeparatorNOT, NumberDecimalSeparator), out lSParsed)) {
                pValue = lSParsed;
                return true;
            }
            else
                return false;
        }
        if (pType.Equals(typeof(DateTime))) {
            if (pValue is DateTime) return true;
            DateTime lDTParsed;
            if (DateTime.TryParse(pValue.ToString(), out lDTParsed)) {
                pValue = lDTParsed;
                return true;
            }
            else if (pValue.ToString().Contains("UTC")) //Date venant de JScript
            {
                if (_MonthsUTC == null) InitMonthsUTC();
                string[] lDateParts = pValue.ToString().Split(' ');
                lDTParsed = new DateTime(int.Parse(lDateParts[5]), _MonthsUTC[lDateParts[1]], int.Parse(lDateParts[2]));
                lDateParts = lDateParts[3].ToString().Split(':');
                pValue = lDTParsed.AddSeconds(int.Parse(lDateParts[0]) * 3600 + int.Parse(lDateParts[1]) * 60 + int.Parse(lDateParts[2]));
                return true;
            }
            else
                return false;

        }
        if (pType.Equals(typeof(Array))) {
            if (pValue is System.Collections.ICollection || pValue is System.Collections.ArrayList)
                return true;
            return pValue is System.Data.DataTable
                || pValue is string && (pValue as string).StartsWith("<");
        }
        if (pType.Equals(typeof(DataTable))) {
            return pValue is System.Data.DataTable
                || pValue is string && (pValue as string).StartsWith("<");

        }
        if (pType.Equals(typeof(System.Drawing.Bitmap))) {
            return pValue is System.Drawing.Image || pValue is byte[];

        }
        if (pType.Equals(typeof(System.Drawing.Image))) {
            return pValue is System.Drawing.Image || pValue is byte[];

        }
        if (pType.Equals(typeof(System.Drawing.Color))) {
            if (pValue is System.Drawing.Color) return true;
            if (pValue is System.Drawing.KnownColor) {
                pValue = System.Drawing.Color.FromKnownColor((System.Drawing.KnownColor)pValue);
                return true;
            }

            int lARGB;
            if (!int.TryParse(lsValue = pValue.ToString(), out lARGB)) {
                if (lsValue.StartsWith("Color [A=", StringComparison.InvariantCulture)) {
                    foreach (string lsARGB in lsValue.Substring("Color [".Length, lsValue.Length - "Color []".Length).Split(','))
                        switch (lsARGB.TrimStart().Substring(0, 1)) {
                            case "A":
                                lARGB = int.Parse(lsARGB.Substring(2)) * 0x1000000;
                                break;
                            case "R":
                                lARGB += int.Parse(lsARGB.TrimStart().Substring(2)) * 0x10000;
                                break;
                            case "G":
                                lARGB += int.Parse(lsARGB.TrimStart().Substring(2)) * 0x100;
                                break;
                            case "B":
                                lARGB += int.Parse(lsARGB.TrimStart().Substring(2));
                                break;
                            default:
                                break;
                        }
                    pValue = System.Drawing.Color.FromArgb(lARGB);
                    return true;
                }
                if (lsValue.StartsWith("Color [", StringComparison.InvariantCulture)) {
                    pValue = System.Drawing.Color.FromName(lsValue.Substring("Color [".Length, lsValue.Length - "Color []".Length));
                    return true;
                }
                return false;
            }
            pValue = System.Drawing.Color.FromArgb(lARGB);
            return true;
        }
        if (pType.IsEnum) {
            try {
                if (pValue == null) return false;
                if (pValue is int || pValue is byte || pValue is ulong || pValue is long || pValue is double)
                    pValue = Enum.ToObject(pType, pValue);
                else
                    pValue = Enum.Parse(pType, pValue.ToString());
            }
            catch {
                return false;
            }
        }
        return true;
    }
}

I used the same base class as that what the others seem to use only I altered it as to allow for string, date and numerical sorting.

You can initialize it using a backing-field like so:

private readonly ListViewColumnSorterExt fileSorter;
...
public Form1()
{
    InitializeComponent();
    fileSorter = new ListViewColumnSorterExt(myListView);
}

Here is the code:

public class ListViewColumnSorterExt : IComparer
{
    /// <summary>
    /// Specifies the column to be sorted
    /// </summary>
    private int ColumnToSort;
    /// <summary>
    /// Specifies the order in which to sort (i.e. 'Ascending').
    /// </summary>
    private SortOrder OrderOfSort;
    /// <summary>
    /// Case insensitive comparer object
    /// </summary>
    private CaseInsensitiveComparer ObjectCompare;

    private ListView listView;
    /// <summary>
    /// Class constructor.  Initializes various elements
    /// </summary>
    public ListViewColumnSorterExt(ListView lv)
    {
        listView = lv;
        listView.ListViewItemSorter = this;
        listView.ColumnClick += new ColumnClickEventHandler(listView_ColumnClick);

        // Initialize the column to '0'
        ColumnToSort = 0;

        // Initialize the sort order to 'none'
        OrderOfSort = SortOrder.None;

        // Initialize the CaseInsensitiveComparer object
        ObjectCompare = new CaseInsensitiveComparer();
    }

    private void listView_ColumnClick(object sender, ColumnClickEventArgs e)
    {
        ReverseSortOrderAndSort(e.Column, (ListView)sender);
    }

    /// <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;
        ListViewItem listviewX, listviewY;

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

        if (decimal.TryParse(listviewX.SubItems[ColumnToSort].Text, out decimal dx) && decimal.TryParse(listviewY.SubItems[ColumnToSort].Text, out decimal dy))
        {
            //compare the 2 items as doubles
            compareResult = decimal.Compare(dx, dy);
        }
        else if (DateTime.TryParse(listviewX.SubItems[ColumnToSort].Text, out DateTime dtx) && DateTime.TryParse(listviewY.SubItems[ColumnToSort].Text, out DateTime dty))
        {
            //compare the 2 items as doubles
            compareResult = DateTime.Compare(dtx, dty);
        }
        else
        {
            // Compare the two items
            compareResult = ObjectCompare.Compare(listviewX.SubItems[ColumnToSort].Text, listviewY.SubItems[ColumnToSort].Text);
        }
        // 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>
    private int SortColumn
    {
        set
        {
            ColumnToSort = value;
        }
        get
        {
            return ColumnToSort;
        }
    }

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

    private void ReverseSortOrderAndSort(int column, ListView lv)
    {
        // Determine if clicked column is already the column that is being sorted.
        if (column == this.SortColumn)
        {
            // Reverse the current sort direction for this column.
            if (this.Order == SortOrder.Ascending)
            {
                this.Order = SortOrder.Descending;
            }
            else
            {
                this.Order = SortOrder.Ascending;
            }
        }
        else
        {
            // Set the column number that is to be sorted; default to ascending.
            this.SortColumn = column;
            this.Order = SortOrder.Ascending;
        }

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

If you'd like to assign icons in regards to the sort order then add an image list to the Listview and make sure you update the below sample to reflect the names of your images that you use for sorting (you can assign them any name when you import them). Update the above listView_ColumnClick to something like this:

private void listView_ColumnClick(object sender, ColumnClickEventArgs e)
{
    if (sender is ListView lv)
    {
        ReverseSortOrderAndSort(e.Column, lv);

        if (   lv.Columns[e.Column].ImageList.Images.Keys.Contains("Ascending") 
            && lv.Columns[e.Column].ImageList.Images.Keys.Contains("Descending"))
        {
            switch (Order)
            {
                case SortOrder.Ascending:
                    lv.Columns[e.Column].ImageKey = "Ascending";
                    break;
                case SortOrder.Descending:
                    lv.Columns[e.Column].ImageKey = "Descending";
                    break;
                case SortOrder.None:
                    lv.Columns[e.Column].ImageKey = string.Empty;
                    break;

            }
        }

    }
}
Related