Pregunta

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

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 hay un evento de clasificación o algo que pueda verse atrapado en el DataGridView (o, incluso mejor, en el 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.

¿Fue útil?

Solución

Aprecio mucho solución de Matthias por su simplicidad y belleza.

Sin embargo, aunque esto da 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.

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

La idea básica es beneficiarse de una comparación fuertemente tipificada, mientras se mantiene el método ApplySortCore genérico. 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 merece la pena.

Otros consejos

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

Hasta ahora no hay una forma integrada en .NET. Debe implementar una clase personalizada basada en BindingList < T > . Una forma se describe en Enlace de datos personalizado, Parte 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;
}

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

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

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 utiliza un método de extensión disponible en Entity Framework biblioteca. Por lo tanto, considere lo siguiente antes de continuar:

  1. Si no desea 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 del EntityFramework.dll es de 5 MB. Si es demasiado grande para usted en la era de Petabytes, siéntase libre de extraer el método y sus dependencias del enlace anterior.
  2. Si está utilizando (o le gustaría usar) Entity Framework (> = v6.0), no tiene nada de qué preocuparse. Simplemente instale el paquete Entity Framework Nuget y comience.

He subido el ejemplo del código LINQPad aquí .

  1. Descargue la muestra, ábrala con LINQPad y presione F4.
  2. Debería ver EntityFramework.dll en rojo. Descargue la dll desde esta ubicación . Busque y agregue la referencia.
  3. Haz clic en Aceptar. Pulsa 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.

Aquí hay una alternativa que es muy limpia y funciona bien en mi caso. Ya tenía configuradas funciones de comparación específicas para usar con List.Sort (Comparación), así que lo adapté 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;
   }
  }
 }
}

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

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

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

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top