mirror of https://github.com/AMT-Cheif/Stylet.git
Replace BindWeak with a WeakEventManager, and let Screen have an instance
This commit is contained in:
parent
f8bb1838d3
commit
6eb9c6dbef
|
@ -19,50 +19,6 @@ namespace Stylet
|
|||
|
||||
public static class PropertyChangedExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Mapping of thing doing the binding -> bindings. Makes sure we retain the bindings (and any compiler-generated classes) for as long as the thing which cares about the notifications exists
|
||||
/// </summary>
|
||||
private static ConditionalWeakTable<object, List<WeakPropertyChangedBinding>> eventMapping = new ConditionalWeakTable<object, List<WeakPropertyChangedBinding>>();
|
||||
private static object eventMappingLock = new object();
|
||||
|
||||
internal class WeakPropertyChangedBinding : IPropertyChangedBinding
|
||||
{
|
||||
private readonly string propertyName;
|
||||
private readonly EventHandler<PropertyChangedEventArgs> handler;
|
||||
private readonly WeakReference binder;
|
||||
private readonly WeakReference<INotifyPropertyChanged> inpc;
|
||||
|
||||
public WeakPropertyChangedBinding(object binder, INotifyPropertyChanged inpc, string propertyName, EventHandler<PropertyChangedEventArgs> handler)
|
||||
{
|
||||
this.binder = new WeakReference(binder);
|
||||
this.inpc = new WeakReference<INotifyPropertyChanged>(inpc);
|
||||
this.propertyName = propertyName;
|
||||
// We need to keep this strongly, in case its target is a compiler-generated class instance
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
INotifyPropertyChanged inpc;
|
||||
|
||||
// If the target's still around, unregister ourselves
|
||||
if (this.inpc.TryGetTarget(out inpc))
|
||||
PropertyChangedEventManager.RemoveHandler(inpc, this.handler, this.propertyName);
|
||||
|
||||
// If the handler's still around (there's a possibility it isn't), remove us from the ConditionalWeakTable
|
||||
var handler = this.handler.Target;
|
||||
if (handler != null)
|
||||
{
|
||||
lock (eventMappingLock)
|
||||
{
|
||||
List<WeakPropertyChangedBinding> listeners;
|
||||
if (eventMapping.TryGetValue(handler, out listeners))
|
||||
listeners.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class StrongPropertyChangedBinding : IPropertyChangedBinding
|
||||
{
|
||||
private WeakReference<INotifyPropertyChanged> inpc;
|
||||
|
@ -84,51 +40,6 @@ namespace Stylet
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Weakly bind to PropertyChanged events for a particular property on a particular object. This won't retain the object registered to receive PropertyChange notifications
|
||||
/// </summary>
|
||||
/// <example>someObject.Bind(this, x => x.PropertyNameToBindTo, newValue => /* do something with the new value */)</example>
|
||||
/// <param name="target">Object raising the PropertyChanged event you're interested in</param>
|
||||
/// <param name="binder">Object on which the handler is defined. This is needed because you might use an anonymous delegate as the handler, which might cause a compiler-generated class to be set as the handler's Target, and that needs to be retained for as this parameter</param>
|
||||
/// <param name="targetSelector">MemberExpression selecting the property to observe for changes (e.g x => x.PropertyName)</param>
|
||||
/// <param name="handler">Handler called whenever that property changed</param>
|
||||
/// <returns>Something which can be used to undo the binding. You can discard it if you want</returns>
|
||||
public static IPropertyChangedBinding BindWeak<TBindTo, TMember>(this TBindTo target, object binder, Expression<Func<TBindTo, TMember>> targetSelector, Action<TMember> handler) where TBindTo : class, INotifyPropertyChanged
|
||||
{
|
||||
var propertyName = targetSelector.NameForProperty();
|
||||
var propertyAccess = targetSelector.Compile();
|
||||
var weakTarget = new WeakReference<TBindTo>(target);
|
||||
|
||||
EventHandler<PropertyChangedEventArgs> ourHandler = (o, e) =>
|
||||
{
|
||||
TBindTo strongTarget;
|
||||
if (weakTarget.TryGetTarget(out strongTarget))
|
||||
handler(propertyAccess(strongTarget));
|
||||
};
|
||||
|
||||
WeakPropertyChangedBinding weakListener = new WeakPropertyChangedBinding(binder, target, propertyName, ourHandler);
|
||||
|
||||
// Right, we have target, propertyName, binder.
|
||||
// Now we have to keep the handler we're about to build (which has a reference to the handler we were passed) alive as long as
|
||||
// binder's alive (the handler that was passed to us might refer to a method on a compiler-generated class, which we've got
|
||||
// the only reference to, so we've got to keep that alive).
|
||||
lock (eventMappingLock)
|
||||
{
|
||||
List<WeakPropertyChangedBinding> listeners;
|
||||
if (!eventMapping.TryGetValue(binder, out listeners))
|
||||
{
|
||||
listeners = new List<WeakPropertyChangedBinding>();
|
||||
eventMapping.Add(binder, listeners);
|
||||
}
|
||||
|
||||
listeners.Add(weakListener);
|
||||
}
|
||||
|
||||
PropertyChangedEventManager.AddHandler(target, ourHandler, propertyName);
|
||||
|
||||
return weakListener;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strongly bind to PropertyChanged events for a particular property on a particular object
|
||||
/// </summary>
|
||||
|
|
|
@ -11,6 +11,9 @@ namespace Stylet
|
|||
{
|
||||
public class Screen : PropertyChangedBase, IScreen
|
||||
{
|
||||
private Lazy<IWeakEventManager> lazyWeakEventManager = new Lazy<IWeakEventManager>(() => new WeakEventManager(), true);
|
||||
protected IWeakEventManager weakEventManager { get { return this.lazyWeakEventManager.Value; } }
|
||||
|
||||
#region IHaveDisplayName
|
||||
|
||||
private string _displayName;
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
<Compile Include="LabelledValue.cs" />
|
||||
<Compile Include="SlightlyInternal\ConductorUtilities.cs" />
|
||||
<Compile Include="INotifyPropertyChangedDispatcher.cs" />
|
||||
<Compile Include="WeakEventManager.cs" />
|
||||
<Compile Include="Xaml\ActionExtension.cs" />
|
||||
<Compile Include="AssemblySource.cs" />
|
||||
<Compile Include="BindableCollection.cs" />
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Stylet
|
||||
{
|
||||
public interface IWeakEventManager
|
||||
{
|
||||
IPropertyChangedBinding BindWeak<TSource, TProperty>(TSource source, Expression<Func<TSource, TProperty>> selector, Action<TProperty> handler)
|
||||
where TSource : class, INotifyPropertyChanged;
|
||||
}
|
||||
|
||||
internal class WeakPropertyBinding<TSource, TProperty> : IPropertyChangedBinding where TSource : class, INotifyPropertyChanged
|
||||
{
|
||||
// Make sure we don't end up retaining the source
|
||||
private readonly WeakReference<TSource> source;
|
||||
private readonly string propertyName;
|
||||
private readonly Func<TSource, TProperty> valueSelector;
|
||||
private readonly Action<TProperty> handler;
|
||||
private readonly Action<IPropertyChangedBinding> remover;
|
||||
|
||||
public WeakPropertyBinding(TSource source, Expression<Func<TSource, TProperty>> selector, Action<TProperty> handler, Action<IPropertyChangedBinding> remover)
|
||||
{
|
||||
this.source = new WeakReference<TSource>(source);
|
||||
this.propertyName = selector.NameForProperty();
|
||||
this.valueSelector = selector.Compile();
|
||||
this.handler = handler;
|
||||
this.remover = remover;
|
||||
|
||||
PropertyChangedEventManager.AddHandler(source, this.PropertyChangedHandler, this.propertyName);
|
||||
}
|
||||
|
||||
internal void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
TSource source;
|
||||
if (this.source.TryGetTarget(out source))
|
||||
this.handler(this.valueSelector(source));
|
||||
else
|
||||
this.remover(this);
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
TSource source;
|
||||
if (this.source.TryGetTarget(out source))
|
||||
PropertyChangedEventManager.RemoveHandler(source, this.PropertyChangedHandler, this.propertyName);
|
||||
this.remover(this);
|
||||
}
|
||||
}
|
||||
|
||||
public class WeakEventManager : IWeakEventManager
|
||||
{
|
||||
private object bindingsLock = new object();
|
||||
private List<IPropertyChangedBinding> bindings = new List<IPropertyChangedBinding>();
|
||||
|
||||
public IPropertyChangedBinding BindWeak<TSource, TProperty>(TSource source, Expression<Func<TSource, TProperty>> selector, Action<TProperty> handler)
|
||||
where TSource : class, INotifyPropertyChanged
|
||||
{
|
||||
// So, the handler's target might point to the class that owns us, or it might point to a compiler-generated class
|
||||
// We assume we're owned by whatever determines how long the handler's target should live for
|
||||
// Therefore we'll retain the handler's target for as long as we're alive (unless it's unregistered)
|
||||
|
||||
// The PropertyChangedEventManager is safe to use with delegates whose targets aren't compiler-generated, so we can
|
||||
// ensure we provide a delegate with a non-compiler-generated target.
|
||||
// To do this, we'll create a new WeakPropertyBinding instance, and retain it ourselves (so it lives as long as we do,
|
||||
// and therefore as long as the thing that owns us does). The PropertyChangedEventManager will have a weak reference to
|
||||
// the WeakPropertyBinding instance, so once we release it, it will too.
|
||||
|
||||
var propertyName = selector.NameForProperty();
|
||||
var compiledSelector = selector.Compile();
|
||||
|
||||
var binding = new WeakPropertyBinding<TSource, TProperty>(source, selector, handler, this.Remove);
|
||||
lock (this.bindingsLock)
|
||||
{
|
||||
this.bindings.Add(binding);
|
||||
}
|
||||
|
||||
return binding;
|
||||
}
|
||||
|
||||
internal void Remove(IPropertyChangedBinding binding)
|
||||
{
|
||||
lock (this.bindingsLock)
|
||||
{
|
||||
this.bindings.Remove(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ namespace StyletUnitTests
|
|||
class BindingClass
|
||||
{
|
||||
public string LastFoo;
|
||||
private WeakEventManager weakEventManager = new WeakEventManager();
|
||||
|
||||
public IPropertyChangedBinding BindStrong(NotifyingClass notifying)
|
||||
{
|
||||
|
@ -45,7 +46,7 @@ namespace StyletUnitTests
|
|||
|
||||
public IPropertyChangedBinding BindWeak(NotifyingClass notifying)
|
||||
{
|
||||
return notifying.BindWeak(this, x => x.Foo, x => this.LastFoo = x);
|
||||
return this.weakEventManager.BindWeak(notifying, x => x.Foo, x => this.LastFoo = x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,9 +130,10 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void WeakBindingBinds()
|
||||
{
|
||||
var manager = new WeakEventManager();
|
||||
string newVal = null;
|
||||
var c1 = new NotifyingClass();
|
||||
c1.BindWeak(this, x => x.Foo, x => newVal = x);
|
||||
manager.BindWeak(c1, x => x.Foo, x => newVal = x);
|
||||
c1.Foo = "bar";
|
||||
|
||||
Assert.AreEqual("bar", newVal);
|
||||
|
@ -140,9 +142,10 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void WeakBindingIgnoresOtherProperties()
|
||||
{
|
||||
var manager = new WeakEventManager();
|
||||
string newVal = null;
|
||||
var c1 = new NotifyingClass();
|
||||
c1.BindWeak(this, x => x.Bar, x => newVal = x);
|
||||
manager.BindWeak(c1, x => x.Bar, x => newVal = x);
|
||||
c1.Foo = "bar";
|
||||
|
||||
Assert.AreEqual(null, newVal);
|
||||
|
@ -151,10 +154,11 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void WeakBindingListensToEmptyString()
|
||||
{
|
||||
var manager = new WeakEventManager();
|
||||
string newVal = null;
|
||||
var c1 = new NotifyingClass();
|
||||
c1.Bar = "bar";
|
||||
c1.BindWeak(this, x => x.Bar, x => newVal = x);
|
||||
manager.BindWeak(c1, x => x.Bar, x => newVal = x);
|
||||
c1.NotifyAll();
|
||||
|
||||
Assert.AreEqual("bar", newVal);
|
||||
|
@ -211,9 +215,10 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void WeakBindingUnbinds()
|
||||
{
|
||||
var manager = new WeakEventManager();
|
||||
string newVal = null;
|
||||
var c1 = new NotifyingClass();
|
||||
var binding = c1.BindWeak(this, x => x.Bar, x => newVal = x);
|
||||
var binding = manager.BindWeak(c1, x => x.Bar, x => newVal = x);
|
||||
binding.Unbind();
|
||||
c1.Bar = "bar";
|
||||
|
||||
|
|
Loading…
Reference in New Issue