28.4 Die Anatomie eines »Command«-Objekts
Wir wollen uns nun ansehen, wie die Infrastruktur eines Commands aussieht. Hierbei spielen ein Interface und zwei Klassen eine wichtige Rolle: ICommand, RoutedCommand und RoutedUICommand.
28.4.1 Das Interface »ICommand«
Ausgangspunkt ist die Schnittstelle ICommand, mit der vorgeschrieben wird, wie ein Command arbeitet. Die Schnittstelle ist wie folgt definiert:
public interface ICommand {
void Execute(object param);
Boolean CanExecute(object param);
event EventHandler CanExecuteChanged;
}
Listing 28.17 Das Interface »ICommand«
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.
Der Event CanExecuteChanged wird ausgelöst, wenn sich der Zustand des Kommandos ändert. 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 haben wir bereits gesehen im Zusammenhang mit den Befehlen Copy und Paste: Nur wenn in der Textbox tatsächlich Text markiert ist, wird die Schaltfläche Kopieren aktiviert.
28.4.2 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:
public class RoutedCommand : ICommand
{
void ICommand.Execute(object param) { }
bool ICommand.CanExecute(object param) { }
public bool CanExecute(object param, IInputElement target) { }
public void Execute(object param, IInputElement target) { }
[...]
}
Listing 28.18 Struktur der Klasse »RoutedCommand«
Neben diesen Methoden werden von RoutedCommand noch drei Eigenschaften bereitgestellt, die Sie Tabelle 28.2 entnehmen können.
Eigenschaft | Beschreibung |
InputGesture |
Diese Eigenschaft beschreibt eine Collection, mit der festgelegt wird, auf welche Tastaturkürzel der Command reagiert. |
Name |
Liefert den Namen des RoutedCommands zurück. |
OwnerType |
Liefert den Besitzer der RoutedCommand-Instanz. |
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.
Alle Command-Objekte werden in Klassen gesammelt, die somit einen Pool von Befehlen bereitstellen. Wir wollen uns auch noch die Struktur einer solchen Klasse am Beispiel von ApplicationCommands und dem Befehl Copy ansehen.
public static class ApplicationCommands {
private static RoutedUICommand copy { get; }
public static RoutedUICommand Copy {
get { return copy;}
}
static ApplicationCommands() {
copy = new RoutedUICommand( ... };
[...]
}
[...]
}
Listing 28.19 Struktur der Klasse »ApplicationCommands«
In ähnlicher Weise können Sie natürlich auch eigene Commands erstellen. Der Konstruktor der Klasse RoutedUICommand hat einige Überladungen, die die Vorinitialisierung von Eigenschaften des Objekts ermöglichen.
28.4.3 Das Interface »ICommandSource«
In den drei Beispielen oben wurde mit den Befehlen Copy und Paste operiert, die bereits vollständig implementiert mit der Zwischenablage interagieren. Das ist sinnvoll, denn die Funktionalität der Aktionen lässt keine andere Logik zu.
Die meisten Befehle weisen aber keine Logik auf, weil sie spezifisch ist. Nehmen wir zum Beispiel den Befehl Open, der bei einem Button registriert ist:
<Button Command="ApplicationCommands.Open"... />
Was genau der Befehl leisten soll, lässt sich nicht für alle Fälle eindeutig bestimmen.
Das Steuerelement, das einen Befehl registriert, muss das Interface ICommandSource implementieren. Damit beschränkt sich die Registrierung eines Commands auf Steuerelemente wie beispielsweise Button, MenuItem, ListBoxItem oder HyperLink.
ICommandSource definiert drei Eigenschaften, die der folgenden Tabelle entnommen werden können.
Eigenschaft | Beschreibung |
Command |
Ruft den Befehl ab. |
CommandTarget |
Gibt das Objekt an, auf dem der Befehl ausgeführt wird. |
CommandParameter |
Definiert einen Wert, der bei Befehlsausführung übernommen werden kann. |
Die drei folgenden Klassen implementieren das Interface:
- UIElement
- UIElement3D
- ContentElement
Wird auf den Button gedrückt, wird der Befehl aufgerufen, und zwar auf dem Element, das unter CommandTarget angegeben ist. Ist CommandTarget nicht angegeben, wird automatisch das selektierte Element zum CommandTarget.
Der XAML-Code
<Button Command="Open" Content="Command-Demo"/>
wird dazu führen, dass der Button deaktiviert angezeigt wird. Grund dafür ist, dass der Befehl keine Implementierung hat – dafür wurde bisher keine Ereignisbehandlung registriert. Solange das der Fall ist, sind die entsprechenden Steuerelemente per Vorgabe deaktiviert.
Für die Zuweisung wird die Klasse CommandBinding benutzt. Das kann sowohl im XAML- als auch im C#-Code erfolgen. CommandBinding kann auch in der Quellkomponente festgelegt werden.
<Window.CommandBindings>
<CommandBinding Command="Open" Executed="Open_Executed" />
</Window.CommandBindings>
<Grid>
<Button Command="Open" Content="Command-Demo"/>
</Grid>
Die Operationen zur Ausführung des RoutedCommands sind nicht in den Execute-Methoden enthalten. Execute löst nämlich nur die beiden Ereignisse PreviewExecuted und Executed aus, die die Elementstruktur abwärts oder aufwärts durchlaufen und ein Objekt suchen, das ein CommandBinding beschreibt. Wird ein CommandBinding für den entsprechenden Befehl gefunden, dann wird der an CommandBinding angefügte ExecutedRoutedEventHandler aufgerufen. Diese Handler stellen die Programmierlogik bereit, mit der der RoutedCommand ausgeführt wird.
Das PreviewExecuted-Ereignis und das Executed-Ereignis werden für das CommandTarget ausgelöst. Wenn CommandTarget für die ICommandSource nicht festgelegt ist, werden das PreviewExecuted-Ereignis und das Executed-Ereignis für das Element mit Tastaturfokus ausgelöst.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.