mirror of https://github.com/AMT-Cheif/Stylet.git
ActionExtension: Handle attached events
This commit is contained in:
parent
f7a7654984
commit
c777d295af
|
@ -61,6 +61,26 @@ namespace Stylet.Xaml
|
|||
this.Method = method;
|
||||
}
|
||||
|
||||
private ActionUnavailableBehaviour commandNullTargetBehaviour
|
||||
{
|
||||
get { return this.NullTarget == ActionUnavailableBehaviour.Default ? (Execute.InDesignMode ? ActionUnavailableBehaviour.Enable : ActionUnavailableBehaviour.Disable) : this.NullTarget; }
|
||||
}
|
||||
|
||||
private ActionUnavailableBehaviour commandActionNotFoundBehaviour
|
||||
{
|
||||
get { return this.ActionNotFound == ActionUnavailableBehaviour.Default ? ActionUnavailableBehaviour.Throw : this.ActionNotFound; }
|
||||
}
|
||||
|
||||
private ActionUnavailableBehaviour eventNullTargetBehaviour
|
||||
{
|
||||
get { return this.NullTarget == ActionUnavailableBehaviour.Default ? ActionUnavailableBehaviour.Enable : this.NullTarget; }
|
||||
}
|
||||
|
||||
private ActionUnavailableBehaviour eventActionNotFoundBehaviour
|
||||
{
|
||||
get { return this.ActionNotFound == ActionUnavailableBehaviour.Default ? ActionUnavailableBehaviour.Throw : this.ActionNotFound; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When implemented in a derived class, returns an object that is provided as the value of the target property for this markup extension.
|
||||
/// </summary>
|
||||
|
@ -79,19 +99,28 @@ namespace Stylet.Xaml
|
|||
if (propertyAsDependencyProperty != null && propertyAsDependencyProperty.PropertyType == typeof(ICommand))
|
||||
{
|
||||
// If they're in design mode and haven't set View.ActionTarget, default to looking sensible
|
||||
var nullTarget = this.NullTarget == ActionUnavailableBehaviour.Default ? (Execute.InDesignMode ? ActionUnavailableBehaviour.Enable : ActionUnavailableBehaviour.Disable) : this.NullTarget;
|
||||
var actionNotFound = this.ActionNotFound == ActionUnavailableBehaviour.Default ? ActionUnavailableBehaviour.Throw : this.ActionNotFound;
|
||||
return new CommandAction((DependencyObject)valueService.TargetObject, this.Method, nullTarget, actionNotFound);
|
||||
return new CommandAction((DependencyObject)valueService.TargetObject, this.Method, this.commandNullTargetBehaviour, this.commandActionNotFoundBehaviour);
|
||||
}
|
||||
|
||||
var propertyAsEventInfo = valueService.TargetProperty as EventInfo;
|
||||
if (propertyAsEventInfo != null)
|
||||
{
|
||||
var nullTarget = this.NullTarget == ActionUnavailableBehaviour.Default ? ActionUnavailableBehaviour.Enable : this.NullTarget;
|
||||
var actionNotFound = this.ActionNotFound == ActionUnavailableBehaviour.Default ? ActionUnavailableBehaviour.Throw : this.ActionNotFound;
|
||||
var ec = new EventAction((DependencyObject)valueService.TargetObject, propertyAsEventInfo, this.Method, nullTarget, actionNotFound);
|
||||
|
||||
var ec = new EventAction((DependencyObject)valueService.TargetObject, propertyAsEventInfo.EventHandlerType, this.Method, this.eventNullTargetBehaviour, this.eventActionNotFoundBehaviour);
|
||||
return ec.GetDelegate();
|
||||
}
|
||||
|
||||
// For attached events
|
||||
var propertyAsMethodInfo = valueService.TargetProperty as MethodInfo;
|
||||
if (propertyAsMethodInfo != null)
|
||||
{
|
||||
var parameters = propertyAsMethodInfo.GetParameters();
|
||||
if (parameters.Length == 2 && typeof(RoutedEventHandler).IsAssignableFrom(parameters[1].ParameterType))
|
||||
{
|
||||
var ec = new EventAction((DependencyObject)valueService.TargetObject, parameters[1].ParameterType, this.Method, this.eventNullTargetBehaviour, this.eventActionNotFoundBehaviour);
|
||||
return ec.GetDelegate();
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException("Can only use ActionExtension with a Command property or an event handler");
|
||||
}
|
||||
|
|
|
@ -29,9 +29,9 @@ namespace Stylet.Xaml
|
|||
private readonly string methodName;
|
||||
|
||||
/// <summary>
|
||||
/// Property on the WPF element we're returning a delegate for
|
||||
/// Type of event handler
|
||||
/// </summary>
|
||||
private readonly EventInfo targetProperty;
|
||||
private readonly Type eventHandlerType;
|
||||
|
||||
/// <summary>
|
||||
/// MethodInfo for the method to call. This has to exist, or we throw a wobbly
|
||||
|
@ -44,11 +44,11 @@ namespace Stylet.Xaml
|
|||
/// Initialises a new instance of the <see cref="EventAction"/> class
|
||||
/// </summary>
|
||||
/// <param name="subject">View whose View.ActionTarget we watch</param>
|
||||
/// <param name="targetProperty">Property on the WPF element we're returning a delegate for</param>
|
||||
/// <param name="eventHandlerType">Type of event handler we're returning a delegate for</param>
|
||||
/// <param name="methodName">The MyMethod in {s:Action MyMethod}, this is what we call when the event's fired</param>
|
||||
/// <param name="targetNullBehaviour">Behaviour for it the relevant View.ActionTarget is null</param>
|
||||
/// <param name="actionNonExistentBehaviour">Behaviour for if the action doesn't exist on the View.ActionTarget</param>
|
||||
public EventAction(DependencyObject subject, EventInfo targetProperty, string methodName, ActionUnavailableBehaviour targetNullBehaviour, ActionUnavailableBehaviour actionNonExistentBehaviour)
|
||||
public EventAction(DependencyObject subject, Type eventHandlerType, string methodName, ActionUnavailableBehaviour targetNullBehaviour, ActionUnavailableBehaviour actionNonExistentBehaviour)
|
||||
{
|
||||
if (targetNullBehaviour == ActionUnavailableBehaviour.Disable)
|
||||
throw new ArgumentException("Setting NullTarget = Disable is unsupported when used on an Event");
|
||||
|
@ -56,7 +56,7 @@ namespace Stylet.Xaml
|
|||
throw new ArgumentException("Setting ActionNotFound = Disable is unsupported when used on an Event");
|
||||
|
||||
this.subject = subject;
|
||||
this.targetProperty = targetProperty;
|
||||
this.eventHandlerType = eventHandlerType;
|
||||
this.methodName = methodName;
|
||||
this.targetNullBehaviour = targetNullBehaviour;
|
||||
this.actionNonExistentBehaviour = actionNonExistentBehaviour;
|
||||
|
@ -133,8 +133,7 @@ namespace Stylet.Xaml
|
|||
/// <returns>An event hander, which, when invoked, will invoke the action</returns>
|
||||
public Delegate GetDelegate()
|
||||
{
|
||||
var parameterType = this.targetProperty.EventHandlerType;
|
||||
var del = Delegate.CreateDelegate(parameterType, this, invokeCommandMethodInfo, false);
|
||||
var del = Delegate.CreateDelegate(this.eventHandlerType, this, invokeCommandMethodInfo, false);
|
||||
if (del == null)
|
||||
{
|
||||
var e = new ActionEventSignatureInvalidException(String.Format("Event being bound to does not have the '(object sender, EventArgsOrSubclass e)' signature we were expecting. Method {0} on target {1}", this.methodName, this.target));
|
||||
|
|
|
@ -20,6 +20,23 @@ namespace StyletUnitTests
|
|||
private Mock<IProvideValueTarget> provideValueTarget;
|
||||
private Mock<IServiceProvider> serviceProvider;
|
||||
|
||||
private class TestExtensions
|
||||
{
|
||||
public static readonly RoutedEvent TestEvent = EventManager.RegisterRoutedEvent("Test", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TestExtensions));
|
||||
public static void AddTestHandler(DependencyObject d, RoutedEventHandler handler)
|
||||
{
|
||||
UIElement uie = d as UIElement;
|
||||
if (uie != null)
|
||||
uie.AddHandler(TestExtensions.TestEvent, handler);
|
||||
}
|
||||
public static void RemoveTestHandler(DependencyObject d, RoutedEventHandler handler)
|
||||
{
|
||||
UIElement uie = d as UIElement;
|
||||
if (uie != null)
|
||||
uie.RemoveHandler(TestExtensions.TestEvent, handler);
|
||||
}
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
|
@ -62,6 +79,14 @@ namespace StyletUnitTests
|
|||
Assert.IsInstanceOf<RoutedEventHandler>(this.actionExtension.ProvideValue(this.serviceProvider.Object));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReturnsEventActionIfTargetIsAttachedEvent()
|
||||
{
|
||||
this.provideValueTarget.Setup(x => x.TargetProperty).Returns(typeof(TestExtensions).GetMethod("AddTestHandler"));
|
||||
|
||||
Assert.IsInstanceOf<RoutedEventHandler>(this.actionExtension.ProvideValue(this.serviceProvider.Object));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsArgumentExceptionIfTargetObjectNotDependencyPropertyOrEventInfo()
|
||||
{
|
||||
|
|
|
@ -80,57 +80,57 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void ThrowsIfNullTargetBehaviourIsDisable()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new EventAction(this.subject, this.eventInfo, "DoSomething", ActionUnavailableBehaviour.Disable, ActionUnavailableBehaviour.Enable));
|
||||
Assert.Throws<ArgumentException>(() => new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomething", ActionUnavailableBehaviour.Disable, ActionUnavailableBehaviour.Enable));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsIfNonExistentActionBehaviourIsDisable()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new EventAction(this.subject, this.eventInfo, "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Disable));
|
||||
Assert.Throws<ArgumentException>(() => new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Disable));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsIfTargetNullBehaviourIsThrowAndTargetBecomesNull()
|
||||
{
|
||||
var cmd = new EventAction(this.subject, this.eventInfo, "DoSomething", ActionUnavailableBehaviour.Throw, ActionUnavailableBehaviour.Enable);
|
||||
var cmd = new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomething", ActionUnavailableBehaviour.Throw, ActionUnavailableBehaviour.Enable);
|
||||
Assert.Throws<ActionTargetNullException>(() => View.SetActionTarget(this.subject, null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsIfActionNonExistentBehaviourIsThrowAndActionIsNonExistent()
|
||||
{
|
||||
var cmd = new EventAction(this.subject, this.eventInfo, "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Throw);
|
||||
var cmd = new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Throw);
|
||||
Assert.Throws<ActionNotFoundException>(() => View.SetActionTarget(this.subject, new Target2()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsIfMethodHasTooManyArguments()
|
||||
{
|
||||
Assert.Throws<ActionSignatureInvalidException>(() => new EventAction(this.subject, this.eventInfo, "DoSomethingWithTooManyArguments", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable));
|
||||
Assert.Throws<ActionSignatureInvalidException>(() => new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomethingWithTooManyArguments", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsIfMethodHasBadParameter()
|
||||
{
|
||||
Assert.Throws<ActionSignatureInvalidException>(() => new EventAction(this.subject, this.eventInfo, "DoSomethingWithBadArgument", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable));
|
||||
Assert.Throws<ActionSignatureInvalidException>(() => new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomethingWithBadArgument", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsIfMethodHasBadEventArgsParameter()
|
||||
{
|
||||
Assert.Throws<ActionSignatureInvalidException>(() => new EventAction(this.subject, this.eventInfo, "DoSomethingWithSenderAndBadArgument", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable));
|
||||
Assert.Throws<ActionSignatureInvalidException>(() => new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomethingWithSenderAndBadArgument", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsIfMethodHasTooManyParameters()
|
||||
{
|
||||
Assert.Throws<ActionSignatureInvalidException>(() => new EventAction(this.subject, this.eventInfo, "DoSomethingWithTooManyArguments", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable));
|
||||
Assert.Throws<ActionSignatureInvalidException>(() => new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomethingWithTooManyArguments", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InvokingCommandDoesNothingIfTargetIsNull()
|
||||
{
|
||||
var cmd = new EventAction(this.subject, this.eventInfo, "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
var cmd = new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
View.SetActionTarget(this.subject, null);
|
||||
cmd.GetDelegate().DynamicInvoke(null, null);
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void InvokingCommandDoesNothingIfActionIsNonExistent()
|
||||
{
|
||||
var cmd = new EventAction(this.subject, this.eventInfo, "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
var cmd = new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
View.SetActionTarget(this.subject, new Target2());
|
||||
cmd.GetDelegate().DynamicInvoke(null, null);
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void InvokingCommandCallsMethod()
|
||||
{
|
||||
var cmd = new EventAction(this.subject, this.eventInfo, "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
var cmd = new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
cmd.GetDelegate().DynamicInvoke(null, null);
|
||||
Assert.True(this.target.DoSomethingCalled);
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void InvokingCommandCallsMethodWithEventArgs()
|
||||
{
|
||||
var cmd = new EventAction(this.subject, this.eventInfo, "DoSomethingWithEventArgs", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
var cmd = new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomethingWithEventArgs", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
var arg = new RoutedEventArgs();
|
||||
cmd.GetDelegate().DynamicInvoke(null, arg);
|
||||
Assert.AreEqual(arg, this.target.EventArgs);
|
||||
|
@ -163,7 +163,7 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void InvokingCommandCallsMethodWithSenderAndEventArgs()
|
||||
{
|
||||
var cmd = new EventAction(this.subject, this.eventInfo, "DoSomethingWithObjectAndEventArgs", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
var cmd = new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomethingWithObjectAndEventArgs", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
var sender = new object();
|
||||
var arg = new RoutedEventArgs();
|
||||
cmd.GetDelegate().DynamicInvoke(sender, arg);
|
||||
|
@ -175,14 +175,14 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void BadEventHandlerSignatureThrows()
|
||||
{
|
||||
var cmd = new EventAction(this.subject, typeof(Subject).GetEvent("BadEventHandler"), "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
var cmd = new EventAction(this.subject, typeof(Subject).GetEvent("BadEventHandler").EventHandlerType, "DoSomething", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
Assert.Throws<ActionEventSignatureInvalidException>(() => cmd.GetDelegate());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PropagatesActionException()
|
||||
{
|
||||
var cmd = new EventAction(this.subject, this.eventInfo, "DoSomethingUnsuccessfully", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
var cmd = new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomethingUnsuccessfully", ActionUnavailableBehaviour.Enable, ActionUnavailableBehaviour.Enable);
|
||||
var e = Assert.Throws<TargetInvocationException>(() => cmd.GetDelegate().DynamicInvoke(null, null));
|
||||
Assert.IsInstanceOf<InvalidOperationException>(e.InnerException);
|
||||
Assert.AreEqual("foo", e.InnerException.Message);
|
||||
|
|
Loading…
Reference in New Issue