[KnowHow]WPF中Button状态不刷新的问题[Lianz]

 
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.         解决方法二
调用对应CommandRaiseCanExecuteChanged方法
此方法未经测试,但可以想象到的问题如下
a)         此方法只能运行于UI线程
理由应该同上,猜测是WPF内部实现的问题
b)         此方法严重依赖于MVVMLight框架的RelayCommand对象
此方法并非ICommand接口提供的方法
 
5.         结论
推荐使用方法一解决,使用时注意线程问题。
如果性能问题严重考虑使用方法二或者探索其他解决办法
posted @ 2013-03-14 09:05  JunBird  阅读(1479)  评论(0编辑  收藏  举报