MVVM: Behandeln von Events über Commands, Teil 2

Posted by incubi on March 9th, 2010

In meinem letzten Eintrag gings darum, wie man Events eines WPF Controls auf Commands eines ViewModels umleiten kann. Verwendet habe ich dort die Attached Properties von WPF. Das Problem dabei war aber, dass man für diese Attached Properties für ein Control nur einmal verwenden kann. Das heißt, dass man zB für ein Border-Control nicht zugleich den MouseEnter- und MouseLeave-Event auf ein Command umleiten kann. Um das mit der Lösung der Attached Properties zu erreichen, wäre es notwendig, einen zusätzlichen Wrapper rund um das Border-Control einzuführen:


<Border Margin="50"
        commanding:AttachedCommand.Command="{Binding MouseEnterCommand}"
        commanding:AttachedCommand.EventName="MouseEnter">
   <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
           Background="YellowGreen" CornerRadius="20"
           commanding:AttachedCommand.Command="{Binding MouseLeaveCommand}"
           commanding:AttachedCommand.CommandParameter="Border #1"
           commanding:AttachedCommand.EventName="MouseLeave">
   </Border>
</Border>

Um diesem Umweg zu vermeiden, möchte ich heute einen neuen Ansatz präsentieren, der die seit Expression Blend 3 zur Verfügung stehenden Behaviors und Triggers verwendet.

TargetedTriggerAction aus System.Windows.Interactivity verwenden
Die Idee dahinter ist, einen Trigger zu erstellen, der diverse Dependency Properties implementiert. Diese Properties müssen deshalb Dependency Properties sein, weil wir sonst kein Databinding darauf anwenden können. Eine dieser DP ist vom Typ ICommand — also das Command, das wir ausführen wollen, wenn der Trigger gestartet wird:


public ICommand Command
{
   get { return (ICommand)GetValue(CommandProperty); }
   set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
 ´  DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandTrigger),
                                new PropertyMetadata(null, OnCommandChanged));

Wichtig bei der Dependency Property ist der Callback OnCommandChanged, der dann aufgerufen wird, wenn der DP ein neuer Wert zugewiesen wird.
Warum ist das wichtig? Bei einer Command-Property, die out-of-the-box bei bereits vorhandenen Controls wie Button vom Framework mitgeliefert wird, wird der Status des Controls abhängig von der CanExecute-Methode des Commands verändert. Also liefert CanExecute false zurück, dann wird der Button, der das Command verwendt, deaktiviert. Genau diese Funktionalität müssen/sollen/wollen wir mit unserem Trigger auch abbilden.

Deshalb registrieren wir uns  in der OnCommandChanged Methode auf den CanExecuteChanged Event, den jede Klasse, die ICommand implementiert, selbstverständlich auch implementieren muss. Sobald dieser Event geworden wurde, wollen wir prüfen, ob unser Control, das ein Binding auf das Command besitzt, noch einen gültigen Status besitzt:


private static void OnCommandChanged(DependencyObject action, DependencyPropertyChangedEventArgs e)
{
   ((CommandTrigger)action)._RefreshCommandHandling((ICommand)e.OldValue, (ICommand)e.NewValue);
}

private void _RefreshCommandHandling(ICommand oldCommand, ICommand newCommand)
{
   if (oldCommand != null) oldCommand.CanExecuteChanged -= new EventHandler((sender, args) => _UpdateCanExecute());
   if (newCommand != null) newCommand.CanExecuteChanged += new EventHandler((sender, args) => _UpdateCanExecute());

   _UpdateCanExecute();
}

Wir bekommen im Eventhandler über die EventArgs den alten und den neuen Wert, den die Dependency Property angenommen hat. Das wir haben die Möglichkeit, uns vom Event des alten Commands (also des alten Wertes der DP) zu entfernen und uns auf den Event des neuen Commands (also der neue Wert der DP) zu registrieren. In der _UpdateCanExecute() Methode prüfen wir schlussendlich, ob das Control, auf dem der Trigger angewendet wird, noch den gültigen Status aufgrund von CanExecute besitzt:


private void _UpdateCanExecute()
{
   if (this.Command == null) return;

   RoutedCommand command = this.Command as RoutedCommand;

   if (command != null) this.IsEnabled = command.CanExecute(this.CommandParameter, this.CommandTarget);
   else this.IsEnabled = this.Command.CanExecute(this.CommandParameter);

   if (this.Target != null && this.SyncOwnerIsEnabled) this.Target.IsEnabled = this.IsEnabled;
}

Im XAML Code wird dieser Trigger jetzt ganz einfach verwendet, indem wir zuerst den richtigen Namespace einbinden und anschließend den Trigger auf ein beliebiges Control anwenden:


<Border>
   <interactivity:Interaction.Triggers>
      <interactivity:EventTrigger EventName="MouseEnter">
         <commanding:CommandTrigger Command="{Binding MouseEnterCommand}" />
      </interactivity:EventTrigger>

      <interactivity:EventTrigger EventName="MouseLeave">
         <commanding:CommandTrigger Command="{Binding MouseLeaveCommand}"  />
      </interactivity:EventTrigger>
   </interactivity:Interaction.Triggers>
</Border>

Das tolle an diesem Trigger ist jetzt, dass er mehrmals auf ein und dem selben Control angewendet werden kann. Wir können jetzt also mehrere Events über den selber Trigger behandeln und auf Commands weiterleiten.

Das Demoprojekt gibts wie immer unter http://downloads.juergenoberngruber.at/blog/CommandAction_Trigger.zip.

In diesem Sinne.

Previous Articles

MVVM: Behandeln von Events über Commands, Teil 1

Posted by incubi on March 7th, 2010

INotifyPropertyChanged mit Hilfe von AOP lösen

Posted by incubi on February 27th, 2010

HowTo: Custom Cursor für Windows Forms

Posted by incubi on February 24th, 2010

HowTo: Erzeugen einer Liste von aufeinanderfolgenden Jahreszahlten?

Posted by incubi on February 18th, 2010

HowTo: Existierende VHD im Bootmanager von Windows 7 registrieren

Posted by incubi on February 17th, 2010

HowTo: Custom ToolStrip Rendering für Windows Forms

Posted by incubi on January 30th, 2010

HowTo: Dynamische Objekte mit System.Reflection.Emit

Posted by incubi on January 11th, 2010

Auflösen von VSS Abhängigkeiten bei Continuous Integration mit Teamcity

Posted by incubi on January 5th, 2010

http://www.slidetounlock.me/

Posted by incubi on December 26th, 2009
MCTS

About

Jürgen Oberngruber is a software engineer, software architect and IT consultant living in Wels, Austria and is currently working at Infoniqa Informationstechnik GmbH in Thalheim, Austria. During his study at the University of Applied Sciences in Hagenberg, Austria he gained a deep knowledge in the field of software engineering using a lot of different programming languages. Since a few years he's focusing on the Microsofts .NET platform including all relevant technologies. One of his passion is to explore, to test and to evaluate new technologies and programming languages (mostly in the field of Microsofts .NET platform). Checkout more information on www.juergenoberngruber.at