| File: Items\MarshalingObservableCollection.cs | Web Access |
| Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls) |
#nullable disable using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using Microsoft.Maui.Dispatching; namespace Microsoft.Maui.Controls { // Wraps a List which implements INotifyCollectionChanged (usually an ObservableCollection) // and marshals all of the list modifications to the main thread. Modifications to the underlying // collection which are made off of the main thread remain invisible to consumers on the main thread // until they have been processed by the main thread. /// <include file="../../../docs/Microsoft.Maui.Controls/MarshalingObservableCollection.xml" path="Type[@FullName='Microsoft.Maui.Controls.MarshalingObservableCollection']/Docs/*" /> public class MarshalingObservableCollection : List<object>, INotifyCollectionChanged { readonly IList _internalCollection; readonly IDispatcher _dispatcher; /// <include file="../../../docs/Microsoft.Maui.Controls/MarshalingObservableCollection.xml" path="//Member[@MemberName='.ctor']/Docs/*" /> public MarshalingObservableCollection(IList list) { if (!(list is INotifyCollectionChanged incc)) throw new ArgumentException($"{nameof(list)} must implement {nameof(INotifyCollectionChanged)}"); _internalCollection = list; _dispatcher = Dispatcher.GetForCurrentThread(); incc.CollectionChanged += InternalCollectionChanged; foreach (var item in _internalCollection) { Add(item); } } class ResetNotifyCollectionChangedEventArgs : NotifyCollectionChangedEventArgs { public IList Items { get; } public ResetNotifyCollectionChangedEventArgs(IList items) : base(NotifyCollectionChangedAction.Reset) => Items = items; } public event NotifyCollectionChangedEventHandler CollectionChanged; void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { CollectionChanged?.Invoke(this, args); } void InternalCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) { if (args.Action == NotifyCollectionChangedAction.Reset) { var items = new List<object>(); for (int n = 0; n < _internalCollection.Count; n++) { items.Add(_internalCollection[n]); } args = new ResetNotifyCollectionChangedEventArgs(items); } _dispatcher.DispatchIfRequired(() => HandleCollectionChange(args)); } void HandleCollectionChange(NotifyCollectionChangedEventArgs args) { switch (args.Action) { case NotifyCollectionChangedAction.Add: Add(args); break; case NotifyCollectionChangedAction.Move: Move(args); break; case NotifyCollectionChangedAction.Remove: Remove(args); break; case NotifyCollectionChangedAction.Replace: Replace(args); break; case NotifyCollectionChangedAction.Reset: Reset(args); break; } } void Move(NotifyCollectionChangedEventArgs args) { var count = args.OldItems.Count; for (int n = 0; n < count; n++) { var toMove = this[args.OldStartingIndex]; RemoveAt(args.OldStartingIndex); Insert(args.NewStartingIndex, toMove); } OnCollectionChanged(args); } void Remove(NotifyCollectionChangedEventArgs args) { var startIndex = args.OldStartingIndex + args.OldItems.Count - 1; for (int n = startIndex; n >= args.OldStartingIndex; n--) { RemoveAt(n); } OnCollectionChanged(args); } void Replace(NotifyCollectionChangedEventArgs args) { var startIndex = args.NewStartingIndex; foreach (var item in args.NewItems) { this[startIndex] = item; startIndex += 1; } OnCollectionChanged(args); } void Add(NotifyCollectionChangedEventArgs args) { var startIndex = args.NewStartingIndex; foreach (var item in args.NewItems) { Insert(startIndex, item); startIndex += 1; } OnCollectionChanged(args); } void Reset(NotifyCollectionChangedEventArgs args) { if (!(args is ResetNotifyCollectionChangedEventArgs resetArgs)) { throw new InvalidOperationException($"Cannot guarantee collection accuracy for Resets which do not use {nameof(ResetNotifyCollectionChangedEventArgs)}"); } Clear(); foreach (var item in resetArgs.Items) { Add(item); } OnCollectionChanged(args); } } }