ICommand. CanExecute, передаваемый пустой указатель даже при том, что CommandParameter установлен

У меня есть хитрая проблема, где я связываю a ContextMenu к ряду ICommand- производные объекты и установка Command и CommandParameter свойства на каждом MenuItem через стиль:

<ContextMenu
    ItemsSource="{Binding Source={x:Static OrangeNote:Note.MultiCommands}}">
    <ContextMenu.Resources>
        <Style
            TargetType="MenuItem">
            <Setter
                Property="Header"
                Value="{Binding Path=Title}" />
            <Setter
                Property="Command"
                Value="{Binding}" />
            <Setter
                Property="CommandParameter"
                Value="{Binding Source={x:Static OrangeNote:App.Screen}, Path=SelectedNotes}" />
...

Однако, в то время как ICommand.Execute( object ) передается набор выбранных примечаний, как он должен, ICommand.CanExecute( object ) (который называют, когда меню создается), получает переданный пустой указатель. Я проверил, и выбранный набор примечаний правильно инстанцируют, прежде чем вызов выполняется (на самом деле, он присвоил значение в своем объявлении, таким образом, это никогда не null). Я не могу выяснить, почему CanEvaluate становится переданным null.

7
задан devios1 12 June 2010 в 02:17
поделиться

1 ответ

Я определил, что есть как минимум две ошибки в ContextMenu, из-за которых его вызовы CanExecute в различных обстоятельствах становятся ненадежными. Он вызывает CanExecute сразу после установки команды. Дальнейшие звонки непредсказуемы и, конечно, ненадежны.

Однажды я провел целую ночь, пытаясь отследить точные условия, при которых он выйдет из строя, и искал обходной путь. В конце концов я сдался и переключился на обработчики кликов, которые запускали нужные команды.

Я определил, что одна из моих проблем заключалась в том, что изменение DataContext в ContextMenu может вызвать вызов CanExecute до того, как будет привязана новая команда или CommandParameter.

Лучшее решение этой проблемы, которое я знаю, - использовать ваши собственные присоединенные свойства для Command и CommandBinding вместо использования встроенных:

  • Когда ваше присоединенное свойство Command установлено, подпишитесь на события Click и DataContextChanged на MenuItem, а также подпишитесь на CommandManager.RequerySuggested.

  • Когда DataContext изменяется, появляется RequerySuggested или любое из двух ваших прикрепленных свойств изменяется, запланируйте операцию диспетчера с помощью Dispatcher.BeginInvoke, которая вызовет ваш CanExecute () и обновит IsEnabled в MenuItem.

  • При возникновении события Click выполните функцию CanExecute, а если она прошла, вызовите Execute ().

Использование аналогично обычным Command и CommandParameter, но вместо этого используются прикрепленные свойства:

<Setter Property="my:ContexrMenuFixer.Command" Value="{Binding}" />
<Setter Property="my:ContextMenuFixer.CommandParameter" Value="{Binding Source=... }" />

Это решение работает и позволяет обойти все проблемы с ошибками в обработке CanExecute ContextMenu.

Надеюсь, что когда-нибудь Microsoft исправит проблемы с ContextMenu, и в этом обходном пути больше не будет необходимости. У меня где-то есть репродукция, которую я собираюсь отправить в Connect. Возможно, мне стоит взять на себя ответственность и действительно сделать это.

Что такое RequerySuggested и зачем его использовать?

Механизм RequerySuggested - это способ RoutedCommand для эффективной обработки ICommand.CanExecuteChanged.В мире, отличном от RoutedCommand, каждая ICommand имеет свой собственный список подписчиков на CanExecuteChanged, но для RoutedCommand любой клиент, подписывающийся на ICommand.CanExecuteChanged, фактически подписывается на CommandManager.RequerySuggested. Эта более простая модель означает, что каждый раз, когда CanExecute RoutedCommand может измениться, все, что необходимо, - это вызвать 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 уже сделает это за вас.

8
ответ дан 6 December 2019 в 11:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: