2008-10-30 18 views
11

Estoy usando un BindingList<T> en mi Windows Forms que contiene una lista de "IComparable<Contact>" Contact-objects. Ahora me gustaría que el usuario pueda ordenar por cualquier columna que se muestre en la cuadrícula.clasificación de DataGridView y p. BindingList <T> en .NET

Hay una forma descrita en MSDN en línea que muestra cómo implementar una colección personalizada basada en BindingList<T> que permite ordenar. 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.

Respuesta

18

Agradezco mucho Matthias' solution por su simplicidad y belleza.

Sin embargo, si bien 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, manteniendo el método ApplySortCore genérico. Lo siguiente sustituye el delegado genérico comparación con una llamada a un comparador específica, 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.

+1

Algún desarrollador anterior implementó esto de la misma manera en un proyecto de Win Forms en el que estoy trabajando, y quería entender por qué. Ahora sé; es un mejor rendimiento. Esta fue una publicación increíblemente útil, porque no solo muestra cómo implementar una solución, sino que explica en qué escenarios esta solución es mejor. +1 – Jim

+1

Sugerencia: agregue un enlace al archivo que el código anterior está modificando. –

+1

Recomiendo un pequeño cambio, en lugar de ordenar dos veces cuando invierte la lista simplemente invierta el comparador 'Comparación comparer = GetComparer (prop); if (direction == ListSortDirection.Descending) {var original = comparer; comparer = (x, y) => original (y, x); } itemsList.Sort (comparer); ' –

0

No es 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.

+0

Gracias por su respuesta. La ordenación se ha vuelto extremadamente fácil con LINQ, pero no he encontrado la forma de activarse cuando el usuario deseaba ordenar la lista ... –

24

Busqué en Google y juzgados por mi cuenta algo más de tiempo ...

No hay manera integrada en .NET hasta ahora. Tienes que implementar una clase personalizada basada en BindingList<T>. Una forma se describe en Custom Data Binding, Part 2 (MSDN). Finalmente, produzco 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; 
} 

El uso de éste, se puede ordenar por cualquier miembro que implementa IComparable.

+0

1+ Gracias es realmente útil –

+7

+1. ¿Por qué MS no implementó esta docena de líneas de código en la biblioteca de clases en primer lugar? –

+0

Consulte también esta publicación para obtener información sobre cómo emular el comportamiento de Sort() de List para SortableBindingList.

4

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

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 clasificación por usted. (IList<T> no tiene una función void Sort(Comparison<T>))

Durante el constructor estático de la clase pasará por el tipo T encontrar todas las propiedades de instancia pública que implementa 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; 
     } 
    } 
} 
+0

Esto debe marcarse como la respuesta correcta. –

6

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 ordenar BindingList<T>.

Aquí es importante el fragmento (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 utiliza un método de extensión disponible en Entity Framework biblioteca. Considere lo siguiente antes de continuar:

  1. Si no desea utilizar Entity Framework, está bien, esta solución tampoco lo está utilizando. 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 Nuget Entity Framework y comience.

He cargado la muestra del código LINQPadhere.

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

Como puede ver, puede ordenar en 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.

+0

El resultado no es una lista vinculable ordenable, así que si la obtuve correctamente, esta hermosa solución funciona bien solo si su objeto ('Persona') es simple y todas sus propiedades se mostrarán en la tabla. – gneri

+0

No tiene que mostrar todas las propiedades en la grilla. Intenta hacer clic en cualquier encabezado de columna, clasificará en esa columna, por lo que es una lista de enlaces ordenables. Y tiene razón, esta solución es solo para objetos simples, ya que utiliza comparadores predeterminados, sin embargo, generalmente solo mostramos y ordenamos tipos primitivos, ¿no? –

Cuestiones relacionadas