WPF - Как заставить команду переоценить CanExecute через ее CommandBindings

131

У меня есть свойство , в Menuкотором каждый элемент MenuItemиерархии имеет Commandзначение, определенное RoutedCommandмной. Связанный CommandBindingобеспечивает обратный вызов, для оценки CanExecuteкоторого контролируется включенное состояние каждого из них MenuItem.

Это почти работает. Пункты меню изначально имеют правильные включенные и выключенные состояния. Однако когда данные, которые CanExecuteиспользует мой обратный вызов, изменяются, мне нужна команда для повторного запроса результата моего обратного вызова, чтобы это новое состояние было отражено в пользовательском интерфейсе.

Там , кажется , не будет каких - либо общедоступные методы RoutedCommandили CommandBindingдля этого.

Обратите внимание, что обратный вызов используется снова, когда я нажимаю или вводю в элемент управления (я предполагаю, что он запускается при вводе, потому что наведение мыши не вызывает обновления).

Дрю Ноукс
источник

Ответы:

172

Не самое красивое в книге, но вы можете использовать CommandManager, чтобы аннулировать все привязки команд:

CommandManager.InvalidateRequerySuggested();

См. Дополнительную информацию на MSDN

Арктур
источник
1
Спасибо, это сработало отлично. В пользовательском интерфейсе есть небольшая задержка, но меня это не особо беспокоит. Кроме того, я немедленно проголосовал за ваш ответ, а затем снова проголосовал, чтобы проверить, сработал ли он. Теперь, когда это работает, я не могу повторно подать голосование. Не уверен, почему у SO есть это правило.
Дрю Ноукс,
5
Я отредактировал ваш ответ, чтобы повторно подать свой голос. Я ничего не менял в редактировании. Еще раз спасибо.
Дрю Ноукс,
У меня была такая же проблема, когда я менял содержимое Texbox из кода программной части. Если вы отредактируете его вручную, все будет работать. В этом приложении тексбокс редактировался с помощью всплывающего элемента управления, а когда вы сохраняли всплывающее окно, он изменял свойство Texbox.Text. Это решило проблему! Спасибо @Arcturus
Dzyann
10
Просто обратите внимание на другой ответ ( stackoverflow.com/questions/783104/refresh-wpf-command ) «он должен вызываться в потоке пользовательского интерфейса»
Самвел Сирадегян
84

Для тех, кто столкнется с этим позже; Если вы используете MVVM и Prism, DelegateCommandреализация Prism ICommandпредоставляет .RaiseCanExecuteChanged()способ для этого.

CodingWithSpike
источник
12
Этот шаблон можно найти и в других библиотеках MVVM, например MVVM Light.
Питер Лиллеволд,
2
В отличие от Prism, исходный код MVVM Light v5 указывает на RaiseCanExecuteChanged() простые вызовы CommandManager.InvalidateRequerySuggested().
Питер
4
примечание к MVVM Light в WPF, вам необходимо использовать пространство имен GalaSoft.MvvmLight.CommandWpf, так как GalaSoft.MvvmLight.Command вызовет проблемы mvvmlight.net/installing/changes#v5_0_2
fuchs777
((RelayCommand)MyCommand).RaiseCanExecuteChanged();работал у меня, используя GalaSoft.MvvmLight.Command - НО после перехода на CommandWPFон работал без необходимости что-либо вызывать. Спасибо @ fuchs777
Робин Беннетт
1
Что делать, если вы не используете стороннюю библиотеку?
Vidar
28

Я не мог использовать, CommandManager.InvalidateRequerySuggested();потому что у меня падала производительность.

Я использовал команду делегирования MVVM Helper , которая выглядит, как показано ниже (я немного изменил ее для нашего требования). тебе нужно звонить command.RaiseCanExecuteChanged()с ВМ

public event EventHandler CanExecuteChanged
{
    add
    {
        _internalCanExecuteChanged += value;
        CommandManager.RequerySuggested += value;
    }
    remove
    {
        _internalCanExecuteChanged -= value;
        CommandManager.RequerySuggested -= value;
    }
}

/// <summary>
/// This method can be used to raise the CanExecuteChanged handler.
/// This will force WPF to re-query the status of this command directly.
/// </summary>
public void RaiseCanExecuteChanged()
{
    if (canExecute != null)
        OnCanExecuteChanged();
}

/// <summary>
/// This method is used to walk the delegate chain and well WPF that
/// our command execution status has changed.
/// </summary>
protected virtual void OnCanExecuteChanged()
{
    EventHandler eCanExecuteChanged = _internalCanExecuteChanged;
    if (eCanExecuteChanged != null)
        eCanExecuteChanged(this, EventArgs.Empty);
}
Бек Раупов
источник
3
Просто к вашему сведению, я закомментировал CommandManager.RequerySuggested + = value; По какой-то причине я получал почти постоянную / циклическую оценку моего кода CanExecute. В противном случае решение сработало, как ожидалось. Спасибо!
robaudas
16

Если у вас есть собственный класс, который реализует, ICommandвы можете потерять много автоматических обновлений статуса, вынуждая вас полагаться на ручное обновление больше, чем необходимо. Также может сломаться InvalidateRequerySuggested(). Проблема в том, что простая ICommandреализация не может связать новую команду с CommandManager.

Решение - использовать следующее:

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void RaiseCanExecuteChanged()
    {
        CommandManager.InvalidateRequerySuggested();
    }

Таким образом, подписчики присоединяются к CommandManagerвашему классу, а не к вашему, и могут правильно участвовать в изменении статуса команды.

Эндрю Коуп
источник
2
Прямолинейно, по делу и позволяет людям контролировать свои реализации ICommand.
Akoi Meexx
2

Я реализовал решение для обработки зависимости свойств от команд, здесь ссылка https://stackoverflow.com/a/30394333/1716620

благодаря этому у вас будет такая команда:

this.SaveCommand = new MyDelegateCommand<MyViewModel>(this,
    //execute
    () => {
      Console.Write("EXECUTED");
    },
    //can execute
    () => {
      Console.Write("Checking Validity");
       return PropertyX!=null && PropertyY!=null && PropertyY.Length < 5;
    },
    //properties to watch
    (p) => new { p.PropertyX, p.PropertyY }
 );
Не важный
источник
-3

Вот что у меня сработало: поместите CanExecute перед командой в XAML.

rmustakos
источник