MVVM 工具包
模型-视图-视图模型 (MVVM) 模式是创建应用程序的绝佳结构基础。 在此模式中,ViewModel 成为应用程序的主干,因为它提供与前端用户界面和后备组件的通信。 若要提供与用户界面的集成,我们将依赖于 ViewModel 的属性和命令。 如更新视图以响应基础视图模型或模型中的更改中所述,ViewModel 上的 INotifyPropertyChanged
接口允许更改属性,以在值更改时发出通知。 实现所有这些功能意味着 ViewModel 最终可能会变得非常详细。 例如,以下代码演示了一个简单的 ViewModel,其中包含引发更改的属性:
public class SampleViewModel : INotifyPropertyChanged
{
private string _name;
private int _value;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get => _name;
set => SetPropertyValue(ref _name, value);
}
public int Value
{
get => _value;
set => SetPropertyValue(ref _value, value);
}
protected void SetPropertyValue<T>(ref T storageField, T newValue, [CallerMemberName] string propertyName = "")
{
if (Equals(storageField, newValue))
return;
storageField = newValue;
RaisePropertyChanged(propertyName);
}
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
虽然可以逐步进行一些优化,但我们最终仍将得到一组相当详细的代码来定义 ViewModel。 此代码可能难以维护,并且容易出错。
CommunityToolkit.Mvvm NuGet 包(也称为 MVVM 工具包)可用于帮助解决和简化这些常见的 MVVM 模式。 MVVM 工具包以及 .NET 语言的较新功能允许使用简化逻辑、轻松采用到项目中以及独立于运行时。 下面的示例演示了使用 MVVM 工具包附带的组件的相同 ViewModel:
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
private string _name;
[ObservableProperty]
private int _value;
}
注意
MVVM 工具包随 CommunityToolkit.Mvvm
包一起提供。 如需了解如何将包添加到项目,请参阅 Microsoft 开发人员中心上的 MVVM Toolkit 简介。
与原始示例相比,我们能够大幅降低整体复杂性并简化 ViewModel 的可维护性。 MVVM 工具包附带许多预生成的常见组件和功能(如以上所示的 ObservableObject
),可简化和标准化整个应用程序中包含的代码。
ObservableObject
MVVM 工具包提供 ObservableObject
,旨在用作 ViewModel
对象或者需要引发更改通知的任何对象的基础。 它实现 INotifyPropertyChanged
和 INotifyPropertyChanging
,以及用于设置属性和引发更改的帮助程序方法。 下面是使用 ObservableObject
的标准 ViewModel 的示例:
public class SampleViewModel : ObservableObject
{
private string _name;
private int _value;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public int Value
{
get => _value;
set => SetProperty(ref _value, value);
}
}
ObservableObject
使用属性资源库中的 SetProperty
方法处理引发更改通知所需的所有逻辑。 如果有属性返回 Task<T>
,则可以使用 SetPropertyAndNotifyOnCompletion
方法延迟发布属性更改,直到任务完成。 方法 OnPropertyChanged
和 OnPropertyChanging
也可用于需要时在对象中引发属性更改。
有关 ObservableObject
的更多详细信息,请参阅 MVVM 工具包开发人员中心中的 ObservableObject。
RelayCommand
和 AsyncRelayCommand
.NET MAUI 控件(例如,点击按钮或从集合中选择项)和 ViewModel 之间的交互是使用 ICommand
接口完成的。 .NET MAUI 附带 ICommand
和 Command
对象的默认实现。 .NET MAUI 的 Command
相当基本,缺少对更高级功能的支持,例如支持异步工作和命令执行状态。
MVVM 工具包附带两个命令:RelayCommand
和 AsyncRelayCommand
。
RelayCommand
适用于需要执行同步代码的情况,并且其实现与 .NET MAUICommand
对象非常相似。
注意
即使 .NET MAUICommand
和 RelayCommand
相似,使用 RelayCommand
也允许将 ViewModel 与任何直接 .NET MAUI 引用分离。 这意味着 ViewModel 更可移植,因此更易于跨项目重用。
AsyncRelayCommand
在使用异步工作流时提供了许多其他功能。 这在 ViewModel 中很常见,因为我们通常与利用 async/await
的存储库、API、数据库和其他系统通信。
AsyncRelayCommand
构造函数接受定义为 Func<Task>
的执行任务或作为构造函数的一部分返回 Task
的委托。 当执行任务正在运行时,AsyncRelayCommand
将监视任务的状态,并使用 IsRunning
属性提供更新。
IsRunning
属性可以绑定到有助于管理控件状态的 UI,例如显示使用 ActivityIndicator
加载或禁用/启用控件。 当执行任务正在执行时,可以调用 Cancel
方法来尝试取消执行任务(如果支持)。
默认情况下,AsyncRelayCommand
不允许并发执行。 有时候用户可能无意中多次点击控件执行长时间运行或成本高昂的操作,这对于这种情况非常有用。 在执行任务期间,AsyncRelayCommand
将自动调用 CanExecuteChanged
事件。 在 .NET MAUI 中,支持 Command
和 CommandParameter
属性的控件(如 Button
)将侦听此事件,并在执行期间自动启用或禁用它。 可以使用自定义 canExecute
参数或在构造函数中设置 AsyncRelayCommandOptions.AllowConcurrentExecutions
标志来重写此功能。
有关实现命令的更多详细信息,请参阅 MVVM 一章中的实现命令部分。 有关 RelayCommand
和 AsyncRelayCommand
的详细信息,请参阅 MVVM 工具包开发人员中心的命令。
源生成器
使用现成的 MVVM 工具包组件可以大大简化 ViewModel。 借助 MVVM 工具包,可以通过使用源生成器进一步简化常见代码用例。 MVVM 工具包源生成器在代码中查找特定属性,并且可以为属性和命令生成包装器。
重要
MVVM 工具包源生成器生成的代码是现有对象的累加代码。 因此,利用源生成器的任何对象都需要标记为 partial
。
MVVM 工具包 ObservableProperty
属性可应用于继承自 ObservableObject
的对象中的字段,并将使用生成更改的属性包装私有字段。 以下代码演示了在 ObservableObject
字段上使用 _name
属性的示例:
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
private string _name;
}
将 ObservableProperty
属性应用于 _name
字段后,源生成器将运行并使用以下代码生成另一个分部类:
partial class SampleViewModel
{
public string Name
{
get => _name;
set
{
if (!EqualityComparer<string>.Default.Equals(_name, value))
{
OnNameChanging(value);
OnPropertyChanging("Name");
_name = value;
OnNameChanged(value);
OnPropertyChanged("Name");
}
}
}
}
生成的 SampleViewModel
已使用私有 _name
字段并生成新的 Name
属性,该属性用于实现引发更改通知所需的所有逻辑。
MVVM 工具包 RelayCommand
属性可应用于 ObservableObject
内的方法,并且将创建相应的 RelayCommand
或 AsyncRelayCommand
。 以下代码演示了使用 RelayCommand
属性的示例:
public partial class SampleViewModel : ObservableObject
{
public INavigationService NavigationService { get; set; }
[ObservableProperty]
private string _name;
[ObservableProperty]
bool _isValid;
[RelayCommand]
private Task SettingsAsync()
{
return NavigationService.NavigateToAsync("Settings");
}
[RelayCommand]
private void Validate()
{
IsValid = !string.IsNullOrEmpty(Name);
}
}
应用于 RelayCommand
方法的 Validate
将生成 RelayCommand
验证 ValidateCommand
,因为它具有 void
返回,并且 SettingsAsync
方法将生成名为 AsyncRelayCommand
的 SettingsCommand
。 源生成器将在其他分部类中生成以下代码:
partial class SampleViewModel
{
private AsyncRelayCommand? settingsCommand;
SettingsCommand => settingsCommand ??= new AsyncRelayCommand(SettingsAsync);
}
partial class SampleViewModel
{
private RelayCommand? validateCommand;
public IRelayCommand ValidateCommand => validateCommand ??= new RelayCommand(Validate);
}
源生成器已处理使用 ICommand
实现包装 ViewModel 方法的所有复杂性。
有关 MVVM 工具包源生成器的更多详细信息,请参阅 MVVM 工具包开发人员中心中的 MVVM 源生成器。
总结
MVVM 工具包是标准化和简化 ViewModel 代码的好方法。 MVVM 工具包可以很好地实现标准 MVVM 组件(如 ObservableObject
和 Async/RelayCommand
)。 源生成器通过生成用户界面交互所需的所有样本代码来帮助简化 ViewModel 属性和命令。 MVVM 工具包提供了本章中未介绍的更多功能。 有关 MVVM 工具包的详细信息,请参阅 MVVM 工具包开发人员中心中的 MVVM 工具包简介。