From 8183c831901441ed61c604e8d1b9d7e6d7a8c861 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Sun, 30 Sep 2018 18:50:55 +0100 Subject: [PATCH] Add BindAndInvoke Fixes #43 --- Stylet/PropertyChangedExtensions.cs | 55 +++++++++++++++---- .../PropertyChangedExtensionsTests.cs | 31 +++++++---- 2 files changed, 62 insertions(+), 24 deletions(-) diff --git a/Stylet/PropertyChangedExtensions.cs b/Stylet/PropertyChangedExtensions.cs index 4a09487..4a09a78 100644 --- a/Stylet/PropertyChangedExtensions.cs +++ b/Stylet/PropertyChangedExtensions.cs @@ -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(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 /// Something which can be used to undo the binding. You can discard it if you want public static IEventBinding Bind(this TSource target, Expression> targetSelector, EventHandler> handler) where TSource : class, INotifyPropertyChanged { + return BindImpl(target, targetSelector, handler, invoke: false); + } + + /// + /// Strongly bind to PropertyChanged events for a particular property on a particular object, + /// and invoke the handler straight away + /// + /// + /// This immediately calls with the current value of the property. + /// + /// someObject.BindAndInvoke(x => x.PropertyNameToBindTo, newValue => /* do something with the new value */) + /// Type of object providing the PropertyChanged event + /// Type of property for which the event is raised + /// 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 BindAndInvoke(this TSource target, Expression> targetSelector, EventHandler> handler) where TSource : class, INotifyPropertyChanged + { + return BindImpl(target, targetSelector, handler, invoke: true); + } + + private static IEventBinding BindImpl(TSource target, Expression> targetSelector, EventHandler> 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(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(propertyName, propertyAccess(strongTarget))); } - }; + } target.PropertyChanged += ourHandler; var listener = new StrongPropertyChangedBinding(target, ourHandler); + if (invoke) + { + handler(target, new PropertyChangedExtendedEventArgs(propertyName, propertyAccess(target))); + } + return listener; } diff --git a/StyletUnitTests/PropertyChangedExtensionsTests.cs b/StyletUnitTests/PropertyChangedExtensionsTests.cs index dfdf0d1..bb3ed16 100644 --- a/StyletUnitTests/PropertyChangedExtensionsTests.cs +++ b/StyletUnitTests/PropertyChangedExtensionsTests.cs @@ -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 ea = null; + c1.BindAndInvoke(s => s.Foo, (o, e) => ea = e); + + Assert.NotNull(ea); + Assert.AreEqual("Foo", ea.PropertyName); + Assert.AreEqual("FooVal", ea.NewValue); + } } }