Command

De Banane Atomic
Aller à la navigationAller à la recherche

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

Xaml.svg
<Button Command="{Binding Path=MyCmd}"
        CommandParameter="MonParamètre"/>
Csharp.svg
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

Xaml.svg
<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>
Csharp.svg
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

Csharp.svg
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

Cs.svg
// 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.

Csharp.svg
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.

Xaml.svg
<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.

Xaml.svg
<UserControl xmlns:autrens="clr-namespace:AutreNamespace">
    <Button Command="{x:Static autrens:MaClasse.MaCommande}"
            CommandParameter="{Binding Path=.}" />
Csharp.svg
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*)