Community Toolkit 8.0官网翻译🔥

1/2/2024 MVVMCommunityToolkit

# 命令(Commands)

创建命令可能会很重复,需要为我们想要以抽象方式向应用程序中的各种UI组件公开的每个方法设置一个属性(例如按钮)。

这就是新的 [RelayCommand] 属性发挥作用的地方:MVVM Toolkit将使用库中包含的RelayCommand类型,根据已注释的方法生成具有正确签名的命令。

为了比较,以下是通常设置命令的方式:

private IRelayCommand<User> greetUserCommand;

public IRelayCommand<User> GreetUserCommand => greetUserCommand ??= new RelayCommand<User>(GreetUser);

private void GreetUser(User user)
{
    Console.WriteLine($"Hello {user.Name}!");
}
1
2
3
4
5
6
7
8

现在可以简化成这样:

[RelayCommand]
private void GreetUser(User user)
{
    Console.WriteLine($"Hello {user.Name}!");
}
1
2
3
4
5

# 可观察属性(Observable properties)

编写可观察属性可能非常冗长,特别是当必须添加处理通知相关属性的附加逻辑时。现在,通过使用MVVM Toolkit的新属性,并让源代码生成器在幕后创建可观察属性,所有这些都可以大大简化。

新属性包括 [ObservableProperty]、[NotifyPropertyChangedFor]、[NotifyCanExecuteChangedFor]、[NotifyDataErrorInfo] 和 [NotifyPropertyChangedRecipients]。让我们快速了解所有这些新属性可以做什么。

考虑一个场景,其中有两个可观察属性,一个依赖属性和上面定义的命令,其中依赖属性和命令都需要在两个可观察属性中的任何一个更改时得到通知。也就是说,每当FirstName或LastName更改时,FullName以及GreetUserCommand也会得到通知。

在过去,这是如何完成的:

private string? firstName;

public string? FirstName
{
    get => firstName;
    set
    {
        if (SetProperty(ref firstName, value))
        {
            OnPropertyChanged(nameof(FullName));
            GreetUserCommand.NotifyCanExecuteChanged();
        }
    }
}

private string? lastName;

public string? LastName
{
    get => lastName;
    set
    {
        if (SetProperty(ref lastName, value))
        {
            OnPropertyChanged(nameof(FullName));
            GreetUserCommand.NotifyCanExecuteChanged();
        }
    }
}

public string? FullName => $"{FirstName} {LastName}";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

现在可以简化成这样:

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
[NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]
private string? firstName;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
[NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]
private string? lastName;
public string? FullName => $"{FirstName} {LastName}";
1
2
3
4
5
6
7
8
9
10

MVVM Toolkit将处理这些属性的代码生成,包括插入所有逻辑以引发指定的属性更改或可执行更改事件。

但是,等等,还有更多!当使用 [ObservableProperty] 生成可观察属性时,MVVM Toolkit 现在还将生成两个没有实现的部分方法:On<PROPERTY_NAME>Changing 和 On<PROPERTY_NAME>Changed。这些方法可用于在属性更改时注入附加逻辑,而无需回退到使用手动属性。请注意,由于这两个方法是部分的、返回 void 且没有定义的,如果未实现它们,Java编译器将完全删除它们,这意味着当不使用它们时,它们将简单消失并对应用程序不会产生任何额外开销 🚀

这是它们如何被使用的一个示例:

[ObservableProperty]
private string name;

partial void OnNameChanging(string name)
{
    Console.WriteLine($"The name is about to change to {name}!");
}

partial void OnNameChanged(string name)
{
    Console.WriteLine($"The name just changed to {name}!");
}
1
2
3
4
5
6
7
8
9
10
11
12

当然,您也可以自由选择只实现这两个方法中的一个,或者根本不实现。

从上面的片段中,源代码生成器将生成类似于以下代码的代码:

public string Name
{
    get => name;
    set
    {
        if (!EqualityComparer<string>.Default.Equals(name, value))
        {
            OnNameChanging(value);
            OnPropertyChanging();
            name = value;
            OnNameChanged();
            OnPropertyChanged();
        }
    }
}

partial void OnNameChanging(string name);

partial void OnNameChanged(string name);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

[ObservableProperty] 属性还支持验证:如果表示属性的任何字段具有一个或多个继承自 ValidationAttribute 的属性,那么这些属性将自动复制到生成的属性中,因此在使用 ObservableValidator 创建可验证表单时,该方法也得到了完全支持。如果您还希望在设置属性值时验证属性,还可以添加 [NotifyDataErrorInfo],以便在属性设置器中生成验证代码。

# 命令的取消支持(Cancellation support for commands)

在 [RelayCommand] 属性中添加了一个新属性,可用于指示源代码生成器在原始命令旁生成一个取消命令。此取消命令可用于取消异步命令的执行。

这还展示了 [RelayCommand] 如何自动适应异步方法和接受参数的方法,并在幕后创建异步命令的实现。这还使得可以轻松设置绑定以显示进度指示器等其他功能!

这是它们如何被使用的一个示例:

[RelayCommand(IncludeCancelCommand = true)]
private async Task DoWorkAsync(CancellationToken token)
{
    // Do some long running work with cancellation support
}
1
2
3
4
5

从这个小片段中,生成器将产生以下代码:

private AsyncRelayCommand? doWorkCommand;

public IAsyncRelayCommand DoWorkCommand => doWorkCommand ??= new AsyncRelayCommand(DoWorkAsync);

ICommand? doWorkCancelCommand;

public ICommand DoWorkCancelCommand => doWorkCancelCommand ??= IAsyncRelayCommandExtensions.CreateCancelCommand(UpdateSomethingCommand);
1
2
3
4
5
6
7

这生成的代码,结合IAsyncRelayCommandExtensions.CreateCancelCommand API中的逻辑,使您只需一行代码即可生成一个命令,通知UI工作何时开始或正在运行,具有自动并发控制(命令在已经运行时默认禁用)。独立的取消命令将在主命令开始或完成运行时得到通知,并在执行时向传递给主命令包装的方法的令牌发出取消信号。所有这些都完全抽象化,只需一个单一的属性就可以轻松访问 🙌

# 为生成的属性提供广播更改支持(Broadcast change support for generated properties)

我们还添加了一个新的 [NotifyPropertyChangedRecipients] 属性,可以用于从继承自 ObservableRecipient 的类型生成的可观察属性(或者带有 [ObservableRecipient] 注释的类型)。使用它将生成对 Broadcast 方法的调用,向所有其他已订阅的组件发送有关刚刚发生的属性更改的消息。这在场景中可能很有用,其中来自视图模型的属性更改还需要通知应用程序中的其他组件(假设有一个 IsLoggedIn 的布尔属性,当用户登录时更新;这可以通过广播的消息通知并触发应用程序中的其他组件刷新)。

它的使用方式如下:

[ObservableProperty]
[NotifyPropertyChangedRecipients]
private string name;
1
2
3

特定的操作或属性将生成类似于以下代码的代码:

public string Name
{
    get => name;
    set
    {
        if (!EqualityComparer<string>.Default.Equals(name, value))
        {
            OnNameChanging(value);
            OnPropertyChanging();
            string oldValue = name;
            name = value;
            Broadcast(oldValue, value, nameof(Name));
            OnNameChanged();
            OnPropertyChanged();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

这是另一个增强生成属性的特性,以确保它们可以在几乎所有场景中使用,而无需强制回退到手动属性。通过引入这样的特性,生成的属性具有更广泛的适用性,可以更轻松地在各种情况下使用,而无需手动处理复杂的代码。这有助于提高代码的可维护性和可重用性。

# 视图模型组合(ViewModel composition)

Java 不支持多继承,有时这可能会成为阻碍。 如果有一个视图模型必须继承自特定类型,但您还想要注入 INotifyPropertyChanged 支持,或者希望它还继承自 ObservableRecipient 以获取其 API,该怎么办? MVVM Toolkit 现在提供了一种绕过这个问题的方法,通过引入代码生成的属性,允许将这些类型的逻辑注入到任意类中。这些属性包括 [INotifyPropertyChanged]、[ObservableObject] 和 [ObservableRecipient]。 将它们添加到一个类中将导致 MVVM Toolkit 源代码生成器将该类型的所有逻辑包含到该类中,就好像该类也从该类型继承一样。例如:

[INotifyPropertyChanged]
partial class MyObservableViewModel : DatabaseItem
{
}
1
2
3
4

这个 MyObservableViewModel 将按照预期继承自 DatabaseItem,但使用 [INotifyPropertyChanged] 将让它也获得对 INotifyPropertyChanged 的支持,以及 ObservableObject 自身包含的所有辅助 API。

尽管在需要时仍建议从基本类型(例如 ObservableObject)继承,因为这样可以帮助减小二进制大小,但在需要时通过这种方式注入代码可以帮助解决 Java 的限制,在无法更改视图模型的基本类型的情况下使用,就像上面的示例中那样。