WPF ICommand CanExecute(): RaiseCanExecuteChanged() 还是通过 DispatchTimer 自动处理?
Posted
技术标签:
【中文标题】WPF ICommand CanExecute(): RaiseCanExecuteChanged() 还是通过 DispatchTimer 自动处理?【英文标题】:WPF ICommand CanExecute(): RaiseCanExecuteChanged() or automatic handling via DispatchTimer? 【发布时间】:2015-09-13 17:30:36 【问题描述】:我正在尝试确定使 ICommands 的 CanExecute() 反映在 UI 中的最佳方式。
我了解 Dispatcher 是处理 UI 绘制的 WPF(引擎?),默认情况下,Dispatcher 在实例化时评估 ICommands 的 CanExecute() 方法以及活动用户界面(点击 UI 或键盘输入。)
显然,当任何给定 ICommand 上的 CanExecute() 发生更改,但未提供鼠标或键盘输入时,这是一个问题 - 因此 UI 不会更改以反映 ICommand CanExecute() 状态的更改。
这个问题似乎有两种解决方案,都包括调用 System.Windows.Input.CommandManager.InvalidateRequerySuggested()。
这指示 Dispatcher 重新评估每个 ICommand 的 CanExecute() 并相应地更新 UI。我也知道这可能会出现与性能相关的问题,但这似乎只是一个问题,如果一个人有很多 ICommands(1000+?)或者在他们的 CanExecute() 方法中执行他们不应该的工作(对于例如,网络操作。)
假设一个具有良好构造的 CanExecute() 方法,并且鉴于解决方案是调用 InvalidateRequerySuggested,我发现有两种方法可以这样做:
-
在您的 ICommand 接口解决方案上实现“RaiseCanExecuteChanged()”方法。
无论是使用继承自 ICommand 的 DelegateCommand 还是 RelayCommand(或其他一些实现),它们都会添加一个“RaiseCanExecuteChanged()”公共方法,该方法只是调用上述 InvalidateRequerySuggested,并且调度程序会重新评估所有 ICommand 并相应地更新 UI .
使用此方法,可能应该通过以下方式调用它:
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(System.Action)(() =>
System.Windows.Input.CommandManager.InvalidateRequerySuggested();
));
(据我所知,应该使用它的原因是因为它告诉 Dispatcher 在主 UI 线程上调用 InvalidateRequerySuggested() - 并且取决于调用“RaiseCanExecuteChanged()”的位置,它们可能不在 UI 线程上,因此 Dispatcher 会尝试更新该线程(而不是主 UI 线程),不会导致控件/UI 按预期更新。)
-
在应用程序启动时实现 DispatcherTimer,并在计时器上运行,让它定期调用 InvalidateRequerySuggested。
此解决方案以设定的时间间隔创建 DispatcherTimer(在后台线程上运行),然后在该时间间隔调用 InvalidateRequerySuggested(),刷新 ICommand。 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,则 UI 可能需要最多 10 秒才能相应地更新。)
我不喜欢这个解决方案,因为它感觉就像是在重新评估 ICommands 很多。 但是,以这种方式自动更新 UI 消除了手动调用“RaiseCanExecuteChanged()”的需要,并节省了样板文件。
INotifyPropertyChanged 的工作方式与 RaiseCanExecuteChanged() 类似,但它使用 NotifyPropertyChanged(string propertyName) 来处理仅更新指定的属性。但是,可以将 null 传递给 NotifyPropertyChanged,这会导致(Dispatcher?)重新评估 all 属性,实际上是刷新它们。
-
社区认为实现 InvalidateRequerySuggested() 的最佳方式是什么?通过 RaiseCanExecuteChanged() 还是通过 DispatcherTimer?
是否可以消除调用“NotifyPropertyChanged()”的需要,而在上面的 DispatcherTimer 中不仅调用“InvalidateRequerySuggested()”而且在给定计时器上调用“NotifyPropertyChanged(null)”,同时刷新 INotifyPropertyChanged 和 ICommands时间?
【问题讨论】:
假设有一个函数提供了CanExecute
调用的布尔值,那么这个函数的更新是什么?
@Contango 一个典型的例子是 Josh Smith 的 RelayCommand 实现,你可以在这里查看:msdn.microsoft.com/en-us/magazine/dd419663.aspx 注意这个实现如何接受一个 Action 和一个 Predicate 作为 ICommand 的 CanExecute() 和 Execute() 的参数.
【参考方案1】:
在回答您的问题时:“显然,当任何给定 ICommand 上的 CanExecute() 发生更改时,这是一个问题,但未提供鼠标或键盘输入 - 因此 UI 不会更改以反映 ICommand CanExecute 中的更改() 状态。”:
通常,您会将按钮的IsEnabled
状态绑定到 ViewModel 中的属性。这意味着您可以手动启用或禁用该按钮。
CanExecute()
是IsEnabled
属性的有效语法糖。
现在我们可以控制按钮的IsEnabled
状态,我们可以在此之上构建任何东西:我们可以使用其他按钮控制它的状态,或者订阅
RX(Reactive Extensions)流等
【讨论】:
以上是关于WPF ICommand CanExecute(): RaiseCanExecuteChanged() 还是通过 DispatchTimer 自动处理?的主要内容,如果未能解决你的问题,请参考以下文章