Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVVM bind RelayCommand CanExecute to a Property?

Tags:

wpf

mvvm-light

I have a Timer and three buttons to control it: Start, Stop, and Pause.
Each button is bound to a RelayCommand.
I have a TimerState property of type enum TimerState. (This is useful for setting various GUI elements.)
Is there a way to somehow bind the RelayCommands' CanExecute functionality to the TimerState property?
Currently, I have 3 methods that look like this:
private bool CanStartTimer() { return (TimerState == TimerState.Stopped || TimerState == TimerState.Paused); }
In the TimerState setter, I call

StartTimerCmd.RaiseCanExecuteChanged();  

Is there a better way bind the CanExecute state of the RelayCommands to a property like TimerState?
Thanks for any insight.

like image 498
Number8 Avatar asked Dec 25 '22 05:12

Number8


2 Answers

I've implemented a class to handle commands, actually it's based on DelegateCommand because i'm using PRISM but it could easily be changed to be used with RelayCommand or any other class implementing ICommand

It could have bugs, i've not yet fully tested it, however it works fine in my scenarios, here it is:

public class MyDelegateCommand<TViewModel> : DelegateCommand where TViewModel : INotifyPropertyChanged {
  private List<string> _PropertiesToWatch;

  public MyDelegateCommand(TViewModel viewModelInstance, Action executedMethod)
     : base(executedMethod) {
  }

  public MyDelegateCommand(TViewModel viewModelInstance, Action executedMethod, Func<bool> canExecuteMethod)
     : base(executedMethod, canExecuteMethod) {
  }

  /// <summary>
  /// 
  /// </summary>
  /// <param name="viewModelInstance"></param>
  /// <param name="executedMethod"></param>
  /// <param name="selector"></param>
  public MyDelegateCommand(TViewModel viewModelInstance, Action executedMethod, Func<bool> canExecuteMethod, Expression<Func<TViewModel, object>> propertiesToWatch)
     : base(executedMethod, canExecuteMethod) {

     _PropertiesToWatch = RegisterPropertiesWatcher(propertiesToWatch);
     viewModelInstance.PropertyChanged += PropertyChangedHandler;
  }


  /// <summary>
  /// handler that, everytime a monitored property changes, calls the RaiseCanExecuteChanged of the command
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) {
     if (_PropertiesToWatch.Contains(e.PropertyName)) {
        this.OnCanExecuteChanged();
     }
  }

  /// <summary>
  /// giving an expression that identify a propriety or a list of properties, return the property names obtained from the expression
  /// Examples on selector usage
  /// proprietà singola:
  ///   entity => entity.PropertyName
  /// proprietà multiple
  ///   entity => new { entity.PropertyName1, entity.PropertyName2 }
  /// </summary>
  /// <param name="selector"></param>
  /// <returns></returns>
  protected List<string> RegisterPropertiesWatcher(Expression<Func<TViewModel, object>> selector) {
     List<string> properties = new List<string>();

     System.Linq.Expressions.LambdaExpression lambda = (System.Linq.Expressions.LambdaExpression)selector;

     if (lambda.Body is System.Linq.Expressions.MemberExpression) {
        System.Linq.Expressions.MemberExpression memberExpression = (System.Linq.Expressions.MemberExpression)(lambda.Body);
        properties.Add(memberExpression.Member.Name);
     }
     else if (lambda.Body is System.Linq.Expressions.UnaryExpression) {
        System.Linq.Expressions.UnaryExpression unaryExpression = (System.Linq.Expressions.UnaryExpression)(lambda.Body);

        properties.Add(((System.Linq.Expressions.MemberExpression)(unaryExpression.Operand)).Member.Name);
     }
     else if (lambda.Body.NodeType == ExpressionType.New) {
        NewExpression newExp = (NewExpression)lambda.Body;
        foreach (var argument in newExp.Arguments) {
           if (argument is System.Linq.Expressions.MemberExpression) {
              System.Linq.Expressions.MemberExpression mExp = (System.Linq.Expressions.MemberExpression)argument;
              properties.Add(mExp.Member.Name);
           }
           else {
              throw new SyntaxErrorException("Syntax Error, selector has to be an expression that returns a new object containing a list of properties, e.g.: s => new { s.Property1, s.Property2 }");
           }
        }
     }
     else {
        throw new SyntaxErrorException("Syntax Error, selector has to be an expression that returns a new object containing a list of properties, e.g.: s => new { s.Property1, s.Property2 }");
     }

     return properties;
  }

}

note that my solution implies that this command has to be wired with the viewmodel that handle it and the viewmodel has to implement the INotifyPropertyChanged interface.

first two constructor are there so the command is backward compatible with DelegateCommand but the 3rd is the important one, that accepts a linq expression to specify which property to monitor

the usage is pretty simple and easy to understand, let me write it here with methods but of course you can create your handler methods. Suppose you have have a ViewModel called MyViewModel with two properties (PropertyX and PropertyY) that rise the propertychanged event, and somewhere in it you create an instance of SaveCommand, it would look like this:

this.SaveCommand = new MyDelegateCommand<MyViewModel>(this,
        //execute
        () => {
          Console.Write("EXECUTED");
        },
        //can execute
        () => {
          Console.Write("Checking Validity");
           return PropertyX!=null && PropertyY!=null && PropertyY.Length < 5;
        },
        //properties to watch
        (p) => new { p.PropertyX, p.PropertyY }
     );

maybe i'll create an article somewhere about this, but this snippet should be clear i hope

like image 125
Not Important Avatar answered Dec 27 '22 20:12

Not Important


Fabio's answer works well. Here's a parameterized version for DelegateCommand<T>. (I've tightened up the code a little, too.)

public class DepedencyCommand<TViewModel, TArg> : DelegateCommand<TArg>
    where TViewModel : INotifyPropertyChanged
{
    private readonly List<string> _propertiesToWatch;

    public DepedencyCommand(Action<TArg> executedMethod)
        : base(executedMethod) { }

    public DepedencyCommand(Action<TArg> executedMethod, Func<TArg, bool> canExecuteMethod)
        : base(executedMethod, canExecuteMethod) { }

    public DepedencyCommand(TViewModel viewModelInstance, Action<TArg> executedMethod, Func<TArg, bool> canExecuteMethod, Expression<Func<TViewModel, object>> propertiesToWatch)
        : base(executedMethod, canExecuteMethod)
    {

        _propertiesToWatch = _RegisterPropertiesWatcher(propertiesToWatch);
        viewModelInstance.PropertyChanged += PropertyChangedHandler;
    }


    /// <summary>
    /// handler that, everytime a monitored property changes, calls the RaiseCanExecuteChanged of the command
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
    {
        if (_propertiesToWatch.Contains(e.PropertyName))
        {
            this.OnCanExecuteChanged();
        }
    }

    /// <summary>
    /// giving an expression that identify a propriety or a list of properties, return the property names obtained from the expression
    /// Examples on selector usage
    /// proprietà singola:
    ///   entity => entity.PropertyName
    /// proprietà multiple
    ///   entity => new { entity.PropertyName1, entity.PropertyName2 }
    /// </summary>
    /// <param name="selector"></param>
    /// <returns></returns>
    private static List<string> _RegisterPropertiesWatcher(Expression<Func<TViewModel, object>> selector)
    {
        var properties = new List<string>();

        LambdaExpression lambda = selector;

        if (lambda.Body is MemberExpression)
        {
            var memberExpression = (MemberExpression)lambda.Body;
            properties.Add(memberExpression.Member.Name);
        }
        else if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = (UnaryExpression)lambda.Body;

            properties.Add(((MemberExpression)unaryExpression.Operand).Member.Name);
        }
        else if (lambda.Body.NodeType == ExpressionType.New)
        {
            var newExp = (NewExpression)lambda.Body;
            foreach (var argument in newExp.Arguments)
            {
                if (argument is MemberExpression)
                {
                    MemberExpression mExp = (MemberExpression)argument;
                    properties.Add(mExp.Member.Name);
                }
                else
                    throw new SyntaxErrorException("Syntax Error, selector has to be an expression that returns a new object containing a list of properties, e.g.: s => new { s.Property1, s.Property2 }");
            }
        }
        else
            throw new SyntaxErrorException("Syntax Error, selector has to be an expression that returns a new object containing a list of properties, e.g.: s => new { s.Property1, s.Property2 }");

        return properties;
    }
}
like image 33
gregsdennis Avatar answered Dec 27 '22 20:12

gregsdennis