Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
618 views
in Technique[技术] by (71.8m points)

c# - DataGridView - Sort Generic Lists by Click on Column Headers

When I assign a List<MyClass> to DataSource of a DataGridView, when I click on column headers, nothing happens and sorting doesn't work; but if I use a DataTable as data source, the sorting works perfect when a header is clicked.

The question is: Which kind of collection types should be used to enable sorting in DataGridView just like it works with DataTable when I click on column header?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

How Sorting works in a data-bound DataGridView

When you click on a column header in a data-bound DataGridView which its automatic sorting is enabled, first it checks if the list behind of the DataSource property is IBindingList, then using SupportsSorting checks if the list supports sorting. Then it calls ApplySort method to sort the list.

When you use a DataTable as data source of the grid, the list behind the data source is actually a DataView which implements IBindingList that supports sorting.

To have automatic support for sorting in a DataGridView, the list should implement IBindingList and its members which are related to sort.

Enable sorting in a BindingList<T>

To have typed list implementation of IBindingList which also supports sorting, a good option is deriving from BindingList<T>. It implements IBindingList but it doesn't support sorting by default. You can override it's methods and properties which are related to sorting: SupportsSortingCore, IsSortedCore, SortPropertyCore, SortDirectionCore and ApplySortCore.

Existing Implementations

There are some implementations around:

  • SortableBindingList<T> implementation which is used in Entity Framework.

  • SortableSearchableList<T> which is published in an MSDN article

  • If you are using Entity Framework, ToBindingList method of the Local property of DbSet<T> returns a sortable BindingList<T>.

SortableBindingList

Here is an implementation which is borrowed from Microsoft internal implementations with some small changes:

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;

public class SortableBindingList<T> : BindingList<T>
{
    private bool _isSorted;
    private ListSortDirection _sortDirection;
    private PropertyDescriptor _sortProperty;

    public SortableBindingList(List<T> list)
        : base(list)
    {
    }
    protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
    {
        if (PropertyComparer.CanSort(prop.PropertyType))
        {
            ((List<T>)Items).Sort(new PropertyComparer(prop, direction));
            _sortDirection = direction;
            _sortProperty = prop;
            _isSorted = true;
            OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        }
    }
    protected override void RemoveSortCore()
    {
        _isSorted = false;
        _sortProperty = null;
    }

    protected override bool IsSortedCore
    {
        get { return _isSorted; }
    }

    protected override ListSortDirection SortDirectionCore
    {
        get { return _sortDirection; }
    }

    protected override PropertyDescriptor SortPropertyCore
    {
        get { return _sortProperty; }
    }

    protected override bool SupportsSortingCore
    {
        get { return true; }
    }

    internal class PropertyComparer : Comparer<T>
    {
        private readonly IComparer _comparer;
        private readonly ListSortDirection _direction;
        private readonly PropertyDescriptor _prop;
        private readonly bool _useToString;

        public PropertyComparer(PropertyDescriptor prop, ListSortDirection direction)
        {
            if (!prop.ComponentType.IsAssignableFrom(typeof(T)))
            {
                throw new MissingMemberException(typeof(T).Name, prop.Name);
            }

            Debug.Assert(CanSort(prop.PropertyType), "Cannot use PropertyComparer unless it can be compared by IComparable or ToString");

            _prop = prop;
            _direction = direction;

            if (CanSortWithIComparable(prop.PropertyType))
            {
                var property = typeof(Comparer<>).MakeGenericType(new[] { prop.PropertyType }).GetTypeInfo().GetDeclaredProperty("Default");
                _comparer = (IComparer)property.GetValue(null, null);
                _useToString = false;
            }
            else
            {
                Debug.Assert(
                    CanSortWithToString(prop.PropertyType),
                    "Cannot use PropertyComparer unless it can be compared by IComparable or ToString");

                _comparer = StringComparer.CurrentCultureIgnoreCase;
                _useToString = true;
            }
        }

        public override int Compare(T left, T right)
        {
            var leftValue = _prop.GetValue(left);
            var rightValue = _prop.GetValue(right);

            if (_useToString)
            {
                leftValue = leftValue != null ? leftValue.ToString() : null;
                rightValue = rightValue != null ? rightValue.ToString() : null;
            }

            return _direction == ListSortDirection.Ascending
                       ? _comparer.Compare(leftValue, rightValue)
                       : _comparer.Compare(rightValue, leftValue);
        }

        public static bool CanSort(Type type)
        {
            return CanSortWithToString(type) || CanSortWithIComparable(type);
        }

        private static bool CanSortWithIComparable(Type type)
        {
            return type.GetInterface("IComparable") != null ||
                   (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
        }

        private static bool CanSortWithToString(Type type)
        {
            return type.Equals(typeof(XNode)) || type.IsSubclassOf(typeof(XNode));
        }
    }
}

public static class EnumerableExtensions
{
    public static BindingList<T> ToSortableBindingList<T>(this IEnumerable<T> source)
    {
        return new SortableBindingList<T>(source.ToList());
    }
}

Example

private void Form1_Load(object sender, EventArgs e)
{
    var list = Enumerable.Range(1, 10)
        .Select(x => new { A = x, B = $"x" })
        .ToSortableBindingList();
    dataGridView1.DataSource = list;
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...