WPF MVVM 模式
大约 9 分钟约 2710 字
WPF MVVM 模式
简介
MVVM(Model-View-ViewModel)是 WPF 应用的标准架构模式。它将应用分为三层:Model(数据模型)、View(视图)、ViewModel(视图模型),通过数据绑定实现 View 和 ViewModel 的解耦。MVVM 让 UI 逻辑和业务逻辑分离,便于测试和维护。
特点
MVVM 架构图
View (视图)
XAML 界面,只负责展示和用户交互
通过 DataBinding 绑定 ViewModel
通过 Command 触发 ViewModel 方法
|
| 数据绑定 + 命令(不直接引用)
v
ViewModel (视图模型)
暴露属性供 View 绑定
暴露命令供 View 调用
处理 UI 逻辑和状态管理
调用 Model 获取数据
|
| 调用
v
Model (数据模型)
实体类、数据访问、业务逻辑、服务INotifyPropertyChanged — 属性变更通知
/// <summary>
/// ViewModel 基类 — 实现 INotifyPropertyChanged
/// 所有 ViewModel 都继承此类
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// 通用属性设置方法 — 检测值变化并触发通知
/// </summary>
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;
}
}ICommand — 命令绑定
通用命令实现
/// <summary>
/// RelayCommand — 通用命令实现
/// 替代事件处理,实现 View 和 ViewModel 的解耦
/// </summary>
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;
}
public RelayCommand(Action execute, Func<bool> canExecute = null)
: this(_ => execute(), canExecute != null ? _ => canExecute() : null)
{
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public bool CanExecute(object parameter) => _canExecute?.Invoke(parameter) ?? true;
public void Execute(object parameter) => _execute(parameter);
}
/// <summary>
/// 泛型命令 — 支持强类型参数
/// </summary>
public class RelayCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Func<T, bool> _canExecute;
public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public bool CanExecute(object parameter) => _canExecute?.Invoke((T)parameter) ?? true;
public void Execute(object parameter) => _execute((T)parameter);
}完整 MVVM 示例 — 用户管理
Model
/// <summary>
/// Model — 数据实体和业务逻辑
/// </summary>
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Department { get; set; }
public DateTime JoinDate { get; set; }
public bool IsActive { get; set; }
}
/// <summary>
/// 数据访问接口
/// </summary>
public interface IUserRepository
{
Task<List<User>> GetAllAsync();
Task<User> GetByIdAsync(int id);
Task AddAsync(User user);
Task UpdateAsync(User user);
Task DeleteAsync(int id);
}ViewModel
/// <summary>
/// ViewModel — 连接 View 和 Model 的桥梁
/// 不包含任何 UI 引用(无 Button、TextBox 等)
/// </summary>
public class UserManagementViewModel : ViewModelBase
{
private readonly IUserRepository _repository;
// ===== 属性 =====
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get => _users;
set => SetProperty(ref _users, value);
}
private User _selectedUser;
public User SelectedUser
{
get => _selectedUser;
set
{
SetProperty(ref _selectedUser, value);
// 选中用户变化时,更新详情
UpdateDetail( value);
}
}
// 表单属性
private string _inputName;
public string InputName
{
get => _inputName;
set => SetProperty(ref _inputName, value);
}
private string _inputEmail;
public string InputEmail
{
get => _inputEmail;
set => SetProperty(ref _inputEmail, value);
}
private string _inputDepartment;
public string InputDepartment
{
get => _inputDepartment;
set => SetProperty(ref _inputDepartment, value);
}
private string _statusMessage;
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
private bool _isLoading;
public bool IsLoading
{
get => _isLoading;
set => SetProperty(ref _isLoading, value);
}
// ===== 命令 =====
public RelayCommand LoadCommand { get; }
public RelayCommand AddCommand { get; }
public RelayCommand<User> DeleteCommand { get; }
public RelayCommand<User> EditCommand { get; }
public RelayCommand SaveCommand { get; }
// ===== 构造函数 =====
public UserManagementViewModel(IUserRepository repository)
{
_repository = repository;
Users = new ObservableCollection<User>();
// 初始化命令
LoadCommand = new RelayCommand(async _ => await LoadUsersAsync(), _ => !IsLoading);
AddCommand = new RelayCommand(_ => AddUser(), _ => CanAddUser());
DeleteCommand = new RelayCommand<User>(async user => await DeleteUserAsync(user));
EditCommand = new RelayCommand<User>(user => EditUser(user));
SaveCommand = new RelayCommand(async _ => await SaveAsync(), _ => SelectedUser != null);
}
// ===== 命令实现 =====
private async Task LoadUsersAsync()
{
IsLoading = true;
StatusMessage = "加载中...";
try
{
var users = await _repository.GetAllAsync();
Users.Clear();
foreach (var user in users)
Users.Add(user);
StatusMessage = $"加载完成,共 {users.Count} 条记录";
}
catch (Exception ex)
{
StatusMessage = $"加载失败:{ex.Message}";
}
finally
{
IsLoading = false;
}
}
private bool CanAddUser()
{
return !string.IsNullOrWhiteSpace(InputName)
&& !string.IsNullOrWhiteSpace(InputEmail);
}
private void AddUser()
{
var user = new User
{
Name = InputName,
Email = InputEmail,
Department = InputDepartment,
JoinDate = DateTime.Now,
IsActive = true
};
Users.Add(user);
StatusMessage = $"已添加用户:{user.Name}";
// 清空输入
InputName = string.Empty;
InputEmail = string.Empty;
InputDepartment = string.Empty;
}
private async Task DeleteUserAsync(User user)
{
if (user == null) return;
Users.Remove(user);
StatusMessage = $"已删除用户:{user.Name}";
await Task.CompletedTask;
}
private void EditUser(User user)
{
if (user == null) return;
InputName = user.Name;
InputEmail = user.Email;
InputDepartment = user.Department;
}
private async Task SaveAsync()
{
if (SelectedUser == null) return;
SelectedUser.Name = InputName;
SelectedUser.Email = InputEmail;
SelectedUser.Department = InputDepartment;
StatusMessage = $"已保存用户:{SelectedUser.Name}";
await Task.CompletedTask;
}
private void UpdateDetail(User user)
{
if (user != null)
{
InputName = user.Name;
InputEmail = user.Email;
InputDepartment = user.Department;
}
}
}View
<!-- View — 纯 XAML,后台代码几乎为空 -->
<Window x:Class="MyApp.Views.UserManagementView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:MyApp.ViewModels"
Title="用户管理" Height="600" Width="800">
<!-- 绑定 ViewModel -->
<Window.DataContext>
<vm:UserManagementViewModel />
</Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 工具栏 -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
<Button Content="刷新" Command="{Binding LoadCommand}" Padding="10,5" />
<Button Content="添加" Command="{Binding AddCommand}" Padding="10,5" Margin="10,0,0,0" />
</StackPanel>
<!-- 用户列表 -->
<DataGrid Grid.Row="1"
ItemsSource="{Binding Users}"
SelectedItem="{Binding SelectedUser}"
AutoGenerateColumns="False"
IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="姓名" Binding="{Binding Name}" />
<DataGridTextColumn Header="邮箱" Binding="{Binding Email}" />
<DataGridTextColumn Header="部门" Binding="{Binding Department}" />
<DataGridTextColumn Header="入职日期" Binding="{Binding JoinDate, StringFormat='{}{0:yyyy-MM-dd}'}" />
<DataGridTemplateColumn Header="操作">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="编辑" Command="{Binding DataContext.EditCommand,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}" Padding="5,2" />
<Button Content="删除" Command="{Binding DataContext.DeleteCommand,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}" Padding="5,2" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- 编辑区域 -->
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,10,0,0">
<TextBox Text="{Binding InputName, UpdateSourceTrigger=PropertyChanged}"
Width="120" Margin="0,0,10,0" />
<TextBox Text="{Binding InputEmail, UpdateSourceTrigger=PropertyChanged}"
Width="180" Margin="0,0,10,0" />
<TextBox Text="{Binding InputDepartment, UpdateSourceTrigger=PropertyChanged}"
Width="120" Margin="0,0,10,0" />
<Button Content="保存" Command="{Binding SaveCommand}" Padding="10,5" />
</StackPanel>
<!-- 状态栏 -->
<TextBlock Grid.Row="3" Text="{Binding StatusMessage}" Margin="0,10,0,0" />
</Grid>
</Window>MVVM 中的消息通信
View 之间的通信 — Messenger 模式
/// <summary>
/// 简易消息总线 — ViewModel 之间的解耦通信
/// </summary>
public class Messenger
{
private static readonly Messenger _instance = new Messenger();
public static Messenger Default => _instance;
private readonly Dictionary<Type, List<Delegate>> _handlers = new();
private readonly object _lock = new();
// 注册消息
public void Register<TMessage>(Action<TMessage> handler)
{
lock (_lock)
{
if (!_handlers.TryGetValue(typeof(TMessage), out var list))
{
list = new List<Delegate>();
_handlers[typeof(TMessage)] = list;
}
list.Add(handler);
}
}
// 发送消息
public void Send<TMessage>(TMessage message)
{
lock (_lock)
{
if (_handlers.TryGetValue(typeof(TMessage), out var list))
{
foreach (var handler in list.ToList())
{
((Action<TMessage>)handler)(message);
}
}
}
}
// 取消注册
public void Unregister<TMessage>(Action<TMessage> handler)
{
lock (_lock)
{
if (_handlers.TryGetValue(typeof(TMessage), out var list))
{
list.Remove(handler);
}
}
}
}
// 消息定义
public class UserSelectedMessage
{
public int UserId { get; set; }
public string UserName { get; set; }
}
// ViewModel A — 发送消息
public class UserListViewModel : ViewModelBase
{
public void SelectUser(User user)
{
Messenger.Default.Send(new UserSelectedMessage
{
UserId = user.Id,
UserName = user.Name
});
}
}
// ViewModel B — 接收消息
public class UserDetailViewModel : ViewModelBase
{
public UserDetailViewModel()
{
Messenger.Default.Register<UserSelectedMessage>(msg =>
{
LoadUserDetail(msg.UserId);
});
}
}使用 MVVM 框架 — CommunityToolkit.Mvvm
dotnet add package CommunityToolkit.Mvvm/// <summary>
/// CommunityToolkit.Mvvm — 微软官方 MVVM 工具包
/// 使用 Source Generator 减少样板代码
/// </summary>
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
// ViewModel — 几乎零样板代码
public partial class MainViewModel : ObservableObject
{
// [ObservableProperty] 自动生成属性变更通知
[ObservableProperty]
private string _title = "MVVM 示例";
[ObservableProperty]
private string _userName = string.Empty;
[ObservableProperty]
private ObservableCollection<User> _users = new();
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))] // UserName变化时刷新Save命令状态
private User _selectedUser;
[ObservableProperty]
private bool _isLoading;
// [RelayCommand] 自动生成 ICommand 实现
[RelayCommand]
private async Task LoadAsync()
{
IsLoading = true;
try
{
var users = await _repository.GetAllAsync();
Users = new ObservableCollection<User>(users);
}
finally
{
IsLoading = false;
}
}
// 带条件的命令
[RelayCommand(CanExecute = nameof(CanSave))]
private async Task SaveAsync()
{
await _repository.UpdateAsync(SelectedUser);
}
private bool CanSave() => SelectedUser != null && !IsLoading;
// 带参数的命令
[RelayCommand]
private void Delete(User user)
{
Users.Remove(user);
}
}<!-- 自动生成的命令名称规则:方法名 + Command -->
<Button Content="加载" Command="{Binding LoadCommand}" />
<Button Content="保存" Command="{Binding SaveCommand}" />
<Button Content="删除" Command="{Binding DeleteCommand}" CommandParameter="{Binding SelectedUser}" />优点
缺点
总结
MVVM 是 WPF 开发的标准模式。核心三要素:INotifyPropertyChanged(属性通知)、ICommand(命令绑定)、数据绑定。推荐使用 CommunityToolkit.Mvvm 减少样板代码,提升开发效率。
关键知识点
- 先分清主题属于界面层、ViewModel 层、线程模型还是设备接入层。
- WPF 文章真正的价值在于把 UI、数据、命令、线程和资源关系讲清楚。
- 上位机场景里,稳定性和异常恢复常常比界面花哨更重要。
- WPF 主题往往要同时理解依赖属性、绑定、可视树和命令系统。
项目落地视角
- 优先明确 DataContext、绑定路径、命令源和线程切换位置。
- 涉及设备时,补齐超时、重连、日志、告警和资源释放策略。
- 复杂控件最好提供最小可运行页面,便于后续复用和排障。
- 优先确认 DataContext、绑定路径、命令触发点和资源引用来源。
常见误区
- 把大量逻辑堆进 code-behind,导致页面难测难维护。
- 在后台线程直接操作 UI,或忽略 Dispatcher 切换。
- 只验证正常路径,不验证设备掉线、权限缺失和资源泄漏。
- 在 code-behind 塞太多状态与业务逻辑。
进阶路线
- 继续向 MVVM 工具链、控件可复用性、性能优化和视觉树诊断深入。
- 把主题放回真实设备流程,思考启动、连接、采集、显示、告警和恢复。
- 沉淀成控件库、通信层和诊断工具,提高整套客户端的复用度。
- 继续补齐控件库、主题系统、诊断工具和启动性能优化。
适用场景
- 当你准备把《WPF MVVM 模式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合桌面业务系统、监控看板、工控上位机和设备交互界面。
- 当系统需要同时处理复杂 UI、后台任务和硬件通信时,这类主题尤为关键。
落地建议
- 优先明确 View、ViewModel、服务层和设备层边界,避免代码隐藏过重。
- 涉及实时刷新时,提前设计 UI 线程切换、节流和资源释放。
- 对硬件通信、日志、告警和异常恢复建立标准流程。
排错清单
- 先检查 DataContext、绑定路径、INotifyPropertyChanged 和命令状态是否正常。
- 排查 Dispatcher 调用、死锁、后台线程直接操作 UI 的问题。
- 出现内存增长时,优先检查事件订阅、图像资源和窗口生命周期。
复盘问题
- 如果把《WPF MVVM 模式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《WPF MVVM 模式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《WPF MVVM 模式》最大的收益和代价分别是什么?
