WPF + Microsoft.ToolKit.Mvvm 技术指南与实战项目

第一部分: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 接口,无需手动实现 CanExecuteExecute 方法,可直接绑定到 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),三种安装方式任选其一:

  1. NuGet 包管理器(推荐) :右键 WPF 项目 → 管理 NuGet 包 → 搜索 CommunityToolkit.Mvvm → 安装最新稳定版(8.4.2)

  2. .NET CLI 命令行 :打开终端,进入项目目录,执行命令: dotnet add package CommunityToolkit.Mvvm --version 8.4.2

  3. 项目文件直接引用 :编辑 .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 运行步骤
  1. 创建 WPF 项目(.NET 8),安装 CommunityToolkit.Mvvm 8.4.2 包;

  2. 按照上述项目结构,创建 Models、ViewModels、Views、Helpers 文件夹及对应文件;

  3. 复制上述代码到对应文件,确保命名空间与项目一致;

  4. 设置 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 扩展学习方向
  1. 依赖注入:集成 Microsoft.Extensions.DependencyInjection,实现 ViewModel、服务(如 API 调用、数据库访问)的依赖注入,提升项目可测试性;

  2. 数据持久化:结合 SQLite、Entity Framework Core,实现任务数据的本地存储,完善项目功能;

  3. UI 美化:集成 HandyControl、MaterialDesignInXamlToolkit 等 UI 框架,打造现代化 WPF 界面;

  4. 多页面导航:结合 WPF 的 Frame、ContentControl 或 Prism 框架,实现多页面应用的导航管理;

  5. 单元测试:针对 ViewModel 编写单元测试,验证业务逻辑的正确性,提升项目稳定性。

4.3 资源推荐

通过本文的学习,你已掌握 Microsoft.ToolKit.Mvvm(CommunityToolkit.Mvvm)的核心用法和 WPF MVVM 开发的最佳实践,可快速上手开发中小型 WPF 项目。建议结合实战项目多动手练习,深入理解框架特性,提升开发效率和代码质量。


👋 关注我!持续分享 C# 实战技巧、代码示例 & 技术干货

  • 获取示例代码,轻松上手!
相关推荐
Smart-佀9 小时前
涨薪秘技:智能家居中的BLE协议与实现
网络·arm开发·嵌入式硬件·microsoft
Y敲键盘的地方21 小时前
第3章 对话即编程
人工智能·microsoft
薛定猫AI1 天前
【深度解析】Claude Code 本地代理架构:用 Free Cloud Code 降低 Agentic Coding 成本
microsoft·架构
武藤一雄1 天前
WPF中逻辑树(Logical Tree)与可视化树(Visual Tree)到底是什么
microsoft·c#·.net·wpf·.netcore
炸炸鱼.1 天前
ELK 企业级日志分析系统完整部署手册
elk·wpf
love530love2 天前
Clink 在 VS 2022 Developer Command Prompt 中的配置与路径精简调校
人工智能·windows·microsoft·clink
Mr_pyx2 天前
微服务可观测性实战:分布式链路追踪从入门到精通
wpf
m0_466525292 天前
东软智行与腾讯云达成战略合作 共筑AI智能座舱新生态
人工智能·microsoft·腾讯云
kuuailetianzi2 天前
Vue 3图片全屏预览组件:打造专业级图像浏览体验
前端·javascript·microsoft