DataGridView sort и, например, BindingList <T> в .NET

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

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
0
27 217
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

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

Спасибо за ответ. Сортировка с LINQ стала чрезвычайно простой, но я не нашел способа срабатывать, когда пользователь хотел отсортировать список ...

Matthias Meid 30.10.2008 14:13

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

В .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.

1+ Спасибо, это действительно помогло

Abdullah BaMusa 14.04.2009 12:53

+1. Почему MS просто не реализовала эту дюжину строк кода в библиотеке классов?

Not Sure 26.06.2009 01:55

Также см. Этот пост для получения информации о том, как имитировать поведение Sort () List <T> для SortableBindingList. <stackoverflow.com/questions/1063917/… Также обратите внимание, что было предложено отключить уведомления при сортировке. Пример того, как это сделать, включен в указанное обсуждение.

Paul Prewett 30.06.2009 19:45

BindingList <T> принимает конструктор с IList, который не поддерживает сортировку. Кроме того, ваш ApplySortCore должен вызывать OnListChanged. Класс также должен переопределить SupportsSortingCore, IsSortedCord, SortDirectionCore, SortPropertyCore и RemoveSortCore для полного SortableBindlingList.

Harald Coppoolse 02.01.2017 13:21
Ответ принят как подходящий

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

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

Я провел тест с набором простых объектов данных, насчитывающим 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;
        }
    }
}

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

Некоторые прошлые разработчики реализовали это таким же образом в проекте Win Forms, над которым я работаю, и я хотел понять, почему. Теперь я знаю; это лучшая производительность. Это был невероятно полезный пост, потому что он не только показывает вам, как реализовать решение, но и объясняет, в каких сценариях это решение лучше. +1

Jim 26.09.2012 19:31

Предложение: добавьте ссылку на файл, который изменяет приведенный выше код.

jm. 16.10.2013 21:24

Я рекомендую одно небольшое изменение, вместо того, чтобы сортировать дважды, когда вы меняете список, просто инвертируйте компаратор Comparison<T> comparer = GetComparer(prop); if (direction == ListSortDirection.Descending) { var original = comparer; comparer = (x, y) => original(y, x); } itemsList.Sort(comparer);

Scott Chamberlain 05.12.2013 02:25

Вот альтернатива, которая очень чиста и отлично работает в моем случае. У меня уже были определенные функции сравнения, настроенные для использования с List.Sort (Comparison), поэтому я просто адаптировал их из частей других примеров 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(Comparison<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;
        }
    }
}

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

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

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

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

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

Я загрузил образец кода LINQPadздесь.

  1. Загрузите образец, откройте его с помощью LINQPad и нажмите F4.
  2. Вы должны увидеть EntityFramework.dll красным цветом. Загрузите dll с этого расположение. Просмотрите и добавьте ссылку.
  3. Щелкните ОК. Нажмите F5.

Как видите, вы можете выполнить сортировку по всем четырем столбцам с разными типами данных, щелкнув их заголовки столбцов в элементе управления DataGridView.

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

В результате получается не сортируемый список привязок, поэтому, если я правильно его понял, это прекрасное решение будет работать хорошо, только если ваш объект (Person) прост и все его свойства будут отображаться в таблице.

gneri 12.11.2015 06:48

Необязательно отображать все свойства в сетке. Попробуйте щелкнуть заголовок любого столбца, он будет отсортирован по этому столбцу, так что это действительно сортируемый список привязки. И вы правы, это решение предназначено только для простых объектов, поскольку оно использует компараторы по умолчанию, однако мы обычно отображаем и сортируем только примитивные типы, не так ли?

Ravi M Patel 12.11.2015 08:13

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