74 votes

Commande WPF Refresh

Quelqu'un sait-il comment je peux forcer CanExecute pour être appelé sur une commande personnalisée (Josh Smith's RelayCommand ) ?

Typiquement, CanExecute est appelé à chaque fois qu'une interaction se produit sur l'interface utilisateur. Si je clique sur quelque chose, mes commandes sont mises à jour.

Je suis dans une situation où la condition pour CanExecute est activé/désactivé par une minuterie en coulisses. Parce que ce n'est pas dirigé par l'interaction de l'utilisateur, CanExecute n'est pas appelé avant que l'utilisateur n'interagisse avec l'interface utilisateur. Le résultat final est que mon Button reste activé/désactivé jusqu'à ce que l'utilisateur clique dessus. Après le clic, il est mis à jour correctement. Parfois, le Button apparaît activé, mais lorsque l'utilisateur clique, il devient désactivé au lieu de s'activer.

Comment puis-je forcer une mise à jour dans le code lorsque le timer change la propriété qui affecte CanExecute ? J'ai essayé d'allumer PropertyChanged ( INotifyPropertyChanged ) sur la propriété qui affecte CanExecute mais cela n'a pas aidé.

Exemple XAML :

<Button Content="Button" Command="{Binding Cmd}"/>

Exemple de code derrière :

private ICommand m_cmd;
public ICommand Cmd
{
    if (m_cmd == null)
        m_cmd = new RelayCommand(
            (param) => Process(),
            (param) => EnableButton);

    return m_cmd;
}

// Gets updated from a timer (not direct user interaction)
public bool EnableButton { get; set; }

0 votes

Avez-vous essayé d'élever INotifyPropertyChanged pour la commande ? Il n'est pas nécessaire d'avoir un champ pour la commande, il suffit de renvoyer un nouveau champ à chaque fois. Cette combinaison devrait fonctionner. Ou créez une nouvelle commande uniquement pour le cas où vous avez besoin du forçage.

104voto

Kent Boogaart Points 97432

Appel à System.Windows.Input.CommandManager.InvalidateRequerySuggested() oblige le CommandManager à déclencher l'événement RequerySuggested.

Remarques : Le CommandManager ne prête attention qu'à certaines conditions pour déterminer quand la cible de la commande a changé, comme le changement du focus clavier. Dans les situations où le CommandManager ne détermine pas suffisamment un changement dans les conditions qui font qu'une commande ne peut pas être exécutée, InvalidateRequerySuggested peut être appelé pour forcer le CommandManager à lever l'événement RequerySuggested.

1 votes

Suggérez-vous d'appeler cela à partir d'une classe ViewModel ?

3 votes

Pas nécessairement, car cela pourrait rendre votre classe difficile à tester. Essayez-la, et déplacez-la dans un service si nécessaire. Une autre option est d'ajouter une méthode à RelayCommand qui vous permet d'élever CanExecuteChanged uniquement pour cette commande (CommandManager.InvalidRequerySuggested invalide toutes les commandes, ce qui est un peu exagéré).

25 votes

Intéressant... Cela fonctionne, mais il faut l'appeler sur le thread de l'interface utilisateur. Je ne suis pas surpris.

29voto

SilverSideDown Points 386

Je connaissais CommandManager.InvalidateRequerySuggested() il y a longtemps, et je l'ai utilisé, mais cela ne fonctionnait pas pour moi parfois. J'ai enfin compris pourquoi c'était le cas ! Même si elle ne lance pas d'action comme d'autres, vous DEVEZ l'appeler sur le thread principal.

L'appeler sur un fil d'arrière-plan semble fonctionner, mais laisse parfois l'interface utilisateur désactivée. J'espère vraiment que cela aidera quelqu'un, et lui épargnera les heures que je viens de perdre.

17voto

tridy.net Points 41

Une solution de contournement pour cela est de lier IsEnabled à une propriété :

<Button Content="Button" Command="{Binding Cmd}" IsEnabled="{Binding Path=IsCommandEnabled}"/>

et ensuite implémenter cette propriété dans votre ViewModel. Cela permet également à l'UnitTesting de travailler un peu plus facilement avec les propriétés plutôt qu'avec les commandes pour voir si la commande peut être exécutée à un moment donné.

Personnellement, je trouve cela plus pratique.

0 votes

Comment rafraîchir IsEnabled ?

0 votes

J'éviterais de tout invalider ; c'est une solution meilleure et plus facile.

0 votes

PersistentWPFIssues12YearAnniversary #NoWonderEverybodyFledToJS

6voto

KeMik Points 89

Cette variante vous conviendra probablement :

 public interface IRelayCommand : ICommand
{
    void UpdateCanExecuteState();
}

Mise en œuvre :

 public class RelayCommand : IRelayCommand
{
    public event EventHandler CanExecuteChanged;

    readonly Predicate<Object> _canExecute = null;
    readonly Action<Object> _executeAction = null;

   public RelayCommand( Action<object> executeAction,Predicate<Object> canExecute = null)
    {
        _canExecute = canExecute;
        _executeAction = executeAction;
    }

    public bool CanExecute(object parameter)
    {
       if (_canExecute != null)
            return _canExecute(parameter);
        return true;
    }

    public void UpdateCanExecuteState()
    {
        if (CanExecuteChanged != null)
            CanExecuteChanged(this, new EventArgs());
    }

    public void Execute(object parameter)
    {
        if (_executeAction != null)
            _executeAction(parameter);
        UpdateCanExecuteState();
    }
}

Utilisation simple :

public IRelayCommand EditCommand { get; protected set; }
...
EditCommand = new RelayCommand(EditCommandExecuted, CanEditCommandExecuted);

 protected override bool CanEditCommandExecuted(object obj)
    {
        return SelectedItem != null ;
    }

    protected override void EditCommandExecuted(object obj)
    {
        // Do something
    }

   ...

    public TEntity SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;

            //Refresh can execute
            EditCommand.UpdateCanExecuteState();

            RaisePropertyChanged(() => SelectedItem);
        }
    }

XAML :

<Button Content="Edit" Command="{Binding EditCommand}"/>

1 votes

Ce n'est pas idéal car cela crée des références fortes aux gestionnaires, ce qui entraîne des fuites de mémoire.

4voto

Michael Kennedy Points 1603

Merci pour les conseils. Voici un bout de code sur la façon d'acheminer cet appel d'un thread BG vers le thread UI :

private SynchronizationContext syncCtx; // member variable

Dans le constructeur :

syncCtx = SynchronizationContext.Current;

Sur le fil d'arrière-plan, pour déclencher la requête :

syncCtx.Post( delegate { CommandManager.InvalidateRequerySuggested(); }, null );

J'espère que cela vous aidera.

-- Michael

3 votes

Il semble que ce serait mieux d'appeler Dispatcher.BeginInvoke()

0 votes

Salut Josh. Peut-être que ce serait mieux. En interne, Dispatcher.BeginInvoke() utilise la classe SynchronizationContextSwitcher qui délègue au SynchronizationContext de toute façon...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X