Your_Unequal
Your_Unequal

Reputation: 246

C# Using IComparer to sort x number of columns

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.

Upvotes: 2

Views: 1852

Answers (1)

Anton&#237;n Lejsek
Anton&#237;n Lejsek

Reputation: 6103

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.

Upvotes: 3

Related Questions