r0ycqq
Last Updated: February 25, 2016
·
662
· keboo

DependsOn extension method

Quite often I find myself writing lots of commands where the CanExecute method depends on the state of one (or many) data bound properties in the view model. The following is a typical example of what my view models looks like:

public class ViewModel : NotificationObject
{
    private readonly DelegateCommand _command;

    public ViewModel()
    {
        _command = new DelegateCommand(OnCommand, CanCommand);
    }

    public ICommand Command
    {
        get { return _command; }
    }

    private void OnCommand()
    {
        //Do something
    }

    private bool CanCommand()
    {
        if (string.IsNullOrWhiteSpace(Title))
            return false;
        return OtherValue > 10;
    }

    private int _otherValue;
    public int OtherValue
    {
        get { return _otherValue; }
        set
        {
            if (_otherValue != value)
            {
                _otherValue = value;
                RaisePropertyChanged(() => OtherValue);
                _command.RaiseCanExecuteChanged();
            }
        }
    }

    private string _title;
    public string Title
    {
        get { return _title; }
        set
        {
            if (_title != value)
            {
                _title = value;
                RaisePropertyChanged(() => Title);
                _command.RaiseCanExecuteChanged();
            }
        }
    }
}

Note: I am making heavy use of the Prism library.

Sure you can declare a DelegateCommand<T> and pass a single parameter to both the Execute and CanExecute delegate, but that does not handle cases where the command depends on multiple or simply different properties.

The following extension method will allow you tell the command which properties it is dependant upon, and check if it CanExecute.

public static void DependsOn<T>(this DelegateCommandBase command, Expression<Func<T>> propertyExpression)
{
    if (command == null) throw new ArgumentNullException("command");
    if (propertyExpression == null) throw new ArgumentNullException("propertyExpression");

    var memberExpression = propertyExpression.Body as MemberExpression;
    if (memberExpression == null)
        throw new ArgumentException("Must be a member access expression", "propertyExpression");

    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
        throw new ArgumentException("Must be a property expression", "propertyExpression");

    var constExp = memberExpression.Expression as ConstantExpression;
    if (constExp == null)
        throw new ArgumentException("Must be a constant expression", "propertyExpression");

    var propertyChanged = constExp.Value as INotifyPropertyChanged;
    if (propertyChanged == null)
        throw new ArgumentException(string.Format("{0} must implement INotifyPropertyChanged", constExp.Type.Name));

    var propertyName = propertyInfo.Name;
    propertyChanged.PropertyChanged +=
        (sender, args) =>
        {
            if (string.Equals(args.PropertyName, propertyName,
                                StringComparison.OrdinalIgnoreCase))
                command.RaiseCanExecuteChanged();
        };
}

This then changes the view model to look like this:

public class ViewModel : NotificationObject
{
    private readonly DelegateCommand _command;

    public ViewModel()
    {
        _command = new DelegateCommand(OnCommand, CanCommand);
        _command.DependsOn(() => Title);
        _command.DependsOn(() => OtherValue);
    }

    public ICommand Command
    {
        get { return _command; }
    }

    private void OnCommand()
    {
        //Do something
    }

    private bool CanCommand()
    {
        if (string.IsNullOrWhiteSpace(Title))
            return false;
        return OtherValue > 10;
    }

    private int _otherValue;
    public int OtherValue
    {
        get { return _otherValue; }
        set
        {
            if (_otherValue != value)
            {
                _otherValue = value;
                RaisePropertyChanged(() => OtherValue);
            }
        }
    }

    private string _title;
    public string Title
    {
        get { return _title; }
        set
        {
            if (_title != value)
            {
                _title = value;
                RaisePropertyChanged(() => Title);
            }
        }
    }
}

The setters for the properties no longer need to invoke RaiseCanExecuteChanged. These are replaced by two calls in the constructor to register property changed event handlers for the two properties.

Source: http://dotnetgeek.tumblr.com/post/33755827925/dependson-extension-method

Say Thanks
Respond