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
- Event
- Command
Utilise un attached behavior pour lier un évènement à une commande.
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*)
|