Avec le modèle MVVM, les commandes sont situées dans le ViewModel avec les propriétés. La façon de faire est de faire un Binding sur la propriété « Command » d’un contrôle. Mais qu’est-ce qu’on fait si on veut utiliser un autre évènement que celui qui est attitré à la propriété Command? Par exemple, sur un bouton la propriété « Command » est liée à « Click ». Si on veut utiliser par exemple MouseMove, est-on obligé de le faire dans le CodeBehind du UI? Et bien la réponse est : « Non ». On peut faire les choses dans les règles de l’art de MVVM. C’est ce qui est décrit dans ce qui suit.
Voici comment il faut faire en WPF.
Premièrement, il faut créer une Classe nommée CommandAction qui dérive de TargetTriggerAction<FrameworkElement> et que ICommandSource.
Voici le code de la classe :
public class CommandAction : TargetedTriggerAction<FrameworkElement>, ICommandSource{ [Category("Command Properties")] public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } } public static readonly DependencyProperty CommandProperty = DependencyProperty.Register( "Command", typeof(ICommand), typeof(CommandAction), new PropertyMetadata( (ICommand)null, OnCommandChanged)); private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var action = (CommandAction)d; action.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue); } private EventHandler CanExecuteChangedHandler; private void OnCommandChanged(ICommand oldCommand, ICommand newCommand) { if (oldCommand != null) UnhookCommand(oldCommand); if (newCommand != null) HookCommand(newCommand); } private void UnhookCommand(ICommand command) { command.CanExecuteChanged -= CanExecuteChangedHandler; UpdateCanExecute(); } private void HookCommand(ICommand command) { CanExecuteChangedHandler = new EventHandler(OnCanExecuteChanged); command.CanExecuteChanged += CanExecuteChangedHandler; UpdateCanExecute(); } private void OnCanExecuteChanged(object sender, EventArgs e) { UpdateCanExecute(); } private void UpdateCanExecute() { if (Command != null) { RoutedCommand command = Command as RoutedCommand; if (command != null) IsEnabled = command.CanExecute(CommandParameter, CommandTarget); else IsEnabled = Command.CanExecute(CommandParameter); if (Target != null && SyncOwnerIsEnabled) Target.IsEnabled = IsEnabled; } } [Category("Command Properties")] public object CommandParameter { get { return (object)GetValue(CommandParameterProperty); } set { SetValue(CommandParameterProperty, value); } } public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register( "CommandParameter", typeof(object), typeof(CommandAction), new PropertyMetadata()); [Category("Command Properties")] public IInputElement CommandTarget { get { return (IInputElement)GetValue(CommandTargetProperty); } set { SetValue(CommandTargetProperty, value); } } public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register( "CommandTarget", typeof(IInputElement), typeof(CommandAction), new PropertyMetadata()); [Category("Command Properties")] public bool SyncOwnerIsEnabled { get { return (bool)GetValue(SyncOwnerIsEnabledProperty); } set { SetValue(SyncOwnerIsEnabledProperty, value); } } public static readonly DependencyProperty SyncOwnerIsEnabledProperty = DependencyProperty.Register( "SyncOwnerIsEnabled", typeof(bool), typeof(CommandAction), new PropertyMetadata()); protected override void Invoke(object o) { if (Command != null) { var command = Command as RoutedCommand; if (command != null) command.Execute(CommandParameter, CommandTarget); else Command.Execute(CommandParameter); } }}
Deuxièmement il faut une référence sur Interactivity
xmlns:interactivity= »clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity »
Finalement, voici comment l’intégrer
(Source : http://jacokarsten.wordpress.com/2009/03/27/applying-command-binding-to-any-control-and-any-event/)
Voici comment il faut faire en Silverlight.
L’approche précédente est très bonne mais ne fonctionne pas en Silverlight car les EventTrigger ne sont pas disponibles. Par conséquent, voici l’approche que je suggère.
Premièrement, on crée une AttachedProperty qui sera distribuée à tous les enfants d’un « Container ». Dans mon exemple j’ai pris un « Grid » mais d’autres logiques pourraient être utilisées.
Exemple de l’AttachedProperty
public class AdvancedBindingGrid : Grid{ public AdvancedBindingGrid(): base() { this.Loaded += new RoutedEventHandler(AdvancedBindingGrid_Loaded); } private void AdvancedBindingGrid_Loaded(object sender, RoutedEventArgs e) { if (!DesignerProperties.GetIsInDesignMode(this)) { foreach (Control c in Children) { ICommand bindedCommand = AdvancedBindingGrid.GetMouseMoveCommand(c); c.MouseMove += new MouseEventHandler(((RelayCommand)bindedCommand).HandlerExecute); } } } public static readonly DependencyProperty MouseMoveCommandProperty = DependencyProperty.RegisterAttached("MouseMoveCommand", typeof(ICommand), typeof(AdvancedBindingGrid), new PropertyMetadata(null)); public static void SetMouseMoveCommand(DependencyObject element, ICommand value) { element.SetValue(MouseMoveCommandProperty, value); } public static ICommand GetMouseMoveCommand(DependencyObject element) { return (ICommand)element.GetValue(MouseMoveCommandProperty); }}
Notez qu’ici il y a un RelayCommand qui devrait être remplacé par la classe utilisée dans votre projet. DesignerProperties sert peut que le tout compile en Design. Finalement, l’approche ici place des propriétés fixent pour chaque évènement à prendre en charge. Il serait possible de changer cette approche pour une collection et spécifier le nom de l’évènement.
Finalement, on intègre l’AttachedProperty: