![]() |
Platforms, Frameworks & Libraries »
LINQ »
General
Intermediate
License: The Code Project Open License (CPOL)
Implementing a Sortable BindingList Very, Very QuicklyBy JahmaniA custom implementation of BindingList that provides sorting for every property of type T. |
C#, .NET (.NET 2.0, .NET 3.0, .NET 3.5), WinForms, LINQ, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||

Implementing parent-child hierarchies (for example, a Sale object and the SaleDetails associated with it) is one of the most common scenarios encountered when modeling the entities in a business domain. If you implement the business objects using classes, a collection of child objects will typically be stored in a List<T>. However, List<T>, will prove anemic should you require a rich user interface built on top of the .NET framework's support for data binding.
The typical solution will be to wrap the List<T> in a BindingSource in order to take advantage of its design time support for data binding. That road will only take you so far as a critical feature will be absent - support for sorting.
This article will seek to remedy that by providing a custom implementation of a BindingList<T> that will automatically provide the methods required to provide sorting capability on every property defined in type T.
BindingList<T>. E.g., write:MySortableBindingList<SaleDetails>sortableSaleDetails =
new MySortableBindingList<SaleDetail>();
and get the sorting functionality.
To illustrate this approach, we shall model two classes, Sale and SaleDetail, as follows:
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;
}
}
}
The above classes are just simple enough to illustrate the main concepts behind the article, as validation, persistence, error handling etc., are beyond the scope of the article.
First, the code:
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();
}
}
If, for instance, you create a MySortableBindingList<Sale> and sort on the Customer property, an expression that conceptually looks something like Enumerable.OrderBy<Sale>(originalList, a => a.Customer) will be created and used to do the sorting.
The code to create the sample data and set up the data binding:
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);
}
You can download the samples at the top of the page and see it at work for yourself. I hope you enjoy.
Cheers!
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 1 Dec 2008 Editor: Smitha Vijayan |
Copyright 2008 by Jahmani Everything else Copyright © CodeProject, 1999-2009 Web11 | Advertise on the Code Project |