Browse Source

Don't use sender as the lookup key.

#4577 was caused because the `sender` of the `CollectionChanged` event didn't correspond to the collection that the event listener was registered on. Instead create an `Entry` object for each collection and put the event handler on that.
pull/4578/head
Steven Kirk 6 years ago
parent
commit
d5fae441df
  1. 100
      src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs

100
src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs

@ -17,12 +17,12 @@ namespace Avalonia.Controls.Utils
void PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e); void PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e);
} }
internal class CollectionChangedEventManager : IWeakSubscriber<NotifyCollectionChangedEventArgs> internal class CollectionChangedEventManager
{ {
public static CollectionChangedEventManager Instance { get; } = new CollectionChangedEventManager(); public static CollectionChangedEventManager Instance { get; } = new CollectionChangedEventManager();
private ConditionalWeakTable<INotifyCollectionChanged, List<WeakReference<ICollectionChangedListener>>> _entries = private ConditionalWeakTable<INotifyCollectionChanged, Entry> _entries =
new ConditionalWeakTable<INotifyCollectionChanged, List<WeakReference<ICollectionChangedListener>>>(); new ConditionalWeakTable<INotifyCollectionChanged, Entry>();
private CollectionChangedEventManager() private CollectionChangedEventManager()
{ {
@ -34,17 +34,13 @@ namespace Avalonia.Controls.Utils
listener = listener ?? throw new ArgumentNullException(nameof(listener)); listener = listener ?? throw new ArgumentNullException(nameof(listener));
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
if (!_entries.TryGetValue(collection, out var listeners)) if (!_entries.TryGetValue(collection, out var entry))
{ {
listeners = new List<WeakReference<ICollectionChangedListener>>(); entry = new Entry(collection);
_entries.Add(collection, listeners); _entries.Add(collection, entry);
WeakSubscriptionManager.Subscribe(
collection,
nameof(INotifyCollectionChanged.CollectionChanged),
this);
} }
foreach (var l in listeners) foreach (var l in entry.Listeners)
{ {
if (l.TryGetTarget(out var target) && target == listener) if (l.TryGetTarget(out var target) && target == listener)
{ {
@ -53,7 +49,7 @@ namespace Avalonia.Controls.Utils
} }
} }
listeners.Add(new WeakReference<ICollectionChangedListener>(listener)); entry.Listeners.Add(new WeakReference<ICollectionChangedListener>(listener));
} }
public void RemoveListener(INotifyCollectionChanged collection, ICollectionChangedListener listener) public void RemoveListener(INotifyCollectionChanged collection, ICollectionChangedListener listener)
@ -62,8 +58,10 @@ namespace Avalonia.Controls.Utils
listener = listener ?? throw new ArgumentNullException(nameof(listener)); listener = listener ?? throw new ArgumentNullException(nameof(listener));
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
if (_entries.TryGetValue(collection, out var listeners)) if (_entries.TryGetValue(collection, out var entry))
{ {
var listeners = entry.Listeners;
for (var i = 0; i < listeners.Count; ++i) for (var i = 0; i < listeners.Count; ++i)
{ {
if (listeners[i].TryGetTarget(out var target) && target == listener) if (listeners[i].TryGetTarget(out var target) && target == listener)
@ -72,10 +70,7 @@ namespace Avalonia.Controls.Utils
if (listeners.Count == 0) if (listeners.Count == 0)
{ {
WeakSubscriptionManager.Unsubscribe( entry.Dispose();
collection,
nameof(INotifyCollectionChanged.CollectionChanged),
this);
_entries.Remove(collection); _entries.Remove(collection);
} }
@ -88,51 +83,72 @@ namespace Avalonia.Controls.Utils
"Collection listener not registered for this collection/listener combination."); "Collection listener not registered for this collection/listener combination.");
} }
void IWeakSubscriber<NotifyCollectionChangedEventArgs>.OnEvent(object sender, NotifyCollectionChangedEventArgs e) private class Entry : IWeakSubscriber<NotifyCollectionChangedEventArgs>, IDisposable
{ {
static void Notify( private INotifyCollectionChanged _collection;
INotifyCollectionChanged incc,
NotifyCollectionChangedEventArgs args, public Entry(INotifyCollectionChanged collection)
List<WeakReference<ICollectionChangedListener>> listeners)
{ {
foreach (var l in listeners) _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)
{ {
if (l.TryGetTarget(out var target)) foreach (var l in listeners)
{ {
target.PreChanged(incc, args); if (l.TryGetTarget(out var target))
{
target.PreChanged(incc, args);
}
} }
}
foreach (var l in listeners) foreach (var l in listeners)
{
if (l.TryGetTarget(out var target))
{ {
target.Changed(incc, args); if (l.TryGetTarget(out var target))
{
target.Changed(incc, args);
}
} }
}
foreach (var l in listeners) foreach (var l in listeners)
{
if (l.TryGetTarget(out var target))
{ {
target.PostChanged(incc, args); if (l.TryGetTarget(out var target))
{
target.PostChanged(incc, args);
}
} }
} }
}
if (sender is INotifyCollectionChanged incc && _entries.TryGetValue(incc, out var listeners)) var l = Listeners.ToList();
{
var l = listeners.ToList();
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
{ {
Notify(incc, e, l); Notify(_collection, e, l);
} }
else else
{ {
var inccCapture = incc;
var eCapture = e; var eCapture = e;
Dispatcher.UIThread.Post(() => Notify(inccCapture, eCapture, l)); Dispatcher.UIThread.Post(() => Notify(_collection, eCapture, l));
} }
} }
} }

Loading…
Cancel
Save