diff --git a/build/System.Drawing.Common.props b/build/System.Drawing.Common.props
new file mode 100644
index 0000000000..a568152bbd
--- /dev/null
+++ b/build/System.Drawing.Common.props
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/packages.cake b/packages.cake
index 134340f459..17411aef4c 100644
--- a/packages.cake
+++ b/packages.cake
@@ -370,10 +370,10 @@ public class Packages
new NuGetPackSettings()
{
Id = "Avalonia.Win32",
- Dependencies = new []
+ Dependencies = new DependencyBuilder(this)
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
- },
+ }.Deps(new string[]{null}, "System.Drawing.Common"),
Files = new []
{
new NuSpecContent { Source = "Avalonia.Win32/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.Win32.dll", Target = "lib/netstandard2.0" }
diff --git a/readme.md b/readme.md
index 5a5d40c984..eeee39dabe 100644
--- a/readme.md
+++ b/readme.md
@@ -51,7 +51,7 @@ Please read the [contribution guidelines](http://avaloniaui.net/contributing/con
### Contributors
-This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
+This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing/contributing)].
diff --git a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
index 54cd132b95..b27b06a277 100644
--- a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
@@ -34,14 +34,18 @@ namespace Avalonia.Collections
///
/// An action called when the collection is reset.
///
+ ///
+ /// Indicates if a weak subscription should be used to track changes to the collection.
+ ///
/// A disposable used to terminate the subscription.
public static IDisposable ForEachItem(
this IAvaloniaReadOnlyList collection,
Action added,
Action removed,
- Action reset)
+ Action reset,
+ bool weakSubscription = false)
{
- return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset);
+ return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset, weakSubscription);
}
///
@@ -63,12 +67,16 @@ namespace Avalonia.Collections
/// An action called when the collection is reset. This will be followed by calls to
/// for each item present in the collection after the reset.
///
+ ///
+ /// Indicates if a weak subscription should be used to track changes to the collection.
+ ///
/// A disposable used to terminate the subscription.
public static IDisposable ForEachItem(
this IAvaloniaReadOnlyList collection,
Action added,
Action removed,
- Action reset)
+ Action reset,
+ bool weakSubscription = false)
{
void Add(int index, IList items)
{
@@ -118,9 +126,17 @@ namespace Avalonia.Collections
};
Add(0, (IList)collection);
- collection.CollectionChanged += handler;
- return Disposable.Create(() => collection.CollectionChanged -= handler);
+ if (weakSubscription)
+ {
+ return collection.WeakSubscribe(handler);
+ }
+ else
+ {
+ collection.CollectionChanged += handler;
+
+ return Disposable.Create(() => collection.CollectionChanged -= handler);
+ }
}
public static IAvaloniaReadOnlyList CreateDerivedList(
diff --git a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
new file mode 100644
index 0000000000..d295cb91ce
--- /dev/null
+++ b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
@@ -0,0 +1,127 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Reactive;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using Avalonia.Utilities;
+
+namespace Avalonia.Collections
+{
+ public static class NotifyCollectionChangedExtensions
+ {
+ ///
+ /// Gets a weak observable for the CollectionChanged event.
+ ///
+ /// The collection.
+ /// An observable.
+ public static IObservable GetWeakCollectionChangedObservable(
+ this INotifyCollectionChanged collection)
+ {
+ Contract.Requires(collection != null);
+
+ return new WeakCollectionChangedObservable(new WeakReference(collection));
+ }
+
+ ///
+ /// Subcribes to the CollectionChanged event using a weak subscription.
+ ///
+ /// The collection.
+ ///
+ /// An action called when the collection event is raised.
+ ///
+ /// A disposable used to terminate the subscription.
+ public static IDisposable WeakSubscribe(
+ this INotifyCollectionChanged collection,
+ NotifyCollectionChangedEventHandler handler)
+ {
+ Contract.Requires(collection != null);
+ Contract.Requires(handler != null);
+
+ return
+ collection.GetWeakCollectionChangedObservable()
+ .Subscribe(e => handler.Invoke(collection, e));
+ }
+
+ ///
+ /// Subcribes to the CollectionChanged event using a weak subscription.
+ ///
+ /// The collection.
+ ///
+ /// An action called when the collection event is raised.
+ ///
+ /// A disposable used to terminate the subscription.
+ public static IDisposable WeakSubscribe(
+ this INotifyCollectionChanged collection,
+ Action handler)
+ {
+ Contract.Requires(collection != null);
+ Contract.Requires(handler != null);
+
+ return
+ collection.GetWeakCollectionChangedObservable()
+ .Subscribe(handler);
+ }
+
+ private class WeakCollectionChangedObservable : ObservableBase,
+ IWeakSubscriber
+ {
+ private WeakReference _sourceReference;
+ private readonly Subject _changed = new Subject();
+
+ private int _count;
+
+ public WeakCollectionChangedObservable(WeakReference source)
+ {
+ _sourceReference = source;
+ }
+
+ public void OnEvent(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ _changed.OnNext(e);
+ }
+
+ protected override IDisposable SubscribeCore(IObserver observer)
+ {
+ if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
+ {
+ if (_count++ == 0)
+ {
+ WeakSubscriptionManager.Subscribe(
+ instance,
+ nameof(instance.CollectionChanged),
+ this);
+ }
+
+ return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed)
+ .Subscribe(observer);
+ }
+ else
+ {
+ _changed.OnCompleted();
+ observer.OnCompleted();
+ return Disposable.Empty;
+ }
+ }
+
+ private void DecrementCount()
+ {
+ if (--_count == 0)
+ {
+ if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
+ {
+ WeakSubscriptionManager.Unsubscribe(
+ instance,
+ nameof(instance.CollectionChanged),
+ this);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 4366de1cd6..6a26e29187 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
@@ -54,6 +55,7 @@ namespace Avalonia.Controls
private IEnumerable _items = new AvaloniaList