第一部分:Microsoft.ToolKit.Mvvm 框架深度解析(含更名说明)
1.1 框架定位与现状
框架核心介绍
Microsoft.ToolKit.Mvvm(现正式更名为CommunityToolkit.Mvvm)是微软官方推出的轻量级 MVVM 框架,前身是 Windows Community Toolkit 的核心组件,专为 WPF、WinUI、MAUI 等 .NET 桌面/跨平台应用设计。其核心目标是简化 MVVM 架构实现,减少样板代码,实现 UI 与业务逻辑的彻底解耦,是当前 WPF 开发中官方推荐的 MVVM 解决方案。
关键更名说明(必看)
重要提示:2021 年后,Microsoft.ToolKit.Mvvm 正式更名为 CommunityToolkit.Mvvm,两者本质是同一个框架,由同一团队维护,功能完全兼容、无缝衔接。旧包Microsoft.ToolKit.Mvvm 已被 NuGet 标记为废弃,不再更新,当前开发需使用新包名 CommunityToolkit.Mvvm(当前最新稳定版 8.4.2)。
核心特性
| 特性 | 说明 |
|---|---|
| 极简无样板 | 基于编译时源生成,通过 [ObservableProperty]、[RelayCommand]等特性,自动生成属性通知、命令绑定代码,无需手动实现接口 |
| 高性能 | 基于编译时源生成,无反射开销,性能优于基于反射的 MVVM 框架,且接近手写实现 |
| 异步友好 | 原生支持 AsyncRelayCommand,无需手动封装,轻松处理异步操作(如 API 请求、数据库查询),避免 UI 阻塞 |
| 强兼容性 | 基于 .NET Standard 2.0+ 构建,兼容 .NET Core 3.0+、.NET 5+ 及后续版本,完美适配 WPF 所有版本,支持跨平台(WinUI、MAUI 等) |
| 功能完备 | 内置数据验证(ObservableValidator)、消息通信(WeakReferenceMessenger)、依赖注入(Ioc),满足各类 WPF 项目需求 |
框架优势(对比旧版及其他 MVVM 框架)
-
对比旧版 Microsoft.ToolKit.Mvvm :新包
CommunityToolkit.Mvvm持续迭代,新增异步命令、编译时生成等核心功能,修复旧版兼容性问题,是官方唯一维护版本 -
对比 MvvmLight :无需手动编写
Set()方法,编译时生成代码更高效,无反射开销,且有微软官方支持,替代 MvvmLight 成为新项目首选 -
对比 Prism:轻量级无依赖,学习曲线平缓,无需引入复杂的架构概念,适合中小型 WPF 项目快速开发;若需复杂导航,可灵活集成
1.2 核心架构原理(沿用 MVVM 三层架构)
三层架构解析(贴合 WPF 实际开发)
Microsoft.ToolKit.Mvvm(CommunityToolkit.Mvvm)严格遵循 MVVM 三层架构,各层职责清晰、完全解耦,结合 WPF 绑定特性优化设计,具体分层如下:
【View 层】:负责 UI 布局与绑定,仅包含 XAML 界面和少量后台代码(仅用于绑定 DataContext、初始化,无任何业务逻辑),通过 DataContext 与 ViewModel 实现双向绑定,完全不依赖 ViewModel
【ViewModel 层】:核心中间层,基于框架核心组件实现属性通知、命令绑定、业务逻辑、状态管理,通过服务调用与 Model 层交互,不直接操作 UI,可独立测试
【Model 层】:纯数据与核心逻辑层,包含实体类(POCO)、数据访问(数据库/API)、业务规则,完全无 UI 依赖,可在不同项目中复用
关键技术原理(框架核心组件)
(1)属性通知机制(ObservableObject + 特性生成)
框架核心基类 ObservableObject 实现了 INotifyPropertyChanged 接口,封装了属性变更通知逻辑。结合[ObservableProperty] 特性,可自动生成属性的 get/set 方法及属性变更通知,彻底消除手写样板代码。
核心用法与源码解析:
csharp
/// <summary>
/// 可观察对象基类,实现 INotifyPropertyChanged 接口
/// 作用:属性变化时通知UI自动刷新,是所有ViewModel的基类
/// </summary>
public class ObservableObject : INotifyPropertyChanged
{
// 属性变更事件:属性值改变后触发,通知UI更新
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 触发属性变更通知
/// [CallerMemberName]:自动获取调用者的属性名,避免手写字符串出错
/// </summary>
/// <param name="propertyName">属性名(自动获取)</param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// 赋值 + 触发属性通知(简化写法)
/// </summary>
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
return false;
field = newValue;
OnPropertyChanged(propertyName);
return true;
}
}
// 实际开发用法(无需手写get/set和通知逻辑)
public partial class MainViewModel : ObservableObject
{
// [ObservableProperty] 特性自动生成 public string Title { get; set; } 及通知逻辑
[ObservableProperty]
private string _title = "WPF + CommunityToolkit.Mvvm 实战";
// 自动生成 Count 属性及 OnCountChanged 方法(可选重写)
[ObservableProperty]
private int _count = 0;
}
关键亮点:通过 partial 类配合编译时生成,无需手动编写属性通知代码,减少人为错误,提升开发效率;支持重写自动生成的变更通知方法,灵活扩展逻辑。
(2)命令绑定原理(RelayCommand + AsyncRelayCommand)
框架提供RelayCommand(同步命令)和 AsyncRelayCommand(异步命令),均实现 ICommand 接口,无需手动实现 CanExecute 和 Execute 方法,可直接绑定到 WPF 控件(如 Button、MenuItem)。
核心用法与源码解析:
csharp
/// <summary>
/// 同步中继命令:包装同步方法,实现命令绑定
/// </summary>
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public event EventHandler CanExecuteChanged;
// 构造方法:传入执行方法和可执行条件(可执行条件可选)
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
// 判断命令是否可执行(控件自动启用/禁用)
public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
// 执行命令逻辑
public void Execute(object parameter) => _execute();
// 手动刷新命令可执行状态
public void NotifyCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
// 异步中继命令(原生支持异步操作,避免UI阻塞)
public class AsyncRelayCommand : ICommand
{
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
private bool _isExecuting;
// 核心逻辑:异步执行方法,自动管理执行状态
public async void Execute(object parameter)
{
if (!CanExecute(parameter) || _isExecuting)
return;
_isExecuting = true;
try
{
await _execute();
}
finally
{
_isExecuting = false;
NotifyCanExecuteChanged(); // 执行完成后刷新状态
}
}
public bool CanExecute(object parameter) => !_isExecuting && (_canExecute?.Invoke() ?? true);
public event EventHandler CanExecuteChanged;
public void NotifyCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
// 实际开发用法(特性自动生成命令)
public partial class MainViewModel : ObservableObject
{
[ObservableProperty]
private int _count = 0;
// [RelayCommand] 特性自动生成 IncrementCommand(ICommand 类型)
[RelayCommand]
private void Increment()
{
Count++;
}
// 带可执行条件的命令
[RelayCommand(CanExecute = nameof(CanDelete))]
private void Delete()
{
// 删除逻辑
}
private bool CanDelete() => Count > 0;
// 异步命令(自动生成 LoadDataCommand)
[RelayCommand]
private async Task LoadDataAsync()
{
// 模拟异步请求(如API调用)
await Task.Delay(1000);
Title = "数据加载完成";
}
}
(3)数据验证机制(ObservableValidator)
框架内置 ObservableValidator 类,继承自 ObservableObject,支持数据注解(如 [Required]、[MinLength]),自动收集验证错误,触发验证通知,无缝集成 WPF 绑定验证。
核心用法:
csharp
using CommunityToolkit.Mvvm.ComponentModel;
using System.ComponentModel.DataAnnotations;
public partial class LoginViewModel : ObservableValidator
{
// 结合数据注解和 ObservableProperty 特性,自动实现验证
[ObservableProperty]
[Required(ErrorMessage = "用户名不能为空")]
[StringLength(20, ErrorMessage = "用户名长度不能超过20个字符")]
private string _username;
[ObservableProperty]
[Required(ErrorMessage = "密码不能为空")]
[MinLength(6, ErrorMessage = "密码长度至少6位")]
private string _password;
[RelayCommand]
private async Task LoginAsync()
{
// 验证所有属性
ValidateAllProperties();
if (HasErrors) return; // 有验证错误,终止执行
// 执行登录逻辑
await AuthService.Login(Username, Password);
}
}
(4)消息通信机制(WeakReferenceMessenger)
框架提供 WeakReferenceMessenger 消息总线,基于弱引用设计,实现 ViewModel 之间、View 与 ViewModel 之间的解耦通信,避免强引用导致的内存泄漏,用法简洁高效。同时支持 StrongReferenceMessenger(高性能,适合无内存泄漏风险的场景)。
核心用法:
csharp
// 1. 定义消息类型(可携带数据)
public class MessageModel
{
public string Content { get; set; }
}
// 2. 发送消息(ViewModel A)
[RelayCommand]
private void SendMessage()
{
WeakReferenceMessenger.Default.Send(new MessageModel { Content = "数据更新通知" });
}
// 3. 接收消息(ViewModel B)
public MainViewModel()
{
// 注册消息监听,自动解耦
WeakReferenceMessenger.Default.Register<MessageModel>(this, (recipient, message) =>
{
// 处理消息
Title = message.Content;
});
}
// 4. 注销消息(避免内存泄漏,在页面关闭时调用)
public void Cleanup()
{
WeakReferenceMessenger.Default.Unregister<MessageModel>(this);
// 注销所有消息监听(推荐)
// WeakReferenceMessenger.Default.UnregisterAll(this);
}
1.3 环境搭建与安装
开发环境要求
-
Visual Studio 2026
-
.NET 6+/7+/8+(推荐 .NET 8,长期支持版本,兼容性最佳,与 CommunityToolkit.Mvvm 8.4.2 完美适配)
-
WPF 项目(默认启用 XAML 绑定,支持 MVVM 开发)
安装 CommunityToolkit.Mvvm(原 Microsoft.ToolKit.Mvvm)
由于旧包Microsoft.ToolKit.Mvvm 已废弃,当前开发需安装新包(最新稳定版 8.4.2),三种安装方式任选其一:
-
NuGet 包管理器(推荐) :右键 WPF 项目 → 管理 NuGet 包 → 搜索
CommunityToolkit.Mvvm→ 安装最新稳定版(8.4.2) -
.NET CLI 命令行 :打开终端,进入项目目录,执行命令:
dotnet add package CommunityToolkit.Mvvm --version 8.4.2 -
项目文件直接引用 :编辑 .csproj 文件,添加如下配置:
<ItemGroup><PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" /></ItemGroup>
第二部分:完整实战项目(WPF + CommunityToolkit.Mvvm)
2.1 项目概述
本实战项目为「WPF 任务管理系统」,基于 CommunityToolkit.Mvvm(原 Microsoft.ToolKit.Mvvm)8.4.2 版本开发,实现任务的增、删、改、查、数据验证、异步加载等核心功能,严格遵循 MVVM 三层架构,贴合实际开发场景,演示框架核心组件的用法。
2.2 项目结构(标准 MVVM 分层)
plain
00024.WPF + Microsoft.ToolKit.Mvvm 技术指南与实战项目/
├── Helpers/ // 辅助工具类
│ ├── BooleanToVisibilityConverter.cs // 布尔值转可见性转换器
│ └── MessengerHelper.cs // 消息通信辅助类(封装WeakReferenceMessenger)
├── Models/ // 数据模型层(纯数据,无UI依赖)
│ └── TaskItem.cs // 任务实体类
├── ViewModels/ // 视图模型层(业务逻辑核心)
│ ├── MainViewModel.cs // 主窗口ViewModel
│ └── TaskViewModel.cs // 任务列表ViewModel(拆分复用)
├── Views/ // 视图层(纯UI,无业务逻辑)
│ ├── MainWindow.xaml // 主窗口(任务列表+新增表单)
│ ├── MainWindow.xaml.cs // 主窗口后台代码(仅初始化)
│ └── TaskItemView.xaml // 任务项子视图(可选,用于复用)
├── App.xaml // 应用程序资源配置文件
├── App.xaml.cs // 应用程序入口代码文件
└── AssemblyInfo.cs // 程序集信息配置文件
2.3 各层实现代码
2.3.1 Model 层:TaskItem.cs(任务实体)
纯数据实体,继承 ObservableObject 实现属性通知,支持 UI 绑定刷新,无任何业务逻辑和 UI 依赖。
csharp
using CommunityToolkit.Mvvm.ComponentModel;
namespace _00024.WPF___Microsoft.ToolKit.Mvvm_技术指南与实战项目.Models
{
/// <summary>
/// 任务实体类(Model层)
/// 继承ObservableObject,实现属性变更通知
/// </summary>
public class TaskItem : ObservableObject
{
// 私有字段
private int _id; // 任务唯一标识
private string _title; // 任务标题
private string _description; // 任务描述
private DateTime _dueDate; // 截止日期
private bool _isCompleted; // 完成状态
/// <summary>
/// 任务ID(唯一标识)
/// </summary>
public int Id
{
get => _id; // 获取任务ID
set => SetProperty(ref _id, value); // 设置任务ID并通知变更
}
/// <summary>
/// 任务标题
/// </summary>
public string Title
{
get => _title; // 获取标题
set => SetProperty(ref _title, value); // 设置标题并通知变更
}
/// <summary>
/// 任务描述
/// </summary>
public string Description
{
get => _description; // 获取描述
set => SetProperty(ref _description, value); // 设置描述并通知变更
}
/// <summary>
/// 截止日期
/// </summary>
public DateTime DueDate
{
get => _dueDate; // 获取截止日期
set => SetProperty(ref _dueDate, value); // 设置截止日期并通知变更
}
/// <summary>
/// 任务完成状态
/// </summary>
public bool IsCompleted
{
get => _isCompleted; // 获取完成状态
set => SetProperty(ref _isCompleted, value); // 设置完成状态并通知变更
}
/// <summary>
/// 构造函数(简化对象创建)
/// </summary>
/// <param name="id">任务ID</param>
/// <param name="title">任务标题</param>
/// <param name="description">任务描述</param>
/// <param name="dueDate">截止日期</param>
/// <param name="isCompleted">是否已完成,默认false</param>
public TaskItem(int id, string title, string description, DateTime dueDate, bool isCompleted = false)
{
Id = id; // 初始化ID
Title = title; // 初始化标题
Description = description; // 初始化描述
DueDate = dueDate; // 初始化截止日期
IsCompleted = isCompleted; // 初始化完成状态
}
}
}
2.3.2 ViewModel 层:核心业务逻辑
(1)TaskViewModel.cs(任务列表业务)
处理任务的增、删、改逻辑,使用框架特性自动生成属性和命令,实现业务逻辑与 UI 解耦。
csharp
using _00024.WPF___Microsoft.ToolKit.Mvvm_技术指南与实战项目.Helpers;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
namespace _00024.WPF___Microsoft.ToolKit.Mvvm_技术指南与实战项目.Models
{
/// <summary>
/// 任务列表ViewModel(处理任务相关业务逻辑)
/// 继承ObservableObject,实现属性通知
/// </summary>
public partial class TaskViewModel : ObservableObject
{
public TaskViewModel()
{
// 注册删除消息
MessengerHelper.Register<DeleteTaskMessage>(this, OnDeleteTaskReceived);
}
// 接收删除消息 → 执行删除
private void OnDeleteTaskReceived(DeleteTaskMessage message)
{
ExecuteDeleteTask(message.TaskId);
}
// 真正执行删除
private void ExecuteDeleteTask(int taskId)
{
var taskToRemove = Tasks.FirstOrDefault(t => t.Id == taskId);
if (taskToRemove != null)
Tasks.Remove(taskToRemove);
}
// 任务集合(绑定到UI的ListView,自动刷新)
[ObservableProperty]
private ObservableCollection<TaskItem> _tasks = new();
// 新增任务表单属性(绑定到UI输入框)
[ObservableProperty]
private string _newTaskTitle = string.Empty;
[ObservableProperty]
private string _newTaskDescription = string.Empty;
[ObservableProperty]
private DateTime _newTaskDueDate = DateTime.Now.AddDays(1);
// 加载状态(用于异步加载时显示加载提示)
[ObservableProperty]
private bool _isLoading;
// 新增任务命令
[RelayCommand]
private void AddTask()
{
if (string.IsNullOrWhiteSpace(NewTaskTitle))
return;
var newTask = new TaskItem(
id: Tasks.Count + 1,
title: NewTaskTitle,
description: NewTaskDescription,
dueDate: NewTaskDueDate
);
Tasks.Add(newTask);
NewTaskTitle = string.Empty;
NewTaskDescription = string.Empty;
}
// 删除任务命令 → 只发消息
[RelayCommand]
private void DeleteTask(int taskId)
{
MessengerHelper.Send(new DeleteTaskMessage(taskId));
}
// 标记完成
[RelayCommand]
private void ToggleTaskCompleted(TaskItem task)
{
if (task != null)
task.IsCompleted = !task.IsCompleted;
}
// 异步加载
[RelayCommand(AllowConcurrentExecutions = false)]
private async Task LoadTasksAsync()
{
IsLoading = true;
try
{
await Task.Delay(1500);
var mockTasks = new List<TaskItem>
{
new TaskItem(1, "学习CommunityToolkit.Mvvm", "掌握属性通知、命令绑定核心用法", DateTime.Now.AddDays(2),true),
new TaskItem(2, "完成WPF实战项目", "实现任务管理系统所有功能", DateTime.Now.AddDays(5),false),
new TaskItem(3, "整理技术文档", "总结框架使用技巧和注意事项", DateTime.Now.AddDays(3),true)
};
Tasks.Clear();
foreach (var task in mockTasks)
{
Tasks.Add(task);
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"异步加载失败:{ex.Message}", "加载异常");
}
finally
{
IsLoading = false;
}
}
}
// 消息类(你也可以单独放文件)
public class DeleteTaskMessage
{
public int TaskId { get; set; }
public DeleteTaskMessage(int taskId) => TaskId = taskId;
}
}
(2)MainViewModel.cs(主窗口业务)
主窗口 ViewModel,嵌入任务列表 ViewModel,管理主窗口全局状态(如窗口标题),实现 ViewModel 拆分复用。
csharp
using _00024.WPF___Microsoft.ToolKit.Mvvm_技术指南与实战项目.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace _00024.WPF___Microsoft.ToolKit.Mvvm_技术指南与实战项目.ViewModels
{
/// <summary>
/// 主窗口ViewModel(全局状态管理)
/// </summary>
public partial class MainViewModel : ObservableObject
{
// 主窗口标题(绑定到UI标题栏)
[ObservableProperty]
private string _appTitle = "WPF + CommunityToolkit.Mvvm 任务管理系统";
// 嵌入任务列表ViewModel(实现业务逻辑拆分)
public TaskViewModel TaskVm { get; } = new();
// 退出应用命令
[RelayCommand]
private void ExitApp()
{
System.Windows.Application.Current.Shutdown();
}
}
}
2.3.3 View 层:XAML 界面(纯UI,无业务逻辑)
(1)MainWindow.xaml(主窗口)
纯 XAML 布局,修复语法错误,通过 DataContext 绑定 ViewModel,实现 UI 与业务逻辑的完全解耦,所有交互通过命令绑定实现。
xml
<Window.Resources>
<helper:BooleanToVisibilityConverter x:Key="InvertBool" Invert="True"/>
</Window.Resources>
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 标题 -->
<TextBlock Text="{Binding AppTitle}" FontSize="26" FontWeight="Bold" Margin="0 0 0 15"/>
<!-- 异步加载任务按钮 -->
<Button Grid.Row="1" Content="异步加载任务数据"
Command="{Binding TaskVm.LoadTasksCommand}"
Visibility="{Binding TaskVm.IsLoading, Converter={StaticResource InvertBool}}"
Background="#2196F3" Foreground="White"
Padding="10" Margin="0 0 0 15"/>
<!-- 新增任务区域 -->
<GroupBox Grid.Row="2" Header="新增任务" Margin="0 0 0 15" Padding="10">
<StackPanel>
<TextBox Text="{Binding TaskVm.NewTaskTitle, UpdateSourceTrigger=PropertyChanged}"
Height="32" Margin="0 0 0 8"/>
<TextBox Text="{Binding TaskVm.NewTaskDescription, UpdateSourceTrigger=PropertyChanged}"
Height="32" Margin="0 0 0 8"/>
<DatePicker SelectedDate="{Binding TaskVm.NewTaskDueDate}" Height="32" Margin="0 0 0 8"/>
<Button Content="添加任务" Command="{Binding TaskVm.AddTaskCommand}"
Background="#4CAF50" Foreground="White" Height="34"/>
</StackPanel>
</GroupBox>
<!-- 任务列表 -->
<ListView Grid.Row="3" ItemsSource="{Binding TaskVm.Tasks}" Margin="0 10 0 0">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding Id}" Width="60"/>
<GridViewColumn Header="标题" DisplayMemberBinding="{Binding Title}" Width="200"/>
<GridViewColumn Header="描述" DisplayMemberBinding="{Binding Description}" Width="280"/>
<GridViewColumn Header="截止日期" DisplayMemberBinding="{Binding DueDate, StringFormat=yyyy-MM-dd}" Width="120"/>
<GridViewColumn Header="完成" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsCompleted, Mode=TwoWay}" HorizontalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="操作" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="删除" Background="#F44336" Foreground="White"
Command="{Binding DataContext.TaskVm.DeleteTaskCommand, RelativeSource={RelativeSource AncestorType=ListView}}"
CommandParameter="{Binding Id}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
(2)BooleanToVisibilityConverter.cs(辅助转换器)
补充缺失的转换器,解决按钮禁用状态绑定问题,放在 Helpers 文件夹下。
csharp
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace _00024.WPF___Microsoft.ToolKit.Mvvm_技术指南与实战项目.Helpers
{
/// <summary>
/// 布尔值转可见性转换器
/// </summary>
public class BooleanToVisibilityConverter : IValueConverter
{
/// <summary>
/// 是否反转布尔值判断
/// </summary>
public bool Invert { get; set; }
/// <summary>
/// 正向转换:bool 转为 Visibility
/// </summary>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool boolValue = (bool)value;
// 开启反转则取反
if (Invert) boolValue = !boolValue;
// true 显示,false 折叠
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
/// <summary>
/// 反向转换 暂不实现
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
(3)MainWindow.xaml.cs(后台代码)
仅包含初始化代码,无任何业务逻辑,符合 MVVM 规范。
csharp
using System.Windows;
namespace _00024.WPF___Microsoft.ToolKit.Mvvm_技术指南与实战项目.Views
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
2.3.4 辅助类:MessengerHelper.cs(消息通信封装)
修正命名空间与项目一致,优化代码逻辑,封装WeakReferenceMessenger,简化消息发送、接收、注销操作,避免重复代码,防止内存泄漏,重点修复泛型约束错误(添加引用类型约束 where TMessage : class)。
csharp
using CommunityToolkit.Mvvm.Messaging;
namespace _00024.WPF___Microsoft.ToolKit.Mvvm_技术指南与实战项目.Helpers
{
/// <summary>
/// 消息通信辅助类
/// 用于ViewModel和页面解耦通信,防止内存泄漏
/// </summary>
public static class MessengerHelper
{
// 默认消息标识(代替null,避免框架报错)
private const string DefaultToken = "";
/// <summary>
/// 注册全局消息(无Token)
/// </summary>
/// <typeparam name="TMessage">消息类型</typeparam>
/// <param name="recipient">消息接收者</param>
/// <param name="action">收到消息后的处理方法</param>
public static void Register<TMessage>(object recipient, Action<TMessage> action) where TMessage : class
{
if (recipient == null)
throw new ArgumentNullException(nameof(recipient), "接收者不能为null");
if (action == null)
throw new ArgumentNullException(nameof(action), "消息处理逻辑不能为null");
WeakReferenceMessenger.Default.Register<object, TMessage, string>(
recipient,
token: DefaultToken,
handler: (_, message) => action(message));
}
/// <summary>
/// 注册带Token的消息(分组隔离)
/// </summary>
public static void Register<TMessage>(object recipient, string token, Action<TMessage> action) where TMessage : class
{
if (recipient == null)
throw new ArgumentNullException(nameof(recipient), "接收者不能为null");
if (string.IsNullOrWhiteSpace(token))
throw new ArgumentException("消息标识Token不能为空", nameof(token));
if (action == null)
throw new ArgumentNullException(nameof(action), "消息处理逻辑不能为null");
WeakReferenceMessenger.Default.Register<object, TMessage, string>(
recipient,
token,
handler: (_, message) => action(message));
}
/// <summary>
/// 发送全局消息
/// </summary>
public static void Send<TMessage>(TMessage message) where TMessage : class
{
if (message == null)
throw new ArgumentNullException(nameof(message), "消息不能为null");
WeakReferenceMessenger.Default.Send<TMessage, string>(message, token: DefaultToken);
}
/// <summary>
/// 发送带Token的消息
/// </summary>
public static void Send<TMessage>(TMessage message, string token) where TMessage : class
{
if (message == null)
throw new ArgumentNullException(nameof(message), "消息不能为null");
if (string.IsNullOrWhiteSpace(token))
throw new ArgumentException("消息标识Token不能为空", nameof(token));
WeakReferenceMessenger.Default.Send<TMessage, string>(message, token);
}
/// <summary>
/// 注销接收者的所有消息订阅
/// </summary>
public static void UnregisterAll(object recipient)
{
if (recipient == null)
throw new ArgumentNullException(nameof(recipient), "接收者不能为null");
WeakReferenceMessenger.Default.UnregisterAll(recipient);
}
/// <summary>
/// 注销指定类型的全局消息
/// </summary>
public static void Unregister<TMessage>(object recipient) where TMessage : class
{
if (recipient == null)
throw new ArgumentNullException(nameof(recipient), "接收者不能为null");
WeakReferenceMessenger.Default.Unregister<TMessage, string>(recipient, token: DefaultToken);
}
}
}
2.4 项目运行与功能说明
2.4.1 运行步骤
-
创建 WPF 项目(.NET 8),安装
CommunityToolkit.Mvvm 8.4.2包; -
按照上述项目结构,创建 Models、ViewModels、Views、Helpers 文件夹及对应文件;
-
复制上述代码到对应文件,确保命名空间与项目一致;
-
设置 MainWindow.xaml 为启动窗口,运行项目。
2.4.2 核心功能演示
-
异步加载任务:点击「加载任务数据」按钮,触发异步命令,加载过程中按钮禁用,加载完成后任务列表显示模拟数据;
-
新增任务:输入任务标题、描述、截止日期,点击「添加任务」,任务列表实时更新,表单自动清空;
-
删除任务:点击任务行的「删除」按钮,删除对应任务,列表自动刷新;
-
标记任务完成:勾选任务行的复选框,切换任务完成状态,UI 实时同步;
-
数据绑定:所有 UI 元素与 ViewModel 属性双向绑定,属性变化时 UI 自动刷新,无需手动操作。
第三部分:高级特性与最佳实践
3.1 高级特性用法
3.1.1 关联属性通知(NotifyPropertyChangedFor)
当一个属性的变化依赖另一个属性时,使用 [NotifyPropertyChangedFor] 特性,自动触发关联属性的变更通知。
csharp
public partial class UserViewModel : ObservableObject
{
[ObservableProperty]
private string _firstName;
[ObservableProperty]
private string _lastName;
// 当FirstName或LastName变化时,自动触发FullName的变更通知
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FirstName))]
[NotifyPropertyChangedFor(nameof(LastName))]
private string _fullName;
// 重写FullName的set方法,实现拼接逻辑
partial void OnFullNameChanged(string value)
{
FullName = $"{FirstName} {LastName}";
}
}
3.1.2 命令参数传递与多参数支持
支持传递单个参数、多个参数(通过匿名对象或实体类),满足复杂交互场景。
csharp
// 带多个参数的命令
[RelayCommand]
private void EditTask(int id, string title)
{
// 编辑逻辑
}
// XAML绑定(传递多个参数)
<Button Content="编辑">
<Button.CommandParameter>
<Tuple Int32="{Binding Id}" String="{Binding Title}"/>
</Button.CommandParameter>
</Button>
3.1.3 数据验证进阶(自定义验证规则)
除了数据注解,还可自定义验证规则,实现复杂业务场景的验证需求。
csharp
public partial class TaskViewModel : ObservableValidator
{
[ObservableProperty]
[Required(ErrorMessage = "截止日期不能为空")]
private DateTime _dueDate;
// 自定义验证规则:截止日期不能小于当前日期
partial void OnDueDateChanged(DateTime value)
{
if (value < DateTime.Now.Date)
{
AddValidationError(nameof(DueDate), "截止日期不能小于当前日期");
}
else
{
ClearValidationError(nameof(DueDate));
}
}
}
3.2 最佳实践
-
ViewModel 拆分复用:将复杂业务拆分为多个 ViewModel(如本项目的 MainViewModel 和 TaskViewModel),提升代码复用性和可维护性;
-
优先使用特性生成 :尽量使用
[ObservableProperty]、[RelayCommand]等特性,减少手写样板代码,避免人为错误; -
异步操作必用 AsyncRelayCommand :所有异步操作(如 API 请求、数据库查询)必须使用
AsyncRelayCommand,避免 UI 阻塞; -
及时注销消息监听:在页面关闭或 ViewModel 销毁时,注销所有消息监听,防止内存泄漏;
-
避免 ViewModel 依赖 View:ViewModel 中不能直接引用 View(如实例化 Window、操作控件),需通过命令、消息实现通信;
-
集合使用 ObservableCollection :绑定到 UI 列表的集合必须使用
ObservableCollection<T>,确保集合变更时 UI 自动刷新; -
统一包版本:确保项目中 CommunityToolkit.Mvvm 版本统一(推荐 8.4.2),避免版本冲突。
3.3 常见问题与解决方案
| 常见问题 | 解决方案 |
|---|---|
| 属性通知不生效 | 1. 确保 ViewModel 是 partial 类;2. 确保属性添加了 [ObservableProperty] 特性;3. 检查 DataContext 绑定是否正确 |
| 命令绑定后按钮不可用 | 1. 检查 CanExecute 条件是否满足;2. 手动调用命令的 NotifyCanExecuteChanged 方法刷新状态;3. 检查命令绑定路径是否正确 |
| 异步命令执行后 UI 不刷新 | 确保异步方法使用 await 关键字,且属性变更在主线程执行(框架自动处理,无需手动切换线程) |
| 内存泄漏 | 1. 及时注销消息监听;2. 避免强引用持有 ViewModel;3. 使用 WeakReferenceMessenger 而非自定义事件 |
第四部分:总结与扩展学习
4.1 核心要点回顾
-
框架更名 :
Microsoft.ToolKit.Mvvm已正式更名为CommunityToolkit.Mvvm,旧包废弃,新项目需使用新包; -
核心组件 :
ObservableObject(属性通知)、RelayCommand/AsyncRelayCommand(命令绑定)、ObservableValidator(数据验证)、WeakReferenceMessenger(消息通信); -
核心优势:编译时生成、无反射、异步友好、轻量级无依赖,是 WPF 新项目 MVVM 首选框架;
-
实战核心:严格遵循 MVVM 三层架构,View 纯 UI、ViewModel 纯逻辑、Model 纯数据,实现完全解耦。
4.2 扩展学习方向
-
依赖注入:集成 Microsoft.Extensions.DependencyInjection,实现 ViewModel、服务(如 API 调用、数据库访问)的依赖注入,提升项目可测试性;
-
数据持久化:结合 SQLite、Entity Framework Core,实现任务数据的本地存储,完善项目功能;
-
UI 美化:集成 HandyControl、MaterialDesignInXamlToolkit 等 UI 框架,打造现代化 WPF 界面;
-
多页面导航:结合 WPF 的 Frame、ContentControl 或 Prism 框架,实现多页面应用的导航管理;
-
单元测试:针对 ViewModel 编写单元测试,验证业务逻辑的正确性,提升项目稳定性。
4.3 资源推荐
-
NuGet 地址:CommunityToolkit.Mvvm;
通过本文的学习,你已掌握 Microsoft.ToolKit.Mvvm(CommunityToolkit.Mvvm)的核心用法和 WPF MVVM 开发的最佳实践,可快速上手开发中小型 WPF 项目。建议结合实战项目多动手练习,深入理解框架特性,提升开发效率和代码质量。
👋 关注我!持续分享 C# 实战技巧、代码示例 & 技术干货
- 获取示例代码,轻松上手!