Question

J'utilise une BindingList < T > dans mes Windows Forms, qui contient la liste des " IComparable < Contact > " Objets de contact. J'aimerais maintenant que l'utilisateur puisse trier toutes les colonnes affichées dans la grille.

Il existe un moyen décrit sur MSDN en ligne qui montre comment implémenter une collection personnalisée basée sur BindingList < T > qui permet le tri. Mais n’y a-t-il pas un événement de tri ou quelque chose qui pourrait être capturé dans le DataGridView (ou, encore mieux, sur le BindingSource) pour trier la collection sous-jacente à l’aide de code personnalisé?

Je n'aime pas vraiment la manière décrite par MSDN. L’autre façon, je pourrais facilement appliquer une requête LINQ à la collection.

Était-ce utile?

La solution

J'apprécie beaucoup la solution de Matthias pour sa simplicité et sa beauté.

Cependant, si cela donne d'excellents résultats pour les faibles volumes de données, les performances ne sont pas très bonnes lorsque vous travaillez avec de gros volumes de données, en raison de la réflexion.

J'ai effectué un test avec une collection d'objets de données simples, comptant 100 000 éléments. Le tri par une propriété de type entier a duré environ 1 min. L’implémentation que je vais détailler a modifié le résultat en ~ 200 ms.

L'idée de base est de bénéficier d'une comparaison fortement typée, tout en conservant la méthode ApplySortCore générique. Ce qui suit remplace le délégué de comparaison générique par un appel à un comparateur spécifique, implémenté dans une classe dérivée:

Nouveauté de SortableBindingList < T >:

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

ApplySortCore devient:

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

Maintenant, dans la classe dérivée, il faut implémenter des comparateurs pour chaque propriété triable:

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

Cette variante nécessite un peu plus de code, mais si les performances sont un problème, je pense que cela en vaut la peine.

Autres conseils

J'ai googlé et essayé par moi-même un peu plus de temps ...

Il n’existe pas de moyen intégré dans .NET jusqu’à présent. Vous devez implémenter une classe personnalisée basée sur BindingList < T > . Une solution est décrite dans la Liaison de données personnalisées, partie 2 (MSDN) . . Enfin, je produis une implémentation différente de la méthode ApplySortCore pour fournir une implémentation qui ne dépend pas du projet.

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

En utilisant celui-ci, vous pouvez trier par n'importe quel membre qui implémente IComparable .

Je comprends que toutes ces réponses étaient bonnes au moment de leur rédaction. Probablement ils sont toujours. Je recherchais quelque chose de similaire et j'ai trouvé une solution alternative pour convertir toute liste ou collection en un BindingList

.

Voici l'extrait de code important (le lien vers l'exemple complet est partagé ci-dessous):

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

Cette solution utilise une méthode d'extension disponible dans Entity Framework . bibliothèque. Veuillez donc prendre en compte les éléments suivants avant de poursuivre:

  1. Si vous ne souhaitez pas utiliser Entity Framework, c'est très bien, cette solution ne l'utilise pas non plus. Nous utilisons simplement une méthode d'extension qu'ils ont développée. La taille de EntityFramework.dll est 5 MB. Si elle est trop grosse pour vous à l'ère des pétaoctets, n'hésitez pas à extraire la méthode et ses dépendances à partir du lien ci-dessus.
  2. Si vous utilisez (ou souhaitez utiliser) Entity Framework (> = v6.0), vous n'avez rien à craindre. Installez simplement le package Entity Framework Nuget et lancez-vous.

J'ai téléchargé l'exemple de code LINQPad ici .

  1. Téléchargez l'exemple, ouvrez-le à l'aide de LINQPad et appuyez sur F4.
  2. Vous devriez voir EntityFramework.dll en rouge. Téléchargez la dll depuis cet emplacement . Parcourez et ajoutez la référence.
  3. Cliquez sur OK. Hit F5.

Comme vous pouvez le constater, vous pouvez trier les quatre colonnes de types de données différents en cliquant sur leurs en-têtes de colonne sur le contrôle DataGridView.

Ceux qui ne disposent pas de LINQPad peuvent toujours télécharger la requête et l’ouvrir avec le bloc-notes pour afficher un exemple complet.

Voici une alternative très propre et qui fonctionne très bien dans mon cas. J'avais déjà des fonctions de comparaison spécifiques configurées pour une utilisation avec List.Sort (Comparaison), donc je viens de l'adapter à partir de parties des autres exemples 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;
   }
  }
 }
}

Voici une nouvelle implémentation utilisant quelques nouvelles astuces.

Le type sous-jacent de IList < T > doit implémenter void Sort (comparaison < T >) ou vous devez passer un délégué pour appeler la fonction de tri à votre place. . ( IList < T > ne possède pas de fonction Trier par vide (comparaison < T >) )

Au cours du constructeur statique, la classe passe par le type T en recherchant toutes les propriétés instanciées publiques implémentant ICompareable ou ICompareable < T > et met en cache les délégués qu'il crée pour une utilisation ultérieure. Ceci est fait dans un constructeur statique car nous n'avons besoin de le faire qu'une fois par type de T et de Dictionnaire est protégé en threads sur les lectures.

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

Pas pour les objets personnalisés. Dans .Net 2.0, je devais lancer mon tri en utilisant BindingList. Il se peut qu’il y ait quelque chose de nouveau dans .Net 3.5 mais je n’ai pas encore étudié la question. Maintenant que LINQ existe et que les options de tri qui s’y rattachent s’il est peut-être plus facile à mettre en oeuvre, maintenant.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top