我有一个基本的MVVM WPF应用程序与视图,由一个文本框和提交按钮组成。这两个控件都正确地绑定到ViewModel中的属性和命令。问题是,CanSubmit不会被触发,因为CanExecuteChanged eventandler(在DelegateCommand中)总是为空。基本上,问题是如何正确地通知命令运行CanExecute检查当textox更新。
public DelegateCommand SubmitCommand => new DelegateCommand(Submit, CanSubmit);
private string _company;
public string Company
{
get => _company;
set
{
SetProperty(ref _company, value);
SubmitCommand.RaiseCanExecuteChanged();
}
}
我的委托命令
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public DelegateCommand(Action<object> execute) : this(execute, null) { }
public virtual bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
if(CanExecuteChanged != null) <------ Always null
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
我认为问题在于初始化get only属性的c#新特性的一个非常细微的差别。使用语法,您可以:
public DelegateCommand SubmitCommand => new DelegateCommand(Submit, CanSubmit);
返回新的命令实例每次调用SumitCommand getter时。您在XAML中绑定的SubmitCommand实例与Company
setter:SubmitCommand.RaiseCanExecuteChanged();
中的实例不同setter的SubmitCommand实例当然没有CanExecuteChaned的处理程序,因为它对UI是未知的。并且后面没有处理程序。
如果你把它改成:
public DelegateCommand SubmitCommand { get; } = new DelegateCommand(Submit, CanSubmit);
我想这个问题会解决的。因为你只创建一次命令,按钮canexecutechange处理程序将绑定到公司setter调用raisecanexecutechange的同一个SubmitCommand
实例。
我会补充@lidqy的答案。
他是完全正确的,问题在于每次访问属性时都创建一个新命令。
命令的实例必须始终相同。
通常,Submit和CanSubmit都是实例方法。
但是字段(属性)初始化器只能访问静态成员。
- ViewModel构造函数中的初始化:
public DelegateCommand SubmitCommand { get; }
public ViewModel()
{
SubmitCommand = new DelegateCommand(Submit, CanSubmit);
}
- 第一次调用属性时的初始化:
private DelegateCommand _submitCommand;
public DelegateCommand SubmitCommand => _submitCommand
?? (_submitCommand = new DelegateCommand(Submit, CanSubmit));
额外建议。
如果您有一个WPF解决方案(不是UWP),那么您应该订阅CommandManager.RequerySuggested命令。
在这种情况下,在GUI中发生更改时,将自动调用命令验证。
在属性setter中这样做的需求将会消失。
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
private readonly EventHandler requerySuggested;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
requerySuggested = (s, e) => RaiseCanExecuteChanged();
CommandManager.RequerySuggested += requerySuggested;
}
public DelegateCommand(Action<object> execute) : this(execute, null) { }
public virtual bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
private string _company;
public string Company { get => _company; set => SetProperty(ref _company, value); }
还请记住,对于绑定到GUI中的WPF元素的命令,CanExecuteChanged事件应该始终在应用程序的主线程上调用。
这也适用于在属性设置器中调用它,因为属性不仅可以从GUI中更改。
参见这些实现:BaseInpc, RelayCommand和RelayCommand