Baumbart's Journey

Computers and the Rest

An Alternative to ObservableCollection

with one comment

A common way of providing data for a listbox is the ObservableCollection. It’s easy to bind the collection to a listbox and then add items to it. But where is the AddRange(IEnumerable<T> collection) method? There is none, so all items must be added one by one. Each time an item is added, the collection notifies the listeners of the CollectionChanged property, usually some data bound controls. That means there is a lot of harmless event noise, but it still bothered me. In some cases I suspect that the performance will drop significantly, e.g. when using controls which are expensive to update several hundred times. (Note that the ListBox uses a VirtualizationPanel to maintain performance.)

So I looked up some documentation on MSDN and deceided to implement my very own ObservableList<T>, a List<T> implementing the INotifyCollectionChanged interface. It seems easy enough, just fire the CollectionChanged event whenever some items are added or removed.

This snipped shows how it works. I wrapp the regular AddRange(IEnumerable collection) method and raise the CollectionChanged event. Same procedure for the other methods which add or remove items, including the indexer property. The NotifyCollectionChangedEventArgs specifies what changed. Sometimes I don’t want to raise the event, so I provided the IsNotifying property to switch that on or off.

 

public class ObservableList<T> : List<T>, INotifyINotifyCollectionChanged

    public new void AddRange(IEnumerable<T> collection)

    {

        base.AddRange(collection);

        NotifyCollectionChangedEventArgs e =

            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(collection));

        OnCollectionChanged(e);

    }

 

    protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)

    {

        if (IsNotifying && CollectionChanged != null)

            CollectionChanged(this, e);

    }

 

Now it’s time to hook up my shiny, new ObservableList<T>! That’s when WPF notified me it doesn’t like ranges.

 

Ranges are not supported

Ranges are not supported

 

So what happened here? The ListBox to wich the list is data bound is being picky about kind of information I give to the NotifyCollectionChangedEventArgs. Obviously, it doen’t like range updates.

That was kind of disappointing but not as bad as I thought at first. Why is the NotifyCollectionChangedEventArgs class that elaborate in the first place?? I fixed this by raising the event slightly differently when this exception occurs.

 

    protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)

    {

        if (IsNotifying && CollectionChanged != null)

            try

            {

                CollectionChanged(this, e);

            }

            catch (System.NotSupportedException)

            {

                NotifyCollectionChangedEventArgs alternativeEventArgs =

                    new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);

                OnCollectionChanged(alternativeEventArgs);

            }

    }

 

So here is the complete code to copy and paste.

 

public interface IObservableList<T> : IList<T>, INotifyCollectionChanged { }

 

public class ObservableList<T> : List<T>, IObservableList<T>, INotifyPropertyChanged

{

    #region Constructors

    public ObservableList()

    {

        IsNotifying = true;

 

        // As a gimmick, I wanted to bind to the Count property, so I

        // use the OnPropertyChanged event from the INotifyPropertyChanged

        // interface to notify about Count changes.

        CollectionChanged += new NotifyCollectionChangedEventHandler(

            delegate(object sender, NotifyCollectionChangedEventArgs e)

            {

                OnPropertyChanged("Count");

            }

            );

    }

    #endregion

 

    #region Properties

    public bool IsNotifying { get; set; }

    #endregion

 

    #region Adding and removing items

    public new void Add(T item)

    {

        base.Add(item);

        NotifyCollectionChangedEventArgs e =

            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item);

        OnCollectionChanged(e);

    }

 

    public new void AddRange(IEnumerable<T> collection)

    {

        base.AddRange(collection);

        NotifyCollectionChangedEventArgs e =

            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(collection));

        OnCollectionChanged(e);

    }

 

    public new void Clear()

    {

        base.Clear();

        NotifyCollectionChangedEventArgs e =

            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);

        OnCollectionChanged(e);

    }

 

    public new void Insert(int i, T item)

    {

        base.Insert(i, item);

        NotifyCollectionChangedEventArgs e =

            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item);

        OnCollectionChanged(e);

    }

 

    public new void InsertRange(int i, IEnumerable<T> collection)

    {

        base.InsertRange(i, collection);

        NotifyCollectionChangedEventArgs e =

            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection);

        OnCollectionChanged(e);

    }

 

    public new void Remove(T item)

    {

        base.Remove(item);

        NotifyCollectionChangedEventArgs e =

            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item);

        OnCollectionChanged(e);

    }

 

    public new void RemoveAll(Predicate<T> match)

    {

        List<T> backup = FindAll(match);

        base.RemoveAll(match);

        NotifyCollectionChangedEventArgs e =

            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, backup);

        OnCollectionChanged(e);

    }

 

    public new void RemoveAt(int i)

    {

        T backup = this[i];

        base.RemoveAt(i);

        NotifyCollectionChangedEventArgs e =

            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, backup);

        OnCollectionChanged(e);

    }

 

    public new void RemoveRange(int index, int count)

    {

        List<T> backup = GetRange(index, count);

        base.RemoveRange(index, count);

        NotifyCollectionChangedEventArgs e =

            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, backup);

        OnCollectionChanged(e);

    }

 

    public new T this[int index]

    {

        get { return base[index]; }

        set

        {

            T oldValue = base[index];

            NotifyCollectionChangedEventArgs e =

                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue);

            OnCollectionChanged(e);

        }

    }

    #endregion

 

    #region INotifyCollectionChanged Members

 

    public event NotifyCollectionChangedEventHandler CollectionChanged;

 

    protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)

    {

        if (IsNotifying && CollectionChanged != null)

            try

            {

                CollectionChanged(this, e);

            }

            catch (System.NotSupportedException)

            {

                NotifyCollectionChangedEventArgs alternativeEventArgs =

                    new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);

                OnCollectionChanged(alternativeEventArgs);

            }

    }

    #endregion

 

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

 

    protected void OnPropertyChanged(string propertyName)

    {

        if (PropertyChanged != null)

        {

            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

        }

    }

    #endregion

}

 

 

 

Still reading? Then I’d recommend taking a look at some supplemental general information about ObservableCollection<T> or at how to make the an observable collection (or list) thread-safe.

Advertisement

Written by baumbartswelt

June 1, 2009 at 12:27 am

One Response

Subscribe to comments with RSS.

  1. Or use extension methods to add just this one method :)

    tiit

    November 24, 2009 at 7:14 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.