« Command » : différence entre les versions
Ligne 89 : | Ligne 89 : | ||
</kode> | </kode> | ||
{{warn | RelayCommand ne prend qu'un seul paramètre pour execute.}} | {{warn | RelayCommand ne prend qu'un seul paramètre pour execute.}} | ||
= Async Command = | |||
<kode lang='csharp'> | |||
this.MyCommand = AsyncDelegateCommand.Create( | |||
this.DoSomethingAsync, | |||
(Item arg) => this.CanExecute); | |||
</kode> | |||
<filebox fn='AsyncDelegateCommand.cs' collapsed> | |||
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; } | |||
} | |||
} | |||
</filebox> | |||
= Rafraichir les commandes = | = Rafraichir les commandes = |
Dernière version du 15 mars 2021 à 11:10
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
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*) |