WPF - So zwingen Sie einen Befehl, 'CanExecute' über seine CommandBindings neu zu bewerten

130

Ich habe eine Menuwo jeder MenuItemin der Hierarchie seine CommandEigenschaft auf eine von RoutedCommandmir definierte gesetzt hat. Das zugehörige CommandBindingstellt einen Rückruf zur Verfügung, dessen Auswertung CanExecuteden aktivierten Status von jedem steuert MenuItem.

Das funktioniert fast . Die Menüpunkte zeigen zunächst die richtigen aktivierten und deaktivierten Zustände an. Wenn sich jedoch die Daten CanExecuteändern, die mein Rückruf verwendet, muss der Befehl ein Ergebnis aus meinem Rückruf erneut anfordern, damit dieser neue Status in der Benutzeroberfläche angezeigt wird.

Es scheint keine öffentlichen Methoden dafür RoutedCommandoder CommandBindingdafür zu geben.

Beachten Sie, dass der Rückruf erneut verwendet wird, wenn ich auf das Steuerelement klicke oder es eingebe (ich denke, er wird bei der Eingabe ausgelöst, da ein Mouse-Over die Aktualisierung nicht verursacht).

Drew Noakes
quelle

Antworten:

172

Nicht die schönste im Buch, aber Sie können den CommandManager verwenden, um alle Befehlsbindungen ungültig zu machen:

CommandManager.InvalidateRequerySuggested();

Weitere Informationen zu MSDN

Arcturus
quelle
1
Danke, das hat gut funktioniert. Es gibt eine leichte Verzögerung in der Benutzeroberfläche, aber ich mache mir darüber keine allzu großen Sorgen. Außerdem habe ich Ihre Antwort sofort hochgestimmt und dann die Abstimmung zurückgenommen, um zu sehen, ob sie funktioniert hat. Jetzt, da es funktioniert, kann ich die Abstimmung nicht mehr wiederholen. Ich bin mir nicht sicher, warum SO diese Regel eingeführt hat.
Drew Noakes
5
Ich habe Ihre Antwort bearbeitet, um meine Stimme erneut zu beantragen. Ich habe nichts an der Bearbeitung geändert. Danke noch einmal.
Drew Noakes
Ich hatte das gleiche Problem, als ich den Inhalt einer Texbox aus dem Code-Behind heraus änderte. Wenn Sie es von Hand bearbeiten, würde es funktionieren. In dieser App wurde die Texbox von einem Steuerelement bearbeitet, das angezeigt wird. Wenn Sie das Popup speichern, wird die Eigenschaft Texbox.Text geändert. Dies löste das Problem! Danke @Arcturus
Dzyann
10
Beachten Sie einfach aus einer anderen Antwort ( stackoverflow.com/questions/783104/refresh-wpf-command ) "es muss im UI-Thread aufgerufen werden"
Samvel Siradeghyan
84

Für alle, die später darauf stoßen; Wenn Sie MVVM und Prism verwenden, bietet die DelegateCommandImplementierung von Prism ICommandeine .RaiseCanExecuteChanged()Methode, um dies zu tun.

CodingWithSpike
quelle
12
Dieses Muster ist auch in anderen MVVM-Bibliotheken zu finden, z. B. MVVM Light.
Peter Lillevold
2
Im Gegensatz zu Prism zeigt der Quellcode von MVVM Light v5 die RaiseCanExecuteChanged() einfachen Aufrufe an CommandManager.InvalidateRequerySuggested().
Peter
4
Als Randnotiz zu MVVM Light in WPF müssen Sie den Namespace GalaSoft.MvvmLight.CommandWpf verwenden, da GalaSoft.MvvmLight.Command Probleme verursacht. mvvmlight.net/installing/changes#v5_0_2
fuchs777
((RelayCommand)MyCommand).RaiseCanExecuteChanged();hat bei mir mit GalaSoft.MvvmLight.Command funktioniert - ABER nach dem Wechsel zu CommandWPFhat es funktioniert, ohne dass etwas aufgerufen werden musste. Danke @ fuchs777
Robin Bennett
1
Was ist, wenn Sie keine Bibliothek eines Drittanbieters verwenden?
Vidar
28

Ich konnte es nicht benutzen, CommandManager.InvalidateRequerySuggested();weil ich einen Leistungseinbruch bekam.

Ich habe den Delegating-Befehl von MVVM Helper verwendet , der wie folgt aussieht (ich habe ihn für unsere Anforderungen ein wenig optimiert). Sie müssen command.RaiseCanExecuteChanged()von VM anrufen

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
quelle
3
Nur zu Ihrer Information, ich habe CommandManager.RequerySuggested + = value auskommentiert. Ich habe aus irgendeinem Grund eine nahezu konstante / schleifenförmige Auswertung meines CanExecute-Codes erhalten. Ansonsten funktionierte die Lösung wie erwartet. Vielen Dank!
Robaudas
15

Wenn Sie Ihre eigene Klasse gewürfelt haben, die implementiert ICommand , können Sie viele der automatischen Statusaktualisierungen verlieren, Sie sich mehr als nötig auf die manuelle Aktualisierung verlassen müssen. Es kann auch brechen InvalidateRequerySuggested(). Das Problem ist, dass eine einfache ICommandImplementierung den neuen Befehl nicht mit dem verknüpfen kann CommandManager.

Die Lösung besteht darin, Folgendes zu verwenden:

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

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

Auf diese Weise werden Abonnenten an CommandManagerIhre Klasse angehängt und können ordnungsgemäß an Änderungen des Befehlsstatus teilnehmen.

Andrue Cope
quelle
2
Einfach und auf den Punkt gebracht und ermöglicht es den Mitarbeitern, die Kontrolle über ihre ICommand-Implementierungen zu haben.
Akoi Meexx
2

Ich habe eine Lösung implementiert, um die Eigenschaftsabhängigkeit von Befehlen zu behandeln. Hier den Link https://stackoverflow.com/a/30394333/1716620

Dank dessen haben Sie am Ende einen Befehl wie diesen:

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 }
 );
Nicht wichtig
quelle
-3

Folgendes hat bei mir funktioniert: Stellen Sie CanExecute vor den Befehl in der XAML.

rmustakos
quelle