Adding IBindingListView Functionality
If you remember from Chapter 7. there is a level of data-binding functionality defined through thelBindingListView interface that you can support to make your collections even richer. The IBindingListView specifically adds the ability to perform sorting on more than one property at a time, and to filter the collection based on some filter expression to only show parts of the underlying collection at a time.
The IBindingListView interface defines four additional properties and two methods you will need to implement on your collection to fully support this interface.
® The SupportsAdvancedSorting and SupportsFiltering Boolean properties indicate which of the two capabilities you support.
• The SortDescriptions property returns a ListSortDescriptionCollection that contains whatever sort criteria is currently in effect. Each ListSortDescription object within that collection is just a pair associating ^PropertyDescriptor and a ListSortDirection for each of the properties on which sorting is applied.
• The Filter property supports getting and setting a string filter expression that you are using to tailor the collection contents that are presented to anyone using the collection.
® The ApplySort method is similar to the one defined on thelBindingList interface, except that it takes a
ListSortDescriptionCollection as a parameter instead of a singlePropertyDescriptor and ListSortDirection. Each ListSortDescription in that collection contains a property descriptor and a sort direction, which enable you to sort on each criteria in turn.
• The RemoveFilter method removes whatever filter is in effect and restores the collection to its full contents.
Although this sounds straightforward, implementing this interface is no trivial matter. Sorting on multiple properties actually is fairly easy to implement, but requires some extensions to the SortComparer<T> class and sorting logic presented earlier. Filtering can be done in a number of ways, but this further complicates matters if you want to allow additions and removals from the collection while it is in the filtered state (for the same reasons that those operations present difficulties when sorted, as discussed earlier). With a filtered list, your collection could also potentially be sorted, so you need to be able to get back to the original, possibly altered, list from sorted/unfiltered, filtered/unsorted, and filtered/sorted states.
To show a reasonable implementation that you can reuse if you can live with not adding and removing items from the list when it is sorted or filtered, I enhanced the BindingListView<T> class to provide an implementation oflBindingListView. The first step is to enhance the SortComparer<T>to support multiproperty comparisons. The full class listing is shown inListing 9.6.
Listing 9.6. SortComparer Class class SortComparer<T> : IComparer<T> {
private ListSortDescriptionCollection m_SortCollection = null;
private PropertyDescriptor m_PropDesc = null; private ListSortDirection m_Direction = ListSortDirection.Ascending;
public SortComparer(PropertyDescriptor propDesc, ListSortDirection direction)
m_PropDesc = propDesc; m_Direction = direction;
public SortComparer(ListSortDescriptionCollection sortCollection) {
m_SortCollection = sortCollection;
int IComparer<T>.Compare(Tx, Ty) {
object xValue = m_PropDesc.GetValue(x); object yValue = m_PropDesc.GetValue(y);
return CompareVaiues(xVaiue, yValue, m_Direction); }
else if (m_SortCollection != null && m_SortCollection.Count > 0)
return RecursiveComparelnternal(x,y, 0);
else return 0;
private int CompareValues(object xValue, object yValue, ListSortDirection direction)
int retValue = 0;
if (xValue is IComparable) // Can ask the x value {
retValue = ((IComparable)xValue).Compare To(yValue);
else if (yValue is IComparable) //Can ask the y value {
retValue = ((IComparable)yValue).Compare To(xValue);
// not comparable, compare String representations else if (!xValue.Equals(yValue)) {
retValue = xValue.ToString().Compare To(yValue.ToString());
if (direction == ListSortDirection.Ascending) {
return retValue;
private int RecursiveComparelnternal(T x, T y, int index) {
if (index >= m_SortCollection.Count) return 0; II termination condition
ListSortDescription MstSortDesc = m_SortCollection[index]; object xValue = MstSortDesc.PropertyDescriptor.GetValue(x); object yValue = MstSortDesc.PropertyDescriptor.GetValue(y);
int retValue = CompareValues(xValue, yValue,MstSortDesc.SortDirection);
return RecursiveComparelnternal(x,y,++index);
else return retValue;
The additions to what was shown in Listing 9.4 are in bold.
• A new parameterized constructor is added that lets you create a SortComparer<T> with a ListSortDescriptionCollection instead of a single property descriptor and sort direction.
® Depending on which constructor was used, the Compare method calls either the Compare Values method directly as shown in Listing 9.4, or calls the newRecursiveComparelnternal method, which recursively compares the two values, starting with the first property specified in the ListSortDescriptionCollection.
• If that property is equal between the two objects, then it proceeds to the next property, and then the next one, until all properties have been compared or a difference has been found.
With this in place, you can make the additions to the BindingListView<T> class to support multiproperty sorting. Filtering is completely separate, but it is also implemented in this version. For simplicity, the filtering code only supports filtering on a single property at a time, based on the string representation of that property's value, and uses quotes to delimit the value that is being filtered. A filter expression that is supported would look something like:
IBindingListView listView = mjDrdersCollection as IBindingListView; if (listView == null) return;
listView. Filter = "ProductName-Deep fryer fat'";
The full BindingListView<T> class is shown in Listing 9.7.
Listing 9.7. BindingListView <T> Class public class BindingListView<T> : BindingList<T>, IBindingListView, IRaiseltemChangedEvents
private bool m_Sorted = false;
private bool m_Filtered = false; private string m_FilterString = null; private ListSortDirection m_SortDirection =
ListSortDirection.Ascending; private PropertyDescriptor m_SortProperty = null; private ListSortDescriptionCollection m_SortDescriptions = new ListSortDescriptionCollection(); private List<T> m_OriginalCollection = new List<T>();
public BindingListView() : base() {
public BindingListView(List<T> list) : base(list) {
protected override bool SupportsSearchingCore {
get {return true;}
protected override int FindCore(PropertyDescriptor property, object key) {
// Simple iteration:
if (property.GetValue(item).Equals(key)) {
return i;
//Alternative search implementation //using List.Findlndex:
//Predicate<T> pred = delegate(T item)
// if (property.GetValue(item).Equals(key)) // return true; // else
//List<T> list = Items as List<T>; //if (list == null) // return -1;
//return list.Findlndex(pred); }
protected override bool SupportsSortingCore {
get {return true;}
protected override bool IsSortedCore {
get {return m_Sorted;}
protected override ListSortDirection SortDirectionCore {
get {return m_SortDirection;}
protected override PropertyDescriptor SortPropertyCore {
get {return m_SortProperty;}
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
m_SortDirection = direction; m_SortProperty = property; SortComparer<T> comparer =
new SortComparer<T>(property,direction); AppiySortlnternal(comparer);
private void ApplySortlnternal(SortComparer<T> comparer) {
if (mjDriginalCollection. Count == 0) {
mjDriginalCollection.AddRange(this);
List<T> listRef = this.Items as List<T>; if (listRef == null)
return; listRef.Sort(comparer); m_Sorted = true;
OnListChanged(new ListChangedEventArgs( ListChangedType.Reset, -1));
protected override void RemoveSortCore() {
foreach (T item in m_OriginalCollection) {
Add(item);
mjDriginalCollection.Clear(); m_SortProperty = null; m_SortDescriptions = null; m_Sorted = false;
void IBindingListView.ApplySort(ListSortDescriptionCollection sorts) {
m_SortProperty = null; m_SortDescriptions = sorts;
SortComparer<T> comparer = new SortComparer<T>(sorts);
ApplySortlnternal(comparer); }
string IBindingListView.Filter {
get return m_FilterString;
m_FilterString = value; m_Filtered = true; UpdateFilter();
void IBindingListView.RemoveFilter() {
return; m_FilterString = null; m_Filtered = false; m_Sorted = false; m_SortDescriptions = null; m_SortProperty = null; Clear();
foreach (T item in mjDriginalCollection) {
Add(item);
m_OriginalCollection.Clear();
ListSortDescriptionCollection IBindingListView.SortDescriptions {
return m_SortDescriptions;
bool IBindingListView.SupportsAdvancedSorting {
get return true;
bool IBindingListView.SupportsFiltering {
get return true;
protected virtual void UpdateFilter() {
int equalsPos = m_FilterString.lndexOf('='); // Get property name string propName = m_FilterString.Substring(0,equalsPos).Trim(); // Get filter criteria string criteria = m_FilterString.Substring(equalsPos+1, m_FilterString.Length - equalsPos - 1).Trim(); // Strip leading and trailing quotes criteria = criteria.Substring^ , criteria.Length - 2); // Get a property descriptor for the filter property PropertyDescriptor propDesc = TypeDescriptor.GetProperties(typeof(T))[propName];
if (mjDriginalCollection. Count == 0) {
mjDriginalCollection. AddRange(this);
List<T> currentCollection = new List<T>(this); Clear();
foreach (T item in currentCollection) {
object value = propDesc.GetValue(item);
bool IBindingList.AllowNew {
return CheckReadOnly();
bool IBindingList.AllowRemove {
return CheckReadOnly();
private bool CheckReadOnly() {
return false;
return true;
protected override void lnsertltem(int index, T item) {
foreach (PropertyDescriptor propDesc in TypeDescriptor.GetProperties(item))
if (propDesc.SupportsChangeEvents) {
propDesc.AddValueChanged(item, OnltemChanged);
base.lnsertltem(index, item); }
protected override void Removeitem(int index) {
T item = ltems[index]; PropertyDescriptorCollection propDescs = TypeDescriptor.GetProperties(item) foreach (PropertyDescriptor propDesc in propDescs){
if (propDesc.SupportsChangeEvents) {
propDesc. RemoveValueChanged(item,OnltemChanged);
base.Removeltem(index);
void OnltemChanged(object sender, EventArgs args) {
int index = Items.lndexOf((T)sender); OnListChanged(new ListChangedEventArgs( ListChangedType.ltemChanged, index));
bool IRaiseltemChangedEvents.RaisesltemChangedEvents {
get {return true;}
Binding to Business Objects Through the Data Sources Window
Now that we have stepped through how to declare business objects and collections that support the full range of functionality for data binding, let's look again at the easiest way to use them in data-binding scenarios using the Data Sources window. The CustomBusinessObjects sample actually declared the Customer, Order, CustomerCollection, and TestDataGenerator classes in a separate class library assembly from the Windows application project that was being used to test them. To show how easy it is to use these types with data binding, let's add a new form, called CustomersForm, to the Windows application project CustomBusinessObjectsClient.
1. Set a reference to the CustomBusinessObjects class library in that project.
2. Bring up the Data Sources window, which is initially blank.
3. Click on the Add New Data Source link, which displays the Data Source Configuration wizard.
4. Select Object as the Data source type in the first step.
5. On the next page, Select the Object you wish to bind to,"navigate to the CustomerCollection type, as shown inFigure 9.5.
Figure 9.5. Selecting the Object Type for a Data Source
Click Finish.
A data source for theCustomerCollection with the Customerld, CustomerName, and Orders properties will display, as shown inFigure
- Figure 9.6. Data Sources Window with Custom Object Collection
Drag the CustomerCollection onto theCustomerForm design surface from the Data Sources window, and aDataGridView, BindingSource, and BindingNavigator are added and wired up so that the grid is ready to present customer collections.
8. Add enough code to get an instance of the CustomersCollection (through the TestDataGenerator class), and set theDataSource of the BindingSource to that live instance of a CustomersCollection:
private void OnFormLoad(object sender, EventArgs e) {
m_CustomerCollectionBindingSource.DataSource = TestDataGenerator. GetCustomerCollection();
With these few steps, you can see that it is just as easy to set up data binding to custom objects and collections using the designer as it is to set up data binding for a data set. However, as you saw in the rest of this chapter, there is certainly a lot more work involved in defining the custom object and collection types themselves so that they will work properly in a data-binding scenario.
Post a comment