The ObservableCollection class has pretty much everything I needed with the exception that the collection can only be modified from the thread that owns it. So I started with that and looked to see what made it tick and it was there that I found what I needed - INotifyCollectionChanged.
INotifyCollectionChanged, when implemented in a class, provides notifications that a collection has changed and allows to UI refresh accordingly. The trick I used to make this multithread friendly is to pass the UI Dispatcher to the ObserableList constructor when creating the object. This allows us to Invoke the CollectionChanged event from INotifyCollectionChanged on the UI thread when working on another thread. This is particularly handy, for example, for when a change to the collection is made in an asyncronous callback method.
For my solution I decided to go with a List as my collection base, implementing IList to provide the methods to work with the collection. And thus, the ObservableList is born.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace MotoCommander
{
public class ObservableList<T> : IList<T>, INotifyCollectionChanged
{
#region INotifyCollectionChanged
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void NotifyCollectionChanged(NotifyCollectionChangedAction pAction, object pItem)
{
if (CollectionChanged != null)
{
if (Thread.CurrentThread == _Dispatcher.Thread)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(pAction, pItem));
else
_Dispatcher.Invoke(new Action(() => CollectionChanged(this, new NotifyCollectionChangedEventArgs(pAction, pItem))));
}
}
private void NotifyCollectionChanged(NotifyCollectionChangedAction pAction, object pItem, int piIndex)
{
if (CollectionChanged != null)
if (Thread.CurrentThread == _Dispatcher.Thread)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(pAction, pItem, piIndex));
else
_Dispatcher.Invoke(new Action(() => CollectionChanged(this, new NotifyCollectionChangedEventArgs(pAction, pItem, piIndex))));
}
private void NotifyCollectionChanged(NotifyCollectionChangedAction pAction, IList<T> pList)
{
if (CollectionChanged != null)
if (Thread.CurrentThread == _Dispatcher.Thread)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(pAction, pList));
else
_Dispatcher.Invoke(new Action(() => CollectionChanged(this, new NotifyCollectionChangedEventArgs(pAction, pList))));
}
private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs args)
{
if (CollectionChanged != null)
if (Thread.CurrentThread == _Dispatcher.Thread)
CollectionChanged(this, args);
else
_Dispatcher.Invoke(new Action(() => CollectionChanged(this, args)));
}
private void NotifyCollectionChanged(NotifyCollectionChangedAction pAction)
{
if (CollectionChanged != null)
if (Thread.CurrentThread == _Dispatcher.Thread)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(pAction));
else
_Dispatcher.Invoke(new Action(() => CollectionChanged(this, new NotifyCollectionChangedEventArgs(pAction))));
}
#endregion
#region IEnumerable
public IEnumerator<T> GetEnumerator()
{
foreach (var item in _List)
{
if (item == null)
break;
yield return item;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
#region Class Declarations
private List<T> _List = new List<T>();
private Dispatcher _Dispatcher = null;
#endregion
#region Constructor
public ObservableList(Dispatcher pDispatcher)
{
_Dispatcher = pDispatcher;
}
public ObservableList(Dispatcher pDispatcher, List<T> pList)
{
_Dispatcher = pDispatcher;
foreach (var item in pList)
{
_List.Add(item);
}
}
#endregion
#region IList Members
public int IndexOf(T item)
{
return _List.IndexOf(item);
}
public void Insert(int index, T item)
{
_List.Insert(index, item);
NotifyCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
}
public void RemoveAt(int index)
{
if (index > -1)
{
_List.RemoveAt(index);
NotifyCollectionChanged(NotifyCollectionChangedAction.Remove, _List[index], index);
}
}
public T this[int index]
{
get
{
if (index > -1 && _List.Count > 0)
return _List.ElementAt(index);
else
return default(T);
}
set
{
if (index > -1)
{
if (index > _List.Count)
{
_List.Add(value);
NotifyCollectionChanged(NotifyCollectionChangedAction.Add, value);
}
else
{
_List[index] = value;
NotifyCollectionChanged(NotifyCollectionChangedAction.Replace, value, index);
}
}
}
}
public void Add(T item)
{
if (item != null)
{
_List.Add(item);
NotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
}
}
public void Clear()
{
_List.Clear();
NotifyCollectionChanged(NotifyCollectionChangedAction.Reset);
}
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 false; }
}
public bool Remove(T item)
{
bool bReturn = false;
if (item != null)
{
int index = _List.IndexOf(item);
if (index > -1)
{
bReturn = _List.Remove(item);
if (bReturn)
NotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item, index);
}
}
return bReturn;
}
#endregion
#region Conversions
//public static implicit operator ObservableList<T>(List<T> list)
//{
// ObservableList<T> lReturn = new ObservableList<T>();
// foreach (var item in list)
// {
// lReturn.Add(item);
// }
// return lReturn;
//}
#endregion
}
public static class ObservableListExtensions
{
public static bool Exists<T>(this ObservableList<T> list, Predicate<T> match)
{
bool bReturn = false;
Parallel.ForEach(list, item =>
{
if (match(item))
{
bReturn = true;
return;
}
});
return bReturn;
}
}
}