28.3 Commands verwenden
Nicht alle Steuerelemente können vordefinierte WPF-Commands registrieren. Diese Fähigkeit bleibt den Controls vorbehalten, die das Interface ICommandSource implementieren. Dazu gehören beispielsweise ButtonBase, HyperLink und MenuItem sowie deren Ableitungen. Die Schnittstelle stellt dazu die Eigenschaft Command zur Verfügung, bei der ein WPF-Command registriert wird, wie oben im XAML-Code zu sehen ist.
Hinter einem WPF-Command steckt natürlich auch Programmlogik. Textboxen sind bereits so ausgestattet, dass sie mit den WPF-Befehlen Copy, Cut, Paste, aber auch Redo und Undo umgehen können. Klickt der Anwender auf eine Schaltfläche oder ein Menüelement, wird der Befehl automatisch ausgeführt und von der TextBox behandelt, die in diesem Moment den Fokus hat. Bei diesen Operationen handelt es sich um Standardoperationen, die auf dem fokussierten Steuerelement ausgeführt werden. Befehle wie beispielsweise Open oder New kennen solche allgemeingültigen Operationen nicht. Daher bieten diese Kommandos keine Programmlogik, Sie müssen diese selbst schreiben. Dazu sind Befehlsbindungen notwendig, die wir uns nun ansehen wollen.
Sollten Sie das Beispiel »Sample1« mit Schaltflächen ausprobieren, die sich nicht in einer Menüleiste oder der Symbolleiste befinden, wird das Verhalten zur Laufzeit nicht wie gewünscht sein. Tatsächlich tragen die Elemente Menu und ToolBar dazu bei, die aktuell fokussierte TextBox zu lokalisieren. Im weiteren Verlauf dieses Kapitels kommen wir auf diese Situation noch einmal zu sprechen.
28.3.1 Command-Bindungen einrichten
Schauen wir uns noch einmal die Registrierung eines Commands bei einem command-fähigen Steuerelement an:
<Button Command="ApplicationCommands.Help" Content="Hilfe" />
Listing 28.2 Registrierung eines Commands
Im Button wird mit der Eigenschaft Command der Help-Befehl mit der Schaltfläche verbunden. Wie oben gezeigt, kann die Eigenschaft auch in der Kurzform
<Button Command="Help" Content="Hilfe" />
Listing 28.3 Kurzform der Command-Registrierung
ausgedrückt werden.
Der Anwender wird bis zu diesem Zeitpunkt die Schaltfläche nicht nutzen können, denn sie ist deaktiviert. Das ist das Standardverhalten und hat seine Ursache darin, dass mit dem Befehl Help keine Logik verknüpft ist.
Zur Ausführung der Befehlslogik beim Aufruf eines Commands werden Ereignisse ausgelöst, deren Ereignishandler registriert werden müssen. In den Ereignishandlern wird das Verhalten des betreffenden Befehls beschrieben.
Der nächste Schritt besteht also darin, einen Ereignishandler bereitzustellen, der auf den Befehl ApplicationCommands.Help reagiert. Dazu ist eine Instanz der Klasse CommandBinding erforderlich, die den Befehl mit dem Ereignishandler verknüpft. Da innerhalb einer Komponente auch mehrere Commands ausgelöst werden können, werden alle Commands in einer Collection zusammengefasst, die durch die Eigenschaft CommandBindings beschrieben wird. Meistens wird die Liste der Bindungen mit dem Window verknüpft.
<Window.CommandBindings>
<CommandBinding Command="Help"
CanExecute="Help_CanExecute"
Executed="Help_Executed" />
</Window.CommandBindings>
<Button Command="Help" />
Listing 28.4 Definieren einer Kommandobindung samt ihren Ereignishandlern
Executed und CanExecute sind zwei Events des CommandBinding-Objekts. Dabei wird Executed ausgelöst, wenn der Anwender auf das auslösende Element klickt, zum Beispiel auf eine Schaltfläche. Somit verbirgt sich hinter diesem Ereignis die Logik, die der Befehl beschreiben soll.
private void Help_Executed(object sender, ExecutedRoutedEventArgs e) {
MessageBox.Show("Logik des Help-Commands.");
}
Listing 28.5 Ereignishandler des Events »Executed«
Das zweite Ereignis CanExecute wird ausgelöst, wenn der Befehl seinerseits prüft, ob er für das Befehlsziel ausgeführt werden kann.
private void Help_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = true;
}
Listing 28.6 Ereignishandler des Events »CanExecute«
Um den Button zu aktivieren, muss im Ereignishandler des CanExecute die Eigenschaft CanExecute des CanExecuteEventArgs-Parameters auf true gesetzt werden. false ist die Vorgabe und demnach die Ursache dafür, dass der Button zu Beginn dieses Abschnitts zunächst deaktiviert war. In diesem Ereignishandler erfolgt in der Regel die Überprüfung einer Bedingung, um festzustellen, ob die mit dem Befehl verknüpften Controls aktiviert oder deaktiviert dargestellt werden sollen. Für unser Codefragment ist vollkommen ausreichend, die Schaltfläche dauerhaft zu aktivieren.
Die Kurzform der Command-Registrierung kann nur im Zusammenhang mit vordefinierten Befehlen benutzt werden. Bei benutzerdefinierten Kommandos müssen Sie die folgende Syntaxschreibweise verwenden:
<Button Command="{x:Static UserdefinedCommands.DoIt}" Content="Mach es" />
28.3.2 Lokalität der Befehlsbindung
Die Bindung des Befehls erfolgte im Beispiel des letzten Abschnitts für das Window innerhalb der Eigenschaft CommandBindings. Diese Eigenschaft besitzen alle Elemente, die entweder von UIElement, UIElement3D oder ContentElement abgeleitet sind. Dazu gehört auch die Klasse Button. Folglich kann die Befehlsbindung auch in der Schaltfläche selbst erfolgen:
<Button Command="ApplicationCommands.Help">
<Button.CommandBindings>
<CommandBinding Command="Help"
CanExecute="Help_CanExecute"
Executed="Help_Executed" />
<Button.CommandBindings>
</Button>
Listing 28.7 Befehlsbindung innerhalb eines WPF-Elements
Andere Elemente, die sich ebenfalls mit dem Help-Befehl verknüpfen, können diese Befehlsbindung nicht nutzen – sie ist exklusiv für die Schaltfläche. Da die Bindungsangabe im Window für alle Elemente des Fensters, die den Befehl nutzen, gleichermaßen gilt, kann mit einer lokalen Kommandobindung in einem Steuerelement ein objektspezifisches Befehlsverhalten realisiert werden. Dabei wird auch gegebenenfalls eine ebenfalls vorhandene Befehlsbindung im Window für das entsprechende Element außer Kraft gesetzt. Genauso arbeitet übrigens die TextBox im Zusammenhang mit Befehlen, die auf die Zwischenablage zugreifen (Copy, Cut und Paste).
Leider ist es nicht möglich, Befehlsbindungen in der Datei App.xaml abzulegen, da die Klasse Application Befehlsbindungen nicht unterstützt. Somit bleibt Ihnen nichts anderes übrig, als alle notwendigen Befehlsbindungen in jedem Fenster ausdrücklich anzugeben.
28.3.3 Befehlsbindung mit Programmcode
Im folgenden Codefragment wird gezeigt, wie man die Befehlsbindung auch mit Code erstellen kann. Es ist sinnvoll, den Code im Konstruktor des Window hinter der Methode InitializeComponent anzugeben.
public MainWindow() {
InitializeComponent();
CommandBinding binding = new CommandBinding(ApplicationCommands.Help);
this.CommandBindings.Add(binding);
binding.Executed += Help_Executed;
binding.CanExecute += Help_CanExecute;
}
Listing 28.8 Befehlsbindung mit C#-Programmcode
28.3.4 Das Befehlsziel mit »CommandTarget« angeben
Wird ein Kommando abgesetzt, ist per Vorgabe das Element, das den Fokus hat, Ziel des Befehls. Mit der Eigenschaft CommandTarget können Sie auch ein anderes Ziel festlegen. Dabei handelt es sich um die Komponente, für die die Ereignisse Executed und CanExecute ausgelöst werden sollen.
Im Einführungsbeispiel Sample1 dieses Kapitels haben Sie gesehen, dass eine »Magie« dafür sorgt, dass die Befehle Copy und Paste (und natürlich auch Cut) auf dem richtigen Zielelement ausgeführt werden – nämlich der fokussierten TextBox. Im Hintergrund spielt sich dabei ein Prozess ab, der das fokussierte Steuerelement ermittelt. Dieser Prozess wird entweder von den Elementen Menu oder ToolBar gesteuert.
Das Verhalten ändert sich sofort, wenn sich die Schaltfläche, die den abonnierten Command auslöst, nicht innerhalb eines Menu-Elements oder einer ToolBar befindet. In diesem Fall muss das Ziel des Kommandos manuell angegeben werden. Dazu wird die Eigenschaft CommandTarget benutzt. Im Beispiel Sample2 wird das gezeigt.
Abbildung 28.2 Fenster des Beispiels »Sample2«
// Beispiel: ..\Kapitel 28\RoutedCommands\Sample2
<Window ...
Title="Sample2" Width="300" Height="135">
<StackPanel>
<WrapPanel>
<Button Width="130" Height="30" Command="ApplicationCommands.Copy"
CommandTarget="{Binding ElementName=text1}">Kopieren
</Button>
<Button Width="130" Height="30" Command="ApplicationCommands.Paste"
CommandTarget="{Binding ElementName=text1}">Einfügen
</Button>
</WrapPanel>
<TextBox Name="text1" Margin="5" Height="25" Background="AntiqueWhite"
FontSize="14"></TextBox>
<TextBox Name="text2" Margin="5,0,5,5" Height="25"
Background="AntiqueWhite" FontSize="14"></TextBox>
</StackPanel>
</Window>
Listing 28.9 Angabe des Ziels eines »Command«-Aufrufs
Beachten Sie, dass der Eigenschaft CommandTarget ein Binding-Objekt übergeben wird. Mit ElementName wird das Befehlsziel angegeben.
Das Festlegen des Befehlsziels mit CommandTarget hat den Nachteil, dass nur ein bestimmtes Element als Ziel angegeben werden kann. Im Beispielprogramm ist es die TextBox mit dem Bezeichner text1. Besser ist eine Lösung, bei der ein Findungsmechanismus die aktuell fokussierte TextBox ermittelt. Dazu wird für die beiden Schaltflächen aus Sample3 ein eigener Fokusbereich definiert. Die beiden Textboxen dürfen diesem Bereich nicht angehören. Die Folge ist, dass beim Klicken auf eine der Schaltflächen die entsprechende TextBox weiterhin den Fokus behält und dieser nicht an die angeklickte Schaltfläche weitergereicht wird.
Ein Fokusbereich wird festgelegt, indem man in einem Containerelement
FocusManager.IsFocusScope=True
setzt. Das Containerelement beschreibt damit den Fokusbereich.
// Beispiel: ..\Kapitel 28\RoutedCommands\Sample3
<StackPanel>
<WrapPanel FocusManager.IsFocusScope="True">
<Button Width="130" Height="30" Command="ApplicationCommands.Copy">
Kopieren
</Button>
<Button Width="130" Height="30" Command="ApplicationCommands.Paste">
Einfügen
</Button>
</WrapPanel>
<TextBox Name="text1" Margin="5" Height="25" Background="AntiqueWhite"
FontSize="14"></TextBox>
<TextBox Name="text2" Margin="5,0,5,5" Height="25" Background="AntiqueWhite"
FontSize="14"></TextBox>
</StackPanel>
Listing 28.10 Dem fokusbesitzenden Element das Kommando zuteilen
28.3.5 Zusätzliche Daten bereitstellen
Steuerelemente, die ein WPF-Kommando abonnieren, implementieren die Schnittstelle ICommandSource. Das Interface schreibt neben der Eigenschaft Command und CommandTarget auch die Eigenschaft CommandParameter vor. CommandParameter dient dazu, dem Kommando zusätzliche Daten zu übermitteln. Die Eigenschaft ist vom Typ Object.
<Window.CommandBindings>
<CommandBinding Executed="Help_Executed"
CanExecute="Help_CanExecute"/>
</Window.CommandBindings>
<Button Command="ApplicationCommands.Help"
CommandParameter="Guten Morgen liebe Sorgen .." Content="Help">
</Button>
Listing 28.11 Kommandoparameter übergeben
Entgegengenommen wird das Objekt im EventArgs-Parameter des Executed-Ereignishandlers. Hier liefert die Eigenschaft Parameter das übermittelte Objekt ab und kann innerhalb der Methode benutzt werden. Im folgenden Listing wird die Zeichenfolge ganz einfach nur in einem Hinweisfenster angezeigt:
private void Help_Executed(object sender, ExecutedRoutedEventArgs e) {
MessageBox.Show(e.Parameter.ToString());
}
Listing 28.12 Auswerten der Übergabeparameter
28.3.6 Befehle mit Maus oder Tastatur aufrufen
In Windows-Anwendungen sind häufig bestimmte Befehle mit Tastenkombinationen ausführbar. Denken Sie beispielsweise an die Taste , um eine laufende Aktion abzubrechen oder ein Fenster zu schließen. Das WPF-Befehlsmodell gestattet es Ihnen, einen WPF-Command entweder mit einer Taste bzw. Tastenkombination auszuführen oder bei Betätigung der Maus.
Zu diesem Zweck dienen die Klassen KeyBinding und MouseBinding. Um zum Beispiel die Tastenkombination + mit dem Befehl Help zu verbinden, genügt der folgende XAML-Code:
<KeyBinding Key="F10" Modifiers="Alt" Command="Help" />
Listing 28.13 Tastenkombination mit einem »Command« verknüpfen
Der Eigenschaft Key wird ein Member der gleichnamigen Enumeration übergeben, Modifiers gibt die Modifizierertaste an, die der Enumeration ModifierKeys entstammt.
Sehr ähnlich können Sie Mausaktionen mit einem Befehl verbinden.
<MouseBinding Gesture="Alt+RightClick" Command="Open" />
Listing 28.14 Maus an ein »Command« binden
In diesem Fall wird der Open-Befehl ausgeführt, wenn beim Klicken auf die rechte Maustaste die -Taste gedrückt ist. In der MouseAction-Enumeration sind die möglichen Mausaktionen beschrieben, die sich wieder mit einer Modifizierertaste kombinieren lassen und der Eigenschaft Gesture übergeben werden.
Alle Maus- und Tastaturzuordnungen werden in einer InputBindingsCollection zusammengefasst, die über die Eigenschaft InputBindings veröffentlicht wird. Es bietet sich an, die Collection dem Window zuzuordnen.
Im folgenden Beispielprogramm sind im Fenster nur zwei Schaltflächen enthalten, die die Befehle Help und Open abonnieren. Jedem der beiden Befehle ist eine Tastatur- und Mausaktion zugeordnet.
// Beispiel: ..\Kapitel 28\RoutedCommands\Sample4
<Window ... >
<Window.CommandBindings>
<CommandBinding Command="Help" CanExecute="Help_CanExecute"
Executed="Help_Executed" />
<CommandBinding Command="Open" CanExecute="Open_CanExecute"
Executed="Open_Executed" />
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Key="F10" Modifiers="Alt" Command="Help" />
<MouseBinding Gesture="Alt+WheelClick" Command="Help" />
<KeyBinding Key="F11" Modifiers="Control" Command="Open" />
<MouseBinding Gesture="Alt+RightClick" Command="Open" />
</Window.InputBindings>
<StackPanel>
<Button Height="45" Content="Help" Command="Help" />
<Button Height="45" Content="Open" Command="Open" />
</StackPanel>
</Window>
Listing 28.15 Das Beispielprogramm »Sample 4«
Zu diesem Beispiel gehört der folgende XAML-Code:
private void Help_Executed(object sender, ExecutedRoutedEventArgs e) {
MessageBox.Show("Logik des Help-Commands.");
}
private void Help_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = true;
}
private void Open_Executed(object sender, ExecutedRoutedEventArgs e) {
MessageBox.Show("Logik des Open-Commands.");
}
private void Open_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = true;
}
Listing 28.16 Der Ablauf beim Aufruf eines Commands
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.