1

我有一个简单的 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 方法时是否存在根本性的问题?

4

2 回答 2

3

安迪的回答很好,应该被接受。TL;DR 版本是“你不能做CanExecute异步”。

我只是在这里回答这部分问题的更多信息:

尝试使用 ICommand 从 CanExecute 运行 Async 方法时是否存在根本性的问题?

是的,肯定有。

从您正在使用的 UI 框架的角度考虑这一点。当操作系统要求它绘制屏幕时,框架必须显示一个 UI,并且它现在必须显示。绘制屏幕时没有时间进行网络调用。视图必须能够随时立即显示。MVVM 是一种模式,其中 ViewModel 是用户界面的逻辑表示,视图和 VM 之间的数据绑定意味着 ViewModel 需要立即同步地将其数据提供给视图。因此,ViewModel 属性需要是常规数据值。

CanExecute是命令的一个设计奇特的方面。从逻辑上讲,它充当数据绑定属性(但带有参数,这就是我认为它被建模为方法的原因)。当 OS 要求 UI 框架显示其窗口,UI 框架要求其 View 渲染(例如)一个按钮,而 View 向 ViewModel 询问该按钮是否被禁用时,必须立即同步返回结果。

换句话说:UI 本质上是同步的。您需要采用不同的 UI 模式来将同步 UI 代码与异步活动结合起来。例如,“正在加载...”用于异步加载数据的 UI,或用于异步验证的 disable-buttons-with-help-text-until-validated(如本问题所示),或带有失败通知的基于队列的异步请求系统.

于 2019-09-21T14:51:51.370 回答
1

Delegatecommand 满足 ICommand 接口。你不能只是改变东西的签名,它仍然可以工作。它还需要在 UI 线程上做这件事,所以你不能线程化它。

请记住,只要有用户交互,视图中的所有命令都可以执行检查。即使您可以使谓词异步,那么它很可能会受到性能影响的无数次打击。

在这种情况下要做的事情是让 CanExecute 快速返回一个布尔值。

将您的代码封装在其他地方,例如在任务中。异步调用该代码并将结果返回到布尔值。然后在您的委托命令上引发 canexecutechanged 以便读取该布尔值。

您可能还希望最初将该 bool 设置为 false 并检查它在 Action 中的值。这样用户就不能重复单击绑定到它的按钮。

根据正在进行的输入量以及视图中可能包含的这些内容的数量,您可能需要考虑采取措施,以便仅在自上次运行以来数据发生更改时才运行昂贵的过程。

由于您已经有一个 canexecute 谓词,您可以更改您的委托命令实现以添加此保护布尔值并将其公开。

笔记

当需要复杂昂贵的验证时,通常会使用另外两种方法。

1) 在输入属性时验证属性。这分散了验证,因此它在用户填写字段时发生,并且可能在他准备好单击该提交按钮之前完成。

2)让用户点击提交,但当时做任何(更昂贵的)检查,然后报告验证失败。这保证了当他们点击提交时只有一个检查。

于 2019-09-21T13:47:28.390 回答