深入解析C# WPF MVVM架构模式 从基础概念到实际应用中的常见问题与解决方案
引言
在现代桌面应用程序开发中,WPF(Windows Presentation Foundation)作为微软推出的强大UI框架,配合MVVM(Model-View-ViewModel)架构模式,已经成为开发复杂、可维护应用程序的标准选择。本文将深入探讨MVVM模式的核心概念、实现细节以及在实际开发中遇到的常见问题和解决方案。
一、MVVM模式基础概念
1.1 什么是MVVM模式
MVVM(Model-View-ViewModel)是一种软件架构模式,它将应用程序分为三个主要部分:
- Model(模型):代表业务逻辑和数据,不包含任何UI相关的代码
- View(视图):用户界面,负责显示数据和接收用户输入
- ViewModel(视图模型):作为View和Model之间的桥梁,处理UI逻辑和数据转换
1.2 MVVM的核心优势
- 关注点分离:各层职责明确,便于独立开发和测试
- 可测试性:ViewModel可以脱离UI进行单元测试
- 可维护性:代码结构清晰,便于长期维护
- 数据绑定:WPF的数据绑定机制天然支持MVVM
二、MVVM各层详细解析
2.1 Model层实现
Model层应该保持纯净,只包含业务逻辑和数据定义:
// Model/User.cs public class User { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public DateTime CreatedDate { get; set; } } // Model/IUserService.cs public interface IUserService { Task<List<User>> GetUsersAsync(); Task<bool> SaveUserAsync(User user); Task<bool> DeleteUserAsync(int userId); } // Model/UserService.cs public class UserService : IUserService { public async Task<List<User>> GetUsersAsync() { // 模拟数据库调用 await Task.Delay(100); return new List<User> { new User { Id = 1, Name = "张三", Email = "zhangsan@example.com", CreatedDate = DateTime.Now }, new User { Id = 2, Name = "李四", Email = "lisi@example.com", CreatedDate = DateTime.Now } }; } public async Task<bool> SaveUserAsync(User user) { // 实际业务中这里会调用数据库或API await Task.Delay(100); return true; } public async Task<bool> DeleteUserAsync(int userId) { await Task.Delay(100); return true; } } 2.2 ViewModel层实现
ViewModel是MVVM的核心,负责管理状态和命令:
// ViewModel/BaseViewModel.cs public class BaseViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetField<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/UserViewModel.cs public class UserViewModel : BaseViewModel { private readonly IUserService _userService; private List<User> _users; public List<User> Users { get => _users; set => SetField(ref _users, value); } private User _selectedUser; public User SelectedUser { get => _selectedUser; set => SetField(ref _selectedUser, value); } private string _newUserName; public string NewUserName { get => _newUserName; set => SetField(ref _newUserName, value); } private string _newUserEmail; public string NewUserEmail { get => _newUserEmail; set => SetField(ref _newUserEmail, value); } private bool _isLoading; public bool IsLoading { get => _isLoading; set => SetField(ref _isLoading, value); } public ICommand LoadUsersCommand { get; } public ICommand AddUserCommand { get; } public ICommand DeleteUserCommand { get; } public UserViewModel(IUserService userService) { _userService = userService; LoadUsersCommand = new RelayCommand(async () => await LoadUsers()); AddUserCommand = new RelayCommand(async () => await AddUser(), CanAddUser); DeleteUserCommand = new RelayCommand(async () => await DeleteUser(), CanDeleteUser); } private async Task LoadUsers() { IsLoading = true; try { Users = await _userService.GetUsersAsync(); } finally { IsLoading = false; } } private async Task AddUser() { if (string.IsNullOrWhiteSpace(NewUserName) || string.IsNullOrWhiteSpace(NewUserEmail)) return; var newUser = new User { Name = NewUserName, Email = NewUserEmail, CreatedDate = DateTime.Now }; if (await _userService.SaveUserAsync(newUser)) { Users.Add(newUser); NewUserName = string.Empty; NewUserEmail = string.Empty; } } private bool CanAddUser() => !string.IsNullOrWhiteSpace(NewUserName) && !string.IsNullOrWhiteSpace(NewUserEmail); private async Task DeleteUser() { if (SelectedUser == null) return; if (await _userService.DeleteUserAsync(SelectedUser.Id)) { Users.Remove(SelectedUser); SelectedUser = null; } } private bool CanDeleteUser() => SelectedUser != null; } 2.3 View层实现
View层应该保持简洁,主要通过XAML实现:
<!-- View/UserView.xaml --> <UserControl x:Class="MvvmApp.Views.UserView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid Margin="10"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <!-- 添加用户区域 --> <StackPanel Grid.Row="0" Margin="0,0,0,10"> <TextBlock Text="添加新用户" FontSize="16" FontWeight="Bold" Margin="0,0,0,5"/> <StackPanel Orientation="Horizontal" Margin="0,5,0,5"> <TextBox Text="{Binding NewUserName, UpdateSourceTrigger=PropertyChanged}" Width="150" Margin="0,0,5,0" ToolTip="输入用户名"/> <TextBox Text="{Binding NewUserEmail, UpdateSourceTrigger=PropertyChanged}" Width="200" Margin="0,0,5,0" ToolTip="输入邮箱"/> <Button Content="添加" Command="{Binding AddUserCommand}" Width="80" Padding="10,2"/> </StackPanel> </StackPanel> <!-- 用户列表区域 --> <Border Grid.Row="1" BorderBrush="LightGray" BorderThickness="1" CornerRadius="4"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!-- 列表头 --> <Border Grid.Row="0" Background="LightBlue" Padding="5"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50"/> <ColumnDefinition Width="100"/> <ColumnDefinition Width="200"/> <ColumnDefinition Width="120"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="ID" FontWeight="Bold"/> <TextBlock Grid.Column="1" Text="姓名" FontWeight="Bold"/> <TextBlock Grid.Column="2" Text="邮箱" FontWeight="Bold"/> <TextBlock Grid.Column="3" Text="创建时间" FontWeight="Bold"/> </Grid> </Border> <!-- 列表内容 --> <ListView Grid.Row="1" ItemsSource="{Binding Users}" SelectedItem="{Binding SelectedUser}" SelectionMode="Single"> <ListView.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50"/> <ColumnDefinition Width="100"/> <ColumnDefinition Width="200"/> <ColumnDefinition Width="120"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding Id}"/> <TextBlock Grid.Column="1" Text="{Binding Name}"/> <TextBlock Grid.Column="2" Text="{Binding Email}"/> <TextBlock Grid.Column="3" Text="{Binding CreatedDate, StringFormat='yyyy-MM-dd HH:mm'}"/> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </Border> <!-- 操作区域 --> <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0"> <Button Content="刷新" Command="{Binding LoadUsersCommand}" Width="80" Margin="0,0,5,0" Padding="10,2"/> <Button Content="删除" Command="{Binding DeleteUserCommand}" Width="80" Padding="10,2" Background="Red" Foreground="White"/> </StackPanel> <!-- 加载指示器 --> <Border Grid.Row="0" Grid.RowSpan="3" Background="#80FFFFFF" Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}"> <TextBlock Text="加载中..." FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> </Grid> </UserControl> 2.4 命令实现(RelayCommand)
// Commands/RelayCommand.cs public class RelayCommand : ICommand { private readonly Action _execute; private readonly Func<bool> _canExecute; public event EventHandler CanExecuteChanged { add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value; } public RelayCommand(Action execute, Func<bool> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) => _canExecute == null || _canExecute(); public void Execute(object parameter) => _execute(); } // 带参数的通用命令 public class RelayCommand<T> : ICommand { private readonly Action<T> _execute; private readonly Func<T, bool> _canExecute; public event EventHandler CanExecuteChanged { add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value; } public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) => _canExecute == null || _canExecute((T)parameter); public void Execute(object parameter) => _execute((T)parameter); } 三、MVVM框架与工具
3.1 CommunityToolkit.Mvvm(推荐)
微软官方推荐的现代化MVVM工具包:
// 使用CommunityToolkit.Mvvm using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; public partial class UserViewModel : ObservableObject { private readonly IUserService _userService; [ObservableProperty] private List<User> _users; [ObservableProperty] private User _selectedUser; [ObservableProperty] private string _newUserName; [ObservableProperty] private string _newUserEmail; [ObservableProperty] private bool _isLoading; public UserViewModel(IUserService userService) { _userService = userService; } [RelayCommand] private async Task LoadUsers() { IsLoading = true; try { Users = await _userService.GetUsersAsync(); } finally { IsLoading = false; } } [RelayCommand(CanExecute = nameof(CanAddUser))] private async Task AddUser() { var newUser = new User { Name = NewUserName, Email = NewUserEmail, CreatedDate = DateTime.Now }; if (await _userService.SaveUserAsync(newUser)) { Users.Add(newUser); NewUserName = string.Empty; NewUserEmail = string.Empty; } } private bool CanAddUser => !string.IsNullOrWhiteSpace(NewUserName) && !string.IsNullOrWhiteSpace(NewUserEmail); [RelayCommand(CanExecute = nameof(CanDeleteUser))] private async Task DeleteUser() { if (SelectedUser == null) return; if (await _userService.DeleteUserAsync(SelectedUser.Id)) { Users.Remove(SelectedUser); SelectedUser = null; } } private bool CanDeleteUser => SelectedUser != null; } 3.2 Prism框架
Prism是一个功能丰富的MVVM框架,提供了依赖注入、模块化等高级功能:
// 使用Prism using Prism.Mvvm; using Prism.Commands; public class UserViewModel : BindableBase { private readonly IUserService _userService; private List<User> _users; public List<User> Users { get => _users; set => SetProperty(ref _users, value); } private User _selectedUser; public User SelectedUser { get => _selectedUser; set => SetProperty(ref _selectedUser, value); } public DelegateCommand LoadUsersCommand { get; } public DelegateCommand AddUserCommand { get; } public DelegateCommand DeleteUserCommand { get; } public UserViewModel(IUserService userService) { _userService = userService; LoadUsersCommand = new DelegateCommand(async () => await LoadUsers()); AddUserCommand = new DelegateCommand(async () => await AddUser(), CanAddUser) .ObservesProperty(() => NewUserName) .ObservesProperty(() => NewUserEmail); DeleteUserCommand = new DelegateCommand(async () => await DeleteUser(), CanDeleteUser) .ObservesProperty(() => SelectedUser); } private string _newUserName; public string NewUserName { get => _newUserName; set => SetProperty(ref _newUserName, value); } private string _newUserEmail; public string NewUserEmail { get => _newUserEmail; set => SetProperty(ref _newUserEmail, value); } private async Task LoadUsers() { IsLoading = true; try { Users = await _userService.GetUsersAsync(); } finally { IsLoading = false; } } private async Task AddUser() { var newUser = new User { Name = NewUserName, Email = NewUserEmail, CreatedDate = DateTime.Now }; if (await _userService.SaveUserAsync(newUser)) { Users.Add(newUser); NewUserName = string.Empty; NewUserEmail = string.Empty; } } private bool CanAddUser() => !string.IsNullOrWhiteSpace(NewUserName) && !string.IsNullOrWhiteSpace(NewUserEmail); private async Task DeleteUser() { if (SelectedUser == null) return; if (await _userService.DeleteUserAsync(SelectedUser.Id)) { Users.Remove(SelectedUser); SelectedUser = null; } } private bool CanDeleteUser() => SelectedUser != null; private bool _isLoading; public bool IsLoading { get => _isLoading; set => SetProperty(ref _isLoading, value); } } 四、实际应用中的常见问题与解决方案
4.1 问题1:内存泄漏
问题描述:事件订阅未正确取消导致内存泄漏。
解决方案:
// 问题代码 public class ProblematicViewModel : BaseViewModel { private readonly EventAggregator _eventAggregator; public ProblematicViewModel(EventAggregator eventAggregator) { _eventAggregator = eventAggregator; // 订阅事件但未取消 _eventAggregator.GetEvent<UserUpdatedEvent>().Subscribe(OnUserUpdated); } private void OnUserUpdated(User user) { /* 处理逻辑 */ } } // 解决方案1:实现IDisposable public class SafeViewModel : BaseViewModel, IDisposable { private readonly EventAggregator _eventAggregator; private SubscriptionToken _subscriptionToken; public SafeViewModel(EventAggregator eventAggregator) { _eventAggregator = eventAggregator; _subscriptionToken = _eventAggregator.GetEvent<UserUpdatedEvent>().Subscribe(OnUserUpdated); } private void OnUserUpdated(User user) { /* 处理逻辑 */ } public void Dispose() { if (_subscriptionToken != null) { _eventAggregator.GetEvent<UserUpdatedEvent>().Unsubscribe(_subscriptionToken); } } } // 解决方案2:使用弱事件模式 public class WeakEventViewModel : BaseViewModel { private readonly EventAggregator _eventAggregator; public WeakEventViewModel(EventAggregator eventAggregator) { _eventAggregator = eventAggregator; // 使用弱引用订阅 _eventAggregator.GetEvent<UserUpdatedEvent>().Subscribe(OnUserUpdated, ThreadOption.UIThread, false); } private void OnUserUpdated(User user) { /* 处理逻辑 */ } } 4.2 问题2:UI线程阻塞
问题描述:长时间运行的任务阻塞UI线程。
解决方案:
// 问题代码 public class BlockingViewModel : BaseViewModel { public void LoadData() { // 同步调用,阻塞UI线程 var data = _dataService.GetData(); // 假设这是耗时操作 Data = data; } } // 解决方案1:使用async/await public class AsyncViewModel : BaseViewModel { public async Task LoadDataAsync() { IsLoading = true; try { // 异步调用,不阻塞UI线程 var data = await _dataService.GetDataAsync(); Data = data; } finally { IsLoading = false; } } } // 解决方案2:使用Task.Run进行CPU密集型操作 public class CpuIntensiveViewModel : BaseViewModel { public async Task ProcessDataAsync() { IsProcessing = true; try { // 在后台线程执行CPU密集型操作 var result = await Task.Run(() => ProcessDataIntensive()); Result = result; } finally { IsProcessing = false; } } private string ProcessDataIntensive() { // 模拟耗时计算 Thread.Sleep(5000); return "处理完成"; } } 4.3 问题3:命令CanExecute更新不及时
问题描述:当ViewModel属性变化时,命令的CanExecute状态不更新。
解决方案:
// 问题代码 public class ProblematicCommandViewModel : BaseViewModel { private string _name; public string Name { get => _name; set { _name = value; // 没有通知命令更新 } } public ICommand SaveCommand => new RelayCommand(Save, CanSave); private bool CanSave() => !string.IsNullOrWhiteSpace(Name); private void Save() { /* 保存逻辑 */ } } // 解决方案1:手动触发CanExecuteChanged public class Solution1ViewModel : BaseViewModel { private string _name; public string Name { get => _name; set { if (SetField(ref _name, value)) { // 手动触发命令CanExecuteChanged ((RelayCommand)SaveCommand).RaiseCanExecuteChanged(); } } } public ICommand SaveCommand { get; } public Solution1ViewModel() { SaveCommand = new RelayCommand(Save, CanSave); } private bool CanSave() => !string.IsNullOrWhiteSpace(Name); private void Save() { /* 保存逻辑 */ } } // 解决方案2:使用CommunityToolkit.Mvvm的RelayCommand public class Solution2ViewModel : ObservableObject { [ObservableProperty] private string _name; [RelayCommand(CanExecute = nameof(CanSave))] private void Save() { /* 保存逻辑 */ } private bool CanSave => !string.IsNullOrWhiteSpace(Name); } // 解决方案3:使用Prism的DelegateCommand public class Solution3ViewModel : BindableBase { private string _name; public string Name { get => _name; set => SetProperty(ref _name, value); } public DelegateCommand SaveCommand { get; } public Solution3ViewModel() { SaveCommand = new DelegateCommand(Save, CanSave) .ObservesProperty(() => Name); // 自动监听属性变化 } private void Save() { /* 保存逻辑 */ } private bool CanSave() => !string.IsNullOrWhiteSpace(Name); } 4.4 问题4:复杂对象导航属性更新通知
问题描述:当对象的子属性变化时,UI不更新。
解决方案:
// 问题代码 public class OrderViewModel : BaseViewModel { private Order _order; public Order Order { get => _order; set => SetField(ref _order, value); } // 当Order.Customer.Name变化时,UI不会更新 } // 解决方案1:让Model实现INotifyPropertyChanged public class Order : BaseViewModel { private Customer _customer; public Customer Customer { get => _customer; set => SetField(ref _customer, value); } private decimal _totalAmount; public decimal TotalAmount { get => _totalAmount; set => SetField(ref _totalAmount, value); } } public class Customer : BaseViewModel { private string _name; public string Name { get => _name; set => SetField(ref _name, value); } } // 解决方案2:使用ObservableCollection public class OrderListViewModel : BaseViewModel { private ObservableCollection<Order> _orders; public ObservableCollection<Order> Orders { get => _orders; set => SetField(ref _orders, value); } public OrderListViewModel() { // ObservableCollection会自动监听集合内对象的属性变化 _orders = new ObservableCollection<Order>(); } } // 解决方案3:手动刷新UI public class ManualRefreshViewModel : BaseViewModel { private Order _order; public Order Order { get => _order; set { if (_order != null) { _order.PropertyChanged -= OnOrderPropertyChanged; } _order = value; if (_order != null) { _order.PropertyChanged += OnOrderPropertyChanged; } OnPropertyChanged(nameof(Order)); } } private void OnOrderPropertyChanged(object sender, PropertyChangedEventArgs e) { // 通知UI更新相关属性 OnPropertyChanged(nameof(Order)); } } 4.5 问题5:循环引用
问题描述:ViewModel持有View的引用,导致内存泄漏。
解决方案:
// 问题代码 public class ProblematicViewModel : BaseViewModel { private MainWindow _mainWindow; // 直接持有View引用 public ProblematicViewModel(MainWindow mainWindow) { _mainWindow = mainWindow; } } // 解决方案1:使用事件/消息机制 public class GoodViewModel : BaseViewModel { private readonly IEventAggregator _eventAggregator; public GoodViewModel(IEventAggregator eventAggregator) { _eventAggregator = eventAggregator; } public void RequestClose() { // 发送关闭请求消息,而不是直接调用View的方法 _eventAggregator.GetEvent<CloseRequestEvent>().Publish(null); } } // 解决方案2:使用接口抽象 public interface IViewService { void ShowMessage(string message); bool Confirm(string message); } public class ViewModelWithService : BaseViewModel { private readonly IViewService _viewService; public ViewModelWithService(IViewService viewService) { _viewService = viewService; } public void DeleteUser() { if (_viewService.Confirm("确定要删除吗?")) { // 执行删除 } } } 4.6 问题6:异步操作中的竞态条件
问题描述:多个异步操作同时进行,导致状态不一致。
解决方案:
// 问题代码 public class RaceConditionViewModel : BaseViewModel { private int _requestCount = 0; public async Task LoadDataAsync() { _requestCount++; IsLoading = true; var data = await _dataService.GetDataAsync(); // 如果有多个请求,可能显示错误的数据 Data = data; IsLoading = false; } } // 解决方案1:使用CancellationToken public class SafeAsyncViewModel : BaseViewModel { private CancellationTokenSource _cts; public async Task LoadDataAsync() { // 取消之前的请求 _cts?.Cancel(); _cts = new CancellationTokenSource(); IsLoading = true; try { var data = await _dataService.GetDataAsync(_cts.Token); Data = data; } catch (OperationCanceledException) { // 请求被取消,忽略 } finally { if (!_cts.Token.IsCancellationRequested) { IsLoading = false; } } } } // 解决方案2:使用锁或信号量 public class LockedAsyncViewModel : BaseViewModel { private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); public async Task LoadDataAsync() { await _semaphore.WaitAsync(); try { IsLoading = true; var data = await _dataService.GetDataAsync(); Data = data; } finally { IsLoading = false; _semaphore.Release(); } } } 五、最佳实践建议
5.1 项目结构组织
MvvmApp/ ├── Models/ │ ├── User.cs │ ├── Order.cs │ └── Interfaces/ │ └── IUserService.cs ├── ViewModels/ │ ├── BaseViewModel.cs │ ├── UserViewModel.cs │ └── OrderViewModel.cs ├── Views/ │ ├── UserView.xaml │ ├── UserView.xaml.cs │ ├── OrderView.xaml │ └── OrderView.xaml.cs ├── Services/ │ ├── UserService.cs │ └── NavigationService.cs ├── Commands/ │ └── RelayCommand.cs ├── Converters/ │ └── BooleanToVisibilityConverter.cs └── App.xaml 5.2 依赖注入配置
// App.xaml.cs public partial class App : Application { private ServiceProvider _serviceProvider; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var serviceCollection = new ServiceCollection(); ConfigureServices(serviceCollection); _serviceProvider = serviceCollection.BuildServiceProvider(); var mainWindow = _serviceProvider.GetRequiredService<MainWindow>(); mainWindow.Show(); } private void ConfigureServices(IServiceCollection services) { // 注册ViewModels services.AddSingleton<UserViewModel>(); services.AddSingleton<OrderViewModel>(); // 注册Services services.AddSingleton<IUserService, UserService>(); services.AddSingleton<IOrderService, OrderService>(); // 注册Views services.AddTransient<MainWindow>(); services.AddTransient<UserView>(); services.AddTransient<OrderView>(); // 注册其他组件 services.AddSingleton<IEventAggregator, EventAggregator>(); } } 5.3 测试策略
// 单元测试示例 public class UserViewModelTests { [Fact] public async Task LoadUsersCommand_ShouldUpdateUsers() { // Arrange var mockService = new Mock<IUserService>(); var users = new List<User> { new User { Id = 1, Name = "Test" } }; mockService.Setup(s => s.GetUsersAsync()).ReturnsAsync(users); var viewModel = new UserViewModel(mockService.Object); // Act await viewModel.LoadUsersCommand.ExecuteAsync(null); // Assert Assert.Equal(users, viewModel.Users); Assert.False(viewModel.IsLoading); } [Fact] public void AddUserCommand_CanExecute_WhenValidData() { // Arrange var mockService = new Mock<IUserService>(); var viewModel = new UserViewModel(mockService.Object); viewModel.NewUserName = "Test"; viewModel.NewUserEmail = "test@example.com"; // Act & Assert Assert.True(viewModel.AddUserCommand.CanExecute(null)); } } 六、总结
MVVM模式在WPF开发中提供了强大的架构支持,但正确实现需要深入理解其原理和常见陷阱。通过本文的详细解析和代码示例,您应该能够:
- 理解MVVM的核心概念:Model、View、ViewModel的职责分离
- 掌握基础实现:从零开始构建MVVM应用
- 使用现代化工具:CommunityToolkit.Mvvm、Prism等框架
- 解决常见问题:内存泄漏、线程安全、命令更新等
- 遵循最佳实践:项目结构、依赖注入、单元测试
记住,MVVM不是银弹,但它确实为复杂WPF应用提供了可维护、可测试的架构基础。在实际开发中,根据项目需求选择合适的工具和模式,才能发挥其最大价值。# 深入解析C# WPF MVVM架构模式 从基础概念到实际应用中的常见问题与解决方案
引言
在现代桌面应用程序开发中,WPF(Windows Presentation Foundation)作为微软推出的强大UI框架,配合MVVM(Model-View-ViewModel)架构模式,已经成为开发复杂、可维护应用程序的标准选择。本文将深入探讨MVVM模式的核心概念、实现细节以及在实际开发中遇到的常见问题和解决方案。
一、MVVM模式基础概念
1.1 什么是MVVM模式
MVVM(Model-View-ViewModel)是一种软件架构模式,它将应用程序分为三个主要部分:
- Model(模型):代表业务逻辑和数据,不包含任何UI相关的代码
- View(视图):用户界面,负责显示数据和接收用户输入
- ViewModel(视图模型):作为View和Model之间的桥梁,处理UI逻辑和数据转换
1.2 MVVM的核心优势
- 关注点分离:各层职责明确,便于独立开发和测试
- 可测试性:ViewModel可以脱离UI进行单元测试
- 可维护性:代码结构清晰,便于长期维护
- 数据绑定:WPF的数据绑定机制天然支持MVVM
二、MVVM各层详细解析
2.1 Model层实现
Model层应该保持纯净,只包含业务逻辑和数据定义:
// Model/User.cs public class User { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public DateTime CreatedDate { get; set; } } // Model/IUserService.cs public interface IUserService { Task<List<User>> GetUsersAsync(); Task<bool> SaveUserAsync(User user); Task<bool> DeleteUserAsync(int userId); } // Model/UserService.cs public class UserService : IUserService { public async Task<List<User>> GetUsersAsync() { // 模拟数据库调用 await Task.Delay(100); return new List<User> { new User { Id = 1, Name = "张三", Email = "zhangsan@example.com", CreatedDate = DateTime.Now }, new User { Id = 2, Name = "李四", Email = "lisi@example.com", CreatedDate = DateTime.Now } }; } public async Task<bool> SaveUserAsync(User user) { // 实际业务中这里会调用数据库或API await Task.Delay(100); return true; } public async Task<bool> DeleteUserAsync(int userId) { await Task.Delay(100); return true; } } 2.2 ViewModel层实现
ViewModel是MVVM的核心,负责管理状态和命令:
// ViewModel/BaseViewModel.cs public class BaseViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetField<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/UserViewModel.cs public class UserViewModel : BaseViewModel { private readonly IUserService _userService; private List<User> _users; public List<User> Users { get => _users; set => SetField(ref _users, value); } private User _selectedUser; public User SelectedUser { get => _selectedUser; set => SetField(ref _selectedUser, value); } private string _newUserName; public string NewUserName { get => _newUserName; set => SetField(ref _newUserName, value); } private string _newUserEmail; public string NewUserEmail { get => _newUserEmail; set => SetField(ref _newUserEmail, value); } private bool _isLoading; public bool IsLoading { get => _isLoading; set => SetField(ref _isLoading, value); } public ICommand LoadUsersCommand { get; } public ICommand AddUserCommand { get; } public ICommand DeleteUserCommand { get; } public UserViewModel(IUserService userService) { _userService = userService; LoadUsersCommand = new RelayCommand(async () => await LoadUsers()); AddUserCommand = new RelayCommand(async () => await AddUser(), CanAddUser); DeleteUserCommand = new RelayCommand(async () => await DeleteUser(), CanDeleteUser); } private async Task LoadUsers() { IsLoading = true; try { Users = await _userService.GetUsersAsync(); } finally { IsLoading = false; } } private async Task AddUser() { if (string.IsNullOrWhiteSpace(NewUserName) || string.IsNullOrWhiteSpace(NewUserEmail)) return; var newUser = new User { Name = NewUserName, Email = NewUserEmail, CreatedDate = DateTime.Now }; if (await _userService.SaveUserAsync(newUser)) { Users.Add(newUser); NewUserName = string.Empty; NewUserEmail = string.Empty; } } private bool CanAddUser() => !string.IsNullOrWhiteSpace(NewUserName) && !string.IsNullOrWhiteSpace(NewUserEmail); private async Task DeleteUser() { if (SelectedUser == null) return; if (await _userService.DeleteUserAsync(SelectedUser.Id)) { Users.Remove(SelectedUser); SelectedUser = null; } } private bool CanDeleteUser() => SelectedUser != null; } 2.3 View层实现
View层应该保持简洁,主要通过XAML实现:
<!-- View/UserView.xaml --> <UserControl x:Class="MvvmApp.Views.UserView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid Margin="10"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <!-- 添加用户区域 --> <StackPanel Grid.Row="0" Margin="0,0,0,10"> <TextBlock Text="添加新用户" FontSize="16" FontWeight="Bold" Margin="0,0,0,5"/> <StackPanel Orientation="Horizontal" Margin="0,5,0,5"> <TextBox Text="{Binding NewUserName, UpdateSourceTrigger=PropertyChanged}" Width="150" Margin="0,0,5,0" ToolTip="输入用户名"/> <TextBox Text="{Binding NewUserEmail, UpdateSourceTrigger=PropertyChanged}" Width="200" Margin="0,0,5,0" ToolTip="输入邮箱"/> <Button Content="添加" Command="{Binding AddUserCommand}" Width="80" Padding="10,2"/> </StackPanel> </StackPanel> <!-- 用户列表区域 --> <Border Grid.Row="1" BorderBrush="LightGray" BorderThickness="1" CornerRadius="4"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!-- 列表头 --> <Border Grid.Row="0" Background="LightBlue" Padding="5"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50"/> <ColumnDefinition Width="100"/> <ColumnDefinition Width="200"/> <ColumnDefinition Width="120"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="ID" FontWeight="Bold"/> <TextBlock Grid.Column="1" Text="姓名" FontWeight="Bold"/> <TextBlock Grid.Column="2" Text="邮箱" FontWeight="Bold"/> <TextBlock Grid.Column="3" Text="创建时间" FontWeight="Bold"/> </Grid> </Border> <!-- 列表内容 --> <ListView Grid.Row="1" ItemsSource="{Binding Users}" SelectedItem="{Binding SelectedUser}" SelectionMode="Single"> <ListView.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50"/> <ColumnDefinition Width="100"/> <ColumnDefinition Width="200"/> <ColumnDefinition Width="120"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding Id}"/> <TextBlock Grid.Column="1" Text="{Binding Name}"/> <TextBlock Grid.Column="2" Text="{Binding Email}"/> <TextBlock Grid.Column="3" Text="{Binding CreatedDate, StringFormat='yyyy-MM-dd HH:mm'}"/> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </Border> <!-- 操作区域 --> <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0"> <Button Content="刷新" Command="{Binding LoadUsersCommand}" Width="80" Margin="0,0,5,0" Padding="10,2"/> <Button Content="删除" Command="{Binding DeleteUserCommand}" Width="80" Padding="10,2" Background="Red" Foreground="White"/> </StackPanel> <!-- 加载指示器 --> <Border Grid.Row="0" Grid.RowSpan="3" Background="#80FFFFFF" Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}"> <TextBlock Text="加载中..." FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> </Grid> </UserControl> 2.4 命令实现(RelayCommand)
// Commands/RelayCommand.cs public class RelayCommand : ICommand { private readonly Action _execute; private readonly Func<bool> _canExecute; public event EventHandler CanExecuteChanged { add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value; } public RelayCommand(Action execute, Func<bool> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) => _canExecute == null || _canExecute(); public void Execute(object parameter) => _execute(); } // 带参数的通用命令 public class RelayCommand<T> : ICommand { private readonly Action<T> _execute; private readonly Func<T, bool> _canExecute; public event EventHandler CanExecuteChanged { add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value; } public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) => _canExecute == null || _canExecute((T)parameter); public void Execute(object parameter) => _execute((T)parameter); } 三、MVVM框架与工具
3.1 CommunityToolkit.Mvvm(推荐)
微软官方推荐的现代化MVVM工具包:
// 使用CommunityToolkit.Mvvm using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; public partial class UserViewModel : ObservableObject { private readonly IUserService _userService; [ObservableProperty] private List<User> _users; [ObservableProperty] private User _selectedUser; [ObservableProperty] private string _newUserName; [ObservableProperty] private string _newUserEmail; [ObservableProperty] private bool _isLoading; public UserViewModel(IUserService userService) { _userService = userService; } [RelayCommand] private async Task LoadUsers() { IsLoading = true; try { Users = await _userService.GetUsersAsync(); } finally { IsLoading = false; } } [RelayCommand(CanExecute = nameof(CanAddUser))] private async Task AddUser() { var newUser = new User { Name = NewUserName, Email = NewUserEmail, CreatedDate = DateTime.Now }; if (await _userService.SaveUserAsync(newUser)) { Users.Add(newUser); NewUserName = string.Empty; NewUserEmail = string.Empty; } } private bool CanAddUser => !string.IsNullOrWhiteSpace(NewUserName) && !string.IsNullOrWhiteSpace(NewUserEmail); [RelayCommand(CanExecute = nameof(CanDeleteUser))] private async Task DeleteUser() { if (SelectedUser == null) return; if (await _userService.DeleteUserAsync(SelectedUser.Id)) { Users.Remove(SelectedUser); SelectedUser = null; } } private bool CanDeleteUser => SelectedUser != null; } 3.2 Prism框架
Prism是一个功能丰富的MVVM框架,提供了依赖注入、模块化等高级功能:
// 使用Prism using Prism.Mvvm; using Prism.Commands; public class UserViewModel : BindableBase { private readonly IUserService _userService; private List<User> _users; public List<User> Users { get => _users; set => SetProperty(ref _users, value); } private User _selectedUser; public User SelectedUser { get => _selectedUser; set => SetProperty(ref _selectedUser, value); } public DelegateCommand LoadUsersCommand { get; } public DelegateCommand AddUserCommand { get; } public DelegateCommand DeleteUserCommand { get; } public UserViewModel(IUserService userService) { _userService = userService; LoadUsersCommand = new DelegateCommand(async () => await LoadUsers()); AddUserCommand = new DelegateCommand(async () => await AddUser(), CanAddUser) .ObservesProperty(() => NewUserName) .ObservesProperty(() => NewUserEmail); DeleteUserCommand = new DelegateCommand(async () => await DeleteUser(), CanDeleteUser) .ObservesProperty(() => SelectedUser); } private string _newUserName; public string NewUserName { get => _newUserName; set => SetProperty(ref _newUserName, value); } private string _newUserEmail; public string NewUserEmail { get => _newUserEmail; set => SetProperty(ref _newUserEmail, value); } private async Task LoadUsers() { IsLoading = true; try { Users = await _userService.GetUsersAsync(); } finally { IsLoading = false; } } private async Task AddUser() { var newUser = new User { Name = NewUserName, Email = NewUserEmail, CreatedDate = DateTime.Now }; if (await _userService.SaveUserAsync(newUser)) { Users.Add(newUser); NewUserName = string.Empty; NewUserEmail = string.Empty; } } private bool CanAddUser() => !string.IsNullOrWhiteSpace(NewUserName) && !string.IsNullOrWhiteSpace(NewUserEmail); private async Task DeleteUser() { if (SelectedUser == null) return; if (await _userService.DeleteUserAsync(SelectedUser.Id)) { Users.Remove(SelectedUser); SelectedUser = null; } } private bool CanDeleteUser() => SelectedUser != null; private bool _isLoading; public bool IsLoading { get => _isLoading; set => SetProperty(ref _isLoading, value); } } 四、实际应用中的常见问题与解决方案
4.1 问题1:内存泄漏
问题描述:事件订阅未正确取消导致内存泄漏。
解决方案:
// 问题代码 public class ProblematicViewModel : BaseViewModel { private readonly EventAggregator _eventAggregator; public ProblematicViewModel(EventAggregator eventAggregator) { _eventAggregator = eventAggregator; // 订阅事件但未取消 _eventAggregator.GetEvent<UserUpdatedEvent>().Subscribe(OnUserUpdated); } private void OnUserUpdated(User user) { /* 处理逻辑 */ } } // 解决方案1:实现IDisposable public class SafeViewModel : BaseViewModel, IDisposable { private readonly EventAggregator _eventAggregator; private SubscriptionToken _subscriptionToken; public SafeViewModel(EventAggregator eventAggregator) { _eventAggregator = eventAggregator; _subscriptionToken = _eventAggregator.GetEvent<UserUpdatedEvent>().Subscribe(OnUserUpdated); } private void OnUserUpdated(User user) { /* 处理逻辑 */ } public void Dispose() { if (_subscriptionToken != null) { _eventAggregator.GetEvent<UserUpdatedEvent>().Unsubscribe(_subscriptionToken); } } } // 解决方案2:使用弱事件模式 public class WeakEventViewModel : BaseViewModel { private readonly EventAggregator _eventAggregator; public WeakEventViewModel(EventAggregator eventAggregator) { _eventAggregator = eventAggregator; // 使用弱引用订阅 _eventAggregator.GetEvent<UserUpdatedEvent>().Subscribe(OnUserUpdated, ThreadOption.UIThread, false); } private void OnUserUpdated(User user) { /* 处理逻辑 */ } } 4.2 问题2:UI线程阻塞
问题描述:长时间运行的任务阻塞UI线程。
解决方案:
// 问题代码 public class BlockingViewModel : BaseViewModel { public void LoadData() { // 同步调用,阻塞UI线程 var data = _dataService.GetData(); // 假设这是耗时操作 Data = data; } } // 解决方案1:使用async/await public class AsyncViewModel : BaseViewModel { public async Task LoadDataAsync() { IsLoading = true; try { // 异步调用,不阻塞UI线程 var data = await _dataService.GetDataAsync(); Data = data; } finally { IsLoading = false; } } } // 解决方案2:使用Task.Run进行CPU密集型操作 public class CpuIntensiveViewModel : BaseViewModel { public async Task ProcessDataAsync() { IsProcessing = true; try { // 在后台线程执行CPU密集型操作 var result = await Task.Run(() => ProcessDataIntensive()); Result = result; } finally { IsProcessing = false; } } private string ProcessDataIntensive() { // 模拟耗时计算 Thread.Sleep(5000); return "处理完成"; } } 4.3 问题3:命令CanExecute更新不及时
问题描述:当ViewModel属性变化时,命令的CanExecute状态不更新。
解决方案:
// 问题代码 public class ProblematicCommandViewModel : BaseViewModel { private string _name; public string Name { get => _name; set { _name = value; // 没有通知命令更新 } } public ICommand SaveCommand => new RelayCommand(Save, CanSave); private bool CanSave() => !string.IsNullOrWhiteSpace(Name); private void Save() { /* 保存逻辑 */ } } // 解决方案1:手动触发CanExecuteChanged public class Solution1ViewModel : BaseViewModel { private string _name; public string Name { get => _name; set { if (SetField(ref _name, value)) { // 手动触发命令CanExecuteChanged ((RelayCommand)SaveCommand).RaiseCanExecuteChanged(); } } } public ICommand SaveCommand { get; } public Solution1ViewModel() { SaveCommand = new RelayCommand(Save, CanSave); } private bool CanSave() => !string.IsNullOrWhiteSpace(Name); private void Save() { /* 保存逻辑 */ } } // 解决方案2:使用CommunityToolkit.Mvvm的RelayCommand public class Solution2ViewModel : ObservableObject { [ObservableProperty] private string _name; [RelayCommand(CanExecute = nameof(CanSave))] private void Save() { /* 保存逻辑 */ } private bool CanSave => !string.IsNullOrWhiteSpace(Name); } // 解决方案3:使用Prism的DelegateCommand public class Solution3ViewModel : BindableBase { private string _name; public string Name { get => _name; set => SetProperty(ref _name, value); } public DelegateCommand SaveCommand { get; } public Solution3ViewModel() { SaveCommand = new DelegateCommand(Save, CanSave) .ObservesProperty(() => Name); // 自动监听属性变化 } private void Save() { /* 保存逻辑 */ } private bool CanSave() => !string.IsNullOrWhiteSpace(Name); } 4.4 问题4:复杂对象导航属性更新通知
问题描述:当对象的子属性变化时,UI不更新。
解决方案:
// 问题代码 public class OrderViewModel : BaseViewModel { private Order _order; public Order Order { get => _order; set => SetField(ref _order, value); } // 当Order.Customer.Name变化时,UI不会更新 } // 解决方案1:让Model实现INotifyPropertyChanged public class Order : BaseViewModel { private Customer _customer; public Customer Customer { get => _customer; set => SetField(ref _customer, value); } private decimal _totalAmount; public decimal TotalAmount { get => _totalAmount; set => SetField(ref _totalAmount, value); } } public class Customer : BaseViewModel { private string _name; public string Name { get => _name; set => SetField(ref _name, value); } } // 解决方案2:使用ObservableCollection public class OrderListViewModel : BaseViewModel { private ObservableCollection<Order> _orders; public ObservableCollection<Order> Orders { get => _orders; set => SetField(ref _orders, value); } public OrderListViewModel() { // ObservableCollection会自动监听集合内对象的属性变化 _orders = new ObservableCollection<Order>(); } } // 解决方案3:手动刷新UI public class ManualRefreshViewModel : BaseViewModel { private Order _order; public Order Order { get => _order; set { if (_order != null) { _order.PropertyChanged -= OnOrderPropertyChanged; } _order = value; if (_order != null) { _order.PropertyChanged += OnOrderPropertyChanged; } OnPropertyChanged(nameof(Order)); } } private void OnOrderPropertyChanged(object sender, PropertyChangedEventArgs e) { // 通知UI更新相关属性 OnPropertyChanged(nameof(Order)); } } 4.5 问题5:循环引用
问题描述:ViewModel持有View的引用,导致内存泄漏。
解决方案:
// 问题代码 public class ProblematicViewModel : BaseViewModel { private MainWindow _mainWindow; // 直接持有View引用 public ProblematicViewModel(MainWindow mainWindow) { _mainWindow = mainWindow; } } // 解决方案1:使用事件/消息机制 public class GoodViewModel : BaseViewModel { private readonly IEventAggregator _eventAggregator; public GoodViewModel(IEventAggregator eventAggregator) { _eventAggregator = eventAggregator; } public void RequestClose() { // 发送关闭请求消息,而不是直接调用View的方法 _eventAggregator.GetEvent<CloseRequestEvent>().Publish(null); } } // 解决方案2:使用接口抽象 public interface IViewService { void ShowMessage(string message); bool Confirm(string message); } public class ViewModelWithService : BaseViewModel { private readonly IViewService _viewService; public ViewModelWithService(IViewService viewService) { _viewService = viewService; } public void DeleteUser() { if (_viewService.Confirm("确定要删除吗?")) { // 执行删除 } } } 4.6 问题6:异步操作中的竞态条件
问题描述:多个异步操作同时进行,导致状态不一致。
解决方案:
// 问题代码 public class RaceConditionViewModel : BaseViewModel { private int _requestCount = 0; public async Task LoadDataAsync() { _requestCount++; IsLoading = true; var data = await _dataService.GetDataAsync(); // 如果有多个请求,可能显示错误的数据 Data = data; IsLoading = false; } } // 解决方案1:使用CancellationToken public class SafeAsyncViewModel : BaseViewModel { private CancellationTokenSource _cts; public async Task LoadDataAsync() { // 取消之前的请求 _cts?.Cancel(); _cts = new CancellationTokenSource(); IsLoading = true; try { var data = await _dataService.GetDataAsync(_cts.Token); Data = data; } catch (OperationCanceledException) { // 请求被取消,忽略 } finally { if (!_cts.Token.IsCancellationRequested) { IsLoading = false; } } } } // 解决方案2:使用锁或信号量 public class LockedAsyncViewModel : BaseViewModel { private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); public async Task LoadDataAsync() { await _semaphore.WaitAsync(); try { IsLoading = true; var data = await _dataService.GetDataAsync(); Data = data; } finally { IsLoading = false; _semaphore.Release(); } } } 五、最佳实践建议
5.1 项目结构组织
MvvmApp/ ├── Models/ │ ├── User.cs │ ├── Order.cs │ └── Interfaces/ │ └── IUserService.cs ├── ViewModels/ │ ├── BaseViewModel.cs │ ├── UserViewModel.cs │ └── OrderViewModel.cs ├── Views/ │ ├── UserView.xaml │ ├── UserView.xaml.cs │ ├── OrderView.xaml │ └── OrderView.xaml.cs ├── Services/ │ ├── UserService.cs │ └── NavigationService.cs ├── Commands/ │ └── RelayCommand.cs ├── Converters/ │ └── BooleanToVisibilityConverter.cs └── App.xaml 5.2 依赖注入配置
// App.xaml.cs public partial class App : Application { private ServiceProvider _serviceProvider; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var serviceCollection = new ServiceCollection(); ConfigureServices(serviceCollection); _serviceProvider = serviceCollection.BuildServiceProvider(); var mainWindow = _serviceProvider.GetRequiredService<MainWindow>(); mainWindow.Show(); } private void ConfigureServices(IServiceCollection services) { // 注册ViewModels services.AddSingleton<UserViewModel>(); services.AddSingleton<OrderViewModel>(); // 注册Services services.AddSingleton<IUserService, UserService>(); services.AddSingleton<IOrderService, OrderService>(); // 注册Views services.AddTransient<MainWindow>(); services.AddTransient<UserView>(); services.AddTransient<OrderView>(); // 注册其他组件 services.AddSingleton<IEventAggregator, EventAggregator>(); } } 5.3 测试策略
// 单元测试示例 public class UserViewModelTests { [Fact] public async Task LoadUsersCommand_ShouldUpdateUsers() { // Arrange var mockService = new Mock<IUserService>(); var users = new List<User> { new User { Id = 1, Name = "Test" } }; mockService.Setup(s => s.GetUsersAsync()).ReturnsAsync(users); var viewModel = new UserViewModel(mockService.Object); // Act await viewModel.LoadUsersCommand.ExecuteAsync(null); // Assert Assert.Equal(users, viewModel.Users); Assert.False(viewModel.IsLoading); } [Fact] public void AddUserCommand_CanExecute_WhenValidData() { // Arrange var mockService = new Mock<IUserService>(); var viewModel = new UserViewModel(mockService.Object); viewModel.NewUserName = "Test"; viewModel.NewUserEmail = "test@example.com"; // Act & Assert Assert.True(viewModel.AddUserCommand.CanExecute(null)); } } 六、总结
MVVM模式在WPF开发中提供了强大的架构支持,但正确实现需要深入理解其原理和常见陷阱。通过本文的详细解析和代码示例,您应该能够:
- 理解MVVM的核心概念:Model、View、ViewModel的职责分离
- 掌握基础实现:从零开始构建MVVM应用
- 使用现代化工具:CommunityToolkit.Mvvm、Prism等框架
- 解决常见问题:内存泄漏、线程安全、命令更新等
- 遵循最佳实践:项目结构、依赖注入、单元测试
记住,MVVM不是银弹,但它确实为复杂WPF应用提供了可维护、可测试的架构基础。在实际开发中,根据项目需求选择合适的工具和模式,才能发挥其最大价值。
支付宝扫一扫
微信扫一扫