csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
156 lines
5.5 KiB
156 lines
5.5 KiB
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<INotifyCollectionChanged, Entry> _entries =
|
|
new ConditionalWeakTable<INotifyCollectionChanged, Entry>();
|
|
|
|
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<ICollectionChangedListener>(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<NotifyCollectionChangedEventArgs>, IDisposable
|
|
{
|
|
private INotifyCollectionChanged _collection;
|
|
|
|
public Entry(INotifyCollectionChanged collection)
|
|
{
|
|
_collection = collection;
|
|
Listeners = new List<WeakReference<ICollectionChangedListener>>();
|
|
WeakSubscriptionManager.Subscribe(
|
|
_collection,
|
|
nameof(INotifyCollectionChanged.CollectionChanged),
|
|
this);
|
|
}
|
|
|
|
public List<WeakReference<ICollectionChangedListener>> Listeners { get; }
|
|
|
|
public void Dispose()
|
|
{
|
|
WeakSubscriptionManager.Unsubscribe(
|
|
_collection,
|
|
nameof(INotifyCollectionChanged.CollectionChanged),
|
|
this);
|
|
}
|
|
|
|
void IWeakSubscriber<NotifyCollectionChangedEventArgs>.OnEvent(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
static void Notify(
|
|
INotifyCollectionChanged incc,
|
|
NotifyCollectionChangedEventArgs args,
|
|
List<WeakReference<ICollectionChangedListener>> 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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|