繁体   English   中英

在 MVVM 中使用 DelegateCommand 的异步 CanExecute 方法

[英]Async CanExecute method using DelegateCommand in MVVM

我有一个简单的 DelegateCommand class ,如下所示:

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 实现似乎无法处理异步场景,所以我有点担心。

Without too much thought it seems that you could potentially re-write the DelegateCommand class to support such functionality but I have looked at the way that Prism deals with this https://prismlibrary.github.io/docs/commanding.html , However it似乎他们也不支持异步 CanExecute 方法。

那么,有没有办法解决这个问题? 或者在尝试使用 ICommand 从 CanExecute 运行 Async 方法时是否存在根本性的问题?

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

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

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

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

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

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

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

笔记

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

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

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

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

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

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

是的,肯定有。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM