Add BindAndInvoke

Fixes #43
This commit is contained in:
Antony Male 2018-09-30 18:50:55 +01:00
parent eb2da81b9d
commit 8183c83190
2 changed files with 62 additions and 24 deletions

View File

@ -59,8 +59,7 @@ namespace Stylet
public void Unbind()
{
INotifyPropertyChanged inpc;
if (this.inpc.TryGetTarget(out inpc))
if (this.inpc.TryGetTarget(out INotifyPropertyChanged inpc))
inpc.PropertyChanged -= this.handler;
}
}
@ -87,8 +86,7 @@ namespace Stylet
private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
TSource source;
var got = this.source.TryGetTarget(out source);
var got = this.source.TryGetTarget(out TSource source);
// We should never hit this case. The PropertyChangedeventManager shouldn't call us if the source became null
Debug.Assert(got);
this.handler(source, new PropertyChangedExtendedEventArgs<TProperty>(this.propertyName, this.valueSelector(source)));
@ -96,8 +94,7 @@ namespace Stylet
public void Unbind()
{
TSource source;
if (this.source.TryGetTarget(out source))
if (this.source.TryGetTarget(out TSource source))
PropertyChangedEventManager.RemoveHandler(source, this.PropertyChangedHandler, this.propertyName);
}
}
@ -113,8 +110,7 @@ namespace Stylet
public void Unbind()
{
IEventBinding wrappedBinding;
if (this.wrappedBinding.TryGetTarget(out wrappedBinding))
if (this.wrappedBinding.TryGetTarget(out IEventBinding wrappedBinding))
wrappedBinding.Unbind();
}
}
@ -131,26 +127,61 @@ namespace Stylet
/// <returns>Something which can be used to undo the binding. You can discard it if you want</returns>
public static IEventBinding Bind<TSource, TProperty>(this TSource target, Expression<Func<TSource, TProperty>> targetSelector, EventHandler<PropertyChangedExtendedEventArgs<TProperty>> handler) where TSource : class, INotifyPropertyChanged
{
return BindImpl(target, targetSelector, handler, invoke: false);
}
/// <summary>
/// Strongly bind to PropertyChanged events for a particular property on a particular object,
/// and invoke the handler straight away
/// </summary>
/// <remarks>
/// This immediately calls <paramref name="handler"/> with the current value of the property.
/// </remarks>
/// <example>someObject.BindAndInvoke(x => x.PropertyNameToBindTo, newValue => /* do something with the new value */)</example>
/// <typeparam name="TSource">Type of object providing the PropertyChanged event</typeparam>
/// <typeparam name="TProperty">Type of property for which the event is raised</typeparam>
/// <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 BindAndInvoke<TSource, TProperty>(this TSource target, Expression<Func<TSource, TProperty>> targetSelector, EventHandler<PropertyChangedExtendedEventArgs<TProperty>> handler) where TSource : class, INotifyPropertyChanged
{
return BindImpl(target, targetSelector, handler, invoke: true);
}
private static IEventBinding BindImpl<TSource, TProperty>(TSource target, Expression<Func<TSource, TProperty>> targetSelector, EventHandler<PropertyChangedExtendedEventArgs<TProperty>> handler, bool invoke) where TSource : class, INotifyPropertyChanged
{
if (target == null)
throw new ArgumentNullException(nameof(target));
if (targetSelector == null)
throw new ArgumentNullException(nameof(targetSelector));
if (handler == null)
throw new ArgumentNullException(nameof(handler));
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<TSource>(target);
PropertyChangedEventHandler ourHandler = (o, e) =>
void ourHandler(object o, PropertyChangedEventArgs e)
{
if (e.PropertyName == propertyName || e.PropertyName == String.Empty)
{
TSource strongTarget;
if (weakTarget.TryGetTarget(out strongTarget))
if (weakTarget.TryGetTarget(out TSource strongTarget))
handler(strongTarget, new PropertyChangedExtendedEventArgs<TProperty>(propertyName, propertyAccess(strongTarget)));
}
};
}
target.PropertyChanged += ourHandler;
var listener = new StrongPropertyChangedBinding(target, ourHandler);
if (invoke)
{
handler(target, new PropertyChangedExtendedEventArgs<TProperty>(propertyName, propertyAccess(target)));
}
return listener;
}

View File

@ -40,16 +40,6 @@ namespace StyletUnitTests
}
}
private string newVal;
private object sender;
[SetUp]
public void SetUp()
{
this.newVal = null;
this.sender = null;
}
[Test]
public void StrongBindingBinds()
{
@ -76,8 +66,10 @@ namespace StyletUnitTests
public void StrongBindingListensToEmptyString()
{
string newVal = null;
var c1 = new NotifyingClass();
c1.Bar = "bar";
var c1 = new NotifyingClass
{
Bar = "bar"
};
c1.Bind(x => x.Bar, (o, e) => newVal = e.NewValue);
c1.NotifyAll();
@ -120,5 +112,20 @@ namespace StyletUnitTests
Assert.AreEqual(null, newVal);
}
[Test]
public void BindAndInvokeInvokes()
{
var c1 = new NotifyingClass()
{
Foo = "FooVal",
};
PropertyChangedExtendedEventArgs<string> ea = null;
c1.BindAndInvoke(s => s.Foo, (o, e) => ea = e);
Assert.NotNull(ea);
Assert.AreEqual("Foo", ea.PropertyName);
Assert.AreEqual("FooVal", ea.NewValue);
}
}
}