Вопрос

Я использую BindingList < T > в моих формах Windows Forms, который содержит список IComparable < Contact > " Контакт-объекты. Теперь я хочу, чтобы пользователь мог сортировать по любому столбцу, отображаемому в сетке.

В сети MSDN описан способ, который показывает, как реализовать пользовательскую коллекцию на основе BindingList < T > , которая позволяет выполнять сортировку. Но разве нет события Sort или чего-то, что можно было бы перехватить в DataGridView (или, что еще лучше, в BindingSource), чтобы отсортировать базовую коллекцию с помощью пользовательского кода?

Мне не очень нравится способ, описанный в MSDN. Другой способ, которым я мог бы легко применить запрос LINQ к коллекции.

Это было полезно?

Решение

Я высоко ценю решение Матиаса за его простоту и красоту.

Однако, несмотря на то, что это дает отличные результаты для небольших объемов данных, при работе с большими объемами данных производительность не такая хорошая из-за отражения.

Я выполнил тест с набором простых объектов данных, насчитывающих 100000 элементов. Сортировка по свойству целочисленного типа заняла около 1 мин. Реализация, которую я собираюсь более подробно изменить, изменила это на ~ 200 мс.

Основная идея состоит в том, чтобы использовать строго типизированное сравнение, сохраняя универсальный метод ApplySortCore. Следующее заменяет универсальный делегат сравнения вызовом конкретного компаратора, реализованного в производном классе:

Новое в SortableBindingList < T >:

protected abstract Comparison<T> GetComparer(PropertyDescriptor prop);

ApplySortCore изменится на:

protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
{
    List<T> itemsList = (List<T>)this.Items;
    if (prop.PropertyType.GetInterface("IComparable") != null)
    {
        Comparison<T> comparer = GetComparer(prop);
        itemsList.Sort(comparer);
        if (direction == ListSortDirection.Descending)
        {
            itemsList.Reverse();
        }
    }

    isSortedValue = true;
    sortPropertyValue = prop;
    sortDirectionValue = direction;
}

Теперь в производном классе нужно реализовать компараторы для каждого сортируемого свойства:

class MyBindingList:SortableBindingList<DataObject>
{
        protected override Comparison<DataObject> GetComparer(PropertyDescriptor prop)
        {
            Comparison<DataObject> comparer;
            switch (prop.Name)
            {
                case "MyIntProperty":
                    comparer = new Comparison<DataObject>(delegate(DataObject x, DataObject y)
                        {
                            if (x != null)
                                if (y != null)
                                    return (x.MyIntProperty.CompareTo(y.MyIntProperty));
                                else
                                    return 1;
                            else if (y != null)
                                return -1;
                            else
                                return 0;
                        });
                    break;

                    // Implement comparers for other sortable properties here.
            }
            return comparer;
        }
    }
}

Этот вариант требует немного больше кода, но, если производительность является проблемой, я думаю, что это стоит усилий.

Другие советы

Я погуглил и попробовал самостоятельно еще немного времени ...

Пока что в .NET нет встроенного способа. Вы должны реализовать собственный класс на основе BindingList < T > . Один из способов описан в пользовательской привязке данных, часть 2 (MSDN) , Наконец, я создаю другую реализацию метода ApplySortCore , чтобы обеспечить реализацию, которая не зависит от проекта.

protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
{
    List<T> itemsList = (List<T>)this.Items;
    if(property.PropertyType.GetInterface("IComparable") != null)
    {
        itemsList.Sort(new Comparison<T>(delegate(T x, T y)
        {
            // Compare x to y if x is not null. If x is, but y isn't, we compare y
            // to x and reverse the result. If both are null, they're equal.
            if(property.GetValue(x) != null)
                return ((IComparable)property.GetValue(x)).CompareTo(property.GetValue(y)) * (direction == ListSortDirection.Descending ? -1 : 1);
            else if(property.GetValue(y) != null)
                return ((IComparable)property.GetValue(y)).CompareTo(property.GetValue(x)) * (direction == ListSortDirection.Descending ? 1 : -1);
            else
                return 0;
        }));
    }

    isSorted = true;
    sortProperty = property;
    sortDirection = direction;
}

Используя этот, вы можете сортировать по любому члену, который реализует IComparable .

Я понимаю, что все эти ответы были хорошими в то время, когда они были написаны. Вероятно, они все еще есть. Я искал нечто похожее и нашел альтернативное решение для преобразования любого списка или коллекции в сортируемый BindingList < T > .

Вот важный фрагмент (ссылка на полный образец приведена ниже):

void Main()
{
    DataGridView dgv = new DataGridView();
    dgv.DataSource = new ObservableCollection<Person>(Person.GetAll()).ToBindingList();
}    

В этом решении используется метод расширения, доступный в Entity Framework библиотека. Поэтому, пожалуйста, примите во внимание следующее, прежде чем продолжить:

<Ол>
  • Если вы не хотите использовать Entity Framework, это нормально, это решение также не использует его. Мы просто используем метод расширения, который они разработали. Размер EntityFramework.dll составляет 5 МБ. Если в эпоху петабайт он слишком велик, извлеките метод и его зависимости из приведенной выше ссылки.
  • Если вы используете (или хотели бы использовать) Entity Framework (> = v6.0), вам не о чем беспокоиться. Просто установите пакет Entity Framework Nuget и начинайте.
  • Я загрузил LINQPad пример кода здесь .

    <Ол>
  • Загрузите образец, откройте его с помощью LINQPad и нажмите F4.
  • Вы должны увидеть EntityFramework.dll в красном. Загрузите dll из этого местоположения . Просмотрите и добавьте ссылку.
  • Нажмите "ОК". Нажмите F5.
  • Как видите, вы можете отсортировать все четыре столбца разных типов данных, щелкнув заголовки столбцов в элементе управления DataGridView.

    Те, у кого нет LINQPad, могут загрузить запрос и открыть его с помощью блокнота, чтобы увидеть полный образец.

    Вот альтернатива, которая очень чистая и прекрасно работает в моем случае. У меня уже были настроены определенные функции сравнения для использования с List.Sort (Сравнение), поэтому я просто адаптировал это из частей других примеров StackOverflow.

    class SortableBindingList<T> : BindingList<T>
    {
     public SortableBindingList(IList<T> list) : base(list) { }
    
     public void Sort() { sort(null, null); }
     public void Sort(IComparer<T> p_Comparer) { sort(p_Comparer, null); }
     public void Sort(Comparison<T> p_Comparison) { sort(null, p_Comparison); }
    
     private void sort(IComparer<T> p_Comparer, Comparison<T> p_Comparison)
     {
      if(typeof(T).GetInterface(typeof(IComparable).Name) != null)
      {
       bool originalValue = this.RaiseListChangedEvents;
       this.RaiseListChangedEvents = false;
       try
       {
        List<T> items = (List<T>)this.Items;
        if(p_Comparison != null) items.Sort(p_Comparison);
        else items.Sort(p_Comparer);
       }
       finally
       {
        this.RaiseListChangedEvents = originalValue;
       }
      }
     }
    }
    

    Вот новая реализация с использованием нескольких новых трюков.

    Базовый тип IList < T > должен реализовывать void Sort (Comparison < T >) , или вы должны передать делегат для вызова функции сортировки для вас , ( IList < T > не имеет функции void Sort (Сравнение < T >) )

    Во время статического конструктора класс будет проходить через тип T , находя все публичные экземпляры свойств, которые реализуют ICompareable или ICompareable < T > и кэширует делегатов, которые он создает для дальнейшего использования. Это делается в статическом конструкторе, потому что нам нужно делать это только один раз для каждого типа T и Dictionary < TKey, TValue > является потокобезопасным при чтениях.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    
    namespace ExampleCode
    {
        public class SortableBindingList<T> : BindingList<T>
        {
            private static readonly Dictionary<string, Comparison<T>> PropertyLookup;
            private readonly Action<IList<T>, Comparison<T>> _sortDelegate;
    
            private bool _isSorted;
            private ListSortDirection _sortDirection;
            private PropertyDescriptor _sortProperty;
    
            //A Dictionary<TKey, TValue> is thread safe on reads so we only need to make the dictionary once per type.
            static SortableBindingList()
            {
                PropertyLookup = new Dictionary<string, Comparison<T>>();
                foreach (PropertyInfo property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    Type propertyType = property.PropertyType;
                    bool usingNonGenericInterface = false;
    
                    //First check to see if it implments the generic interface.
                    Type compareableInterface = propertyType.GetInterfaces()
                        .FirstOrDefault(a => a.Name == "IComparable`1" &&
                                             a.GenericTypeArguments[0] == propertyType);
    
                    //If we did not find a generic interface then use the non-generic interface.
                    if (compareableInterface == null)
                    {
                        compareableInterface = propertyType.GetInterface("IComparable");
                        usingNonGenericInterface = true;
                    }
    
                    if (compareableInterface != null)
                    {
                        ParameterExpression x = Expression.Parameter(typeof(T), "x");
                        ParameterExpression y = Expression.Parameter(typeof(T), "y");
    
                        MemberExpression xProp = Expression.Property(x, property.Name);
                        Expression yProp = Expression.Property(y, property.Name);
    
                        MethodInfo compareToMethodInfo = compareableInterface.GetMethod("CompareTo");
    
                        //If we are not using the generic version of the interface we need to 
                        // cast to object or we will fail when using structs.
                        if (usingNonGenericInterface)
                        {
                            yProp = Expression.TypeAs(yProp, typeof(object));
                        }
    
                        MethodCallExpression call = Expression.Call(xProp, compareToMethodInfo, yProp);
    
                        Expression<Comparison<T>> lambada = Expression.Lambda<Comparison<T>>(call, x, y);
                        PropertyLookup.Add(property.Name, lambada.Compile());
                    }
                }
            }
    
            public SortableBindingList() : base(new List<T>())
            {
                _sortDelegate = (list, comparison) => ((List<T>)list).Sort(comparison);
            }
    
            public SortableBindingList(IList<T> list) : base(list)
            {
                MethodInfo sortMethod = list.GetType().GetMethod("Sort", new[] {typeof(Comparison<T>)});
                if (sortMethod == null || sortMethod.ReturnType != typeof(void))
                {
                    throw new ArgumentException(
                        "The passed in IList<T> must support a \"void Sort(Comparision<T>)\" call or you must provide one using the other constructor.",
                        "list");
                }
    
                _sortDelegate = CreateSortDelegate(list, sortMethod);
            }
    
            public SortableBindingList(IList<T> list, Action<IList<T>, Comparison<T>> sortDelegate)
                : base(list)
            {
                _sortDelegate = sortDelegate;
            }
    
            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; }
            }
    
            private static Action<IList<T>, Comparison<T>> CreateSortDelegate(IList<T> list, MethodInfo sortMethod)
            {
                ParameterExpression sourceList = Expression.Parameter(typeof(IList<T>));
                ParameterExpression comparer = Expression.Parameter(typeof(Comparison<T>));
                UnaryExpression castList = Expression.TypeAs(sourceList, list.GetType());
                MethodCallExpression call = Expression.Call(castList, sortMethod, comparer);
                Expression<Action<IList<T>, Comparison<T>>> lambada =
                    Expression.Lambda<Action<IList<T>, Comparison<T>>>(call,
                        sourceList, comparer);
                Action<IList<T>, Comparison<T>> sortDelegate = lambada.Compile();
                return sortDelegate;
            }
    
            protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
            {
                Comparison<T> comparison;
    
                if (PropertyLookup.TryGetValue(property.Name, out comparison))
                {
                    if (direction == ListSortDirection.Descending)
                    {
                        _sortDelegate(Items, (x, y) => comparison(y, x));
                    }
                    else
                    {
                        _sortDelegate(Items, comparison);
                    }
    
                    _isSorted = true;
                    _sortProperty = property;
                    _sortDirection = direction;
    
                    OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, property));
                }
            }
    
            protected override void RemoveSortCore()
            {
                _isSorted = false;
            }
        }
    }
    

    Не для пользовательских объектов. В .Net 2.0 мне пришлось проверять сортировку с помощью BindingList. В .Net 3.5 может быть что-то новое, но я еще не рассматривал это. Теперь, когда есть LINQ и опции сортировки, которые могут быть реализованы, теперь это может быть легче реализовать.

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top