// using System; // using System.Collections; // using System.Collections.ObjectModel; // // namespace InABox.Avalonia // { // // Licensed to the .NET Foundation under one or more agreements. // // The .NET Foundation licenses this file to you under the MIT license. // // See the LICENSE file in the project root for more information. // // using System.Collections.Generic; // using System.Collections.Specialized; // using System.ComponentModel; // using System.Diagnostics; // using System.Linq; // // /// // /// 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. // /// // /// Modified to Implement Cross-thread Synchronization of events // /// Will trigger event callbacks on creating, rather than current thread. // /// Collections should be created on Msin UI thread the to prevent exceptions // /// // public class CoreObservableCollection : ObservableCollection // { // //------------------------------------------------------ // // // // Private Fields // // // //------------------------------------------------------ // // #region Private Fields // // [NonSerialized] private DeferredEventsCollection? _deferredEvents; // // [NonSerialized] private SynchronizationContext? _synchronizationContext = SynchronizationContext.Current; // // #endregion Private Fields // // // //------------------------------------------------------ // // // // Constructors // // // //------------------------------------------------------ // // #region Constructors // // /// // /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity. // /// // public CoreObservableCollection() // { // } // // /// // /// 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 CoreObservableCollection(IEnumerable collection) : base(collection) // { // } // // /// // /// 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 CoreObservableCollection(List list) : base(list) // { // } // // #endregion Constructors // // //------------------------------------------------------ // // // // Public Properties // // // //------------------------------------------------------ // // #region Public Properties // // EqualityComparer? _Comparer; // // public EqualityComparer Comparer // { // get => _Comparer ??= EqualityComparer.Default; // private set => _Comparer = value; // } // // /// // /// Gets or sets a value indicating whether this collection acts as a , // /// disallowing duplicate items, based on . // /// This might indeed consume background performance, but in the other hand, // /// it will pay off in UI performance as less required UI updates are required. // /// // public bool AllowDuplicates { get; set; } = true; // // // If the collection is created on a non-UI thread, but needs to update the UI, // // we should be able to set the UI context here to prevent exceptions // public SynchronizationContext? SynchronizationContext // { // get => _synchronizationContext; // set => _synchronizationContext = value; // } // // #endregion Public Properties // // //------------------------------------------------------ // // // // Public Methods // // // //------------------------------------------------------ // // #region Public Methods // // /// // /// Adds the elements of the specified collection to the end of the . // /// // /// // /// The collection whose elements should be added to the end of the . // /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. // /// // /// is null. // public void AddRange(IEnumerable collection) // { // InsertRange(Count, collection); // } // // /// // /// Inserts the elements of a collection into the at the specified index. // /// // /// The zero-based index at which the new elements should be inserted. // /// The collection whose elements should be inserted into the List. // /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. // /// is null. // /// is not in the collection range. // public void InsertRange(int index, IEnumerable collection) // { // if (collection == null) // throw new ArgumentNullException(nameof(collection)); // if (index < 0) // throw new ArgumentOutOfRangeException(nameof(index)); // if (index > Count) // throw new ArgumentOutOfRangeException(nameof(index)); // // if (!AllowDuplicates) // collection = // collection // .Distinct(Comparer) // .Where(item => !Items.Contains(item, Comparer)) // .ToList(); // // if (collection is ICollection countable) // { // if (countable.Count == 0) // return; // } // else if (!collection.Any()) // return; // // CheckReentrancy(); // // //expand the following couple of lines when adding more constructors. // var target = (List)Items; // target.InsertRange(index, collection); // // OnEssentialPropertiesChanged(); // // if (!(collection is IList list)) // list = new List(collection); // // OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, index)); // } // // // /// // /// Removes the first occurence of each item in the specified collection from the . // /// // /// The items to remove. // /// is null. // public void RemoveRange(IEnumerable collection) // { // if (collection == null) // throw new ArgumentNullException(nameof(collection)); // // if (Count == 0) // return; // else if (collection is ICollection countable) // { // if (countable.Count == 0) // return; // else if (countable.Count == 1) // using (IEnumerator enumerator = countable.GetEnumerator()) // { // enumerator.MoveNext(); // Remove(enumerator.Current); // return; // } // } // else if (!collection.Any()) // return; // // CheckReentrancy(); // // var clusters = new Dictionary>(); // var lastIndex = -1; // List? lastCluster = null; // foreach (T item in collection) // { // var index = IndexOf(item); // if (index < 0) // continue; // // Items.RemoveAt(index); // // if (lastIndex == index && lastCluster != null) // lastCluster.Add(item); // else // clusters[lastIndex = index] = lastCluster = new List { item }; // } // // OnEssentialPropertiesChanged(); // // if (Count == 0) // OnCollectionReset(); // else // foreach (KeyValuePair> cluster in clusters) // OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, // cluster.Value, cluster.Key)); // } // // /// // /// Iterates over the collection and removes all items that satisfy the specified match. // /// // /// The complexity is O(n). // /// // /// Returns the number of elements that where // /// is null. // public int RemoveAll(Predicate match) // { // return RemoveAll(0, Count, match); // } // // /// // /// Iterates over the specified range within the collection and removes all items that satisfy the specified match. // /// // /// The complexity is O(n). // /// The index of where to start performing the search. // /// The number of items to iterate on. // /// // /// Returns the number of elements that where // /// is out of range. // /// is out of range. // /// is null. // public int RemoveAll(int index, int count, Predicate match) // { // if (index < 0) // throw new ArgumentOutOfRangeException(nameof(index)); // if (count < 0) // throw new ArgumentOutOfRangeException(nameof(count)); // if (index + count > Count) // throw new ArgumentOutOfRangeException(nameof(index)); // if (match == null) // throw new ArgumentNullException(nameof(match)); // // if (Count == 0) // return 0; // // List? cluster = null; // var clusterIndex = -1; // var removedCount = 0; // // using (BlockReentrancy()) // using (DeferEvents()) // { // for (var i = 0; i < count; i++, index++) // { // T item = Items[index]; // if (match(item)) // { // Items.RemoveAt(index); // removedCount++; // // if (clusterIndex == index) // { // Debug.Assert(cluster != null); // cluster!.Add(item); // } // else // { // cluster = new List { item }; // clusterIndex = index; // } // // index--; // } // else if (clusterIndex > -1) // { // OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, // cluster, clusterIndex)); // clusterIndex = -1; // cluster = null; // } // } // // if (clusterIndex > -1) // OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, // cluster, clusterIndex)); // } // // if (removedCount > 0) // OnEssentialPropertiesChanged(); // // return removedCount; // } // // /// // /// Removes a range of elements from the >. // /// // /// The zero-based starting index of the range of elements to remove. // /// The number of elements to remove. // /// The specified range is exceeding the collection. // public void RemoveRange(int index, int count) // { // if (index < 0) // throw new ArgumentOutOfRangeException(nameof(index)); // if (count < 0) // throw new ArgumentOutOfRangeException(nameof(count)); // if (index + count > Count) // throw new ArgumentOutOfRangeException(nameof(index)); // // if (count == 0) // return; // // if (count == 1) // { // RemoveItem(index); // return; // } // // //Items will always be List, see constructors // var items = (List)Items; // List removedItems = items.GetRange(index, count); // // CheckReentrancy(); // // items.RemoveRange(index, count); // // OnEssentialPropertiesChanged(); // // if (Count == 0) // OnCollectionReset(); // else // OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, // removedItems, index)); // } // // /// // /// Clears the current collection and replaces it with the specified collection, // /// using . // /// // /// The items to fill the collection with, after clearing it. // /// is null. // public void ReplaceRange(IEnumerable collection) // { // ReplaceRange(0, Count, collection); // } // // /// // /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact. // /// // /// The index of where to start the replacement. // /// The number of items to be replaced. // /// The collection to insert in that location. // /// is out of range. // /// is out of range. // /// is null. // /// is null. // public void ReplaceRange(int index, int count, IEnumerable collection) // { // if (index < 0) // throw new ArgumentOutOfRangeException(nameof(index)); // if (count < 0) // throw new ArgumentOutOfRangeException(nameof(count)); // if (index + count > Count) // throw new ArgumentOutOfRangeException(nameof(index)); // // if (collection == null) // throw new ArgumentNullException(nameof(collection)); // // if (!AllowDuplicates) // collection = // collection // .Distinct(Comparer) // .ToList(); // // if (collection is ICollection countable) // { // if (countable.Count == 0) // { // RemoveRange(index, count); // return; // } // } // else if (!collection.Any()) // { // RemoveRange(index, count); // return; // } // // if (index + count == 0) // { // InsertRange(0, collection); // return; // } // // if (!(collection is IList list)) // list = new List(collection); // // using (BlockReentrancy()) // using (DeferEvents()) // { // var rangeCount = index + count; // var addedCount = list.Count; // // var changesMade = false; // List? // newCluster = null, // oldCluster = null; // // // int i = index; // for (; i < rangeCount && i - index < addedCount; i++) // { // //parallel position // T old = this[i], @new = list[i - index]; // if (Comparer.Equals(old, @new)) // { // OnRangeReplaced(i, newCluster!, oldCluster!); // continue; // } // else // { // Items[i] = @new; // // if (newCluster == null) // { // Debug.Assert(oldCluster == null); // newCluster = new List { @new }; // oldCluster = new List { old }; // } // else // { // newCluster.Add(@new); // oldCluster!.Add(old); // } // // changesMade = true; // } // } // // OnRangeReplaced(i, newCluster!, oldCluster!); // // //exceeding position // if (count != addedCount) // { // var items = (List)Items; // if (count > addedCount) // { // var removedCount = rangeCount - addedCount; // T[] removed = new T[removedCount]; // items.CopyTo(i, removed, 0, removed.Length); // items.RemoveRange(i, removedCount); // OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, // removed, i)); // } // else // { // var k = i - index; // T[] added = new T[addedCount - k]; // for (int j = k; j < addedCount; j++) // { // T @new = list[j]; // added[j - k] = @new; // } // // items.InsertRange(i, added); // OnCollectionChanged( // new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, i)); // } // // OnEssentialPropertiesChanged(); // } // else if (changesMade) // { // OnIndexerPropertyChanged(); // } // } // } // // #endregion Public Methods // // // //------------------------------------------------------ // // // // 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() // { // if (Count == 0) // return; // // CheckReentrancy(); // base.ClearItems(); // OnEssentialPropertiesChanged(); // OnCollectionReset(); // } // // /// // protected override void InsertItem(int index, T item) // { // if (!AllowDuplicates && Items.Contains(item)) // return; // // base.InsertItem(index, item); // } // // /// // protected override void SetItem(int index, T item) // { // if (AllowDuplicates) // { // if (Comparer.Equals(this[index], item)) // return; // } // else if (Items.Contains(item, Comparer)) // return; // // CheckReentrancy(); // T oldItem = this[index]; // base.SetItem(index, item); // // OnIndexerPropertyChanged(); // OnCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem!, item!, index); // } // // /// // /// 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 override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) // { // if (_synchronizationContext == null || SynchronizationContext.Current == _synchronizationContext) // { // // Execute the CollectionChanged event on the current thread // RaiseCollectionChanged(e); // } // else // { // // Raises the CollectionChanged event on the creator thread // _synchronizationContext.Send(RaiseCollectionChanged, e); // } // } // // private void RaiseCollectionChanged(object? param) // { // // We are in the creator thread, call the base implementation directly // if (param is NotifyCollectionChangedEventArgs args) // { // if (_deferredEvents != null) // { // _deferredEvents.Add(args); // return; // } // // base.OnCollectionChanged(args); // } // } // // protected virtual IDisposable DeferEvents() => new DeferredEventsCollection(this); // // #endregion Protected Methods // // // //------------------------------------------------------ // // // // Private Methods // // // //------------------------------------------------------ // // #region Private Methods // // /// // /// Helper to raise Count property and the Indexer property. // /// // void OnEssentialPropertiesChanged() // { // OnPropertyChanged(EventArgsCache.CountPropertyChanged); // OnIndexerPropertyChanged(); // } // // /// // /// /// Helper to raise a PropertyChanged event for the Indexer property // /// /// // void OnIndexerPropertyChanged() => // OnPropertyChanged(EventArgsCache.IndexerPropertyChanged); // // protected override void OnPropertyChanged(PropertyChangedEventArgs e) // { // if (_synchronizationContext == null || SynchronizationContext.Current == _synchronizationContext) // { // // Execute the PropertyChanged event on the current thread // RaisePropertyChanged(e); // } // else // { // // Raises the PropertyChanged event on the creator thread // _synchronizationContext.Send(RaisePropertyChanged, e); // } // } // // private void RaisePropertyChanged(object? param) // { // // We are in the creator thread, call the base implementation directly // if (param is PropertyChangedEventArgs args) // base.OnPropertyChanged(args); // } // // /// // /// Helper to raise CollectionChanged event to any listeners // /// // 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 // /// // void OnCollectionReset() => // OnCollectionChanged(EventArgsCache.ResetCollectionChanged); // // /// // /// Helper to raise event for clustered action and clear cluster. // /// // /// The index of the item following the replacement block. // /// // /// // //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerable collection, IEqualityComparer comparer), // //move when supported language version updated. // void OnRangeReplaced(int followingItemIndex, ICollection newCluster, ICollection oldCluster) // { // if (oldCluster == null || oldCluster.Count == 0) // { // Debug.Assert(newCluster == null || newCluster.Count == 0); // return; // } // // OnCollectionChanged( // new NotifyCollectionChangedEventArgs( // NotifyCollectionChangedAction.Replace, // new List(newCluster), // new List(oldCluster), // followingItemIndex - oldCluster.Count)); // // oldCluster.Clear(); // newCluster.Clear(); // } // // #endregion Private Methods // // //------------------------------------------------------ // // // // Private Types // // // //------------------------------------------------------ // // #region Private Types // // sealed class DeferredEventsCollection : List, IDisposable // { // readonly CoreObservableCollection _collection; // // public DeferredEventsCollection(CoreObservableCollection collection) // { // Debug.Assert(collection != null); // Debug.Assert(collection._deferredEvents == null); // _collection = collection; // _collection._deferredEvents = this; // } // // public void Dispose() // { // _collection._deferredEvents = null; // foreach (var args in this) // _collection.OnCollectionChanged(args); // } // } // // #endregion Private Types // } // // /// // /// To be kept outside , since otherwise, a new instance will be created for each generic type used. // /// // internal static class EventArgsCache // { // internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs("Count"); // // internal static readonly PropertyChangedEventArgs IndexerPropertyChanged = // new PropertyChangedEventArgs("Item[]"); // // internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = // new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); // } // }