簡體   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