MVVM 工具包功能

提示

此内容摘自电子书《使用 .NET MAUI 的企业应用程序模式》,可在 .NET 文档上获取,也可作为免费可下载的 PDF 脱机阅读。

《使用 .NET MAUI 的企业应用程序模式》电子书包含缩略图。

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 对象或者需要引发更改通知的任何对象的基础。 它实现 INotifyPropertyChangedINotifyPropertyChanging,以及用于设置属性和引发更改的帮助程序方法。 下面是使用 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 方法延迟发布属性更改,直到任务完成。 方法 OnPropertyChangedOnPropertyChanging 也可用于需要时在对象中引发属性更改。

有关 ObservableObject 的更多详细信息,请参阅 MVVM 工具包开发人员中心中的 ObservableObject

RelayCommandAsyncRelayCommand

.NET MAUI 控件(例如,点击按钮或从集合中选择项)和 ViewModel 之间的交互是使用 ICommand 接口完成的。 .NET MAUI 附带 ICommandCommand 对象的默认实现。 .NET MAUI 的 Command 相当基本,缺少对更高级功能的支持,例如支持异步工作和命令执行状态。

MVVM 工具包附带两个命令:RelayCommandAsyncRelayCommandRelayCommand 适用于需要执行同步代码的情况,并且其实现与 .NET MAUICommand 对象非常相似。

注意

即使 .NET MAUICommandRelayCommand 相似,使用 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 中,支持 CommandCommandParameter 属性的控件(如 Button)将侦听此事件,并在执行期间自动启用或禁用它。 可以使用自定义 canExecute 参数或在构造函数中设置 AsyncRelayCommandOptions.AllowConcurrentExecutions 标志来重写此功能。

有关实现命令的更多详细信息,请参阅 MVVM 一章中的实现命令部分。 有关 RelayCommandAsyncRelayCommand 的详细信息,请参阅 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 内的方法,并且将创建相应的 RelayCommandAsyncRelayCommand。 以下代码演示了使用 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 方法将生成名为 AsyncRelayCommandSettingsCommand。 源生成器将在其他分部类中生成以下代码:

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 组件(如 ObservableObjectAsync/RelayCommand)。 源生成器通过生成用户界面交互所需的所有样本代码来帮助简化 ViewModel 属性和命令。 MVVM 工具包提供了本章中未介绍的更多功能。 有关 MVVM 工具包的详细信息,请参阅 MVVM 工具包开发人员中心中的 MVVM 工具包简介