Question Tri de DataGridView et par ex. BindingList dans .NET


J'utilise un BindingList<T> dans mes Windows Forms qui contient une liste de "IComparable<Contact>"Objets de contact. Maintenant, j'aimerais que l'utilisateur soit en mesure de trier par n'importe quelle colonne affichée 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> ce qui permet de trier. Mais n'y a-t-il pas un événement de tri ou quelque chose qui pourrait être intercepté dans le DataGridView (ou, plus intéressant encore, sur le BindingSource) pour trier la collection sous-jacente à l'aide d'un code personnalisé?

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


11
2017-10-30 10:46


origine


Réponses:


J'apprécie énormément La solution de Matthias pour sa simplicité et sa beauté.

Cependant, même si cela donne d'excellents résultats pour les volumes de données faibles, lorsque vous travaillez avec de gros volumes de données, les performances ne sont pas très bonnes 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 pris environ 1 min. La mise en œuvre que je vais plus en détail a changé à environ 200ms.

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 change pour:

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 posent problème, je pense que cela en vaut la peine.


18
2017-07-24 14:41



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

Il n'y a pas encore de manière intégrée dans .NET. Vous devez implémenter une classe personnalisée basée sur BindingList<T>. Une voie est décrite dans Liaison de données personnalisées, partie 2 (MSDN). Je produis enfin une implémentation différente du ApplySortCore-Méthode 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.


24
2017-11-11 16:08



Je comprends que toutes ces réponses étaient bonnes au moment où elles ont été écrites. Ils le sont probablement encore. Je cherchais quelque chose de similaire et j'ai trouvé une solution alternative pour convertir tout liste ou collection à trier BindingList<T>.

Voici l'extrait de code important (le lien vers l'échantillon 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 Cadre d'entité bibliothèque. Alors, veuillez considérer les points suivants avant de poursuivre:

  1. Si vous ne souhaitez pas utiliser Entity Framework, sa 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 de 5 Mo. Si c'est trop gros 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 Cadre d'entitéPack Nuget et lancez-vous.

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

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

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

Ceux qui n’ont pas LINQPad peuvent toujours télécharger la requête et l’ouvrir avec le bloc-notes pour voir l’échantillon complet.


6
2018-02-14 22:20



Voici une alternative qui est très propre et fonctionne très bien dans mon cas. J'avais déjà des fonctions de comparaison spécifiques configurées pour être utilisées avec List.Sort (Comparison). Je les ai donc adaptées à partir de parties des autres exemples 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;
   }
  }
 }
}

4
2017-09-10 19:41



Voici une nouvelle implémentation utilisant quelques nouvelles astuces.

Le type sous-jacent du IList<T> doit mettre en œuvre void Sort(Comparison<T>) ou vous devez passer un délégué pour appeler la fonction de tri pour vous. (IList<T> n'a pas de void Sort(Comparison<T>) fonction)

Pendant le constructeur statique, la classe passera par le type T trouver toutes les propriétés publiques instanciées qui implémentent 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 il suffit de le faire une fois par type de T et Dictionary<TKey,TValue> est thread sûr 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;
        }
    }
}

3
2017-12-05 17:08



Pas pour les objets personnalisés. Dans .Net 2.0, je devais lancer mon tri en utilisant BindingList. Il y a peut-être quelque chose de nouveau dans .Net 3.5 mais je ne l'ai pas encore examiné. Maintenant qu'il y a LINQ et les options de tri qui viennent avec si cela peut maintenant être plus facile à implémenter.


0
2017-10-30 11:06