Click here to Skip to main content
15,861,125 members
Articles / Desktop Programming / XAML
Tip/Trick

MVVM ICommand, Lambda Expression for CanExecuteChanged

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
1 Mar 2013CPOL1 min read 34.2K   12   3
Parse CanExecute to detect when CanExecuteChanged should be called.

Introduction

Consider the following scenario:

C#
public class SampleViewModel
{
    public ICollectionView Customers { get; set; }
    public ICommand DoSomethingWithCurrentCustomer { get; set; }
    public SampleViewModel()
    {
        DoSomethingWithCurrentCustomer = new DelegateCommand(execute: () =>
        {
            // To Do
        }, 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.

C#
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:

C#
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(); // Expression will compiled to code which is invokable
        }
    }

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:

  1. Find all Properties
  2. 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.

C#
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)
    {   // To Find PropNames
        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)
    {   // To Find Object which maybe are notifier !
        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:

C#
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! 

C#
_AllNotifiers.ForEach(notifierObject =>
    {
        notifierObject.PropertyChanged += notifyableObject_PropertyChanged;
    });   
{
    if (_AllPropNamesToWatch.Contains(e.PropertyName))
        RaiseCanExecuteChanged();
}

And finally at RaiseCanExecuteChanged we have:

C#
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
Iran (Islamic Republic of) Iran (Islamic Republic of)
Technical manager who leads technological development activities in Fermium team. More info about Fermium team in http://fermium.co/

Comments and Discussions

 
QuestionDependency Tracking Pin
MichaelLPerry3-Dec-12 2:42
MichaelLPerry3-Dec-12 2:42 
AnswerSource Code & Reply Pin
Yasser Moradi DNP3-Dec-12 20:59
Yasser Moradi DNP3-Dec-12 20:59 
AnswerRe: Dependency Tracking Pin
Dmitri Raiko1-Mar-13 5:08
Dmitri Raiko1-Mar-13 5:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.