简体   繁体   English

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

[英]Async CanExecute method using DelegateCommand in MVVM

I have a simple DelegateCommand class that looks like this:我有一个简单的 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);
    }
}

I am using GalaSoft.MvvmLight for validation and normally I would just do something like this in the View constructor:我正在使用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 all works great when I have a simple validation check like:当我有一个简单的验证检查时,这一切都很好:

this.Validator.AddRequiredRule(() => this.SomeProperty, "You must select.......");

but now I need a validation method that executes a long running task (in my case a WebService call) so when I want to do somthing like this:但现在我需要一个验证方法来执行一个长时间运行的任务(在我的例子中是一个 WebService 调用)所以当我想做这样的事情时:

this.Validator.AddAsyncRule(async () =>
{
    //Long running webservice call....
    return RuleResult.Assert(true, "Some message");
});

and therefore declare the command like this:因此声明这样的命令:

this.MyCommand = new DelegateCommand<object>(o => {
   //Do execute stuff
}, async o => 
{
   //Do CanExecute ASYNC stuff
   var validateResult = await this.Validator.ValidateAllAsync();
   return validateResult.IsValid;
});

I'm in a bit of a pickle because the standard ICommand implementation doesn't appear to deal with async scenarios.因为标准的 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 seems that they also DO NOT support async CanExecute methods. 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 方法。

So, is there a way around this problem?那么,有没有办法解决这个问题? Or is there something fundamentally broken in trying to run an Async method from CanExecute using ICommand?或者在尝试使用 ICommand 从 CanExecute 运行 Async 方法时是否存在根本性的问题?

Delegatecommand is satisfying the ICommand interface. Delegatecommand 满足 ICommand 接口。 You can't just change the signature of stuff and it'll still work.你不能只是改变东西的签名,它仍然可以工作。 It also needs to do it's thing on the UI thread so you can't thread it.它还需要在 UI 线程上做这件事,所以你不能线程化它。

Bear in mind that all commands in a view will have canexecute checked whenever there is a user interaction.请记住,只要有用户交互,视图中的所有命令都可以执行检查。 Even if you could make the predicate async then it could well be hit numerous times with performance implications.即使您可以使谓词异步,那么它很可能会受到性能影响的无数次打击。

The thing to do in this case is to make CanExecute quickly return the value of a bool.在这种情况下要做的事情是让 CanExecute 快速返回一个布尔值。

Encapsulate your code elsewhere, say in a Task.将您的代码封装在其他地方,例如在任务中。 Call that code asynchronously and return the result to the bool.异步调用该代码并将结果返回到布尔值。 Then raise canexecutechanged on your delegatecommand so the value of that bool is read.然后在您的委托命令上引发 canexecutechanged 以便读取该布尔值。

You probably also want to set that bool false initially and check it's value inside your Action.您可能还希望最初将该 bool 设置为 false 并检查它在 Action 中的值。 That way the user can't click a button bound to it repeatedly.这样用户就不能重复单击绑定到它的按钮。

Depending on the amount of input going on and how many of these things you might have in a view you might want to consider taking steps so the expensive process is only run whilst the data has changed since last run.根据正在进行的输入量以及视图中可能包含的这些内容的数量,您可能需要考虑采取措施,以便仅在自上次运行以来数据发生更改时才运行昂贵的过程。

Since you already have a canexecute predicate you could alter your delegatecommand implementation to add this guard bool and make it public.由于您已经有一个 canexecute 谓词,您可以更改您的委托命令实现以添加此保护布尔值并将其公开。

Note笔记

When complicated expensive validation is necessary there are two other approaches often used.当需要复杂昂贵的验证时,通常会使用另外两种方法。

1) Validate properties as they're entered. 1) 在输入属性时验证属性。 This spreads out the validation so it happens as the user fills fields and is probably done before he's ready to click that submit button.这分散了验证,因此它在用户填写字段时发生,并且可能在他准备好单击该提交按钮之前完成。

2) Let the user hit submit but do any (further expensive) checks at that time and report validation failures then. 2)让用户点击提交,但当时做任何(更昂贵的)检查,然后报告验证失败。 This guarantees there's only the one check when they hit submit.这保证了当他们点击提交时只有一个检查。

Andy's answer is great and should be accepted.安迪的回答很好,应该被接受。 The TL;DR version is "you can't make CanExecute asynchronous". TL;DR 版本是“你不能让CanExecute异步”。

I'm just going to answer more on this part of the question here:我只是在这里回答这部分问题的更多信息:

is there something fundamentally broken in trying to run an Async method from CanExecute using ICommand?尝试使用 ICommand 从 CanExecute 运行 Async 方法时是否存在根本性的问题?

Yes, there definitely is.是的,肯定有。

Consider this from the perspective of the UI framework you're using.从您正在使用的 UI 框架的角度考虑这一点。 When the OS asks it to paint the screen, the framework has to display a UI, and it has to display it now .当操作系统要求它绘制屏幕时,框架必须显示一个 UI,并且它现在必须显示它。 There's no time for network calls when painting the screen.绘制屏幕时没有时间进行网络调用。 The view must be able to be displayed immediately at any time.视图必须能够随时立即显示。 MVVM is a pattern where the ViewModels are a logical representation of the user interface, and the data binding between views and VMs means that ViewModels need to provide their data to the views immediately and synchronously. MVVM 是一种模式,其中 ViewModel 是用户界面的逻辑表示,视图和 VM 之间的数据绑定意味着 ViewModel 需要立即同步地将其数据提供给视图。 Therefore, ViewModel properties need to be regular data values.因此,ViewModel 属性需要是常规数据值。

CanExecute is a weirdly-designed aspect of commands. CanExecute是一个设计奇特的命令方面。 Logically, it acts as a data-bound property (but with an argument, which is why I think it was modeled as a method).从逻辑上讲,它充当数据绑定属性(但带有参数,这就是我认为它被建模为方法的原因)。 When the OS asks the UI framework to display its window, and the UI framework asks its View to render (eg) a button, and the View asks the ViewModel whether the button is disabled, the result must be returned immediately and synchronously.当 OS 要求 UI 框架显示其 window,UI 框架要求其 View 渲染(例如)一个按钮,而 View 向 ViewModel 询问该按钮是否被禁用时,必须立即同步返回结果。

To put it another way: UIs are inherently synchronous.换句话说:UI 本质上是同步的。 You'll need to adopt different UI patterns to marry the synchronous UI code with asynchronous activities.您需要采用不同的 UI 模式来将同步 UI 代码与异步活动结合起来。 Eg, "Loading..." UIs for asynchronously loading data, or disable-buttons-with-help-text-until-validated for asynchronous validation (as in this question), or a queue-based system of asynchronous requests with failure notifications.例如,“正在加载...”用于异步加载数据的 UI,或用于异步验证的 disable-buttons-with-help-text-until-validated(如本问题所示),或带有失败通知的基于队列的异步请求系统.

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

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