I promised an alternative, so here is a good starting point:
http://www.deanchalk.me.uk/post/Thread-Safe-Dispatcher-Safe-Observable-Collection-for-WPF.aspx
It is custom implementation of
INotifyCollectionChanged
. As a bonus, it is thread-safe. :)
For serialization support, you have to replace
public event NotifyCollectionChangedEventHandler CollectionChanged;
with the following code:
[field: NonSerializedAttribute, XmlIgnore]
public event NotifyCollectionChangedEventHandler CollectionChanged;
For an option to disable notifications, just add
public bool IsCollectionNotificationDisabled { get; set; }
and replace all
if (CollectionChanged != null)
with
if (IsCollectionNotificationDisabled && CollectionChanged != null)
Complete source:
public class SafeObservable<T> : IList<T>, INotifyCollectionChanged
{
private IList<T> collection = new List<T>();
private Dispatcher dispatcher;
[field: NonSerializedAttribute, XmlIgnore]
public event NotifyCollectionChangedEventHandler CollectionChanged;
private ReaderWriterLock sync = new ReaderWriterLock();
public bool IsCollectionNotificationDisabled { get; set; }
public SafeObservable()
{
dispatcher = Dispatcher.CurrentDispatcher;
}
public void Add(T item)
{
if (Thread.CurrentThread == dispatcher.Thread)
DoAdd(item);
else
dispatcher.BeginInvoke((Action)(() => { DoAdd(item); }));
}
private void DoAdd(T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.Add(item);
if (IsCollectionNotificationDisabled && CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
sync.ReleaseWriterLock();
}
public void Clear()
{
if (Thread.CurrentThread == dispatcher.Thread)
DoClear();
else
dispatcher.BeginInvoke((Action)(() => { DoClear(); }));
}
private void DoClear()
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.Clear();
if (IsCollectionNotificationDisabled && CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
sync.ReleaseWriterLock();
}
public bool Contains(T item)
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection.Contains(item);
sync.ReleaseReaderLock();
return result;
}
public void CopyTo(T[] array, int arrayIndex)
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.CopyTo(array, arrayIndex);
sync.ReleaseWriterLock();
}
public int Count
{
get
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection.Count;
sync.ReleaseReaderLock();
return result;
}
}
public bool IsReadOnly
{
get { return collection.IsReadOnly; }
}
public bool Remove(T item)
{
if (Thread.CurrentThread == dispatcher.Thread)
return DoRemove(item);
else
{
var op = dispatcher.BeginInvoke(new Func<t,bool>(DoRemove), item);
if (op == null || op.Result == null)
return false;
return (bool)op.Result;
}
}
private bool DoRemove(T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
var index = collection.IndexOf(item);
if (index == -1)
{
sync.ReleaseWriterLock();
return false;
}
var result = collection.Remove(item);
if (result && CollectionChanged != null)
CollectionChanged(this, new
NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
sync.ReleaseWriterLock();
return result;
}
public IEnumerator<T> GetEnumerator()
{
return collection.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return collection.GetEnumerator();
}
public int IndexOf(T item)
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection.IndexOf(item);
sync.ReleaseReaderLock();
return result;
}
public void Insert(int index, T item)
{
if (Thread.CurrentThread == dispatcher.Thread)
DoInsert(index, item);
else
dispatcher.BeginInvoke((Action)(() => { DoInsert(index, item); }));
}
private void DoInsert(int index, T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.Insert(index, item);
if (IsCollectionNotificationDisabled && CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
sync.ReleaseWriterLock();
}
public void RemoveAt(int index)
{
if (Thread.CurrentThread == dispatcher.Thread)
DoRemoveAt(index);
else
dispatcher.BeginInvoke((Action)(() => { DoRemoveAt(index); }));
}
private void DoRemoveAt(int index)
{
sync.AcquireWriterLock(Timeout.Infinite);
if (collection.Count == 0 || collection.Count <= index)
{
sync.ReleaseWriterLock();
return;
}
collection.RemoveAt(index);
if (IsCollectionNotificationDisabled && CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
sync.ReleaseWriterLock();
}
public T this[int index]
{
get
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection[index];
sync.ReleaseReaderLock();
return result;
}
set
{
sync.AcquireWriterLock(Timeout.Infinite);
if (collection.Count == 0 || collection.Count <= index)
{
sync.ReleaseWriterLock();
return;
}
collection[index] = value;
sync.ReleaseWriterLock();
}
}
}