#if FRAMEWORK20 using System; using System.Collections.Generic; using System.Text; using System.Collections; using System.Diagnostics; using System.Runtime.InteropServices; using System.Collections.ObjectModel; using System.ComponentModel; using System.Collections.Specialized; namespace DevComponents.Schedule.Model.Primitives { /// /// Represents custom collection with INotifyPropertyChanged and INotifyCollectionChanged interface support. /// /// [Serializable, DebuggerDisplay("Count = {Count}"), ComVisible(false)] public class CustomCollection : IList, ICollection, IEnumerable, IList, ICollection, IEnumerable, INotifyPropertyChanged, INotifyCollectionChanged { #region Events /// /// Occurs when property value has changed. /// public event PropertyChangedEventHandler PropertyChanged; #endregion #region Internal Implementation private SimpleMonitor _monitor; [NonSerialized] private object _syncRoot; private IList items; /// /// Creates new instance of object. /// public CustomCollection() { this._monitor = new SimpleMonitor(); this.items = new List(); } /// /// Creates new instance of object. /// /// List to initialize collection with. public CustomCollection(IList list) { if (list == null) { throw new ArgumentNullException("list"); } this._monitor = new SimpleMonitor(); this.items = list; } /// /// Add item to collection. /// /// Item to add. public void Add(T item) { if (this.items.IsReadOnly) { throw new NotSupportedException("collection is read-only"); } int count = this.items.Count; this.InsertItem(count, item); } /// /// Remove all items from collection. /// public void Clear() { if (this.items.IsReadOnly) { throw new NotSupportedException("collection is read-only"); } this.ClearItems(); } /// /// Remove all items from collection. /// protected virtual void ClearItems() { this.CheckReentrancy(); this.items.Clear(); OnPropertyChanged(new PropertyChangedEventArgs(this.CountString)); OnPropertyChanged(new PropertyChangedEventArgs(this.ItemString)); this.OnCollectionReset(); } /// /// Checks whether collection contains item. /// /// Item to look for. /// true if item is in collection. public bool Contains(T item) { OnCollectionReadAccess(); return this.items.Contains(item); } /// /// Copy collection to array. /// /// Array to copy to. /// Index to copy from. public void CopyTo(T[] array, int index) { OnCollectionReadAccess(); this.items.CopyTo(array, index); } /// /// Gets enumerator for collection. /// /// Enumerator. public IEnumerator GetEnumerator() { OnCollectionReadAccess(); return this.items.GetEnumerator(); } /// /// Returns index of an item. /// /// Reference to item. /// Index of item. public int IndexOf(T item) { OnCollectionReadAccess(); return this.items.IndexOf(item); } /// /// Insert item at specified location. /// /// Index to insert item in. /// Item to insert. public void Insert(int index, T item) { if ((index < 0) || (index > this.items.Count)) { throw new ArgumentOutOfRangeException("index"); } this.InsertItem(index, item); } /// /// Inserts item. /// /// Index to insert item at. /// Reference to item. protected virtual void InsertItem(int index, T item) { this.CheckReentrancy(); this.items.Insert(index, item); OnPropertyChanged(new PropertyChangedEventArgs(this.CountString)); this.OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index); } private static bool IsCompatibleObject(object value) { if (!(value is T) && ((value != null) || typeof(T).IsValueType)) { return false; } return true; } /// /// Removes item from collection. /// /// Item to remove. /// true if item was removed. public bool Remove(T item) { if (this.items.IsReadOnly) { throw new NotSupportedException("collection is read-only"); } int index = this.items.IndexOf(item); if (index < 0) { return false; } this.RemoveItem(index); return true; } /// /// Remove item at specified location. /// /// Index of item to remove. public void RemoveAt(int index) { if (this.items.IsReadOnly) { throw new NotSupportedException("collection is read-only"); } if ((index < 0) || (index >= this.items.Count)) { throw new ArgumentOutOfRangeException(); } this.RemoveItem(index); } /// /// Remove item at specified location. /// /// Index of item to remove. protected virtual void RemoveItem(int index) { this.CheckReentrancy(); object item = items[index]; this.items.RemoveAt(index); OnPropertyChanged(new PropertyChangedEventArgs(this.CountString)); this.OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, index); } /// /// Set item on location. /// /// Index /// Item to assign. protected virtual void SetItem(int index, T item) { this.CheckReentrancy(); T oldItem = items[index]; this.items[index] = item; OnPropertyChanged(new PropertyChangedEventArgs(this.CountString)); OnPropertyChanged(new PropertyChangedEventArgs(this.ItemString)); this.OnCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem, item, index); } void ICollection.CopyTo(Array array, int index) { this.CheckReentrancy(); OnCollectionReadAccess(); if (array == null) { throw new ArgumentNullException("array"); } if (array.Rank != 1) { throw new ArgumentException("Argument array.Rank multi-dimensional not supported"); } if (array.GetLowerBound(0) != 0) { throw new ArgumentException("Argument array non zero lower bound not supported"); } if (index < 0) { throw new ArgumentOutOfRangeException("index must be non-negative number"); } if ((array.Length - index) < this.Count) { throw new ArgumentException("array too small"); } T[] localArray = array as T[]; if (localArray != null) { this.items.CopyTo(localArray, index); } else { Type elementType = array.GetType().GetElementType(); Type c = typeof(T); if (!elementType.IsAssignableFrom(c) && !c.IsAssignableFrom(elementType)) { throw new ArgumentException("Argument array of invalid type"); } object[] objArray = array as object[]; if (objArray == null) { throw new ArgumentException("Argument array invalid type"); } int count = this.items.Count; try { for (int i = 0; i < count; i++) { objArray[index++] = this.items[i]; } } catch (ArrayTypeMismatchException) { throw new ArgumentException("Argument array invalid type"); } } } IEnumerator IEnumerable.GetEnumerator() { OnCollectionReadAccess(); return this.items.GetEnumerator(); } int IList.Add(object value) { if (this.items.IsReadOnly) { throw new NotSupportedException("collection is read-only"); } VerifyValueType(value); this.Add((T)value); return (this.Count - 1); } private static void VerifyValueType(object value) { if (!IsCompatibleObject(value)) { throw new ArgumentException("value is of wrong type"); } } bool IList.Contains(object value) { return (CustomCollection.IsCompatibleObject(value) && this.Contains((T)value)); } int IList.IndexOf(object value) { OnCollectionReadAccess(); if (CustomCollection.IsCompatibleObject(value)) { return this.IndexOf((T)value); } return -1; } void IList.Insert(int index, object value) { if (this.items.IsReadOnly) { throw new NotSupportedException("collection is read-only"); } VerifyValueType(value); this.Insert(index, (T)value); } void IList.Remove(object value) { if (this.items.IsReadOnly) { throw new NotSupportedException("collection is read-only"); } if (CustomCollection.IsCompatibleObject(value)) { this.Remove((T)value); } } /// /// Returns number of items in collection. /// public int Count { get { OnCollectionReadAccess(); return this.items.Count; } } /// /// Returns item at index. /// /// Index of item. /// Item at index. public T this[int index] { get { OnCollectionReadAccess(); return this.items[index]; } set { if (this.items.IsReadOnly) { throw new NotSupportedException("collection is read-only"); } if ((index < 0) || (index >= this.items.Count)) { throw new ArgumentOutOfRangeException(); } this.SetItem(index, value); } } /// /// Returns the IList interface for items in collection. /// protected IList Items { get { OnCollectionReadAccess(); return this.items; } } /// /// Returns items directly without checks. /// /// List of items. protected IList GetItemsDirect() { return this.items; } bool ICollection.IsReadOnly { get { return this.items.IsReadOnly; } } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { if (this._syncRoot == null) { ICollection items = this.items as ICollection; if (items != null) { this._syncRoot = items.SyncRoot; } else { System.Threading.Interlocked.CompareExchange(ref this._syncRoot, new object(), null); } } return this._syncRoot; } } bool IList.IsFixedSize { get { IList items = this.items as IList; return ((items != null) && items.IsFixedSize); } } bool IList.IsReadOnly { get { return this.items.IsReadOnly; } } object IList.this[int index] { get { OnCollectionReadAccess(); return this.items[index]; } set { VerifyValueType(value); this[index] = (T)value; } } /// /// Occurs when collection is read. /// protected virtual void OnCollectionReadAccess() { } /// /// Occurs when collection property has changed. /// /// Event arguments. protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { if (this.PropertyChanged != null) { this.PropertyChanged(this, e); } } private string CountString { get { return "Count"; } } private string ItemString { get { return "Item[]"; } } /// /// Blocks the collection re-entrancy. /// /// IDisposable to end re-entrancy protected IDisposable BlockReentrancy() { this._monitor.Enter(); return this._monitor; } /// /// Checks whether call creates re-entrancy. /// protected void CheckReentrancy() { if ((this._monitor.Busy && (this.CollectionChanged != null)) && (this.CollectionChanged.GetInvocationList().Length > 1)) { throw new InvalidOperationException("CustomCollectionReentrancyNotAllowed"); } } #region SimpleMonitor [Serializable] private class SimpleMonitor : IDisposable { // Fields private int _busyCount; // Methods public void Dispose() { this._busyCount--; } public void Enter() { this._busyCount++; } // Properties public bool Busy { get { return (this._busyCount > 0); } } } #endregion #endregion #region INotifyCollectionChanged Members /// /// Occurs when collection has changed. /// public event NotifyCollectionChangedEventHandler CollectionChanged; private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index) { this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index)); } private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index, int oldIndex) { this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex)); } private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) { this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index)); } private void OnCollectionReset() { this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// /// Called when collection has changed. /// /// Event arguments. protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (this.CollectionChanged != null) { using (this.BlockReentrancy()) { this.CollectionChanged(this, e); } } } #endregion } #region INotifyCollectionChanged /// /// Represents collection changed notification interface. /// public interface INotifyCollectionChanged { /// /// Occurs when collection changed. /// event NotifyCollectionChangedEventHandler CollectionChanged; } /// /// Defines change actions. /// public enum NotifyCollectionChangedAction { /// /// Items were added. /// Add, /// /// Items were removed. /// Remove, /// /// Items were replaced. /// Replace, /// /// Items were moved. /// Move, /// /// Collection was reset. /// Reset } /// /// Defines delegate for collection notification events. /// /// Event sender. /// Event arguments. public delegate void NotifyCollectionChangedEventHandler(object sender, NotifyCollectionChangedEventArgs e); /// /// Defines collection change notification event arguments. /// public class NotifyCollectionChangedEventArgs : EventArgs { #region Private Vars private NotifyCollectionChangedAction _action; private IList _newItems; private int _newStartingIndex; private IList _oldItems; private int _oldStartingIndex; #endregion /// /// Create new instance of object. /// /// Action public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action) { this._newStartingIndex = -1; this._oldStartingIndex = -1; if (action != NotifyCollectionChangedAction.Reset) { throw new ArgumentException("WrongActionForCtor"); } this.InitializeAdd(action, null, -1); } /// /// Creates new instance of object. /// /// Specifies action. /// List of changed items. public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems) { this._newStartingIndex = -1; this._oldStartingIndex = -1; if (((action != NotifyCollectionChangedAction.Add) && (action != NotifyCollectionChangedAction.Remove)) && (action != NotifyCollectionChangedAction.Reset)) { throw new ArgumentException("MustBeResetAddOrRemoveActionForCtor"); } if (action == NotifyCollectionChangedAction.Reset) { if (changedItems != null) { throw new ArgumentException("ResetActionRequiresNullItem"); } this.InitializeAdd(action, null, -1); } else { if (changedItems == null) { throw new ArgumentNullException("changedItems"); } this.InitializeAddOrRemove(action, changedItems, -1); } } /// /// Creates new instance of object. /// /// Specifies action. /// Item that was changed. public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem) { this._newStartingIndex = -1; this._oldStartingIndex = -1; if (((action != NotifyCollectionChangedAction.Add) && (action != NotifyCollectionChangedAction.Remove)) && (action != NotifyCollectionChangedAction.Reset)) { throw new ArgumentException("MustBeResetAddOrRemoveActionForCtor"); } if (action == NotifyCollectionChangedAction.Reset) { if (changedItem != null) { throw new ArgumentException("ResetActionRequiresNullItem"); } this.InitializeAdd(action, null, -1); } else { this.InitializeAddOrRemove(action, new object[] { changedItem }, -1); } } /// /// Creates new instance of object. /// /// Action. /// New items in collection. /// Old items in collection. public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList newItems, IList oldItems) { this._newStartingIndex = -1; this._oldStartingIndex = -1; if (action != NotifyCollectionChangedAction.Replace) { throw new ArgumentException("WrongActionForCtor"); } if (newItems == null) { throw new ArgumentNullException("newItems"); } if (oldItems == null) { throw new ArgumentNullException("oldItems"); } this.InitializeMoveOrReplace(action, newItems, oldItems, -1, -1); } /// /// Creates new instance of object. /// /// Action. /// List of changed items. /// Starting index of change. public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems, int startingIndex) { this._newStartingIndex = -1; this._oldStartingIndex = -1; if (((action != NotifyCollectionChangedAction.Add) && (action != NotifyCollectionChangedAction.Remove)) && (action != NotifyCollectionChangedAction.Reset)) { throw new ArgumentException("MustBeResetAddOrRemoveActionForCtor"); } if (action == NotifyCollectionChangedAction.Reset) { if (changedItems != null) { throw new ArgumentException("ResetActionRequiresNullItem"); } if (startingIndex != -1) { throw new ArgumentException("ResetActionRequiresIndexMinus1"); } this.InitializeAdd(action, null, -1); } else { if (changedItems == null) { throw new ArgumentNullException("changedItems"); } if (startingIndex < -1) { throw new ArgumentException("IndexCannotBeNegative"); } this.InitializeAddOrRemove(action, changedItems, startingIndex); } } /// /// Creates new instance of object. /// /// Action /// Changed item /// Index of change public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem, int index) { this._newStartingIndex = -1; this._oldStartingIndex = -1; if (((action != NotifyCollectionChangedAction.Add) && (action != NotifyCollectionChangedAction.Remove)) && (action != NotifyCollectionChangedAction.Reset)) { throw new ArgumentException("MustBeResetAddOrRemoveActionForCtor"); } if (action == NotifyCollectionChangedAction.Reset) { if (changedItem != null) { throw new ArgumentException("ResetActionRequiresNullItem"); } if (index != -1) { throw new ArgumentException("ResetActionRequiresIndexMinus1"); } this.InitializeAdd(action, null, -1); } else { this.InitializeAddOrRemove(action, new object[] { changedItem }, index); } } /// /// Creates new instance of object. /// /// Action /// New item /// Old item public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object newItem, object oldItem) { this._newStartingIndex = -1; this._oldStartingIndex = -1; if (action != NotifyCollectionChangedAction.Replace) { throw new ArgumentException("WrongActionForCtor"); } this.InitializeMoveOrReplace(action, new object[] { newItem }, new object[] { oldItem }, -1, -1); } /// /// Creates new instance of object. /// /// Action /// New items. /// Removed items. /// Starting index of change. public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList newItems, IList oldItems, int startingIndex) { this._newStartingIndex = -1; this._oldStartingIndex = -1; if (action != NotifyCollectionChangedAction.Replace) { throw new ArgumentException("WrongActionForCtor"); } if (newItems == null) { throw new ArgumentNullException("newItems"); } if (oldItems == null) { throw new ArgumentNullException("oldItems"); } this.InitializeMoveOrReplace(action, newItems, oldItems, startingIndex, startingIndex); } /// /// Creates new instance of object. /// /// Action /// Changed items /// New index /// Old index public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList changedItems, int index, int oldIndex) { this._newStartingIndex = -1; this._oldStartingIndex = -1; if (action != NotifyCollectionChangedAction.Move) { throw new ArgumentException("WrongActionForCtor"); } if (index < 0) { throw new ArgumentException("IndexCannotBeNegative"); } this.InitializeMoveOrReplace(action, changedItems, changedItems, index, oldIndex); } /// /// Creates new instance of object. /// /// Action /// Changed item /// New index /// Old index public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem, int index, int oldIndex) { this._newStartingIndex = -1; this._oldStartingIndex = -1; if (action != NotifyCollectionChangedAction.Move) { throw new ArgumentException("WrongActionForCtor"); } if (index < 0) { throw new ArgumentException("IndexCannotBeNegative"); } object[] newItems = new object[] { changedItem }; this.InitializeMoveOrReplace(action, newItems, newItems, index, oldIndex); } /// /// Creates new instance of object. /// /// Action. /// New item /// Old item /// New index public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object newItem, object oldItem, int index) { this._newStartingIndex = -1; this._oldStartingIndex = -1; if (action != NotifyCollectionChangedAction.Replace) { throw new ArgumentException("WrongActionForCtor"); } this.InitializeMoveOrReplace(action, new object[] { newItem }, new object[] { oldItem }, index, index); } private void InitializeAdd(NotifyCollectionChangedAction action, IList newItems, int newStartingIndex) { this._action = action; this._newItems = (newItems == null) ? null : ArrayList.ReadOnly(newItems); this._newStartingIndex = newStartingIndex; } private void InitializeAddOrRemove(NotifyCollectionChangedAction action, IList changedItems, int startingIndex) { if (action == NotifyCollectionChangedAction.Add) { this.InitializeAdd(action, changedItems, startingIndex); } else if (action == NotifyCollectionChangedAction.Remove) { this.InitializeRemove(action, changedItems, startingIndex); } else { //Invariant.Assert(false, "Unsupported action: {0}", action.ToString()); } } private void InitializeMoveOrReplace(NotifyCollectionChangedAction action, IList newItems, IList oldItems, int startingIndex, int oldStartingIndex) { this.InitializeAdd(action, newItems, startingIndex); this.InitializeRemove(action, oldItems, oldStartingIndex); } private void InitializeRemove(NotifyCollectionChangedAction action, IList oldItems, int oldStartingIndex) { this._action = action; this._oldItems = (oldItems == null) ? null : ArrayList.ReadOnly(oldItems); this._oldStartingIndex = oldStartingIndex; } /// /// Gets the type of the collection change action. /// public NotifyCollectionChangedAction Action { get { return this._action; } } /// /// Gets list of newly added items. /// public IList NewItems { get { return this._newItems; } } /// /// Gets new starting index. /// public int NewStartingIndex { get { return this._newStartingIndex; } } /// /// Gets list of removed items. /// public IList OldItems { get { return this._oldItems; } } /// /// Old starting index. /// public int OldStartingIndex { get { return this._oldStartingIndex; } } } #endregion } #endif