MVVM 架构
Model-View-ViewModel 架构模式完整指南
MVVM 简介
MVVM (Model-View-ViewModel) 是一种软件架构模式,专门为 UI 框架设计,实现了 UI 与业务逻辑的分离。
MVVM 架构图
Model
数据模型和业务逻辑
↔
ViewModel
视图模型和 UI 逻辑
↔
View
用户界面
各层职责
Model(模型层)
负责数据和业务逻辑,不包含任何 UI 相关代码。Model 类通常包含属性、方法和数据访问逻辑。
View(视图层)
负责 UI 展示,通过数据绑定与 ViewModel 交互。View 只关注外观和布局,不包含业务逻辑。
ViewModel(视图模型层)
作为 View 和 Model 之间的桥梁,处理 UI 逻辑,通过命令和属性与 View 通信。
MVVM 的优势
关注点分离
UI 逻辑与业务逻辑完全分离,代码结构清晰
可测试性
ViewModel 可以独立于 UI 进行单元测试
可维护性
代码组织清晰,易于理解和维护
设计师协作
设计师可以独立处理 XAML,不影响开发
代码复用
ViewModel 可以在多个 View 中复用
团队协作
前后端开发可以并行进行
Model 层
Model 层包含数据模型和业务逻辑,是应用程序的核心。
基础 Model 类
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public DateTime BirthDate { get; set; }
public int GetAge()
{
var today = DateTime.Today;
var age = today.Year - BirthDate.Year;
if (BirthDate.Date > today.AddYears(-age)) age--;
return age;
}
}
带通知的 Model
public class ObservablePerson : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
数据服务
public interface IPersonService
{
Task<IEnumerable<Person>> GetAllAsync();
Task<Person> GetByIdAsync(int id);
Task<Person> AddAsync(Person person);
Task<Person> UpdateAsync(Person person);
Task DeleteAsync(int id);
}
public class PersonService : IPersonService
{
private readonly DbContext _context;
public PersonService(DbContext context)
{
_context = context;
}
public async Task<IEnumerable<Person>> GetAllAsync()
{
return await _context.People.ToListAsync();
}
public async Task<Person> GetByIdAsync(int id)
{
return await _context.People.FindAsync(id);
}
public async Task<Person> AddAsync(Person person)
{
_context.People.Add(person);
await _context.SaveChangesAsync();
return person;
}
public async Task<Person> UpdateAsync(Person person)
{
_context.People.Update(person);
await _context.SaveChangesAsync();
return person;
}
public async Task DeleteAsync(int id)
{
var person = await _context.People.FindAsync(id);
if (person != null)
{
_context.People.Remove(person);
await _context.SaveChangesAsync();
}
}
}
View 层
View 层由 XAML 和代码隐藏组成,负责 UI 的展示和用户交互。
MainWindow.xaml
<Window x:Class="WpfApp.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="人员管理" Height="600" Width="900">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 工具栏 -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,20">
<Button Content="添加" Command="{Binding AddCommand}"
Width="80" Margin="0,0,10,0" />
<Button Content="编辑" Command="{Binding EditCommand}"
Width="80" Margin="0,0,10,0" />
<Button Content="删除" Command="{Binding DeleteCommand}"
Width="80" />
</StackPanel>
<!-- 数据网格 -->
<DataGrid Grid.Row="1"
ItemsSource="{Binding People}"
SelectedItem="{Binding SelectedPerson}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="*" />
<DataGridTextColumn Header="姓名" Binding="{Binding Name}" Width="2*" />
<DataGridTextColumn Header="邮箱" Binding="{Binding Email}" Width="3*" />
<DataGridTextColumn Header="年龄" Binding="{Binding Age}" Width="*" />
</DataGrid.Columns>
</DataGrid>
<!-- 状态栏 -->
<StatusBar Grid.Row="2">
<StatusBarItem>
<TextBlock Text="{Binding StatusMessage}" />
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace WpfApp.Views
{
public partial class MainWindow : Window
{
public MainWindow(MainViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
}
UserControl 示例
<UserControl x:Class="WpfApp.Views.PersonEditView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="姓名:" Margin="0,0,0,5" />
<TextBox Grid.Row="1" Text="{Binding Person.Name, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,15" />
<TextBlock Grid.Row="2" Text="邮箱:" Margin="0,0,0,5" />
<TextBox Grid.Row="3" Text="{Binding Person.Email, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,15" />
<StackPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="保存" Command="{Binding SaveCommand}"
Width="80" Margin="0,0,10,0" />
<Button Content="取消" Command="{Binding CancelCommand}"
Width="80" />
</StackPanel>
</Grid>
</UserControl>
ViewModel 层
ViewModel 层是 MVVM 的核心,负责 UI 逻辑和数据绑定。
基础 ViewModel
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class MainViewModel : ObservableObject
{
private readonly IPersonService _personService;
[ObservableProperty]
private ObservableCollection<Person> _people = new();
[ObservableProperty]
private Person _selectedPerson;
[ObservableProperty]
private string _statusMessage = "就绪";
public IRelayCommand AddCommand { get; }
public IRelayCommand EditCommand { get; }
public IRelayCommand DeleteCommand { get; }
public IAsyncRelayCommand LoadCommand { get; }
public MainViewModel(IPersonService personService)
{
_personService = personService;
AddCommand = new RelayCommand(Add);
EditCommand = new RelayCommand(Edit, CanEdit);
DeleteCommand = new RelayCommand(Delete, CanDelete);
LoadCommand = new AsyncRelayCommand(LoadAsync);
LoadCommand.Execute(null);
}
private void Add()
{
var person = new Person { Name = "新用户", Email = "" };
_people.Add(person);
SelectedPerson = person;
StatusMessage = "已添加新用户";
}
private bool CanEdit() => SelectedPerson != null;
private void Edit()
{
StatusMessage = $"编辑用户: {SelectedPerson.Name}";
}
private bool CanDelete() => SelectedPerson != null;
private void Delete()
{
if (SelectedPerson != null)
{
_people.Remove(SelectedPerson);
StatusMessage = "用户已删除";
}
}
private async Task LoadAsync()
{
try
{
StatusMessage = "加载中...";
var people = await _personService.GetAllAsync();
People.Clear();
foreach (var person in people)
{
People.Add(person);
}
StatusMessage = $"已加载 {People.Count} 个用户";
}
catch (Exception ex)
{
StatusMessage = $"加载失败: {ex.Message}";
}
}
partial void OnSelectedPersonChanged(Person value)
{
EditCommand.NotifyCanExecuteChanged();
DeleteCommand.NotifyCanExecuteChanged();
}
}
编辑 ViewModel
public partial class PersonEditViewModel : ObservableObject
{
[ObservableProperty]
private Person _person;
public IRelayCommand SaveCommand { get; }
public IRelayCommand CancelCommand { get; }
public PersonEditViewModel(Person person)
{
_person = person;
SaveCommand = new RelayCommand(Save, CanSave);
CancelCommand = new RelayCommand(Cancel);
}
private bool CanSave() => !string.IsNullOrWhiteSpace(Person.Name) &&
!string.IsNullOrWhiteSpace(Person.Email);
private void Save()
{
StatusMessage = "保存成功";
CloseRequested?.Invoke(this, EventArgs.Empty);
}
private void Cancel()
{
CloseRequested?.Invoke(this, EventArgs.Empty);
}
public event EventHandler CloseRequested;
}
DataContext
DataContext 是 WPF 数据绑定的核心,它定义了数据上下文。
设置 DataContext
// App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 配置依赖注入
var services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
// 创建主窗口
var mainWindow = serviceProvider.GetRequiredService<MainWindow>();
mainWindow.Show();
}
private void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<DbContext>();
services.AddScoped<IPersonService, PersonService>();
services.AddTransient<MainViewModel>();
services.AddTransient<MainWindow>();
}
}
XAML 中设置 DataContext
<!-- 直接设置 -->
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<!-- 使用资源 -->
<Window.Resources>
<local:MainViewModel x:Key="ViewModel" />
</Window.Resources>
<!-- 使用绑定 -->
<ContentControl Content="{Binding}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:MainViewModel}">
<local:MainView />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
DataContext 继承
<!-- DataContext 会向下继承 -->
<Grid DataContext="{Binding ViewModel}">
<StackPanel>
<!-- 继承 Grid 的 DataContext -->
<TextBlock Text="{Binding Property1}" />
<TextBlock Text="{Binding Property2}" />
<!-- 可以覆盖继承的 DataContext -->
<StackPanel DataContext="{Binding SubViewModel}">
<TextBlock Text="{Binding SubProperty}" />
</StackPanel>
</StackPanel>
</Grid>
命令绑定
命令绑定实现了 UI 事件与 ViewModel 方法的解耦。
RelayCommand 实现
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 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);
}
使用 CommunityToolkit.Mvvm
public partial class MainViewModel : ObservableObject
{
[RelayCommand]
private void Save()
{
// 保存逻辑
}
[RelayCommand(CanExecute = nameof(CanDelete))]
private void Delete()
{
// 删除逻辑
}
private bool CanDelete() => SelectedPerson != null;
[RelayCommand]
private async Task LoadAsync()
{
// 异步加载逻辑
}
[RelayCommand(AllowConcurrentExecutions = false)]
private async Task ProcessAsync()
{
// 防止并发执行
}
}
XAML 中绑定命令
<Button Content="保存" Command="{Binding SaveCommand}" />
<!-- 带参数的命令 -->
<Button Content="删除"
Command="{Binding DeleteCommand}"
CommandParameter="{Binding SelectedPerson}" />
<!-- 菜单项命令 -->
<MenuItem Header="新建" Command="{Binding NewCommand}" />
<!-- 键盘快捷键 -->
<Button Content="保存"
Command="{Binding SaveCommand}">
<Button.InputBindings>
<KeyBinding Key="S" Modifiers="Ctrl"
Command="{Binding SaveCommand}" />
</Button.InputBindings>
</Button>
CommunityToolkit.Mvvm
CommunityToolkit.Mvvm 是微软官方提供的 MVVM 工具库,提供了大量实用的工具和特性。
安装
dotnet add package CommunityToolkit.Mvvm
ObservableObject
public partial class MainViewModel : ObservableObject
{
[ObservableProperty]
private string _title = "欢迎使用 WPF";
[ObservableProperty]
private int _count = 0;
[ObservableProperty]
private bool _isLoading = false;
// 自动生成属性和 OnPropertyChanged 调用
}
RelayCommand
public partial class MainViewModel : ObservableObject
{
[RelayCommand]
private void Increment()
{
Count++;
}
[RelayCommand(CanExecute = nameof(CanSave))]
private void Save()
{
// 保存逻辑
}
private bool CanSave() => Count > 0;
[RelayCommand]
private async Task LoadAsync()
{
IsLoading = true;
try
{
await Task.Delay(1000);
}
finally
{
IsLoading = false;
}
}
}
ObservableValidator
public partial class PersonViewModel : ObservableValidator
{
[ObservableProperty]
[Required(ErrorMessage = "姓名不能为空")]
[MinLength(2, ErrorMessage = "姓名至少2个字符")]
private string _name;
[ObservableProperty]
[Required(ErrorMessage = "邮箱不能为空")]
[EmailAddress(ErrorMessage = "邮箱格式不正确")]
private string _email;
[ObservableProperty]
[Range(0, 150, ErrorMessage = "年龄必须在0-150之间")]
private int _age;
}
Source Generators
// 使用 Source Generator 生成属性
public partial class PersonViewModel : ObservableObject
{
[ObservableProperty]
private string _name;
// 编译器自动生成:
// public string Name
// {
// get => _name;
// set => SetProperty(ref _name, value);
// }
}
💡 CommunityToolkit.Mvvm 优势
- 使用 Source Generators 提高性能
- 减少样板代码,提高开发效率
- 内置验证功能
- 支持异步命令
- 微软官方维护,稳定可靠