【问题标题】:DataGridView sort and e.g. BindingList<T> in .NETDataGridView 排序和例如.NET 中的 BindingList<T>
【发布时间】:2010-09-19 23:18:01
【问题描述】:

我在我的 Windows 窗体中使用 BindingList&lt;T&gt;,其中包含“IComparable&lt;Contact&gt;”联系人对象列表。现在我希望用户能够按网格中显示的任何列进行排序。

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

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

【问题讨论】:

    标签: .net winforms data-binding sorting bindinglist


    【解决方案1】:

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

    到目前为止,.NET 中还没有内置的方法。您必须实现一个基于BindingList&lt;T&gt; 的自定义类。 Custom Data Binding, Part 2 (MSDN) 中描述了一种方法。我最终生成了ApplySortCore-方法的不同实现,以提供不依赖于项目的实现。

    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的成员排序。

    【讨论】:

    • 1+ 谢谢,真的很有帮助
    • +1。为什么MS一开始不在类库中实现这十几行代码?
    • 另见这篇文章,了解如何为 SortableBindingList 模拟 List 的 Sort() 行为。 stackoverflow.com/questions/1063917/… 另请注意,建议在排序时禁用通知。参考讨论中包含如何执行此操作的示例
    • BindingList 接受带有 IList 的构造函数,它不支持排序。此外,您的 ApplySortCore 应该调用 OnListChanged。该类还应覆盖 SupportsSortingCore、IsSortedCord、SortDirectionCore、SortPropertyCore 和 RemoveSortCore 以获得完整的 SortableBindlingList
    【解决方案2】:

    我非常感谢 Matthias' solution 的简单和美丽。

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

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

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

    SortableBindingList 中的新功能:

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

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

    【讨论】:

    • 一些过去的开发人员在我正在处理的 Win Forms 项目中以相同的方式实现了这一点,我想了解原因。现在我明白了;这是更好的表现。这是一篇非常有用的帖子,因为它不仅向您展示了如何实现解决方案,而且解释了该解决方案在哪些场景中更好。 +1
    • 建议:添加上述代码正在修改的文件的链接。
    • 我建议做一个小改动,而不是在反转列表时排序两次,只需反转比较器Comparison&lt;T&gt; comparer = GetComparer(prop); if (direction == ListSortDirection.Descending) { var original = comparer; comparer = (x, y) =&gt; original(y, x); } itemsList.Sort(comparer);
    【解决方案3】:

    我了解所有这些答案在编写时都很好。可能他们仍然是。我一直在寻找类似的东西,并找到了一种替代解决方案,将 any 列表或集合转换为可排序的BindingList&lt;T&gt;

    这是重要的 sn-p(完整示例的链接在下面共享):

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

    此解决方案使用Entity Framework 库中提供的扩展方法。因此,请在继续之前考虑以下事项:

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

    我已经上传了LINQPad 代码示例here

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

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

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

    【讨论】:

    • 结果不是一个可排序的绑定列表,所以如果我得到了正确的结果,这个漂亮的解决方案只有在你的对象 (Person) 很简单并且它的所有属性都将显示在表中的情况下才有效。
    • 您不必在网格中显示所有属性。尝试单击任何列标题,它将按该列排序,因此它确实是一个可排序的绑定列表。你是对的,这个解决方案只适用于简单对象,因为它使用默认比较器,但是,我们通常只显示和排序原始类型,不是吗?
    【解决方案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;
       }
      }
     }
    }
    

    【讨论】:

      【解决方案5】:

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

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

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 感谢您的回复。使用 LINQ 进行排序变得非常容易,但是当用户希望对列表进行排序时,我发现无法触发......
        猜你喜欢
        • 1970-01-01
        • 2011-04-16
        • 2017-02-04
        • 1970-01-01
        • 1970-01-01
        • 2013-09-16
        • 2010-11-30
        • 2021-11-04
        • 1970-01-01
        相关资源
        最近更新 更多