diff --git a/Stylet/PropertyChangedExtensions.cs b/Stylet/PropertyChangedExtensions.cs
index c8c3604..3b110ab 100644
--- a/Stylet/PropertyChangedExtensions.cs
+++ b/Stylet/PropertyChangedExtensions.cs
@@ -11,6 +11,29 @@ using System.Reflection;
namespace Stylet
{
+ ///
+ /// Extension of PropertyChangedEventArgs, which includes the new value of the property
+ ///
+ ///
+ public class PropertyChangedExtendedEventArgs : PropertyChangedEventArgs
+ {
+ ///
+ /// New value of the property
+ ///
+ public virtual TProperty NewValue { get; private set; }
+
+ ///
+ /// Instantiate a new PropertyChangedExtendedEventArgs
+ ///
+ /// Name of the property which changed
+ /// New value of the property which changed
+ public PropertyChangedExtendedEventArgs(string propertyName, TProperty newValue)
+ : base(propertyName)
+ {
+ this.NewValue = newValue;
+ }
+ }
+
///
/// A binding to a PropertyChanged event, which can be used to unbind the binding
///
@@ -51,11 +74,11 @@ namespace Stylet
internal class WeakPropertyChangedHandler : IEventBinding where TSource : class, INotifyPropertyChanged
{
private readonly WeakReference source;
- private Action handler;
+ private EventHandler> handler;
private string propertyName;
private Func valueSelector;
- public WeakPropertyChangedHandler(TSource source, Expression> selector, Action handler)
+ public WeakPropertyChangedHandler(TSource source, Expression> selector, EventHandler> 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(this.propertyName, this.valueSelector(source)));
}
public void Unbind()
@@ -110,21 +133,21 @@ namespace Stylet
/// MemberExpression selecting the property to observe for changes (e.g x => x.PropertyName)
/// Handler called whenever that property changed
/// Something which can be used to undo the binding. You can discard it if you want
- public static IEventBinding Bind(this TBindTo target, Expression> targetSelector, Action handler) where TBindTo : class, INotifyPropertyChanged
+ public static IEventBinding Bind(this TSource target, Expression> targetSelector, EventHandler> 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(target);
+ var weakTarget = new WeakReference(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(propertyName, propertyAccess(strongTarget)));
}
};
@@ -135,13 +158,21 @@ namespace Stylet
return listener;
}
- public static IEventBinding BindWeak(this TBindTo target, Expression> targetSelector, Action handler) where TBindTo : class, INotifyPropertyChanged
+ ///
+ /// Weakly bind to PropertyChanged events for a particular property on a particular object
+ ///
+ /// someObject.Bind(x => x.PropertyNameToBindTo, newValue => /* do something with the new value */)
+ /// Object raising the PropertyChanged event you're interested in
+ /// MemberExpression selecting the property to observe for changes (e.g x => x.PropertyName)
+ /// Handler called whenever that property changed
+ /// Something which can be used to undo the binding. You can discard it if you want
+ public static IEventBinding BindWeak(this TSource target, Expression> targetSelector, EventHandler> handler) where TSource : class, INotifyPropertyChanged
{
var attribute = handler.Target.GetType().GetCustomAttribute();
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(target, targetSelector, handler);
+ var binding = new WeakPropertyChangedHandler(target, targetSelector, handler);
return new WeakPropertyChangedBinding(binding);
}
}
diff --git a/StyletUnitTests/PropertyChangedExtensionsTests.cs b/StyletUnitTests/PropertyChangedExtensionsTests.cs
index 2390259..d8af7c1 100644
--- a/StyletUnitTests/PropertyChangedExtensionsTests.cs
+++ b/StyletUnitTests/PropertyChangedExtensionsTests.cs
@@ -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(() => c1.BindWeak(x => x.Foo, x => newVal = x));
+ Assert.Throws(() => c1.BindWeak(x => x.Foo, (o, e) => newVal = e.NewValue));
}
}
}