WPF - Comment forcer une commande à réévaluer 'CanExecute' via ses CommandBindings

130

J'ai un Menuoù chacun MenuItemdans la hiérarchie a sa Commandpropriété définie sur un RoutedCommandque j'ai défini. L'associé CommandBindingfournit un rappel pour l'évaluation de CanExecutequi contrôle l'état activé de chacun MenuItem.

Cela fonctionne presque . Les éléments de menu présentent initialement les états activés et désactivés corrects. Cependant, lorsque les données que mon CanExecuterappel utilise changent, j'ai besoin de la commande pour demander à nouveau un résultat de mon rappel afin que ce nouvel état soit reflété dans l'interface utilisateur.

Il ne semble pas y avoir de méthodes publiques sur RoutedCommandou CommandBindingpour cela.

Notez que le rappel est utilisé à nouveau lorsque je clique ou tape dans le contrôle (je suppose qu'il est déclenché en entrée car le survol de la souris ne provoque pas l'actualisation).

Drew Noakes
la source

Réponses:

172

Ce n'est pas le plus joli du livre, mais vous pouvez utiliser le CommandManager pour invalider toutes les liaisons de commandes:

CommandManager.InvalidateRequerySuggested();

Voir plus d'informations sur MSDN

Arcturus
la source
1
Merci, cela a très bien fonctionné. Il y a un léger retard dans l'interface utilisateur, mais cela ne m'inquiète pas trop. De plus, j'ai immédiatement voté pour votre réponse, puis j'ai repris le vote pour voir si cela fonctionnait. Maintenant que cela fonctionne, je ne peux plus réappliquer le vote. Je ne sais pas pourquoi SO a mis en place cette règle.
Drew Noakes
5
J'ai modifié votre réponse afin de réappliquer mon vote. Je n'ai rien changé dans l'édition. Merci encore.
Drew Noakes
J'ai eu le même problème lorsque je changeais le contenu d'une Texbox du code-behind. Si vous le modifiez à la main, cela fonctionnerait. Dans cette application, ils avaient la texbox en cours de modification par un contrôle qui pop-up, et lorsque vous enregistrez le popup, cela change la propriété Texbox.Text. Cela a résolu le problème! Merci @Arcturus
Dzyann
10
Notez simplement une autre réponse ( stackoverflow.com/questions/783104/refresh-wpf-command ) "il doit être appelé sur le fil de l'interface utilisateur"
Samvel Siradeghyan
84

Pour tous ceux qui rencontrent cela plus tard; Si vous utilisez MVVM et Prism, l' DelegateCommandimplémentation de Prism ICommandfournit une .RaiseCanExecuteChanged()méthode pour ce faire.

Codage avec Spike
la source
12
Ce modèle se trouve également dans d'autres bibliothèques MVVM, par exemple MVVM Light.
Peter Lillevold
2
Contrairement à Prism, le code source de MVVM Light v5 indique RaiseCanExecuteChanged() simplement ses appels CommandManager.InvalidateRequerySuggested().
Peter
4
une note latérale à MVVM Light dans WPF, vous devez utiliser l'espace de noms GalaSoft.MvvmLight.CommandWpf puisque GalaSoft.MvvmLight.Command causera des problèmes mvvmlight.net/installing/changes#v5_0_2
fuchs777
((RelayCommand)MyCommand).RaiseCanExecuteChanged();a fonctionné pour moi, en utilisant GalaSoft.MvvmLight.Command - MAIS après le passage à CommandWPF, cela a fonctionné sans avoir besoin d'appeler quoi que ce soit. Merci @ fuchs777
Robin Bennett
1
Et si vous n'utilisez pas de bibliothèque tierce?
Vidar
28

Je ne pouvais pas utiliser CommandManager.InvalidateRequerySuggested();parce que mes performances étaient affectées.

J'ai utilisé la commande de délégation de MVVM Helper , qui ressemble à ci-dessous (je l'ai un peu modifiée pour notre demande). vous devez appeler command.RaiseCanExecuteChanged()depuis 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
la source
3
Juste un FYI j'ai commenté CommandManager.RequerySuggested + = valeur; J'obtenais une évaluation quasi constante / en boucle de mon code CanExecute pour une raison quelconque. Sinon, la solution a fonctionné comme prévu. Merci!
robaudas
16

Si vous avez lancé votre propre classe qui implémente, ICommandvous pouvez perdre une grande partie des mises à jour automatiques de statut, vous obligeant à compter sur une actualisation manuelle plus que nécessaire. Cela peut aussi casser InvalidateRequerySuggested(). Le problème est qu'une ICommandimplémentation simple ne parvient pas à lier la nouvelle commande au CommandManager.

La solution consiste à utiliser ce qui suit:

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

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

De cette façon, les abonnés s'attachent à CommandManagerplutôt qu'à votre classe et peuvent participer correctement aux changements d'état des commandes.

Andrue faire face
la source
2
Simple, direct, et permet aux utilisateurs de contrôler leurs implémentations ICommand.
Akoi Meexx
2

J'ai implémenté une solution pour gérer la dépendance des propriétés sur les commandes, ici le lien https://stackoverflow.com/a/30394333/1716620

grâce à cela, vous finirez par avoir une commande comme celle-ci:

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 }
 );
Pas important
la source
-3

C'est ce qui a fonctionné pour moi: mettez le CanExecute avant la commande dans le XAML.

rmustakos
la source