jump to navigation

AggregateCollection March 31, 2007

Posted by Karl Hulme in .Net, C#, WPF, XAML.
6 comments

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!

My apparent obsession with collection classes continues, and hopefully reaches it’s conclusion, with the AggregateCollection class.  Simply put, it allows you to access multiple collections from a single collection object.  A basic usage would be something like…

        private void TestAggregate()
        {
            List<Int32> listOne = new List<Int32>();
            List<String> listTwo = new List<String>();
            for (int i = 0; i < 10; i += 2)
            {
                listOne.Add(i + 1);
                listTwo.Add(i.ToString());
            }
 
            AggregateCollection agg = new AggregateCollection(listOne, listTwo);
            foreach (Object o in agg)
            {
                Console.WriteLine(o.ToString());
            }
        }

And the output is as you might expect…

The lists passed to the constructor need only support IEnumerable, but if they also support INotifyCollectionChanged then you’ll get a better WPF binding experience.  I wrote this class for WPF in the first place, because I want to be able to pull information together from different lists into the UI and I don’t want to be monitoring for the change events myself.

The AggregateCollection class itself supports the IEnumerable interface of course, and it also implements IList (which thus brings ICollection into the fray).  The fact that it implements IList, means that even if the constituent lists don’t support IList, you can still access all the objects in the aggregate collection via a single index (from 0 to “count of all the lists”).  I think this is quite cool, but it serves a greater purpose…

When you bind to a collection class in WPF, a CollectionView (or derivative) is created as a wrapper around your collection, and WPF will bind to that instead.  This allows items to be sorted, grouped, etc, without affecting the underlying data.  Bea Costa (data binding supremo) explains this very well (post November 22nd ’06).  The AggregateCollection class supports IList, so that at binding-time WPF will instantiate a ListCollectionView, instead of a CollectionView – which is necessary to support sorting.

The example app below demonstrates the AggregateCollection class in action in WPF, and shows how, with sorting, this can be helpful.  The first and second columns show the contents of two seperate ObservableCollections.  There’s also some buttons for adding and removing objects from those collections, and for moving those items around.  The ListBox in the third column is bound to an AggregateCollection containing the first 2 lists.  The ListBox in the fourth column is bound to a sorted CollectionViewSource which references the same AggregateCollection.  As the items in the first two collections change the ListBoxes in columns 3 and 4 will update automatically.

Example application exe  (save to your local machine first, then run)
Example application solution

The AggregateCollection class can’t be synchronised, because I only intended for it to be used in the UI layer, but I guess there would be a way to make this work.  I also made it read-only because I couldn’t determine a satisfactory strategy for which constituent list a new item should be added to.  Finally, you can’t add or remove constituent lists after it’s been created, but there’s no fundamental obstacle to implementing this.  The code for the AggregateCollection class is below.

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Threading;
using System.Collections;
using System.Collections.ObjectModel;
 
namespace AggCollectionTest
{
    public class AggregateCollection : IEnumerable, IList, INotifyCollectionChanged
    {
        private IEnumerable[] lists;
        private List<Object> cache = new List<Object>();
        private Boolean cacheValid = false;
        private Object syncRoot = new Object();
 
        public AggregateCollection(params IEnumerable[] lists)
        {
            if (lists == null) throw new ArgumentNullException("lists");
 
            this.lists = lists;
            foreach (IEnumerable list in lists)
            {
                if (list is INotifyCollectionChanged)
                {
                    ((INotifyCollectionChanged)list).CollectionChanged +=
                        delegate(Object sender, NotifyCollectionChangedEventArgs e)
                        {
                            cacheValid = false;
                            NotifyCollectionChangedEventArgs eventArgs;
                            switch (e.Action)
                            {
                                case NotifyCollectionChangedAction.Add:
                                    eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,
                                        e.NewItems, DetermineIndexInAggregateCollection(sender as IEnumerable, e.NewStartingIndex));
                                    break;
                                case NotifyCollectionChangedAction.Move:
                                    eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move,
                                        e.NewItems, DetermineIndexInAggregateCollection(sender as IEnumerable, e.NewStartingIndex),
                                        DetermineIndexInAggregateCollection(sender as IEnumerable, e.OldStartingIndex));
                                    break;
                                case NotifyCollectionChangedAction.Remove:
                                    eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
                                        e.OldItems, DetermineIndexInAggregateCollection(sender as IEnumerable, e.OldStartingIndex));
                                    break;
                                case NotifyCollectionChangedAction.Replace:
                                    eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
                                        e.NewItems, e.OldItems, DetermineIndexInAggregateCollection(sender as IEnumerable, e.OldStartingIndex));
                                    break;
                                default:
                                case NotifyCollectionChangedAction.Reset:
                                    eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                                    break;
                            }
 
                            OnCollectionChanged(eventArgs);
                        };
                }
            }
        }
 
        private int DetermineIndexInAggregateCollection(IEnumerable list, int index)
        {
            if(list == null) throw new ArgumentNullException("list");
            if (index == -1) return -1;
 
            Int32 adjustedIndex = -1;
            for (int i = 0; i < lists.Length; i++)
            {
                IEnumerable l = lists[i];
                if (l == list) { break; }
                    else adjustedIndex++;
 
                foreach (Object o in l)
                    adjustedIndex++;
            }
 
            return list == lists[0] ? adjustedIndex + index + 1 :
                adjustedIndex + index;
        }
 
        #region IEnumerable Members
 
        public IEnumerator GetEnumerator()
        {
            return new Enumerator(lists);
        }
 
        private class Enumerator : IEnumerator
        {
            private Int32 listIndex = -1;
            private IEnumerator[] enumerators;
 
            public Enumerator(IEnumerable[] lists)
            {
                enumerators = new IEnumerator[lists.Length];
 
                for (int i = 0; i < lists.Length; i++) 
                    enumerators[i] = lists[i].GetEnumerator();
            }
 
            public object Current
            {
                get
                {
                    try
                    {
                        return enumerators[listIndex].Current;
                    }
                    catch (IndexOutOfRangeException)
                    {
                        throw new InvalidOperationException();
                    }
                }
            }
 
            public bool MoveNext()
            {
 
                if (listIndex > -1 && listIndex < enumerators.Length && 
                    enumerators[listIndex].MoveNext())
                {
                    return true;
                }
                else
                {
                    listIndex++;
                    if (listIndex < enumerators.Length)
                    {
                        try
                        {
                            enumerators[listIndex].Reset();
                        }
                        catch (InvalidOperationException)
                        {
                            return false;
                        }
 
                        return MoveNext();
                    }
                    else return false;
                }
            }
 
            public void Reset()
            {
                listIndex = -1;
            }
        }
 
        #endregion
 
        #region INotifyCollectionChanged Members
 
        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (e == null) throw new ArgumentNullException("e");
 
            if (CollectionChanged != null) 
                CollectionChanged(this, e);
        }
 
        public event NotifyCollectionChangedEventHandler CollectionChanged;
 
        #endregion
 
        #region IList Members
 
        public int Add(object value)
        {
            throw new NotSupportedException();
        }
 
        public void Clear()
        {
            throw new NotSupportedException();
        }
 
        public bool Contains(object value)
        {
            return IndexOf(value) != -1;
        }
 
        public int IndexOf(object value)
        {
            Int32 i = -1;
            foreach (Object o in this)
            {
                i++;
                if ((value == null && o == null) | (value != null && value.Equals(o)))
                {
                    return i;
                }
            }
 
            return -1;
        }
 
        public void Insert(int index, object value)
        {
            throw new NotSupportedException();
        }
 
        public bool IsFixedSize
        {
            get { return false; }
        }
 
        public bool IsReadOnly
        {
            get { return true; }
        }
 
        public void Remove(object value)
        {
            throw new NotSupportedException();
        }
 
        public void RemoveAt(int index)
        {
            throw new NotSupportedException();
        }
 
        public object this[int index]
        {
            get
            {
                if (!cacheValid)
                {
                    cache.Clear();
                    foreach (Object o in this)
                    {
                        cache.Add(o);
                    }
                    cacheValid = true;
                }
 
                return cache[index];
            }
            set
            {
                throw new NotSupportedException();
            }
        }
 
        #endregion
 
        #region ICollection Members
 
        public void CopyTo(Array array, int index)
        {
            Int32 i = -1;
            foreach (Object o in this)
                array.SetValue(o, index + ++i);
        }
 
        public int Count
        {
            get
            {
                Int32 count = 0;
                foreach (Object o in this) count++;
                return count;
            }
        }
 
        public bool IsSynchronized
        {
            get { return false; }
        }
 
        public object SyncRoot
        {
            get { return syncRoot; }
        }
 
        #endregion
    }
}

Using a ContentControl and DataTemplate to indicate new and/or modified data March 6, 2007

Posted by Karl Hulme in .Net, C#, WPF, XAML.
13 comments

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!

I was familiar with using DataTemplate’s for multiple items, say for items in a ListBox, but what if you want to use a DataTemplate for a single item? 

Why would I want to do that?  I can declare the UIElements I want, and bind the properties that I want.  You don’t need a DataTemplate here!!

Well, not strictly true!  If you want to be able to use properties of your business object within conditions that control the layout, then you’re going to need the services of DataTrigger’s. And although most UIElements have a Triggers property, DataTriggers are only supported on a DataTemplate.  So this blog entry is an example of how to set that up…

 

Think about Visual Studio tabs for a second.  Whenever you change the content of a document, a small asterisk appears after the filename to indicate to you that it’s been changed, as illustrated below.

I wanted to do a similar thing in my application.  I wanted to..

  • Show an asterisk whenever the data has been changed since the last save, and
  • Show if an item was new (that is has never been saved.

For me, the second point is particularly important because my application deals with Customer entities, and until the customer is saved they don’t have the all-important customer number.

The first step is to add the required properties to the business object being bound to, and implement INotifyPropertyChanged so that we know when they’re changing.  A heavily abridged/shell-like version of my Entity class is below.  (Please note, the code below is only intended to highlight the need to define the properties and inplement INotifyPropertyChanged on the business object in use.)

using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; namespace MyNamespace { /// <summary> /// Represents an entity loaded from the database. /// </summary> public class Entity: INotifyPropertyChanged { #region properties private Int32? id; /// <summary> /// Gets the unique id of the entity. /// </summary> public Int32? Id { get { return id; } internal set { id = value; OnPropertyChanged("Id"); OnPropertyChanged("IsNew"); } } private EntityDefinition definition; /// <summary> /// Gets the type of the entity. /// </summary> public EntityDefinition Definition { get { return definition; } } /// <summary> /// Gets a value that indicates if the entity has been modified /// since it was loaded. /// </summary> public Boolean IsModified { get { return changeHistory.Count > 0; } } private EntityDataFieldChangeCollection changeHistory = new EntityDataFieldChangeCollection(); public EntityDataFieldChangeCollection ChangeHistory { get { return changeHistory; } } #endregion #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(String propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion } }

Then in XAML, define a ContentControl and set the Content of that control to be an instance of your business object.  Clearly the ContentControl doesn’t know how to render said business object, so I’ve provided a DataTemplate.  This DataTemplate uses a horizontal StackPanel to lay out a series of TextBlock elements.  I then use the Triggers to turn the visibility on and off, dependent upon the IsNew and IsModified properties. 

<ContentControl Grid.Row="0" Content="{Binding Source={StaticResource entity}}"> <ContentControl.ContentTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Name="newBlock" Text="New " Visibility="Visible"/> <TextBlock Name="nameBlock" Text="{Binding Path=Definition.Name}" Visibility="Visible"/> <TextBlock Name="spacer" Text=" " Visibility="Collapsed"/> <TextBlock Name="idBlock" Text="{Binding Path=Id}" Visibility="Collapsed" /> <TextBlock Name="modifiedBlock" Text="*" Visibility="Collapsed"/> </StackPanel> <DataTemplate.Triggers> <DataTrigger Binding="{Binding Path=IsNew}" Value="False"> <DataTrigger.Setters> <Setter TargetName="newBlock" Property="UIElement.Visibility" Value="Collapsed" /> <Setter TargetName="idBlock" Property="UIElement.Visibility" Value="Visible" /> <Setter TargetName="spacer" Property="UIElement.Visibility" Value="Visible" /> </DataTrigger.Setters> </DataTrigger> <DataTrigger Binding="{Binding Path=IsModified}" Value="True"> <DataTrigger.Setters> <Setter TargetName="modifiedBlock" Property="UIElement.Visibility" Value="Visible" /> </DataTrigger.Setters> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </ContentControl.ContentTemplate> </ContentControl>

So the tab headers throughout the life of one of my Customer entities would start out as follows…

Then, I would expect something to be changed…

Once the first set of changes can be made, it should be saved.  This will result in an Id being returned from the database…

Obviously the data could be modified again at this point…

I originally implemented this feature using IValueConverters (and the like) but I believe the XAML solution is much nicer. 

So if you need to display information related to a business object and you have some conditions associated with the display, then you could try using a data-templated content control.

SynchronizedObservableCollection and BindableCollection March 4, 2007

Posted by Karl Hulme in .Net, WPF.
25 comments

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!

Yesterday I posted some code that marshals the events raised by an ObservableCollection onto the same thread as is running the UI layer.  I called it a SynchronizedObservableCollection but said at the end of the post that I was unhappy with the name.  Well, while continuing to work on my project I’ve found need for a genuinely synchronized version of the ObservableCollection class. I also need a ReadOnly version – predominately as a base class so that I can add the methods I want for altering the collection, rather than the standard Add/Remove.  On top of these two, I still need to be able to bind them both to controls in WPF and not worry about which thread is causing the collections to change.

Therefore, the classes I need are:

  • SynchronizedObservableCollection
  • ReadOnlySynchronizedObservableCollection
  • BindableCollection

The SynchronizedObservableCollection is a collection that will accept updates and changes from lot’s of different threads without complaining.  It’s implemented the same way as the SynchronizedCollection class provided in the framework, e.g. you can provide your own syncRoot or one will be created.  You can also provide an existing list if you wish, but this is not a wrapper class, so if you do pass a list to the constructor it will be copied and any changes made to one list will not be reflected in the other.

[ComVisible(false)] public class SynchronizedObservableCollection<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable, INotifyPropertyChanged, INotifyCollectionChanged { private ObservableCollection<T> items; private Object sync; #region Constructors public SynchronizedObservableCollection(Object syncRoot, IEnumerable<T> list) { this.sync = (syncRoot == null) ? new Object() : syncRoot; this.items = (list == null) ? new ObservableCollection<T>() : new ObservableCollection<T>(new List<T>(list)); items.CollectionChanged += delegate(Object sender, NotifyCollectionChangedEventArgs e) { OnCollectionChanged(e); }; INotifyPropertyChanged propertyChangedInterface = items as INotifyPropertyChanged; propertyChangedInterface.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs e) { OnPropertyChanged(e); }; } public SynchronizedObservableCollection(object syncRoot) : this(syncRoot, null) { } public SynchronizedObservableCollection(): this(null, null) { } #endregion #region Methods public void Add(T item) { lock (this.sync) { int index = this.items.Count; this.InsertItem(index, item); } } public void Clear() { lock (this.sync) { this.ClearItems(); } } protected virtual void ClearItems() { this.items.Clear(); } public bool Contains(T item) { lock (this.sync) { return this.items.Contains(item); } } public void CopyTo(T[] array, int index) { lock (this.sync) { this.items.CopyTo(array, index); } } public IEnumerator<T> GetEnumerator() { lock (this.sync) { return this.items.GetEnumerator(); } } public int IndexOf(T item) { lock (this.sync) { return this.InternalIndexOf(item); } } public void Insert(int index, T item) { lock (this.sync) { if ((index < 0) || (index > this.items.Count)) { throw new ArgumentOutOfRangeException("index", index, "Value must be in range."); } this.InsertItem(index, item); } } protected virtual void InsertItem(int index, T item) { this.items.Insert(index, item); } private int InternalIndexOf(T item) { int count = this.items.Count; for (int i = 0; i < count; i++) { if (object.Equals(this.items[i], item)) { return i; } } return -1; } public bool Remove(T item) { lock (this.sync) { int index = this.InternalIndexOf(item); if (index < 0) { return false; } this.RemoveItem(index); return true; } } public void RemoveAt(int index) { lock (this.sync) { if ((index < 0) || (index >= this.items.Count)) { throw new ArgumentOutOfRangeException("index", index, "Value must be in range."); } this.RemoveItem(index); } } protected virtual void RemoveItem(int index) { this.items.RemoveAt(index); } protected virtual void SetItem(int index, T item) { this.items[index] = item; } void ICollection.CopyTo(Array array, int index) { lock (this.sync) { for (int i = 0; i < items.Count; i++) { array.SetValue(items[i], index + i); } } } IEnumerator IEnumerable.GetEnumerator() { return this.items.GetEnumerator(); } int IList.Add(object value) { VerifyValueType(value); lock (this.sync) { this.Add((T)value); return (this.Count - 1); } } bool IList.Contains(object value) { VerifyValueType(value); return this.Contains((T)value); } int IList.IndexOf(object value) { VerifyValueType(value); return this.IndexOf((T)value); } void IList.Insert(int index, object value) { VerifyValueType(value); this.Insert(index, (T)value); } void IList.Remove(object value) { VerifyValueType(value); this.Remove((T)value); } private static void VerifyValueType(object value) { if (value == null) { if (typeof(T).IsValueType) { throw new ArgumentException("Synchronized collection wrong type null."); } } else if (!(value is T)) { throw new ArgumentException("Synchronized collection wrong type."); } } #endregion #region Properties public int Count { get { lock (this.sync) { return this.items.Count; } } } public T this[int index] { get { lock (this.sync) { return this.items[index]; } } set { lock (this.sync) { if ((index < 0) || (index >= this.items.Count)) { throw new ArgumentOutOfRangeException("index", index, "Value must be in range."); } this.SetItem(index, value); } } } protected ObservableCollection<T> Items { get { return this.items; } } public object SyncRoot { get { return this.sync; } } bool ICollection<T>.IsReadOnly { get { return false; } } bool ICollection.IsSynchronized { get { return true; } } object ICollection.SyncRoot { get { return this.sync; } } bool IList.IsFixedSize { get { return false; } } bool IList.IsReadOnly { get { return false; } } object IList.this[int index] { get { return this[index]; } set { VerifyValueType(value); this[index] = (T)value; } } #endregion #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { if (PropertyChanged != null) PropertyChanged(this, e); } #endregion #region INotifyCollectionChanged Members public event NotifyCollectionChangedEventHandler CollectionChanged; protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (CollectionChanged != null) CollectionChanged(this, e); } #endregion }

The ReadOnlySynchronizedObservableCollection is a wrapper class that expects a SynchronizedObservableCollection as the only constructor parameter.  Because it’s a wrapper class, you could maintain a SynchronizedObservableCollection in one layer of your app, and just expose a ReadOnlySynchronizedObservableCollection to  the other layers.  Similarly, as a read only class you wouldn’t expect change events, hence the INotifyPropertyChanged and INotifyCollectionChanged interfaces are implemented explicitly (again following the pattern of the ReadOnlyObservableCollection).  Don’t forget you can always consume the events by casting the object to the interfaces of course, and for this reason, it doesn’t affect WPF binding. 

[ComVisible(false)] public class ReadOnlySynchronizedObservableCollection<T> : ReadOnlyCollection<T>, INotifyPropertyChanged, INotifyCollectionChanged { #region Constructor public ReadOnlySynchronizedObservableCollection(SynchronizedObservableCollection<T> list): base(list) { list.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs e) { OnPropertyChanged(e); }; list.CollectionChanged += delegate(Object sender, NotifyCollectionChangedEventArgs e) { OnCollectionChanged(e); }; } #endregion #region Event Handling private NotifyCollectionChangedEventHandler collectionChanged; private PropertyChangedEventHandler propertyChanged; protected event NotifyCollectionChangedEventHandler CollectionChanged { add { collectionChanged += value; } remove { collectionChanged -= value; } } protected event PropertyChangedEventHandler PropertyChanged { add { propertyChanged += value; } remove { propertyChanged -= value; } } event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged { add { collectionChanged += value; } remove { collectionChanged -= value; } } event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { add { propertyChanged += value; } remove { propertyChanged -= value; } } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { if (propertyChanged != null) { propertyChanged(this, e); } } protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (collectionChanged != null) { collectionChanged(this, e); } } #endregion }

Finally, the BindableCollection is the class I started building yesterday. This class can be bound to a WPF ItemsControl (or similar) and then be updated on any thread.  Among some minor coding changes, I’ve added support for the non-generic interfaces (e.g. IList as well as IList<T>) and I’ve allowed the Dispatcher to be passed in.  This is necessary if the BindableCollection is not created on the UI thread.  I’ve also dropped the word ‘Observable’ from the class name on the grounds that the class must be Observable if it is to be Bindable, making that bit superflous.  The BindableCollection is a wrapper class and will accept anything that implements IList<T>, INotifyCollectionChanged and INotifyPropertyChanged.  However, most commonly it will be fed an ObservableCollection, ReadOnlyObservableCollection, SynchronizedObservableCollection, ReadOnlySynchronizedObservableCollection or a class derived from one of these.

public class BindableCollection<T>: ICollection<T>, IList<T>, IEnumerable<T>, ICollection, IList, IEnumerable, INotifyCollectionChanged, INotifyPropertyChanged { private IList<T> list; private Dispatcher dispatcher; #region Constructor public BindableCollection(IList<T> list) : this(list, null) { } public BindableCollection(IList<T> list, Dispatcher dispatcher) { if (list == null || list as INotifyCollectionChanged == null || list as INotifyPropertyChanged == null) { throw new ArgumentNullException("The list must support IList, INotifyCollectionChanged " + "and INotifyPropertyChanged."); } this.list = list; this.dispatcher = (dispatcher == null) ? Dispatcher.CurrentDispatcher : dispatcher; INotifyCollectionChanged collectionChanged = list as INotifyCollectionChanged; collectionChanged.CollectionChanged += delegate(Object sender, NotifyCollectionChangedEventArgs e) { this.dispatcher.Invoke(DispatcherPriority.Normal, new RaiseCollectionChangedEventHandler(RaiseCollectionChangedEvent), e); }; INotifyPropertyChanged propertyChanged = list as INotifyPropertyChanged; propertyChanged.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs e) { this.dispatcher.Invoke(DispatcherPriority.Normal, new RaisePropertyChangedEventHandler(RaisePropertyChangedEvent), e); }; } #endregion #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) { list.Add(item); } public void Clear() { list.Clear(); } public bool Contains(T item) { return list.Contains(item); } public void CopyTo(T[] array, int arrayIndex) { list.CopyTo(array, arrayIndex); } public int Count { get { return list.Count; } } public bool IsReadOnly { get { return list.IsReadOnly; } } public bool Remove(T item) { return list.Remove(item); } #endregion #region IList<T> Members public int IndexOf(T item) { return list.IndexOf(item); } public void Insert(int index, T item) { list.Insert(index, item); } public void RemoveAt(int index) { list.RemoveAt(index); } public T this[int index] { get { return list[index]; } set { list[index] = value; } } #endregion #region IEnumerable<T> Members public IEnumerator<T> GetEnumerator() { return list.GetEnumerator(); } #endregion #region ICollection Members void ICollection.CopyTo(Array array, int index) { ((ICollection)list).CopyTo(array, index); } int ICollection.Count { get { return ((ICollection)list).Count; } } bool ICollection.IsSynchronized { get { return ((ICollection)list).IsSynchronized; } } object ICollection.SyncRoot { get { return ((ICollection)list).SyncRoot; } } #endregion #region IList Members int IList.Add(object value) { return ((IList)list).Add(value); } void IList.Clear() { ((IList)list).Clear(); } bool IList.Contains(object value) { return ((IList)list).Contains(value); } int IList.IndexOf(object value) { return ((IList)list).IndexOf(value); } void IList.Insert(int index, object value) { ((IList)list).Insert(index, value); } bool IList.IsFixedSize { get { return ((IList)list).IsFixedSize; } } bool IList.IsReadOnly { get { return ((IList)list).IsReadOnly; } } void IList.Remove(object value) { ((IList)list).Remove(value); } void IList.RemoveAt(int index) { ((IList)list).RemoveAt(index); } object IList.this[int index] { get { return ((IList)list)[index]; } set { ((IList)list)[index] = value; } } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((System.Collections.IEnumerable)list).GetEnumerator(); } #endregion }

If anyone can make use of the above classes then you’re very welcome to use them, although you do so at your own risk! 

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

Posted by Karl Hulme in .Net, WPF.
10 comments

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!