12

BindingList<T>在我的 Windows 窗体中使用了一个包含“ IComparable<Contact>”联系人对象的列表。现在我希望用户能够按网格中显示的任何列进行排序。

MSDN online 上描述了一种方法,它显示了如何实现基于BindingList<T>允许排序的自定义集合。但是,是不是有一个排序事件或可以在 DataGridView(或者更好的是,在 BindingSource 上)中捕获的东西来使用自定义代码对基础集合进行排序?

我不太喜欢 MSDN 描述的方式。另一种方法是我可以轻松地将 LINQ 查询应用于集合。

4

6 回答 6

28

我用谷歌搜索并自己尝试了一段时间......

到目前为止,.NET 中还没有内置的方法。您必须实现一个基于BindingList<T>. 自定义数据绑定,第 2 部分 (MSDN)中描述了一种方法。我最终生成了ApplySortCore-method 的不同实现,以提供不依赖于项目的实现。

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

使用这个,您可以按实现的任何成员进行排序IComparable

于 2008-11-11T16:08:27.910 回答
22

我非常欣赏Matthias 的简单和美观的解决方案。

然而,虽然这为低数据量提供了出色的结果,但在处理大数据量时,由于反射,性能并不是那么好。

我用一组简单的数据对象进行了测试,计算了 100000 个元素。按整数类型属性排序大约需要 1 分钟。我将进一步详细介绍的实现将其更改为~200ms。

基本思想是有利于强类型比较,同时保持 ApplySortCore 方法的通用性。下面将通用比较委托替换为对特定比较器的调用,在派生类中实现:

SortableBindingList<T> 中的新功能:

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

ApplySortCore 更改为:

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

现在,在派生类中,必须为每个可排序属性实现比较器:

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

这个变体需要更多的代码,但如果性能是一个问题,我认为值得付出努力。

于 2009-07-24T14:41:52.260 回答
7

我知道所有这些答案在编写时都很好。可能他们仍然是。我正在寻找类似的东西,并找到了将任何列表或集合转换为可排序的替代解决方案BindingList<T>

这是重要的片段(完整示例的链接在下面共享):

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

此解决方案使用实体框架库中可用的扩展方法。因此,在继续之前,请考虑以下事项:

  1. 如果您不想使用实体框架,那很好,这个解决方案也没有使用它。我们只是使用他们开发的扩展方法。EntityFramework.dll 的大小为 5 MB。如果它在 PB 时代对您来说太大了,请随时从上面的链接中提取该方法及其依赖项。
  2. 如果您正在使用(或想使用)Entity Framework (>=v6.0),您无需担心。只需安装Entity Framework Nuget 包并开始。

我在这里上传了LINQPad代码示例。

  1. 下载示例,使用 LINQPad 打开它并按 F4。
  2. 您应该看到 EntityFramework.dll 为红色。从此位置下载 dll 。浏览并添加参考。
  3. 单击确定。按 F5。

如您所见,您可以通过在 DataGridView 控件上单击它们的列标题对不同数据类型的所有四列进行排序。

那些没有 LINQPad 的人,仍然可以下载查询并用记事本打开它,以查看完整的示例。

于 2015-02-14T22:20:35.900 回答
4

这是一个非常干净的替代方案,在我的情况下工作得很好。我已经设置了与 List.Sort(Comparison) 一起使用的特定比较函数,所以我只是从其他 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;
   }
  }
 }
}
于 2010-09-10T19:41:52.020 回答
4

这是一个使用一些新技巧的新实现。

IList<T>必须实现的基础类型,void Sort(Comparison<T>)或者您必须传入一个委托来为您调用排序函数。(IList<T>没有void Sort(Comparison<T>)功能)

在静态构造函数期间,该类将通过类型T查找所有公共实例化属性,这些属性实现ICompareableICompareable<T>缓存它创建的委托以供以后使用。这是在静态构造函数中完成的,因为我们只需要对每种类型执行一次,T并且Dictionary<TKey,TValue>在读取时是线程安全的。

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;
        }
    }
}
于 2013-12-05T17:08:04.737 回答
0

不适用于自定义对象。在 .Net 2.0 中,我不得不使用 BindingList 进行排序。.Net 3.5 中可能有一些新的东西,但我还没有研究过。现在有了 LINQ 和附带的排序选项,如果现在可能更容易实现的话。

于 2008-10-30T11:06:36.313 回答