// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace Nova.Utilities
{
/// <summary>
/// This is a version of the <see cref="ObservableCollection{T}"/> class that does invokes if necessary
/// so that any changes by a background thread are synchronously marshalled to the main UI thread in order
/// to avoid various threading issues.
/// </summary>
public class ObservableCollectionMT<T> : IList<T>, ICollection, INotifyCollectionChanged
{
protected IList<T> _collection = new List<T>();
protected readonly Dispatcher _dispatcher;
protected ReaderWriterLockSlim _readerWriterLock = new ReaderWriterLockSlim();
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>
/// Create a new instance, making certain that it's created on the UI thread.
/// </summary>
/// <param name="_collection">An existing _collection to be copied into the new one.</param>
/// <returns>The new <see cref="ObservableCollectionMT{T}"/>.</returns>
public static ObservableCollectionMT<T> Create(IEnumerable _collection)
{
Dispatcher _dispatcher = (Application.Current != null ? Application.Current.Dispatcher : Dispatcher.CurrentDispatcher);
if (_dispatcher == null || _dispatcher.CheckAccess())
return new ObservableCollectionMT<T>(_collection);
return (ObservableCollectionMT<T>)_dispatcher.Invoke(DispatcherPriority.Send, new Func<ObservableCollectionMT<T>>(delegate { return new ObservableCollectionMT<T>(_collection); }));
}
/// <summary>
/// Create a new instance, making certain that it's created on the UI thread.
/// </summary>
/// <returns>The new <see cref="ObservableCollectionMT{T}"/>.</returns>
public static ObservableCollectionMT<T> Create()
{
return Create((IEnumerable)null);
}
/// <summary>
/// Create a new instance, as a thread-safe UI-bindable wrapper around the specified <see cref="ObservableCollection{T}"/>.
/// </summary>
/// <param name="_collection">The existing <see cref="ObservableCollection{T}"/> to be wrapped.</param>
/// <returns>The new <see cref="ObservableCollectionMT{T}"/>.</returns>
public static ObservableCollectionMT<T> Create(ObservableCollection<T> _collection)
{
Dispatcher _dispatcher = (Application.Current != null ? Application.Current.Dispatcher : Dispatcher.CurrentDispatcher);
if (_dispatcher == null || _dispatcher.CheckAccess())
return new ObservableCollectionMT<T>(_collection);
return (ObservableCollectionMT<T>)_dispatcher.Invoke(DispatcherPriority.Send, new Func<ObservableCollectionMT<T>>(delegate { return new ObservableCollectionMT<T>(_collection); }));
}
/// <summary>
/// Create a new instance - this method is protected for internal use, to force the use of <see cref="Create(IEnumerable)"/> instead.
/// </summary>
protected ObservableCollectionMT(IEnumerable _collection)
{
_dispatcher = (Application.Current != null ? Application.Current.Dispatcher : null);
if (_collection != null)
{
foreach (T item in _collection)
Add(item);
}
}
/// <summary>
/// Create a new instance - this method is protected for internal use, to force the use of <see cref="Create(IEnumerable)"/> instead.
/// </summary>
protected ObservableCollectionMT()
: this(null)
{ }
/// <summary>
/// Create a new wrapper instance - this method is protected for internal use, to force the use of <see cref="Create(ObservableCollection{T})"/> instead.
/// </summary>
protected ObservableCollectionMT(ObservableCollection<T> _collection)
{
_dispatcher = (Application.Current != null ? Application.Current.Dispatcher : null);
_collection.CollectionChanged += WrappedCollection_Changed;
}
/// <summary>
/// Handle any items added or removed from the wrapped <see cref="ObservableCollection{T}"/> _collection.
/// </summary>
protected void WrappedCollection_Changed(object obj, NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (object item in args.NewItems)
Add((T)item);
break;
case NotifyCollectionChangedAction.Remove:
foreach (object item in args.OldItems)
Remove((T)item);
break;
}
}
public void Add(T item)
{
if (_dispatcher.CheckAccess())
AddInternal(item);
else
_dispatcher.BeginInvoke((Action)(delegate { AddInternal(item); }));
}
private void AddInternal(T item)
{
_readerWriterLock.EnterWriteLock();
_collection.Add(item);
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
_readerWriterLock.ExitWriteLock();
}
public void Clear()
{
if (_dispatcher.CheckAccess())
ClearInternal();
else
_dispatcher.BeginInvoke((Action)(ClearInternal));
}
private void ClearInternal()
{
_readerWriterLock.EnterWriteLock();
_collection.Clear();
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
_readerWriterLock.ExitWriteLock();
}
public bool Contains(T item)
{
_readerWriterLock.EnterReadLock();
bool result = _collection.Contains(item);
_readerWriterLock.ExitReadLock();
return result;
}
public void CopyTo(T[] array, int index)
{
_readerWriterLock.EnterReadLock();
_collection.CopyTo(array, index);
_readerWriterLock.ExitReadLock();
}
public void CopyTo(Array array, int index)
{
_readerWriterLock.EnterReadLock();
((ICollection)_collection).CopyTo(array, index);
_readerWriterLock.ExitReadLock();
}
public int Count
{
get
{
_readerWriterLock.EnterReadLock();
int count = _collection.Count;
_readerWriterLock.ExitReadLock();
return count;
}
}
public bool IsSynchronized
{
get { return true; }
}
public object SyncRoot
{
get { return _readerWriterLock; }
}
public bool IsReadOnly
{
get { return _collection.IsReadOnly; }
}
public bool Remove(T item)
{
if (_dispatcher.CheckAccess())
return RemoveInternal(item);
DispatcherOperation operation = _dispatcher.BeginInvoke(new Func<T, bool>(RemoveInternal), item);
return (operation.Result != null && (bool)operation.Result);
}
private bool RemoveInternal(T item)
{
bool successful = false;
_readerWriterLock.EnterWriteLock();
int index = _collection.IndexOf(item);
if (index >= 0)
{
successful = _collection.Remove(item);
if (successful && CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
}
_readerWriterLock.ExitWriteLock();
return successful;
}
public IEnumerator<T> GetEnumerator()
{
return _collection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _collection.GetEnumerator();
}
public int IndexOf(T item)
{
_readerWriterLock.EnterReadLock();
int index = _collection.IndexOf(item);
_readerWriterLock.ExitReadLock();
return index;
}
public void Insert(int index, T item)
{
if (_dispatcher.CheckAccess())
InsertInternal(index, item);
else
_dispatcher.BeginInvoke((Action)(delegate { InsertInternal(index, item); }));
}
private void InsertInternal(int index, T item)
{
_readerWriterLock.EnterWriteLock();
_collection.Insert(index, item);
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
_readerWriterLock.ExitWriteLock();
}
public void RemoveAt(int index)
{
if (_dispatcher.CheckAccess())
RemoveAtInternal(index);
else
_dispatcher.BeginInvoke((Action)(delegate { RemoveAtInternal(index); }));
}
private void RemoveAtInternal(int index)
{
_readerWriterLock.EnterWriteLock();
if (index < _collection.Count)
{
_collection.RemoveAt(index);
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, index));
}
_readerWriterLock.ExitWriteLock();
}
public T this[int index]
{
get
{
_readerWriterLock.EnterReadLock();
T result = _collection[index];
_readerWriterLock.ExitReadLock();
return result;
}
set
{
_readerWriterLock.EnterWriteLock();
if (index < _collection.Count)
_collection[index] = value;
_readerWriterLock.ExitWriteLock();
}
}
protected void ExecuteOrBeginInvoke(Action action)
{
if (_dispatcher == null || _dispatcher.CheckAccess())
action();
else
_dispatcher.Invoke(action);
}
}
}