我有一个计时器和三个按钮来控制它:开始,停止和暂停
每个按钮都绑定到一个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之类的属性?
感谢您的任何见解。
答案 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。这仍然是丑陋的,但至少你有一个街区的所有垃圾。