当前位置: 首页 > news >正文

WPF命令与MVVM模式:打造优雅的应用程序架构

🎮 打造优雅的应用程序架构

    • 1. 🧩 命令系统基础
      • 1.1 🤔 为什么需要命令?
      • 1.2 🏗️ ICommand接口
      • 1.3 🛠️ 实现基本命令
    • 2. 🏛️ MVVM模式详解
      • 2.1 🧱 MVVM三大组件
      • 2.2 🏗️ 创建ViewModel基类
      • 2.3 🎯 典型ViewModel示例
    • 3. 🧩 命令绑定实战
      • 3.1 🎨 View中的命令绑定
      • 3.2 🎭 带参数的命令
    • 4. 🚀 MVVM进阶技巧
      • 4.1 🧩 消息传递
      • 4.2 🏗️ 依赖注入
    • 5. 🧪 测试与调试
      • 5.1 🧪 单元测试ViewModel
      • 5.2 🐞 常见问题解决
    • 6. 🏆 实战:完整的CRUD应用
      • 6.1 📦 数据服务层
      • 6.2 🏗️ 完整ViewModel
    • 7. 🚀 总结与进阶

1. 🧩 命令系统基础

1.1 🤔 为什么需要命令?

在传统事件处理模型中,我们这样处理按钮点击:

private void Button_Click(object sender, RoutedEventArgs e)
{// 处理点击逻辑
}

这种模式存在几个问题:

  • 🚫 业务逻辑与UI代码混杂
  • 🔌 难以实现启用/禁用状态管理
  • 🔄 复用性差

命令系统解决了这些问题!

1.2 🏗️ ICommand接口

WPF命令系统基于ICommand接口:

public interface ICommand
{event EventHandler CanExecuteChanged;bool CanExecute(object parameter);void Execute(object parameter);
}

1.3 🛠️ 实现基本命令

创建一个简单的命令类:

/// <summary>
/// 实现ICommand接口的RelayCommand类,用于将UI操作(如按钮点击)绑定到ViewModel中的方法
/// </summary>
public class RelayCommand : ICommand
{// 私有字段,存储要执行的委托private readonly Action _execute;// 私有字段,存储判断命令是否可以执行的委托(可选)private readonly Func<bool> _canExecute;/// <summary>/// 构造函数/// </summary>/// <param name="execute">要执行的委托方法</param>/// <param name="canExecute">判断命令是否可以执行的委托方法(可选)</param>/// <exception cref="ArgumentNullException">当execute参数为null时抛出</exception>public RelayCommand(Action execute, Func<bool> canExecute = null){// 检查execute参数是否为null,如果是则抛出异常_execute = execute ?? throw new ArgumentNullException(nameof(execute));// 可选地设置canExecute委托_canExecute = canExecute;}/// <summary>/// 当命令的可执行状态可能已更改时触发的事件/// </summary>public event EventHandler CanExecuteChanged;/// <summary>/// 确定此命令是否可以在其当前状态下执行/// </summary>/// <param name="parameter">命令使用的数据(此实现中未使用)</param>/// <returns>如果此命令可以执行,则为true;否则为false</returns>public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;/// <summary>/// 执行命令/// </summary>/// <param name="parameter">命令使用的数据(此实现中未使用)</param>public void Execute(object parameter) => _execute();/// <summary>/// 引发CanExecuteChanged事件,通知命令的可执行状态可能已更改/// </summary>public void RaiseCanExecuteChanged(){// 使用空事件参数触发CanExecuteChanged事件CanExecuteChanged?.Invoke(this, EventArgs.Empty);}
}

2. 🏛️ MVVM模式详解

2.1 🧱 MVVM三大组件

组件职责示例
🎨 ​​View​​ 用户界面展示MainWindow.xaml
📦 ​​ViewModel​​ 业务逻辑和状态MainViewModel.cs
🗃️ ​​Model​​数据模型和业务规则Product.cs

2.2 🏗️ 创建ViewModel基类

public abstract class ViewModelBase : 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;}
}

2.3 🎯 典型ViewModel示例

public class ProductViewModel : ViewModelBase
{private string _searchText;private ObservableCollection<Product> _products;public string SearchText{get => _searchText;set{if (SetField(ref _searchText, value)){SearchCommand.RaiseCanExecuteChanged();}}}public ObservableCollection<Product> Products{get => _products;private set => SetField(ref _products, value);}public RelayCommand SearchCommand { get; }public RelayCommand<Product> DeleteCommand { get; }public ProductViewModel(){Products = new ObservableCollection<Product>(LoadProducts());SearchCommand = new RelayCommand(SearchProducts, () => !string.IsNullOrWhiteSpace(SearchText));DeleteCommand = new RelayCommand<Product>(DeleteProduct, product => product != null);}private void SearchProducts(){var results = _productService.Search(SearchText);Products = new ObservableCollection<Product>(results);}private void DeleteProduct(Product product){Products.Remove(product);_productService.Delete(product.Id);}
}

3. 🧩 命令绑定实战

3.1 🎨 View中的命令绑定

<Window x:Class="WpfApp.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WpfApp"Title="产品管理" Height="450" Width="800"><Grid Margin="10"><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions><!-- 搜索区域 --><StackPanel Orientation="Horizontal" Grid.Row="0"><TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" Width="200" Margin="0,0,10,0"/><Button Content="搜索" Command="{Binding SearchCommand}" Padding="10,0"/></StackPanel><!-- 产品列表 --><ListView Grid.Row="1" ItemsSource="{Binding Products}" SelectedItem="{Binding SelectedProduct}"><ListView.View><GridView><GridViewColumn Header="名称" DisplayMemberBinding="{Binding Name}"/><GridViewColumn Header="价格" DisplayMemberBinding="{Binding Price, StringFormat=C}"/></GridView></ListView.View></ListView><!-- 操作按钮 --><StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right"><Button Content="删除" Command="{Binding DeleteCommand}" CommandParameter="{Binding SelectedProduct}" Margin="0,0,10,0" Padding="15,5"/></StackPanel></Grid>
</Window>

3.2 🎭 带参数的命令

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 bool CanExecute(object parameter) => _canExecute?.Invoke((T)parameter) ?? true;public void Execute(object parameter) => _execute((T)parameter);public event EventHandler CanExecuteChanged;public void RaiseCanExecuteChanged(){CanExecuteChanged?.Invoke(this, EventArgs.Empty);}
}

4. 🚀 MVVM进阶技巧

4.1 🧩 消息传递

使用消息总线解耦ViewModel

/// <summary>
/// 消息传递器,用于在应用程序不同部分之间传递消息(发布-订阅模式)
/// </summary>
public class Messenger
{// 使用静态字典存储消息类型和对应的处理动作列表// Key: 消息类型(Type)// Value: 该类型消息的处理动作列表(List<Action<object>>)private static readonly Dictionary<Type, List<Action<object>>> _actions = new Dictionary<Type, List<Action<object>>>();/// <summary>/// 注册消息处理器/// </summary>/// <typeparam name="T">要处理的消息类型</typeparam>/// <param name="action">处理消息的回调方法</param>public static void Register<T>(Action<T> action){// 获取消息类型var type = typeof(T);// 如果字典中不存在该类型的处理列表,则创建一个新的列表if (!_actions.ContainsKey(type)){_actions[type] = new List<Action<object>>();}// 将强类型回调转换为泛型object类型回调,并添加到处理列表中_actions[type].Add(obj => action((T)obj));}/// <summary>/// 发送消息/// </summary>/// <typeparam name="T">消息类型</typeparam>/// <param name="message">要发送的消息内容</param>public static void Send<T>(T message){// 获取消息类型var type = typeof(T);// 检查是否有该类型的消息处理器if (_actions.ContainsKey(type)){// 遍历所有注册的处理方法并执行foreach (var action in _actions[type]){action(message);}}}
}

4.2 🏗️ 依赖注入

在MVVM中使用DI容器:

public class ViewModelLocator
{private readonly IServiceProvider _serviceProvider;public ViewModelLocator(){var services = new ServiceCollection();// 注册服务services.AddSingleton<IProductService, ProductService>();// 注册ViewModelservices.AddTransient<MainViewModel>();_serviceProvider = services.BuildServiceProvider();}public MainViewModel MainViewModel => _serviceProvider.GetRequiredService<MainViewModel>();
}

在App.xaml中设置:

<Application ...><Application.Resources><local:ViewModelLocator x:Key="Locator"/></Application.Resources>
</Application>

5. 🧪 测试与调试

5.1 🧪 单元测试ViewModel

[TestClass]
public class ProductViewModelTests
{[TestMethod]public void SearchCommand_WhenExecuted_UpdatesProductsList(){// 准备var mockService = new Mock<IProductService>();mockService.Setup(x => x.Search("test")).Returns(new List<Product> { new Product() });var vm = new ProductViewModel(mockService.Object){SearchText = "test"};// 执行vm.SearchCommand.Execute(null);// 断言Assert.AreEqual(1, vm.Products.Count);}
}

5.2 🐞 常见问题解决

​​命令不执行​​:

  • 检查CanExecute是否返回false
  • 确认CommandParameter是否正确绑定
  • 验证DataContext是否设置正确

​​内存泄漏​​

  • 确保注销事件处理程序
  • 使用WeakEventManager处理事件

6. 🏆 实战:完整的CRUD应用

6.1 📦 数据服务层

public interface IProductService
{IEnumerable<Product> GetAll();IEnumerable<Product> Search(string term);void Add(Product product);void Update(Product product);void Delete(int id);
}public class ProductService : IProductService
{private readonly List<Product> _products = new List<Product>();public IEnumerable<Product> GetAll() => _products;public IEnumerable<Product> Search(string term) => _products.Where(p => p.Name.Contains(term));public void Add(Product product) => _products.Add(product);public void Update(Product product){var existing = _products.FirstOrDefault(p => p.Id == product.Id);if (existing != null){existing.Name = product.Name;existing.Price = product.Price;}}public void Delete(int id) => _products.RemoveAll(p => p.Id == id);
}

6.2 🏗️ 完整ViewModel

public class ProductManagementViewModel : ViewModelBase
{private readonly IProductService _productService;private Product _selectedProduct;private string _searchText;public ObservableCollection<Product> Products { get; }public Product SelectedProduct{get => _selectedProduct;set => SetField(ref _selectedProduct, value);}public string SearchText{get => _searchText;set{if (SetField(ref _searchText, value)){SearchCommand.RaiseCanExecuteChanged();}}}public RelayCommand SearchCommand { get; }public RelayCommand AddCommand { get; }public RelayCommand UpdateCommand { get; }public RelayCommand DeleteCommand { get; }public ProductManagementViewModel(IProductService productService){_productService = productService;Products = new ObservableCollection<Product>(_productService.GetAll());SearchCommand = new RelayCommand(SearchProducts, () => !string.IsNullOrWhiteSpace(SearchText));AddCommand = new RelayCommand(AddProduct);UpdateCommand = new RelayCommand(UpdateProduct, () => SelectedProduct != null);DeleteCommand = new RelayCommand(DeleteProduct, () => SelectedProduct != null);}private void SearchProducts(){var results = _productService.Search(SearchText);Products.Clear();foreach (var product in results){Products.Add(product);}}private void AddProduct(){var newProduct = new Product { Name = "新产品", Price = 0 };_productService.Add(newProduct);Products.Add(newProduct);SelectedProduct = newProduct;}private void UpdateProduct(){if (SelectedProduct != null){_productService.Update(SelectedProduct);}}private void DeleteProduct(){if (SelectedProduct != null){_productService.Delete(SelectedProduct.Id);Products.Remove(SelectedProduct);}}
}

7. 🚀 总结与进阶

通过本教程,你已经掌握了:

  • 🎮 WPF命令系统的核心概念
  • 🏛️ MVVM模式的三层架构
  • 🧩 命令绑定和参数传递
  • 🏗️ ViewModel的最佳实践
  • 🧪可测试的应用程序架构

​​进阶学习方向​​:

​​1.框架应用​​:

  • Prism:企业级MVVM框架
  • MVVM Light:轻量级MVVM工具包

​​2.高级主题​​:

  • 路由导航
  • 对话框服务
  • 多语言支持
  • 主题切换

​​3.性能优化​​:

  • 虚拟化列表
  • 异步数据加载
  • 数据分页

MVVM不是银弹,但它是构建可维护、可测试WPF应用程序的强大模式!💪🚀

相关文章:

  • Milvus向量数据库DML操作实战教程
  • uniapp-商城-72-shop(5-商品列表,步进器添加商品到的购物车实现)
  • 动态防御新纪元:AI如何重构DDoS攻防成本格局
  • 【教程】给Apache服务器装上轻量级的防DDoS模块
  • 【自用资源分享】Protocol Buffers 构建脚本: 支持生成 ​C++、Go、Python、Java 的 Protobuf 和 gRPC 代码
  • 计算机视觉---YOLOv3
  • Jenkins实践(8):服务器A通过SSH调用服务器B执行Python自动化脚本
  • Rust编程环境安装
  • 工业控制系统的神经网络:TSN交换机是如何改变自动化通信的?
  • 【Docker】存储卷
  • Rust 学习笔记:迭代器
  • 第十一节:第一部分:正则表达式:应用案例、爬取信息、搜索替换
  • 【Java开发日记】说一说序列化与反序列化中存在的问题
  • Apache DolphinScheduler存储系统详解| AI生成技术文档系列
  • Sql Server TLSv1 协议问题
  • Linux --进度条小程序更新
  • Mysql之用户管理
  • 机器学习-决策树
  • 《数据结构初阶》【番外篇:快速排序的前世今生】
  • 大型三甲医院更换HIS系统全流程分析与经验考察(下)
  • 网站个人备案百度推官/举例说明seo
  • 外贸网站如何做推广/静态网页制作
  • 青岛君哲网站建设公司/2022年关键词排名
  • 石家庄做网站比较好的公司有哪些/杭州seo百度关键词排名推广
  • 网站首页的head标签内/百度搜索数据查询
  • 国内跨境电商网站/seochan是什么意思