web-dev-qa-db-fra.com

Actualiser la commande WPF

Est-ce que quelqu'un sait comment je peux forcer CanExecute à être appelé avec une commande personnalisée (Josh Smith's RelayCommand )?

De manière générale, 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. 

J'ai une situation où la condition pour CanExecute est activée/désactivée par une minuterie dans les coulisses. Etant donné que cela n’est pas fonction de l’interaction de l’utilisateur, CanExecute n’est appelé que lorsque l’utilisateur interagit avec l’UI. Le résultat final est que ma Button reste activée/désactivée jusqu'à ce que l'utilisateur clique dessus. Après le clic, il est mis à jour correctement. Parfois, Button apparaît activé, mais lorsque l'utilisateur clique dessus, il devient désactivé au lieu de se déclencher.

Comment puis-je forcer une mise à jour dans le code lorsque le minuteur modifie la propriété qui affecte CanExecute? J'ai essayé de tirer 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; }
68
Josh G
100
Kent Boogaart

J'étais au courant de l'existence de CommandManager.InvalidateRequerySuggested () il y a longtemps et je l'ai utilisé, mais cela ne fonctionnait parfois pas pour moi. J'ai enfin compris pourquoi c'était le cas! Même s'il ne lance pas comme d'autres actions, vous devez l'appeler sur le thread principal. 

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

28
SilverSideDown

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

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

puis implémentez cette propriété dans votre ViewModel. Cela facilite également un peu le fonctionnement de UnitTesting 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.

16
tridy.net

Probablement cette variante vous conviendra:

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

La mise en oeuvre:

 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}"/>
6
R.Titov

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

private SynchronizationContext syncCtx; // member variable

Dans le constructeur:

syncCtx = SynchronizationContext.Current;

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

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

J'espère que cela pourra aider.

-- Michael

4
Michael Kennedy

Pour mettre à jour un seul GalaSoft.MvvmLight.CommandWpf.RelayCommand que vous pourriez utiliser 

mycommand.RaiseCanExecuteChanged();

et pour moi j'ai créé une méthode d'extension:

public static class ExtensionMethods
    {
        public static void RaiseCanExecuteChangedDispatched(this RelayCommand cmd)
        {
            System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { cmd.RaiseCanExecuteChanged(); }));
        }

        public static void RaiseCanExecuteChangedDispatched<T>(this RelayCommand<T> cmd)
        {
            System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { cmd.RaiseCanExecuteChanged(); }));
        }
    }
0
Apfelkuacha