Actions throw on execute of ActionTarget hasn't changed from the default

This commit is contained in:
Antony Male 2015-01-18 22:02:06 +00:00
parent 3efcb250cf
commit b3601e6f81
5 changed files with 71 additions and 3 deletions

View File

@ -125,6 +125,15 @@ namespace Stylet.Xaml
}
}
/// <summary>
/// The View.ActionTarget was not set. This probably means the item is in a ContextMenu/Popup
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")]
public class ActionNotSetException : Exception
{
internal ActionNotSetException(string message) : base(message) { }
}
/// <summary>
/// The Action Target was null, and shouldn't have been (NullTarget = Throw)
/// </summary>

View File

@ -81,7 +81,10 @@ namespace Stylet.Xaml
// If they've opted to throw if the target is null, then this will cause that exception.
// We'll just wait until the ActionTarget is assigned, and we're called again
if (newTarget == View.InitialActionTarget)
{
this.target = newTarget;
return;
}
this.guardPropertyGetter = null;
if (newTarget == null)
@ -95,7 +98,7 @@ namespace Stylet.Xaml
}
else
{
logger.Info("ActionTarget on element {0} is null (method name is {1}), nut NullTarget is not Throw, so carrying on", this.Subject, this.MethodName);
logger.Info("ActionTarget on element {0} is null (method name is {1}), but NullTarget is not Throw, so carrying on", this.Subject, this.MethodName);
}
}
else
@ -147,12 +150,11 @@ namespace Stylet.Xaml
if (oldTarget != null)
oldTarget.PropertyChanged -= this.PropertyChangedHandler;
this.target = newTarget;
var inpc = newTarget as INotifyPropertyChanged;
if (this.guardPropertyGetter != null && inpc != null)
inpc.PropertyChanged += this.PropertyChangedHandler;
this.target = newTarget;
this.targetMethodInfo = targetMethodInfo;
this.UpdateCanExecute();
@ -183,6 +185,13 @@ namespace Stylet.Xaml
/// <returns>true if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter)
{
// This can happen if the ActionTarget hasn't been set from its default -
// e.g. if the button/etc in question is in a ContextMenu/Popup, which attached properties
// aren't inherited by.
// Show the control as enabled, but throw if they try and click on it
if (this.target == View.InitialActionTarget)
return true;
// It's enabled only if both the targetNull and actionNonExistent tests pass
// Throw is handled when the target is set
@ -215,6 +224,17 @@ namespace Stylet.Xaml
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public void Execute(object parameter)
{
// If we've made it this far and the target is still the default, then something's wrong
// Make sure they know
if (this.target == View.InitialActionTarget)
{
var e = new ActionNotSetException(String.Format("View.ActionTarget not on control {0} (method {1}). " +
"This probably means the control hasn't inherited it from a parent, e.g. because a ContextMenu or Popup sits in the visual tree. " +
"You will need so set 's:View.ActionTarget' explicitly. See the wiki for more details.", this.Subject, this.MethodName));
logger.Error(e);
throw e;
}
// Any throwing would have been handled prior to this
if (this.target == null || this.targetMethodInfo == null)
return;

View File

@ -77,7 +77,10 @@ namespace Stylet.Xaml
// If they've opted to throw if the target is null, then this will cause that exception.
// We'll just wait until the ActionTarget is assigned, and we're called again
if (newTarget == View.InitialActionTarget)
{
this.target = newTarget;
return;
}
if (newTarget == null)
{
@ -146,6 +149,17 @@ namespace Stylet.Xaml
// ReSharper disable once UnusedMember.Local
private void InvokeCommand(object sender, EventArgs e)
{
// If we've made it this far and the target is still the default, then something's wrong
// Make sure they know
if (this.target == View.InitialActionTarget)
{
var ex = new ActionNotSetException(String.Format("View.ActionTarget not on control {0} (method {1}). " +
"This probably means the control hasn't inherited it from a parent, e.g. because a ContextMenu or Popup sits in the visual tree. " +
"You will need so set 's:View.ActionTarget' explicitly. See the wiki for more details.", this.subject, this.methodName));
logger.Error(ex);
throw ex;
}
// Any throwing will have been handled above
if (this.target == null || this.targetMethodInfo == null)
return;

View File

@ -221,5 +221,21 @@ namespace StyletUnitTests
var e = Assert.Throws<InvalidOperationException>(() => cmd.Execute(null));
Assert.AreEqual("woo", e.Message);
}
[Test]
public void ControlIsEnabledIfTargetIsDefault()
{
View.SetActionTarget(this.subject, View.InitialActionTarget);
var cmd = new CommandAction(this.subject, "DoSomething", ActionUnavailableBehaviour.Throw, ActionUnavailableBehaviour.Throw);
Assert.True(cmd.CanExecute(null));
}
[Test]
public void ExecuteThrowsIfTargetIsDefault()
{
View.SetActionTarget(this.subject, View.InitialActionTarget);
var cmd = new CommandAction(this.subject, "DoSomething", ActionUnavailableBehaviour.Throw, ActionUnavailableBehaviour.Throw);
Assert.Throws<ActionNotSetException>(() => cmd.Execute(null));
}
}
}

View File

@ -190,5 +190,14 @@ namespace StyletUnitTests
Assert.IsInstanceOf<InvalidOperationException>(e.InnerException);
Assert.AreEqual("foo", e.InnerException.Message);
}
[Test]
public void ExecuteThrowsIfActionTargetIsDefault()
{
View.SetActionTarget(this.subject, View.InitialActionTarget);
var cmd = new EventAction(this.subject, this.eventInfo.EventHandlerType, "DoSomething", ActionUnavailableBehaviour.Throw, ActionUnavailableBehaviour.Throw);
var e = Assert.Throws<TargetInvocationException>(() => cmd.GetDelegate().DynamicInvoke(null, null));
Assert.IsInstanceOf<ActionNotSetException>(e.InnerException);
}
}
}