Domanda

Sto usando un BindingList < T > nei miei Windows Form che contiene un elenco di " IComparable < Contact > " Contatto-oggetti. Ora vorrei che l'utente fosse in grado di ordinare in base a qualsiasi colonna visualizzata nella griglia.

Esiste un modo descritto su MSDN online che mostra come implementare una raccolta personalizzata basata su BindingList < T > che consente l'ordinamento. Ma non c'è un evento di ordinamento o qualcosa che potrebbe essere catturato in DataGridView (o, ancora meglio, su BindingSource) per ordinare la raccolta sottostante usando un codice personalizzato?

Non mi piace molto il modo descritto da MSDN. L'altro modo ho potuto facilmente applicare una query LINQ alla raccolta.

È stato utile?

Soluzione

Apprezzo molto la soluzione di Matthias per la sua semplicità e bellezza.

Tuttavia, sebbene ciò fornisca risultati eccellenti per volumi di dati bassi, quando si lavora con grandi volumi di dati le prestazioni non sono così buone, a causa della riflessione.

Ho eseguito un test con una raccolta di semplici oggetti dati, contando 100000 elementi. L'ordinamento per una proprietà di tipo intero ha richiesto circa 1 minuto. L'implementazione che ho intenzione di approfondire ha cambiato questo a ~ 200ms.

L'idea di base è quella di beneficiare di un confronto fortemente tipizzato, mantenendo il metodo ApplySortCore generico. Quanto segue sostituisce il delegato del confronto generico con una chiamata a un comparatore specifico, implementato in una classe derivata:

Novità in SortableBindingList < T > ;:

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

ApplySortCore cambia in:

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

Ora, nella classe derivata si devono implementare comparatori per ogni proprietà ordinabile:

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

Questa variante richiede un po 'più di codice ma, se le prestazioni sono un problema, penso che valga la pena.

Altri suggerimenti

Ho cercato su Google e ho provato da solo ancora un po '...

Finora non esiste un modo integrato in .NET. Devi implementare una classe personalizzata basata su BindingList < T > . Un modo è descritto in Rilegatura dei dati personalizzati, parte 2 (MSDN) . Finalmente produco un'implementazione diversa del metodo ApplySortCore per fornire un'implementazione che non dipende dal progetto.

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 questo, puoi ordinare per qualsiasi membro che implementa IComparable .

Comprendo che tutte queste risposte erano buone al momento in cui sono state scritte. Probabilmente lo sono ancora. Stavo cercando qualcosa di simile e ho trovato una soluzione alternativa per convertire qualsiasi elenco o collezione in BindingList ordinabile < T > .

Ecco l'importante frammento (il link al campione completo è condiviso di seguito):

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

Questa soluzione utilizza un metodo di estensione disponibile in Entity Framework biblioteca. Pertanto, ti preghiamo di considerare quanto segue prima di procedere oltre:

  1. Se non si desidera utilizzare Entity Framework, va bene, questa soluzione non lo sta nemmeno usando. Stiamo solo usando un metodo di estensione che hanno sviluppato. La dimensione di EntityFramework.dll è 5 MB. Se è troppo grande per te nell'era di Petabytes, sentiti libero di estrarre il metodo e le sue dipendenze dal link sopra.
  2. Se stai utilizzando (o desideri utilizzare) Entity Framework (> = v6.0), non hai nulla di cui preoccuparti. Installa il pacchetto Entity Framework Nuget e inizia.

Ho caricato il LINQPad esempio di codice qui .

  1. Scarica l'esempio, aprilo usando LINQPad e premi F4.
  2. Dovresti vedere EntityFramework.dll in rosso. Scarica la dll da questa posizione . Sfoglia e aggiungi il riferimento.
  3. Fai clic su OK. Premi F5.

Come puoi vedere, puoi ordinare su tutte e quattro le colonne di diversi tipi di dati facendo clic sulle loro intestazioni di colonna sul controllo DataGridView.

Coloro che non hanno LINQPad, possono comunque scaricare la query e aprirla con il blocco note, per vedere l'intero esempio.

Ecco un'alternativa che è molto pulita e funziona bene nel mio caso. Avevo già impostato funzioni di confronto specifiche da utilizzare con List.Sort (Comparison), quindi l'ho adattato da parti degli altri esempi 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;
   }
  }
 }
}

Ecco una nuova implementazione usando alcuni nuovi trucchi.

Il tipo sottostante di IList < T > deve implementare void Sort (Confronto < T >) oppure devi passare un delegato per chiamare la funzione di ordinamento per te . ( IList < T > non ha un void Sort (confronto < T >) )

Durante il costruttore statico la classe passerà attraverso il tipo T trovando tutte le proprietà di proprietà pubblica che implementano ICompareable o ICompareable < T > e memorizza nella cache i delegati che crea per un uso successivo. Questo viene fatto in un costruttore statico perché dobbiamo farlo una sola volta per tipo di T e Dictionary < TKey, TValue > è sicuro per le letture.

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

Non per oggetti personalizzati. In .Net 2.0, ho dovuto completare il mio ordinamento usando BindingList. Potrebbe esserci qualcosa di nuovo in .Net 3.5 ma non l'ho ancora esaminato. Ora che c'è LINQ e le opzioni di ordinamento che ne derivano se questo ora potrebbe essere più facile da implementare.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top