20.5 Navigation zwischen den Seiten
Ein NavigationWindow dient als Host für eine oder x-beliebig viele Pages. Um dem Benutzer die Navigation zwischen den Seiten der Anwendung zu ermöglichen, werden Ihnen mit
zwei Möglichkeiten angeboten. Darüber hinaus sind auch die Zustandsdaten in der navigationsauslösenden Seite ein wichtiger Gesichtspunkt. Bleiben sie erhalten, wenn zu der ursprünglichen Seite zurücknavigiert wird, oder sind sie verloren? Wie können Daten an eine neu anzuzeigende Seite übergeben werden? Diesen Fragen wollen wir uns in den folgenden Abschnitten widmen.
20.5.1 Navigation mit »HyperLink«
Verwenden Sie ein HyperLink-Element zur Navigation, müssen Sie sowohl die Zieladresse als auch einen Text angeben, auf den der Benutzer klicken soll.
<TextBlock>
<Hyperlink NavigateUri="Page2.xaml">Zur Seite 2</Hyperlink>
</TextBlock>
Listing 20.9 Hyperlink zur Navigation zu einer anderen Seite
Das HyperLink-Element muss entweder in einem TextBlock oder einem FlowDocument eingebettet sein. Das Ziel des Hyperlinks wird in der Eigenschaft NavigateUri angegeben. Dabei muss man beachten, dass die XAML-Datei angegeben wird und nicht der Klassenbezeichner. Anstatt einer Zeichenfolge lassen sich, wie in HTML-Seiten, auch Bilder verlinken.
<TextBlock>
<Hyperlink NavigateUri="Page2.xaml">
<Image Source="TraumFrau.jpg" />
</Hyperlink>
</TextBlock>
Listing 20.10 Ein Image zur Navigation über »HyperLink«
Befindet sich die verlinkte XAML-Datei im aktuellen Verzeichnis, genügt die einfache Dateiangabe wie gezeigt. In der Entwicklungsumgebung können Sie aber innerhalb eines Projekts auch eine Verzeichnisstruktur festlegen, beispielsweise um Ressourcen besser verwalten zu können. Angenommen, die Seite mit dem Namen Page1.xaml würde sich im Ordner Friends befinden, müssten Sie die Seite wie folgt adressieren:
NavigateUri="../Friends/Page1.xaml
Beachten Sie jedoch, dass es sich dabei nicht um ein Verzeichnis handelt, das nach dem Kompilieren im Ausgabeverzeichnis des Kompilats zu finden ist.
20.5.2 Der Verlauf der Navigation – das Journal
Die Navigation mit Seiten erinnert uns an die Navigation in einem Browser. Immer dann, wenn wir zu einer neuen Seite navigieren, wird die alte Seite in eine »Zurück«-Liste eingetragen. Klicken wir in der zweiten Seite auf die »Zurück«-Schaltfläche, wird die erste Seite aus einem Stack geholt und angezeigt, während die zweite Seite in eine »Vorwärts«-Liste eingetragen wird.
Die Vorgänge, die sich dabei in WPF-Navigationsfenstern bei der Navigation im Hintergrund abspielen, sind ziemlich komplex. Nehmen wir an, in der WPF-Anwendung wären die beiden Seiten Page1 und Page2 vorhanden. Unterstellen wir nun eine Navigation von Page1 nach Page2 und wieder zurück zu Page1. Der letzte Schritt, zurück zu Page1 zu navigieren, resultiert in einer neuen Instanz von Page1.
So weit ist das noch alles sofort verständlich. Nun bauen wir aber in Page1 neben dem Hyperlink auf Page2 eine TextBox ein.
<StackPanel>
<TextBlock Height="15" Margin="20, 20, 20, 0">
<Hyperlink NavigateUri="Page2.xaml">Seite2 anzeigen</Hyperlink>
</TextBlock>
<TextBox Height="25" Margin="20" Background="AntiqueWhite" />
</StackPanel>
Listing 20.11 Inhalt der »Page1«
Starten Sie nun die Anwendung, und tragen Sie einen beliebigen Text in die TextBox ein. Navigieren Sie anschließend zu Page2 und danach zurück zu Page1. Die TextBox hat ihren Inhalt behalten. Stimmt jetzt die Aussage etwa nicht, dass bei der Navigation ein neues Page-Objekt erstellt wird?
Doch, sie stimmt. Aber wie ich schon zuvor erwähnt habe, sind die Vorgänge, die sich dabei abspielen, sehr komplex. Zunächst einmal werden tatsächlich die Page-Objekte beim Verlassen während der Navigation zerstört. Das hat einen ziemlich einfachen Grund, denn eine Seite kann sehr viele speicherintensive Ressourcen beschreiben. Denken Sie dabei nur an Grafiken, Video- bzw. Audiomedien oder Animationen. In einer Anwendung mit sehr vielen Seiten, in denen die Page-Objekte nicht zerstört würden, hätte das eine extreme Beanspruchung der Systemressourcen zur Folge. Daher speichert die WPF nur die Zustände der Steuerelemente der Seite. Führt die Navigation wieder zurück zu der ursprünglichen Seite, werden die Zustände der Steuerelemente wiederhergestellt. Dieses Verfahren schont die Ressourcen, weil nicht der gesamte visuelle Baum der Objekte gespeichert werden muss.
Nun müssen wir noch ein weiteres Detail betrachten, denn es werden nicht alle Steuerelementzustände einer Seite gespeichert. Sie können das sehr leicht testen, wenn Sie den XAML-Code aus Listing 20.11 um eine Schaltfläche erweitern, in der die Hintergrundfarbe der Seite neu festgelegt wird (siehe Listing 20.12).
// im XAML-Code
<StackPanel>
<TextBlock Height="15" Margin="20, 20, 20, 0">
<Hyperlink NavigateUri="Page2.xaml">Seite2 anzeigen</Hyperlink>
</TextBlock>
<TextBox Height="25" Margin="20" Background="AntiqueWhite" />
<Button Content="Button1" Width="100" Click="Button1_Click"/>
</StackPanel>
// in der Code-Behind-Datei
private void Button1_Click(object sender, RoutedEventArgs e) {
Background = new SolidColorBrush(Colors.Yellow);
}
Listing 20.12 Inhalt von »Page1«, ergänzt um eine Schaltfläche
Sie finden dieses Beispiel auf der Buch-DVD unter \Kapitel 20\NavigationHistory.
Sie werden feststellen, dass beim Zurücknavigieren die eingestellte Hintergrundfarbe verlorengeht, der Inhalt der TextBox jedoch nicht. Das Beispiel beweist, dass nicht die Zustände aller Eigenschaften gespeichert werden, sondern nur ein bestimmter Teil. Tatsächlich verfährt die WPF in der Weise, dass der gesamte Elementbaum durchlaufen und nach allen abhängigen Eigenschaften gesucht wird. Abhängige Eigenschaften haben eine ganze Reihe von zusätzlichen Eigenschaften, den sogenannten Metadaten. Zu diesen gehört auch das Journal-Flag, mit dem eine abhängige Eigenschaft kennzeichnet, dass der Zustand bei einer Navigation protokolliert werden soll.
Abhängige Eigenschaften sind vollkommen anders implementiert als CLR-Eigenschaften. Daher werden Sie eben mit etwas Verwunderung zur Kenntnis genommen haben, dass abhängige Eigenschaften durch Metadaten charakterisiert werden und auch ein Journal-Flag haben. Seien Sie geduldig, wir werden in einem späteren Kapitel noch sehen, wie abhängige Eigenschaften implementiert werden.
Was also können Sie tun, wenn Sie entgegen dem Standardverhalten alle Daten bewahren wollen? Es gibt dazu mehrere Ansätze. Der sicherlich einfachste Weg führt über die Eigenschaft KeepAlive der Seite, die Sie auf true festlegen können. Der Standard ist false. Damit werden alle Daten einer Seite gespeichert und nicht nur die mit dem Journal-Flag.
Die KeepAlive-Option sollten Sie mit Bedacht einsetzen und wirklich nur die Seiten so ausstatten, bei denen es wirklich notwendig ist. Wie angedeutet gibt es noch weitere Möglichkeiten, die aber im Rahmen dieses Buches nicht erörtert werden.
20.5.3 Navigation mit »NavigationService«
Das HyperLink-Element eignet sich besonders zur Angabe im XAML-Code. Das ist sehr einfach umzusetzen, beispielsweise wenn Sie mit den Seitenaufrufen sequenziell Benutzereingaben anfordern, ähnlich wie bei einem Assistenten. In komplexeren Szenarien stellt sich das HyperLink-Element jedoch sehr schnell als unzureichend heraus. An dessen Stelle betritt die Klasse NavigationService die Bühne, die sowohl von einem NavigationWindow als auch von einem Frame zur Verfügung gestellt wird. Sie können sich die Referenz auf das NavigationService-Objekt besorgen, wenn Sie die statische Methode GetNavigationService aufrufen und die Referenz auf den Host als Argument übergeben, auf dessen NavigationService Sie zurückgreifen wollen:
NavigationService nav = NavigationService.GetNavigationService(this);
Listing 20.13 Abrufen des »NavigationService«-Objekts eines Hosts
Eine andere Möglichkeit bietet das Page-Objekt mit seiner Eigenschaft NavigationService. Diese liefert die Referenz auf den NavigationService des eigenen Hosts.
Die wohl wichtigste Methode des NavigationService-Objekts ist Navigate, der Sie die Seite als URI übergeben, zu der navigiert werden soll.
NavigationService nav = NavigationService.GetNavigationService(this);
Uri uri = new Uri("Page2.xaml", UriKind.RelativeOrAbsolute);
nav.Navigate(uri);
Listing 20.14 Übergabe eines »Uri«-Objekts an die Methode »Navigate«
Erwähnenswert im Zusammenhang mit Navigate ist, dass die Methode asynchron aufgerufen wird. Dieses Verhalten ist natürlich sinnvoll, da eine Seite durchaus auch Inhalte haben kann, deren Ladevorgang verhältnismäßig lange dauert. Hier seien noch einmal Mediendateien exemplarisch genannt.
Eine weitere Möglichkeit bietet sich, indem Sie ein Objekt der Page neu erzeugen, zu der navigiert werden soll, und dieses als Argument an Navigate übergeben:
NavigationService nav = NavigationService.GetNavigationService(this);
nav.Navigate(new Page2());
Listing 20.15 Übergabe eines »Page«-Objekts an die Methode »Navigate«
Sie dürfen die beiden in Listing 20.14 und Listing 20.15 nicht beliebig verwenden, weil es einen großen Unterschied gibt: Während bei der Übergabe des URI (siehe Listing 20.14) nur die entsprechenden Journal-befähigten Eigenschaften protokolliert werden, ist es beim Erzeugen eines neuen Page-Objekts über den Konstruktor (wie in Listing 20.15 gezeigt) das gesamte Objekt.
Diesem Nachteil der Objekterstellung über den Konstruktor steht aber andererseits der Vorteil gegenüber, dass Sie einen parametrisierten Konstruktor aufrufen können und damit der neu anzuzeigenden Seite Daten übergeben können.
Wollen Sie eine über die standardmäßigen Navigationsschaltflächen des Hosts hinausgehende Navigation in einer Seite realisieren (beispielsweise mit separaten Schaltflächen), können Sie die Methoden GoForward oder GoBack des NavigationService-Objekts benutzen. Beide Methoden lösen eine Ausnahme vom Typ InvalidOperationException aus, wenn die Navigation fehlschlägt. Um das zu vermeiden, sollte daher zuerst geprüft werden, ob eine Navigation zurück oder nach vorne überhaupt möglich ist. Auch dazu hilft Ihnen das NavigationService-Objekt weiter, diesmal mit seinen beiden Eigenschaften CanGoBack und CanGoForward. Beide liefern true als Ergebnis, wenn ein Navigationsverlauf nach vorne oder zurück möglich ist.
Wie bereits angedeutet, werden die Inhalte der aufgerufenen Seite mit der Methode Navigate asynchron abgerufen. So wundert es nicht, dass das NavigationService-Objekt mit StopLoading eine Methode bereitstellt, um die Navigation abzubrechen oder mit der Methode Refresh eine Seite neu zu laden.
Ereignisse des »NavigationService«-Objekts
Das Navigieren zu einer anderen Seite mit dem NavigationService-Objekt ist ein relativ komplexer Vorgang. Dabei werden mehrere Schritte der Reihe nach ausgeführt:
- Die aufgerufene Seite muss zuerst lokalisiert werden.
- Die Seiteninformationen müssen abgerufen werden.
- Alle Ressourcen, die in der zu ladenden Seite enthalten sind, müssen geladen werden.
- Erst nach den genannten drei Schritten ist die Seite so weit vorbereitet, dass sie erstellt werden kann, was zu der Auslösung der Ereignisse Initialized und Loaded der Seite führt. Das gilt natürlich nicht, wenn die Informationen aus dem Journal entnommen werden.
- Zum Schluss wird die Seite dargestellt (gerendert).
Während des beschriebenen Ablaufs werden eine Reihe von Ereignissen durch das NavigationService-Objekt ausgelöst. In Tabelle 20.3 sind die wichtigsten Ereignisse aufgeführt.
Ereignis | Beschreibung |
Dieses Ereignis wird kurz vor dem Seitenwechsel ausgelöst. In diesem Ereignis kann der Seitenwechsel im letzten Moment noch abgebrochen werden, indem die Eigenschaft Cancel des EventArgs-Parameters auf true gesetzt wird. |
|
Wird dieses Ereignis ausgelöst, hat die Navigation bereits begonnen. |
|
Dieses Ereignis tritt während des Ladevorgangs der Seite permanent auf. Im Ereignishandler lässt sich das EventArgs-Objekt auswerten. So können Sie die Eigenschaft MaxBytes auswerten, um zu erfahren, wie viele Daten die Seite insgesamt erfordert, während ReadBytes angibt, wie viele Daten bereits übertragen worden sind. Dieses Ereignis wird übrigens immer dann ausgelöst, wenn 1 KByte Daten geladen worden sind. Es eignet sich daher gut, um den Ladefortschritt anzuzeigen. |
|
Dieses Ereignis wird ausgelöst, wenn der Inhalt der Seite, zu der navigiert worden ist, geladen wurde und die Seite mit dem Rendering begonnen hat. |
|
Das Ereignis wird ausgelöst, wenn während des Ladevorgangs ein Fehler auftritt. Das kann beispielsweise der Fall sein, wenn die angegebene Seite nicht gefunden wird. |
|
Dieses Ereignis wird infolge des Methodenaufrufs StopLoading ausgelöst. |
Die aufgeführten Ereignisse werden auch von den Klassen Application, NavigationWindow und Frame ausgelöst. Somit ist es möglich, in Anwendungen, in denen mehrere Navigations-Hosts enthalten sind, die Navigation für jeden Host separat zu behandeln oder, über das Application-Objekt, eine gemeinsame Behandlung zu implementieren. Das Page-Objekt selbst löst im Zusammenhang mit der Navigation keine Ereignisse aus.
20.5.4 Navigation im Internet
Die Navigation ist nicht zwangsläufig auf die Seiten der aktuellen Anwendung beschränkt. Sie können der Eigenschaft Source des NavigationWindow auch eine Webadresse übergeben. Gleiches gilt auch für die Methode Navigate. Allerdings müssen Sie dann auch der Methode mitteilen, dass es sich um eine Absolutadresse handelt, beispielsweise mit:
nav.Navigate(new Uri("http://dotnet-training.de", UriKind.Absolute));
Sollten Sie nun der Meinung sein, Sie hätten es hierbei mit einer abgespeckten Variante eines WebBrowser-Steuerelements zu tun, liegen Sie falsch. Das Navigationsverhalten des NavigationWindow protokolliert unverständlicherweise nicht die besuchten Seiten im Internet. Drücken Sie die Zurück-Taste im Fenster, landen Sie wieder bei der Page, von der aus Sie sich die erste Webseite haben anzeigen lassen.
20.5.5 Navigieren mit dem Ereignis »RequestNavigate« des »HyperLink«-Elements
Eine weitere Variante zur Navigation bietet das Ereignis RequestNavigate des HyperLink-Objekts. Sehen wir uns das am besten an einem Beispiel an. Beginnen wir mit dem XAML-Code, in dem an das Ereignis RequestNavigate ein Ereignishandler gebunden wird.
<TextBlock>
<Hyperlink NavigateUri="Page2.xaml"
RequestNavigate="Hyperlink_RequestNavigate">
Zur Seite 2
</Hyperlink>
</TextBlock>
Listing 20.16 Ereignishandler im XAML-Code registrieren
In der Code-Behind-Datei der Seite wird ebenfalls die Methode Navigate von NavigationService aufgerufen. Als Argument wird die Eigenschaft Uri des EventArgs-Objekts weitergeleitet. Durch das Setzen von true der Eigenschaft Handled teilen wir dem Objekt mit, die Aktion selbst übernommen zu haben. Alle weiteren Operationen des Hyperlinks werden damit unterbunden.
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e){
NavigationService.Navigate(e.Uri);
e.Handled = true;
}
Listing 20.17 Der Ereignishandler des Events »RequestNavigate«
Mit dem Ereignishandler gewinnen wir ein hohes Maß an Flexibilität, die uns gestattet, parallel zur Navigation weitere Operationen auszuführen.
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.