jump to navigation

ObservableCollection events and WPF Window/UI on Different Threads March 3, 2007

Posted by Karl Hulme in .Net, WPF.
trackback

This blog is no longer actively maintained. The content is well over 5 years old – which is like 50 coder years. Use at your own risk!

Warning: There’s an improved version of the class outlined in a subsequent blog post.

When binding ObservableCollection’s using WPF you’ll probably encounter the following error, ‘This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.’

This is simply telling you that the CollectionChanged event was not raised on the thread that the Window was created on.  It’s an easy problem to fix, we just need to wrap the collection so that any events raised on the source collection will be raised on the same thread as the UI.  My complete implementation is below which can be copy/pasted and the namespace renamed.

using System; using System.Collections.Generic; using System.Text; using System.Collections.Specialized; using System.ComponentModel; using System.Windows.Threading; namespace MyNamespace { public class SynchronisedObservableCollection<T>: ICollection<T>, INotifyCollectionChanged, INotifyPropertyChanged { private ICollection<T> collection; private Dispatcher dispatcher; public SynchronisedObservableCollection(ICollection<T> collection) { if (collection == null || collection as INotifyCollectionChanged == null || collection as INotifyPropertyChanged == null) { throw new ArgumentException("Collection must support ICollection, INotifyCollectionChanged " + " and INotifyPropertyChanged interfaces."); } this.collection = collection; this.dispatcher = Dispatcher.CurrentDispatcher; INotifyCollectionChanged collectionChanged = collection as INotifyCollectionChanged; collectionChanged.CollectionChanged += delegate(Object sender, NotifyCollectionChangedEventArgs e) { dispatcher.Invoke(DispatcherPriority.Normal, new RaiseCollectionChangedEventHandler(RaiseCollectionChangedEvent), e); }; INotifyPropertyChanged propertyChanged = collection as INotifyPropertyChanged; propertyChanged.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs e) { dispatcher.Invoke(DispatcherPriority.Normal, new RaisePropertyChangedEventHandler(RaisePropertyChangedEvent), e); }; } #region INotifyCollectionChanged Members public event NotifyCollectionChangedEventHandler CollectionChanged; private void RaiseCollectionChangedEvent(NotifyCollectionChangedEventArgs e) { if (CollectionChanged != null) { CollectionChanged(this, e); } } private delegate void RaiseCollectionChangedEventHandler(NotifyCollectionChangedEventArgs e); #endregion #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChangedEvent(PropertyChangedEventArgs e) { if (PropertyChanged != null) { PropertyChanged(this, e); } } private delegate void RaisePropertyChangedEventHandler(PropertyChangedEventArgs e); #endregion #region ICollection<T> Members public void Add(T item) { collection.Add(item); } public void Clear() { collection.Clear(); } public bool Contains(T item) { return collection.Contains(item); } public void CopyTo(T[] array, int arrayIndex) { collection.CopyTo(array, arrayIndex); } public int Count { get { return collection.Count; } } public bool IsReadOnly { get { return collection.IsReadOnly; } } public bool Remove(T item) { return collection.Remove(item); } #endregion #region IEnumerable<T> Members public IEnumerator<T> GetEnumerator() { return collection.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((System.Collections.IEnumerable)collection).GetEnumerator(); } #endregion } }

The wrapper above accepts any ICollection<T> that also supports INotifyCollectionChanges and INotifyPropertyChanged.  Best candidates for this are the ObservableCollection<T> and ReadOnlyObservableCollection<T> classes.  You use the wrapper inbetween your business layer (the original collection) and the UI classes that will consume it.  For me, this means it sits in class that provides central access to all Model data (thinking MVC/MVP)

public static class AppModel { public static SynchronisedObservableCollection<String> GetSyncCollection() { ObservableCollection<String> innerCollection = new ObservableCollection<String>(); return new SynchronisedObservableCollection<String>(innerCollection); } }

You can now bind to the collection using an ObjectDataProvider that calls the GetSyncCollection method.  Note that my implementation doesn’t require you to pass in the Dispatcher of the UI thread.  That’s because I assume that the UI thread will be the one creating the wrapper.  Since this class forms part of the UI layer and not the business layer, then it’s a fair assumption, but if it doesn’t work for you then you can always pass a Dispatcher into the constructor.

<Window x:Class="MyApplication.Host" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:model="clr-namespace:MyNamespace.Model;assembly=MyAssembly.Model" Width="800" Height="600" > <Window.Resources> <ObjectDataProvider x:Key="myList" ObjectType="model:AppModel" MethodName="GetSyncCollection"/> </Window.Resources> <ListBox ItemsSource="{Binding Source={StaticResource myList}}"> </Window>

Incidently, I’m not particularly happy with SynchronisedObservableCollection as the class name.  It implies that the collection is thread-safe, and although in one way it is, it many others it isn’t!

Advertisements

Comments»

1. eldevyWrirl - August 10, 2007

To put it quite simply and non-mechanical, blackheads are little bumps embedded into the pores of our skin. They are usually yellowish or blackish in color.
Blackheads are a type of acne called an open comedone. According to medical practitioners, blackhead on face are the first stage of acne!!!
You can extract blackheads yourself if there is no sign of infection on or surrounding the blemish.
link:www.medicalnewstoday.com/articles/71615.php

2. casinosfreebonusesv - August 12, 2007

deposit free bonus casino
see to signature…

3. PlayRadioMen - September 2, 2007
4. Stasigragov - September 27, 2007

Hello, nice site keep up good job!!!
http://accomodations-ragovij.cn/

5. SeveQ - December 12, 2007

I need to figure out how to filter such a SynchronizedObservableCollection using a CollectionView. Any hints?

6. Roman S. Golubin - February 15, 2008

Karl, big thanks for this artice!

7. Kuniko - November 10, 2008

Keep up the good work.

8. Paul - February 24, 2009

Why are you accepting ICollection collection in the constructor and the throw exception if passed collection is not INotifyCollectionChanged and INotifyPropertyChanged (wonder why do you even need this interface)??? Why can’t you just accept IEnumerable and INotifyCollection as generic T?

9. Ajay - September 28, 2011

Fantastic, works great!!! I have been trying others and this one works the best.

Thanks for all your effort.

10. porte de garage motorisée - August 1, 2014

Hi there, its fastidious article regarding
media print, we all know media is a impressive source of information.


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 )

Google+ photo

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

Connecting to %s

%d bloggers like this: