MVVM架构与ICommand核心笔记
一、MVVM 架构核心理论
1. 定义与本质
MVVM(Model-View-ViewModel)是基于 MVC 改进的 UI 架构模式,核心是通过 ViewModel 实现 View 与 Model 的解耦,适用于支持数据绑定的框架(如 WPF、.NET MAUI、Vue.js),核心目标是提升代码可维护性、可测试性与复用性。
2. 三大组件职责与数据流向
| 组件 | 核心职责 | 关键特性与注意事项 |
|---|---|---|
| Model(模型) | 1. 存储业务数据(如用户信息、订单数据);2. 封装业务逻辑(如数据校验、计算);3. 数据持久化(读写数据库、XML 等)。 | - 完全独立于 UI,不依赖任何视图组件;- 数据来源:外部存储(数据库 / XML)、初始值、View 收集后同步;- 示例:User实体类、OrderService业务逻辑类。 |
| View(视图) | 1. 展示 UI 界面(按钮、输入框、列表等);2. 收集用户输入(如表单填写);3. 渲染界面外观(样式、布局)。 | - 仅保留简单数据校验(如非空判断、格式校验),不包含业务逻辑;- 通过数据绑定关联 ViewModel,不直接操作 Model;- 示例:WPF 的 XAML 页面、.NET MAUI 的页面布局。 |
| ViewModel(视图模型) | 1. 作为 View 与 Model 的 “桥梁”,传递数据;2. 暴露 View 可绑定的属性(如格式化后的数据);3. 响应 View 的用户操作(如按钮点击)。 | - 实现INotifyPropertyChanged接口,确保属性变更同步到 View;- 不依赖 UI 组件,可独立编写单元测试;- 核心作用:解耦 View 与 Model,隔离业务逻辑。 |
3. 数据交互流程(以 “用户信息展示与修改” 为例)
读取数据(Model→View):
Model 从数据库读取用户信息(如User{Id=1, Name="张三", Age=25})→ ViewModel 接收 Model 数据并封装为可绑定属性(如UserName“张三”)→ View 通过数据绑定展示UserName。
保存数据(View→Model):
View 收集用户输入(如修改UserName为 “李四”)→ ViewModel 接收输入并验证→ 调用 Model 的持久化方法→ Model 将 “李四” 同步到数据库。
4. MVVM 与 MVP 的差异
| 架构 | 核心组件 | 关键区别 | 适用场景 |
|---|---|---|---|
| MVVM | Model-View-ViewModel | 1. View 与 ViewModel 通过数据绑定关联,无直接代码依赖;2. ViewModel 独立于 UI,可测试性更强。 | WPF、.NET MAUI、前端框架 |
| MVP | Model-View-Presenter | 1. Presenter(如 WinForm 的 Form.cs)直接持有 View 引用,耦合度高;2. View 需暴露方法供 Presenter 调用更新。 | WinForm、传统桌面应用 |
核心结论:MVVM 是 MVP 的优化,用 ViewModel 替代 Presenter,通过数据绑定彻底解耦 View 与业务逻辑。
二、ViewModel 核心技术:INotifyPropertyChanged
1. 作用
当 ViewModel 的属性值变更时,通过INotifyPropertyChanged接口触发PropertyChanged事件,通知 View 自动更新 UI(实现 “数据驱动 UI”)。
2. 实现示例(以 “用户信息 ViewModel” 为例)
using System.ComponentModel;
using System.Runtime.CompilerServices;
// ViewModel基类:封装INotifyPropertyChanged,供所有ViewModel继承
public class BaseViewModel : INotifyPropertyChanged
{// 事件:属性变更时触发public event PropertyChangedEventHandler? PropertyChanged;
// 触发事件的辅助方法([CallerMemberName]自动获取调用属性名)protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}
// 简化属性赋值:赋值后自动触发事件protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null){if (EqualityComparer<T>.Default.Equals(field, value)) return false;field = value;OnPropertyChanged(propertyName);return true;}
}
// 具体ViewModel:用户信息视图模型
public class UserViewModel : BaseViewModel
{// 持有Model实例(业务数据来源)private readonly User _userModel;
// 可绑定属性:用户名(供View绑定)private string _userName;public string UserName{get => _userName;set => SetProperty(ref _userName, value); // 赋值后自动触发UI更新}
// 可绑定属性:年龄(格式化展示,如“25岁”)public string FormattedAge => $"{_userModel.Age}岁";
// 构造函数:初始化Model并同步数据到ViewModelpublic UserViewModel(User userModel){_userModel = userModel;// 将Model数据同步到ViewModel属性_userName = _userModel.Name;}
// 业务方法:保存用户信息(View调用此方法同步到Model)public void SaveUserInfo(){// 验证数据(ViewModel层的业务逻辑)if (string.IsNullOrWhiteSpace(UserName))throw new ArgumentException("用户名不能为空");// 同步ViewModel数据到Model_userModel.Name = UserName;// 调用Model的持久化方法(如保存到数据库)_userModel.SaveToDatabase();}
}
// Model:用户实体类
public class User
{public int Id { get; set; }public string Name { get; set; } = string.Empty;public int Age { get; set; }
// Model层业务逻辑:保存到数据库(模拟)public void SaveToDatabase(){Console.WriteLine($"用户{Name}(ID:{Id})已保存到数据库");}
}三、ICommand:ViewModel 响应 View 操作的核心
1. 定义与作用
ICommand 是.NET 中用于解耦用户操作(如按钮点击)与业务逻辑的接口,核心能力:
判断操作是否可执行(CanExecute);
执行操作逻辑(Execute);
通知 UI 更新操作可用性(CanExecuteChanged事件)。
2. 接口核心成员
public interface ICommand
{// 1. 判断命令是否可执行(返回true则按钮启用,false则禁用)bool CanExecute(object? parameter);
// 2. 执行命令对应的业务逻辑void Execute(object? parameter);
// 3. 当命令可执行状态变化时触发,通知UI更新按钮状态event EventHandler? CanExecuteChanged;
}3. 经典实现:RelayCommand(通用命令封装)
由于 ICommand 是接口,需自定义实现类(RelayCommand),通过委托灵活接收业务逻辑:
using System.Windows.Input;
public class RelayCommand : ICommand
{// 委托:存储命令执行逻辑private readonly Action<object?> _execute;// 委托:存储命令可执行状态判断逻辑(可选)private readonly Func<object?, bool>? _canExecute;
// 构造函数:传入执行逻辑,可执行判断逻辑可选(默认始终可执行)public RelayCommand(Action<object?> execute, Func<object?, bool>? canExecute = null){_execute = execute ?? throw new ArgumentNullException(nameof(execute));_canExecute = canExecute;}
// 实现ICommand:判断命令是否可执行public bool CanExecute(object? parameter){return _canExecute?.Invoke(parameter) ?? true; // 无判断逻辑则默认可执行}
// 实现ICommand:执行命令逻辑public void Execute(object? parameter){_execute(parameter);}
// 实现ICommand:订阅/取消订阅CanExecuteChanged事件// 依赖CommandManager自动监测UI变化,更新可执行状态public event EventHandler? CanExecuteChanged{add => CommandManager.RequerySuggested += value;remove => CommandManager.RequerySuggested -= value;}
// 手动触发CanExecuteChanged事件(可选,强制更新UI状态)public void RaiseCanExecuteChanged(){CommandManager.InvalidateRequerySuggested();}
}4. ICommand 在 ViewModel 中的应用示例
public class UserViewModel : BaseViewModel
{private readonly User _userModel;private string _userName;
// 1. 定义命令属性(供View绑定按钮点击)public ICommand SaveCommand { get; }
public string UserName{get => _userName;set {SetProperty(ref _userName, value);// 用户名变化时,手动更新SaveCommand的可执行状态((RelayCommand)SaveCommand).RaiseCanExecuteChanged();}}
public UserViewModel(User userModel){_userModel = userModel;_userName = _userModel.Name;
// 2. 初始化命令:绑定执行逻辑与可执行判断SaveCommand = new RelayCommand(execute: parameter => SaveUserInfo(), // 执行“保存用户”逻辑canExecute: parameter => !string.IsNullOrWhiteSpace(UserName) // 用户名非空才可用);}
// 3. 命令对应的业务逻辑private void SaveUserInfo(){_userModel.Name = UserName;_userModel.SaveToDatabase();// 可添加UI反馈逻辑(如弹出提示)Console.WriteLine("用户信息保存成功!");}
}5. View 与 ICommand 的绑定(WPF/XAML 示例)
<!-- 1. 引用ViewModel命名空间 -->
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:vm="clr-namespace:YourProject.ViewModels"Title="用户信息管理" Height="300" Width="400"><!-- 2. 设置DataContext为ViewModel实例(绑定上下文) --><Window.DataContext><vm:UserViewModel><!-- 初始化Model参数 --><vm:UserViewModel.UserModel><vm:User Id="1" Name="张三" Age="25" /></vm:UserViewModel.UserModel></vm:UserViewModel></Window.DataContext>
<!-- 3. 绑定ViewModel的属性与命令 --><StackPanel Margin="20"><!-- 输入框:绑定UserName属性(双向绑定,输入变化同步到ViewModel) --><TextBox Text="{Binding UserName, Mode=TwoWay}" Margin="0 0 0 10" Hint="请输入用户名" /><!-- 按钮:绑定SaveCommand命令(仅当UserName非空时启用) --><Button Content="保存用户信息" Command="{Binding SaveCommand}" /></StackPanel>
</Window>四、MVVM 完整流程示例(用户信息修改场景)
初始化:View 加载时,ViewModel 接收 Model 的初始数据(“张三”),并通过数据绑定展示在 TextBox 中。
用户操作:用户在 TextBox 中修改用户名为 “李四”,输入变化通过双向绑定同步到 ViewModel 的UserName属性,触发PropertyChanged事件。
命令状态更新:UserName变化后,SaveCommand的CanExecute重新判断(“李四” 非空,返回 true),按钮保持启用。
执行命令:用户点击 “保存” 按钮,SaveCommand的Execute方法调用SaveUserInfo,将 “李四” 同步到 Model 并保存到数据库。
反馈:Model 保存成功后,ViewModel 可通过弹窗、日志等方式反馈结果(如控制台输出 “用户信息保存成功!”)。
