MVVM bind RelayCommand CanExecute to a Property?

后端 未结 3 949
情歌与酒
情歌与酒 2021-01-14 02:04

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 Timer

3条回答
  •  醉梦人生
    2021-01-14 02:39

    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 : DelegateCommand where TViewModel : INotifyPropertyChanged {
      private List _PropertiesToWatch;
    
      public MyDelegateCommand(TViewModel viewModelInstance, Action executedMethod)
         : base(executedMethod) {
      }
    
      public MyDelegateCommand(TViewModel viewModelInstance, Action executedMethod, Func canExecuteMethod)
         : base(executedMethod, canExecuteMethod) {
      }
    
      /// 
      /// 
      /// 
      /// 
      /// 
      /// 
      public MyDelegateCommand(TViewModel viewModelInstance, Action executedMethod, Func canExecuteMethod, Expression> propertiesToWatch)
         : base(executedMethod, canExecuteMethod) {
    
         _PropertiesToWatch = RegisterPropertiesWatcher(propertiesToWatch);
         viewModelInstance.PropertyChanged += PropertyChangedHandler;
      }
    
    
      /// 
      /// handler that, everytime a monitored property changes, calls the RaiseCanExecuteChanged of the command
      /// 
      /// 
      /// 
      private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) {
         if (_PropertiesToWatch.Contains(e.PropertyName)) {
            this.OnCanExecuteChanged();
         }
      }
    
      /// 
      /// 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 }
      /// 
      /// 
      /// 
      protected List RegisterPropertiesWatcher(Expression> selector) {
         List properties = new List();
    
         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(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

提交回复
热议问题