using System; using System.Collections; using System.Collections.Generic; using System.Linq; using JD.Collections.Extensions; using JD.Collections.Interfaces; using JD.Kernel.StaticHelpers; namespace JD.Collections { /// /// A Keyed collection that you can listen to through the CollectionChanged event. Differs from keyed collection in the fact the items are not stored in a list and in the collection. /// public abstract class ObservableKeyedCollection : IObservableKeyedCollection { #region Variables private readonly InnerCollection m_collection; private int m_updateLevel; #endregion #region Constructor /// /// Ctor /// protected ObservableKeyedCollection() : this(null) { } /// /// Override default key Comparer /// /// protected ObservableKeyedCollection(IEqualityComparer comparer) { m_collection = new InnerCollection(this, comparer); } #endregion #region Methods /// /// Get value from key. /// /// /// /// Does not throw when the key is not contained. public virtual TItem this[TKey key] { get { TItem value; m_collection.TryGetValue(key, out value); return value; } } /// /// Removes the item with the given . /// /// /// public bool Remove(TKey key) { TItem value; if (m_collection.TryGetValue(key, out value)) { return RemoveItem(value); } return false; } /// /// When implemented in a derived class, extracts the key from the specified element. /// /// /// protected abstract TKey GetKeyForItem(TItem value); /// /// Whether the keyed collection contains the key. /// /// /// public virtual bool ContainsKey(TKey key) { return m_collection.ContainsKey(key); } /// /// Adds the item in the list. /// /// public void Add(TItem item) { Throw.IfNull(item, "item"); InsertItem(item); RegisterItem(item); OnCollectionChanged(new CollectionChangedEventArgs(CollectionChangeAction.ItemAdded, item)); } protected virtual void InsertItem(TItem item) { m_collection.Add(item); } public bool Remove(TItem item) { return RemoveItem(item); } protected virtual bool RemoveItem(TItem item) { bool removed = m_collection.Remove(item); if (removed) { UnregisterItem(item); OnCollectionChanged(new CollectionChangedEventArgs(CollectionChangeAction.ItemRemoved, item)); } return removed; } /// /// Clears the list. /// public virtual void Clear() { if (Count == 0) { return; } OnCollectionChanged(new CollectionChangedEventArgs(CollectionChangeAction.Clearing, default(TItem))); m_collection.Clear(); OnCollectionChanged(new CollectionChangedEventArgs(CollectionChangeAction.Cleared, default(TItem))); } /// /// True if it contains the item. /// /// /// public bool Contains(TItem item) { return !ReferenceEquals(item, null) && ContainsKey(GetKeyForItem(item)); } public void CopyTo(TItem[] array, int arrayIndex) { Enumerable.AsEnumerable(this).CopyTo(array, arrayIndex); } public bool TryGetValue(TKey key, out TItem value) { return m_collection.TryGetValue(key, out value); } public int Count { get { return m_collection.Count; } } public bool IsReadOnly { get { throw new NotImplementedException(); } } #endregion #region ObservableCollectionTemplate /// /// REMARK : This code is copy pasted from ObservableCollectionTemplate /// /// Any modification should be applied to other observable collection. /// /// /// Whether this set is updating. /// public bool IsUpdating { get { return m_updateLevel > 0; } } /// /// Suspends all events for this set. /// public void BeginUpdate() { ++m_updateLevel; } /// /// Resumes events for this set. /// /// public bool EndUpdate() { if (--m_updateLevel == 0) { OnCollectionChanged(new CollectionChangedEventArgs(CollectionChangeAction.EndUpdated, default(TItem))); return true; } return false; } /// /// Register an item in the list (for example, can bind to its events) /// /// protected virtual void RegisterItem(TItem item) { IObservableItem observableItem = item as IObservableItem; if (observableItem != null) { observableItem.Changed += internal_itemChanged; } } /// /// Unregister an item from the list (for example, unbind from its events). /// /// protected virtual void UnregisterItem(TItem item) { IObservableItem observableItem = item as IObservableItem; if (observableItem != null) { observableItem.Changed -= internal_itemChanged; } } #region Handlers private void internal_itemChanged(object sender, EventArgs e) { if (sender is TItem) { OnCollectionChanged(new CollectionChangedEventArgs(CollectionChangeAction.ItemChanged, (TItem)sender)); } else { System.Diagnostics.Debug.Assert(false, "Bad implementation of the IObservableItem interface. The object should be the sender of the event args"); OnCollectionChanged(new CollectionChangedEventArgs(CollectionChangeAction.ItemChanged, default(TItem))); } } #endregion #region Events /// /// Triggered when the collection changes. /// public event EventHandler> CollectionChanged; /// /// Triggers the event. /// /// Event Args. protected virtual void OnCollectionChanged(CollectionChangedEventArgs e) { CollectionChanged?.Invoke(this, e); } #endregion #endregion #region Implementation of IEnumerable public virtual IEnumerator GetEnumerator() { return m_collection.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region Nested private class InnerCollection : KeyedCollection { private readonly ObservableKeyedCollection m_owner; public InnerCollection(ObservableKeyedCollection owner, IEqualityComparer comparer) : base(comparer) { m_owner = owner; } protected override TKey GetKeyForItem(TItem value) { return m_owner.GetKeyForItem(value); } } #endregion } }