//---------------------------------------------------------------------------
//
//
// Copyright (C) 2003 by Microsoft Corporation. All rights reserved.
//
//
//
// Description: Implementation of an Collection implementing INotifyCollectionChanged
// to notify listeners of dynamic changes of the list.
//
// See spec at http://avalon/connecteddata/Specs/Collection%20Interfaces.mht
//
// History:
// 11/22/2004 : [....] - created
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace System.Collections.ObjectModel
{
///
/// Implementation of a dynamic data collection based on generic Collection<T>,
/// implementing INotifyCollectionChanged to notify listeners
/// when items get added, removed or the whole list is refreshed.
///
#if !FEATURE_NETCORE
[Serializable()]
#if !MOBILE
[TypeForwardedFrom("WindowsBase, Version=3.0.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
#endif
#endif
public class ObservableCollection : Collection, INotifyCollectionChanged, INotifyPropertyChanged
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
///
/// Initializes a new instance of ObservableCollection that is empty and has default initial capacity.
///
public ObservableCollection() : base() { }
///
/// Initializes a new instance of the ObservableCollection class
/// that contains elements copied from the specified list
///
/// The list whose elements are copied to the new list.
///
/// The elements are copied onto the ObservableCollection in the
/// same order they are read by the enumerator of the list.
///
/// list is a null reference
public ObservableCollection(List list)
: base((list != null) ? new List(list.Count) : list)
{
// Workaround for VSWhidbey bug 562681 (tracked by Windows bug 1369339).
// We should be able to simply call the base(list) ctor. But Collection
// doesn't copy the list (contrary to the documentation) - it uses the
// list directly as its storage. So we do the copying here.
//
CopyFrom(list);
}
///
/// Initializes a new instance of the ObservableCollection class that contains
/// elements copied from the specified collection and has sufficient capacity
/// to accommodate the number of elements copied.
///
/// The collection whose elements are copied to the new list.
///
/// The elements are copied onto the ObservableCollection in the
/// same order they are read by the enumerator of the collection.
///
/// collection is a null reference
public ObservableCollection(IEnumerable collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
CopyFrom(collection);
}
private void CopyFrom(IEnumerable collection)
{
IList items = Items;
if (collection != null && items != null)
{
using (IEnumerator enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
items.Add(enumerator.Current);
}
}
}
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
#region Public Methods
///
/// Move item at oldIndex to newIndex.
///
public void Move(int oldIndex, int newIndex)
{
MoveItem(oldIndex, newIndex);
}
#endregion Public Methods
//------------------------------------------------------
//
// Public Events
//
//------------------------------------------------------
#region Public Events
//------------------------------------------------------
#region INotifyPropertyChanged implementation
///
/// PropertyChanged event (per ).
///
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add
{
PropertyChanged += value;
}
remove
{
PropertyChanged -= value;
}
}
#endregion INotifyPropertyChanged implementation
//------------------------------------------------------
///
/// Occurs when the collection changes, either by adding or removing an item.
///
///
/// see
///
#if !FEATURE_NETCORE
[field:NonSerializedAttribute()]
#endif
public virtual event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion Public Events
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
#region Protected Methods
///
/// Called by base class Collection<T> when the list is being cleared;
/// raises a CollectionChanged event to any listeners.
///
protected override void ClearItems()
{
CheckReentrancy();
base.ClearItems();
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnCollectionReset();
}
///
/// Called by base class Collection<T> when an item is removed from list;
/// raises a CollectionChanged event to any listeners.
///
protected override void RemoveItem(int index)
{
CheckReentrancy();
T removedItem = this[index];
base.RemoveItem(index);
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index);
}
///
/// Called by base class Collection<T> when an item is added to list;
/// raises a CollectionChanged event to any listeners.
///
protected override void InsertItem(int index, T item)
{
CheckReentrancy();
base.InsertItem(index, item);
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
}
///
/// Called by base class Collection<T> when an item is set in list;
/// raises a CollectionChanged event to any listeners.
///
protected override void SetItem(int index, T item)
{
CheckReentrancy();
T originalItem = this[index];
base.SetItem(index, item);
OnPropertyChanged(IndexerName);
OnCollectionChanged(NotifyCollectionChangedAction.Replace, originalItem, item, index);
}
///
/// Called by base class ObservableCollection<T> when an item is to be moved within the list;
/// raises a CollectionChanged event to any listeners.
///
protected virtual void MoveItem(int oldIndex, int newIndex)
{
CheckReentrancy();
T removedItem = this[oldIndex];
base.RemoveItem(oldIndex);
base.InsertItem(newIndex, removedItem);
OnPropertyChanged(IndexerName);
OnCollectionChanged(NotifyCollectionChangedAction.Move, removedItem, newIndex, oldIndex);
}
///
/// Raises a PropertyChanged event (per ).
///
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
///
/// PropertyChanged event (per ).
///
#if !FEATURE_NETCORE
[field:NonSerializedAttribute()]
#endif
protected virtual event PropertyChangedEventHandler PropertyChanged;
///
/// Raise CollectionChanged event to any listeners.
/// Properties/methods modifying this ObservableCollection will raise
/// a collection changed event through this virtual method.
///
///
/// When overriding this method, either call its base implementation
/// or call to guard against reentrant collection changes.
///
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
{
using (BlockReentrancy())
{
CollectionChanged(this, e);
}
}
}
///
/// Disallow reentrant attempts to change this collection. E.g. a event handler
/// of the CollectionChanged event is not allowed to make changes to this collection.
///
///
/// typical usage is to wrap e.g. a OnCollectionChanged call with a using() scope:
///
/// using (BlockReentrancy())
/// {
/// CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, item, index));
/// }
///
///
protected IDisposable BlockReentrancy()
{
_monitor.Enter();
return _monitor;
}
/// Check and assert for reentrant attempts to change this collection.
/// raised when changing the collection
/// while another collection change is still being notified to other listeners
protected void CheckReentrancy()
{
if (_monitor.Busy)
{
// we can allow changes if there's only one listener - the problem
// only arises if reentrant changes make the original event args
// invalid for later listeners. This keeps existing code working
// (e.g. Selector.SelectedItems).
if ((CollectionChanged != null) && (CollectionChanged.GetInvocationList().Length > 1))
throw new InvalidOperationException(SR.GetString(SR.ObservableCollectionReentrancyNotAllowed));
}
}
#endregion Protected Methods
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
///
/// Helper to raise a PropertyChanged event />).
///
private void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
///
/// Helper to raise CollectionChanged event to any listeners
///
private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
}
///
/// Helper to raise CollectionChanged event to any listeners
///
private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index, int oldIndex)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex));
}
///
/// Helper to raise CollectionChanged event to any listeners
///
private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
}
///
/// Helper to raise CollectionChanged event with action == Reset to any listeners
///
private void OnCollectionReset()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Types
//
//------------------------------------------------------
#region Private Types
// this class helps prevent reentrant calls
#if !FEATURE_NETCORE
[Serializable()]
#if !MOBILE
[TypeForwardedFrom("WindowsBase, Version=3.0.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
#endif
#endif
private class SimpleMonitor : IDisposable
{
public void Enter()
{
++ _busyCount;
}
public void Dispose()
{
-- _busyCount;
}
public bool Busy { get { return _busyCount > 0; } }
int _busyCount;
}
#endregion Private Types
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private const string CountString = "Count";
// This must agree with Binding.IndexerName. It is declared separately
// here so as to avoid a dependency on PresentationFramework.dll.
private const string IndexerName = "Item[]";
private SimpleMonitor _monitor = new SimpleMonitor();
#endregion Private Fields
}
}