Ustaliłem, że są co najmniej dwa błędy w ContextMenu, które powodują, że jego wywołania CanExecute mogą być niewiarygodne w różnych okolicznościach. Wywołuje CanExecute natychmiast po ustawieniu polecenia. Późniejsze rozmowy są nieprzewidywalne i na pewno nie są wiarygodne.
Spędziłem całą noc, próbując wyśledzić dokładne warunki, w których zawiedzie i szuka obejścia. W końcu zrezygnowałem i przełączyłem się na programy obsługi kliknięć, które uruchamiały żądane polecenia.
Zrobiłem, że jednym z moich problemów było to, że zmiana DataContext w ContextMenu może spowodować wywołanie CanExecute przed związaniem nowego Command lub CommandParameter.
Najlepszym rozwiązaniem znam tego problemu jest wykorzystanie własnych dołączonych właściwości Command and CommandBinding zamiast przy użyciu wbudowanych w te:
Gdy załączony właściwość poleceń jest ustawiony, zapisz się do Kliknij zdarzenia DataContextChanged w menuItem, a także zasubskrybuj CommandManager.RequerySuggested.
Po zmianie DataContext, RequerySuggested przychodzi, czy też swoich dwóch dołączonych właściwości zmian, zaplanować pracę dyspozytora za pomocą Dispatcher.BeginInvoke że wezwie swój CanExecute() i aktualizować IsEnabled na MenuItem.
Po uruchomieniu zdarzenia Click, wykonaj czynność CanExecute, a jeśli się uda, wywołaj Execute().
Wykorzystanie jest jak regularny Command and CommandParameter, ale przy użyciu załączonego właściwości zamiast:
<Setter Property="my:ContexrMenuFixer.Command" Value="{Binding}" />
<Setter Property="my:ContextMenuFixer.CommandParameter" Value="{Binding Source=... }" />
To rozwiązanie działa i omija wszelkie problemy z błędami w obsłudze contextMenu za CanExecute.
Mam nadzieję, że pewnego dnia Microsoft rozwiąże problemy z ContextMenu i to obejście nie będzie już konieczne. Mam sprawę repro siedzącą gdzieś tutaj, którą zamierzam złożyć w Connect. Być może powinienem dostać piłkę i faktycznie to zrobić.
Co to jest RequerySuggested i dlaczego go używać?
Mechanizm RequerySuggested to metoda RoutedCommand do sprawnej obsługi ICommand.CanExecuteChanged.W świecie innym niż RoutedCommand, każda ICommand ma własną listę subskrybentów CanExecuteChanged, ale dla RoutedCommand każdy klient subskrybujący ICommand.CanExecuteChanged będzie faktycznie subskrybował CommandManager.RequerySuggested. Ten prostszy model oznacza, że za każdym razem, gdy CanExecute RoutedCommand może się zmienić, wystarczy wywołać CommandManager.InvalidateRequerySuggested(), co spowoduje to samo, co uruchomienie ICommand.CanExecuteChanged, ale zrobi to dla wszystkich RoutedCommands jednocześnie i wątku tła. Ponadto wywołania RequerySuggested są łączone razem, więc jeśli pojawi się wiele zmian, CanExecute musi zostać wywołane tylko jeden raz.
Powody, dla których poleciłem, aby zasubskrybować CommandManager.RequerySuggested zamiast ICommand.CanExecuteChanged, to: 1. Nie potrzebujesz kodu do usuwania starej subskrypcji i dodawania nowej za każdym razem, gdy zmieni się wartość Twojej właściwości Załączonej komendy i 2. CommandManager.RequerySuggested ma wbudowaną funkcję słabej referencji, która pozwala ustawić obsługę zdarzeń i nadal być zbierane śmieci. Postępując tak samo z ICommand, musisz wdrożyć swój własny słaby mechanizm referencyjny.
Z drugiej strony, jeśli subskrybujesz CommandManager.RequerySuggested zamiast ICommand.CanExecuteChanged, otrzymasz aktualizacje tylko dla RoutedCommands. Korzystam wyłącznie z RoutedCommands, więc nie jest to dla mnie problemem, ale powinienem był wspomnieć, że jeśli korzystasz z regularnych poleceń ICommands, powinieneś rozważyć wykonanie dodatkowej pracy polegającej na słabym subskrybowaniu ICommand.CanExecutedChanged. Zauważ, że jeśli to zrobisz, nie musisz także subskrybować RequerySuggested, ponieważ RoutedCommand.add_CanExecutedChanged już to robi.
miałem dokładnie ten sam problem. Moim rozwiązaniem było powiązanie komendy po parametrze komend, po prostu ustawiając parametr polecenia setter przed komendą ustawiającą i nagle związany parametr został przekazany do pierwszego wywołania 'CanExecute'. – Cubinator73