You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

MVVMLight RelayCommand.RaiseCanExecuteChanged未触发CanExecuteChanged事件

解决MVVMLight RelayCommand在单元测试中CanExecuteChanged事件不触发的问题

嘿,我之前做WPF单元测试时也碰到过一模一样的问题,太懂这种困惑了!让我给你拆解下原因和解决办法:

问题根源

MVVMLight的RelayCommand(包括泛型版本RelayCommand<T>)默认是依赖WPF的CommandManager.RequerySuggested事件来实现CanExecuteChanged的。具体来说:

  • 当你调用RaiseCanExecuteChanged()时,它并没有直接触发你订阅的事件处理器,而是调用CommandManager.InvalidateRequerySuggested()
  • 在WPF运行时,CommandManager会自动监听UI交互(比如按钮点击、输入框内容变化),并主动重新查询命令的CanExecute状态,所以你的按钮能正常启用/禁用。
  • 但在单元测试的无UI环境下,CommandManager的自动机制不会生效,所以你订阅的CanExecuteChanged事件永远不会被触发。

解决办法

有两种可行的方式,根据你的测试需求选择:

1. 创建RelayCommand时关闭CommandManager依赖

使用RelayCommand的重载构造函数,传入useCommandManager: false参数,这样命令会维护自己的事件订阅列表,调用RaiseCanExecuteChanged()时会直接触发你订阅的事件:

// 原来的代码
// public RelayCommand MyCommand { get; } = new RelayCommand(ExecuteMyCommand, CanExecuteMyCommand);

// 修改后的代码
public RelayCommand MyCommand { get; } = new RelayCommand(ExecuteMyCommand, CanExecuteMyCommand, useCommandManager: false);

这样在单元测试里订阅MyCommand.CanExecuteChanged后,调用MyCommand.RaiseCanExecuteChanged()就能触发你的事件处理器了。

2. 手动触发CommandManager的RequerySuggested事件(不修改命令代码)

如果你不想修改原命令的实现,可以在单元测试中手动触发CommandManager.RequerySuggested事件。不过这个方式需要反射,因为该事件的触发方法是内部的:

// 在单元测试中调用RaiseCanExecuteChanged后,手动触发RequerySuggested
typeof(CommandManager).GetMethod("RequerySuggested", BindingFlags.NonPublic | BindingFlags.Static)?.Invoke(null, null);

不过这种方式有点hack,不如第一种方式直接可靠。

额外提示

如果你的测试只是想验证命令的CanExecute状态是否正确更新,其实不需要订阅事件,直接调用command.CanExecute(null)来断言结果即可,这样会更简洁:

// 假设你的命令依赖某个布尔属性IsValid
viewModel.IsValid = false;
viewModel.MyCommand.RaiseCanExecuteChanged();
Assert.IsFalse(viewModel.MyCommand.CanExecute(null));

viewModel.IsValid = true;
viewModel.MyCommand.RaiseCanExecuteChanged();
Assert.IsTrue(viewModel.MyCommand.CanExecute(null));

内容的提问来源于stack exchange,提问作者Corabox

火山引擎 最新活动