私は、ContextMenu に少なくとも 2 つのバグがあり、さまざまな状況で CanExecute 呼び出しが信頼できないものになっていると判断しました。Command が設定されると、すぐに CanExecute を呼び出します。その後の呼び出しは予測不可能であり、確かに信頼できません。
失敗する正確な条件を追跡し、回避策を探して、一晩中過ごしました。最後に、私はあきらめて、目的のコマンドを起動する Click ハンドラーに切り替えました。
私の問題の 1 つは、ContextMenu の DataContext を変更すると、新しい Command または CommandParameter がバインドされる前に CanExecute が呼び出される可能性があることであると判断しました。
この問題に対する私が知っている最善の解決策は、組み込みのプロパティを使用する代わりに、Command および CommandBinding に独自の添付プロパティを使用することです。
添付の Command プロパティが設定されている場合は、MenuItem の Click イベントと DataContextChanged イベントをサブスクライブし、CommandManager.RequerySuggested もサブスクライブします。
DataContext が変更された場合、RequerySuggested が入った場合、または 2 つの添付プロパティのいずれかが変更された場合、Dispatcher.BeginInvoke を使用してディスパッチャ操作をスケジュールします。これにより、CanExecute() が呼び出され、MenuItem で IsEnabled が更新されます。
Click イベントが発生したら、CanExecute を実行し、合格した場合は Execute() を呼び出します。
使用法は通常の Command および CommandParameter と同じですが、代わりに添付プロパティを使用します。
<Setter Property="my:ContexrMenuFixer.Command" Value="{Binding}" />
<Setter Property="my:ContextMenuFixer.CommandParameter" Value="{Binding Source=... }" />
このソリューションは機能し、ContextMenu の CanExecute 処理のバグに関するすべての問題を回避します。
いつか Microsoft が ContextMenu の問題を修正し、この回避策が不要になることを願っています。Connect に提出する予定の再現ケースがここのどこかにあります。おそらく、私はボールに乗って実際にやるべきです。
RequerySuggested とは何ですか? なぜ使用するのですか?
RequerySuggested メカニズムは、ICommand.CanExecuteChanged を効率的に処理する RoutedCommand の方法です。非 RoutedCommand の世界では、各 ICommand は CanExecuteChanged へのサブスクライバーの独自のリストを持っていますが、RoutedCommand の場合、ICommand.CanExecuteChanged をサブスクライブするすべてのクライアントは、実際には CommandManager.RequerySuggested をサブスクライブします。この単純なモデルは、RoutedCommand の CanExecute が変更される可能性があるときはいつでも、CommandManager.InvalidateRequerySuggested() を呼び出すだけでよいことを意味します。これは、ICommand.CanExecuteChanged を起動するのと同じことを行いますが、すべての RoutedCommand に対して同時にバックグラウンド スレッドで行います。さらに、RequerySuggested 呼び出しは結合されるため、多数の変更が発生した場合でも、CanExecute を 1 回呼び出すだけで済みます。
ICommand.CanExecuteChanged の代わりに CommandManager.RequerySuggested をサブスクライブすることをお勧めする理由は次のとおりです。 CommandManager.RequerySuggested には弱い参照機能が組み込まれており、イベント ハンドラーを設定してもガベージ コレクションを実行できます。ICommand で同じことを行うには、独自の弱参照メカニズムを実装する必要があります。
これの裏返しとして、ICommand.CanExecuteChanged の代わりに CommandManager.RequerySuggested をサブスクライブすると、RoutedCommands の更新のみが取得されるということです。私は RoutedCommands のみを使用しているので、これは私にとっては問題ではありませんが、通常の ICommands を使用する場合は、ICommand.CanExecutedChanged を弱くサブスクライブするという余分な作業を行うことを検討する必要があることを言及しておく必要がありました。これを行う場合、RoutedCommand.add_CanExecutedChanged が既にこれを行っているため、RequerySuggested もサブスクライブする必要はありません。