WPF ICommand CanExecute(): 通过RaiseCanExecuteChanged()手动触发还是使用DispatchTimer自动处理?

3
我正在尝试确定引起ICommand的CanExecute()在UI中得到反映的最佳方法。
我了解到Dispatcher是处理UI绘图的WPF(引擎?),默认情况下,Dispatcher在实例化时以及在活动用户界面(点击UI或键盘输入)时评估ICommands的CanExecute()方法。
很明显,当给定ICommand上的CanExecute()更改时,这是一个问题,但既没有鼠标也没有键盘输入,因此UI不会发生变化以反映ICommand CanExecute()状态的更改。
有两种解决此问题的方法,都包括调用System.Windows.Input.CommandManager.InvalidateRequerySuggested()。
这将指示Dispatcher重新评估每个ICommand的CanExecute(),并相应地更新UI。我也了解到,这可能会有与性能相关的问题,但这只是在有很多ICommands(1000+?)或在其CanExecute()方法中执行不应该执行的工作(例如,网络操作)时才成为问题。
假设一个人有良好构建的CanExecute()方法,并且考虑到解决方案是调用InvalidateRequerySuggested,则以下是我找到的两种方法:
1. 在您的ICommand接口解决方案中实现“RaiseCanExecuteChanged()”方法。
无论是使用DelegateCommand还是RelayCommand(或其他一些实现)继承自ICommand,都可以添加一个“RaiseCanExecuteChanged()”公共方法,该方法只是调用上面的InvalidateRequerySuggested,并且Dispatcher重新评估所有ICommand并相应地更新UI。
使用此方法,应该按以下方式调用它:
Application.Current.Dispatcher.BeginInvoke(
    DispatcherPriority.Normal,
    (System.Action)(() => 
    {
        System.Windows.Input.CommandManager.InvalidateRequerySuggested();
    }));

据我所知,应该使用这个的原因是它告诉Dispatcher在主UI线程上调用InvalidateRequerySuggested()--根据调用"RaiseCanExecuteChanged()"的位置,可能不在UI线程上,因此Dispatcher会尝试更新那个线程(而不是主UI线程),导致控件/ UI无法按预期更新。
2. 在应用程序启动时实现一个DispatcherTimer,并在定时器上运行,定期调用InvalidateRequerySuggested。
此解决方案创建了一个DispatcherTimer(在后台线程上运行)以一定的间隔,然后在该间隔上调用InvalidateRequerySuggested(),刷新ICommands。 DispatcherTimer的性质是在Dispatcher线程(UI线程)上运行,因此上述包装调用是不必要的。
例如,可以将其添加到App.xaml.cs中:
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            System.Windows.Threading.DispatcherTimer dt = new System.Windows.Threading.DispatcherTimer()
            {
                Interval = new TimeSpan(0, 0, 0, 0, 25),
                IsEnabled = true
            };

            dt.Tick += delegate(object sender, EventArgs be)
            {
                System.Windows.Input.CommandManager.InvalidateRequerySuggested();
            };

            dt.Start();

           // ... Other startup logic in your app here.
    }

这个解决方案会导致InvalidateRequerySuggested()在给定的定时器上重新评估(此处每四分之一秒呈现),并根据需要自动更新UI。

问题 / 思考

我喜欢DispatcherTimer自动运行并在设置的时间间隔内重新评估的想法。但是,该间隔必须很小,否则ICommand的CanExecute()更改和UI更新之间的延迟可能对用户产生重大影响。(例如,如果DispatchTimer在10秒间隔上运行,并且应用程序中发生了某些事情导致按钮的CanExecute()更改为False,则可能需要最多10秒才能相应地更新UI。)

我不喜欢这个解决方案,因为它感觉像是在频繁地重新评估 ICommands。但是,以这种方式自动更新UI可以省去手动调用“RaiseCanExecuteChanged()”的需要,并节省样板文件。

INotifyPropertyChanged的工作方式类似于RaiseCanExecuteChanged(),但是使用NotifyPropertyChanged(string propertyName)来仅处理更新指定的属性。但是,可以将null传递给NotifyPropertyChanged,这会导致(Dispatcher?)重新评估所有属性,实际上刷新它们。

  1. 社区认为哪种实现InvalidateRequerySuggested()的方法是最好的?通过RaiseCanExecuteChanged()还是通过DispatcherTimer?
  2. 是否可以消除调用“NotifyPropertyChanged()”的需要,并且在上述DispatcherTimer中不仅调用“InvalidateRequerySuggested()”而且在给定的时间器上同时调用“NotifyPropertyChanged(null)”,以同时刷新INotifyPropertyChanged和ICommands?

假设有一个函数提供了一个布尔值,CanExecute 调用它,那么是什么更新了这个函数? - Contango
@Contango 一个典型的例子是Josh Smith的RelayCommand实现,您可以在此处查看:https://msdn.microsoft.com/en-us/magazine/dd419663.aspx请注意,此实现接受Action和Predicate作为ICommand的CanExecute()和Execute()的参数。 - Justin Shidell
1个回答

0
针对你的问题:“显然,当任何给定ICommand上的CanExecute()更改时,但是没有提供鼠标或键盘输入--因此UI不会更改以反映ICommand CanExecute()状态的更改。”:
通常,您会将按钮的IsEnabled状态绑定到ViewModel中的属性。这意味着您可以手动启用或禁用按钮。 CanExecute()实际上是IsEnabled属性的语法糖。
现在我们可以控制按钮的IsEnabled状态,我们可以在其上构建任何东西:我们可以使用其他按钮来控制其状态,或者订阅RX(Reactive Extensions)流等。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接