C# MVVM模式实现:从原理到实战的完整指南


一、MVVM模式概述

1.1 什么是MVVM模式

MVVM(Model-View-ViewModel)是一种软件架构模式,由微软WPF团队提出,专门为支持XAML的平台(如WPF、Silverlight、UWP等)设计。它将应用程序分为三个核心组件:

  • Model:数据模型和业务逻辑层
  • View:用户界面展示层
  • ViewModel:连接View和Model的桥梁

1.2 MVVM的核心优势

  1. 关注点分离:界面逻辑与业务逻辑解耦
  2. 可测试性:ViewModel不依赖View,便于单元测试
  3. 可维护性:各组件职责明确,代码更易维护
  4. 开发协作:设计师和开发者可以并行工作
  5. 数据绑定:利用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最佳实践

  1. 保持ViewModel纯净:不包含UI相关代码
  2. 合理使用依赖注入:管理组件生命周期
  3. 避免代码重复:使用基类和辅助类
  4. 适度使用框架:根据项目复杂度选择
  5. 重视单元测试:ViewModel应易于测试
  6. 性能优化
  • 对集合使用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模式虽然有一定学习曲线,但带来的架构优势在复杂项目中会愈发明显。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注