2

I would like to be able to sort data by x columns, where x is not constant.

Context: I have a DataGridView (unbound), in which there are a number of rows. As I want to sort by more than one column, I have created a class which implements IComparer for the purposes of sorting the grid rows. I have got this working for sorting a single column however I am unsure how to now alter this class to allow sorting for a number of columns > 1.

A lot of the answers I have seen previously provide examples for sorting two or three columns however these appear to be comparing A->B, then B->C and so on. I am looking for something slightly more dynamic.

Example:

  1. User clicks Column 4; data is sorted in order of records in Column 4;
  2. User then clicks Column 6; data is sorted in order of records in Column 4 THEN Column 6;
  3. User clicks Column 2; data is sorted in order of records in Column 4 THEN Column 6 THEN Column 2;

etc.

What I currently have is as follows:

public class FormGrid : DataGridView
{
    List<GridSortData> ColIndexSorts = new List<GridSortData>();

    private class GridSortData
    {
        public int ColumnSortIndex;
        public System.Windows.Forms.SortOrder SortOrder;
    }       

    private class GridSort : System.Collections.IComparer
    {
        private static int SortOrder = 1;
        private int SortingColumn;

        public GridSort(System.Windows.Forms.SortOrder sortOrder, int ColumnToSort)
        {
            SortingColumn = ColumnToSort;
            SortOrder = sortOrder == System.Windows.Forms.SortOrder.Ascending ? 1 : -1;
        }

        public int Compare(object x, object y)
        {
            FormGridRow FirstComparable = (FormGridRow)x;
            FormGridRow SecondComparable = (FormGridRow)y;

            int result = 1;

                    result =  FirstComparable.Cells[SortingColumn].Value.ToString().CompareTo(SecondComparable.Cells[SortingColumn].Value.ToString());

            return result * SortOrder;
        }
    }

    private void TSortGrid(int ColIndexToSort, MouseButtons MouseButton)
    {
        GridSortData ColumnToSort = new GridSortData();

        ColumnToSort.ColumnSortIndex = ColIndexToSort;

        if (MouseButton == System.Windows.Forms.MouseButtons.Left)
        {
            ColumnToSort.SortOrder = System.Windows.Forms.SortOrder.Ascending;
        }
        else
        {
            ColumnToSort.SortOrder = System.Windows.Forms.SortOrder.Descending;
        }

        ColIndexSorts.Add(ColumnToSort);

        for (int i = 0; i < ColIndexSorts.Count; i++)
        {
            this.Sort(new GridSort(ColIndexSorts[i].SortOrder, ColIndexSorts[i].ColumnSortIndex));
        }
    }
}

The issue in which this results currently is that after you have selected five columns, the ColIndexSorts list contains data for sorting of five columns; however, due to the way the for loop is operating, it is sorting by ascending / descending correctly however it is only sorting by the final sort in the list.

I feel like the solution to this is for each sort within the list of sorts to perform, remember the row order after each sort and then perform an additional sort upon that data.

Your_Unequal
  • 246
  • 1
  • 5
  • 17

1 Answers1

3

You need the sort to be stable. If the one You use is not, use another, linq provides one on IEnumerable for instance. I admit it means quite big changes to the code, as You need to sort outside of datagridview and only assign the result. Btw comparing values by string representation is far from perfect for numbers.

EDIT

I somehow overlooked that it is unbound. If You want to go this way, You can do it like this:

private class GridSort : System.Collections.IComparer
{
    List<GridSortData> ColIndexSorts = new List<GridSortData>();

    public GridSort(List<GridSortData> ColIndexSorts)
    {
        this.ColIndexSorts = ColIndexSorts;
    }

    public int Compare(object x, object y)
    {
        FormGridRow FirstComparable = (FormGridRow)x;
        FormGridRow SecondComparable = (FormGridRow)y;

        for (int i = 0; i < ColIndexSorts.Count; ++i)
        {
            int index = ColIndexSorts[i].ColumnSortIndex;
            object a = FirstComparable.Cells[index].Value;
            object b = SecondComparable.Cells[index].Value;
            int result = a.ToString().CompareTo(b.ToString());
            if (result != 0)
            {
                if (ColIndexSorts[i].SortOrder == SortOrder.Ascending)
                {
                    return result;
                }
                else
                {
                    return -result;
                }
            }
        }

        return 0;
    }
}

You have to set SortMode for all columns to programmatic and handle ColumnHeaderMouseClick, but I guess You already know that.

Antonín Lejsek
  • 6,003
  • 2
  • 16
  • 18
  • I previously looked at sorting outside of the DataGridView as this did allow greater efficiency in terms of allowing the values to be handled 'as is' for the sorting however the main problem with that is that the control is unbound. Therefore when sorting, it had to sort the data outside of the control, then clear all of the rows from the control and add the data again; which, for even more than a few hundred records, looks horrible on the UI. – Your_Unequal Feb 05 '16 at 08:55
  • This works perfectly, many thanks. Honestly I never thought to have the 'for' loop inside the sort function itself; thinking about it now having it outside of it would just result in the grid only keeping the last column on which it sorted! Also yes you are correct, all of the columns which go into the grid are set to programmatic sort when they're declared and everything's inside a ColumnHeaderMouseClick event. I've finessed it so that it sorts by multiple columns as you hold 'Shift' and then goes back to single column sort when you let go and click :-) – Your_Unequal Feb 05 '16 at 18:22