WPF - jak wymusić na poleceniu ponowną ocenę „CanExecute” za pośrednictwem jego CommandBindings

130

Mam Menugdzie każdy MenuItemw hierarchii ma swoją Commandwłaściwość ustawioną na RoutedCommandzdefiniowaną przeze mnie. Skojarzony CommandBindingzapewnia wywołanie zwrotne do oceny, CanExecutektóre kontroluje stan włączenia każdego MenuItem.

To prawie działa. Pozycje menu początkowo pojawiają się z prawidłowymi stanami włączenia i wyłączenia. Jednak gdy dane, których CanExecuteużywa moje wywołanie zwrotne, ulegną zmianie, potrzebuję polecenia, aby ponownie zażądać wyniku z mojego wywołania zwrotnego, aby ten nowy stan został odzwierciedlony w interfejsie użytkownika.

Tam nie wydają się być wszelkie metody publiczne na RoutedCommandlub CommandBindingza to.

Zwróć uwagę, że wywołanie zwrotne jest używane ponownie, gdy klikam lub piszę w kontrolce (myślę, że jest wyzwalane przy wprowadzaniu danych, ponieważ najechanie kursorem myszy nie powoduje odświeżenia).

Drew Noakes
źródło

Odpowiedzi:

172

Nie jest to najładniejsze w książce, ale możesz użyć CommandManager do unieważnienia wszystkich poleceń:

CommandManager.InvalidateRequerySuggested();

Zobacz więcej informacji w witrynie MSDN

Arcturus
źródło
1
Dzięki, to działało dobrze. Występuje niewielkie opóźnienie w interfejsie użytkownika, ale nie martwię się o to. Ponadto natychmiast zagłosowałem pozytywnie na twoją odpowiedź, a potem cofnąłem głosowanie, aby sprawdzić, czy zadziałało. Teraz, gdy to działa, nie mogę ponownie zagłosować. Nie jestem pewien, dlaczego SO stosuje tę zasadę.
Drew Noakes
5
Zredagowałem Twoją odpowiedź, aby ponownie oddać mój głos. Nic nie zmieniłem w edycji. Dzięki jeszcze raz.
Drew Noakes
Miałem ten sam problem, gdy zmieniałem zawartość Texboksa zza kodu. Jeśli edytujesz to ręcznie, zadziała. W tej aplikacji mieli edytowaną skrzynkę tekstową przez kontrolkę, która wyskakuje, a po zapisaniu wyskakującego okienka zmieniło to właściwość Texbox.Text. To rozwiązało problem! Dzięki @Arcturus
Dzyann
10
Zwróć uwagę na inną odpowiedź ( stackoverflow.com/questions/783104/refresh-wpf-command ) „należy ją wywołać w wątku interfejsu użytkownika”
Samvel Siradeghyan
83

Dla każdego, kto zetknie się z tym później; Jeśli używasz MVVM i Prism, DelegateCommandimplementacja Prism ICommandzapewnia .RaiseCanExecuteChanged()metodę, aby to zrobić.

CodingWithSpike
źródło
12
Ten wzorzec można również znaleźć w innych bibliotekach MVVM, np. MVVM Light.
Peter Lillevold
2
W przeciwieństwie do Prism, kod źródłowy MVVM Light v5 wskazuje na jego RaiseCanExecuteChanged() proste wywołania CommandManager.InvalidateRequerySuggested().
Peter
4
uwaga dodatkowa do MVVM Light w WPF, musisz użyć przestrzeni nazw GalaSoft.MvvmLight.CommandWpf, ponieważ GalaSoft.MvvmLight.Command spowoduje problemy mvvmlight.net/installing/changes#v5_0_2
fuchs777
((RelayCommand)MyCommand).RaiseCanExecuteChanged();działało dla mnie, używając GalaSoft.MvvmLight.Command - ALE po zmianie na CommandWPFdziałało bez potrzeby dzwonienia do czegokolwiek. Dzięki @ fuchs777
Robin Bennett
1
A co jeśli nie korzystasz z biblioteki innej firmy?
Vidar
28

Nie mogłem użyć, CommandManager.InvalidateRequerySuggested();ponieważ dostałem hit wydajności.

Użyłem polecenia delegowania MVVM Helpera , które wygląda jak poniżej (trochę go poprawiłem dla naszego wymagania). musisz zadzwonić command.RaiseCanExecuteChanged()z 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);
}
Bek Raupov
źródło
3
Tylko wiadomość do Twojej wiadomości Skomentowałem CommandManager.RequerySuggested + = wartość; Z jakiegoś powodu uzyskiwałem niemal stałą / zapętloną ocenę mojego kodu CanExecute. W przeciwnym razie rozwiązanie działało zgodnie z oczekiwaniami. Dzięki!
robaudas
16

Jeśli wyrzuciłeś swoją własną klasę, która implementuje ICommand, możesz stracić wiele automatycznych aktualizacji statusu, zmuszając cię do polegania na ręcznym odświeżaniu bardziej niż powinno. Może też pęknąć InvalidateRequerySuggested(). Problem polega na tym, że prosta ICommandimplementacja nie łączy nowego polecenia z plikiem CommandManager.

Rozwiązaniem jest użycie:

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

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

W ten sposób subskrybenci przyłączają się do CommandManagerTwojej klasy, a nie do Twojej klasy i mogą odpowiednio uczestniczyć w zmianach statusu poleceń.

Andrue Cope
źródło
2
Prosty, na temat i pozwala ludziom mieć kontrolę nad ich implementacjami ICommand.
Akoi Meexx
2

Zaimplementowałem rozwiązanie do obsługi zależności właściwości od poleceń, tutaj link https://stackoverflow.com/a/30394333/1716620

dzięki temu otrzymasz takie polecenie:

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 }
 );
Nieważne
źródło
-3

Oto, co zadziałało dla mnie: umieść CanExecute przed poleceniem w kodzie XAML.

rmustakos
źródło