65.9K
CodeProject 正在变化。 阅读更多。
Home

快速实现可排序的 BindingList

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (64投票s)

2008 年 12 月 2 日

CPOL

2分钟阅读

viewsIcon

300985

downloadIcon

4622

一个自定义的 BindingList 实现,为类型 T 的每个属性提供排序功能。

SortableBindingList

引言

实现父子层次结构(例如,一个 Sale 对象及其相关的 SaleDetails)是在建模业务领域实体时遇到的最常见场景之一。 如果您使用类来实现业务对象,则通常会将子对象集合存储在 List<T> 中。 但是,如果您需要基于 .NET 框架的数据绑定支持构建丰富的用户界面,则 List<T> 将会显得不足。

典型的解决方案是将 List<T> 包装在 BindingSource 中,以便利用其设计时数据绑定支持。 然而,这条路只能走这么远,因为一个关键功能将缺失——排序支持。

本文旨在通过提供 BindingList<T> 的自定义实现来解决这个问题,该实现将自动提供在类型 T 中定义的每个属性上提供排序功能所需的方法。

实现目标

  • 通过实例化自定义 BindingList<T> 实现的实例来支持对所有属性进行排序。 例如,编写
  • MySortableBindingList<SaleDetails>sortableSaleDetails = 
                          new MySortableBindingList<SaleDetail>();

    并获得排序功能。

激励示例

为了说明这种方法,我们将建模两个类,SaleSaleDetail,如下所示

public class Sale {

    public Sale() {
        SaleDate = DateTime.Now;
    }

    public MySortableBindingList<SaleDetail> SaleDetails { get; set; }
    public string Salesman { get; set; }
    public string Client { get; set; }
    public DateTime SaleDate { get; set; }

    public decimal TotalAmount {
        get {
            Debug.Assert(SaleDetails != null);
            return SaleDetails.Sum(a => a.TotalAmount);
        }
    }
}

public class SaleDetail {

    public string Product { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }

    public decimal TotalAmount {
        get {
            return UnitPrice * Quantity;
        }
    }
}

上述类只是简单到足以说明文章的主要概念,因为验证、持久化、错误处理等超出了本文的范围。

继承 BindingList<T>

首先,代码

public class MySortableBindingList<T> : BindingList<T> {

    // reference to the list provided at the time of instantiation
    List<T> originalList;
    ListSortDirection sortDirection;
    PropertyDescriptor sortProperty;

    // function that refereshes the contents
    // of the base classes collection of elements
    Action<MySortableBindingList<T>, List<T>> 
                   populateBaseList = (a, b) => a.ResetItems(b);

    // a cache of functions that perform the sorting
    // for a given type, property, and sort direction
    static Dictionary<string, Func<List<T>, IEnumerable<T>>> 
       cachedOrderByExpressions = new Dictionary<string, Func<List<T>, 
                                                 IEnumerable<T>>>();

    public MySortableBindingList() {
        originalList = new List<T>();
    }

    public MySortableBindingList(IEnumerable<T> enumerable) {
        originalList = enumerable.ToList();
        populateBaseList(this, originalList);
    }

    public MySortableBindingList(List<T> list) {
        originalList = list;
        populateBaseList(this, originalList);
    }

    protected override void ApplySortCore(PropertyDescriptor prop, 
                            ListSortDirection direction) {
        /*
         Look for an appropriate sort method in the cache if not found .
         Call CreateOrderByMethod to create one. 
         Apply it to the original list.
         Notify any bound controls that the sort has been applied.
         */

        sortProperty = prop;

        var orderByMethodName = sortDirection == 
            ListSortDirection.Ascending ? "OrderBy" : "OrderByDescending";
        var cacheKey = typeof(T).GUID + prop.Name + orderByMethodName;

        if (!cachedOrderByExpressions.ContainsKey(cacheKey)) {
            CreateOrderByMethod(prop, orderByMethodName, cacheKey);
        }

        ResetItems(cachedOrderByExpressions[cacheKey](originalList).ToList());
        ResetBindings();
        sortDirection = sortDirection == ListSortDirection.Ascending ? 
                        ListSortDirection.Descending : ListSortDirection.Ascending;
    }


    private void CreateOrderByMethod(PropertyDescriptor prop, 
                 string orderByMethodName, string cacheKey) {

        /*
         Create a generic method implementation for IEnumerable<T>.
         Cache it.
        */

        var sourceParameter = Expression.Parameter(typeof(List<T>), "source");
        var lambdaParameter = Expression.Parameter(typeof(T), "lambdaParameter");
        var accesedMember = typeof(T).GetProperty(prop.Name);
        var propertySelectorLambda =
            Expression.Lambda(Expression.MakeMemberAccess(lambdaParameter, 
                              accesedMember), lambdaParameter);
        var orderByMethod = typeof(Enumerable).GetMethods()
                                      .Where(a => a.Name == orderByMethodName &&
                                                   a.GetParameters().Length == 2)
                                      .Single()
                                      .MakeGenericMethod(typeof(T), prop.PropertyType);

        var orderByExpression = Expression.Lambda<Func<List<T>, IEnumerable<T>>>(
                                    Expression.Call(orderByMethod,
                                            new Expression[] { sourceParameter, 
                                                               propertySelectorLambda }),
                                            sourceParameter);

        cachedOrderByExpressions.Add(cacheKey, orderByExpression.Compile());
    }

    protected override void RemoveSortCore() {
        ResetItems(originalList);
    }

    private void ResetItems(List<T> items) {

        base.ClearItems();

        for (int i = 0; i < items.Count; i++) {
            base.InsertItem(i, items[i]);
        }
    }

    protected override bool SupportsSortingCore {
        get {
            // indeed we do
            return true;
        }
    }

    protected override ListSortDirection SortDirectionCore {
        get {
            return sortDirection;
        }
    }

    protected override PropertyDescriptor SortPropertyCore {
        get {
            return sortProperty;
        }
    }

    protected override void OnListChanged(ListChangedEventArgs e) {
        originalList = base.Items.ToList();
    }
}

简而言之

例如,如果您创建一个 MySortableBindingList<Sale> 并按 Customer 属性排序,则将创建一个概念上类似于 Enumerable.OrderBy<Sale>(originalList, a => a.Customer) 的表达式,并用于执行排序。

创建示例数据并设置数据绑定的代码

public void OnLoad(object source, EventArgs e) {

    var sales = new[] {
        new Sale(){

            Client = "Jahmani Mwaura",
            SaleDate = new DateTime(2008,1,1),
            Salesman = "Gachie",

            SaleDetails = new MySortableBindingList<SaleDetail>(){

                new SaleDetail(){
                    Product = "Sportsman",
                    Quantity = 1,
                    UnitPrice = 80
                },

                 new SaleDetail(){
                    Product = "Tusker Malt",
                    Quantity = 2,
                    UnitPrice = 100
                },

                new SaleDetail(){
                    Product = "Alvaro",
                    Quantity = 1,
                    UnitPrice = 50
                }
            }
        },

        new Sale(){

            Client = "Ben Kones",
            SaleDate = new DateTime(2008,1,1),
            Salesman = "Danny",

            SaleDetails = new MySortableBindingList<SaleDetail>(){

                new SaleDetail(){
                    Product = "Embassy Kings",
                    Quantity = 1,
                    UnitPrice = 80
                },

                 new SaleDetail(){
                    Product = "Tusker",
                    Quantity = 5,
                    UnitPrice = 100
                },

                new SaleDetail(){
                    Product = "Novida",
                    Quantity = 3,
                    UnitPrice = 50
                }
            }
        },

        new Sale(){

            Client = "Tim Kim",
            SaleDate = new DateTime(2008,1,1),
            Salesman = "Kiplagat",

            SaleDetails = new MySortableBindingList<SaleDetail>(){

                new SaleDetail(){
                    Product = "Citizen Special",
                    Quantity = 10,
                    UnitPrice = 30

                },

                new SaleDetail(){
                    Product = "Burn",
                    Quantity = 2,
                    UnitPrice = 100
                }
            }
        }
    };

    saleBindingSource.DataSource = new MySortableBindingList<Sale>(sales);
}

亲身体验

您可以下载页面顶部的示例并亲身体验。 希望您喜欢。

祝好!

历史

  • 2008 年 12 月 2 日:文章发布。
© . All rights reserved.