一、MVVM模式概述
1.1 什么是MVVM模式
MVVM(Model-View-ViewModel)是一种软件架构模式,由微软WPF团队提出,专门为支持XAML的平台(如WPF、Silverlight、UWP等)设计。它将应用程序分为三个核心组件:
- Model:数据模型和业务逻辑层
- View:用户界面展示层
- ViewModel:连接View和Model的桥梁
1.2 MVVM的核心优势
- 关注点分离:界面逻辑与业务逻辑解耦
- 可测试性:ViewModel不依赖View,便于单元测试
- 可维护性:各组件职责明确,代码更易维护
- 开发协作:设计师和开发者可以并行工作
- 数据绑定:利用WPF强大的数据绑定能力减少样板代码
二、MVVM核心组件详解
2.1 Model层实现
Model代表应用程序的数据模型和业务逻辑:
public class Product : INotifyPropertyChanged
{
private string _name;
private decimal _price;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public decimal Price
{
get => _price;
set
{
_price = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2.2 ViewModel层实现
ViewModel是MVVM的核心,它不直接引用View:
public class ProductViewModel : INotifyPropertyChanged
{
private ObservableCollection<Product> _products;
private Product _selectedProduct;
public ObservableCollection<Product> Products
{
get => _products;
set
{
_products = value;
OnPropertyChanged();
}
}
public Product SelectedProduct
{
get => _selectedProduct;
set
{
_selectedProduct = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsProductSelected));
}
}
public bool IsProductSelected => SelectedProduct != null;
public ICommand AddCommand { get; }
public ICommand DeleteCommand { get; }
public ProductViewModel()
{
Products = new ObservableCollection<Product>
{
new Product { Name = "笔记本", Price = 5999 },
new Product { Name = "手机", Price = 3999 }
};
AddCommand = new RelayCommand(AddProduct);
DeleteCommand = new RelayCommand(DeleteProduct, CanDeleteProduct);
}
private void AddProduct(object parameter)
{
Products.Add(new Product { Name = "新产品", Price = 999 });
}
private void DeleteProduct(object parameter)
{
Products.Remove(SelectedProduct);
}
private bool CanDeleteProduct(object parameter) => IsProductSelected;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2.3 View层实现
View只负责展示和用户交互,不包含业务逻辑:
<Window x:Class="MvvmDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="产品管理" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="产品列表" FontSize="16" Margin="5"/>
<ListView Grid.Row="1" ItemsSource="{Binding Products}"
SelectedItem="{Binding SelectedProduct}">
<ListView.View>
<GridView>
<GridViewColumn Header="名称" DisplayMemberBinding="{Binding Name}" Width="200"/>
<GridViewColumn Header="价格" DisplayMemberBinding="{Binding Price, StringFormat=C}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="添加" Command="{Binding AddCommand}" Margin="5" Padding="10,5"/>
<Button Content="删除" Command="{Binding DeleteCommand}" Margin="5" Padding="10,5"
IsEnabled="{Binding IsProductSelected}"/>
</StackPanel>
</Grid>
</Window>
代码后台只需设置DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ProductViewModel();
}
}
三、MVVM核心机制实现
3.1 RelayCommand实现
ICommand接口是MVVM中命令模式的核心:
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 bool CanExecute(object parameter) => _canExecute?.Invoke(parameter) ?? true;
public void Execute(object parameter) => _execute(parameter);
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested();
}
3.2 INotifyPropertyChanged优化
使用CallerMemberName简化属性通知:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
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;
}
}
使用示例:
public class Product : ObservableObject
{
private string _name;
private decimal _price;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public decimal Price
{
get => _price;
set => SetProperty(ref _price, value);
}
}
3.3 依赖注入整合
现代MVVM应用通常结合依赖注入:
public class Startup
{
public IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
// 注册服务
services.AddSingleton<IProductService, ProductService>();
// 注册ViewModels
services.AddTransient<ProductViewModel>();
// 注册Views
services.AddTransient<MainWindow>();
return services.BuildServiceProvider();
}
}
// App.xaml.cs
public partial class App : Application
{
private readonly IServiceProvider _serviceProvider;
public App()
{
_serviceProvider = new Startup().ConfigureServices();
}
protected override void OnStartup(StartupEventArgs e)
{
var mainWindow = _serviceProvider.GetRequiredService<MainWindow>();
mainWindow.Show();
base.OnStartup(e);
}
}
四、高级MVVM技巧
4.1 消息总线实现
解决ViewModel间通信问题:
public class Messenger
{
private static readonly Dictionary<Type, List<Action<object>>> _actions = new();
public static void Register<T>(Action<T> action) where T : class
{
var type = typeof(T);
if (!_actions.ContainsKey(type))
{
_actions[type] = new List<Action<object>>();
}
_actions[type].Add(o => action(o as T));
}
public static void Send<T>(T message) where T : class
{
var type = typeof(T);
if (!_actions.ContainsKey(type)) return;
foreach (var action in _actions[type])
{
action(message);
}
}
public static void Unregister<T>(Action<T> action) where T : class
{
var type = typeof(T);
if (!_actions.ContainsKey(type)) return;
_actions[type].RemoveAll(a => a.Target == action.Target && a.Method == action.Method);
}
}
使用示例:
// 发送消息
Messenger.Send(new ProductAddedMessage(newProduct));
// 接收消息
Messenger.Register<ProductAddedMessage>(msg =>
{
// 处理新增产品逻辑
});
4.2 验证机制实现
public abstract class ValidatableObject : ObservableObject, INotifyDataErrorInfo
{
private readonly Dictionary<string, List<string>> _errors = new();
public bool HasErrors => _errors.Any();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName)) return _errors.Values;
return _errors.ContainsKey(propertyName) ? _errors[propertyName] : null;
}
protected void AddError(string propertyName, string error)
{
if (!_errors.ContainsKey(propertyName))
{
_errors[propertyName] = new List<string>();
}
if (!_errors[propertyName].Contains(error))
{
_errors[propertyName].Add(error);
OnErrorsChanged(propertyName);
}
}
protected void ClearErrors(string propertyName)
{
if (_errors.Remove(propertyName))
{
OnErrorsChanged(propertyName);
}
}
protected virtual void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
}
使用示例:
public class Product : ValidatableObject
{
private string _name;
public string Name
{
get => _name;
set
{
SetProperty(ref _name, value);
ValidateName();
}
}
private void ValidateName()
{
ClearErrors(nameof(Name));
if (string.IsNullOrWhiteSpace(Name))
{
AddError(nameof(Name), "产品名称不能为空");
}
else if (Name.Length > 50)
{
AddError(nameof(Name), "产品名称不能超过50个字符");
}
}
}
4.3 视图导航服务
public interface INavigationService
{
void NavigateTo<TViewModel>() where TViewModel : class;
void GoBack();
}
public class NavigationService : INavigationService
{
private readonly IServiceProvider _serviceProvider;
private readonly Stack<FrameworkElement> _navigationStack = new();
public NavigationService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void NavigateTo<TViewModel>() where TViewModel : class
{
var viewModel = _serviceProvider.GetRequiredService<TViewModel>();
var viewType = GetViewType(viewModel.GetType());
var view = (FrameworkElement)_serviceProvider.GetRequiredService(viewType);
view.DataContext = viewModel;
if (Application.Current.MainWindow.Content is FrameworkElement currentContent)
{
_navigationStack.Push(currentContent);
}
Application.Current.MainWindow.Content = view;
}
public void GoBack()
{
if (_navigationStack.TryPop(out var previousView))
{
Application.Current.MainWindow.Content = previousView;
}
}
private static Type GetViewType(Type viewModelType)
{
var viewName = viewModelType.FullName.Replace("ViewModel", "View");
return Assembly.GetExecutingAssembly().GetType(viewName);
}
}
五、MVVM框架推荐
5.1 Prism框架
Prism是微软模式与实践团队提供的MVVM框架:
// 安装NuGet包
Install-Package Prism.Unity -Version 8.1.97
// ViewModel基类
public class ProductViewModel : BindableBase
{
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public DelegateCommand SaveCommand { get; }
public ProductViewModel()
{
SaveCommand = new DelegateCommand(ExecuteSave, CanSave)
.ObservesProperty(() => Name);
}
private void ExecuteSave()
{
// 保存逻辑
}
private bool CanSave() => !string.IsNullOrWhiteSpace(Name);
}
5.2 MVVM Light Toolkit
轻量级MVVM框架:
// 安装NuGet包
Install-Package MvvmLightLibsStd10
// ViewModel基类
public class MainViewModel : ViewModelBase
{
private string _welcomeText = "Hello MVVM Light";
public string WelcomeText
{
get => _welcomeText;
set => Set(ref _welcomeText, value);
}
public RelayCommand ClickCommand { get; }
public MainViewModel()
{
ClickCommand = new RelayCommand(() =>
{
WelcomeText = "Button clicked!";
});
}
}
六、MVVM最佳实践
- 保持ViewModel纯净:不包含UI相关代码
- 合理使用依赖注入:管理组件生命周期
- 避免代码重复:使用基类和辅助类
- 适度使用框架:根据项目复杂度选择
- 重视单元测试:ViewModel应易于测试
- 性能优化:
- 对集合使用
ObservableCollection<T>
- 对大量数据考虑虚拟化
- 避免频繁的属性通知
七、完整MVVM示例项目
项目结构:
MvvmDemo/
├── Models/ # 数据模型
│ └── Product.cs
├── ViewModels/ # ViewModel层
│ ├── ViewModelBase.cs
│ └── ProductViewModel.cs
├── Views/ # 视图层
│ └── MainWindow.xaml
├── Services/ # 服务层
│ └── IProductService.cs
├── Commands/ # 命令实现
│ └── RelayCommand.cs
└── App.xaml # 应用入口
IProductService.cs
public interface IProductService
{
IEnumerable<Product> GetAllProducts();
void AddProduct(Product product);
void DeleteProduct(Product product);
}
ProductViewModel.cs
public class ProductViewModel : ViewModelBase
{
private readonly IProductService _productService;
private Product _selectedProduct;
public ObservableCollection<Product> Products { get; }
public Product SelectedProduct
{
get => _selectedProduct;
set => Set(ref _selectedProduct, value);
}
public RelayCommand AddCommand { get; }
public RelayCommand DeleteCommand { get; }
public ProductViewModel(IProductService productService)
{
_productService = productService;
Products = new ObservableCollection<Product>(_productService.GetAllProducts());
AddCommand = new RelayCommand(AddProduct);
DeleteCommand = new RelayCommand(DeleteProduct, () => SelectedProduct != null);
}
private void AddProduct()
{
var newProduct = new Product { Name = "新产品", Price = 999 };
_productService.AddProduct(newProduct);
Products.Add(newProduct);
}
private void DeleteProduct()
{
_productService.DeleteProduct(SelectedProduct);
Products.Remove(SelectedProduct);
}
}
通过以上完整实现,开发者可以构建出结构清晰、易于维护和测试的WPF应用程序。MVVM模式虽然有一定学习曲线,但带来的架构优势在复杂项目中会愈发明显。