PRISM: DelegateCommand und CompositeCommand

Ein Command ist ein Objekt vom Typ ICommand und definiert eine Art abstraktere, losgekoppelte Form eines Events. WPF-Commands werden an einer zentralen Stellen definiert (oftmals innerhalb des ViewModels) und können dann von beliebigen Controls (wie z.B. MenuItems, Toolbar-Buttons usw.) verwendet werden. Commands bieten auch die integrierte Unterstützung der sogenannten Input Gestures, das sind Tastaturkürzel, wie beispielsweise Strg + C. Ein weiterer großer Vorteil von Commands ist das automatische Aktivieren/Deaktivieren von Controls, z.B. werden MenuItems oder Buttons in einer Toolbar automatisch deaktiviert, wenn das entsprechende Command nicht ausgeführt werden kann. Innerhalb der verschiedenen MVVM-Frameworks gibt es nun diverse Implementierungen der ICommand-Schnittstelle, so auch in Prism. In diesem Artikel werden nun ein paar Grundlagen zu WPF-Commands erläutert und die Besonderheiten des DelegateCommands und CompositeCommands der Prism-Bibliothek beschrieben.

ICommand-Schnittstelle

Basis des WPF-Command-Systems bildet die ICommand-Schnittstelle (System.Windows.Input.ICommand), mit der vorgeschrieben wird, wie ein Command arbeitet. Die Schnittstelle ist wie folgt definiert:

Die Methode Execute beschreibt die Anwendungslogik, die sich hinter einem Kommando verbirgt. Tatsächlich wird in Execute durch einen Event ein komplexer Prozess angestoßen, an dessen Ende erreicht werden kann, dass ein einziger Command von mehreren Elementen genutzt werden kann. Der Parameter kann dazu verwendet werden, um Daten zur Ausführung des Befehls zur Verfügung zu stellen.

CanExecute gibt ganz einfach nur Auskunft darüber, ob ein Command ausgeführt werden kann. Der Parameter dient denselben Zwecken wie der der Methode Execute.

Das Event CanExecuteChanged wird ausgelöst, wenn der Befehls-Manager, der die Befehlsabläufe zentral verwaltet, eine Änderung der Befehlsquelle erkennt. Das kann man als Signal an alle Steuerelemente, die das Kommando benutzen, verstehen, die Methode CanExecute aufzurufen und den Zustand des Kommandos zu überprüfen. Ein typisches Beispiel in diesem Zusammenhang: Man möchte den Text innerhalb einer TextBox kopieren. Hier soll das Command nur ausgeführt werden, wenn der zu kopierende Text auch markiert ist. Dies könnte dann in der CanExecute-Methode geprüft werden. Liefert diese Methode false zurück wird das entsprechende Steuerelement (an welches dieses Command gebunden ist) auch automatisch deaktiviert.

Jetzt gibt es diverse Implementierungen, die auf der ICommand-Schnittstelle basieren, so auch innerhalb von PRISM. In den nächsten Abschnitten werden diese näher erläutert und anhand von Beispielen gezeigt, wie man diese einsetzen kann. Kommen wir aber zunächst einmal zu ein paar Grundlagen bzgl. WPF-Commands.

WPF-Commands

WPF bietet bereits „ab Werk“ eine ganze Reihe von Commands, die in der täglichen Programmierpraxis immer wieder anfallen. Diese Commands gliedern sich in die folgenden Gruppen:

  • ApplicationCommands (z.B. Cut, Past, Help, New, Print, Save, Stop, Undo)
  • ComponentCommands (z.B. ExtendSelectionLeft, MoveLeft)
  • NavigationCommands (z.B. FirstPage, GotoPage, Refresh, Search)
  • MediaCommands (z.B. Play, Pause, FastForward, IncreaseVolume)
  • EditingCommands z.B. Delete, MoveUpByLine, ToggleBold)

Hier mal ein kurzer Überblick die Beteiligten:

  • Command – Ein Kommando beschreibt eine bestimmte Aufgabe. Dabei wird verfolgt, ob der Command ausgeführt werden kann oder nicht (CanExecute-Methode)
  • Command-Quelle (CommandSource) – mit der Quelle ist die Komponente gemeint, die einen Command anstößt. Das kann ein Button oder auch beispielsweise ein Menüelement sein. Allen Quellen ist gemeinsam, die Schnittstelle ICommandSource zu implementieren
  • Command-Ziel (CommandTarget) – damit ist das Element gemeint, auf dem das Kommando ausgeführt wird (dies ist ein spezifisches Control wie TextBox oder Image)
  • Command-Bindung (CommandBinding) – eine Bindung sorgt für die Verknüpfung eines Commands mit der Anwendungslogik

Die Verwendung eines vordefinierten Commands sieht dann wie folgt aus:

Hinweis:

Doch Achtung: Nicht das Command an sich stellt die Logik, z.B. für das Einfügen von Zwischenablageinhalten, zur Verfügung, sondern das jeweilige Ziel-Objekt (CommandTarget), d.h. ein spezifisches Control wie TextBox oder Image.

Die Klassen RoutedCommand und RoutedUICommand

Kommandos wie beispielsweise Copy und Paste implementieren allerdings nicht direkt die Schnittstelle ICommand. Das übernimmt die Klasse RoutedCommand, die darüber hinaus die Infrastruktur bereitstellt, einen Event durch den Elementbaum bubbeln zu lassen. Das Event-Bubbling ist wichtig, wenn die Kommandobindung ins Spiel kommt, die ein Ereignis auslöst. Damit wird sichergestellt, dass das Ereignis an einer Stelle behandelt werden kann, auch wenn unterschiedliche Kommandoquellen im Fenster den Event ausgelöst haben.

Interessant ist, dass RoutedCommand das Interface ICommand explizit definiert. Daher können die Schnittstellenmethoden nicht direkt aufgerufen werden. Trotzdem werden die beiden Methoden Execute und CanExecute veröffentlicht, jedoch mit einem zusätzlichen Parameter vom Typ IInputElement, der das Zielelement beschreibt. Darunter ist das Element zu verstehen, bei dem die Ereigniskette startet, um dann den Elementbaum nach oben zu bubbeln, bis eine Stelle erreicht wird, die auf den Befehl reagiert.

Das folgende Listing zeigt die Struktur der Klasse RoutedCommand:

Die meisten Kommandos sind jedoch nicht vom Typ RoutedCommand, sondern vom Typ RoutedUICommand. Diese Klasse ist direkt von RoutedCommand abgeleitet und stellt die zusätzliche Eigenschaft Text bereit. Dabei handelt es sich genau um die Eigenschaft, die dafür sorgt, dass beispielsweise ein MenuItem mit der passenden Beschriftung versorgt wird.

Das sind nur mal einige Grundlagen zu Commands in WPF (hier gibt es noch weitere Details wie z.B. das ICommandSource-Interface oder Input-Gestures). Diese alle hier zu beschreiben würde den Umfang des Artikels überschreiten und daher der Verweis auf die Weblinks am Ende des Artikels (z.B. eine Übersicht über WPF-Commands ist in [1] – WPF-Commands zu finden)

PRISM: DelegateCommand

Die DelegateCommand-Klasse beinhaltet zwei Delegaten (jeweils für die Execute– und CanExecute-Methode), die entsprechende Methoden innerhalb des ViewModels referenzieren. Diese Klasse erbt von der DelegateCommandBase-Klasse, in welcher die ICommand-Schnittstelle implementiert wird und die Delegaten für die Execute– bzw. CanExecute-Methode aufgerufen werden. Die Referenzierung der Delegaten auf die entsprechenden Methoden innerhalb des ViewModels erfolgt im Konstruktor der DelegateCommand-Klasse, die wie folgt definiert ist:

Der folgende Quellcode zeigt wie ein DelegateCommand instanziiert wird. Dabei werden dem Execute– und CanExecute-Delegat (über den Konstruktor) entsprechende Methoden aus dem ViewModel zugewiesen. Dieses Command ist innerhalb des ViewModels als schreibgeschützte Eigenschaft (vom Typ ICommand) ausgeprägt und kann dann via DataBinding innerhalb des Views verwendet werden.

Das oben gezeigte Beispiel stammt aus dem PrismMahAppsSample (https://github.com/steve600/PrismMahAppsSample) und hier wird ein Command erzeugt um sogenannte Flyouts innerhalb des Dialogs zu öffnen. Das Command erhält als Parameter den Namen des Flyouts. In Zeile 3 wird eine schreibgeschützte Eigenschaft vom Typ ICommand erstellt. In Zeile 7 wird dann das eigentliche Command instanziiert und der Eigenschaft zugewiesen. Wie zu sehen werden dem Command bei der Erstellung die Methoden für den Execute– und CanExecute-Delegate als Parameter mitgegeben. Im Beispiel wird die generische Variante des DelegateCommands verwendet und hier kann der Typ des Parameters für das Command angegeben werden. Es existiert aber ebenfalls eine nicht generische Implementierung (wenn keine Command-Parameter benötigt werden)!

RaiseExecuteChanged

Ein DelegateCommand wird nicht durch den CommandManager überwacht. Der CommandManager ist für die zentrale Verwaltung der Befehlsabläufe zuständig und löst das CanExecuteChanged-Ereignis aus, sobald eine Änderung an der Befehlsquelle (CommandSource) erkannt wird. Für ein DelegateCommand muss dies manuell erledigt werden bzw. liegt im Verantwortungsbereich des Entwicklers. Innerhalb des ViewModels können Statusänderungen mit dem Aufruf RaiseCanExecuteChanged-Methode (Methode des DelegateCommand-Objekts) an das entsprechende Command weitergereicht werden. Dadurch wird das CanExecuteChanged-Event des Commands ausgelöst und die CanExecute-Methode erneut ausgeführt. Dadurch werden alle Controls, die an das Command gebunden sind, innerhalb des UI angewiesen ihren Enabled-Status zu aktualisieren.

Dies hat aber den Nachteil, dass man RaiseCanExecuteChanged-Methode, bei jeder Eigenschaftsänderung, manuell aufrufen muss (dies würde man dann normalerweise im Setter der jeweiligen Eigenschaft tun). Es gibt aber noch eine andere Möglichkeit: Das DelegateCommand stellt eine Methode namens ObservesProperty bereit. Mit Hilfe dieser Methode können Eigenschaften, die die INotifyPropertyChanged-Schnittstelle implementieren, für das entsprechende Command registriert und überwacht werden. Immer wenn dann das PropertyChanged-Event einer registrierten Eigenschaft ausgelöst wird, wird automatisch die Methode RaiseCanExecuteChanged des Commands ausgeführt.

Jetzt braucht man nur noch die zu überwachenden Eigenschaften registrieren und bei jeder Eigenschaftsänderung wird die RaiseCanExecuteChanged-Methode automatisch vom entsprechenden Command aufgerufen. Ein Registrierung könnte dabei wie folgt aussehen:

PRISM: CompositeCommand

In den meisten Fällen erstellt man ein Command innerhalb des ViewModels und bindet dieses Command an ein UI-Element. Somit kann dieses Command dann direkt aus dem entsprechenden View aufgerufen werden (z.B. durch Betätigen eines Buttons). Jetzt kann es aber durchaus Fälle geben, dass ein UI-Element mehrere Commands (evtl. sogar Commands aus mehreren ViewModels) aufrufen möchten. Ein gutes Beispiel dafür wäre z.B. Visual Studio: Innerhalb von Visual Studio hat man ja mehrere Tabs für die verschiedenen Dateien, die aktuell geöffnet sind. Angenommen der Benutzer hat jetzt aktuell 10 Dateien geöffnet und von diesen 10 Dateien sind 5 geändert worden. Um jetzt nicht 5-mal den Speichern-Button betätigen zu müssen gibt es eine Funktion namens „Alle speichern“. Wird diese Funktion ausgeführt werden die geänderten 5-Dateien gespeichert. In einem MVVM-Szenario würde man also das Save-Command der einzelnen (geänderten) ViewModels aufrufen und für genau ein solches Szenario ist das CompositeCommand gedacht.

PRISM_CompositeCommand
(Quelle: [2] – PRISM 5 – Developer’s Guide to Microsoft Prism Library 5.0 for WPF)

Ein CompositeCommand setzt sich also aus mehreren Commands zusammen. Wird das CompositeCommand aufgerufen werden alle registrierten Commands der Reihe nach ausgeführt. Das ist vor allem in Situationen hilfreich, in denen ein einzelnes UI-Command mehrere Commands aufrufen soll (siehe dazu das obige Beispiel „Alle Speichern“). Im Quellcode würde das so aussehen:

Um das zu Erreichen hält die CompositeCommand-Klasse eine Liste von Commands (im Normalfall eine Liste von einzelnen DelegateCommands). Wird dann die Execute-Methode des CompositeCommands aufgerufen wird einfach die Liste der Reihe nach abgearbeitet und die Execute-Methode des aktuellen Commands aufgerufen. Hier wird natürlich auch die CanExecute-Methode berücksichtigt. Dafür wird ebenfalls die CanExecute-Methode der einzelnen registrierten Commands ausgewertet und liefert nur ein Command false zurück kann das gesamte Command nicht ausgeführt werden.

Wie zu sehen sind die die Klassen DelegateCommand und CompositeCommand universell einsetzbar und können einem Entwickler viel Arbeit abnehmen. Die Implementierung des DelegateCommands kann auch in WPF-Anwendungen eingesetzt werden, die nicht den vollen Funktionsumfang der Prism-Bibliothek nutzt (z.B. Module, Regionen, usw.). Weitere Informationen zur Verwendung des DelegateCommands sind in der Prism-Dokumentation zu finden (siehe dazu die unten aufgeführten Weblinks).

Literaturverzeichnis und Weblinks

Abk.Quelle
[1]MSDN
Übersicht WPF-Commands
[2]PRISM 5 - Developer's Guide to Microsoft Prism Library 5.0 for WPF
Part 5: Implementing the MVVM-Pattern
Part 6: Advanced MVVM-Scenarios
[3]Rheinwerk <openbook> - Visual C# 2012 - Das umfassende Handbuch
Kapitel 28: WPF-Commands
[4]MSDN
DelegateCommand-Klasse
DelegateCommand<T>-Klasse
[5]MSDN
CompositeCommand-Klasse
Fork me on GitHub