WPF - Як змусити команду переоцінити 'CanExecute' через його CommandBindings


130

У мене є місце, 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()метод для цього.


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
Що робити, якщо ви не використовуєте сторонню бібліотеку?
Видар

28

Я не міг використати, CommandManager.InvalidateRequerySuggested();тому що я отримував хіт продуктивності.

Я використав команду Delegating MVVM Helper 's Delegating, яка виглядає як нижче (я трохи змінив її для нашого запиту). вам потрібно зателефонувати command.RaiseCanExecuteChanged()з VM

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
Просто FYI я прокоментував CommandManager.RequerySugges + = value; Я чомусь отримував майже постійну / циклічну оцінку свого коду CanExecute. Інакше рішення спрацювало як очікувалося. Дякую!
robaudas

15

Якщо ви прокатали свій власний клас, який реалізує, 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 }
 );

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.