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

Loading…
Cancel
Save