MVVM привязать RelayCommand CanExecute к свойству?

У меня есть таймер и три кнопки для управления им: Пуск, Стоп и Пауза.
Каждая кнопка связана с RelayCommand.
У меня есть свойство TimerState типа enum TimerState, (Это полезно для настройки различных элементов графического интерфейса.)
Есть ли способ как-то связать функциональность CanExecute RelayCommands со свойством TimerState?
В настоящее время у меня есть 3 метода, которые выглядят так:
private bool CanStartTimer() { return (TimerState == TimerState.Stopped || TimerState == TimerState.Paused); }
В установщик TimerState я вызываю

StartTimerCmd.RaiseCanExecuteChanged();  

Есть ли лучший способ привязать состояние CanExecute RelayCommands к свойству, как TimerState?
Спасибо за понимание.

3 ответа

Я реализовал класс для обработки команд, на самом деле он основан на DelegateCommand, потому что я использую PRISM, но его можно легко изменить, чтобы использовать с RelayCommand или любым другим классом, реализующим ICommand.

В нем могут быть ошибки, я еще не полностью протестировал его, однако он отлично работает в моих сценариях, вот он:

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

}

обратите внимание, что мое решение подразумевает, что эта команда должна быть связана с моделью представления, которая ее обрабатывает, и модель представления должна реализовывать интерфейс INotifyPropertyChanged.

первые два конструктора существуют, поэтому команда обратно совместима с DelegateCommand, но третий является важным, который принимает выражение linq, чтобы указать, какое свойство контролировать

использование довольно простое и понятное, позвольте мне написать это здесь с методами, но, конечно, вы можете создавать свои методы-обработчики. Предположим, что у вас есть ViewModel с именем MyViewModel с двумя свойствами (PropertyX и PropertyY), которые вызывают событие changeloaded, и где-то в нем вы создаете экземпляр 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 }
     );

возможно я создам статью где-нибудь об этом, но этот фрагмент должен быть ясным, я надеюсь

Ответ Фабио работает хорошо. Вот параметризованная версия для 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;
    }
}

Кажется, нет лучшего решения. Я знаю, что вы имеете в виду, это кажется не элегантным, но какую бы помаду вы ни наносили, бремя лежит на объектах, участвующих в выражении, чтобы уведомить команду.

Если ваше условие основано исключительно на других свойствах уведомлений, вы можете добавить свой собственный обработчик в PropertyChanged, что обеспечивает некоторую абстракцию.

В этом случае TimerState будет свойством VM. Затем вы можете обработать ваше событие ViewModel.PropertyChanged. Затем вы можете проверить имя свойства и обновить свой CanExecute. Это все еще уродливо, но по крайней мере у вас есть весь мусор в одном блоке.

Другие вопросы по тегам