mirror of https://github.com/AMT-Cheif/Stylet.git
Change the handler type for Bind and BindWeak, to include the sender and property name
This commit is contained in:
parent
c18563267f
commit
58d5a6e55e
|
@ -11,6 +11,29 @@ using System.Reflection;
|
|||
|
||||
namespace Stylet
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension of PropertyChangedEventArgs, which includes the new value of the property
|
||||
/// </summary>
|
||||
/// <typeparam name="TProperty"></typeparam>
|
||||
public class PropertyChangedExtendedEventArgs<TProperty> : PropertyChangedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// New value of the property
|
||||
/// </summary>
|
||||
public virtual TProperty NewValue { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Instantiate a new PropertyChangedExtendedEventArgs
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property which changed</param>
|
||||
/// <param name="newValue">New value of the property which changed</param>
|
||||
public PropertyChangedExtendedEventArgs(string propertyName, TProperty newValue)
|
||||
: base(propertyName)
|
||||
{
|
||||
this.NewValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A binding to a PropertyChanged event, which can be used to unbind the binding
|
||||
/// </summary>
|
||||
|
@ -51,11 +74,11 @@ namespace Stylet
|
|||
internal class WeakPropertyChangedHandler<TSource, TProperty> : IEventBinding where TSource : class, INotifyPropertyChanged
|
||||
{
|
||||
private readonly WeakReference<TSource> source;
|
||||
private Action<TProperty> handler;
|
||||
private EventHandler<PropertyChangedExtendedEventArgs<TProperty>> handler;
|
||||
private string propertyName;
|
||||
private Func<TSource, TProperty> valueSelector;
|
||||
|
||||
public WeakPropertyChangedHandler(TSource source, Expression<Func<TSource, TProperty>> selector, Action<TProperty> handler)
|
||||
public WeakPropertyChangedHandler(TSource source, Expression<Func<TSource, TProperty>> selector, EventHandler<PropertyChangedExtendedEventArgs<TProperty>> handler)
|
||||
{
|
||||
// We keep a strong reference to the handler, and have the PropertyChangedEventManager keep a weak
|
||||
// reference to us. This means that anyone retaining us will also retain the handler.
|
||||
|
@ -74,7 +97,7 @@ namespace Stylet
|
|||
var got = this.source.TryGetTarget(out source);
|
||||
// We should never hit this case. The PropertyChangedeventManager shouldn't call us if the source became null
|
||||
Debug.Assert(got);
|
||||
this.handler(this.valueSelector(source));
|
||||
this.handler(source, new PropertyChangedExtendedEventArgs<TProperty>(this.propertyName, this.valueSelector(source)));
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
|
@ -110,21 +133,21 @@ namespace Stylet
|
|||
/// <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 IEventBinding Bind<TBindTo, TMember>(this TBindTo target, Expression<Func<TBindTo, TMember>> targetSelector, Action<TMember> handler) where TBindTo : class, INotifyPropertyChanged
|
||||
public static IEventBinding Bind<TSource, TProperty>(this TSource target, Expression<Func<TSource, TProperty>> targetSelector, EventHandler<PropertyChangedExtendedEventArgs<TProperty>> handler) where TSource : class, INotifyPropertyChanged
|
||||
{
|
||||
var propertyName = targetSelector.NameForProperty();
|
||||
var propertyAccess = targetSelector.Compile();
|
||||
// Make sure we don't capture target strongly, otherwise we'll retain it when we shouldn't
|
||||
// If it does get released, we're released from the delegate list
|
||||
var weakTarget = new WeakReference<TBindTo>(target);
|
||||
var weakTarget = new WeakReference<TSource>(target);
|
||||
|
||||
PropertyChangedEventHandler ourHandler = (o, e) =>
|
||||
{
|
||||
if (e.PropertyName == propertyName || e.PropertyName == String.Empty)
|
||||
{
|
||||
TBindTo strongTarget;
|
||||
TSource strongTarget;
|
||||
if (weakTarget.TryGetTarget(out strongTarget))
|
||||
handler(propertyAccess(strongTarget));
|
||||
handler(strongTarget, new PropertyChangedExtendedEventArgs<TProperty>(propertyName, propertyAccess(strongTarget)));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -135,13 +158,21 @@ namespace Stylet
|
|||
return listener;
|
||||
}
|
||||
|
||||
public static IEventBinding BindWeak<TBindTo, TMember>(this TBindTo target, Expression<Func<TBindTo, TMember>> targetSelector, Action<TMember> handler) where TBindTo : class, INotifyPropertyChanged
|
||||
/// <summary>
|
||||
/// Weakly bind to PropertyChanged events for a particular property on a particular object
|
||||
/// </summary>
|
||||
/// <example>someObject.Bind(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="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 IEventBinding BindWeak<TSource, TProperty>(this TSource target, Expression<Func<TSource, TProperty>> targetSelector, EventHandler<PropertyChangedExtendedEventArgs<TProperty>> handler) where TSource : class, INotifyPropertyChanged
|
||||
{
|
||||
var attribute = handler.Target.GetType().GetCustomAttribute<CompilerGeneratedAttribute>();
|
||||
if (attribute != null)
|
||||
throw new InvalidOperationException("Handler passed to BindWeak refers to a compiler-generated class. You may not capture local variables in the handler");
|
||||
|
||||
var binding = new WeakPropertyChangedHandler<TBindTo, TMember>(target, targetSelector, handler);
|
||||
var binding = new WeakPropertyChangedHandler<TSource, TProperty>(target, targetSelector, handler);
|
||||
return new WeakPropertyChangedBinding(binding);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,16 +40,17 @@ namespace StyletUnitTests
|
|||
public IEventBinding BindStrong(NotifyingClass notifying)
|
||||
{
|
||||
// Must make sure the compiler doesn't generate an inner class for this, otherwise we're not testing the right thing
|
||||
return notifying.Bind(x => x.Foo, x => this.LastFoo = x);
|
||||
return notifying.Bind(x => x.Foo, (o, e) => this.LastFoo = e.NewValue);
|
||||
}
|
||||
|
||||
public IEventBinding BindWeak(NotifyingClass notifying)
|
||||
{
|
||||
return notifying.BindWeak(x => x.Foo, x => this.LastFoo = x);
|
||||
return notifying.BindWeak(x => x.Foo, (o, e) => this.LastFoo = e.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private string newVal;
|
||||
private object sender;
|
||||
|
||||
[TestFixtureSetUp]
|
||||
public void SetUpFixture()
|
||||
|
@ -61,6 +62,7 @@ namespace StyletUnitTests
|
|||
public void SetUp()
|
||||
{
|
||||
this.newVal = null;
|
||||
this.sender = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -68,7 +70,7 @@ namespace StyletUnitTests
|
|||
{
|
||||
string newVal = null;
|
||||
var c1 = new NotifyingClass();
|
||||
c1.Bind(x => x.Foo, x => newVal = x);
|
||||
c1.Bind(x => x.Foo, (o, e) => newVal = e.NewValue);
|
||||
c1.Foo = "bar";
|
||||
|
||||
Assert.AreEqual("bar", newVal);
|
||||
|
@ -79,7 +81,7 @@ namespace StyletUnitTests
|
|||
{
|
||||
string newVal = null;
|
||||
var c1 = new NotifyingClass();
|
||||
c1.Bind(x => x.Bar, x => newVal = x);
|
||||
c1.Bind(x => x.Bar, (o, e) => newVal = e.NewValue);
|
||||
c1.Foo = "bar";
|
||||
|
||||
Assert.AreEqual(null, newVal);
|
||||
|
@ -91,7 +93,7 @@ namespace StyletUnitTests
|
|||
string newVal = null;
|
||||
var c1 = new NotifyingClass();
|
||||
c1.Bar = "bar";
|
||||
c1.Bind(x => x.Bar, x => newVal = x);
|
||||
c1.Bind(x => x.Bar, (o, e) => newVal = e.NewValue);
|
||||
c1.NotifyAll();
|
||||
|
||||
Assert.AreEqual("bar", newVal);
|
||||
|
@ -112,12 +114,22 @@ namespace StyletUnitTests
|
|||
Assert.IsFalse(weakNotifying.TryGetTarget(out notifying));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StrongBindingPassesTarget()
|
||||
{
|
||||
var c1 = new NotifyingClass();
|
||||
object sender = null;
|
||||
c1.Bind(x => x.Foo, (o, e) => sender = o);
|
||||
c1.Foo = "foo";
|
||||
Assert.AreEqual(c1, sender);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StrongBindingUnbinds()
|
||||
{
|
||||
string newVal = null;
|
||||
var c1 = new NotifyingClass();
|
||||
var binding = c1.Bind(x => x.Bar, x => newVal = x);
|
||||
var binding = c1.Bind(x => x.Bar, (o, e) => newVal = e.NewValue);
|
||||
binding.Unbind();
|
||||
c1.Bar = "bar";
|
||||
|
||||
|
@ -128,7 +140,7 @@ namespace StyletUnitTests
|
|||
public void WeakBindingBinds()
|
||||
{
|
||||
var c1 = new NotifyingClass();
|
||||
c1.BindWeak(x => x.Foo, x => this.newVal = x);
|
||||
c1.BindWeak(x => x.Foo, (o, e) => this.newVal = e.NewValue);
|
||||
c1.Foo = "bar";
|
||||
|
||||
Assert.AreEqual("bar", this.newVal);
|
||||
|
@ -138,7 +150,7 @@ namespace StyletUnitTests
|
|||
public void WeakBindingIgnoresOtherProperties()
|
||||
{
|
||||
var c1 = new NotifyingClass();
|
||||
c1.BindWeak(x => x.Bar, x => this.newVal = x);
|
||||
c1.BindWeak(x => x.Bar, (o, e) => this.newVal = e.NewValue);
|
||||
c1.Foo = "bar";
|
||||
|
||||
Assert.IsNull(this.newVal);
|
||||
|
@ -149,7 +161,7 @@ namespace StyletUnitTests
|
|||
{
|
||||
var c1 = new NotifyingClass();
|
||||
c1.Bar = "bar";
|
||||
c1.BindWeak(x => x.Bar, x => this.newVal = x);
|
||||
c1.BindWeak(x => x.Bar, (o, e) => this.newVal = e.NewValue);
|
||||
c1.NotifyAll();
|
||||
|
||||
Assert.AreEqual("bar", this.newVal);
|
||||
|
@ -166,6 +178,8 @@ namespace StyletUnitTests
|
|||
var notifying = new NotifyingClass();
|
||||
binding.BindWeak(notifying);
|
||||
|
||||
|
||||
|
||||
binding = null;
|
||||
GC.Collect();
|
||||
Assert.IsFalse(weakBinding.TryGetTarget(out binding));
|
||||
|
@ -190,19 +204,28 @@ namespace StyletUnitTests
|
|||
public void WeakBindingUnbinds()
|
||||
{
|
||||
var c1 = new NotifyingClass();
|
||||
var binding = c1.BindWeak(x => x.Bar, x => this.newVal = x);
|
||||
var binding = c1.BindWeak(x => x.Bar, (o, e) => this.newVal = e.NewValue);
|
||||
binding.Unbind();
|
||||
c1.Bar = "bar";
|
||||
|
||||
Assert.IsNull(this.newVal);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BindWeakPassesSender()
|
||||
{
|
||||
var c1 = new NotifyingClass();
|
||||
c1.BindWeak(x => x.Foo, (o, e) => this.sender = o);
|
||||
c1.Foo = "foo";
|
||||
Assert.AreEqual(c1, this.sender);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BindWeakThrowsIfTargetIsCompilerGenerated()
|
||||
{
|
||||
var c1 = new NotifyingClass();
|
||||
string newVal = null;
|
||||
Assert.Throws<InvalidOperationException>(() => c1.BindWeak(x => x.Foo, x => newVal = x));
|
||||
Assert.Throws<InvalidOperationException>(() => c1.BindWeak(x => x.Foo, (o, e) => newVal = e.NewValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue