概述
CommunityToolkit.Mvvm(原名 Microsoft.Toolkit.Mvvm)是微软官方维护的 MVVM 工具包,提供了一套轻量、高效、可预测的 MVVM 基础设施。它支持 .NET Standard 2.0+,可在 WPF、UWP、WinUI、MAUI 等平台使用。
核心功能:
ObservableObject — 可观察对象基类
RelayCommand — 命令封装
ObservableRecipient — 带消息机制的观察者
IMessenger — 弱引用消息通信
- Source Generators — 编译时源代码生成器,减少样板代码
第一层:基础 — ObservableObject 与 RelayCommand
ObservableObject
所有 ViewModel 的基类,实现了 INotifyPropertyChanged。
1 2 3 4 5 6 7 8 9
| public class MainViewModel : ObservableObject { private string _name; public string Name { get => _name; set => SetProperty(ref _name, value); } }
|
SetProperty 做了三件事:
- 比较新旧值,相等则跳过
- 赋值
- 触发
PropertyChanged 事件
也可以手动触发:
1 2 3 4 5
| public void Update() { Name = "New"; OnPropertyChanged(nameof(Name)); }
|
RelayCommand
将方法暴露为 ICommand,用于 View 绑定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MainViewModel : ObservableObject { public RelayCommand SubmitCommand { get; }
public MainViewModel() { SubmitCommand = new RelayCommand(Submit, CanSubmit); }
private void Submit() { }
private bool CanSubmit() => !string.IsNullOrEmpty(Name); }
|
带参数的版本 RelayCommand<T>:
1 2 3
| public RelayCommand<string> SaveCommand { get; }
|
View 绑定
1 2
| <TextBox Text="{Binding Name}" /> <Button Content="提交" Command="{Binding SubmitCommand}" />
|
第二层:进阶 — 消息通信与异步命令
IMessenger 与 ObservableRecipient
ObservableRecipient 继承 ObservableObject,内置 IMessenger 支持,适用于页面间通信。
发送消息:
1 2 3 4 5 6 7 8
| Messenger.Register<MainViewModel, NavigationMessage>(this, (r, m) => { r.CurrentView = m.Value; });
Messenger.Send(new NavigationMessage("DetailPage"));
|
弱引用机制:IMessenger 内部使用弱引用持有接收者,避免内存泄漏。
AsyncRelayCommand
处理异步操作,自动管理 IsRunning 状态。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public AsyncRelayCommand LoadDataCommand { get; }
public MainViewModel() { LoadDataCommand = new AsyncRelayCommand(LoadDataAsync); }
private async Task LoadDataAsync() { await Task.Delay(3000); }
|
支持取消令牌:
1 2 3 4 5 6
| public AsyncRelayCommand<CancellationToken> SearchCommand { get; }
private async Task SearchAsync(CancellationToken ct) { await Task.Delay(5000, ct); }
|
第三层:深入 — Source Generators(推荐)
CommunityToolkit.Mvvm 8.0+ 引入了 源代码生成器,大幅减少样板代码。
[ObservableProperty]
替换手写的属性 + 字段:
1 2 3 4 5 6 7 8 9 10 11 12
| public partial class MainViewModel : ObservableObject { [ObservableProperty] private string _name;
[ObservableProperty] private int _age; }
|
属性名转换规则:字段名 _name → 属性名 Name(去掉下划线 + 驼峰大写)。
[RelayCommand]
将方法直接转为 ICommand 属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public partial class MainViewModel : ObservableObject { [RelayCommand] private void Submit() { }
[RelayCommand] private async Task LoadDataAsync() { } }
|
带 CanExecute:
1 2 3 4
| [RelayCommand(CanExecute = nameof(CanSubmit))] private void Submit() { }
private bool CanSubmit() => !string.IsNullOrEmpty(Name);
|
[NotifyPropertyChangedFor]
当属性 A 变化时通知属性 B:
1 2 3 4 5 6 7 8 9
| [ObservableProperty] [NotifyPropertyChangedFor(nameof(FullName))] private string _firstName;
[ObservableProperty] [NotifyPropertyChangedFor(nameof(FullName))] private string _lastName;
public string FullName => $"{FirstName} {LastName}";
|
[NotifyCanExecuteChangedFor]
属性变化时刷新命令状态:
1 2 3
| [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SubmitCommand))] private string _name;
|
[ObservableProperty] 完整示例
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
| public partial class MainViewModel : ObservableObject { [ObservableProperty] private string _name;
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SubmitCommand))] private string _email;
partial void OnNameChanged(string value) { Console.WriteLine($"Name changed to: {value}"); }
partial void OnEmailChanged(string value) { Console.WriteLine($"Email changed to: {value}"); }
[RelayCommand(CanExecute = nameof(CanSubmit))] private void Submit() { }
private bool CanSubmit() => !string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Email); }
|
第四层:高级模式
依赖注入集成
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
| public partial class App : Application { public IServiceProvider Services { get; }
public App() { var services = new ServiceCollection(); services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default); services.AddSingleton<IDialogService, DialogService>(); services.AddTransient<MainViewModel>(); services.AddTransient<MainWindow>(); Services = services.BuildServiceProvider(); } }
public partial class MainViewModel : ObservableRecipient { private readonly IDialogService _dialog;
public MainViewModel(IDialogService dialog, IMessenger messenger) : base(messenger) { _dialog = dialog; } }
|
Messenger 消息类型
WeakReferenceMessenger(默认,弱引用)和 StrongReferenceMessenger(强引用,按需手动清理):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| IMessenger messenger = WeakReferenceMessenger.Default;
public sealed class UserLoggedInMessage : ValueChangedMessage<string> { public UserLoggedInMessage(string username) : base(username) { } }
WeakReferenceMessenger.Default.Send(new UserLoggedInMessage("admin"));
WeakReferenceMessenger.Default.Register<MainViewModel, UserLoggedInMessage>(this, (r, m) => { r.CurrentUser = m.Value; });
|
ObservableObject 扩展
1 2 3 4 5 6 7 8 9
| public partial class LoginViewModel : ObservableObject { [ObservableProperty] [NotifyDataErrorInfo] [Required(ErrorMessage = "用户名不能为空")] [MinLength(3)] private string _username; }
|
配合 ObservableValidator 使用数据注解验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public partial class LoginViewModel : ObservableValidator { [ObservableProperty] [Required] [MinLength(3)] [NotifyDataErrorInfo] private string _username;
[RelayCommand] private void Login() { ValidateAllProperties(); if (HasErrors) return; } }
|
实战:完整登录页面
ViewModel
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 32 33 34 35 36 37 38
| public partial class LoginViewModel : ObservableValidator { [ObservableProperty] [Required(ErrorMessage = "请输入用户名")] [NotifyDataErrorInfo] [NotifyCanExecuteChangedFor(nameof(LoginCommand))] private string _username;
[ObservableProperty] [Required(ErrorMessage = "请输入密码")] [NotifyDataErrorInfo] [NotifyCanExecuteChangedFor(nameof(LoginCommand))] private string _password;
[ObservableProperty] private string _statusMessage;
[RelayCommand(CanExecute = nameof(CanLogin))] private async Task LoginAsync() { ValidateAllProperties(); if (HasErrors) return;
StatusMessage = "登录中..."; try { await Task.Delay(2000); StatusMessage = "登录成功"; } catch { StatusMessage = "登录失败"; } }
private bool CanLogin() => !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password); }
|
View (XAML)
1 2 3 4 5 6 7 8
| <StackPanel MaxWidth="400" HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBox Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}" placeholder="用户名" /> <PasswordBox Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}" placeholder="密码" /> <TextBlock Text="{Binding StatusMessage}" /> <Button Content="登录" Command="{Binding LoginCommand}" /> </StackPanel>
|
与 Prism 对比
| 特性 |
CommunityToolkit.Mvvm |
Prism |
| 包体积 |
轻量(~200KB) |
较大 |
| Source Generators |
✅ 原生支持 |
❌ 无 |
| 依赖注入 |
需自行集成 |
内置容器 |
| 导航 |
❌ 需自行实现 |
内置导航 |
| 区域管理 |
❌ |
✅ |
| 学习曲线 |
低 |
中高 |
选型建议:小项目或追求简洁用 CommunityToolkit.Mvvm;大型企业应用,需要导航/区域/模块化用 Prism。
总结
- 基础层:
ObservableObject + RelayCommand 构成 MVVM 最小单元
- 进阶层:
AsyncRelayCommand 处理异步,IMessenger 通信
- 生产力层:Source Generators(
[ObservableProperty]、[RelayCommand])消除样板代码
- 架构层:依赖注入、
ObservableValidator、消息解耦
CommunityToolkit.Mvvm 的核心哲学是约定优于配置 + 编译时代码生成,让你写更少的代码,做更多的事。