Introduction
Consider the following scenario:
public class SampleViewModel
{
public ICollectionView Customers { get; set; }
public ICommand DoSomethingWithCurrentCustomer { get; set; }
public SampleViewModel()
{
DoSomethingWithCurrentCustomer = new DelegateCommand(execute: () =>
{
}, canExecute: () => Customers.CurrentItem != null);
}
}
Main Problem
We bind the command of one button in the View to this command. The button will remain disabled while canexecute
returns
false
. But to get this working we should do another step which I don't consider entirely appropriate.
Customers.CurrentChanged += delegate
{
DoSomethingWithCurrentCustomer.RaiseCanExecuteChanged();
};
Or we should use Command Manager, which is accessible only in .NET. But I've provided another solution with the power of Expression trees. I've implemented
an ICommand
as below:
public class DelegateCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Func<T, Boolean> _canExecute;
public DelegateCommand(Action<T> execute, Expression<Func<T, Boolean>> canExecuteExpression = null)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
if (canExecuteExpression != null)
{
_canExecute = canExecuteExpression.Compile(); }
}
As you can see, I have two parameters for a constructor, Action
to execute
, Expression
to Parse
and Execute
.
Next Step
We should parse the provided lambda expression for two reasons:
- Find all Properties
- Find all Notifier Objects
The property in our sample is CurrentItem
and the notifier is Customers
. We should listen
to Customers' notifier's PropertyChanged
event handler.
If any change happens to CurrentItem
, we should call the RaiseCanExecuteChanged
method.
I understand that it's not enough, but it will come in handy for more than 90% of all scenarios. For others we can call RaiseCanExecuteChanged
manually.
But remember: we may not repeat ourselves. To find props and notifiers I've developed an expression visitor.
public sealed class PropObjectFinder : ExpressionVisitor
{
public IList<String> PropNames { get; set; }
public IList<Object> Objects { get; set; }
public PropObjectFinder(Expression expression)
{
PropNames = new Collection<String>();
Objects = new Collection<Object>();
Visit(expression);
}
protected override Expression VisitMember(MemberExpression node)
{ MemberInfo member = node.Member;
if (member is PropertyInfo && !PropNames.Contains(member.Name))
{
var prop = (PropertyInfo)member;
PropNames.Add(prop.Name);
}
return base.VisitMember(node);
}
protected override Expression VisitConstant(ConstantExpression node)
{ var value = node.Value;
if (value != null && !Objects.Contains(value))
Objects.Add(value);
return base.VisitConstant(node);
}
}
VisitMember
will provide all the used props, methods, and then we use prop names. We use this class in our delegate command class.
To find all notifiers from objects and all important props, we should do something like below:
var propFinder = new PropObjectFinder(canExecute);
_AllPropNamesToWatch = propFinder.PropNames.ToList();
var allObjects = propFinder.Objects;
foreach (var obj in allObjects)
{
FindAllNotifyableObjects(obj, propFinder.PropNames);
}
FindAllNotifyableObjects
's code is in the source code which is attached. After that we can start to listen
to all Notifires!
_AllNotifiers.ForEach(notifierObject =>
{
notifierObject.PropertyChanged += notifyableObject_PropertyChanged;
});
{
if (_AllPropNamesToWatch.Contains(e.PropertyName))
RaiseCanExecuteChanged();
}
And finally at RaiseCanExecuteChanged
we have:
public virtual void RaiseCanExecuteChanged()
{
var tempEventHandler = CanExecuteChanged;
if (tempEventHandler != null)
{
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
{
tempEventHandler(this, new EventArgs());
});
}
}
It's good to go. After that you may call the RaiseCanExecuteChanged
method manually for special purposes, which aren't supported,
but most of the scenarios are covered. And you can use it easily in .NET, Silverlight, WinRT, Windows Phone.