我确定ContextMenu中至少有两个错误会导致其CanExecute调用在不同情况下不可靠。它在命令设置时立即调用CanExecute。后来的通话是不可预知的,当然也不可靠。
我花了整整一个晚上,一旦试图追查它会失败并寻找解决方法的确切条件。最后,我放弃了并切换到触发所需命令的Click处理程序。
我确定我的问题之一是更改ContextMenu的DataContext可能导致CanExecute在绑定新的Command或CommandParameter之前被调用。
我知道这个问题是使用自己的附加属性为指挥的CommandBinding而不是使用内置的那些最好的解决办法:
当您连接命令属性设置,订阅单击MenuItem上的DataContextChanged事件,并订阅CommandManager.RequerySuggested。
当DataContext更改,RequerySuggested进来或两个附加属性发生更改时,使用Dispatcher.BeginInvoke调度您的CanExecute()并更新MenuItem上的IsEnabled。
当Click事件触发时,执行CanExecute事件,如果通过,则调用Execute()。
用法就像普通的命令和CommandParameter,但使用附加的属性,而不是:
<Setter Property="my:ContexrMenuFixer.Command" Value="{Binding}" />
<Setter Property="my:ContextMenuFixer.CommandParameter" Value="{Binding Source=... }" />
此解决方案,并绕过所有与文本菜单的CanExecute处理的错误的问题。
希望有一天微软将解决ContextMenu的问题,并且此解决方法将不再需要。我有一个repro案件,坐在这里我打算提交给Connect的地方。也许我应该拿到球并且真正做到。
RequerySuggested是什么,为什么使用它?
RequerySuggested机制是RoutedCommand有效处理ICommand.CanExecuteChanged的方法。在非RoutedCommand世界中,每个ICommand都有自己的CanExecuteChanged订阅者列表,但对于RoutedCommand,任何订阅ICommand.CanExecuteChanged的客户端实际上都会订阅CommandManager.RequerySuggested。这个更简单的模型意味着,只要RoutedCommand的CanExecute可能发生变化,所有必需的就是调用CommandManager.InvalidateRequerySuggested(),它将执行与调用ICommand.CanExecuteChanged相同的操作,但同时在后台线程上执行所有RoutedCommand。另外,RequerySuggested调用被组合在一起,所以如果发生很多变化,CanExecute只需要调用一次。
我建议你订阅CommandManager.RequerySuggested而不是ICommand.CanExecuteChanged的原因是:1.不需要代码就可以删除旧的订阅,并且每次Command附加属性的值发生更改时都会添加一个新订阅,和2. CommandManager.RequerySuggested有一个内置的弱引用特性,允许你设置你的事件处理程序,并且仍然被垃圾收集。用ICommand做同样的事情需要你实现你自己的弱引用机制。
另一方面是,如果您订阅CommandManager.RequerySuggested而不是ICommand.CanExecuteChanged,那么您将仅获取RoutedCommands的更新。我完全使用RoutedCommands,所以这对我来说不是问题,但我应该提到,如果您使用常规ICommands,有时您应该考虑执行弱订阅ICommand.CanExecutedChanged的额外工作。请注意,如果你这样做,你也不需要订阅RequerySuggested,因为RoutedCommand.add_CanExecutedChanged已经为你做了这个。
我有完全相同的问题。我的解决方案是在命令参数后面绑定命令,只需将command参数的setter放在命令的setter之前,并且突然将绑定参数传递给CanExecute的第一个调用。 – Cubinator73 2016-08-30 15:15:13