Friday, December 12, 2008

How to sort a BindingList using CollectionViewSource

If you've ever tried to sort a BindingList (or any other collection implementing IBindingList) using CollectionViewSource then you've most probably not had much luck. The BindingListCollectionView used for this task lacks the functionality of ListCollectionView and on top of that has a bug making it even more onerous to use. This post describes a workaround so you can use ListCollectionView instead.

BindingListCollectionView unfortunately doesn't have the sorting capabilities of ListCollectionView. It only uses the in-built sorting defined in IBindingList instead of doing it itself and if that's not enabled it will throw an exception. Not very useful.

On top of that, if your BindingList doesn't implement ITypedList then the sorting fails silently even if you have implemented the sorting!

Here's a nice trick to adapt a BindingList to an ObservableCollection and therefore make the CollectionViewSource use the ListCollectionView implementation for sorting:

class ExtendedCollectionViewSource : CollectionViewSource
{
    private BindingListAdapter mAdapter;

    static ExtendedCollectionViewSource()
    {
        CollectionViewSource.SourceProperty.OverrideMetadata(
            typeof(ExtendedCollectionViewSource),
            new FrameworkPropertyMetadata(null, CoerceSource));
    }

    private static object CoerceSource(DependencyObject d, object baseValue)
    {
        ExtendedCollectionViewSource cvs = (ExtendedCollectionViewSource)d;
        if (cvs.mAdapter != null)
        {
            cvs.mAdapter.Dispose();
            cvs.mAdapter = null;
        }
        IBindingList bindingList = baseValue as IBindingList;
        if (bindingList != null)
        {
            cvs.mAdapter = new BindingListAdapter(bindingList);
            return cvs.mAdapter;
        }
        return baseValue;
    }
}

private class BindingListAdapter : ObservableCollection<object>, IDisposable
{
    private readonly IBindingList mBindingList;
    private bool mIsDisposed;

    public BindingListAdapter(IBindingList bindingList)
    {
        if (bindingList == null)
        {
            throw new ArgumentNullException("bindingList");
        }

        mBindingList = bindingList;

        foreach (object item in mBindingList)
        {
            Items.Add(item);
        }

        mBindingList.ListChanged += BindingList_ListChanged;
    }

    private void BindingList_ListChanged(object sender, ListChangedEventArgs e)
    {
        if (e.ListChangedType == ListChangedType.ItemAdded)
        {
            InsertItem(e.NewIndex, mBindingList[e.NewIndex]);
        }
        else if (e.ListChangedType == ListChangedType.ItemChanged)
        {
            SetItem(e.NewIndex, mBindingList[e.NewIndex]);
        }
        else if (e.ListChangedType == ListChangedType.ItemDeleted)
        {
            RemoveItem(e.NewIndex);
        }
        else if (e.ListChangedType == ListChangedType.ItemMoved)
        {
            MoveItem(e.OldIndex, e.NewIndex);
        }
        else if (e.ListChangedType == ListChangedType.Reset)
        {
            Items.Clear();

            foreach (object item in mBindingList)
            {
                Items.Add(item);
            }
            
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Reset));
        }
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!mIsDisposed)
        {
            if (disposing)
            {
                mBindingList.ListChanged -= BindingList_ListChanged;
            }
        }
        mIsDisposed = true;
    }

    #endregion
}



ExtendedCollectionViewSource.cs



1 comment:

net performance said...

Nice writeup on .NET for the performance issues,Its always difficult to carry on the good work in .net thanks for the help and sharing the article!!..