using System.Linq;
using System.Runtime.CompilerServices;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.Threading;
namespace System.Collections.Generic
{
///
/// Extends the Generic class with an event that will fire when the list is updated via standard list methods
///
/// Type of object the list will contain
///
/// This class is being provided by the RoboSharp DLL
///
///
///
public class ObservableList : List, INotifyCollectionChanged
{
#region < Constructors >
///
public ObservableList() : base() { }
///
public ObservableList(int capacity) : base(capacity) { }
///
public ObservableList(IEnumerable collection) : base(collection) { }
#endregion
#region < Events >
/// This event fires whenever the List's array is updated.
public event NotifyCollectionChangedEventHandler CollectionChanged;
///
/// Raise the event.
///
/// Override this method to provide post-processing of Added/Removed items within derived classes.
///
///
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
// This Syncronization code was taken from here: https://stackoverflow.com/a/54733415/12135042
// This ensures that the CollectionChanged event is invoked from the proper thread, no matter where the change came from.
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the CollectionChanged event on the current thread
CollectionChanged?.Invoke(this, e);
}
else
{
// Raises the CollectionChanged event on the creator thread
_synchronizationContext.Send((callback) => CollectionChanged?.Invoke(this, e), null);
}
}
private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
#region < Alternate methods for OnCollectionChanged + reasoning why it wasn't used >
/*
* This standard method cannot be used because RoboQueue is adding results onto the ResultsLists as they complete, which means the events may not be on the original thread that RoboQueue was constructed in.
* protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) => CollectionChanged?.Invoke(this, e);
*/
// --------------------------------------------------------------------------------------------------------------
/*
* This code was taken from here: https://www.codeproject.com/Articles/64936/Threadsafe-ObservableImmutable-Collection
* It works, but its a bit more involved since it loops through all handlers.
* This was not used due to being unavailable in some targets. (Same reasoning for creating new class instead of class provided by above link)
*/
// ///
// /// Raise the event.
// /// Override this method to provide post-processing of Added/Removed items within derived classes.
// ///
// ///
// protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
// {
// var notifyCollectionChangedEventHandler = CollectionChanged;
// if (notifyCollectionChangedEventHandler == null)
// return;
// foreach (NotifyCollectionChangedEventHandler handler in notifyCollectionChangedEventHandler.GetInvocationList())
// {
//#if NET40_OR_GREATER
// var dispatcherObject = handler.Target as DispatcherObject;
// if (dispatcherObject != null && !dispatcherObject.CheckAccess())
// {
// dispatcherObject.Dispatcher.Invoke(handler, this, e);
// }
// else
//#endif
// handler(this, e); // note : this does not execute handler in target thread's context
// }
// }
// --------------------------------------------------------------------------------------------------------------
#endregion
#endregion
#region < New Methods >
///
/// Replace an item in the list.
///
/// Search for this item in the list. If found, replace it. If not found, return false. will not be added to the list.
/// This item will replace the . If was not found, this item does not get added to the list.
/// True if the was found in the list and successfully replaced. Otherwise false.
public virtual bool Replace(T itemToReplace, T newItem)
{
if (!this.Contains(itemToReplace)) return false;
return Replace(this.IndexOf(itemToReplace), newItem);
}
///
/// Replace an item in the list
///
/// Index of the item to replace
/// This item will replace the item at the specified
/// True if the the item was successfully replaced. Otherwise throws.
///
public virtual bool Replace(int index, T newItem)
{
this[index] = newItem;
return true;
}
///
/// Replaces the items in this list with the items in supplied collection. If the collection has more items than will be removed from the list, the remaining items will be added to the list.
/// EX: List has 10 items, collection has 5 items, index of 8 is specified (which is item 9 on 0-based index) -> Item 9 + 10 are replaced, and remaining 3 items from collection are added to the list.
///
/// Index of the item to replace
/// Collection of items to insert into the list.
/// True if the the collection was successfully inserted into the list. Otherwise throws.
///
///
///
public virtual bool Replace(int index, IEnumerable collection)
{
int collectionCount = collection.Count();
int ItemsAfterIndex = this.Count - index; // # of items in the list after the index
int CountToReplace = collectionCount <= ItemsAfterIndex ? collectionCount : ItemsAfterIndex; //# if items that will be replaced
int AdditionalItems = collectionCount - CountToReplace; //# of additional items that will be added to the list.
List oldItemsList = this.GetRange(index, CountToReplace);
//Insert the collection
base.RemoveRange(index, CountToReplace);
if (AdditionalItems > 0)
base.AddRange(collection);
else
base.InsertRange(index, collection);
List insertedList = collection.ToList();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems: insertedList.GetRange(0, CountToReplace), oldItems: oldItemsList, index));
if (AdditionalItems > 0)
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, insertedList.GetRange(CountToReplace, AdditionalItems)));
return true;
}
#endregion
#region < Methods that Override List Methods >
///
/// Get or Set the element at the specified index.
///
/// The zero-based index of the item to Get or Set.
///
new public T this[int index] {
get => base[index];
set {
if (index >= 0 && index < Count)
{
//Perform Replace
T old = base[index];
base[index] = value;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItem: value, oldItem: old, index));
}
else if (index == Count && index <= Capacity - 1)
{
//Append value to end only if the capacity doesn't need to be changed
base.Add(value);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value, index));
}
else
{
base[index] = value; // Generate ArgumentOutOfRangeException exception
}
}
}
#region < Add >
///
new public virtual void Add(T item)
{
base.Add(item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
///
new public virtual void AddRange(IEnumerable collection)
{
if (collection == null || collection.Count() == 0) return;
base.AddRange(collection);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection.ToList()));
}
#endregion
#region < Insert >
///
/// Generates event for item that was added and item that was shifted ( Event is raised twice )
new public virtual void Insert(int index, T item)
{
base.Insert(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index: index));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, this[index + 1], index + 1, index));
}
///
/// Generates event for items that were added and items that were shifted ( Event is raised twice )
new public virtual void InsertRange(int index, IEnumerable collection)
{
if (collection == null || collection.Count() == 0) return;
int i = index + collection.Count() < this.Count ? collection.Count() : this.Count - index;
List movedItems = base.GetRange(index, i);
base.InsertRange(index, collection);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, changedItems: collection.ToList(), index));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, changedItems: movedItems, IndexOf(movedItems[0]), index));
}
#endregion
#region < Remove >
///
new public virtual void Clear()
{
base.Clear();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
///
new public virtual bool Remove(T item)
{
if (!base.Contains(item)) return false;
int i = base.IndexOf(item);
if (base.Remove(item))
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, i));
return true;
}
else
return false;
}
///
new public virtual void RemoveAt(int index)
{
T item = base[index];
base.RemoveAt(index);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index: index));
}
///
new public virtual void RemoveRange(int index, int count)
{
List removedItems = base.GetRange(index, count);
if (removedItems.Count > 0)
{
base.RemoveRange(index, count);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems.ToList(), index));
}
}
///
new public virtual int RemoveAll(Predicate match)
{
List removedItems = base.FindAll(match);
int ret = removedItems.Count;
if (ret > 0)
{
ret = base.RemoveAll(match);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems));
}
return ret;
}
#endregion
#region < Move / Sort >
#region < Hide base members >
///
new virtual public void Sort(int index, int count, IComparer comparer) => Sort(index, count, comparer, true);
///
new virtual public void Sort(IComparer comparer) => Sort(comparer, true);
///
new virtual public void Sort(Comparison comparison) => Sort(comparison, true);
///
new virtual public void Sort() => Sort(true);
///
new virtual public void Reverse(int index, int count) => Reverse(index, count, true);
///
new virtual public void Reverse() => Reverse(true);
#endregion
///
///
public virtual void Reverse(bool verbose)
{
PerformMove(new Action(() => base.Reverse()), this, verbose);
}
///
///
public virtual void Reverse(int index, int count, bool verbose)
{
List OriginalOrder = base.GetRange(index, count);
PerformMove(new Action(() => base.Reverse(index, count)), OriginalOrder, verbose);
}
///
///
public virtual void Sort(bool verbose)
{
PerformMove(new Action(() => base.Sort()), this, verbose);
}
///
///
public virtual void Sort(Comparison comparison, bool verbose)
{
PerformMove(new Action(() => base.Sort(comparison)), this, verbose);
}
///
///
public virtual void Sort(IComparer comparer, bool verbose)
{
PerformMove(new Action(() => base.Sort(comparer)), this, verbose);
}
///
///
public virtual void Sort(int index, int count, IComparer comparer, bool verbose)
{
List OriginalOrder = base.GetRange(index, count);
Action action = new Action(() => base.Sort(index, count, comparer));
PerformMove(action, OriginalOrder, verbose);
}
///
/// Per rules, generates event for every item that has moved within the list.
/// Set parameter in overload to generate a single event instead.
///
/// Action to perform that will rearrange items in the list - should not add, remove or replace!
/// List of items that are intended to rearrage - can be whole or subset of list
///
/// If TRUE: Create a 'Move' OnCollectionChange event for all items that were moved within the list.
/// If FALSE: Generate a single event with
///
protected void PerformMove(Action MoveAction, List OriginalOrder, bool verbose)
{
//Store Old List Order
List OldIndexList = this.ToList();
//Perform the move
MoveAction.Invoke();
//Generate the event
foreach (T obj in OriginalOrder)
{
int oldIndex = OldIndexList.IndexOf(obj);
int newIndex = this.IndexOf(obj);
if (oldIndex != newIndex)
{
if (verbose)
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, changedItem: obj, newIndex, oldIndex));
else
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
break;
}
}
}
//OldIndexList no longer needed
OldIndexList.Clear();
}
#endregion
#endregion
}
}