C# WPF ICommand CanExecute():RaiseCanExecuteChanged()还是通过DispatchTimer自动处理?

C# WPF ICommand CanExecute():RaiseCanExecuteChanged()还是通过DispatchTimer自动处理?,c#,wpf,inotifypropertychanged,dispatcher,icommand,C#,Wpf,Inotifypropertychanged,Dispatcher,Icommand,我正在尝试确定在UI中反映ICommands的CanExecute()的最佳方式 我知道调度程序是处理UI绘图的WPF(引擎?),默认情况下,调度程序在实例化ICommands的CanExecute()方法以及活动用户界面(单击UI或键盘输入)时对其进行评估 显然,当任何给定ICommand上的CanExecute()发生更改,但既不提供鼠标也不提供键盘输入时,这是一个问题,因此UI不会更改以反映ICommand CanExecute()状态的更改 这个问题似乎有两种解决方案,都包括调用Syst

我正在尝试确定在UI中反映ICommands的CanExecute()的最佳方式

我知道调度程序是处理UI绘图的WPF(引擎?),默认情况下,调度程序在实例化ICommands的CanExecute()方法以及活动用户界面(单击UI或键盘输入)时对其进行评估

显然,当任何给定ICommand上的CanExecute()发生更改,但既不提供鼠标也不提供键盘输入时,这是一个问题,因此UI不会更改以反映ICommand CanExecute()状态的更改

这个问题似乎有两种解决方案,都包括调用System.Windows.Input.CommandManager.InvalidateRequestSuggested()。

这指示调度器重新评估每个ICommand的CanExecute(),并相应地更新UI。我也知道这可能会有与性能相关的问题,但只有当一个人有很多icommand(1000+?)或正在CanExecute()方法中执行不应该执行的工作(例如,网络操作)时,这才似乎是一个问题

假设有一个构造良好的CanExecute()方法,并且解决方案是调用InvalidateRequestySuggested,我发现有两种方法可以做到这一点:

  • 在ICommand接口解决方案上实现“RaiseCanExecuteChanged()”方法 无论是使用DelegateCommand还是从ICommand继承的RelayCommand(或其他实现),它们都会添加一个“RaiseCanceTechChanged()”公共方法,该方法只需调用上述InvalidateRequestSuggested,分派器就会重新评估所有ICommand并相应地更新UI

    使用此方法时,可能应通过以下方式调用它:

    Application.Current.Dispatcher.BeginInvoke(
        DispatcherPriority.Normal,
        (System.Action)(() => 
        {
            System.Windows.Input.CommandManager.InvalidateRequerySuggested();
        }));
    
    (据我所知,之所以应该使用它,是因为它告诉调度程序在主UI线程上调用InvalidateRequestSuggested()——并且根据调用“RaiseCanceTechChanged()”的位置,它们可能不在UI线程上,因此调度程序将尝试更新该线程(而不是主UI线程),不会导致控件/UI按预期更新。)

  • 在应用程序开始时实现一个Dispatcher,并在计时器上运行,让它定期调用InvalidateRequestSuggestived
  • 此解决方案以设置的间隔创建Dispatcher(在后台线程上运行),然后在该间隔上调用InvalidateRequestSuggested(),刷新ICommands。Dispatcher的本质是它在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.
        }
    
    此解决方案会导致InvalidateRequestSuggested()在给定计时器上重新计算(此处每四分之一秒显示一次),并根据需要自动更新UI

    问题/想法

    我喜欢调度员自动运行并按设定的时间间隔重新评估的想法。但是,该间隔必须很小,否则ICommand的CanExecute()更改和UI更新之间的延迟可能对用户很重要。(例如,如果DispatchTimer以10秒的间隔运行,并且应用程序中发生了一些事情,导致按钮的CanExecute()更改为False,则在UI相应更新之前可能需要10秒。)

    我不喜欢这个解决方案,因为它感觉像是在重新评估ICommands。但是,通过这种方式自动更新UI,就不需要手动调用“RaiseCanExecuteChanged()”并保存样板文件

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

  • 社区认为实现InvalidateRequestSuggested()的最佳方式是什么?通过RaiseCanExecuteChanged()还是通过Dispatchermer
  • 是否可以消除调用“NotifyPropertyChanged()”的需要,而在上述Dispatchermer中,在给定计时器上不仅调用“InvalidateRequestySuggested()”而且调用“NotifyPropertyChanged(null)”,同时刷新INotifyPropertyChanged和ICommand

  • 在回答您的问题时:“显然,当任何给定ICommand上的CanExecute()发生更改时,这是一个问题,但既不提供鼠标输入也不提供键盘输入,因此UI不会更改以反映ICommand CanExecute()状态的更改。”

    通常,您会将按钮的
    IsEnabled
    状态绑定到ViewModel中的属性。这意味着您可以手动启用或禁用该按钮

    CanExecute()
    IsEnabled
    属性的有效语法糖类

    现在我们可以控制按钮的
    IsEnabled
    状态,我们可以在此基础上构建任何东西:我们可以使用其他按钮控制其状态,或者订阅
    RX(反应式扩展)流等。

    假设有一个函数提供一个布尔值,
    CanExecute
    调用,那么是什么更新了这个函数?@Contango一个典型的例子是Josh Smith的RelayCommand实现,您可以在这里查看:注意此实现如何接受操作和谓词作为ICommand的CanExecute()和Execute()的参数。