1. 现象
当使用CanExecute控制Button是否Enable时,有时会出现Button状态没有刷新,除非对UI进行一些操作(例如改变Focus)。
2. 分析
这种情况经常发生在CanExecute的内部条件变了,但UI并没有响应
考虑如下代码
publicclassSomeViewModel
{
privateboolcanDoSomething;
publicICommandDoSomethingCommand { get; privateset; }
privatevoidDoSomething()
{
}
privateboolCanDoSomething()
{
returncanDoSomething;
}
publicSomeViewModel()
{
this.DoSomethingCommand=newRelayCommand(this.DoSomething, this.CanDoSomething);
}
}
|
当canDoSomething变化后,并没人通知UI状态的变化,所以UI没有响应
3. 解决方法一
调用如下方法
CommandManager.InvalidateRequerySuggested(); |
调用时机应该为触发CanExecute变化之后
使用这种方式会引起以下一些问题
a) 此方法只能运行于UI线程
虽然MSDN没有明确说明,但从测试结果来看在后台线程调用上述方法是不起作用的,在调用前请确保代码运行在UI线程。
关于ST中常见的几种后台线程相关问题说明如下:
i. IProgress对象的ProgressChanged事件
依赖于IProgress对象的实现。
Progress对象保证ProgressChanged事件运行在Progress对象创建的线程上
针对ST,一般来说Progress对象是在UI线程上创建的,也就是说其ProgressChanged事件运行在UI线程
SimpleProgress对象的ProgressChanged事件运行在调用Report方法的线程上
针对ST,一般来说Report方法由后台线程调用,也就是说其ProgressChanged事件运行在后台线程。
所以ViewModel层请确保使用Progress对象而不要使用SimpleProgress对象
ii. Task对象的ContinueWith方法
运行在指定的TaskScheduler所指示的线程上,如果没有指定TaskScheduler,则运行在父Task所在的线程上
针对ST,一般来说DeviceService创建的Task对象都运行在后台线程。所以如果想运行在UI线程,请在UI线程获取TaskScheduler并传入ContinueWith方法
b) 此方法引起性能问题
此方法本质上会使所有Command重新检查其CanExecute,从而对性能造成影响
4. 解决方法二
调用对应Command的RaiseCanExecuteChanged方法
此方法未经测试,但可以想象到的问题如下
a) 此方法只能运行于UI线程
理由应该同上,猜测是WPF内部实现的问题
b) 此方法严重依赖于MVVMLight框架的RelayCommand对象
此方法并非ICommand接口提供的方法
5. 结论
推荐使用方法一解决,使用时注意线程问题。
如果性能问题严重考虑使用方法二或者探索其他解决办法