在视图模型中使用命令

已完成

你已了解如何将数据从视图模型获取到 UI,以及如何使用双向绑定将数据返回到视图模型中。

每当数据发生更改时,对 UI 中的更改作出反应的首选方法就是使用这样的双向绑定。 作为事件处理的许多内容都可通过双向绑定和模型-视图-视图模型 (MVVM) 模式进行处理。 其他示例包括 Switch.IsToggledSlider.Value,在视图模型中无需使用事件即可反映为布尔值或整数值。

但还有一些内容与更改的数据不直接关联,例如 ButtonMenuItem 激活。 这些交互仍然需要使用类似事件处理。 由于这些 UI 组件通常使用数据调用某种逻辑,因此我们希望在视图模型中使用这种逻辑。 但是如果可能的话,我们不希望将它们作为代码隐藏中的 ClickedSelected 事件进行处理。 我们希望尽可能多地位于视图模型中,这样就可以对其进行测试。

使用命令模式

许多具有此类交互的 .NET MAUI 控件都支持绑定到公开 ICommand 接口的属性。 此属性很可能命名为 Command。 一个例子是 Button 控件:

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

控件知道何时调用命令。 例如,按钮在按下时调用命令。 此示例中的命令绑定到视图模型的 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;
}

使用 Command 类

此命令模式使你可以从 UI 实现中明确分离出 UI 行为。 但是,如果需要创建一个单独的类来实现每个事件处理程序,那么代码可能会变得复杂。

与其创建几个实现接口的自定义类,不如使用 CommandCommand<T>,这样做更为常见。 这些类实现 ICommand,但将其行为公开为可在视图模型中设置的属性。 通过这种方法可以在视图模型类中实现上述 GiveBonus 属性:

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.
    }
}

在此代码中,Execute 行为通过方法 GiveBonusExecute 提供。 并且 CanExecute 通过 GiveBonusCanExecute 提供。 对这些方法的委托会传递至 Command 构造函数。 在此示例中,没有 CanExecuteChanged 的实现。

使用 MVVM 工具包进行简化

MVVM 工具包库包含称为 RelayCommandAsyncRelayCommandICommand 实现。 它还提供源生成器,以进一步简化此代码。 在以下示例中,将生成 GiveBonusCommand,从而同时设置调用后可执行该命令的方法,以及调用后可查看是否可执行该命令的方法。 [RelayCommand] 属性将用于 GiveBonus 方法,并将生成 GiveBonusCommand。 此外,通过将该属性上的 CanExecute 属性设置为相应方法(即我们想要连接到 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 绑定到不正确的类型,请应用转换器将值转换为正确类型。