27.2 Routing-Strategien
Auch wenn es bis jetzt den Anschein hat, dass die Ereignisse in der WPF keine Besonderheiten bergen – dem ist nicht so. Der Grund ist, dass in einer WPF-Benutzeroberfläche die Elemente ineinander verschachtelt werden können, beispielsweise:
<Window>
<StackPanel>
<Button Height="110" Width="250" Margin="10">
<StackPanel Orientation="Horizontal">
<Image Source="smile.jpg" Stretch="None" />
<Label VerticalAlignment="Center">Abbrechen</Label>
</StackPanel>
</Button>
[...]
</StackPanel>
</Window>
Listing 27.3 Verschachtelte Elemente im XAML-Code
Hier enthält ein StackPanel einen Button, der seinerseits als Inhaltseigenschaft ein weiteres StackPanel beschreibt. In diesem sind eine Image- und eine Label-Komponente horizontal angeordnet. Mit der Vorgabe dieser Struktur könnte zur Laufzeit das Folgende passieren: Der Anwender möchte auf den Button klicken, trifft dabei aber das Label. In herkömmlichen GUIs würde das Label sein eigenes Click-Ereignis auslösen, während der Button keine Reaktion zeigt. Das gilt natürlich auch, wenn auf das Image geklickt wird. Probleme dieser Art gibt es eigentlich in allen Benutzeroberflächen. Natürlich lässt sich im Falle unseres auf dem Button positionierten Labels auch in herkömmlichen GUIs das Click-Ereignis der Schaltfläche auslösen, aber der Aufwand dafür ist nicht unerheblich.
Um eine einfache Lösung zu ermöglichen, erweitert die WPF das traditionelle Konzept der Ereignisse um die Routed Events. Dabei müssen wir drei verschiedene Routing-Strategien unterscheiden:
- Direkte Events: Als direkte Ereignisse werden die Ereignisse bezeichnet, die nur von dem Element, bei dem das Ereignis aufgetreten ist, ausgelöst werden. Direkte Ereignisse unterscheiden sich nicht von den sonst üblichen Ereignissen im .NET Framework. Das Ereignis Click ist beispielsweise ein direkter Event.
- Tunneling-Events: Beim Tunneling beginnt die Ereigniskette beim Wurzelelement. In der Regel wird es sich dabei um ein Window handeln. Das Ereignis wird also zuerst im Window ausgelöst und danach im nächsten untergeordneten Element. Von dort setzt sich die Ereignisauslösung im nächsten untergeordneten Element fort. In Listing 27.3 wäre das das StackPanel. Das geschieht so lange, bis der eigentliche Auslöser erreicht ist. Tunneling-Events sind durch das Präfix Preview gekennzeichnet.
- Bubbling-Events: Das Bubbling beschreibt genau die Umkehrung des Tunneling-Prinzips. Zuerst wird der Event in der eigentlichen Komponente ausgelöst, anschließend wird die Ereignisauslösung der Reihe nach an die übergeordneten Elemente im Elementbaum weitergereicht bis hin zum Wurzelelement.
Bei den Routed Events kommt es also zu einer klaren Abfolge von Ereignisauslösungen. Legen wir das Listing 27.3 von oben zugrunde, würde zuerst das Ereignis im Window ausgelöst, anschließend im StackPanel und dann im Button. Da die Schaltfläche ihrerseits ein StackPanel enthält, ist dieses der nächste Empfänger. Zuletzt kommt dann eventuell noch das Image oder das Label.
Ist die Kette der Tunneling-Events durchlaufen, geht es mit den Bubbling-Events in entgegengesetzter Richtung zurück. Also zuerst der Bubbling-Event im Image oder Label, dann folgt das innere StackPanel, der Button, das äußere StackPanel und letztendlich das Window. Abbildung 27.1 verdeutlicht den Zusammenhang.
Abbildung 27.1 Elementbaum aus Listing 27.3 und Routed Events
Es werden in der Kette alle Handler aufgerufen, die sich bei dem Ereignis registriert haben. Behandelt ein Element innerhalb des Elementbaums das aufgetretene Ereignis nicht, wird die Ereigniskette nicht unterbrochen.
27.2.1 Der durchlaufene Elementbaum
In der WPF wird zwischen dem visuellen Elementbaum (Visual Tree) und dem logischen Elementbaum (Logical Tree) unterschieden (siehe Kapitel 18). In der Regel wird von einem Routed Event der Visual Tree durchlaufen. Aus der Wortwahl können Sie entnehmen, dass das nicht immer so ist. Hintergrund ist, dass Routed Events nur von Komponenten ausgelöst werden können, die auf eine der folgenden Klassen zurückzuführen sind: UIElement, UIElement3D oder ContentElement. ContentElement-Instanzen gehören aber nicht zum Visual Tree, sondern zum Logical Tree. Somit wäre es nicht richtig, zu sagen, dass ausschließlich der Virtual Tree durchlaufen wird – obwohl das im überwiegenden Teil der Fälle so ist.
27.2.2 Beispielanwendung
Die Routed Events wollen wir uns nun an einem Beispiel ansehen, dem das Codefragment von oben zugrunde liegt. Es werden hier die Ereignisse PreviewMouseRightButtonDown (getunnelt) und MouseRightButtonDown (gebubbelt) behandelt, die beim Drücken der rechten Maustaste ausgelöst werden. Damit die Abfolge der Ereigniskette auch optisch sichtbar wird, enthält das Window zusätzlich noch eine ListBox, in der sich jeder ausgelöste Event einträgt. Der entsprechende Code dafür ist in der Code-Behind-Datei zu finden.
// Beispiel: ..\Kapitel 27\RoutedEventSamples\Sample1
<Window ...
PreviewMouseRightButtonDown="PreviewMouseRight"
MouseRightButtonDown="MouseRight">
<StackPanel MouseRightButtonDown="MouseRight"
PreviewMouseRightButtonDown="PreviewMouseRight">
<Button MouseRightButtonDown="MouseRight"
PreviewMouseRightButtonDown="PreviewMouseRight">
<StackPanel Orientation="Horizontal" MouseRightButtonDown="MouseRight"
PreviewMouseRightButtonDown="PreviewMouseRight">
<Image MouseRightButtonDown="MouseRight"
PreviewMouseRightButtonDown="PreviewMouseRight"/>
<Label MouseRightButtonDown="MouseRight"
PreviewMouseRightButtonDown="PreviewMouseRight">
Abbrechen
</Label>
</StackPanel>
</Button>
<ListBox x:Name="listBox1"></ListBox>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<Button Click="Button_Click">Liste löschen</Button>
</Grid>
</StackPanel>
</Window>
// Code in der Code-Behind-Datei
private void MouseRight(object sender, MouseButtonEventArgs e) {
listBox1.Items.Add(sender.ToString());
}
private void PreviewMouseRight(object sender, MouseButtonEventArgs e) {
listBox1.Items.Add("Preview: " + sender.ToString());
}
private void Button_Click(object sender, RoutedEventArgs e) {
listBox1.Items.Clear();
}
Listing 27.4 Demonstration der Routed Events
Das Klicken auf das Image führt zu der in Abbildung 27.2 gezeigten Ausgabe.
Abbildung 27.2 Routed Events beim Klicken auf das Image mit der rechten Maustaste
27.2.3 Sonderfall der Mausereignisse
Werden die beiden Ereignisse PreviewMouseRightButtonDown und MouseRightButtonDown durch die Paare PreviewMouseLeftButtonDown/ MouseLeftButtonDown bzw. PreviewMouseDown/MouseDown ersetzt, tritt ein seltsames Phänomen auf: Bei den gebubbelten Events wird der letzte Ereignishandleraufruf vom StackPanel ausgeführt, das sich innerhalb des Buttons befindet. Alle anderen gebubbelten Events werden unterdrückt und nicht mehr ausgelöst. Offensichtlich wird die Ereigniskette an dieser Stelle unterbrochen (siehe Abbildung 27.3).
Ursache für diese Verhaltensweise ist, dass im Ereignis MouseLeftButtonDown (und auch MouseLeftButtonUp) das Click-Ereignis ausgelöst wird. Dieses ersetzt MouseLeftButtonDown und MouseLeftButtonUp und unterbricht den Bubbling-Prozess, indem der ausgelöste Event als behandelt gekennzeichnet wird. Wie man dennoch die gebubbelte Ereigniskette weiter fortsetzen kann, werde ich Ihnen später noch zeigen.
Abbildung 27.3 Routed Events, wenn die Ereigniskette des »MouseDown-Events« durchlaufen wird
Das Beispielprogramm, dem Abbildung 27.3 zugrunde liegt, ist unter \Kapitel 27\RoutedEventSamples\Sample4 zu finden. Wie Sie den beschriebenen Effekt unterbinden können, wird in Abschnitt 27.4.3 beschrieben.
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.