Skip to content

Commit

Permalink
Use a DependencyProperty in CommandAction/EventAction to watch Action…
Browse files Browse the repository at this point in the history
…Target for changes

Seems to work much better. Could probably remove quite a bit of code though
a sensible base class
  • Loading branch information
canton7 committed Feb 24, 2015
1 parent f75833f commit b98226b
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 221 deletions.
95 changes: 0 additions & 95 deletions Stylet/DependencyPropertyChangeNotifier.cs

This file was deleted.

1 change: 0 additions & 1 deletion Stylet/Stylet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
38 changes: 24 additions & 14 deletions Stylet/Xaml/CommandAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using Expressions = System.Linq.Expressions;

Expand All @@ -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));

Expand All @@ -41,11 +42,20 @@ public class CommandAction : ICommand
/// </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>
Expand All @@ -60,20 +70,22 @@ public CommandAction(DependencyObject subject, string methodName, ActionUnavaila
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
{
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
Expand All @@ -82,7 +94,6 @@ private void UpdateGuardAndMethod()
// We'll just wait until the ActionTarget is assigned, and we're called again
if (newTarget == View.InitialActionTarget)
{
this.target = newTarget;
return;
}

Expand Down Expand Up @@ -146,15 +157,14 @@ private void UpdateGuardAndMethod()
}
}

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();
Expand Down
31 changes: 21 additions & 10 deletions Stylet/Xaml/EventAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
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);
Expand Down Expand Up @@ -38,7 +39,17 @@ public class EventAction
/// </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
Expand All @@ -61,15 +72,17 @@ public EventAction(DependencyObject subject, Type eventHandlerType, string metho
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
Expand All @@ -78,7 +91,6 @@ private void UpdateMethod()
// We'll just wait until the ActionTarget is assigned, and we're called again
if (newTarget == View.InitialActionTarget)
{
this.target = newTarget;
return;
}

Expand Down Expand Up @@ -126,7 +138,6 @@ private void UpdateMethod()
}
}

this.target = newTarget;
this.targetMethodInfo = targetMethodInfo;
}

Expand Down
14 changes: 14 additions & 0 deletions StyletUnitTests/CommandActionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,19 @@ public void DoesNotRetainTarget()

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);
}
}
}
100 changes: 0 additions & 100 deletions StyletUnitTests/DependencyPropertyChangeNotifierTests.cs

This file was deleted.

0 comments on commit b98226b

Please sign in to comment.