views:

1230

answers:

4

How do I sort columns of integers in a ListView

c#, .net 2.0, Winform

System.Windows.Forms.ListView

A: 

I'd do it in the data source (model) instead of the view. Sort it there and it should update it in the view through databinding.

Jimmy Chandra
you mean sort the datasouce, datatable in my instance, and then reload the listview when ever a column is clicked?
Brad
no, bad practice to reload each time. Google "ListViewItemSorter" and it will show you how to sort by each column.
Neil N
yeah that's what I thought.
Brad
Plus data binding doesn't work on a ListView
Grammarian
Oops... didn't know this was for windows form. I just assumed wpf.
Jimmy Chandra
+5  A: 

This is how I accomplished being able to sort on multiple columns, and being able to sort each column as a number, or as text.

First use this class:

class Sorter : System.Collections.IComparer
{
    public int Column = 0;
    public System.Windows.Forms.SortOrder Order = SortOrder.Ascending;
    public int Compare(object x, object y) // IComparer Member
    {
        if (!(x is ListViewItem))
            return (0);
        if (!(y is ListViewItem))
            return (0);

        ListViewItem l1 = (ListViewItem)x;
        ListViewItem l2 = (ListViewItem)y;

        if (l1.ListView.Columns[Column].Tag == null)
        {
            l1.ListView.Columns[Column].Tag = "Text";
        }

        if (l1.ListView.Columns[Column].Tag.ToString() == "Numeric")
        {
            float fl1 = float.Parse(l1.SubItems[Column].Text);
            float fl2 = float.Parse(l2.SubItems[Column].Text);

            if (Order == SortOrder.Ascending)
            {
                return fl1.CompareTo(fl2);
            }
            else
            {
                return fl2.CompareTo(fl1);
            }
        }
        else
        {
            string str1 = l1.SubItems[Column].Text;
            string str2 = l2.SubItems[Column].Text;

            if (Order == SortOrder.Ascending)
            {
                return str1.CompareTo(str2);
            }
            else
            {
                return str2.CompareTo(str1);
            }
        }
    }
}

In your form's constructor, set the sorter like this:

lvSeries.ListViewItemSorter = new Sorter();

Then handle the ColumnClick even of your listview control like this:

private void lvSeries_ColumnClick(object sender, ColumnClickEventArgs e)
    {
        Sorter s = (Sorter)lvSeries.ListViewItemSorter;
        s.Column = e.Column;

        if (s.Order == System.Windows.Forms.SortOrder.Ascending)
        {
            s.Order = System.Windows.Forms.SortOrder.Descending;
        }
        else
        {
            s.Order = System.Windows.Forms.SortOrder.Ascending;
        }
        lvSeries.Sort();
    }

This is all dependent on the Tag property of each column either being set to "Numeric" or not, so the sorter knows how to sort.

In the above example I cast the values as floats when numeric, you may want to change that to int.

Neil N
@Neil: It sorts them as text by default. E.g. 100 comes before 3. However, you can sort int's properly with custom ListViewItemSorter.
Josip Medved
Ahh thats right. string is the default type.
Neil N
I followeed the example at http://support.microsoft.com/kb/319401 but it still does not sort the integers properly.Wrt text, How do you set up the default column to sort. It sorts just fine on column 1 but I cant find the property to set for the column to sort on.
Brad
+2  A: 

You will need to create a class that implements the IComparer interface (the non-generic one). In that class you read the Text property from the correct sub-item, convert it to int, and do the comparison:

public class IntegerComparer : IComparer
{
    private int _colIndex;
    public IntegerComparer(int colIndex)
    {
        _colIndex = colIndex;
    }
    public int Compare(object x, object y)
    {
        int nx = int.Parse((x as ListViewItem).SubItems[_colIndex].Text);
        int ny = int.Parse((y as ListViewItem).SubItems[_colIndex].Text);
        return nx.CompareTo(ny);
    }
}

Then you assign such a comparer to the ListViewItemSorter property and invoke the sort method of the ListView control:

// create a comparer for column index 1 and assign it to the control, and sort
myListView.ListViewItemSorter = new IntegerComparer(1);
myListView.Sort();
Fredrik Mörk
some columns are numeric others are text...should I test for numeric prior to sorting?
Brad
I once made a ListViewItem comparer that examined each element pair in the Compare method, but this became rather slow. You will be better off having separate comparers and choosing which one to use based on which column that is being sorted, or creating one comparer class where you pass a value to the constructor indicating whether to make a sort based on numbers, dates or text, and then switching in the Compare method based on that value (which was the solution I went for in that case).
Fredrik Mörk
Good idea. But I created a private variable like 'sortType' inside my listview compare class instead.
Chris
+3  A: 

If you are getting started with a ListView, your life will be much much easier if you use an ObjectListView instead. ObjectListView is an open source wrapper around .NET WinForms ListView, and it solves all these annoying little problems that normally make working with a ListView so frustrating. For example, it automatically sorts ints so that '100' comes after '3' (DateTimes, bools, and everything else sorts correctly too).

Seriously, you will never want to go back to a plain ListView after using an ObjectListView.

Yes, I am the author -- but that doesn't mean I'm biased... OK, well maybe it does :) Look here for some other people's opinions.

Grammarian
Sounds cool, wish I had known about it sooner. +1
Neil N
Thanks...going to give it a test drive right now
Brad