using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Runtime.CompilerServices; using Avalonia.Threading; using Avalonia.Utilities; #nullable enable namespace Avalonia.Controls.Utils { internal interface ICollectionChangedListener { void PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e); void Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e); void PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e); } internal class CollectionChangedEventManager { public static CollectionChangedEventManager Instance { get; } = new CollectionChangedEventManager(); private ConditionalWeakTable _entries = new ConditionalWeakTable(); private CollectionChangedEventManager() { } public void AddListener(INotifyCollectionChanged collection, ICollectionChangedListener listener) { collection = collection ?? throw new ArgumentNullException(nameof(collection)); listener = listener ?? throw new ArgumentNullException(nameof(listener)); Dispatcher.UIThread.VerifyAccess(); if (!_entries.TryGetValue(collection, out var entry)) { entry = new Entry(collection); _entries.Add(collection, entry); } foreach (var l in entry.Listeners) { if (l.TryGetTarget(out var target) && target == listener) { throw new InvalidOperationException( "Collection listener already added for this collection/listener combination."); } } entry.Listeners.Add(new WeakReference(listener)); } public void RemoveListener(INotifyCollectionChanged collection, ICollectionChangedListener listener) { collection = collection ?? throw new ArgumentNullException(nameof(collection)); listener = listener ?? throw new ArgumentNullException(nameof(listener)); Dispatcher.UIThread.VerifyAccess(); if (_entries.TryGetValue(collection, out var entry)) { var listeners = entry.Listeners; for (var i = 0; i < listeners.Count; ++i) { if (listeners[i].TryGetTarget(out var target) && target == listener) { listeners.RemoveAt(i); if (listeners.Count == 0) { entry.Dispose(); _entries.Remove(collection); } return; } } } throw new InvalidOperationException( "Collection listener not registered for this collection/listener combination."); } private class Entry : IWeakSubscriber, IDisposable { private INotifyCollectionChanged _collection; public Entry(INotifyCollectionChanged collection) { _collection = collection; Listeners = new List>(); WeakSubscriptionManager.Subscribe( _collection, nameof(INotifyCollectionChanged.CollectionChanged), this); } public List> Listeners { get; } public void Dispose() { WeakSubscriptionManager.Unsubscribe( _collection, nameof(INotifyCollectionChanged.CollectionChanged), this); } void IWeakSubscriber.OnEvent(object sender, NotifyCollectionChangedEventArgs e) { static void Notify( INotifyCollectionChanged incc, NotifyCollectionChangedEventArgs args, List> listeners) { foreach (var l in listeners) { if (l.TryGetTarget(out var target)) { target.PreChanged(incc, args); } } foreach (var l in listeners) { if (l.TryGetTarget(out var target)) { target.Changed(incc, args); } } foreach (var l in listeners) { if (l.TryGetTarget(out var target)) { target.PostChanged(incc, args); } } } var l = Listeners.ToList(); if (Dispatcher.UIThread.CheckAccess()) { Notify(_collection, e, l); } else { var eCapture = e; Dispatcher.UIThread.Post(() => Notify(_collection, eCapture, l)); } } } } }