MVVM将RelayCommand CanExecute绑定到属性?

时间:2014-08-06 17:18:17

标签: wpf mvvm-light

我有一个计时器和三个按钮来控制它:开始,停止和暂停 每个按钮都绑定到一个RelayCommand 我有一个类型为enum TimerState的TimerState属性。 (这对于设置各种GUI元素很有用。)
有没有办法以某种方式将RelayCommands的CanExecute功能绑定到TimerState属性?
目前,我有3种方法,如下所示:
private bool CanStartTimer() { return (TimerState == TimerState.Stopped || TimerState == TimerState.Paused); }
在TimerState设置器中,我调用

StartTimerCmd.RaiseCanExecuteChanged();  

是否有更好的方法将RelayCommands的CanExecute状态绑定到TimerState之类的属性?
感谢您的任何见解。

3 个答案:

答案 0 :(得分:5)

我已经实现了一个处理命令的类,实际上它基于DelegateCommand,因为我使用的是PRISM,但它很容易被改为与RelayCommand或任何其他实现ICommand的类一起使用< / p>

它可能有错误,我还没有完全测试它,但它在我的场景中工作正常,这里是:

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;
  }

}

请注意,我的解决方案意味着此命令必须与处理它的viewmodel连接,并且viewmodel必须实现INotifyPropertyChanged接口。

前两个构造函数在那里,所以命令向后兼容DelegateCommand但第三个是重要的,它接受一个linq表达式来指定要监视的属性

用法非常简单易懂,让我用方法在这里写,但当然你可以创建你的处理程序方法。假设您有一个名为MyViewModel的ViewModel,它具有两个属性(PropertyX和PropertyY),它们会引发propertychanged事件,并且在其中的某个位置创建一个SaveCommand实例,它将如下所示:

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 }
     );

也许我会在某个地方创建一篇关于此的文章,但这段代码应该很清楚我希望

答案 1 :(得分:1)

Fabio的回答很有效。这是DelegateCommand<T>的参数化版本。 (我已经收紧了一些代码。)

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;
    }
}

答案 2 :(得分:0)

似乎没有更好的解决方案。我知道你的意思,看起来不太优雅,但无论你涂上什么口红,责任都在表达中涉及的对象上,以通知命令。

如果您的条件完全基于其他通知属性,您可以将自己的处理程序添加到PropertyChanged,这提供了一些抽象。

在这种情况下,TimerState将是VM属性。然后,您可以处理ViewModel.PropertyChanged事件。然后,您可以检查属性名称并更新CanExecute。这仍然是丑陋的,但至少你有一个街区的所有垃圾。