.NET 中的 DataGridView 排序和例如 BindingList<T>

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/249779/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 10:27:43  来源:igfitidea点击:

DataGridView sort and e.g. BindingList<T> in .NET

.netwinformsdata-bindingsortingbindinglist

提问by Matthias Meid

I'm using a BindingList<T>in my Windows Forms that contains a list of "IComparable<Contact>" Contact-objects. Now I'd like the user to be able to sort by any column displayed in the grid.

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

There is a way described on MSDN online which shows how to implement a custom collection based on BindingList<T>which allows sorting. But isn't there a Sort-event or something that could be caught in the DataGridView (or, even nicer, on the BindingSource) to sort the underlying collection using custom code?

MSDN在线上描述了一种方法,它展示了如何实现基于BindingList<T>允许排序的自定义集合。但是,是否没有 Sort 事件或可以在 DataGridView(或者甚至更好,在 BindingSource 上)捕获的东西以使用自定义代码对底层集合进行排序?

I don't really like the way described by MSDN. The other way I could easily apply a LINQ query to the collection.

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

回答by Matthias Meid

I googled and tried on my own some more time...

我在谷歌上搜索并尝试了更多时间......

There is no built-in way in .NET so far. You have to implement a custom class based on BindingList<T>. One way is described in Custom Data Binding, Part 2 (MSDN). I finally produces a different implementation of the ApplySortCore-method to provide an implementation which is not project-dependent.

到目前为止,.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;
}

Using this one, you can sort by any member that implements IComparable.

使用这个,您可以按任何实现IComparable.

回答by Sorin Comanescu

I higly appreciate Matthias' solutionfor its simplicity and beauty.

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

However, while this gives excellent results for low data volumes, when working with large data volumes the performance is not so good, due to reflection.

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

I ran a test with a collection of simple data objects, counting 100000 elements. Sorting by an integer type property took around 1 min. The implementation I'm going to further detail changed this to ~200ms.

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

The basic idea is to benefit strongly typed comparison, while keeping the ApplySortCore method generic. The following replaces the generic comparison delegate with a call to a specific comparer, implemented in a derived class:

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

New in SortableBindingList<T>:

SortableBindingList<T> 中的新内容:

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

ApplySortCore changes to:

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

Now, in the derived class one have to implement comparers for each sortable property:

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

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

This variant requires a little bit more code but, if performance is an issue, I think it worths the effort.

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

回答by Ravi M Patel

I understand all these answers were good at the time they were written. Probably they still are. I was looking for something similar and found an alternative solution to convert anylist or collection to sortable BindingList<T>.

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

Here is the important snippet (link to the full sample is shared below):

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

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

This solution uses an extension method available in Entity Frameworklibrary. So please consider the following before you proceed further:

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

  1. If you don't want to use Entity Framework, its fine, this solution is not using it either. We are just using an extension method they have developed. The size of the EntityFramework.dll is 5 MB. If it's too big for you in the era of Petabytes, feel free to extract the method and its dependencies from the above link.
  2. If you are using (or would like to use) Entity Framework (>=v6.0), you've nothing to worry about. Just install the Entity FrameworkNuget package and get going.
  1. 如果您不想使用实体框架,那很好,这个解决方案也不使用它。我们只是使用他们开发的扩展方法。EntityFramework.dll 的大小为 5 MB。如果在 PB 时代对你来说太大了,可以随意从上面的链接中提取方法及其依赖项。
  2. 如果您正在使用(或想使用)实体框架 (>=v6.0),则无需担心。只需安装Entity FrameworkNuget 包并开始。

I have uploaded the LINQPadcode sample here.

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

  1. Download the sample, open it using LINQPad and hit F4.
  2. You should see EntityFramework.dll in red. Download the dll from this location. Browse and add the reference.
  3. Click OK. Hit F5.
  1. 下载示例,使用 LINQPad 打开它并按 F4。
  2. 您应该会看到红色的 EntityFramework.dll。从这个位置下载 dll 。浏览并添加参考。
  3. 单击确定。按F5。

As you can see, you can sort on all four columns of different data types by clicking their column headers on the DataGridView control.

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

Those who don't have LINQPad, can still download the query and open it with notepad, to see the full sample.

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

回答by Dan Koster

Here is an alternative that is very clean and works just fine in my case. I already had specific comparison functions set up for use with List.Sort(Comparison) so I just adapted this from parts of the other StackOverflow examples.

这是一个非常干净的替代方案,在我的情况下效果很好。我已经设置了与 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;
   }
  }
 }
}

回答by Scott Chamberlain

Here is a new implmentation using a few new tricks.

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

The underlying type of the IList<T>must implement void Sort(Comparison<T>)or you must pass in a delegate to call the sort function for you. (IList<T>does not have a void Sort(Comparison<T>)function)

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

During the static constructor the class will go through the type Tfinding all public instanced properties that implements ICompareableor ICompareable<T>and caches the delegates it creates for later use. This is done in a static constructor because we only need to do it once per type of Tand Dictionary<TKey,TValue>is thread safe on reads.

在静态构造函数期间,类将通过类型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;
        }
    }
}

回答by Darren C

Not for custom objects. In .Net 2.0, I had to roll my on sorting using BindingList. There may be something new in .Net 3.5 but I have not looked into that yet. Now that there is LINQ and the sorting options that come with if this now may be easier to implement.

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