Aller au contenu

Command

De Banane Atomic

Liens

RelayCommand

A la différence des RoutedCommand, elle permet d'exécuter directement le délégué associé.
Le code source est disponible dans MSDN Magazine de Février 2009.
Relaying Command Logic

<Button Command="{Binding Path=MyCmd}"
        CommandParameter="MonParamètre"/>
public ICommand MyCmd { get; private set; }

MyCmd = new RelayCommand<int>(MyCmdAction, MyCmdCanExecute);

private void MyCmdAction(object obj) { ... }
private bool MyCmdCanExecute(object obj) { ... }

// avec des méthodes anonymes
MyCmd = new RelayCommand<int>(param => { ... }, param => { ... });

// version compacte
private ICommand _myCmd; 
public ICommand MyCmd 
{ 
  get 
  { 
    return _myCmd 
      ?? (_myCmd = new RelayCommand<int>( 
        param => { ... }, 
        param => { ... }));
  } 
}


// version compacte sans paramètres
private ICommand _myCmd; 
public ICommand MyCmd 
{ 
  get 
  { 
    return _myCmd 
      ?? (_myCmd = new RelayCommand( 
        () => { ... }));
  } 
}

Multi-paramètres

<xxx xmlns:ConverterNameSpace="clr-namespace:MonNameSpace.MyCmdConverterClass">
    <xxx.Resources>
        <ConverterNameSpace:MyCmdConverterClass x:Key="MyCmdConverterKey" />
    </xxx.Resources>

    <Button Command="{Binding Path=ViewModel.MyCmd}">
        <Button.CommandParameter>
            <MultiBinding Converter="{StaticResource ResourceKey=MyCmdConverterKey}">
                <Binding Path="." />
                <Binding RelativeSource="{RelativeSource AncestorType=TextBlock}" Path="Text" />
            </MultiBinding>
        </Button.CommandParameter>
    </Button>
namespace MonNameSpace
{
    public class MyCmdConverterClass : IMultiValueConverter
    {
        #region IMultiValueConverter Membres
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            // on ne peut retourner directement values sinon une fois dans la méthode Execute le tableau d'object ne contient plus que des null
            return values.ToArray();
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
        #endregion
    }
}
RelayCommand ne prend qu'un seul paramètre pour execute.

Async Command

this.MyCommand = AsyncDelegateCommand.Create(
    this.DoSomethingAsync,
    (Item arg) => this.CanExecute);
AsyncDelegateCommand.cs
public sealed class AsyncDelegateCommand : DelegateCommand
{
    private readonly AsyncDelegateProxy proxy;
    private bool lastCanExecuteValue;

    public static AsyncDelegateCommand Create(Func<Task> executeAsyncMethod)
    {
        return new AsyncDelegateCommand(new AsyncDelegateProxy(executeAsyncMethod));
    }

    public static AsyncDelegateCommand Create(Func<Task> executeAsyncMethod, Func<bool> canExecuteMethod)
    {
        return new AsyncDelegateCommand(new AsyncDelegateProxy(executeAsyncMethod), canExecuteMethod);
    }

    public static AsyncDelegateCommand<T> Create<T>(Func<T, Task> executeAsyncMethod)
    {
        return new AsyncDelegateCommand<T>(executeAsyncMethod);
    }

    public static AsyncDelegateCommand<T> Create<T>(Func<T, Task> executeAsyncMethod, Func<T, bool> canExecuteMethod)
    {
        return new AsyncDelegateCommand<T>(executeAsyncMethod, canExecuteMethod);
    }

    private AsyncDelegateCommand(AsyncDelegateProxy proxy)
        : this(proxy, () => true)
    {
    }

    private AsyncDelegateCommand(AsyncDelegateProxy proxy, Func<bool> canExecuteMethod)
        : base(proxy.ExecuteAsync, canExecuteMethod)
    {
        this.proxy = proxy;
        proxy.TaskStarted += this.OnTaskStartedOrEnded;
        proxy.TaskEnded += this.OnTaskStartedOrEnded;
    }

    private void OnTaskStartedOrEnded(object sender, EventArgs e)
    {
        if (this.lastCanExecuteValue)
        {
            this.RaiseCanExecuteChanged();
        }
    }

    protected override bool CanExecute(object parameter)
    {
        this.lastCanExecuteValue = base.CanExecute(parameter);
        return !this.proxy.TaskIsRunning && this.lastCanExecuteValue;
    }

    private class AsyncDelegateProxy
    {
        private readonly Func<Task> executeAsyncMethod;

        public AsyncDelegateProxy(Func<Task> executeAsyncMethod)
        {
            this.executeAsyncMethod = executeAsyncMethod;
            this.ExecuteAsync = async () => await this.RunAsync();
        }

        public event EventHandler TaskStarted;
        public event EventHandler TaskEnded;

        private async Task RunAsync()
        {
            this.TaskIsRunning = true;
            this.TaskStarted?.Invoke(this, EventArgs.Empty);

            try
            {
                await this.executeAsyncMethod();
            }
            finally
            {
                this.TaskIsRunning = false;
                this.TaskEnded?.Invoke(this, EventArgs.Empty);
            }
        }

        public Action ExecuteAsync { get; }

        public bool TaskIsRunning { get; private set; }
    }
}

Rafraichir les commandes

Utile dans le cas où CommandManager n'a pas pu déterminer si la cible de la Commande a changé. Ce qui est souvent le cas si c'est un thread autre que le thread graphique qui modifie le cible. MSDN

// Force l'appel de l'event CanExecute sur toutes les Commandes.
CommandManager.InvalidateRequerySuggested();

Command vs Event

  1. Event
  2. Command

InvokeCommandAction

Utilise un attached behavior pour lier un évènement à une commande.

RoutedCommand

La commande se trouve dans une classe statique pour faciliter son accès.

namespace AutreNamespace
{
    public static class MaClasse // probablement la fenêtre
    {
        public static RoutedCommand MaCommande = new RoutedCommand();
    }
}

On attrape l’événement lancé par la RoutedCommand et on exécute le code que l'on veut.

<Window x:Class="CurrentNamespace.MainWindow"
        xmlns:autrens="clr-namespace:AutreNamespace">
    <Window.CommandBindings>
        <CommandBinding Command="{x:Static Member=autrens:MaClasse.MaCommande}"
                        Executed="MaCommandeExecuted"
                        CanExecute="MaCommandeCanExecute"/>
    </Window.CommandBindings>

Lance un RoutedEvent qui va remonter l'arbre visuel jusqu'à trouver un CommandBinding.
On lie le bouton à la commande.

<UserControl xmlns:autrens="clr-namespace:AutreNamespace">
    <Button Command="{x:Static autrens:MaClasse.MaCommande}"
            CommandParameter="{Binding Path=.}" />
namespace CurrentNamespace
{
    public partial class MainWindow : Window
    {
        private void MaCommandeExecuted(object sender, ExecutedRoutedEventArgs e)
        {
             // e.Parameter contient le paramètre de la commande
             // e.Source contient le composant graphique branché à la commande
        }

        private void MaCommandeCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
    }
}

Routed Events

Ils possèdent une des trois stratégie de routing suivante:

Direct même fonctionnement que dans Windows form (MouseEnter)
Bubbling part de l’élément dont l'evt est originaire et remonte l'arbre visuel (ButtonClick)
Tunneling part de la fenêtre principale et descend l'arbre visuel jusqu'à l'élément cible (Preview*)