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. Это все еще уродливо, но по крайней мере у вас есть весь мусор в одном блоке.