快速实现可排序的 BindingList






4.96/5 (64投票s)
一个自定义的 BindingList 实现,为类型 T 的每个属性提供排序功能。
引言
实现父子层次结构(例如,一个 Sale
对象及其相关的 SaleDetails
)是在建模业务领域实体时遇到的最常见场景之一。 如果您使用类来实现业务对象,则通常会将子对象集合存储在 List<T>
中。 但是,如果您需要基于 .NET 框架的数据绑定支持构建丰富的用户界面,则 List<T>
将会显得不足。
典型的解决方案是将 List<T>
包装在 BindingSource
中,以便利用其设计时数据绑定支持。 然而,这条路只能走这么远,因为一个关键功能将缺失——排序支持。
本文旨在通过提供 BindingList<T>
的自定义实现来解决这个问题,该实现将自动提供在类型 T
中定义的每个属性上提供排序功能所需的方法。
实现目标
- 通过实例化自定义
BindingList<T>
实现的实例来支持对所有属性进行排序。 例如,编写
MySortableBindingList<SaleDetails>sortableSaleDetails =
new MySortableBindingList<SaleDetail>();
并获得排序功能。
激励示例
为了说明这种方法,我们将建模两个类,Sale
和 SaleDetail
,如下所示
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 日:文章发布。