I’m probably posting this too early; I haven’t had a chance to extensively test it yet but I basically just locked every function down, and made any method that actually modifies the list run on the main thread so that notifications can be sent. It ought to work 🙂
class ObservableList<T> : IList<T>, INotifyCollectionChanged where T : INotifyPropertyChanged
{
private Dispatcher dispatcher;
private List<T> list;
private object sync;
public ObservableList(Dispatcher dispatcher = null)
{
this.dispatcher = dispatcher ?? Dispatcher.CurrentDispatcher;
this.list = new List<T>();
this.sync = new object();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
lock (sync)
{
if (CollectionChanged != null)
{
CollectionChanged(this, e);
}
}
}
public int IndexOf(T item)
{
lock (sync)
{
return list.IndexOf(item);
}
}
public void Insert(int index, T item)
{
if (dispatcher.CheckAccess())
{
lock (sync)
{
list.Insert(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}
}
else
{
dispatcher.Invoke(new Action<int, T>(Insert), DispatcherPriority.Send, index, item);
}
}
public void RemoveAt(int index)
{
if (dispatcher.CheckAccess())
{
lock (sync)
{
var item = list[index];
list.RemoveAt(index);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
}
}
else
{
dispatcher.Invoke(new Action<int>(RemoveAt), DispatcherPriority.Send, index);
}
}
public T this[int index]
{
get
{
lock (sync) { return list[index]; }
}
set
{
lock (sync) { list[index] = value; }
}
}
public void Add(T item)
{
if (dispatcher.CheckAccess())
{
lock (sync)
{
list.Add(item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
}
else
{
dispatcher.Invoke(new Action<T>(Add), DispatcherPriority.Send, item);
}
}
public void Clear()
{
if (dispatcher.CheckAccess())
{
lock (sync)
{
list.Clear();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
else
{
dispatcher.Invoke(new Action(Clear), DispatcherPriority.Send);
}
}
public bool Contains(T item)
{
lock (sync) { return list.Contains(item); }
}
public void CopyTo(T[] array, int arrayIndex)
{
lock (sync) { list.CopyTo(array, arrayIndex); }
}
public int Count
{
get { lock (sync) { return list.Count; } }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
if (dispatcher.CheckAccess())
{
lock (sync)
{
var index = list.IndexOf(item);
var result = list.Remove(item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
return result;
}
}
else
{
return (bool)dispatcher.Invoke(new Func<T, bool>(Remove), DispatcherPriority.Send, item);
}
}
public IEnumerator<T> GetEnumerator()
{
lock (sync)
{
return list.GetEnumerator();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
You can simply delete the “where T : INotifyPropertyChanged” if you don’t like that restriction, but I put it there so that you don’t forget that your objects should notify your controls that their properties have changed, so that the GUI gets refreshed properly.