C# DataGridView sorting with Generic List as underlying source

Viewed 46871

I'm using a Windows Forms DataGridView to display a generic list of MyObject objects.

First of all I wrap this collection into a BindingSource Collection, then:

dataGridView.DataSource = myBindingSource;

What I want to do is allow the user to sort the columns by clickin on the header of the column representing a concrete Property in MyObject.

I've read some articles that I should do sorting before binding. But it does not help me if I want to sort the columns in real time, being said when it's already binded.

The question is, what exactly do I need to do, so I could see the sorting arrows in DataGridView and I could sort every column ?

11 Answers

If creating your own user control is preferable, you can make a custom sort method using the code below:

    private string _lastSortColumn;
    private ListSortDirection _lastSortDirection;

    public void Sort(DataGridViewColumn column)
    {
        // Flip sort direction, if the column chosen was the same as last time
        if (column.Name == _lastSortColumn)
            _lastSortDirection = 1 - _lastSortDirection;
        // Otherwise, reset the sort direction to its default, ascending
        else
        {
            _lastSortColumn = column.Name;
            _lastSortDirection = ListSortDirection.Ascending;
        }

        // Prep data for sorting
        var data = (IEnumerable<dynamic>)DataSource;
        var orderProperty = column.DataPropertyName;

        // Sort data
        if (_lastSortDirection == ListSortDirection.Ascending)
            DataSource = data.OrderBy(x => x.GetType().GetProperty(orderProperty).GetValue(x, null)).ToList();
        else
            DataSource = data.OrderByDescending(x => x.GetType().GetProperty(orderProperty).GetValue(x, null)).ToList();

        // Set direction of the glyph
        Columns[column.Index].HeaderCell.SortGlyphDirection
            = _lastSortDirection == ListSortDirection.Ascending
            ? SortOrder.Ascending : SortOrder.Descending;
    }

You can then override the header click method to call your sort function:

    protected override void OnColumnHeaderMouseClick(DataGridViewCellMouseEventArgs e)
    {
        base.OnColumnHeaderMouseClick(e);

        var column = Columns[e.ColumnIndex];

        if (column.SortMode == DataGridViewColumnSortMode.Automatic
            || column.SortMode == DataGridViewColumnSortMode.NotSortable)
            Sort(column);
    }

Another option to solve the sorting issue with DataGridView when binding to List is, if you are not dealing with huge data set then probably you can attempt to convert the List to DataTable, and then bind the resulting DataTable to the BindingSource/DataGridView.

This would the need for a custom implementation of IComparer. In my case, I was dealing with a smaller list but there were more fields to display. So implementing IComparer meant writing too much boiler plate code.

Check this for terse way of converting the List to DataTable: https://stackoverflow.com/a/34062898/4534493

First of all I used System.Reflection; then: write this method

 public DataTable ToDataTable<T>(List<T> items)
    {
        DataTable dataTable = new DataTable(typeof(T).Name);
        //Get all the properties
        PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | 
        BindingFlags.Instance);

        foreach (PropertyInfo prop in Props)
        {
            //Setting column names as Property names

            dataTable.Columns.Add(prop.Name);
        }
        foreach (T item in items)
        {
            var values = new object[Props.Length];

            for (int i = 0; i < Props.Length; i++)
            {
                //inserting property values to datatable rows

                values[i] = Props[i].GetValue(item, null);
            }
            dataTable.Rows.Add(values);
        }
           //put a breakpoint here and check datatable
        return dataTable;
    }

then call method : DataTable dt = ToDataTable(lst.ToList());

Universal sorting of DataGridView columns with DataSource

private void ItemsTable_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
    var sortingColumn = ItemsTable.Columns[e.ColumnIndex];

    var propertyInfo = typeof(ListingInfo).GetProperty(sortingColumn.DataPropertyName);
    if (propertyInfo == null) // ignore columns with buttons or pictures
        return;

    foreach (DataGridViewColumn dataGridViewColumn in ItemsTable.Columns)
    {
        if (dataGridViewColumn != sortingColumn)
            dataGridViewColumn.HeaderCell.SortGlyphDirection = SortOrder.None;
    }
    sortingColumn.HeaderCell.SortGlyphDirection = sortingColumn.HeaderCell.SortGlyphDirection == SortOrder.Ascending
        ? SortOrder.Descending : SortOrder.Ascending;

    _listingsList.Sort((x, y) =>
    {
        var first = sortingColumn.HeaderCell.SortGlyphDirection == SortOrder.Ascending ? x : y;
        var second = sortingColumn.HeaderCell.SortGlyphDirection == SortOrder.Ascending ? y : x;
        var firstValue = propertyInfo.GetValue(first);
        var secondValue = propertyInfo.GetValue(second);
        if (firstValue == null)
            return secondValue == null ? 0 : -1;
        return secondValue == null ? 1 : ((IComparable)firstValue).CompareTo(secondValue);
    });

    // tell the binding list to raise a list change event so that bound controls reflect the new item order
    ItemsTable.ResetBindings();
    ItemsTable.Refresh();
}
Related