Muokkaa

Jaa


Navigation Overview

Windows Presentation Foundation (WPF) supports browser-style navigation that can be used in two types of applications: standalone applications and XAML browser applications (XBAPs). To package content for navigation, WPF provides the Page class. You can navigate from one Page to another declaratively, by using a Hyperlink, or programmatically, by using the NavigationService. WPF uses the journal to remember pages that have been navigated from and to navigate back to them.

Page, Hyperlink, NavigationService, and the journal form the core of the navigation support offered by WPF. This overview explores these features in detail before covering advanced navigation support that includes navigation to loose Extensible Application Markup Language (XAML) files, HTML files, and objects.

Note

In this topic, the term "browser" refers only to browsers that can host WPF applications, which currently includes Microsoft Internet Explorer and Firefox. Where specific WPF features are supported only by a particular browser, the browser version is referred to.

This topic provides an overview of the key navigation capabilities in WPF. These capabilities are available to both standalone applications and XBAPs, although this topic presents them within the context of an XBAP.

Note

This topic doesn't discuss how to build and deploy XBAPs. For more information on XBAPs, see WPF XAML Browser Applications Overview.

This section explains and demonstrates the following aspects of navigation:

Implementing a Page

In WPF, you can navigate to several content types that include .NET Framework objects, custom objects, enumeration values, user controls, XAML files, and HTML files. However, you'll find that the most common and convenient way to package content is by using Page. Furthermore, Page implements navigation-specific features to enhance their appearance and simplify development.

Using Page, you can declaratively implement a navigable page of XAML content by using markup like the following.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />

A Page that is implemented in XAML markup has Page as its root element and requires the WPF XML namespace declaration. The Page element contains the content that you want to navigate to and display. You add content by setting the Page.Content property element, as shown in the following markup.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <Page.Content>
    <!-- Page Content -->
    Hello, Page!
  </Page.Content>
</Page>

Page.Content can only contain one child element; in the preceding example, the content is a single string, "Hello, Page!" In practice, you will usually use a layout control as the child element (see Layout) to contain and compose your content.

The child elements of a Page element are considered to be the content of a Page and, consequently, you don't need to use the explicit Page.Content declaration. The following markup is the declarative equivalent to the preceding sample.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <!-- Page Content -->
  Hello, Page!
</Page>

In this case, Page.Content is automatically set with the child elements of the Page element. For more information, see WPF Content Model.

A markup-only Page is useful for displaying content. However, a Page can also display controls that allow users to interact with the page, and it can respond to user interaction by handling events and calling application logic. An interactive Page is implemented by using a combination of markup and code-behind, as shown in the following example.

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.HomePage">
  Hello, from the XBAP HomePage!
</Page>
using System.Windows.Controls;

namespace SDKSample
{
    public partial class HomePage : Page
    {
        public HomePage()
        {
            InitializeComponent();
        }
    }
}

Imports System.Windows.Controls

Namespace SDKSample
    Partial Public Class HomePage
        Inherits Page
        Public Sub New()
            InitializeComponent()
        End Sub
    End Class
End Namespace

To allow a markup file and code-behind file to work together, the following configuration is required:

  • In markup, the Page element must include the x:Class attribute. When the application is built, the existence of x:Class in the markup file causes Microsoft build engine (MSBuild) to create a partial class that derives from Page and has the name that is specified by the x:Class attribute. This requires the addition of an XML namespace declaration for the XAML schema ( xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" ). The generated partial class implements InitializeComponent, which is called to register the events and set the properties that are implemented in markup.

  • In code-behind, the class must be a partial class with the same name that is specified by the x:Class attribute in markup, and it must derive from Page. This allows the code-behind file to be associated with the partial class that is generated for the markup file when the application is built (see Building a WPF Application).

  • In code-behind, the Page class must implement a constructor that calls the InitializeComponent method. InitializeComponent is implemented by the markup file's generated partial class to register events and set properties that are defined in markup.

Note

When you add a new Page to your project using Visual Studio, the Page is implemented using both markup and code-behind, and it includes the necessary configuration to create the association between the markup and code-behind files as described here.

Once you have a Page, you can navigate to it. To specify the first Page that an application navigates to, you need to configure the start Page.

Configuring a Start Page

XBAPs require a certain amount of application infrastructure to be hosted in a browser. In WPF, the Application class is part of an application definition that establishes the required application infrastructure (see Application Management Overview).

An application definition is usually implemented using both markup and code-behind, with the markup file configured as an MSBuildApplicationDefinition item. The following is an application definition for an XBAP.

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.App" />
using System.Windows;

namespace SDKSample
{
    public partial class App : Application { }
}

Imports System.Windows

Namespace SDKSample
    Partial Public Class App
        Inherits Application
    End Class
End Namespace

An XBAP can use its application definition to specify a start Page, which is the Page that is automatically loaded when the XBAP is launched. You do this by setting the StartupUri property with the uniform resource identifier (URI) for the desired Page.

Note

In most cases, the Page is either compiled into or deployed with an application. In these cases, the URI that identifies a Page is a pack URI, which is a URI that conforms to the pack scheme. Pack URIs are discussed further in Pack URIs in WPF. You can also navigate to content using the http scheme, which is discussed below.

You can set StartupUri declaratively in markup, as shown in the following example.

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.App"
    StartupUri="PageWithHyperlink.xaml" />

In this example, the StartupUri attribute is set with a relative pack URI that identifies HomePage.xaml. When the XBAP is launched, HomePage.xaml is automatically navigated to and displayed. This is demonstrated by the following figure, which shows an XBAP that was launched from a Web server.

XBAP page

Note

For more information regarding the development and deployment of XBAPs, see WPF XAML Browser Applications Overview and Deploying a WPF Application.

Configuring the Host Window's Title, Width, and Height

One thing you may have noticed from the previous figure is that the title of both the browser and the tab panel is the URI for the XBAP. Besides being long, the title is neither attractive nor informative. For this reason, Page offers a way for you to change the title by setting the WindowTitle property. Furthermore, you can configure the width and height of the browser window by setting WindowWidth and WindowHeight, respectively.

WindowTitle, WindowWidth, and WindowHeight can be set declaratively in markup, as shown in the following example.

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.HomePage"
    WindowTitle="Page Title"
    WindowWidth="500"
    WindowHeight="200">
  Hello, from the XBAP HomePage!
</Page>

The result is shown in the following figure.

Window title, height, width

A typical XBAP comprises several pages. The simplest way to navigate from one page to another is to use a Hyperlink. You can declaratively add a Hyperlink to a Page by using the Hyperlink element, which is shown in the following markup.

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  WindowTitle="Page With Hyperlink"
  WindowWidth="250"
  WindowHeight="250">
<Hyperlink NavigateUri="UriOfPageToNavigateTo.xaml">
  Navigate to Another Page
</Hyperlink>
</Page>

A Hyperlink element requires the following:

  • The pack URI of the Page to navigate to, as specified by the NavigateUri attribute.

  • Content that a user can click to initiate the navigation, such as text and images (for the content that the Hyperlink element can contain, see Hyperlink).

The following figure shows an XBAP with a Page that has a Hyperlink.

Page with Hyperlink

As you would expect, clicking the Hyperlink causes the XBAP to navigate to the Page that is identified by the NavigateUri attribute. Additionally, the XBAP adds an entry for the previous Page to the Recent Pages list in Internet Explorer. This is shown in the following figure.

Back and Forward buttons

As well as supporting navigation from one Page to another, Hyperlink also supports fragment navigation.

Fragment Navigation

Fragment navigation is the navigation to a content fragment in either the current Page or another Page. In WPF, a content fragment is the content that is contained by a named element. A named element is an element that has its Name attribute set. The following markup shows a named TextBlock element that contains a content fragment.

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    WindowTitle="Page With Fragments" >
<!-- Content Fragment called "Fragment1" -->
<TextBlock Name="Fragment1">
  Ea vel dignissim te aliquam facilisis ...
</TextBlock>
</Page>

For a Hyperlink to navigate to a content fragment, the NavigateUri attribute must include the following:

  • The URI of the Page with the content fragment to navigate to.

  • A "#" character.

  • The name of the element on the Page that contains the content fragment.

A fragment URI has the following format.

PageURI # ElementName

The following shows an example of a Hyperlink that is configured to navigate to a content fragment.

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  WindowTitle="Page That Navigates To Fragment" >
<Hyperlink NavigateUri="PageWithFragments.xaml#Fragment1">
  Navigate To pack Fragment
</Hyperlink>
</Page>

Note

This section describes the default fragment navigation implementation in WPF. WPF also allows you to implement your own fragment navigation scheme which, in part, requires handling the NavigationService.FragmentNavigation event.

Important

You can navigate to fragments in loose XAML pages (markup-only XAML files with Page as the root element) only if the pages can be browsed via HTTP.

However, a loose XAML page can navigate to its own fragments.

While Hyperlink allows a user to initiate navigation to a particular Page, the work of locating and downloading the page is performed by the NavigationService class. Essentially, NavigationService provides the ability to process a navigation request on behalf of client code, such as the Hyperlink. Additionally, NavigationService implements higher-level support for tracking and influencing a navigation request.

When a Hyperlink is clicked, WPF calls NavigationService.Navigate to locate and download the Page at the specified pack URI. The downloaded Page is converted to a tree of objects whose root object is an instance of the downloaded Page. A reference to the root Page object is stored in the NavigationService.Content property. The pack URI for the content that was navigated to is stored in the NavigationService.Source property, while the NavigationService.CurrentSource stores the pack URI for the last page that was navigated to.

Note

It is possible for a WPF application to have more than one currently active NavigationService. For more information, see Navigation Hosts later in this topic.

Programmatic Navigation with the Navigation Service

You don't need to know about NavigationService if navigation is implemented declaratively in markup using Hyperlink, because Hyperlink uses the NavigationService on your behalf. This means that, as long as either the direct or indirect parent of a Hyperlink is a navigation host (see Navigation Hosts), Hyperlink will be able to find and use the navigation host's navigation service to process a navigation request.

However, there are situations when you need to use NavigationService directly, including the following:

  • When you need to instantiate a Page using a non-parameterless constructor.

  • When you need to set properties on the Page before you navigate to it.

  • When the Page that needs to be navigated to can only be determined at run time.

In these situations, you need to write code to programmatically initiate navigation by calling the Navigate method of the NavigationService object. That requires getting a reference to a NavigationService.

Getting a Reference to the NavigationService

For reasons that are covered in the Navigation Hosts section, a WPF application can have more than one NavigationService. This means that your code needs a way to find a NavigationService, which is usually the NavigationService that navigated to the current Page. You can get a reference to a NavigationService by calling the staticNavigationService.GetNavigationService method. To get the NavigationService that navigated to a particular Page, you pass a reference to the Page as the argument of the GetNavigationService method. The following code shows how to get the NavigationService for the current Page.

using System.Windows.Navigation;
// Get a reference to the NavigationService that navigated to this Page
NavigationService ns = NavigationService.GetNavigationService(this);
' Get a reference to the NavigationService that navigated to this Page
Dim ns As NavigationService = NavigationService.GetNavigationService(Me)

As a shortcut for finding the NavigationService for a Page, Page implements the NavigationService property. This is shown in the following example.

using System.Windows.Navigation;
// Get a reference to the NavigationService that navigated to this Page
NavigationService ns = this.NavigationService;
' Get a reference to the NavigationService that navigated to this Page
Dim ns As NavigationService = Me.NavigationService

Note

A Page can only get a reference to its NavigationService when Page raises the Loaded event.

Programmatic Navigation to a Page Object

The following example shows how to use the NavigationService to programmatically navigate to a Page. Programmatic navigation is required because the Page that is being navigated to can only be instantiated using a single, non-parameterless constructor. The Page with the non-parameterless constructor is shown in the following markup and code.

<Page
    x:Class="SDKSample.PageWithNonDefaultConstructor"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="PageWithNonDefaultConstructor">
  
  <!-- Content goes here -->
  
</Page>
using System.Windows.Controls;

namespace SDKSample
{
    public partial class PageWithNonDefaultConstructor : Page
    {
        public PageWithNonDefaultConstructor(string message)
        {
            InitializeComponent();

            this.Content = message;
        }
    }
}

Namespace SDKSample
    Partial Public Class PageWithNonDefaultConstructor
        Inherits Page
        Public Sub New(ByVal message As String)
            InitializeComponent()

            Me.Content = message
        End Sub
    End Class
End Namespace

The Page that navigates to the Page with the non-parameterless constructor is shown in the following markup and code.

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.NSNavigationPage">

  <Hyperlink Click="hyperlink_Click">
    Navigate to Page with Non-Default Constructor
  </Hyperlink>

</Page>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;

namespace SDKSample
{
    public partial class NSNavigationPage : Page
    {
        public NSNavigationPage()
        {
            InitializeComponent();
        }

        void hyperlink_Click(object sender, RoutedEventArgs e)
        {
            // Instantiate the page to navigate to
            PageWithNonDefaultConstructor page = new PageWithNonDefaultConstructor("Hello!");

            // Navigate to the page, using the NavigationService
            this.NavigationService.Navigate(page);
        }
    }
}

Namespace SDKSample
    Partial Public Class NSNavigationPage
        Inherits Page
        Public Sub New()
            InitializeComponent()
        End Sub

        Private Sub hyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Instantiate the page to navigate to
            Dim page As New PageWithNonDefaultConstructor("Hello!")

            ' Navigate to the page, using the NavigationService
            Me.NavigationService.Navigate(page)
        End Sub
    End Class
End Namespace

When the Hyperlink on this Page is clicked, navigation is initiated by instantiating the Page to navigate to using the non-parameterless constructor and calling the NavigationService.Navigate method. Navigate accepts a reference to the object that the NavigationService will navigate to, rather than a pack URI.

Programmatic Navigation with a Pack URI

If you need to construct a pack URI programmatically (when you can only determine the pack URI at run time, for example), you can use the NavigationService.Navigate method. This is shown in the following example.

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.NSUriNavigationPage">
  <Hyperlink Click="hyperlink_Click">Navigate to Page by Pack URI</Hyperlink>
</Page>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;

namespace SDKSample
{
    public partial class NSUriNavigationPage : Page
    {
        public NSUriNavigationPage()
        {
            InitializeComponent();
        }

        void hyperlink_Click(object sender, RoutedEventArgs e)
        {
            // Create a pack URI
            Uri uri = new Uri("AnotherPage.xaml", UriKind.Relative);

            // Get the navigation service that was used to
            // navigate to this page, and navigate to
            // AnotherPage.xaml
            this.NavigationService.Navigate(uri);
        }
    }
}

Namespace SDKSample
    Partial Public Class NSUriNavigationPage
        Inherits Page
        Public Sub New()
            InitializeComponent()
        End Sub

        Private Sub hyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Create a pack URI
            Dim uri As New Uri("AnotherPage.xaml", UriKind.Relative)

            ' Get the navigation service that was used to 
            ' navigate to this page, and navigate to 
            ' AnotherPage.xaml
            Me.NavigationService.Navigate(uri)
        End Sub
    End Class
End Namespace

Refreshing the Current Page

A Page is not downloaded if it has the same pack URI as the pack URI that is stored in the NavigationService.Source property. To force WPF to download the current page again, you can call the NavigationService.Refresh method, as shown in the following example.

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.NSRefreshNavigationPage">
 <Hyperlink Click="hyperlink_Click">Refresh this page</Hyperlink>
</Page>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;

namespace SDKSample
{
    public partial class NSRefreshNavigationPage : Page
    {

Namespace SDKSample
    Partial Public Class NSRefreshNavigationPage
        Inherits Page
        void hyperlink_Click(object sender, RoutedEventArgs e)
        {
            // Force WPF to download this page again
            this.NavigationService.Refresh();
        }
    }
}
        Private Sub hyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Force WPF to download this page again
            Me.NavigationService.Refresh()
        End Sub
    End Class
End Namespace

There are many ways to initiate navigation, as you've seen. When navigation is initiated, and while navigation is in progress, you can track and influence the navigation using the following events that are implemented by NavigationService:

  • Navigating. Occurs when a new navigation is requested. Can be used to cancel the navigation.

  • NavigationProgress. Occurs periodically during a download to provide navigation progress information.

  • Navigated. Occurs when the page has been located and downloaded.

  • NavigationStopped. Occurs when the navigation is stopped (by calling StopLoading), or when a new navigation is requested while a current navigation is in progress.

  • NavigationFailed. Occurs when an error is raised while navigating to the requested content.

  • LoadCompleted. Occurs when content that was navigated to is loaded and parsed, and has begun rendering.

  • FragmentNavigation. Occurs when navigation to a content fragment begins, which happens:

    • Immediately, if the desired fragment is in the current content.

    • After the source content has been loaded, if the desired fragment is in different content.

The navigation events are raised in the order that is illustrated by the following figure.

Page navigation flow chart

In general, a Page isn't concerned about these events. It is more likely that an application is concerned with them and, for that reason, these events are also raised by the Application class:

Every time NavigationService raises an event, the Application class raises the corresponding event. Frame and NavigationWindow offer the same events to detect navigation within their respective scopes.

In some cases, a Page might be interested in these events. For example, a Page might handle the NavigationService.Navigating event to determine whether or not to cancel navigation away from itself. This is shown in the following example.

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.CancelNavigationPage">
  <Button Click="button_Click">Navigate to Another Page</Button>
</Page>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;

namespace SDKSample
{
    public partial class CancelNavigationPage : Page
    {
        public CancelNavigationPage()
        {
            InitializeComponent();

            // Can only access the NavigationService when the page has been loaded
            this.Loaded += new RoutedEventHandler(CancelNavigationPage_Loaded);
            this.Unloaded += new RoutedEventHandler(CancelNavigationPage_Unloaded);
        }

        void button_Click(object sender, RoutedEventArgs e)
        {
            // Force WPF to download this page again
            this.NavigationService.Navigate(new Uri("AnotherPage.xaml", UriKind.Relative));
        }

        void CancelNavigationPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.NavigationService.Navigating += new NavigatingCancelEventHandler(NavigationService_Navigating);
        }

        void CancelNavigationPage_Unloaded(object sender, RoutedEventArgs e)
        {
            this.NavigationService.Navigating -= new NavigatingCancelEventHandler(NavigationService_Navigating);
        }

        void NavigationService_Navigating(object sender, NavigatingCancelEventArgs e)
        {
            // Does the user really want to navigate to another page?
            MessageBoxResult result;
            result = MessageBox.Show("Do you want to leave this page?", "Navigation Request", MessageBoxButton.YesNo);

            // If the user doesn't want to navigate away, cancel the navigation
            if (result == MessageBoxResult.No) e.Cancel = true;
        }
    }
}

Namespace SDKSample
    Partial Public Class CancelNavigationPage
        Inherits Page
        Public Sub New()
            InitializeComponent()

            ' Can only access the NavigationService when the page has been loaded
            AddHandler Loaded, AddressOf CancelNavigationPage_Loaded
            AddHandler Unloaded, AddressOf CancelNavigationPage_Unloaded
        End Sub

        Private Sub button_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Force WPF to download this page again
            Me.NavigationService.Navigate(New Uri("AnotherPage.xaml", UriKind.Relative))
        End Sub

        Private Sub CancelNavigationPage_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            AddHandler NavigationService.Navigating, AddressOf NavigationService_Navigating
        End Sub

        Private Sub CancelNavigationPage_Unloaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            RemoveHandler NavigationService.Navigating, AddressOf NavigationService_Navigating
        End Sub

        Private Sub NavigationService_Navigating(ByVal sender As Object, ByVal e As NavigatingCancelEventArgs)
            ' Does the user really want to navigate to another page?
            Dim result As MessageBoxResult
            result = MessageBox.Show("Do you want to leave this page?", "Navigation Request", MessageBoxButton.YesNo)

            ' If the user doesn't want to navigate away, cancel the navigation
            If result = MessageBoxResult.No Then
                e.Cancel = True
            End If
        End Sub
    End Class
End Namespace

If you register a handler with a navigation event from a Page, as the preceding example does, you must also unregister the event handler. If you don't, there may be side effects with respect to how WPF navigation remembers Page navigation using the journal.

Remembering Navigation with the Journal

WPF uses two stacks to remember the pages that you have navigated from: a back stack and a forward stack. When you navigate from the current Page to a new Page or forward to an existing Page, the current Page is added to the back stack. When you navigate from the current Page back to the previous Page, the current Page is added to the forward stack. The back stack, the forward stack, and the functionality to manage them, are collectively referred to as the journal. Each item in the back stack and the forward stack is an instance of the JournalEntry class, and is referred to as a journal entry.

Conceptually, the journal operates the same way that the Back and Forward buttons in Internet Explorer do. These are shown in the following figure.

Back and Forward buttons

For XBAPs that are hosted by Internet Explorer, WPF integrates the journal into the navigation UI of Internet Explorer. This allows users to navigate pages in an XBAP by using the Back, Forward, and Recent Pages buttons in Internet Explorer.

Important

In Internet Explorer, when a user navigates away from and back to an XBAP, only the journal entries for pages that were not kept alive are retained in the journal. For discussion on keeping pages alive, see Page Lifetime and the Journal later in this topic.

By default, the text for each Page that appears in the Recent Pages list of Internet Explorer is the URI for the Page. In many cases, this is not particularly meaningful to the user. Fortunately, you can change the text using one the following options:

  1. The attached JournalEntry.Name attribute value.

  2. The Page.Title attribute value.

  3. The Page.WindowTitle attribute value and the URI for the current Page.

  4. The URI for the current Page. (Default)

The order in which the options are listed matches the order of precedence for finding the text. For example, if JournalEntry.Name is set, the other values are ignored.

The following example uses the Page.Title attribute to change the text that appears for a journal entry.

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.PageWithTitle"
    Title="This is the title of the journal entry for this page.">
</Page>
using System.Windows.Controls;

namespace SDKSample
{
    public partial class PageWithTitle : Page
    {

Namespace SDKSample
    Partial Public Class PageWithTitle
        Inherits Page
    }
}
    End Class
End Namespace

Although a user can navigate the journal by using the Back, Forward, and Recent Pages in Internet Explorer, you can also navigate the journal using both declarative and programmatic mechanisms provided by WPF. One reason to do this is to provide custom navigation UIs in your pages.

You can declaratively add journal navigation support by using the navigation commands exposed by NavigationCommands. The following example demonstrates how to use the BrowseBack navigation command.

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.NavigationCommandsPage">
<Hyperlink Command="NavigationCommands.BrowseBack">Back</Hyperlink>
<Hyperlink Command="NavigationCommands.BrowseForward">Forward</Hyperlink>
</Page>

You can programmatically navigate the journal by using one of the following members of the NavigationService class:

The journal can also be manipulated programmatically, as discussed in Retaining Content State with Navigation History later in this topic.

Page Lifetime and the Journal

Consider an XBAP with several pages that contain rich content, including graphics, animations, and media. The memory footprint for pages like these could be quite large, particularly if video and audio media are used. Given that the journal "remembers" pages that have been navigated to, such an XBAP could quickly consume a large and noticeable amount of memory.

For this reason, the default behavior of the journal is to store Page metadata in each journal entry rather than a reference to a Page object. When a journal entry is navigated to, its Page metadata is used to create a new instance of the specified Page. As a consequence, each Page that is navigated has the lifetime that is illustrated by the following figure.

Page lifetime

Although using the default journaling behavior can save on memory consumption, per-page rendering performance might be reduced; reinstantiating a Page can be time-intensive, particularly if it has a lot of content. If you need to retain a Page instance in the journal, you can draw on two techniques for doing so. First, you can programmatically navigate to a Page object by calling the NavigationService.Navigate method.

Second, you can specify that WPF retain an instance of a Page in the journal by setting the KeepAlive property to true (the default is false). As shown in the following example, you can set KeepAlive declaratively in markup.

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.KeepAlivePage"
    KeepAlive="True">
  
  An instance of this page is stored in the journal.
  
</Page>

The lifetime of a Page that is kept alive is subtly different from one that is not. The first time a Page that is kept alive is navigated to, it is instantiated just like a Page that is not kept alive. However, because an instance of the Page is retained in the journal, it is never instantiated again for as long as it remains in the journal. Consequently, if a Page has initialization logic that needs to be called every time the Page is navigated to, you should move it from the constructor into a handler for the Loaded event. As shown in the following figure, the Loaded and Unloaded events are still raised each time a Page is navigated to and from, respectively.

When the Loaded and Unloaded events are raised

When a Page is not kept alive, you should not do either of the following:

  • Store a reference to it, or any part of it.

  • Register event handlers with events that are not implemented by it.

Doing either of these will create references that force the Page to be retained in memory, even after it has been removed from the journal.

In general, you should prefer the default Page behavior of not keeping a Page alive. However, this has state implications that are discussed in the next section.

Retaining Content State with Navigation History

If a Page is not kept alive, and it has controls that collect data from the user, what happens to the data if a user navigates away from and back to the Page? From a user experience perspective, the user should expect to see the data they entered previously. Unfortunately, because a new instance of the Page is created with each navigation, the controls that collected the data are reinstantiated and the data is lost.

Fortunately, the journal provides support for remembering data across Page navigations, including control data. Specifically, the journal entry for each Page acts as a temporary container for the associated Page state. The following steps outline how this support is used when a Page is navigated from:

  1. An entry for the current Page is added to the journal.

  2. The state of the Page is stored with the journal entry for that page, which is added to the back stack.

  3. The new Page is navigated to.

When the page Page is navigated back to, using the journal, the following steps take place:

  1. The Page (the top journal entry on the back stack) is instantiated.

  2. The Page is refreshed with the state that was stored with the journal entry for the Page.

  3. The Page is navigated back to.

WPF automatically uses this support when the following controls are used on a Page:

If a Page uses these controls, data entered into them is remembered across Page navigations, as demonstrated by the Favorite ColorListBox in the following figure.

Page with controls that remember state

When a Page has controls other than those in the preceding list, or when state is stored in custom objects, you need to write code to cause the journal to remember state across Page navigations.

If you need to remember small pieces of state across Page navigations, you can use dependency properties (see DependencyProperty) that are configured with the FrameworkPropertyMetadata.Journal metadata flag.

If the state that your Page needs to remember across navigations comprises multiple pieces of data, you may find it less code intensive to encapsulate your state in a single class and implement the IProvideCustomContentState interface.

If you need to navigate through various states of a single Page, without navigating from the Page itself, you can use IProvideCustomContentState and NavigationService.AddBackEntry.

Cookies

Another way that WPF applications can store data is with cookies, which are created, updated, and deleted by using the SetCookie and GetCookie methods. The cookies that you can create in WPF are the same cookies that other types of Web applications use; cookies are arbitrary pieces of data that are stored by an application on a client machine either during or across application sessions. Cookie data typically takes the form of a name/value pair in the following format.

Name = Value

When the data is passed to SetCookie, along with the Uri of the location for which the cookie should be set, a cookie is created in-memory, and it is only available for the duration of the current application session. This type of cookie is referred to as a session cookie.

To store a cookie across application sessions, an expiration date must be added to the cookie, using the following format.

NAME = VALUE ; expires=DAY, DD-MMM-YYYY HH:MM:SS GMT

A cookie with an expiration date is stored in the current Windows installation's Temporary Internet Files folder until the cookie expires. Such a cookie is known as a persistent cookie because it persists across application sessions.

You retrieve both session and persistent cookies by calling the GetCookie method, passing the Uri of the location where the cookie was set with the SetCookie method.

The following are some of the ways that cookies are supported in WPF:

  • WPF standalone applications and XBAPs can both create and manage cookies.

  • Cookies that are created by an XBAP can be accessed from the browser.

  • XBAPs from the same domain can create and share cookies.

  • XBAPs and HTML pages from the same domain can create and share cookies.

  • Cookies are dispatched when XBAPs and loose XAML pages make Web requests.

  • Both top-level XBAPs and XBAPs hosted in IFRAMES can access cookies.

  • Cookie support in WPF is the same for all supported browsers.

  • In Internet Explorer, P3P policy that pertains to cookies is honored by WPF, particularly with respect to first-party and third-party XBAPs.

Structured Navigation

If you need to pass data from one Page to another, you can pass the data as arguments to a non-parameterless constructor of the Page. Note that if you use this technique, you must keep the Page alive; if not, the next time you navigate to the Page, WPF reinstantiates the Page by using the parameterless constructor.

Alternatively, your Page can implement properties that are set with the data that needs to be passed. Things become tricky, however, when a Page needs to pass data back to the Page that navigated to it. The problem is that navigation doesn't natively support mechanisms for guaranteeing that a Page will be returned to after it is navigated from. Essentially, navigation doesn't support call/return semantics. To solve this problem, WPF provides the PageFunction<T> class that you can use to ensure that a Page is returned to in a predictable and structured fashion. For more information, see Structured Navigation Overview.

The NavigationWindow Class

To this point, you've seen the gamut of navigation services that you are most likely to use to build applications with navigable content. These services were discussed in the context of XBAPs, although they are not limited to XBAPs. Modern operating systems and Windows applications take advantage of the browser experience of modern users to incorporate browser-style navigation into standalone applications. Common examples include:

  • Word Thesaurus: Navigate word choices.

  • File Explorer: Navigate files and folders.

  • Wizards: Breaking down a complex task into multiple pages that can be navigated between. An example is the Windows Components Wizard that handles adding and removing Windows features.

To incorporate browser-style navigation into your standalone applications, you can use the NavigationWindow class. NavigationWindow derives from Window and extends it with the same support for navigation that XBAPs provide. You can use NavigationWindow as either the main window of your standalone application or as a secondary window such as a dialog box.

To implement a NavigationWindow, as with most top-level classes in WPF (Window, Page, and so on), you use a combination of markup and code-behind. This is shown in the following example.

<NavigationWindow
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.MainWindow" 
    Source="HomePage.xaml"/>
using System.Windows.Navigation;

namespace SDKSample
{
    public partial class MainWindow : NavigationWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

Namespace SDKSample
    Partial Public Class MainWindow
        Inherits NavigationWindow
        Public Sub New()
            InitializeComponent()
        End Sub
    End Class
End Namespace

This code creates a NavigationWindow that automatically navigates to a Page (HomePage.xaml) when the NavigationWindow is opened. If the NavigationWindow is the main application window, you can use the StartupUri attribute to launch it. This is shown in the following markup.

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    StartupUri="MainWindow.xaml" />

The following figure shows the NavigationWindow as the main window of a standalone application.

A main window

From the figure, you can see that the NavigationWindow has a title, even though it wasn't set in the NavigationWindow implementation code from the preceding example. Instead, the title is set using the WindowTitle property, which is shown in the following code.

<Page 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Title="Home Page"
    WindowTitle="NavigationWindow">
</Page>

Setting the WindowWidth and WindowHeight properties also affects the NavigationWindow.

Usually, you implement your own NavigationWindow when you need to customize either its behavior or its appearance. If you do neither, you can use a shortcut. If you specify the pack URI of a Page as the StartupUri in a standalone application, Application automatically creates a NavigationWindow to host the Page. The following markup shows how to enable this.

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    StartupUri="HomePage.xaml" />

If you want a secondary application window such as a dialog box to be a NavigationWindow, you can use the code in the following example to open it.

// Open a navigation window as a dialog box
NavigationWindowDialogBox dlg = new NavigationWindowDialogBox();
dlg.Source = new Uri("HomePage.xaml", UriKind.Relative);
dlg.Owner = this;
dlg.ShowDialog();
' Open a navigation window as a dialog box
Dim dlg As New NavigationWindowDialogBox()
dlg.Source = New Uri("HomePage.xaml", UriKind.Relative)
dlg.Owner = Me
dlg.ShowDialog()

The following figure shows the result.

A dialog box

As you can see, NavigationWindow displays Internet Explorer-style Back and Forward buttons that allow users to navigate the journal. These buttons provide the same user experience, as shown in the following figure.

Back and Forward buttons in a NavigationWindow

If your pages provide their own journal navigation support and UI, you can hide the Back and Forward buttons displayed by NavigationWindow by setting the value of the ShowsNavigationUI property to false.

Alternatively, you can use customization support in WPF to replace the UI of the NavigationWindow itself.

The Frame Class

Both the browser and NavigationWindow are windows that host navigable content. In some cases, applications have content that does not need to be hosted by an entire window. Instead, such content be hosted inside other content. You can insert navigable content into other content by using the Frame class. Frame provides the same support as NavigationWindow and XBAPs.

The following example shows how to add a Frame to a Page declaratively by using the Frame element.

<Page 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  WindowTitle="Page that Hosts a Frame"
  WindowWidth="250"
  WindowHeight="250">
<Frame Source="FramePage1.xaml" />
</Page>

This markup sets the Source attribute of the Frame element with a pack URI for the Page that the Frame should initially navigate to. The following figure shows an XBAP with a Page that has a Frame that has navigated between several pages.

A frame that has navigated between multiple pages

You don't only have to use Frame inside the content of a Page. It is also common to host a Frame inside the content of a Window.

By default, Frame only uses its own journal in the absence of another journal. If a Frame is part of content that is hosted inside either a NavigationWindow or an XBAP, Frame uses the journal that belongs to the NavigationWindow or XBAP. Sometimes, though, a Frame might need to be responsible for its own journal. One reason to do so is to allow journal navigation within the pages that are hosted by a Frame. This is illustrated by the following figure.

Frame and Page diagram

In this case, you can configure the Frame to use its own journal by setting the JournalOwnership property of the Frame to OwnsJournal. This is shown in the following markup.

<Page 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  WindowTitle="Page that Hosts a Frame"
  WindowWidth="250"
  WindowHeight="250">
<Frame Source="FramePage1.xaml" JournalOwnership="OwnsJournal" />
</Page>

The following figure illustrates the effect of navigating within a Frame that uses its own journal.

A frame that uses its own journal

Notice that the journal entries are shown by the navigation UI in the Frame, rather than by Internet Explorer.

Note

If a Frame is part of content that is hosted in a Window, Frame uses its own journal and, consequently, displays its own navigation UI.

If your user experience requires a Frame to provide its own journal without showing the navigation UI, you can hide the navigation UI by setting the NavigationUIVisibility to Hidden. This is shown in the following markup.

<Page 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  WindowTitle="Page that Hosts a Frame"
  WindowWidth="250"
  WindowHeight="250">
<Frame 
  Source="FramePage1.xaml" 
  JournalOwnership="OwnsJournal" 
  NavigationUIVisibility="Hidden" />
</Page>

Frame and NavigationWindow are classes that are known as navigation hosts. A navigation host is a class that can navigate to and display content. To accomplish this, each navigation host uses its own NavigationService and journal. The basic construction of a navigation host is shown in the following figure.

Navigator diagrams

Essentially, this allows NavigationWindow and Frame to provide the same navigation support that an XBAP provides when hosted in the browser.

Besides using NavigationService and a journal, navigation hosts implement the same members that NavigationService implements. This is illustrated by the following figure.

A journal in a Frame and in a NavigationWindow

This allows you to program navigation support directly against them. You may consider this if you need to provide a custom navigation UI for a Frame that is hosted in a Window. Furthermore, both types implement additional, navigation-related members, including BackStack (NavigationWindow.BackStack, Frame.BackStack) and ForwardStack (NavigationWindow.ForwardStack, Frame.ForwardStack), which allow you to enumerate the journal entries in the back stack and forward stack, respectively.

As mentioned earlier, more than one journal can exist within an application. The following figure provides an example of when this can happen.

Multiple journals within one application

Throughout this topic, Page and pack XBAPs have been used to demonstrate the various navigation capabilities of WPF. However, a Page that is compiled into an application is not the only type of content that can be navigated to, and pack XBAPs aren't the only way to identify content.

As this section demonstrates, you can also navigate to loose XAML files, HTML files, and objects.

A loose XAML file is a file with the following characteristics:

  • Contains only XAML (that is, no code).

  • Has an appropriate namespace declaration.

  • Has the .xaml file name extension.

For example, consider the following content that is stored as a loose XAML file, Person.xaml.

<!-- Person.xaml -->
<TextBlock xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <TextBlock FontWeight="Bold">Name:</TextBlock>
  <TextBlock>Nancy Davolio</TextBlock>
  <LineBreak />
  <TextBlock FontWeight="Bold">Favorite Color:</TextBlock>
  <TextBlock>Yellow</TextBlock>
</TextBlock>

When you double-click the file, the browser opens and navigates to and displays the content. This is shown in the following figure.

Display of the content in the Person.XAML file

You can display a loose XAML file from the following:

  • A Web site on the local machine, the intranet, or the Internet.

  • A Universal Naming Convention (UNC) file share.

  • The local disk.

A loose XAML file can be added to the browser's favorites, or be the browser's home page.

Note

For more information about publishing and launching loose XAML pages, see Deploying a WPF Application.

One limitation with respect to loose XAML is that you can only host content that is safe to run in partial trust. For example, Window cannot be the root element of a loose XAML file. For more information, see WPF Partial Trust Security.

As you might expect, you can also navigate to HTML. You simply need to provide a URI that uses the http scheme. For example, the following XAML shows a Frame that navigates to an HTML page.

<Frame Source="http://www.microsoft.com/default.aspx" />

Navigating to HTML requires special permissions. For example, you can't navigate from an XBAP that is running in the Internet zone partial trust security sandbox. For more information, see WPF Partial Trust Security.

The WebBrowser control supports HTML document hosting, navigation and script/managed code interoperability. For detailed information regarding the WebBrowser control, see WebBrowser.

Like Frame, navigating to HTML using WebBrowser requires special permissions. For example, from a partial-trust application, you can navigate only to HTML located at the site of origin. For more information, see WPF Partial Trust Security.

If you have data that is stored as custom objects, one way to display that data is to create a Page with content that is bound to those objects (see Data Binding Overview). If you don't need the overhead of creating an entire page just to display the objects, you can navigate directly to them instead.

Consider the Person class that is implemented in the following code.

using System.Windows.Media;

namespace SDKSample
{
    public class Person
    {
        string name;
        Color favoriteColor;

        public Person() { }
        public Person(string name, Color favoriteColor)
        {
            this.name = name;
            this.favoriteColor = favoriteColor;
        }

        public string Name
        {
            get { return this.name; }
            set { this.name = value; }
        }

        public Color FavoriteColor
        {
            get { return this.favoriteColor; }
            set { this.favoriteColor = value; }
        }
    }
}

Namespace SDKSample
    Public Class Person
        Private _name As String
        Private _favoriteColor As Color

        Public Sub New()
        End Sub
        Public Sub New(ByVal name As String, ByVal favoriteColor As Color)
            Me._name = name
            Me._favoriteColor = favoriteColor
        End Sub

        Public Property Name() As String
            Get
                Return Me._name
            End Get
            Set(ByVal value As String)
                Me._name = value
            End Set
        End Property

        Public Property FavoriteColor() As Color
            Get
                Return Me._favoriteColor
            End Get
            Set(ByVal value As Color)
                Me._favoriteColor = value
            End Set
        End Property
    End Class
End Namespace

To navigate to it, you call the NavigationWindow.Navigate method, as demonstrated by the following code.

<Page 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="SDKSample.HomePage"
  WindowTitle="Page that Navigates to an Object">
<Hyperlink Name="hyperlink" Click="hyperlink_Click">
  Navigate to Nancy Davolio
</Hyperlink>
</Page>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace SDKSample
{
    public partial class HomePage : Page
    {
        public HomePage()
        {
            InitializeComponent();
        }

        void hyperlink_Click(object sender, RoutedEventArgs e)
        {
            Person person = new Person("Nancy Davolio", Colors.Yellow);
            this.NavigationService.Navigate(person);
        }
    }
}

Namespace SDKSample
    Partial Public Class HomePage
        Inherits Page
        Public Sub New()
            InitializeComponent()
        End Sub

        Private Sub hyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim person As New Person("Nancy Davolio", Colors.Yellow)
            Me.NavigationService.Navigate(person)
        End Sub
    End Class
End Namespace

The following figure shows the result.

A page that navigates to a class

From this figure, you can see that nothing useful is displayed. In fact, the value that is displayed is the return value of the ToString method for the Person object; by default, this is the only value that WPF can use to represent your object. You could override the ToString method to return more meaningful information, although it will still only be a string value. One technique you can use that takes advantage of the presentation capabilities of WPF is to use a data template. You can implement a data template that WPF can associate with an object of a particular type. The following code shows a data template for the Person object.

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SDKSample" 
    x:Class="SDKSample.App"
    StartupUri="HomePage.xaml">

  <Application.Resources>

    <!-- Data Template for the Person Class -->
    <DataTemplate DataType="{x:Type local:Person}">
      <TextBlock xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
        <TextBlock FontWeight="Bold">Name:</TextBlock>
        <TextBlock Text="{Binding Path=Name}" />
        <LineBreak />
        <TextBlock FontWeight="Bold">Favorite Color:</TextBlock>
        <TextBlock Text="{Binding Path=FavoriteColor}" />
      </TextBlock>
    </DataTemplate>
    
  </Application.Resources>

</Application>

Here, the data template is associated with the Person type by using the x:Type markup extension in the DataType attribute. The data template then binds TextBlock elements (see TextBlock) to the properties of the Person class. The following figure shows the updated appearance of the Person object.

Navigating to a class that has a data template

An advantage of this technique is the consistency you gain by being able to reuse the data template to display your objects consistently anywhere in your application.

For more information on data templates, see Data Templating Overview.

Security

WPF navigation support allows XBAPs to be navigated to across the Internet, and it allows applications to host third-party content. To protect both applications and users from harmful behavior, WPF provides a variety of security features that are discussed in Security and WPF Partial Trust Security.

See also