x:Code-Abschnitte.
Ein Code-Abschnitt innerhalb von XAML muss in ein x:Code-Element eingebettet werden. Da der Code beliebig sein kann, also auch Operatoren wie < und > enthalten kann und er
sich in einer XML-Datei befindet (XAML ist ja letzten Endes XML) sollte dieser Code-Abschnitt in einen CDATA-Abschnitt eingeschlossen werden. Dadurch wird der Inhalt durch einen XML-Parser (also auch bei der Interpretation der XAML-Datei) nicht als XML-Daten ausgewertet. Ansonsten ist die Verwendung des CDATA-Abschnitts optional.
Eine Beschränkung dieser Lösung ist beispielsweise, dass nur Code für die partielle Klasse eingefügt werden kann, welche zur XAML-Datei gehört. Separate Klassen können z.B. nicht in einem solchen Abschnitt deklariert werden (ausgenommen innere Klassen).
Innerhalb einer XAML-Datei werden ein Button und eine TextBox innerhalb eines StackPanels eingefügt. Der Button ist mit dem Text Klick mich beschriftet, die TextBox enthält den Text Hallo. Um auf das
Klicken des Buttons zu reagieren, muss das Attribut Click, welches hier dem Click-Ereignis entspricht, mit dem Namen einer Methode verknüpft werden, die
auf dieses Ereignis reagiert. In diesem Fall heißt die Methode ClickMich(). Durch den Klick auf den Button soll der Inhalt der TextBox geändert werden.
Damit Sie das TextBox-Element ansprechen können, müssen Sie einen Namen für die TextBox vergeben. Es wird der Name TbInfo über das Attribut Name zugewiesen.
Um Code in der XAML-Datei unterzubringen, wird das Element x:Code verwendet. In einem CDATA-Abschnitt wird dann der Code für die Ereignisbehandlung untergebracht, der selbsterklärend sein sollte.
Abbildung 4.1: Ereignisse in XAML verpackt
<StackPanel Margin="10" HorizontalAlignment="Left">
<Button Click="ClickMich" Width="100">Klick mich</Button>
<x:Code>
<![CDATA[
void ClickMich(object sender, RoutedEventArgs args)
{
TbInfo.Text = "Der Button wurde geklickt";
}
]]>
</x:Code>
<TextBox Name="TbInfo" Width="140">Hallo</TextBox>
</StackPanel>
Listing 4.1: Beispiele\Kap04\EreignisCodeInXamlProj\Window1.xamlHINWEIS: Die Verwendung von Code innerhalb der XAML-Datei ist zwar eine mögliche Lösung für eine Ereignisbehandlung, allerdings sollte davon eher weniger Gebrauch gemacht werden. Ziel sollte es stattdessen sein, eine saubere Trennung zwischen dem Design der Oberfläche und der dahinter liegenden Programmlogik zu schaffen.
Der Code wird jetzt aber in der Code-Behind-Datei hinterlegt. Dazu muss die Methode entsprechend der Signatur des Ereignisses erstellt und in der partiellen Klasse der Code-Behind-Datei, welche der XAML-Datei zugeordnet ist, implementiert werden.
Das folgende Beispiel verknüpft wiederum das Click-Ereignis eines Buttons mit einer Methode namens ClickMich(). Diesmal wird der Code allerdings in die Code-Behind-Datei des Fensters ausgelagert.
<StackPanel Margin="10" HorizontalAlignment="Left"> <Button Click="ClickMich" Width="100">Klick mich</Button> <TextBox Name="TbInfo" Width="140">Hallo</TextBox> </StackPanel>Listing 4.2: Beispiele\Kap04\EreignisCodeInCodeBehindProj\Window1.xaml
Die Methode ClickMich() wird in der Fensterklasse Window1 deklariert. Der Name der TextBox steht ebenfalls im Code zur Verfügung, auch wenn er in der XAML-Datei
über das Attribut Name vergeben wurde.
namespace EreignisCodeInCodeBehindProj
{
public partial class Window1: System.Windows.Window
{
public Window1()
{
InitializeComponent();
}
private void ClickMich(object sender, RoutedEventArgs e)
{
TbInfo.Text = "Der Button wurde geklickt";
}
}
}
Listing 4.3: Beispiele\Kap04\EreignisCodeInCodeBehindProj\Window1.xaml.cs
Application aus dem Namespace System.Windows.
In der XAML-Datei mit dem Standardnamen App.xaml befindet sich ein wesentlicher Eintrag. Gemeint ist das Attribut StartupUri, welches den Namen
der Fensterklasse enthält, von der eine Instanz nach dem Start angezeigt werden soll, das Hauptfenster sozusagen. Man kann hier aber auch auf andere
Anwendungsereignisse reagieren, z.B. wenn die Anwendung geladen oder beendet wird. Die Klasse Application enthält dazu verschiedene Ereignisse, von denen einige in
der folgenden Tabelle gezeigt werden.
| Ereignis | Beschreibung |
| Activated | Die Anwendung gelangt in den Vordergrund. |
| Deactivated | Die Anwendung ist keine Vordergrundanwendung mehr. |
| DispatcherUnhandledException | Wird ausgelöst, wenn eine nicht behandelte Exception aufgetreten ist. |
| Exit | Die Anwendung wird beendet. |
| SessionEnding | Soll die aktuelle Windowssitzung beendet werden, wird dieses Ereignis ausgelöst. Die Anwendung hat noch die Möglichkeit, das Beenden zu unterbinden. Beachten Sie allerdings dass es Verfahren gibt, auch ohne Rücksicht auf andere Anwendungen Windows zu beenden (z.B. durch einen Blue Screen). |
| Startup | Die Run()-Methode der Anwendung wurde aufgerufen, d.h. die Anwendung wurde gestartet. |
Tabelle 4.1: Einige Ereignisse der Klasse Application
Wenn Sie eine Windows-Anwendung mit der WPF erzeugen, wird das Hauptfenster über den XAML-Code StartupUri="Window1.xaml" in der Datei App.xaml festgelegt. Sie können das Hauptfenster
aber auch anders erzeugen, z.B. manuell im Ereignis Startup. Dazu wird die Angabe der StartupUri entfernt und stattdessen eine Verknüpfung zum
Ereignis Startup hinzugefügt. Alternativ lassen Sie auch diese Ereignisverknüpfung weg und überschreiben stattdessen die Methode OnStartup() der
Klasse Application, welche automatisch beim Ereignis Startup aufgerufen wird. In diesem Fall gibt es keinen Parameter vom Typ object und
Sie müssen zum Überschreiben protected override angeben. Kurz gesagt gibt es zahlreiche Varianten, Initialisierungscode unterzubringen.
Um nicht behandelte Exceptions abzufangen, kann auf das Ereignis DispatcherUnhandledException reagiert werden. Beim Auftreten einer solchen unbehandelten Exception wird
jetzt die Methode MainExHandler() aufgerufen. Darin kann die Exception ausgewertet und durch das Setzen der Eigenschaft Handled des Parameters vom Typ DispatcherUnhandledExceptionEventArgs auf
true als behandelt markiert werden. Die Exception selbst wird im Hauptfenster der Anwendung durch einen Klick auf einen Button ausgelöst.
<Application...
<!-- In XAML sind dieser und der folg. Kommentar ungültig! -->
<!-- StartupUri="Window1.xaml" -->
Startup="OnStartup"
DispatcherUnhandledException="MainExHandler">
<Application.Resources>
</Application.Resources>
</Application>
Listing 4.4: Beispiele\Kap04\AnwendungsEreignisseProj\MyApp.xaml
Entweder Sie implementieren eine Ereignisbehandlung wie sie im folgenden Listing gezeigt wird oder Sie entfernen die Verknüpfung zwischen dem Startereignis Startup und der Methode OnStartup(),
kommentieren die Methode OnStartup() im folgenden Code-Teil aus und verwenden die überschriebene Methode OnStartup(), die hier denselben Namen besitzt.
public partial class App: System.Windows.Application
{
void OnStartup(object sender, StartupEventArgs e)
{
Window1 mainWindow = new Window1();
mainWindow.Show();
}
// protected override void OnStartup(StartupEventArgs e)
// {
// Window1 mainWindow = new Window1();
// mainWindow.Show();
// }
private void MainExHandler(object sender, DispatcherUnhandledExceptionEventArgs e)
{
MessageBox.Show("Exception aufgetreten: " + e.Exception.Message);
e.Handled = true;
}
}
Listing 4.5: Beispiele\Kap04\AnwendungsEreignisseProj\MyApp.xaml.cs
<Window ...>
<Grid>
<Button Click="ErzeugeException">Exception auslösen</Button>
</Grid>
</Window>
Listing 4.6: Beispiele\Kap04\AnwendungsEreignisseProj\Window1.xaml
private void ErzeugeException(object sender, RoutedEventArgs e)
{
throw new Exception("Kabumm");
}
Listing 4.7: Beispiele\Kap04\AnwendungsEreignisseProj\Window1.xaml.cs
Window einige interessante Ereignisse. Insbesondere kann für die Initialisierung der Oberfläche auf das Ereignis Loaded zurückgegriffen werden. Hier können z.B. noch weitere Komponenten dynamisch der Oberfläche hinzugefügt werden oder Sie können vorhandene Komponenten konfigurieren. Die folgende Tabelle listet einige Ereignisse der Klasse Window auf.
| Ereignis | Beschreibung |
| Activated | Das Fenster gelangt in den Vordergrund. |
| Closed | Das Fenster wird geschlossen. |
| Closing | Wird aufgerufen wenn das Fenster geschlossen werden soll. Dies kann über die Eigenschaft Cancel des Parameters vom Typ CancelEventArgs aber verhindert werden. Weisen Sie ihm dazu den Wert true zu. |
| Deactivated | Das Fenster wurde deaktiviert, d.h. ein anderes Fenster wurde ausgewählt. |
| Initialized | Direkt nach der Erzeugung des Objekts durch den Konstruktor wird dieses Ereignis ausgelöst. Es sollten hier allerdings noch keine Zugriffe auf die Werte der Komponenten durchgeführt werden, da diese zum Teil noch unbestimmt sein können. |
| Loaded | Bringen Sie hier den Code unter, der direkt nach dem Initialisieren aller Elemente eines Fensters ausgeführt werden soll. Das Ereignis Initialized ist dazu meist ungeeignet, da das Layout der Komponenten noch nicht abgeschlossen ist. |
| LocationChanged | Die Position des Fensters hat sich geändert. |
| SizeChanged | Die Größe des Fensters hat sich geändert. |
| StateChanged | Der Fensterstatus (minimiert, maximiert, normal) hat sich geändert. |
| Unloaded | Das Fenster ist zerstört. Ein Zugriff auf die Elemente ist nicht mehr möglich. |
Tabelle 4.2: Einige Ereignisse der Klasse Window
Die Anwendung verfügt über zwei Schaltflächen, von denen eine bereits mit einem Click-Ereignishandler verknüpft ist. Die andere Schaltfläche verfügt über den Namen BtnEreignis, unter dem Sie später angesprochen werden kann. In der Methode ErzeugeVerknuepfung() wird ein neuer Delegate vom Typ RoutedEventHandler erzeugt, dem als Parameter ein Verweis auf die Methode übergeben wird, welche die Ereignisbehandlung implementiert. Dieser EreignisHandler wird dem Click-Ereignis hinzugefügt. Danach wird die Beschriftung der Schaltfläche nach Jetzt verknüpft geändert. Die Methode BtnEreignisClick() besitzt wieder den Standardaufbau der Click-Ereignisbehandlung und zeigt eine MessageBox an.
<StackPanel>
<Button Click="ErzeugeVerknuepfung">
Verknüpfung herstellen
</Button>
<Button Name="BtnEreignis">Keine Verknüpfung</Button>
</StackPanel>
Listing 4.8: Beispiele\Kap04\DynamischeEreignisseProj\Window1.xaml
private void ErzeugeVerknuepfung(object sender, RoutedEventArgs e)
{
BtnEreignis.Click += new RoutedEventHandler(BtnEreignisClick);
BtnEreignis.Content = "Jetzt verknüpft";
}
private void BtnEreignisClick(object sender, RoutedEventArgs e)
{
MessageBox.Show("Klappt");
}
Listing 4.9: Beispiele\Kap04\DynamischeEreignisseProj\Window1.xaml.cs
RoutedEventArgs bereits mehrfach in den Ereignishandlern verwendet.
Bei einem RoutedEvent wird ein Klick entweder an das jeweils untergeordnete oder übergeordnete Element weitergereicht oder es wird wie bisher direkt am auftreffenden Element konsumiert. Die WPF verwendet drei verschiedene Mechanismen zum Event Routing, d.h. auf welchem Wege Events durch den Elementbaum durchgereicht werden.
Click.
Preview versehen, allerdings ist diese Namensgebung keine Pflicht (z.B. bei der Erstellung eigener Ereignisse). Auf diese Weise kann z.B. ein übergeordnetes Element sämtliche Tastatureingaben überprüfen, bevor sie im konkreten untergeordneten Element verarbeitet werden.
Abbildung 4.2: Ereignisweiterleitung
Die meisten RoutedEvents haben auch ein korrespondierendes Preview-Event. So hat z.B. ein gebubbeltes MouseLeftButtonDown-Ereignis ein korrespondierendes getunneltes Ereignis PreviewMouseLeftButtonDown. Beide basieren auf demselben Ereignistyp MouseButtonEventHandler. Einige spezielle Ereignisse sind vom Typ Direct wie z.B. die Ereignisse MouseEnter und MouseLeave. Direkte Ereignisse werden direkt an der auftretenden Komponente verarbeitet (oder auch nicht). Sie werden weder gebubbelt noch getunnelt. Bei den beiden hier erwähnten Ereignissen wäre das auch nicht sonderlich sinnvoll, denn das Ereignis MouseEnter soll ja gerade dann eintreten, wenn die Maus eine ganz bestimmte Komponente beginnt zu überfahren.
Erwähnenswert ist auch das Ereignis Click. Dieses Ereignis wird in der Klasse ButtonBase bereitgestellt und basiert auf den Ereignissen MouseLeftButtonDown und MouseLeftButtonUp bzw.
wird es in diesem Ereignissen ausgelöst. Es ist ein Bubbled Event und besitzt kein äquivalentes getunneltes Event. Da es in den Ereignissen MouseLeftButtonDown und
MouseLeftButtonUp ausgelöst wird und diese eigentlich ersetzen soll, wird es als behandelt markiert. Das heißt, die beiden Ereignisse MouseLeftButtonDown und MouseLeftButtonUp werden
nicht bis zur Wurzel "durchgebubbelt".
Um Festzustellen, von welchem Typ ein Ereignis ist, verwenden Sie die MSDN-Hilfe. Im Abschnitt Routed Event Information wird der Ereignistyp, die Routing Strategie sowie der Typ des Delegates angegeben. Gibt es ein korrespondierendes Ereignis, wird dies unter den Standardinformationen ebenfalls angegeben. Kann außerdem eine Methode überschrieben werden, die beim Auftreten des Ereignisses aufgerufen wird, steht diese ebenfalls hier.
Abbildung 4.3: Informationen zum Ereignistyp ermitteln
Zur Veranschaulichung der Ereignisweiterleitung verschachtelt das Beispiel einige Komponenten. Insbesondere besteht der oben angedeutete Button aus verschiedenen Komponenten. Wird nun
beispielsweise auf das Label mit dem Text Hallo geklickt, ergibt sich ein recht umfangreicher Ereignisfluss. Die Komponenten werden mit jeweils einem
Bubbled- und einem Tunneled-Ereignis verknüpft. Außerdem werden das Window-, das äußere StackPanel- sowie das Button-Element noch mit
dem Ereignis Click verknüpft.
Im Ergebnis sehen Sie nach einem Klick auf den Text Hallo, die Ereignisfolge aus Abbildung 4.3. Insbesondere fällt dabei auf, dass plötzlich nach den MouseDown-Ereignissen Click-Ereignisse
auftreten. Dies ist genau die Stelle, an der der Button das Klicken mit der linken Maustaste in ein Click-Ereignis umwandelt und das originale Ereignis als behandelt
(Handled=true) markiert.
Abbildung 4.4: Protokollierter Ereignisfluss
<Window ... MouseDown="RoutedClick" PreviewMouseDown="TunnelClick"
ButtonBase.Click="RoutedClick">
<StackPanel Name="StackPanelAussen" MouseDown="RoutedClick"
PreviewMouseDown="TunnelClick" ButtonBase.Click="RoutedClick">
<Button Click="RoutedClick" MouseDown="RoutedClick"
PreviewMouseDown="TunnelClick" Name="ButtonAussen">
<StackPanel Orientation="Horizontal"
Name="StackPanelMitte" MouseDown="RoutedClick"
PreviewMouseDown="TunnelClick">
<Border Background="LightBlue" BorderBrush="Black"
BorderThickness="2" Name="Border" MouseDown="RoutedClick"
PreviewMouseDown="TunnelClick">
<StackPanel Orientation="Horizontal" Name="StackPanelInnen"
MouseDown="RoutedClick" PreviewMouseDown="TunnelClick">
<Label Name="Label" MouseDown="RoutedClick"
PreviewMouseDown="TunnelClick">Hallo</Label>
</StackPanel>
</Border>
</StackPanel>
</Button>
<TextBox>Hallo</TextBox>
<ListBox Name="EventLB" Height="250"/>
</StackPanel>
</Window>
Listing 4.10: Beispiele\Kap04\RoutedEventsProj\Window1.xaml
private void RoutedClick(object sender, RoutedEventArgs e)
{
EventLB.Items.Add(e.RoutedEvent.ToString() + "> " + sender.GetType().ToString() +
", " + (sender as FrameworkElement).Name);
e.Handled = false;
}
private void TunnelClick(object sender, MouseButtonEventArgs e)
{
EventLB.Items.Add(e.RoutedEvent.ToString() + "> " + sender.GetType().ToString() +
", " + (sender as FrameworkElement).Name);
e.Handled = false;
}
Listing 4.11: Beispiele\Kap04\RoutedEventsProj\Window1.xaml.cs
UIElement werden für die meisten Ereignisse virtuelle Methoden bereitgestellt, die zur Behandlung des Ereignisses einfach überschrieben werden müssen. Dazu muss allerdings
eine neue Klasse von der betreffenden Elementklasse abgeleitet werden, um diese Möglichkeit zu nutzen. Im Falle eines Fensters oder der gesamten Anwendung ist das kein Problem, da für beide
sowieso neue Klassen erzeugt werden (siehe Beispiel im Abschnitt Anwendungsereignisse). Wollen Sie allerdings eine solche Methode für einen Button überschreiben, müssen
Sie eine neue Klasse erzeugen, die von Button abgeleitet ist.
Beim Überschreiben der Methoden muss darauf geachtet werden, dass sie mit protected override gekennzeichnet werden. Für das Ereignis Click müsste z.B. die Methode OnClick() überschrieben werden.
Im Beispiel wird ein neuer Button mit dem Namen NewButton deklariert. Dieser wird dann innerhalb einer XAML-Datei genutzt. Dazu muss der Namespace des Buttons in der XAML-Datei eingebunden
werden. Über den darüber neu definierten Alias frischa wird später der Button referenziert.
Im neuen Button wird nur die Methode OnClick() überschrieben und darin eine MessageBox angezeigt.
<Window ...
xmlns:frischa="clr-namespace:EreignisMethodenProj"
Title="EreignisMethodenProj" Height="300" Width="300">
<StackPanel>
<frischa:NewButton>Klick mich</frischa:NewButton>
</StackPanel>
</Window>
Listing 4.20: Beispiele\Kap04\EreignisMethodenProj\Window1.xaml
using System;
using System.Windows.Controls;
using System.Windows;
namespace EreignisMethodenProj
{
class NewButton: Button
{
protected override void OnClick()
{
base.OnClick();
MessageBox.Show("Klick");
}
}
}
Listing 4.21: Beispiele\Kap04\EreignisMethodenProj\NewButton.cs