第一部分:MvvmLight 框架深度解析
1.1 框架定位与现状
MvvmLight 框架介绍
MvvmLight 是由 Laurent Bugnion 于 2009 年创建的开源 MVVM 框架,曾是 .NET 平台最主流的 MVVM 解决方案之一。
核心特性
| 特性 | 说明 |
|---|---|
| 轻量级 | 核心库仅约 200KB,采用无侵入式设计,对项目架构无强依赖 |
| 易用性 | 内置 ViewModelBase、RelayCommand 核心组件,大幅简化 MVVM 模式的开发成本 |
| 实用性 | 集成 Messenger 消息机制,完美解决 ViewModel 之间的通信问题 |
⚠️ 重要维护提示
- 维护状态:已进入
遗留维护模式,最后一个主要版本发布于 2018 年 - 兼容性:基于 .NET Standard 2.0 构建,可兼容 .NET Core、.NET 5+ 及后续版本
- 官方建议:微软官方推荐新项目优先使用
CommunityToolkit.Mvvm替代
1.2 核心架构原理
三层架构图解
关键技术原理
(1)属性通知机制(INotifyPropertyChanged)
MvvmLight 的 ViewModelBase 继承 ObservableObject(属性通知) + ICleanup(资源清理)方法:
/// <summary>
/// MVVM 模式中 ViewModel 类的基类
/// 继承 ObservableObject(属性通知) + ICleanup(资源清理)
/// </summary>
public abstract class ViewModelBase : ObservableObject, ICleanup
{
/// <summary>
/// 消息器实例(用于组件间消息通信)
/// </summary>
private IMessenger _messengerInstance;
/// <summary>
/// 判断当前是否处于设计模式(VS/Blend 设计界面中)
/// </summary>
public bool IsInDesignMode => IsInDesignModeStatic;
/// <summary>
/// 静态属性:全局判断是否处于设计模式
/// </summary>
public static bool IsInDesignModeStatic => DesignerLibrary.IsInDesignMode;
/// <summary>
/// 消息器实例属性
/// 如果未手动设置,则使用框架默认的全局消息器
/// </summary>
protected IMessenger MessengerInstance
{
get
{
return _messengerInstance ?? Messenger.Default;
}
set
{
_messengerInstance = value;
}
}
/// <summary>
/// 默认构造函数
/// </summary>
public ViewModelBase()
: this(null)
{
}
/// <summary>
/// 带消息器参数的构造函数
/// </summary>
/// <param name="messenger">自定义消息器实例</param>
public ViewModelBase(IMessenger messenger)
{
MessengerInstance = messenger;
}
/// <summary>
/// 资源清理方法(ICleanup 接口实现)
/// 作用:注销当前实例的所有消息注册,防止内存泄漏
/// </summary>
public virtual void Cleanup()
{
MessengerInstance.Unregister(this);
}
/// <summary>
/// 广播属性变化消息
/// 用于跨ViewModel/组件通知属性变更
/// </summary>
/// <typeparam name="T">属性类型</typeparam>
/// <param name="oldValue">旧值</param>
/// <param name="newValue">新值</param>
/// <param name="propertyName">属性名</param>
protected virtual void Broadcast<T>(T oldValue, T newValue, string propertyName)
{
// 创建属性变化消息
PropertyChangedMessage<T> message = new PropertyChangedMessage<T>(this, oldValue, newValue, propertyName);
// 通过消息器发送消息
MessengerInstance.Send(message);
}
/// <summary>
/// 触发属性变更通知 + 可选广播消息
/// [CallerMemberName]:自动获取调用的属性名
/// </summary>
/// <typeparam name="T">属性类型</typeparam>
/// <param name="propertyName">属性名</param>
/// <param name="oldValue">旧值</param>
/// <param name="newValue">新值</param>
/// <param name="broadcast">是否广播消息</param>
public virtual void RaisePropertyChanged<T>([CallerMemberName] string propertyName = null, T oldValue = default(T), T newValue = default(T), bool broadcast = false)
{
// 不允许空属性名
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentException("This method cannot be called with an empty string", "propertyName");
}
// 触发基础的属性变更通知(刷新UI)
RaisePropertyChanged(propertyName);
// 如果开启广播,则发送消息
if (broadcast)
{
Broadcast(oldValue, newValue, propertyName);
}
}
/// <summary>
/// 表达式树方式触发属性变更通知 + 可选广播消息
/// 强类型,避免字符串错误
/// </summary>
/// <typeparam name="T">属性类型</typeparam>
/// <param name="propertyExpression">属性表达式</param>
/// <param name="oldValue">旧值</param>
/// <param name="newValue">新值</param>
/// <param name="broadcast">是否广播消息</param>
public virtual void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression, T oldValue, T newValue, bool broadcast)
{
// 触发基础通知
RaisePropertyChanged(propertyExpression);
// 开启广播则发送消息
if (broadcast)
{
string propertyName = ObservableObject.GetPropertyName(propertyExpression);
Broadcast(oldValue, newValue, propertyName);
}
}
/// <summary>
/// 赋值 + 通知 + 消息广播(表达式树版本)
/// </summary>
/// <typeparam name="T">属性类型</typeparam>
/// <param name="propertyExpression">属性表达式</param>
/// <param name="field">后台字段</param>
/// <param name="newValue">新值</param>
/// <param name="broadcast">是否广播</param>
/// <returns>是否发生值变更</returns>
protected bool Set<T>(Expression<Func<T>> propertyExpression, ref T field, T newValue, bool broadcast)
{
// 值未变化,不执行操作
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return false;
}
T oldValue = field;
field = newValue;
// 触发通知 + 广播
RaisePropertyChanged(propertyExpression, oldValue, field, broadcast);
return true;
}
/// <summary>
/// 赋值 + 通知 + 消息广播(字符串属性名版本)
/// </summary>
/// <typeparam name="T">属性类型</typeparam>
/// <param name="propertyName">属性名</param>
/// <param name="field">后台字段</param>
/// <param name="newValue">新值</param>
/// <param name="broadcast">是否广播</param>
/// <returns>是否发生值变更</returns>
protected bool Set<T>(string propertyName, ref T field, T newValue = default(T), bool broadcast = false)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return false;
}
T oldValue = field;
field = newValue;
RaisePropertyChanged(propertyName, oldValue, field, broadcast);
return true;
}
/// <summary>
/// 最常用赋值方法:自动获取属性名 + 可选消息广播
/// 简化ViewModel属性编写
/// </summary>
/// <typeparam name="T">属性类型</typeparam>
/// <param name="field">后台字段</param>
/// <param name="newValue">新值</param>
/// <param name="broadcast">是否广播消息</param>
/// <param name="propertyName">属性名(自动获取)</param>
/// <returns>是否发生值变更</returns>
protected bool Set<T>(ref T field, T newValue = default(T), bool broadcast = false, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return false;
}
T oldValue = field;
field = newValue;
RaisePropertyChanged(propertyName, oldValue, field, broadcast);
return true;
}
}
ObservableObject:
// 可观察对象基类,实现 INotifyPropertyChanged 接口
// 作用:属性变化时通知UI自动刷新
public class ObservableObject : INotifyPropertyChanged
{
// 给子类提供 PropertyChanged 事件的访问入口
protected PropertyChangedEventHandler PropertyChangedHandler => this.PropertyChanged;
// 属性变更事件:属性值改变后触发
public event PropertyChangedEventHandler PropertyChanged;
// 验证属性名是否存在
// 仅在DEBUG模式生效,防止属性改名后报错
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// 获取当前类的类型信息
TypeInfo typeInfo = GetType().GetTypeInfo();
// 属性名为空 或 当前类直接包含该属性 → 直接返回
if (string.IsNullOrEmpty(propertyName) || (object)typeInfo.GetDeclaredProperty(propertyName) != null)
{
return;
}
// 递归检查父类是否包含该属性
bool flag = false;
while ((object)typeInfo.BaseType != typeof(object))
{
typeInfo = typeInfo.BaseType.GetTypeInfo();
if ((object)typeInfo.GetDeclaredProperty(propertyName) != null)
{
flag = true;
break;
}
}
// 整个继承链都找不到该属性 → 抛出异常
if (!flag)
{
throw new ArgumentException("Property not found", propertyName);
}
}
// 触发属性变更通知
// [CallerMemberName]:自动获取调用者的属性名
public virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
// 触发事件通知UI
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// 用表达式树的方式触发属性变更通知(强类型,无字符串)
public virtual void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
if (this.PropertyChanged != null)
{
// 从表达式中提取属性名
string propertyName = GetPropertyName(propertyExpression);
if (!string.IsNullOrEmpty(propertyName))
{
RaisePropertyChanged(propertyName);
}
}
}
// 从表达式树中提取属性名
// 输入:()=>this.Name 输出:"Name"
protected static string GetPropertyName<T>(Expression<Func<T>> propertyExpression)
{
// 表达式不能为空
if (propertyExpression == null)
{
throw new ArgumentNullException("propertyExpression");
}
// 把表达式解析为属性,并返回属性名
return ((((propertyExpression.Body as MemberExpression)
?? throw new ArgumentException("Invalid argument", "propertyExpression")).Member as PropertyInfo)
?? throw new ArgumentException("Argument is not a property", "propertyExpression")).Name;
}
// 赋值并触发通知(表达式树版本)
protected bool Set<T>(Expression<Func<T>> propertyExpression, ref T field, T newValue)
{
// 值没变化 → 不处理
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return false;
}
// 更新值
field = newValue;
// 触发通知
RaisePropertyChanged(propertyExpression);
return true;
}
// 赋值并触发通知(属性名字符串版本)
protected bool Set<T>(string propertyName, ref T field, T newValue)
{
// 值没变化 → 不处理
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return false;
}
// 更新值
field = newValue;
// 触发通知
RaisePropertyChanged(propertyName);
return true;
}
// 最常用赋值方法:自动获取属性名,简化调用
protected bool Set<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
return Set(propertyName, ref field, newValue);
}
}
(2)命令绑定原理(ICommand)
RelayCommand 实现了 ICommand 接口,将方法包装为命令对象:
/// <summary>
/// 中继命令:WPF命令绑定核心类
/// 作用:把按钮等控件的点击事件 绑定到ViewModel的方法
/// </summary>
public class RelayCommand : ICommand
{
// 要执行的方法(弱引用,防止内存泄漏)
private readonly WeakAction _execute;
// 判断命令是否可执行的条件(弱引用)
private readonly WeakFunc<bool> _canExecute;
// 命令可执行状态改变时触发的事件(界面按钮自动启用/禁用)
public event EventHandler CanExecuteChanged;
/// <summary>
/// 构造方法:只传执行方法,默认命令永远可执行
/// </summary>
public RelayCommand(Action execute, bool keepTargetAlive = false)
: this(execute, null, keepTargetAlive) { }
/// <summary>
/// 构造方法:传 执行方法 + 是否可执行方法
/// </summary>
public RelayCommand(Action execute, Func<bool> canExecute, bool keepTargetAlive = false)
{
// 执行方法不能为空,否则抛异常
if (execute == null) throw new ArgumentNullException("execute");
// 保存执行逻辑
_execute = new WeakAction(execute, keepTargetAlive);
// 保存可执行判断逻辑
if (canExecute != null)
_canExecute = new WeakFunc<bool>(canExecute, keepTargetAlive);
}
/// <summary>
/// 手动触发命令状态刷新(让界面重新判断按钮是否可用)
/// </summary>
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// 判断命令是否可以执行
/// </summary>
public bool CanExecute(object parameter)
{
// 如果有可执行判断条件,就执行判断
if (_canExecute != null)
{
if (_canExecute.IsStatic || _canExecute.IsAlive)
return _canExecute.Execute();
return false;
}
// 没有条件,默认永远可执行
return true;
}
/// <summary>
/// 执行命令(按钮点击时调用)
/// </summary>
public virtual void Execute(object parameter)
{
// 可执行 + 方法有效 → 调用绑定的方法
if (CanExecute(parameter) && _execute != null && (_execute.IsStatic || _execute.IsAlive))
_execute.Execute();
}
}
(3)消息通信机制(Messenger)
基于弱引用(WeakReference)的事件总线,避免内存泄漏:
public static class Messenger
{
// 注册表结构:Dictionary<Type, List<WeakActionAndToken>>
public static void Register<TMessage>(object recipient, Action<TMessage> action, object token = null)
{
// 使用 WeakReference 包装 recipient,避免强引用导致内存泄漏
var weakReference = new WeakReference(recipient);
// 存储到注册表...
}
public static void Send<TMessage>(TMessage message, object token = null)
{
// 1. 根据 TMessage 类型查找注册表
// 2. 遍历所有弱引用
// 3. 检查引用目标是否存活(IsAlive)
// 4. 调用 action(message)
}
// 发送消息给特定接收者
public static void Send<TMessage, TTarget>(TMessage message, TTarget target, object token = null)
}
1.3 主流 MVVM 框架深度对比(精简优化版)
| 核心维度 | MvvmLight | CommunityToolkit.Mvvm | Prism |
|---|---|---|---|
| 维护状态 | ⚠️ 停止维护 2018年后基本停止更新,仅做兼容修复 | ✅ 微软官方 持续活跃维护,官方生态一部分 | ✅ 社区成熟 企业级稳定维护,迭代频繁 |
| 属性通知 | 运行时反射 手写 Set() 方法 | ✅ 编译时源生成[ObservableProperty] 特性,零手写AOT 友好,性能极强 | 运行时反射 手写 SetProperty() |
| 异步命令 | ❌ 不支持 需手动封装 ICommand | ✅ 原生支持[AsyncRelayCommand] 完美支持异步命令 | ✅ 原生支持 内置完善的异步命令机制 |
| 代码体量 | 中等 需手动编写大量属性与通知逻辑 | ✅ 极简 特性驱动,自动生成代码,开发效率最高 | 较全 框架体积与代码量相对较大 |
| 学习曲线 | 平缓 入门简单,API 直观 | 平缓 上手极快,官方文档完善 | 陡峭 概念多,需学习导航、DI 等架构模式 |
| 核心优势 | 轻量、历史用户基数大 | 现代、高效、性能最优 官方主推,未来主流 | 全能、企业级 功能完备,适合大型复杂系统 |
| 适用场景 | ⚠️ 仅维护 适合维护老旧遗留项目,不适合新项目 | ✅ 新项目首选 WPF/WinUI/MAUI 等现代应用开发 | ✅ 大型企业应用 中大型项目,需要完备架构体系 |
💡 一句话选型总结
- MvvmLight:已淘汰。只适合维护老项目,新项目绝对不要用。
- CommunityToolkit.Mvvm:全能首选。代码最少、性能最好、官方支持,是目前 WPF/WinUI 开发的最优解。
- Prism:企业重器。功能最全,但学习成本高,适合超大型团队和复杂项目。
第二部分:完整实战项目
基于以上原理,创建一个增强版学生管理系统,包含完整的 CRUD、验证、状态提示、对话框和异步操作。
2.1 项目结构
00023.WPF + MvvmLight 完整技术指南与实战项目/
├── Helpers/ // 消息通信辅助类
│ └── MessengerHelper.cs // MVVM Light 消息发送与接收
├── Model/ // 数据模型
│ └── Student.cs // 学生实体类
├── View/ // 界面视图
│ ├── EditStudentWindow.xaml // 编辑学生窗口
│ │ └── EditStudentWindow.xaml.cs
│ └── MainWindow.xaml // 主窗口
│ └── MainWindow.xaml.cs
├── ViewModel/ // 视图模型(业务逻辑)
│ └── MainViewModel.cs // 主窗口业务逻辑、命令、数据
├── App.xaml / App.xaml.cs // 程序入口
├── AssemblyInfo.cs // 程序集信息
└── ViewModelLocator.cs // ViewModel 定位器
2.2 Model/Student.cs(实体层)
using GalaSoft.MvvmLight;
namespace _00023.WPF___MvvmLight_完整技术指南与实战项目.Model
{
/// <summary>
/// 学生实体模型(继承MVVM Light通知基类)
/// </summary>
public class Student : ObservableObject
{
// 私有字段
private int _id;
private string _name =string.Empty;
private int _age;
/// <summary>
/// 学生ID
/// </summary>
public int Id
{
get => _id;
// Set方法自动触发属性变更通知,实现UI自动更新
set => Set(ref _id, value);
}
/// <summary>
/// 学生姓名
/// </summary>
public string Name
{
get => _name;
set => Set(ref _name, value);
}
/// <summary>
/// 学生年龄
/// </summary>
public int Age
{
get => _age;
set => Set(ref _age, value);
}
}
}
2.3 ViewModel/MainViewModel.cs(核心业务)
using _00023.WPF___MvvmLight_完整技术指南与实战项目.Helpers;
using _00023.WPF___MvvmLight_完整技术指南与实战项目.Model;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace _00023.WPF___MvvmLight_完整技术指南与实战项目.ViewModel
{
/// <summary>
/// 主窗口ViewModel,处理学生列表的增删改
/// </summary>
public class MainViewModel : ViewModelBase
{
/// <summary>
/// 学生列表(绑定到界面,自动刷新)
/// </summary>
public ObservableCollection<Student> Students { get; set; }
/// <summary>
/// 当前选中的学生
/// </summary>
private Student _selectedStudent;
public Student SelectedStudent
{
get => _selectedStudent;
set
{
Set(ref _selectedStudent, value);
// 👇 加这一行!通知命令刷新可用状态
((RelayCommand)EditCommand).RaiseCanExecuteChanged();
}
}
/// <summary>
/// 界面输入的姓名
/// </summary>
private string _inputName;
public string InputName
{
get => _inputName;
set => Set(ref _inputName, value);
}
/// <summary>
/// 界面输入的年龄
/// </summary>
private int _inputAge;
public int InputAge
{
get => _inputAge;
set => Set(ref _inputAge, value);
}
// 三个按钮命令:添加、删除、编辑
public ICommand AddCommand { get; }
public ICommand DeleteCommand { get; }
public ICommand EditCommand { get; }
/// <summary>
/// 构造函数:初始化列表和命令
/// </summary>
public MainViewModel()
{
Students = new ObservableCollection<Student>();
AddCommand = new RelayCommand(ExecuteAdd);
DeleteCommand = new RelayCommand(ExecuteDelete);
EditCommand = new RelayCommand(ExecuteEdit, CanExecuteEdit);
}
/// <summary>
/// 执行添加学生
/// </summary>
private void ExecuteAdd()
{
// 输入校验:姓名不能为空,年龄必须大于0
if (string.IsNullOrWhiteSpace(InputName) || InputAge <= 0)
return;
// 创建新学生对象
var student = new Student
{
Id = Students.Count + 1,
Name = InputName,
Age = InputAge
};
// 添加到列表
Students.Add(student);
// 清空输入框
InputName = string.Empty;
InputAge = 0;
}
/// <summary>
/// 执行删除选中学生
/// </summary>
private void ExecuteDelete()
{
if (SelectedStudent != null)
Students.Remove(SelectedStudent);
}
/// <summary>
/// 编辑按钮是否可用:必须选中学生才可用
/// </summary>
private bool CanExecuteEdit()
{
return SelectedStudent != null;
}
/// <summary>
/// 执行编辑:发送消息给编辑窗口
/// </summary>
private void ExecuteEdit()
{
MessengerHelper.Send(SelectedStudent, "EditStudent");
}
/// <summary>
/// 清理资源:注销消息
/// </summary>
public override void Cleanup()
{
MessengerHelper.Unregister(this);
base.Cleanup();
}
}
}
2.4 ViewModelLocator.cs(MvvmLight 依赖注入)
using CommonServiceLocator;
using GalaSoft.MvvmLight.Ioc;
using _00023.WPF___MvvmLight_完整技术指南与实战项目.ViewModel;
namespace _00023.WPF___MvvmLight_完整技术指南与实战项目
{
/// <summary>
/// 视图模型定位器:统一管理和提供ViewModel实例
/// </summary>
public class ViewModelLocator
{
/// <summary>
/// 对外提供 MainViewModel 实例(界面绑定用)
/// </summary>
public MainViewModel MainViewModel => SimpleIoc.Default.GetInstance<MainViewModel>();
/// <summary>
/// 构造函数:注册服务、配置Ioc容器
/// </summary>
public ViewModelLocator()
{
// 设置Ioc服务定位器
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
// 注册 MainViewModel 到Ioc容器
SimpleIoc.Default.Register<MainViewModel>();
}
}
}
2.5 View/MainWindow.xaml(主界面)
<Window x:Class="_00023.WPF___MvvmLight_完整技术指南与实战项目.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:conv="clr-namespace:_00023.WPF___MvvmLight_完整技术指南与实战项目.Converters"
mc:Ignorable="d"
DataContext="{Binding MainViewModel, Source={StaticResource Locator}}"
Title="学生管理系统"
Height="520"
Width="850"
WindowStartupLocation="CenterScreen"
FontSize="14">
<Window.Resources>
<conv:NullToBoolConverter x:Key="NullToBoolConverter"/>
<conv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<conv:BoolInverter x:Key="BoolInverter"/>
</Window.Resources>
<Border Padding="25">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="15"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center">
<TextBox Text="{Binding InputName}" Width="160" Height="32" Padding="6" Margin="0 0 8 0"/>
<TextBox Text="{Binding InputAge}" Width="120" Height="32" Padding="6" Margin="0 0 8 0"/>
<Button Content="添加" Command="{Binding AddCommand}" Width="100" Height="32"/>
<Button Content="编辑" Command="{Binding EditCommand}" Width="100" Height="32" Margin="8 0 0 0"
IsEnabled="{Binding SelectedStudent, Converter={StaticResource NullToBoolConverter}}"/>
<Button Content="删除" Command="{Binding DeleteCommand}" Width="100" Height="32" Margin="8 0 0 0"
IsEnabled="{Binding SelectedStudent, Converter={StaticResource NullToBoolConverter}}"/>
</StackPanel>
<DataGrid Grid.Row="2"
ItemsSource="{Binding Students}"
SelectedItem="{Binding SelectedStudent}"
AutoGenerateColumns="True"
IsReadOnly="True"
HorizontalAlignment="Center"
Width="780"
Height="380"/>
</Grid>
</Border>
</Window>
2.6 MessengerHelper(消息管理最佳实践)
using GalaSoft.MvvmLight.Messaging;
using System;
namespace _00023.WPF___MvvmLight_完整技术指南与实战项目.Helpers
{
/// <summary>
/// 消息助手类(封装 MVVM Light 消息器)
/// 作用:ViewModel 之间、页面之间 发送/接收消息,解耦通信
/// </summary>
public static class MessengerHelper
{
/// <summary>
/// 注册消息监听
/// </summary>
/// <param name="recipient">消息接收者</param>
/// <param name="token">消息标识(区分不同消息)</param>
/// <param name="action">收到消息后执行的方法</param>
public static void Register<T>(object recipient, string token, Action<T> action)
{
Messenger.Default.Register(recipient, token, action);
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="message">要发送的内容</param>
/// <param name="token">消息标识</param>
public static void Send<T>(T message, string token)
{
Messenger.Default.Send(message, token);
}
/// <summary>
/// 取消注册(释放监听)
/// </summary>
/// <param name="recipient">接收者对象</param>
public static void Unregister(object recipient)
{
Messenger.Default.Unregister(recipient);
}
}
}
第三部分:功能清单与运行效果
功能 实现方式 技术亮点
✅ 添加学生 输入验证 → 创建对象 → 加入 ObservableCollection 自动 UI 刷新,验证错误提示
✅ 删除学生 选中确认对话框 → 弱引用消息通知 防止误删,操作反馈
✅ 编辑学生 Messenger 发送消息 → 对话框显示 ViewModel 间解耦通信
有点简陋,但逻辑走通
第四部分:核心知识点总结
4.1 必须掌握的三核心
4.2 关键设计模式
属性变更通知链
// Model 继承 ObservableObject → 属性 Set() → UI 自动更新
public class Student : ObservableObject // 在 Model 层就具备通知能力
{
public string Name
{
get => _name;
set => Set(nameof(Name), ref _name, value); // 自动通知 UI
}
}
命令与逻辑分离
// ViewModel 只通过 Command 暴露操作,不直接操作 UI
public ICommand AddCommand { get; } // 绑定到 XAML
// 业务逻辑完全在 ViewModel 中处理
消息驱动对话框(解耦)
// 避免在 ViewModel 中直接引用 View(如直接实例化 Window)
Messenger.Default.Send(SelectedStudent, "EditStudent"); // 发送消息
// EditStudentViewModel 接收消息并显示对话框
建议:
- 维护旧项目:继续使用 MvvmLight(稳定可靠)
- 新项目启动:直接学习 CommunityToolkit.Mvvm(微软官方推荐,性能更好,支持 AOT)
👋 关注我!持续分享 C# 实战技巧、代码示例 & 技术干货
-
获取示例代码,轻松上手!
-
私信输入数字: k42v
-
博主网站:tool.bugcome.com