.net winforms data-binding sorting bindinglist

Clasificación de DataGridView y, por ejemplo, BindingList<T> en.NET



winforms data-binding (6)

Estoy usando BindingList<T> en mis formularios de Windows que contiene una lista de objetos de IComparable<Contact> " IComparable<Contact> ". Ahora me gustaría que el usuario pueda ordenar por cualquier columna que se muestre en la grilla.

Hay una forma descrita en MSDN en línea que muestra cómo implementar una colección personalizada basada en BindingList<T> que permite la clasificación. Pero, ¿no existe un Sort-event o algo que pueda capturarse en DataGridView (o, incluso mejor, en BindingSource) para ordenar la colección subyacente utilizando un código personalizado?

Realmente no me gusta la forma descrita por MSDN. De otra manera, podría aplicar fácilmente una consulta LINQ a la colección.


No para objetos personalizados En .Net 2.0, tuve que pasar mi clasificación utilizando BindingList. Puede haber algo nuevo en .Net 3.5 pero aún no lo he investigado. Ahora que hay LINQ y las opciones de clasificación que vienen si esto ahora puede ser más fácil de implementar.


Aquí hay una alternativa que está muy limpia y funciona bien en mi caso. Ya tenía funciones de comparación específicas configuradas para usar con List.Sort (Comparación) así que acabo de adaptar esto de partes de los otros ejemplos de .

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; } } } }


Aquí hay una nueva implementación usando algunos trucos nuevos.

El tipo subyacente de IList<T> debe implementar void Sort(Comparison<T>) o debe pasar un delegado para llamar a la función de ordenación por usted. ( IList<T> no tiene una función void Sort(Comparison<T>) )

Durante el constructor estático, la clase pasará por el tipo T encontrar todas las propiedades públicas instanciadas que implementen ICompareable o ICompareable<T> y almacena en caché los delegados que crea para su uso posterior. Esto se hace en un constructor estático porque solo necesitamos hacerlo una vez por tipo de T y Dictionary<TKey,TValue> es seguro para hilos en las lecturas.

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; } } }


Entiendo que todas estas respuestas fueron buenas en el momento en que fueron escritas. Probablemente todavía lo sean. Estaba buscando algo similar y encontré una solución alternativa para convertir cualquier lista o colección para BindingList<T> .

Aquí está el fragmento importante (el enlace a la muestra completa se comparte a continuación):

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

Esta solución usa un método de extensión disponible en la biblioteca de Entity Framework . Por favor, considere lo siguiente antes de continuar:

  1. Si no quiere usar Entity Framework, está bien, esta solución tampoco lo está usando. Solo estamos usando un método de extensión que han desarrollado. El tamaño de EntityFramework.dll es de 5 MB. Si es demasiado grande para ti en la era de Petabytes, puedes extraer el método y sus dependencias del enlace de arriba.
  2. Si está utilizando (o le gustaría usar) Entity Framework (> = v6.0), no tiene de qué preocuparse. Simplemente instale el paquete Entity Framework Nuget y comience.

He subido la muestra del código LINQPad aquí .

  1. Descargue la muestra, ábrala usando LINQPad y presione F4.
  2. Debería ver EntityFramework.dll en rojo. Descarga el dll desde esta ubicación . Busque y agregue la referencia.
  3. Haga clic en Aceptar. Presione F5.

Como puede ver, puede ordenar las cuatro columnas de diferentes tipos de datos haciendo clic en los encabezados de sus columnas en el control DataGridView.

Aquellos que no tienen LINQPad, aún pueden descargar la consulta y abrirla con el bloc de notas, para ver la muestra completa.


Busqué en Google y probé por mi cuenta un poco más de tiempo ...

No hay una forma incorporada en .NET hasta el momento. BindingList<T> implementar una clase personalizada basada en BindingList<T> . Una forma se describe en Custom Data Binding, Part 2 (MSDN) . Finalmente, ApplySortCore una implementación diferente del método ApplySortCore para proporcionar una implementación que no depende del proyecto.

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; }

Usando este, puedes ordenar por cualquier miembro que implemente IComparable .


Agradezco enormemente la solución de Matthias por su simplicidad y belleza.

Sin embargo, aunque esto ofrece excelentes resultados para volúmenes de datos bajos, cuando se trabaja con grandes volúmenes de datos el rendimiento no es tan bueno debido a la reflexión.

Ejecuté una prueba con una colección de objetos de datos simples, contando 100000 elementos. Ordenar por una propiedad de tipo entero tomó alrededor de 1 min. La implementación que voy a detallar más cambió esto a ~ 200ms.

La idea básica es aprovechar la comparación fuertemente tipada, mientras se mantiene genérico el método ApplySortCore. Lo siguiente reemplaza al delegado de comparación genérico con una llamada a un comparador específico, implementado en una clase derivada:

Nuevo en SortableBindingList <T>:

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

ApplySortCore cambia a:

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; }

Ahora, en la clase derivada, uno tiene que implementar comparadores para cada propiedad clasificable:

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; } } }

Esta variante requiere un poco más de código pero, si el rendimiento es un problema, creo que vale la pena el esfuerzo.