web-dev-qa-db-fra.com

ICommand CanExecute ne se déclenche pas après PropertyChanged?

J'ai eu une application WPF qui montre un bouton lié à une commande comme ça:

<Button Command="{Binding Path=TestrunStartCommand}" Content="GO!">

La commande est définie comme ça:

public ICommand TestrunStartCommand
{
    get { return new RelayCommand(TestrunStartExecute, () => !IsTestrunInProgress); }
}

public bool IsTestrunInProgress
{
    get{
        return _isTestrunInProgress;
    }
    set{
        _isTestrunInProgress = value;
        RaisePropertyChanged(IsTestrunInProgressPropertyName);
    }
}   

Le problème est que le bouton ne sera pas activé immédiatement après avoir défini IsTestrunInProgress sur false, mais uniquement après avoir cliqué dans la fenêtre de l'application.

Pourriez-vous m'aider à comprendre ce comportement et me montrer comment y remédier?

Pour en savoir plus: le modèle de commande wpf - quand est-il interrogé peut-il être exécuté

37
nabulke

L'interface ICommand expose un événement ICommand.CanExecuteChanged qui est utilisé pour indiquer à l'interface utilisateur quand déterminer à nouveau l'état IsEnabled des composants d'interface utilisateur pilotés par commande.

Selon l'implémentation du RelayCommand que vous utilisez, vous devrez peut-être déclencher cet événement; De nombreuses implémentations exposent une méthode telle que RelayCommand.RaiseCanExecuteChanged() que vous pouvez invoquer pour forcer l'interface utilisateur à se rafraîchir.

Certaines implémentations de RelayCommand utilisent CommandManager.RequerySuggested , auquel cas vous devrez appeler CommandManager.InvalidateRequerySuggested() pour forcer l'interface utilisateur à se rafraîchir.

Pour faire court, vous devrez appeler l'une de ces méthodes depuis votre configurateur de propriétés.

Mise à jour

Comme l'état du bouton est déterminé lorsque le focus actif change, je pense que le CommandManager est utilisé. Donc, dans le setter de votre propriété, après avoir assigné le champ de support, appelez CommandManager.InvalidateRequerySuggested() .

Mise à jour 2

L'implémentation RelayCommand provient de la boîte à outils légère MVVM. Lorsqu'elle est consommée à partir de WPF/.NET, l'implémentation encapsule les méthodes et événements exposés à partir de CommandManager. Cela signifie que ces commandes fonctionnent automatiquement dans la majorité des situations (lorsque l'interface utilisateur est modifiée ou l'élément ciblé est modifié). Mais dans certains cas, comme celui-ci, vous devrez forcer manuellement la commande à réexécuter la requête. La façon appropriée de le faire à l'aide de cette bibliothèque serait d'appeler la méthode RaiseCanExecuteChanged() sur le RelayCommand.

48
Lukazoid

C'est tellement important et facile à manquer, je répète ce que @Samir a dit dans un commentaire. M. Laurent Bugnion a écrit dans son blog :

Cependant, dans WPF 4 et WPF 4.5, il y a un problème: le CommandManager cessera de fonctionner après la mise à niveau de MVVM Light vers V5. Ce que vous observerez, c'est que vos éléments d'interface utilisateur (boutons, etc.) cesseront d'être désactivés/activés lorsque le délégué CanExecute de RelayCommand renvoie false.

Si vous êtes pressé, voici la solution: dans toute classe qui utilise RelayCommand, remplacez la ligne disant:

using GalaSoft.MvvmLight.Command;

avec:

using GalaSoft.MvvmLight.CommandWpf;
47
Heliac

Vous pouvez essayer avec CommandManager.InvalidateRequerySuggested .

De toute façon, cela ne m'a pas aidé parfois dans le passé. Pour moi, la meilleure solution s'est avérée être de lier la propriété booléenne au Button.IsEnabled propriété de dépendance.

Dans votre cas, quelque chose comme

IsEnabled={Binding IsTestrunInProgress}
4
Klaus78

Le problème est que la propriété ICommand TestrunStartCommand renvoie toujours un nouvel objet de commande à chaque accès.

Une solution simple consiste à créer une fois l'objet ICommand et à l'utiliser à plusieurs reprises.

private ICommand _testRunCommand = null;
public ICommand TestrunStartCommand
{
    get 
    { 
        return _testRunCommand ?? (_testRunCommand = new RelayCommand(TestrunStartExecute, () => !IsTestrunInProgress)); 
    }
}

C'était une solution assez simple et cela a fonctionné pour moi.

1
Amit Kumar