Pergunta

Eu estou usando um BindingList<T> em meus Windows Forms que contém uma lista de "IComparable<Contact>" contato-objetos. Agora eu gostaria que o usuário seja capaz de classificar por qualquer coluna exibidos na grade.

Há uma maneira descrita no MSDN online, que mostra como implementar uma coleção personalizada com base em BindingList<T> que permite a classificação. Mas não há uma Sort-evento ou algo que poderia ser pego no DataGridView (ou, ainda melhor, na BindingSource) para classificar a coleção subjacente usando código personalizado?

Eu não gosto muito da maneira descrita por MSDN. A outra maneira que eu poderia facilmente aplicar uma consulta LINQ para a coleção.

Foi útil?

Solução

Eu agarraria apreciar Matthias' solução por sua simplicidade e beleza.

No entanto, enquanto esta dá excelentes resultados para volumes de dados baixos, quando se trabalha com grandes volumes de dados do desempenho não é tão bom, devido à reflexão.

Eu corri um teste com uma coleção de objetos de dados simples, contando 100000 elementos. Triagem por uma propriedade de tipo inteiro levou cerca de 1 min. A implementação vou mais detalhes mudou este para ~ 200ms.

A idéia básica é a de beneficiar comparação fortemente digitado, mantendo o método ApplySortCore genérico. O texto seguinte substitui o delegado comparação genérica com uma chamada para um comparador específico, implementado em uma classe derivada:

Novo em SortableBindingList :

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

ApplySortCore muda para:

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

Agora, na classe derivada um tem que implementar comparadores para cada propriedade sortable:

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 requer um pouco mais de código, mas, se o desempenho for um problema, eu acho que worths o esforço.

Outras dicas

Eu pesquisei e tentei no meu próprio mais algum tempo ...

Não há built-in maneira em .NET até agora. Você tem que implementar uma classe personalizada com base em BindingList<T>. Uma forma é descrita em dados personalizados Binding, Parte 2 (MSDN) . Eu finalmente produz uma implementação diferente do método-ApplySortCore para fornecer uma implementação que não é dependente do projeto.

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 este, você pode classificar por qualquer membro que implementa IComparable.

Eu entendo todas essas respostas eram boas no momento em que foram escritos. Provavelmente eles ainda são. Eu estava procurando por algo semelhante e encontrou uma solução alternativa para converter qualquer lista ou coleção para BindingList<T> classificáveis.

Aqui está o trecho importante (link para o total da amostra é compartilhada abaixo):

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

Esta solução usa um método de extensão disponível em href="https://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/ObservableCollectionExtensions.cs" rel="noreferrer"> Entity Framework biblioteca. Então, por favor, considere o seguinte antes de prosseguir:

  1. Se você não quiser usar o Entity Framework, sua multa, esta solução é não usá-lo também. Estamos apenas usando um método de extensão que têm desenvolvido. O tamanho do EntityFramework.dll é de 5 MB. Se ele é muito grande para você na época da Petabytes, sinta-se livre para extrair o método e as suas dependências a partir do link acima.
  2. Se você estiver usando (ou gostaria de usar) Entity Framework (> = v6.0), você não tem nada para se preocupar. Basta instalar o Entity Framework pacote Nuget e ir em frente.

Eu tenho carregado o href="http://www.linqpad.net/" rel="noreferrer"> LINQPad amostra aqui .

  1. Baixe o exemplo, abri-lo usando LINQPad e bateu F4.
  2. Você deverá ver EntityFramework.dll em vermelho. Baixe o dll a partir desta localização . Procurar e adicionar a referência.
  3. Clique em OK. Hit F5.

Como você pode ver, você pode classificar em todas as quatro colunas de diferentes tipos de dados, clicando em seus cabeçalhos de coluna no controle DataGridView.

Aqueles que não têm LINQPad, ainda pode baixar a consulta e abri-lo com o bloco de notas, para ver a amostra completa.

Aqui é uma alternativa que é muito limpo e funciona muito bem no meu caso. Já tinha funções de comparação específicos criados para uso com list.sort (Comparação), de modo que apenas esta adaptado a partir de partes dos outros exemplos 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;
   }
  }
 }
}

Aqui está um novo implmentation usando alguns truques novos.

O tipo subjacente da IList<T> deve implementar void Sort(Comparison<T>) ou você deve passar em um delegado para chamar a função de classificação para você. (IList<T> não tem uma função void Sort(Comparison<T>))

Durante o construtor estático da classe irá percorrer o tipo T encontrar todas as propriedades instanced públicas que implementos ICompareable ou ICompareable<T> e armazena em cache os delegados que ela cria para uso posterior. Isso é feito em um construtor estático, porque só precisa fazer isso uma vez por tipo de T e Dictionary<TKey,TValue> é thread-safe em lê.

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

Não para objetos personalizados. Na Net 2.0, eu tinha que fazer a minha na classificação usando BindingList. Pode haver algo de novo na Net 3.5, mas eu não olhei para isso ainda. Agora que há LINQ e as opções de classificação que vêm com se isso agora pode ser mais fácil de implementar.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top