在视图模型中使用命令
你已了解如何将数据从视图模型获取到 UI,以及如何使用双向绑定将数据返回到视图模型中。
每当数据发生更改时,对 UI 中的更改作出反应的首选方法就是使用这样的双向绑定。 作为事件处理的许多内容都可通过双向绑定和模型-视图-视图模型 (MVVM) 模式进行处理。 其他示例包括 Switch.IsToggled
和 Slider.Value
,在视图模型中无需使用事件即可反映为布尔值或整数值。
但还有一些内容与更改的数据不直接关联,例如 Button
或 MenuItem
激活。 这些交互仍然需要使用类似事件处理。 由于这些 UI 组件通常使用数据调用某种逻辑,因此我们希望在视图模型中使用这种逻辑。 但是如果可能的话,我们不希望将它们作为代码隐藏中的 Clicked
和 Selected
事件进行处理。 我们希望尽可能多地位于视图模型中,这样就可以对其进行测试。
使用命令模式
许多具有此类交互的 .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
接口还有两种方法:CanExecute
和 CanExecuteChanged
,用于确定是启用还是禁用控件显示。
例如,一个按钮可能会在 CanExecute
返回 false 的情况下显示为灰色。
ICommand
接口在 C# 中类似如下所示:
public interface ICommand
{
bool CanExecute(object parameter);
void Execute(object parameter);
event EventHandler CanExecuteChanged;
}
使用 Command 类
此命令模式使你可以从 UI 实现中明确分离出 UI 行为。 但是,如果需要创建一个单独的类来实现每个事件处理程序,那么代码可能会变得复杂。
与其创建几个实现接口的自定义类,不如使用 Command
或 Command<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 工具包库包含称为 RelayCommand
和 AsyncRelayCommand
的 ICommand
实现。 它还提供源生成器,以进一步简化此代码。 在以下示例中,将生成 GiveBonusCommand
,从而同时设置调用后可执行该命令的方法,以及调用后可查看是否可执行该命令的方法。 [RelayCommand]
属性将用于 GiveBonus
方法,并将生成 GiveBonusCommand
。 此外,通过将该属性上的 CanExecute
属性设置为相应方法(即我们想要连接到 ICommand
的 CanExecute
方法的方法)的名称,它将生成代码来为我们完成此设置。
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
接口接受 CanExecute
和 Execute
方法的 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
绑定到不正确的类型,请应用转换器将值转换为正确类型。