WPF + MvvmLight 完整技术指南与实战项目

MVVM介绍 MVVM 和 MVVMLight介绍 MVVM是Model-View-ViewModel的简写。类似于目前比较流行的MVC、MVP设计模式,主要目的是 为了分离视图(View)和模型(Model)的耦合。 它是一种极度优秀的设计模式,但并非框架级别的东西,由MVP(Model-View-Presenter)模式与 WPF结合的应用方式时发展演变过来的一种新型架构。 立足于原有MVP框架并且把WPF的新特性糅合进去,以应对PC端开发日益复杂的需求变化。 结构如图所示: 相对于之前把逻辑结构写在. 阅读详情

第一部分: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 核心架构原理
三层架构图解

image

关键技术原理

(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 框架深度对比(精简优化版)
核心维度MvvmLightCommunityToolkit.MvvmPrism
维护状态⚠️ 停止维护
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 间解耦通信
image
有点简陋,但逻辑走通


第四部分:核心知识点总结

4.1 必须掌握的三核心

image

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# 实战技巧、代码示例 & 技术干货

  • 获取示例代码,轻松上手!
WPF MvvmLight RelayCommand 绑定Command 的使用 RelayCommand Mvvm最大的特点就是分离了View和ViewModel,将数据的显示和业务逻辑分开。使用WPF的Binding,我们不仅能够将数据从ViewModel绑定到View,同时也可以将行为绑定到View。例如,在主界面上点击一个按钮,这个按钮实际完成的操作是ViewModel中对应的方法。这里我们用到Mvvm框架中的RelayCommand。下面是几种常用的情况 不带参数... 阅读详情

相关推荐

MvvmLight mvvm 消息订阅与发布 详解

MVVM Light框架中的消息订阅与发布机制是其实现View与ViewModel解耦的核心功能,主要通过‌‌类实现跨组件通信。

StevenChen的博客 481

WPF MVVMLight1:MVVMLight介绍以及在项目中的使用

MVVMLight介绍以及在项目中的使用

Guarder_7的博客 3485

基于MVVMLight的WPF页面跳转实现指南

MVVM(Model-View-ViewModel)是一种专为WPF、UWP等XAML平台设计的软件架构模式,旨在实现视图(View)与业务逻辑(ViewModel)的解耦。其核心思想是通过数据绑定和命令机制,将UI逻辑从业务逻辑中剥离,提高代码的可维护性与可测试性。MVVMLight 是一个轻量级的 MVVM 框架,广泛应用于中小型WPF项目中,它提供了如 ViewModelLocator、Messenger、RelayCommand 等关键组件,有效简化了MVVM模式的实现难度。

weixin_42186387的博客 800

20WPF---MVVMLight框架

轻量(相对于Prism),专注(仅负责MVVM架构模式的实现,没有其他多余的组件),高效(使用简易,省时省力).MainWindow中弹出子窗体,但在子窗体绑定的数据模型中开启一个线程,该线程不会随着子窗体的关闭而关闭。在ViewModelLocator类中做统一的对象释放。4.MVVMLight框架常用对象。5.8如何拿到子窗体的打开关闭状态。5.7Messenger全局消息。5.2 ICleanup清除内存。若传递EventArgs参数。5.6 ICommand命令。在子窗体关闭事件中调用。

qq_58563355的博客 2877

MVVMLight全面应用实例项目实战

职责分离是软件工程中一项基本原则,而在MVVM模式下,这一原则被具象化为三个明确的层级分工。每个层级都有其不可替代的功能定位,且彼此之间通过松散耦合的方式进行通信,避免了传统代码后置文件(Code-behind)中常见的“面条式”逻辑堆积问题。// 使用MVVMLight的Set方法自动通知代码解释:- 继承(MVVMLight 提供)简化通知。自动比较并触发。优势:减少样板代码,提高开发效率。要点说明属性名准确性。

weixin_30978239的博客 1026

基于MvvmLight的MVVM程序内附详细说明.7z

一个 基于 MVVM LIGHT程序实例,内附详细的教程。包括1.1 MVVM Light Toolkit下载,安装 目录 1 MVVM Light的安装 1 1.1 MVVM Light Toolkit下载 1 1.2 MVVM Light Toolkit安装 2 2 程序实例 3 2.1 建立工程文件 3 2.2 增加自己的MVVM模块 3 2.2.1 创建Model 3 2.2.2 创建Design 5 2.2.3 创建View Model 6 2.2.4 创建MvvmViewModelLocator 7 2.2.5 修改App.xaml 10 2.2.6 创建View 12 2.2.7 主窗口中增加启动MyFirsMvvmView的相关代码 13 2.2.8 程序运行结果如下 14 3 总结 14 3.1 程序编写流程 14 3.2 未尽事宜 15

WPF真入门教程29--MVVM常用框架之MvvmLight

在WPF中,有几个常用的MVVM(Model-View-ViewModel)开源框架,这些框架提供了方便的工具和模式来帮助开发人员实现MVVM架构。以下是几个常用的MVVM开源框架:1. Prism:Prism是一个由微软模式和实践团队开发的开源框架,它提供了一套用于开发可扩展、可测试和可维护的WPF应用程序的工具和指南。Prism提供了解耦视图、模型和视图模型的机制,以及用于导航、命令、事件聚合等常用功能。

hqwest的专栏 5780

Spring AI 实战:5 分钟搭建你的第一个 AI 应用(含完整代码)

能力关键 API适用场景基础对话简单问答、文本生成流式输出实时打字机效果@Bean模型需要调用外部工具结构化输出需要返回 Java 对象多轮对话聊天机器人场景Spring AI 让 Java 开发者可以用最熟悉的方式(Spring Boot + Maven)进入 AI 应用开发领域,不用学新语言,不用换框架,这是它最大的优势。但目前为止,我们的 AI 应用只能回答"它脑子里已有的知识"。如果让它读懂你自己的技术文档、公司内部知识库呢?这就是下一篇文章的主题——RAG(检索增强生成)实战。

qq_17204557的博客 642

探索WPF开发的新境界:MVVMLight框架实战指南

探索WPF开发的新境界:MVVMLight框架实战指南 去发现同类优质开源项目:https://gitcode.com/ 在这个追求高效、模块化软件开发的时代,WPF(Windows Presentation Foundation)作为.NET平台上强大的UI框架,携手MVVMLight,正成为开发桌面应用的一股不可忽视的力量。今天,我们为您揭秘一个精心打造的WPF MVVMLight框架Demo...

gitblog_09732的博客 888

AD9361 LVDS数据接口时序解析与FPGA实现

本文详细解析了AD9361 LVDS数据接口的时序特性与FPGA实现方法,重点探讨了DATA_CLK、Rx_FRAME等关键信号的时序关系及数据拼接技巧。通过Verilog代码示例和调试经验分享,帮助开发者解决跨时钟域处理、IQ数据重组等常见问题,提升射频收发系统的稳定性与性能。

weixin_29186249的博客 128

STM32+ESP8266接入OneNet的MQTT嵌入式实现

MQTT是一种轻量级发布/订阅模式的消息传输协议,广泛应用于资源受限的物联网终端设备通信。其基于TCP/IP栈构建,具备低带宽占用、小代码体积和高可靠性等技术优势,特别适合STM32等MCU与Wi-Fi模组协同工作的边缘节点场景。在实际工程中,需结合AT指令集控制ESP8266完成Wi-Fi连接、TCP建链及MQTT会话管理,并通过环形缓冲区、空闲中断检测等机制保障UART异步通信稳定性。本文围绕STM32F103与ESP8266硬件协同、OneNet平台对接、JSON消息解析与OLED状态反馈等关键环节,

yhn456789的博客 1036

WPF MvvmLight 超详细使用教程

本文介绍了WPF开发中MVVM模式的轻量级框架MvvmLight的使用教程。主要内容包括:MVVM模式的三层架构(View、ViewModel、Model)及其解耦优势;MvvmLight框架的核心组件(ViewModelBase、RelayCommand、Messenger等)和功能特点;项目搭建步骤与规范目录结构;详细讲解了ViewModelBase实现属性通知和RelayCommand命令绑定的具体用法。通过实战代码示例,帮助开发者快速掌握MvvmLight框架,实现WPF应用的MVVM规范化开发。

故事不长丨的博客 1972

SAP 货币类型和公司代码的货币设置

货币类型分为公司代码和集团货币,一般FI 10类型和集团货币 30,事务代码是8KEM; 设置货币类型的事务代码是OB22 在S/4 1809版本里编辑功能统合到事务代码FINSC_LEDGER 中了,这里集中了分类账和公司代码的设置; 设置多账簿的币种(会出现在BSEG和FAGLFLEXA表中) 汇率维护在TCURR表中,可通过BAPI_EXCHANGERATE_GETDETAIL函数获取。 CO货币的事务代码是成本控制范围货币:OKKP ,一般设置为货币码20和30; 另外, SCC4设置集团货币,

RichardYang2020的博客 1万+

WPF MvvmLight

WPF框架---MvvmLight的使用和描述

代码改变世界 1924

PCL 区域生长分割【2026最新版】

区域生长算法原理介绍、代码实现以及一个具体实验场景的应用案例进一步实现将分割结果的分类保存至本地文件夹。最近一次更新时间为:2026年5月4日。

点云侠的博客 40万+

手把手教你学Simulink--移动机器人多轮协同场景实例:基于Simulink的双轮差速PMSM轮毂电机协同驱动仿真

本文介绍了基于Simulink的双轮差速PMSM轮毂电机协同驱动仿真系统。针对移动机器人存在的电机参数不一致、负载分配不均等问题,提出主从同步+力矩补偿的协同控制策略。通过搭建包含运动学模型、PMSM电机模型和矢量控制器的仿真系统,实现了速度同步误差<0.005m/s、轨迹跟踪误差<0.8cm、能耗降低12.5%的优化效果。文章详细阐述了建模步骤、核心算法实现和性能验证,为移动机器人动力系统设计提供了实用参考方案。

MHD0815的博客 131

【WPF】MVVMLight

一、MVVM介绍 MVVM(Model-View-ViewModel)—— 类似于MVC、MVP的设计模式(非框架级别),由MVP(Model-View-Presenter)模式与WPF结合的应用方式发展演变过来的一种新型架构。主要目的是分离视图(View)和模型(Model)的耦合。 1、结构: 2、优点: (1)低耦合:视图(View)独立于Model的变化,一个ViewModel可以绑定到不同的View上。View变化时Model可以不变,Model变化时View可以不变。 ...

weixin_56814032的博客 2305

GEE:哨兵时间序列遥感数据和动态阈值方法计算物候时期EOS/SOS(2)

本文将分享和解释论文《Improved Estimates of Arctic Land Surface Phenology Using Sentinel-2 Time Series》中使用到的基于阈值估算北极地区的植被物候,特别是北极地区的植被季节开始和结束的日期(SoS和EoS)的方法和代码。该方法使用的是使用哨兵2数据,并实现了两种阈值方法( 无平滑的阈值方法、平滑后的阈值方法)。

养乐多的博客 2971

MVVMLight(一)入门教程、WPF使用MVVMLitht教程。

一、入门教程 1、http://www.cnblogs.com/shanlin/p/3954531.html 不全是MVVM模式,但是楼主自己做了一个软件给自己使用,值得学习。软件界面以及在代码在连接里面有 2、https://www.cnblogs.com/manupstairs/p/4890300.html 提供了MvvmLight框架使用入门(一)到(四)入门教程 3、http...

xpj8888的博客 9942

YOLOv11-seg改进系列 | 引入CMTFNet(TGRS2023)+TransNeXt(CVPR2024)的C3k2_MSMHSA_CGLU模块,多尺度空洞注意力扩大感受野,增强复杂背景下

本文提出一种改进YOLOv11分割模型的C3k2_MSMHSA_CGLU模块,融合多尺度多头自注意力(MSMHSA)和卷积门控线性单元(CGLU)。MSMHSA通过多尺度空洞卷积提取不同感受野特征,生成跨尺度注意力,降低计算量同时保留全局上下文。CGLU采用卷积门控机制增强特征变换能力。实验表明,改进后模型参数量仅增加7.8%,GFLOPs增加4.2%,显著提升了多尺度目标检测性能,尤其适用于遥感图像等复杂场景。该模块有效解决了原始C3k2感受野有限、缺乏全局建模能力的问题。

qq_44224801的博客 200

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

WPF MVVM框架MvvmLight使用入门

MVVM Light是WPF的一个mvvm框架,目前已经停止维护,适用于Net framework版本下的WPF程序,如果要在net5或者更新的net版本使用,可以使用新的CommunityToolkit.Mvvm包,它是 .NET 社区工具包的一部分,用法和MvvmLight类似。

海盗Sharp的博客 3699

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

WPF MvvmLight的使用

WPF MvvmLight的使用 一.在新建的项目中管理NuGet程序包,如下图,下载并安装: 我们会看到安装成功后项目中多了一个文件夹ViewModel,内部有两个文件:MainViewModel.cs 和 ViewModelLocator.cs(有时候生成的这个文件中的命名空间有错误,需要重新添加,此文件不用也可删除) 二.在MainViewModel 类中实现关联命令和属性; using ...

韬光养晦 585

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

【亲测免费】 WPF+Mvvmlight 简单导航实例:快速上手WPF应用开发

WPF+Mvvmlight 简单导航实例:快速上手WPF应用开发 去发现同类优质开源项目:https://gitcode.com/ 项目介绍 在现代桌面应用程序开发中,WPF(Windows Presentation Foundation)因其强大的UI定制能力和丰富的控件库而备受开发者青睐。然而,对于初学者来说,WPF的复杂性可能会成为一道门槛。为了帮助开发者快速上手WPF应用开发,我们推出了一...

gitblog_09701的博客 946

WPF中MVVM和MVVMLight模式

MVVM模式通过分离视图、视图模型和模型,提升代码可维护性。MVVMlight作为轻量级框架,简化了MVVM实现:模型层管理数据并支持变更通知;视图模型层连接模型与视图,处理业务逻辑;视图层专注UI展示。关键技术点包括INotifyPropertyChanged接口实现、ViewModelBase继承和ObservableCollection应用。该框架还提供RelayCommand简化命令绑定,使开发者无需直接实现ICommand接口。这种架构显著提高了软件的可测试性和复用性,特别适合大型项目开发。

过客 1378

wpf mvvmlight框架demo

https://www.cnblogs.com/manupstairs/p/4890300.html 1.在nuget中搜索安装mvvmlight。过程如果提示nuget版本低,则卸载nuget,重新下载并重启即可(网络有出错的解决教程,不重复讲解) 2.MVVM其实包括三部分,model即数据模型,viewmodel依赖于model,操作model,并定义了一些列的方法与command命令,VI...

weixin_43632687的博客 1814

MVVMLight介绍以及在项目中的使用

一、MVVM概述 MVVM是Model-View-ViewModel的简写,主要目的是为了解耦视图(View)和模型(Model)。 MVVM结构如下: 相对于之前把逻辑结构写在Code Behind 的方式,MVVM模式几乎完全解耦了视图和逻辑业务的关系,通过数据绑定和命令绑定来处理UI属性及事件驱动; 同时,ViewModel中对属性的变更也会通知到View前端,让View前端实时更新。 关于更多的MVVM,可以参考https://www.codeproject.com/kb/wpf/wpfmvvmq

樱花的博客 1万+
上一篇: 从 MVVMLight 到 CommunityToolkit.Mvvm:MVVM 框架的现代化演进与全面对比
下一篇: WPF + Microsoft.ToolKit.Mvvm 技术指南与实战项目
bugcome_com
博客等级 码龄3年 32粉丝 131原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcome_com

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值