//The MIT License(MIT) //Copyright(c) 2016 Alberto Rodriguez & LiveCharts Contributors //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: //The above copyright notice and this permission notice shall be included in all //copies or substantial portions of the Software. //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //SOFTWARE. using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; namespace LiveCharts.Helpers { /// /// /// /// /// The old items. /// The new items. public delegate void NoisyCollectionCollectionChanged( IEnumerable oldItems, IEnumerable newItems); /// /// /// /// public interface INoisyCollection : IList, INotifyPropertyChanged, INotifyCollectionChanged { /// /// Occurs when [noisy collection changed]. /// event NoisyCollectionCollectionChanged NoisyCollectionChanged; /// /// Adds the range. /// /// The items. void AddRange(IEnumerable items); /// /// Inserts the range. /// /// The index. /// The collection. void InsertRange(int index, IEnumerable collection); } /// /// A collection that notifies every time a value is added or removed /// /// public class NoisyCollection : INoisyCollection, IList { #region Private Fields private readonly object _sync = new object(); private readonly List _source; private const string CountString = "Count"; private const string IndexerString = "Item[]"; #endregion #region Constructors /// /// Initializes a new instance of NoisyCollection class /// public NoisyCollection() { _source = new List(); } /// /// Initializes a new instance of NoisyCollection class with a given collection /// /// given collection public NoisyCollection(IEnumerable collection) { _source = new List(collection); } /// /// Initializes a new instance of NoisiCollection class with a given capacity /// /// given capacity public NoisyCollection(int capacity) { _source = new List(capacity); } #endregion #region Events /// /// Occurs when [collection reset]. /// public event Action CollectionReset; /// /// Occurs when [noisy collection changed]. /// event NoisyCollectionCollectionChanged INoisyCollection.NoisyCollectionChanged { add { NoisyCollectionChanged += value as NoisyCollectionCollectionChanged; } remove { NoisyCollectionChanged -= value as NoisyCollectionCollectionChanged; } } /// /// Occurs when [noisy collection changed]. /// public event NoisyCollectionCollectionChanged NoisyCollectionChanged; /// /// Occurs when the collection changes. /// public virtual event NotifyCollectionChangedEventHandler CollectionChanged; /// /// Occurs when a property value changes. /// protected virtual event PropertyChangedEventHandler PropertyChanged; /// /// Occurs when a property value changes. /// event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { add { PropertyChanged += value; } remove { PropertyChanged -= value; } } #endregion #region Properties /// /// Gets or sets an item from/in a specific index /// /// index to get/set /// public T this[int index] { get { lock (_sync) { return _source[index]; } } set { var original = this[index]; lock (_sync) { _source[index] = value; } ReplaceItem(original, value, index); } } /// /// Gets or sets an item from/in a specific index /// /// index to get/set /// object IList.this[int index] { get { lock (_sync) { return _source[index]; } } set { var original = this[index]; lock (_sync) { _source[index] = (T)value; } ReplaceItem(original, value, index); } } /// /// Enumerates the collection /// /// collection enumeration public IEnumerator GetEnumerator() { lock (_sync) { return new List(_source).GetEnumerator(); } } /// /// Enumerates the collection /// /// collection enumeration IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Gets the number of items in the array /// /// items count public int Count { get { return _source.Count; } } /// /// Gets whether the collection is read only /// /// result bool ICollection.IsReadOnly { get { return ((ICollection)_source).IsReadOnly; } } /// /// Gets the number of items in the array /// /// result bool IList.IsReadOnly { get { return ((IList)_source).IsReadOnly; } } /// /// Gets whether the collection is synchronized /// /// result public bool IsSynchronized { get { return ((ICollection)_source).IsSynchronized; } } /// /// Gets the collections's sync root /// public object SyncRoot { get { return ((ICollection)_source).SyncRoot; } } /// /// Gets whether the collection is fixed /// public bool IsFixedSize { get { return ((IList)_source).IsFixedSize; } } #endregion #region Public Methods /// /// Adds an object to the collection, and notifies the change /// /// item to add /// number of items in the collection int IList.Add(object value) { var v = (T)value; Add(v); lock (_sync) { return _source.IndexOf(v); } } /// /// Add an item to the collection, and notifies the change /// /// number of items in the collection public void Add(T item) { lock (_sync) { _source.Add(item); } OnNoisyCollectionChanged(null, new[] { item }); OnPropertyChanged(CountString); OnPropertyChanged(IndexerString); OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, item, _source.Count - 1)); } /// /// Adds many items to the collection, and notifies the change /// /// collection to add public void AddRange(IEnumerable items) { AddRange(items.Cast()); } /// /// Adds many items to the collection, and notifies the change /// /// collection to add public void AddRange(IEnumerable items) { var newItems = items as T[] ?? items.ToArray(); lock (_sync) { _source.AddRange(newItems); } OnNoisyCollectionChanged(null, newItems); OnPropertyChanged(CountString); OnPropertyChanged(IndexerString); //This scenario is not supported normally in ObservableCollections //in this case we'll send a reset action. OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset)); } /// /// Insert an item in a specific index, then notifies the change /// /// index to insert at /// item to insert public void Insert(int index, T item) { lock (_sync) { _source.Insert(index, item); } OnNoisyCollectionChanged(null, new[] { item }); OnPropertyChanged(CountString); OnPropertyChanged(IndexerString); OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, item, index)); } /// /// Insert an item in a specific index, then notifies the change /// /// index to insert at /// item to insert public void Insert(int index, object value) { Insert(index, (T)value); } /// /// Insert a range of values, starting in a specific index, then notifies the change /// /// index to start at /// collection to insert public void InsertRange(int index, IEnumerable collection) { InsertRange(index, collection.Cast()); } /// /// Insert a range of values, starting in a specific index, then notifies the change /// /// index to start at /// collection to insert public void InsertRange(int index, IEnumerable collection) { var newItems = collection as T[] ?? collection.ToArray(); lock (_sync) { _source.InsertRange(index, newItems); } OnNoisyCollectionChanged(null, newItems); OnPropertyChanged(CountString); OnPropertyChanged(IndexerString); //This scenario is not supported normally in ObservableCollections //in this case we'll send a reset action. OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset)); } /// /// Removes an item from a collection, then notifies the change /// /// item to remove public void Remove(object value) { Remove((T)value); } /// /// Remove an item from a collection, then notifies the change /// /// item to remove /// number of items in the collection public bool Remove(T item) { int index; lock (_sync) { index = _source.IndexOf(item); } if (index < 0) return false; RemoveAt(index); return true; } /// /// Removes an item at a specific index, then notifies the change /// /// index to remove at void IList.RemoveAt(int index) { RemoveAt(index); } /// /// Removes an item at a specific index, then notifies the change /// /// index to remove at void IList.RemoveAt(int index) { RemoveAt(index); } /// /// Removes an item at a specific index, then notifies the change /// /// index to remove at public void RemoveAt(int index) { T item; lock (_sync) { item = _source[index]; _source.RemoveAt(index); } OnNoisyCollectionChanged(new[] { item }, null); OnPropertyChanged(CountString); OnPropertyChanged(IndexerString); OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Remove, item, index)); } /// /// Removes all the items from the collection, then notifies the change /// void IList.Clear() { Clear(); } /// /// Removes all the items from the collection, then notifies the change /// void ICollection.Clear() { Clear(); } /// /// Removes all the items from the collection, then notifies the change /// public void Clear() { T[] backup; lock (_sync) { backup = _source.ToArray(); _source.Clear(); } OnNoisyCollectionChanged(backup, null); OnNoisyCollectionReset(); OnPropertyChanged(CountString); OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset)); } /// /// Evaluates whether an item is in this collection /// /// object to look for /// evaluation public bool Contains(object value) { return Contains((T)value); } /// /// Evaluates whether an item is in this collection /// /// item to look for /// evaluation public bool Contains(T item) { lock (_sync) { return _source.Contains(item); } } /// /// Copies the collection to another array /// /// backup array /// array index public void CopyTo(Array array, int index) { CopyTo(array.Cast().ToArray(), index); } /// /// Copies the collection to another array /// /// backup array /// array index public void CopyTo(T[] array, int index) { lock (_sync) { _source.CopyTo(array, index); } } /// /// Returns the index of an item in the collection /// /// item to look for /// public int IndexOf(object value) { return IndexOf((T)value); } /// /// Returns the index of an item in the collection /// /// item to look for /// public int IndexOf(T item) { lock (_sync) { return _source.IndexOf(item); } } #endregion #region Protected Methods /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, e); } } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (CollectionChanged != null) { CollectionChanged.Invoke(this, e); } } #endregion #region Private Methods private void OnNoisyCollectionChanged(IEnumerable olditems, IEnumerable newItems) { if (NoisyCollectionChanged != null) NoisyCollectionChanged.Invoke(olditems, newItems); } private void OnNoisyCollectionReset() { if (CollectionReset != null) CollectionReset.Invoke(); } private void OnPropertyChanged(string propertyName) { OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); } private void ReplaceItem(object original, object item, int index) { OnPropertyChanged(IndexerString); OnNoisyCollectionChanged(new List { (T)original }, new List { (T)item }); OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Replace, original, item, index)); } #endregion } }