mirror of https://github.com/AMT-Cheif/Stylet.git
Use a DependencyProperty in CommandAction/EventAction to watch ActionTarget for changes
Seems to work much better. Could probably remove quite a bit of code though a sensible base class
This commit is contained in:
parent
f75833f508
commit
b98226b29f
|
@ -1,95 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Stylet
|
||||
{
|
||||
/// <summary>
|
||||
/// DependencyProperty change notifier which does not root the DependencyObject
|
||||
/// </summary>
|
||||
// Adapted from https://agsmith.wordpress.com/2008/04/07/propertydescriptor-addvaluechanged-alternative/
|
||||
public class DependencyPropertyChangeNotifier : DependencyObject, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Watch for changes of the given property on the given propertySource
|
||||
/// </summary>
|
||||
/// <param name="propertySource">Object to observe a property on</param>
|
||||
/// <param name="property">Property on the object to observe</param>
|
||||
/// <param name="handler">Handler to invoke when the property changes</param>
|
||||
/// <returns>The constructed PropertyChangeNotifier</returns>
|
||||
public static DependencyPropertyChangeNotifier AddValueChanged(DependencyObject propertySource, PropertyPath property, PropertyChangedCallback handler)
|
||||
{
|
||||
return new DependencyPropertyChangeNotifier(propertySource, property, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Watch for changes of the given property on the given propertySource
|
||||
/// </summary>
|
||||
/// <param name="propertySource">Object to observe a property on</param>
|
||||
/// <param name="property">Property on the object to observe</param>
|
||||
/// <param name="handler">Handler to invoke when the property changes</param>
|
||||
/// <returns>The constructed PropertyChangeNotifier</returns>
|
||||
public static DependencyPropertyChangeNotifier AddValueChanged(DependencyObject propertySource, DependencyProperty property, PropertyChangedCallback handler)
|
||||
{
|
||||
if (property == null)
|
||||
throw new ArgumentNullException("property");
|
||||
return AddValueChanged(propertySource, new PropertyPath(property), handler);
|
||||
}
|
||||
|
||||
private PropertyChangedCallback handler;
|
||||
private readonly WeakReference<DependencyObject> propertySource;
|
||||
|
||||
private DependencyPropertyChangeNotifier(DependencyObject propertySource, PropertyPath property, PropertyChangedCallback handler)
|
||||
{
|
||||
if (propertySource == null)
|
||||
throw new ArgumentNullException("propertySource");
|
||||
if (property == null)
|
||||
throw new ArgumentNullException("property");
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException("handler");
|
||||
|
||||
this.propertySource = new WeakReference<DependencyObject>(propertySource);
|
||||
|
||||
var binding = new Binding()
|
||||
{
|
||||
Path = property,
|
||||
Mode = BindingMode.OneWay,
|
||||
Source = propertySource
|
||||
};
|
||||
BindingOperations.SetBinding(this, ValueProperty, binding);
|
||||
|
||||
// Needs to be set after binding set, so it doesn't catch the initial property set
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
private void OnValueChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
// This happens on the firsrt invocation ever, when the initial value is set
|
||||
// and on disposal
|
||||
if (this.handler == null)
|
||||
return;
|
||||
|
||||
// Target *should* never be null at this point...
|
||||
DependencyObject propertySource = null;
|
||||
this.propertySource.TryGetTarget(out propertySource);
|
||||
Debug.Assert(propertySource != null);
|
||||
this.handler(propertySource, e);
|
||||
}
|
||||
|
||||
private static readonly DependencyProperty ValueProperty =
|
||||
DependencyProperty.Register("Value", typeof(object), typeof(DependencyPropertyChangeNotifier), new FrameworkPropertyMetadata(null, (d, e) =>
|
||||
{
|
||||
((DependencyPropertyChangeNotifier)d).OnValueChanged(e);
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// Releases the binding
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.handler = null; // Otherwise it's called as the binding is unset
|
||||
BindingOperations.ClearBinding(this, ValueProperty);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -52,7 +52,6 @@
|
|||
<Compile Include="Logging\ILogger.cs" />
|
||||
<Compile Include="Logging\NullLogger.cs" />
|
||||
<Compile Include="Logging\TraceLogger.cs" />
|
||||
<Compile Include="DependencyPropertyChangeNotifier.cs" />
|
||||
<Compile Include="StyletIoC\Creation\ICreator.cs" />
|
||||
<Compile Include="StyletIoC\Creation\IRegistration.cs" />
|
||||
<Compile Include="StyletIoC\Internal\Builders\BuilderAbstractFactoryBinding.cs" />
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.ComponentModel;
|
|||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Expressions = System.Linq.Expressions;
|
||||
|
||||
|
@ -17,7 +18,7 @@ namespace Stylet.Xaml
|
|||
/// Watches the current View.ActionTarget, and looks for a method with the given name, calling it when the ICommand is called.
|
||||
/// If a bool property with name Get(methodName) exists, it will be observed and used to enable/disable the ICommand.
|
||||
/// </remarks>
|
||||
public class CommandAction : ICommand
|
||||
public class CommandAction : DependencyObject, ICommand
|
||||
{
|
||||
private static readonly ILogger logger = LogManager.GetLogger(typeof(CommandAction));
|
||||
|
||||
|
@ -41,11 +42,20 @@ namespace Stylet.Xaml
|
|||
/// </summary>
|
||||
private MethodInfo targetMethodInfo;
|
||||
|
||||
private object target;
|
||||
|
||||
private readonly ActionUnavailableBehaviour targetNullBehaviour;
|
||||
private readonly ActionUnavailableBehaviour actionNonExistentBehaviour;
|
||||
|
||||
private object target
|
||||
{
|
||||
get { return (object)GetValue(targetProperty); }
|
||||
}
|
||||
|
||||
private static readonly DependencyProperty targetProperty =
|
||||
DependencyProperty.Register("target", typeof(object), typeof(CommandAction), new PropertyMetadata(null, (d, e) =>
|
||||
{
|
||||
((CommandAction)d).UpdateGuardAndMethod(e.OldValue, e.NewValue);
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// Initialises a new instance of the <see cref="CommandAction"/> class
|
||||
/// </summary>
|
||||
|
@ -60,10 +70,13 @@ namespace Stylet.Xaml
|
|||
this.targetNullBehaviour = targetNullBehaviour;
|
||||
this.actionNonExistentBehaviour = actionNonExistentBehaviour;
|
||||
|
||||
this.UpdateGuardAndMethod();
|
||||
|
||||
// Observe the View.ActionTarget for changes, and re-bind the guard property and MethodInfo if it changes
|
||||
DependencyPropertyChangeNotifier.AddValueChanged(this.Subject, View.ActionTargetProperty, (o, e) => this.UpdateGuardAndMethod());
|
||||
var binding = new Binding()
|
||||
{
|
||||
Path = new PropertyPath(View.ActionTargetProperty),
|
||||
Mode = BindingMode.OneWay,
|
||||
Source = this.Subject,
|
||||
};
|
||||
BindingOperations.SetBinding(this, targetProperty, binding);
|
||||
}
|
||||
|
||||
private string GuardName
|
||||
|
@ -71,9 +84,8 @@ namespace Stylet.Xaml
|
|||
get { return "Can" + this.MethodName; }
|
||||
}
|
||||
|
||||
private void UpdateGuardAndMethod()
|
||||
private void UpdateGuardAndMethod(object oldTarget, object newTarget)
|
||||
{
|
||||
var newTarget = View.GetActionTarget(this.Subject);
|
||||
MethodInfo targetMethodInfo = null;
|
||||
|
||||
// If it's being set to the initial value, ignore it
|
||||
|
@ -82,7 +94,6 @@ namespace Stylet.Xaml
|
|||
// We'll just wait until the ActionTarget is assigned, and we're called again
|
||||
if (newTarget == View.InitialActionTarget)
|
||||
{
|
||||
this.target = newTarget;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -146,15 +157,14 @@ namespace Stylet.Xaml
|
|||
}
|
||||
}
|
||||
|
||||
var oldTarget = this.target as INotifyPropertyChanged;
|
||||
if (oldTarget != null)
|
||||
PropertyChangedEventManager.RemoveHandler(oldTarget, this.PropertyChangedHandler, this.GuardName);
|
||||
var oldInpc = oldTarget as INotifyPropertyChanged;
|
||||
if (oldInpc != null)
|
||||
PropertyChangedEventManager.RemoveHandler(oldInpc, this.PropertyChangedHandler, this.GuardName);
|
||||
|
||||
var inpc = newTarget as INotifyPropertyChanged;
|
||||
if (this.guardPropertyGetter != null && inpc != null)
|
||||
PropertyChangedEventManager.AddHandler(inpc, this.PropertyChangedHandler, this.GuardName);
|
||||
|
||||
this.target = newTarget;
|
||||
this.targetMethodInfo = targetMethodInfo;
|
||||
|
||||
this.UpdateCanExecute();
|
||||
|
|
|
@ -4,13 +4,14 @@ using System.ComponentModel;
|
|||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Stylet.Xaml
|
||||
{
|
||||
/// <summary>
|
||||
/// Created by ActionExtension, this can return a delegate suitable adding binding to an event, and can call a method on the View.ActionTarget
|
||||
/// </summary>
|
||||
public class EventAction
|
||||
public class EventAction : DependencyObject
|
||||
{
|
||||
private static readonly ILogger logger = LogManager.GetLogger(typeof(EventAction));
|
||||
private static readonly MethodInfo invokeCommandMethodInfo = typeof(EventAction).GetMethod("InvokeCommand", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
@ -38,7 +39,17 @@ namespace Stylet.Xaml
|
|||
/// </summary>
|
||||
private MethodInfo targetMethodInfo;
|
||||
|
||||
private object target;
|
||||
private object target
|
||||
{
|
||||
get { return (object)GetValue(targetProperty); }
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for target. This enables animation, styling, binding, etc...
|
||||
private static readonly DependencyProperty targetProperty =
|
||||
DependencyProperty.Register("target", typeof(object), typeof(EventAction), new PropertyMetadata(null, (d, e) =>
|
||||
{
|
||||
((EventAction)d).UpdateMethod(e.NewValue);
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// Initialises a new instance of the <see cref="EventAction"/> class
|
||||
|
@ -61,15 +72,17 @@ namespace Stylet.Xaml
|
|||
this.targetNullBehaviour = targetNullBehaviour;
|
||||
this.actionNonExistentBehaviour = actionNonExistentBehaviour;
|
||||
|
||||
this.UpdateMethod();
|
||||
|
||||
// Observe the View.ActionTarget for changes, and re-bind the guard property and MethodInfo if it changes
|
||||
DependencyPropertyChangeNotifier.AddValueChanged(this.subject, View.ActionTargetProperty, (o, e) => this.UpdateMethod());
|
||||
var binding = new Binding()
|
||||
{
|
||||
Path = new PropertyPath(View.ActionTargetProperty),
|
||||
Mode = BindingMode.OneWay,
|
||||
Source = this.subject,
|
||||
};
|
||||
BindingOperations.SetBinding(this, targetProperty, binding);
|
||||
}
|
||||
|
||||
private void UpdateMethod()
|
||||
private void UpdateMethod(object newTarget)
|
||||
{
|
||||
var newTarget = View.GetActionTarget(this.subject);
|
||||
MethodInfo targetMethodInfo = null;
|
||||
|
||||
// If it's being set to the initial value, ignore it
|
||||
|
@ -78,7 +91,6 @@ namespace Stylet.Xaml
|
|||
// We'll just wait until the ActionTarget is assigned, and we're called again
|
||||
if (newTarget == View.InitialActionTarget)
|
||||
{
|
||||
this.target = newTarget;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -126,7 +138,6 @@ namespace Stylet.Xaml
|
|||
}
|
||||
}
|
||||
|
||||
this.target = newTarget;
|
||||
this.targetMethodInfo = targetMethodInfo;
|
||||
}
|
||||
|
||||
|
|
|
@ -270,5 +270,19 @@ namespace StyletUnitTests
|
|||
|
||||
Assert.False(weakView.IsAlive);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OperatesAfterCollection()
|
||||
{
|
||||
var view = new DependencyObject();
|
||||
var cmd = new CommandAction(view, "DoSomething", ActionUnavailableBehaviour.Throw, ActionUnavailableBehaviour.Throw);
|
||||
|
||||
GC.Collect();
|
||||
|
||||
View.SetActionTarget(view, this.target);
|
||||
|
||||
cmd.Execute(null);
|
||||
Assert.IsTrue(this.target.DoSomethingCalled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
using NUnit.Framework;
|
||||
using Stylet;
|
||||
using Stylet.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace StyletUnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class DependencyPropertyChangeNotifierTests
|
||||
{
|
||||
[Test]
|
||||
public void ThrowsIfTargetIsNull()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => DependencyPropertyChangeNotifier.AddValueChanged(null, View.ActionTargetProperty, (d, e) => { }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsIfPropertyIsNull()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => DependencyPropertyChangeNotifier.AddValueChanged(new DependencyObject(), (PropertyPath)null, (d, e) => { }));
|
||||
Assert.Throws<ArgumentNullException>(() => DependencyPropertyChangeNotifier.AddValueChanged(new DependencyObject(), (DependencyProperty)null, (d, e) => { }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsIfHandlerIsNull()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => DependencyPropertyChangeNotifier.AddValueChanged(new DependencyObject(), View.ActionTargetProperty, null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DoesNotRetainTarget()
|
||||
{
|
||||
var target = new DependencyObject();
|
||||
var weakTarget = new WeakReference(target);
|
||||
|
||||
DependencyPropertyChangeNotifier.AddValueChanged(target, View.ActionTargetProperty, (d, e) => { });
|
||||
|
||||
target = null;
|
||||
GC.Collect();
|
||||
|
||||
Assert.IsFalse(weakTarget.IsAlive);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NotifiesOfChange()
|
||||
{
|
||||
var view = new DependencyObject();
|
||||
|
||||
var value1 = new object();
|
||||
var value2 = new object();
|
||||
|
||||
View.SetActionTarget(view, value1);
|
||||
|
||||
DependencyObject subject = null;
|
||||
DependencyPropertyChangedEventArgs ea = default(DependencyPropertyChangedEventArgs);
|
||||
DependencyPropertyChangeNotifier.AddValueChanged(view, View.ActionTargetProperty, (d, e) =>
|
||||
{
|
||||
subject = d;
|
||||
ea = e;
|
||||
});
|
||||
|
||||
View.SetActionTarget(view, value2);
|
||||
|
||||
Assert.AreEqual(view, subject);
|
||||
Assert.AreEqual(value1, ea.OldValue);
|
||||
Assert.AreEqual(value2, ea.NewValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HandlerNotCalledBeforeDependencyPropertyChanged()
|
||||
{
|
||||
var view = new DependencyObject();
|
||||
|
||||
var called = false;
|
||||
DependencyPropertyChangeNotifier.AddValueChanged(view, View.ActionTargetProperty, (d, e) => called = true);
|
||||
|
||||
Assert.IsFalse(called);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DisposeUnsubscribes()
|
||||
{
|
||||
var view = new DependencyObject();
|
||||
|
||||
var called = false;
|
||||
var disposable = DependencyPropertyChangeNotifier.AddValueChanged(view, View.ActionTargetProperty, (d, e) => called = true);
|
||||
|
||||
disposable.Dispose();
|
||||
|
||||
View.SetActionTarget(view, new object());
|
||||
|
||||
Assert.IsFalse(called);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,7 +64,6 @@
|
|||
<Compile Include="ConductorOneActiveTests.cs" />
|
||||
<Compile Include="ConductorTests.cs" />
|
||||
<Compile Include="DebugConverterTests.cs" />
|
||||
<Compile Include="DependencyPropertyChangeNotifierTests.cs" />
|
||||
<Compile Include="StyletIoC\StyletIoCFuncFactoryTests.cs" />
|
||||
<Compile Include="StyletIoC\StyletIoCInstanceBindingTests.cs" />
|
||||
<Compile Include="StyletIoC\StyletIoCModuleTests.cs" />
|
||||
|
|
Loading…
Reference in New Issue