Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Databinding - BindingList, BindingSource, and BusinessObjects: Part 2

4.50/5 (8 votes)
27 Jun 2009CPOL4 min read 68.2K   1.3K  
Databinding - Searching and sorting BindingList.

Introduction

After my first article on BindingSource and BindingList, I was planning to have the second part also to be very simple and easy. In many applications, we use custom business entities and BindingSource to get the benefit of databinding. Now, the problem comes while needing sorting and searching features which do not come with BindingList by default. So in this article, I would show an easy way to implement sorting and searching in BindingSource (I would suggest beginners have a look at MSDN also, which I do very frequently).

Background

I would continue with the same example of Employee-Employee details to elaborate sorting and searching. You can download the code from the above link to look into the implementation.

Now, for each of the business entities, we are having a collection class inherited from BindingList; like for EmployeeBE, we have EmployeeBEList : BindingList<EmployeeBE>. I have decided to have a generic BindingList class which would have the sorting and searching features. All these collection classes would inherit the generic BindingList class. So in the whole project, you only need one implementation of sorting and searching. Is not that proper re-usability of code?

Generic BindingList

The first step is to create the new generic BindingList class. In BaseBindingList, we will incorporate the sorting and searching features.

C#
public class BaseBindingList : BindingList
{
}

Searching Feature

First, we have to override the read-only property SupportsSearchingCore which is set to false by default. To have searching enabled, this should return true.

C#
protected override bool SupportsSearchingCore
{
    get{return true;}
}

Now it's time to implement searching. For that, we have to override the FindCore method. In general, you would find that the index of the item matching the search criteria is returned. But, I felt this is only good if you have unique items. I tried to get the indices of all the items matching the search criteria. For that, I kept an ArrayList, selectedIndices, and used the FindCore return value as an indicator of whether the item(s) was found or not.

C#
protected override int FindCore(PropertyDescriptor prop, object key) 
{ 
    // Get the property info for the specified property. 
    PropertyInfo propInfo = typeof(T).GetProperty(prop.Name); 
    T item; 
    int found = -1; 
    selectedIndices = new ArrayList(); 
    if (key != null) 
    { 
        // Loop through the items to see if the key 
        // value matches the property value. 
        for (int i = 0; i < Count; ++i) 
        { 
            item = (T)Items[i]; 
            if (propInfo.GetValue(item, null).Equals(key)) 
            { 
                found = 0; 
                selectedIndices.Add(i); 
            } 
        } 
    } 
    return found; 
}

We are done with the implementation of searching, and now we have to just expose this search with a method Find so that rest of the world can access this.

C#
public int[] Find(string property, object key)
{
    // Check the properties for a property with the specified name.
    PropertyDescriptorCollection properties =
    TypeDescriptor.GetProperties(typeof(T));
    PropertyDescriptor prop = properties.Find(property, true);
    // If there is not a match, return -1 otherwise pass search to
    // FindCore method.
    if (prop == null)
        returnIndices = null;
    else
    {
        if (FindCore(prop, key) >= 0)
        {
            returnIndices = (int[])(selectedIndices.ToArray(typeof(int)));
        }
    }
    return returnIndices;
}

Sorting Feature

We will now implement the sorting feature in BaseBindingList. First, override the read-only property SortPropertyCore which is set to false by default. Sorting is supported by returning true.

C#
protected override PropertyDescriptor SortPropertyCore
{
    get { return sortPropertyValue; }
}

In the same manner, we have to override another read-only property to indicate whether the list is sorted or not.

C#
private bool isSortedValue;
protected override bool IsSortedCore
{
    get { return isSortedValue; }
}

Before we directly start the sorting implementation, there are couple of read-only properties which needs to be overridden: SortDirectionCore and SortPropertyCore. SortDirectionCore indicates the direction of sorting, and SortPropertyCore is the property descriptor used for sorting the list. Here, you have to keep one thing in mind. The property to be used should implement the IComparable interface which has the CompareTo method. This implementation of sorting would work for simple datatypes, leaving a scope for future enhancements for complex datatypes.

C#
private PropertyDescriptor sortPropertyValue;
protected override PropertyDescriptor SortPropertyCore
{
    get { return sortPropertyValue; }
}
private ListSortDirection sortDirectionValue;
protected override ListSortDirection SortDirectionCore
{
    get { return sortDirectionValue; }
}

We are all set for the sorting implementation by overriding the ApplySortCore method. Here's the implementation where I have tried to keep track of the unsorted items as well, which I will use while removing sorting. In sorting, I use a simple swapping approach:

C#
protected override void ApplySortCore(PropertyDescriptor prop,
ListSortDirection direction)
{
    sortedList = new ArrayList();
    // Check to see if the property type we are sorting by implements
    // the IComparable interface.
    Type interfaceType = prop.PropertyType.GetInterface("IComparable");
    if (interfaceType != null)
    {
        // If so, set the SortPropertyValue and SortDirectionValue.
        sortPropertyValue = prop;
        sortDirectionValue = direction;
        if (!isSortedValue)
        {
            unsortedList = new ArrayList(this.Count);
        }
        // Loop through each item, adding it the the sortedItems ArrayList.
        foreach (Object item in this.Items)
        {
            sortedList.Add(prop.GetValue(item));
            //Make sure that unsorted list keeps the original 
            //value when sorting is applied for the first time
            if (!isSortedValue)
            {
                unsortedList.Add(item);
            }
        }
        // Call Sort on the ArrayList.
        sortedList.Sort();
        // Check the sort direction and then copy the sorted items
        // back into the list.
        if (direction == ListSortDirection.Descending)
        sortedList.Reverse();
        for (int i = 0; i < this.Count; i++)
        {
            int[] selectedIndices = this.Find(prop.Name, sortedList[i]);
            if (selectedIndices != null && selectedIndices.Length > 0)
            {
                foreach (int position in selectedIndices)
                {
                    if (position != i)
                    {
                        SwapItems(i, position);
                    }
                }
            }
        }
        isSortedValue = true;
        // Raise the ListChanged event so bound controls refresh their
        // values.
        OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
    }
    else
        // If the property type does not implement IComparable, let the user
        // know.
        throw new NotSupportedException("Cannot sort by " + prop.Name +
          ". This" + prop.PropertyType.ToString() +
          " does not implement IComparable");
}

This sorting method needs to be exposed. Here's the code to do that:

C#
public void ApplySort(string property, ListSortDirection direction)
{
    PropertyDescriptorCollection properties =
        TypeDescriptor.GetProperties(typeof(T));
    PropertyDescriptor prop = properties.Find(property, true);
    if (prop != null)
        ApplySortCore(prop, direction);
    else
        throw new NotSupportedException("Cannot sort by " + prop.Name +
            ". This" + prop.Name +
            " does not exist.");
}

We are almost done with the implementation except for the removal of sorting and adding a new item. To remove the sorting, you need to populate the list with the unsorted list which is kept in the sorting method. Here, I wanted to store the initial list as an unsorted list. I have overridden the RemoveSortCore method with this implementation. This removal of sorting is done via the RemoveSort method.

If you have a sorted list, while adding a new item, it needs to be taken care that the item is placed in the proper position. So, I have overridden the EndNew method to place the newly added item properly in a sorted list. The whole implementation is available in the attached code.

C#
protected override void RemoveSortCore()
{
    // Ensure the list has been sorted.
    if (unsortedList != null)
    {
        // Loop through the unsorted items and reorder the
        // list per the unsorted list.
        for (int i = 0; i < unsortedList.Count;i++ )
        {
            this[i] = (T)unsortedList[i];
        }
        isSortedValue = false;
        OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
    }
}

public override void EndNew(int itemIndex)
{
    // Check to see if the item is added to the end of the list,
    // and if so, re-sort the list.
    if (sortPropertyValue != null && itemIndex == this.Count - 1)
        ApplySortCore(this.sortPropertyValue, this.sortDirectionValue);
    base.EndNew(itemIndex);
}

History

This article is the next part of my previous article on BindingSource, BindingList. Please go through the first part of this article, Part 1, if you are new to BindingList and BindingSource.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)