我有一个简单的DelegateCommand类,如下所示:
public class DelegateCommand<T> : System.Windows.Input.ICommand where T : class
{
public event EventHandler CanExecuteChanged;
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public DelegateCommand(Action<T> execute) : this(execute, null)
{
}
public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
{
this._execute = execute;
this._canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (this._canExecute == null)
return true;
return this._canExecute((T)parameter);
}
public void Execute(object parameter)
{
this._execute((T)parameter);
}
public void RaiseCanExecuteChanged()
{
this.CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
我正在使用GalaSoft.MvvmLight
进行验证,通常我会在View构造函数中做类似的事情:
this.MyCommand = new DelegateCommand<object>(o => {
//Do execute stuff
}, o =>
{
//Do CanExecute stuff
var validateResult = this.Validator.ValidateAll();
return validateResult.IsValid;
});
public DelegateCommand<object> MyCommand { get; }
当我进行以下简单的验证检查时,这一切都很好:
this.Validator.AddRequiredRule(() => this.SomeProperty, "You must select.......");
但是现在我需要一个验证方法来执行长时间运行的任务(在我的情况下是WebService调用),因此当我想做这样的事情时:
this.Validator.AddAsyncRule(async () =>
{
//Long running webservice call....
return RuleResult.Assert(true, "Some message");
});
,因此声明如下命令:
this.MyCommand = new DelegateCommand<object>(o => {
//Do execute stuff
}, async o =>
{
//Do CanExecute ASYNC stuff
var validateResult = await this.Validator.ValidateAllAsync();
return validateResult.IsValid;
});
我有点不高兴,因为标准的ICommand实现似乎无法处理异步情况。
无需过多考虑,您似乎可以重新编写DelegateCommand类以支持此类功能,但是我已经看过Prism处理此https://prismlibrary.github.io/docs/commanding.html的方式,但是似乎它们也不支持异步CanExecute方法。
那么,有没有办法解决这个问题?还是在尝试使用ICommand从CanExecute运行Async方法方面有根本的突破?
答案 0 :(得分:1)
Delegatecommand满足ICommand接口。您不仅可以更改内容的签名,而且仍然可以使用。它还需要在UI线程上执行此操作,因此您无法对其进行线程化。
请记住,只要有用户交互,视图中的所有命令都可以执行。即使您可以使谓词异步,它也可能多次受到性能影响。
在这种情况下,要做的是使CanExecute快速返回布尔值。
将代码封装在其他地方,例如在Task中。异步调用该代码,然后将结果返回给布尔。然后在您的委托命令上提高canexecutechanged,以便读取该布尔值。
您可能还希望最初将bool设置为false并在Action中检查其值。这样,用户将无法重复单击绑定到该按钮的按钮。
根据正在进行的输入量以及视图中可能包含的这些内容,您可能需要考虑采取步骤,以便仅在自上次运行以来数据发生更改时才运行昂贵的过程。
由于您已经有一个可以执行的谓词,因此可以更改您的委托命令实现以添加此保护布尔并将其公开。
注意
当需要进行复杂且昂贵的验证时,经常使用另外两种方法。
1)在输入属性时对其进行验证。这样会分散验证,因此验证会在用户填写字段时发生,并且可能在他准备单击该提交按钮之前就已经完成。
2)让用户点击“提交”,但此时进行任何(更昂贵的)检查,然后报告验证失败。这样可以保证在他们点击提交时只有一张支票。
答案 1 :(得分:1)
Andy's answer很不错,应该接受。 TL; DR版本是“您不能使CanExecute
异步”。
我将在这里就问题的这一部分回答更多:
在尝试使用ICommand从CanExecute运行Async方法时,有根本的突破吗?
是的,肯定有。
从您使用的UI框架的角度考虑这一点。当OS要求它绘制屏幕时,框架必须显示UI,并且必须立即显示它。绘制屏幕时,没有时间进行网络呼叫。该视图必须能够随时显示。 MVVM是一种模式,其中ViewModels是用户界面的逻辑表示,并且视图和VM之间的数据绑定意味着ViewModels需要立即且同步地将其数据提供给视图。因此,ViewModel属性必须是常规数据值。
CanExecute
是命令的一个奇怪设计的方面。从逻辑上讲,它充当数据绑定属性(但带有参数,这就是为什么我认为将其建模为方法的原因)。当OS要求UI框架显示其窗口,并且UI框架要求其View渲染(例如)按钮时,并且View询问ViewModel该按钮是否被禁用时,必须立即并同步返回结果。>
换句话说,UI本质上是同步的。您需要采用不同的UI模式,以将同步UI代码与异步活动结合起来。例如,用于异步加载数据的“正在加载...” UI,或者用于异步验证的禁用带有帮助文本的按钮直到未验证(如本问题所述),或者带有故障通知的基于队列的异步请求系统