CommunityToolkit.Mvvm 框架
CommunityToolkit.Mvvm 框架
参考资料
- 官方文档:https://learn.microsoft.com/zh-cn/dotnet/communitytoolkit/mvvm/
- 项目地址:https://github.com/CommunityToolkit/dotnet/tree/main/src/CommunityToolkit.Mvvm
Prism 是什么
- 包
CommunityToolkit.Mvvm
(又名 MVVM 工具包,以前名为Microsoft.Toolkit.Mvvm
) 是一个现代、快速且模块化的 MVVM 库。 - 面向 .NET Standard,因此可在任何应用平台上使用:UWP、WinForms、WPF、Xamarin、Uno ,可以在任何运行时上使用:.NET Native、.NET Core、.NET Framework或 Mono
框架组成
ObservableObject
- CommunityToolkit.Mvvm.ComponentModel
- ObservableObject:用作 ViewModel 基类,实现了 INotifyPropertyChanging,提供了方便的属性修改方法 SetProperty
- ObservableRecipient:支持 IMessenger 功能,继承了 ObservableObject
- ObservableValidator:支持属性验证,继承了 ObservableObject ,实现了 INotifyDataErrorInfo
IRelayCommand
- CommunityToolkit.Mvvm.Input
- IRelayCommand : 实现了 ICommand ,提供了 NotifyCanExecuteChanged 方法
- 提供了普通命令 RelayCommand 、泛型命令 RelayCommand
<T
> 以及对应的异步命令类型
IOC
- CommunityToolkit.Mvvm.DependencyInjection
- MVVM 工具包不提供内置 API 来方便使用此模式
- 需要手动创建容器并维护
IMessenger
CommunityToolkit.Mvvm.Messaging
CommunityToolkit.Mvvm.Messaging.Messages
接口 IMessenger 是可用于在不同对象之间交换消息的类型协定
WeakReferenceMessenger:弱引用,框架为收件人提供自动内存管理
StrongReferenceMessenger:强引用,要求开发人员在不再需要接收者时手动取消订阅
源生成器
- 从版本 8.0 开始,MVVM 工具包包含全新的 Roslyn 源生成器,有助于在使用 MVVM 体系结构编写代码时大幅减少样板。
- 可以自动生成 ObservableProperty 和 RelayCommand 等内容
- 使用方法:
- 将 ViewModel 声明为分部类
- 在需要生成的位置添加对应的特性,可以查看框架项目源码了解有哪些特性
ObservableObject
internal class MainWindowViewModel : ObservableObject
{
// 1. 基本用法
private string title = string.Empty;
public string Title { get => title; set => SetProperty(ref title, value); }
// 2. 包装不可观测类,实现当不可观测类的属性修改时通知前台该属性的修改
[ObservableProperty] // 如果不添加该特性,ObUser属性修改时就不能通知,但是 ObservableUser 对象属性修改时依然可以通知
private ObservableUser obUser;
// 3. Task属性,实质是对 Task 的包装,使 Task 的属性修改时能够通知前台
// SetPropertyAndNotifyOnCompletion 在任务完成后才进行通知
// TaskNotifier 是对 Task 的包装
private TaskNotifier<string>? info;
public Task<string>? Info { get => info; set => SetPropertyAndNotifyOnCompletion<string>(ref info, value); }
}
// 不可观测类的包装类
public class ObservableUser : ObservableObject
{
private readonly User user;
public ObservableUser(User user) => this.user = user;
// 提供外部访问属性
public string Name
{
get => user.Name;
// 调用 SetProperty 的重载方法,将新的 value 赋值给 user
set => SetProperty(user.Name, value, user, (u, n) => u.Name = n);
}
}
ObservableRecipient
接收者实现 IRecipient
<TMessage
> 接口,当收到消息通知时将执行 Receive 方法接收者使用 IsActive 属性控制激活状态,控制是否接收通知
public class MyViewModel : ObservableRecipient, IRecipient<LoggedInUserRequestMessage> { public void Receive(LoggedInUserRequestMessage message) { // Handle the message here } }
ObservableValidator
属性修改时先进行验证
public class RegistrationForm : ObservableValidator { private string name; [Required] [MinLength(2)] [MaxLength(100)] [CustomValidation(typeof(RegistrationForm), nameof(ValidateName))] // 自定义验证方法 public string Name { get => name; set => SetProperty(ref name, value, true); } public static ValidationResult ValidateName(string name, ValidationContext context) { // 自定义验证方法 } }
自定义验证属性
public sealed class GreaterThanAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { // 验证代码 } }
IRelayCommand
该接口公开了 NotifyCanExecuteChanged 引发 CanExecuteChanged 事件的方法。
private bool btnEnable = false; public RelayCommand ClickCommand = new RelayCommand(ButtonClick,()=>btnEnable);
AsyncRelayCommand 扩展了同步命令功能
- ExecutionTask 属性:监视操作进度
- CanBeCanceled 、IsCancellationRequested 属性:取消相关操作
- IsRunning 属性:命令是否正在运行
- Cancel() 方法:取消执行
RelayCommand(Action<T?> execute) 调用方法传递的参数需要是可空的
IOC
public sealed partial class App : Application
{
public App()
{
// 定义容器并调用静态方法注册服务
Services = ConfigureServices();
this.InitializeComponent();
}
// 用于访问当前程序
public new static App Current => (App)Application.Current;
// 用于通过 App 获取容器
public IServiceProvider Services { get; }
// 定义容器注册服务,以 Microsoft.Extensions.DependencyInjection 为例
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddSingleton<IFilesService, FilesService>();
services.AddSingleton<IEmailService, EmailService>();
return services.BuildServiceProvider();
}
}
// 需要手动获取服务的位置调用 App 的属性获取
IFilesService filesService = App.Current.Services.GetService<IFilesService>();
// 也可以通过构造函数注入
public FileLogger(IFilesService fileService, IConsoleService consoleService) {}
// 为 View 绑定 ViewModel
public ContactsView()
{
this.InitializeComponent();
this.DataContext = App.Current.Services.GetService<ContactsViewModel>();
}
Messenger
消息类型:可以直接使用,也可以通过派生类使用
- AsyncCollectionRequestMessage
<T
> - AsyncRequestMessage
<T
> - CollectionRequestMessage
<T
> - RequestMessage
<T
> - ValueChangedMessage
<T
> - PropertyChangedMessage
<T
>
- AsyncCollectionRequestMessage
创建消息:
public class LoggedInUserChangedMessage : ValueChangedMessage<User> { public LoggedInUserChangedMessage(User user) : base(user){} }
发送消息:
// 使用 Messenger 发送消息 WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(user)); // 如果继承了 ObservableRecipient 基类, 属性修改是会自动调用 Broadcast 方法发布消息 Broadcast(oldValue, value); /* ObservableRecipient 中的 SetProperty<T> 源码中增加 Broadcast 的调用 if (propertyChanged && broadcast) { Broadcast(oldValue, newValue, propertyName); } Broadcast 源码,可以重写实现向指定频道发送消息 protected virtual void Broadcast<T>(T oldValue, T newValue, string? propertyName) { PropertyChangedMessage<T> message = new(this, propertyName, oldValue, newValue); _ = Messenger.Send(message); } */
注册消息:
实现接口
IRecipient<TMessage>
并设置 IsActive 属性为 true 可以自动注册所有,设置 IsActive 属性为 false 自动取消全部订阅或者直接调用
WeakReferenceMessenger.Default.Register<TMessage>()
方法注册消息时可以指定 token 用于只接收指定频道的信息
public class MyRecipient : IRecipient<LoggedInUserChangedMessage> { public MyRecipient(){ // 自动注册全部 IRecipient<T> 接口中的消息,如果需要指定 token,需要重写 OnActivated() 方法 this.IsActive = true; // IMessenger 直接注册 // WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this, (obj,msg) => { }); // 注册到 MainWindowViewModel 频道,string 表示频道的标志的类型 // WeakReferenceMessenger.Default.Register<ValueChangedMessage<string>,string>(this, "MainWindowViewModel", (obj,msg) => { }); // 注册全部消息,也可以指定频道 // WeakReferenceMessenger.Default.RegisterAll(this); } public void Receive(LoggedInUserChangedMessage message) { // 消息处理函数 } }
SourceGenerators
将类型声明为 partial
在需要生成代码的位置使用特性
属性生成:支持字段名为 lowerCamel 、_lowerCamel 、m_lowerCamel,属性为对应的 UpperCamel
[ObservableProperty] private string? name;
// 生成的代码 public string? Name { get => name; set { // 判定新的值与原值是否相等,相等则不会重新赋值 if (!EqualityComparer<string?>.Default.Equals(name, value)) { OnNameChanging(value); // 分部方法,在修改属性前执行 OnPropertyChanging(); // 通知属性将要修改 name = value; OnNameChanged(value); // 分部方法,在修改属性后执行 OnPropertyChanged(); // 通知属性已经修改 } } } // 如果分部方法没有被重写,编译时将删掉该分部方法和对其的调用 partial void OnNameChanging(string? value); partial void OnNameChanged(string? value);
命令生成器:直接在方法上标注特性,将更加方法是否异步生成对应的命令
[RelayCommand] async Task AsyncButtonClick () {} // 可以传入参数 // CanExecute 可以直接传入 bool 类型属性,toolkit 会自动生成 Action<bool> ()=> ... // IncludeCancelCommand 是否生成对应的取消命令, 如果取消必须传入 token [RelayCommand(CanExecute = nameof(IsActive),IncludeCancelCommand = true)] Task FooAsync(Cancellation token) { try{ return Task.Delay(2000).ContinueWith(Task t => {if(t.IsCompletedSuccessfully){/* 具体操作 */})} }catch(TaskCanceledException){ return Task.FromCanceled(token); } }
其他生成器
[ObservableProperty] // 依赖属性 [NotifyPropertyChangedFor(nameof(FullName))] // 通知 FullName 属性修改 [NotifyCanExecuteChangedFor(nameof(MyCommand))] // 通知命令状态改变 [Required] // 属性校验 [NotifyPropertyChangedRecipients] // 消息广播 PropertyChangedMessage<T>IMessenger private string? name;