Я использую BindingList<T> в своих формах Windows Forms, который содержит список контактных объектов «IComparable<Contact>». Теперь я бы хотел, чтобы пользователь мог выполнять сортировку по любому столбцу, отображаемому в сетке.
В сети MSDN описан способ, показывающий, как реализовать настраиваемую коллекцию на основе BindingList<T>, которая позволяет выполнять сортировку. Но разве нет события Sort или чего-то еще, что можно было бы перехватить в DataGridView (или, что еще лучше, в BindingSource), чтобы отсортировать базовую коллекцию с использованием настраиваемого кода?
Мне не очень нравится способ, описанный в MSDN. С другой стороны, я мог бы легко применить LINQ-запрос к коллекции.





Не для нестандартных объектов. В .Net 2.0 мне пришлось откатить сортировку с помощью BindingList. В .Net 3.5 может быть что-то новое, но я еще не изучал это. Теперь, когда есть LINQ и параметры сортировки, которые могут быть реализованы, это может быть проще.
Я погуглил и попробовал еще немного ...
В .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+ Спасибо, это действительно помогло
+1. Почему MS просто не реализовала эту дюжину строк кода в библиотеке классов?
Также см. Этот пост для получения информации о том, как имитировать поведение Sort () List <T> для SortableBindingList. <stackoverflow.com/questions/1063917/… Также обратите внимание, что было предложено отключить уведомления при сортировке. Пример того, как это сделать, включен в указанное обсуждение.
BindingList <T> принимает конструктор с IList, который не поддерживает сортировку. Кроме того, ваш ApplySortCore должен вызывать OnListChanged. Класс также должен переопределить SupportsSortingCore, IsSortedCord, SortDirectionCore, SortPropertyCore и RemoveSortCore для полного SortableBindlingList.
Я высоко ценю Решение Матиаса за его простоту и красоту.
Однако, хотя это дает отличные результаты для небольших объемов данных, при работе с большими объемами данных производительность не так хороша из-за отражения.
Я провел тест с набором простых объектов данных, насчитывающим 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
Предложение: добавьте ссылку на файл, который изменяет приведенный выше код.
Я рекомендую одно небольшое изменение, вместо того, чтобы сортировать дважды, когда вы меняете список, просто инвертируйте компаратор Comparison<T> comparer = GetComparer(prop); if (direction == ListSortDirection.Descending) { var original = comparer; comparer = (x, y) => original(y, x); } itemsList.Sort(comparer);
Вот альтернатива, которая очень чиста и отлично работает в моем случае. У меня уже были определенные функции сравнения, настроенные для использования с 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. Поэтому, пожалуйста, примите во внимание следующее, прежде чем продолжить:
Я загрузил образец кода LINQPadздесь.
Как видите, вы можете выполнить сортировку по всем четырем столбцам с разными типами данных, щелкнув их заголовки столбцов в элементе управления DataGridView.
Те, у кого нет LINQPad, могут загрузить запрос и открыть его с помощью блокнота, чтобы увидеть полный образец.
В результате получается не сортируемый список привязок, поэтому, если я правильно его понял, это прекрасное решение будет работать хорошо, только если ваш объект (Person) прост и все его свойства будут отображаться в таблице.
Необязательно отображать все свойства в сетке. Попробуйте щелкнуть заголовок любого столбца, он будет отсортирован по этому столбцу, так что это действительно сортируемый список привязки. И вы правы, это решение предназначено только для простых объектов, поскольку оно использует компараторы по умолчанию, однако мы обычно отображаем и сортируем только примитивные типы, не так ли?
Спасибо за ответ. Сортировка с LINQ стала чрезвычайно простой, но я не нашел способа срабатывать, когда пользователь хотел отсортировать список ...