在 ViewModel 中使用命令

已完成

您已看到如何將資料從 viewmodel 移至 UI,以及如何使用雙向繫結,將資料移回 viewmodel。

使用這樣的雙向繫結,是在「資料」發生變更時,回應 UI 變更的慣用方法。 許多可作為「事件」處理的項目,都可以使用雙向繫結與 Model-View-ViewModel (MVVM) 模式來處理。 其他範例,例如 Switch.IsToggledSlider.Value,可以在我們的 ViewModel 中反映為布林值或整數值,而不必使用事件。

但有些項目,例如 ButtonMenuItem 啟用,就與資料變更沒有直接關係。 這些互動仍然需要「類似事件」處理。 由於這些 UI 元件通常會使用資料叫用某種邏輯,因此我們想要該邏輯在 ViewModel 上。 但是,我們不想要在程式碼後置中將其當成 ClickedSelected 事件處理,如果可能的話。 我們想要盡可能在 ViewModel 中,如此一來,就可以對其進行測試。

使用命令模式

許多具有這種互動的 .NET MAUI 控制項都支援繫結至公開 ICommand 介面的屬性。 這個屬性最有可能命名為 CommandButton 控制項是一個範例:

<Button Text="Give Bonus" Command="{Binding GiveBonusCommand}" />

控件項知道何時要叫用命令。 例如,按鈕會在按下時叫用命令。 此範例中的命令會繫結至 ViewModel 的 GiveBonus 屬性。 屬性類型必須實作 ICommand 介面。 程式碼看起來應該類似下列範例:

public class EmployeeViewModel : INotifyPropertyChanged
{
    public ICommand GiveBonusCommand {get; private set;}
    ...
}

ICommand 介面具有 Execute 方法,按一下該按鈕即會呼叫此方法。 如此一來,ICommand.Execute 可直接取代 Button.Click 事件處理程式碼。

完整的 ICommand 介面還有兩個方法:CanExecuteCanExecuteChanged,用來決定控制項應顯示已啟用還是已停用。

例如,如果 CanExecute 傳回 false,則按鈕可能顯示為灰色。

以下是 ICommand 介面在 C# 中的外觀:

public interface ICommand
{
    bool CanExecute(object parameter);
    void Execute(object parameter);
    event EventHandler CanExecuteChanged;
}

使用命令類別

此命令模式可讓您清楚地分離 UI 行為與 UI 實作。 但是,如果您需要建立個別的類別來實作每個事件處理常式,它可能會讓程式碼變得很複雜。

通常使用 CommandCommand<T>,而不是建立數個實作介面的自訂類別。 這些類別會實作 ICommand,但會將其行為公開為 ViewModel 中您可以設定的屬性。 使用此方式,我們可以實作 GiveBonus 屬性,稍早已在我們的 viewmodel 類別內完整描述此屬性:

public class EmployeeViewModel : INotifyPropertyChanged
{
    public ICommand GiveBonusCommand {get; private set;}
    public EmployeeViewModel(Employee model)
    {
        GiveBonusCommand = new Command(GiveBonusExecute, GiveBonusCanExecute)
    }

    void GiveBonusExecute()
    {
        //logic for giving bonus
    }

    bool GiveBonusCanExecute()
    {
        //logic for deciding if "give bonus" button should be enabled.
    }
}

在此程式碼中,方法 GiveBonusExecute 提供 Execute 行為。 而 GiveBonusCanExecute 提供 CanExecute。 這些方法的委派將傳遞至 Command 建構函式。 在此範例中,不實作 CanExecuteChanged

使用 MVVM 工具組簡化

MVVM 工具組程式庫包含 ICommand 的實作,亦稱為 RelayCommandAsyncRelayCommand。 它也提供來源產生器,以進一步簡化此程式碼。 在下列範例中,會產生 GiveBonusCommand ,同時設定呼叫以執行的方法,以及呼叫以查看它是否可執行的方法。 [RelayCommand] 屬性會用於 GiveBonus 方法,並會產生 GiveBonusCommand。 此外,藉由將屬性 (attribute) 上的 CanExecute 屬性 (property) 設定為我們想要連結至 ICommandCanExecute 方法的方法名稱,其會產生程式碼來為我們進行此設定。

public partial class EmployeeViewModel : ObservableObject
{
    public EmployeeViewModel(Employee model)
    {
    }

    [RelayCommand(CanExecute = nameof(GiveBonusCanExecute))]
    void GiveBonus()
    {
        //logic for giving bonus
    }

    bool GiveBonusCanExecute()
    {
        //logic for deciding if "give bonus" button should be enabled.
        return true;
    }
}

MVVM 工具組也會處理 async 方法,這在 .NET 程式設計中很常見。

具有參數的命令

ICommand 介面接受 CanExecuteExecute 方法的 object 參數。 .NET MAUI 會實作此介面,而不需要透過 Command 類別進行任何類型檢查。 您附加至命令的委派必須執行自己的類型檢查,以確保傳遞正確的參數。 .NET MAUI 也會提供 Command<T> 實作,您可以在其中設定預期的參數類型。 當您建立接受單一參數類型的命令時,請使用 Command<T>

實作命令模式的 .NET MAUI 控制項會提供 CommandParameter 屬性。 藉由設定此屬性,您可以在使用 Execute 叫用命令時,或在命令檢查 CanExecute 方法以取得狀態時,將參數傳遞至該命令。

在此範例中,字串值「25」會傳送至該命令:

<Button Text="Give Bonus" Command="{Binding GiveBonusCommand}" CommandParameter="25" />

此命令必須解譯並轉換該字串參數。 有許多方法可提供強型別參數。

  • 請使用 XAML 元素,而不是使用屬性語法來定義 CommandParameter

    <Button Text="Give Bonus" Command="{Binding GiveBonusCommand}">
        <Button.CommandParameter>
            <x:Int32>25</x:Int32>
        </Button.CommandParameter>
    </Button>
    
  • CommandParameter 繫結至正確類型的執行個體。

  • 如果 CommandParameter 繫結至不正確的類型,請套用轉換器,將值轉換成正確的類型。

檢定您的知識

1.

ICommand 介面的用途是什麼?

2.

CommandCommand<T> 類別如何簡化 ICommand 介面的使用?

3.

.NET MAUI 控制項中 CommandParameter 屬性的角色是什麼?