Application-Level Programming - Navigation

Summary

Basic Navigation Example

The following example, which presents basic navigation principles, will help illustrate some of the navigation concepts presented after the example:

<!-- App.xaml -->

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

<!-- Page1.xaml -->

<!-- Typically a browsable content has a Page at its root. The content is added by setting the <Page.Content> element. <Page.Content> can have only one child element which will contain and compose the elements and controls for your UI. Therefoer, an XAML page is simply a host for WPF content.
 
An XAML page can determine to some extent how it appears in its host. For example, An XAML page can specify the title, width, and height of the window that hosts it (see properties for Page element below)
 
Note that once a page is defined and saved in .xaml, you can double click it to view it in Internet Explorer! However, XAMLPad which comes with the Windows SDK is the preferred tool to view XAML files

-->
<Page      
  
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   WindowTitle="Task Page"  WindowWidth="250" WindowHeight="150" >

      <!-- Page.Content is optional and is often not specified! -->
      <
Page.Content>
           
            <!--
The one and only one child element of Page.Content. A StackPanel allows you
            to stack elements in a specified direction (default is vertical)-->
            <StackPanel>
                 
                  <!--
First item in stack panel is a text block which contains two hyperlinks-->
                  <TextBlock>
                        <!-- Shows the simplest navigation - via a hyperlink -->
                        <Hyperlink NavigateUri="HyperlinkedPage.xaml">Go to hyperlinked page</Hyperlink>
                        <LineBreak/>
                        <Hyperlink NavigateUri="HyperlinkedPage.xaml#TextBlockFragment">Go to fragment on hyperlinked page</Hyperlink>
                  </TextBlock>
                 
                  <!--
Second element is s Frame element. Note how the Source property refers to a page-->

                  <Frame Source="FramePage.xaml"></Frame>
            </StackPanel>
      </Page.Content>
</Page>

<!-- FramePage.xaml -->

<!—This page is used as content in Page1.xaml -->
<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    WindowTitle="Page2" >
 
      <!--
The one and only one child element of the implied Page.Content -->
      <GroupBox Width="200" Height="50">
            <GroupBox.Header>
                  <Label>Content of Frame</Label>
            </GroupBox.Header>            <TextBlock>
                  <TextBlock FontSize="16">This is a frame content</TextBlock>/
            </TextBlock>
      </GroupBox>
</Page>

<!-- HyperlinkedPage.xaml -->

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    WindowTitle="Page2">
 
      <!--
The one and only one child element of the implied Page.Content -->
      <TextBlock>
            <TextBlock FontSize="20">Name: Yazan</TextBlock>
            <LineBreak/>
            <!—- This is a fragment – a named element-->
            <
TextBlock FontSize="20" Name="TextBlockFragment">ID: 123</TextBlock>
      </TextBlock>
</Page>

Output Screens

First of all, note that launching the application results in creating a WPF browser as the main frame. This browser hosts Page1.xaml which was specified in the StartupUri property of the <Application> object:

Clicking on “Go to hyperlinked page” link displays the contents of the HyperlinkedPage.xaml page:

Navigating backwards by clicking the left-heading arrow:

Clicking on “Go to fragment on hyperlinked page”:

The Page Class

Page is the WPF equivalent of an HTML page. Recall that WPF supports both menu-driven and hyperlink-driven navigation in both stand-alone with browser-based applications.  While the content cornerstone for menu-driven navigation is the Window class (you use menu options to create other windows/dialog boxes), the content cornerstone for hyperlink-driven navigation is the Page class.

From VS.NET you can add a Page to your WPF application using solution’s content menu and then Add | New Item … | Page (WPF). This generates the following skeletal code:

<Page x:Class="Navigation.Page1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Page1"
    >
    <Grid>
       
    </Grid>
</Page>

public partial class Page1 : System.Windows.Controls.Page
    {
        public Page1()
        {
            InitializeComponent();
        }
    }

Note that the Page markup file is configured as a Page build item. As with Window, this is done so it can be loaded from a URI, which means you can configure Application.StartupUri to automatically load a page when an application starts:

<!--App.xaml (markup)-->
<Application ... StartupUri="Page1.xaml" />

And because the Page class is not a window, and doesn't derive from Window, it can't host itself. Fortunately, the Application class is smart enough to detect when a particular page is set as the StartupUri. If so, Application creates a window in which to host the page:

Navigation

Development Model

WPF application model distinguishes between two application types: stand-alone and browser applications. A stand-alone application displays content through windows, dialogs, and messages boxes, while a browser application consists of its own pages that are hosted in a browser. Similarly, WPF application model distinguishes between two styles of navigation: menu-driven and hyperlink-driven. Menu-driven applications allow users to navigate content and functionality just as with any traditional Desktop application. Hyperlink-driven uses hyperlinks to deliver a navigation experience similar to Web applications.

While standalone applications typically support menu-driven navigation and browser applications typically support hyperlink-driven navigation, the WPF application model lets you mix and match elements of both application and navigation models. The combination you use should be based upon the type of experience that will most benefit your users. Once you've decided on the experience you want to deliver, you can use the WPF application model to build it.

Navigating to a target page

Page navigation takes place within a container. However, Page does not have explicit knowledge about what container is hosting it. Page can determine the host using its Parent property, but Parent returns a reference to a DependencyObject, rather than a strongly typed reference to a particular host type. In fact, Page can have different types of hosts. Consequently, if you intend for your pages to be hosted by multiple types of hosts, you need a host-independent way of programmatically performing navigation. You can get a page's container by calling GetNavigationService method and passing it the root element of the page:

NaviagationService nav = NaviagationService.GetNavigationService( pageRoot );

NavigationService class is the basic navigation engine and includes sets of events, methods and properties to manage page and frame navigations, including navigation history, navigation content, etc.

A Page can be navigated from within three types of containers:

A common way to identify a target page is by its URI. The page's URI is the XAML's file path relative to the root project folder. For example a file named Page1.XAML in the Src folder which is a subdirectory of the project folder, with a code-behind file of Page1.xaml.cs has a URI of "Page1.xaml". The following sections discuss different ways of navigating to a new page:

Hyperlink

Any nontrivial hyperlink-driven application will have more than one XAML page, and you'll need to give your users a way to navigate between these pages. WPF enables hyperlink-driven navigation with ... hyperlinks.

The simplest way to add hyperlink navigation to UI is to use <Hyperlink> elements and set its NavigateUri attribute to the target page's URI string. When using a frame, more than one page can be hosted. To navigate to a different page within the same frame, use the TargetName property of the <Hyperlink> element:

<Hyperlink NavigateUri="Page2.xaml" TargetName="MyFrame"/>

Navigate method

Sometimes you can't determine your navigation declaratively. For example, if you want to view a specific page you need to create an instance of that page and navigate to it. This can't be done declaratively. Instead, you need to handle it programmatically using the navigation container's Navigate method. This method supports two basic approaches to navigation

MyFrame.Navigate( new Uri("Page1.xaml", UriKink.RelativeOrAbsolute));

With this approach, the system automatically creates and initializes a new page object.

·    You can also create a target page explicitly and pass the object to Navigate. One advantage of this approach is that you can implement a non-default constructor which can be used to initialize the page as appropriate: For example:

// Create and initialize page
Page1 nextPage = new Page1();
nextPage.InitializeComponent();

// Get navigation service and navigate to the page nextPage. Approach 1
NaviagationService nav = NaviagationService.GetNavigationService( this );
nav.Navigate(nextPage); 

// Get navigation service and navigate to the page nextPage. Approach 2
this.NavigationService.Navigate(nextPage); 

You can also load a new page by setting the container’s Source or Content properties. For example:

Page2 p2 = new Page2();
P2.InitializeComponent();
navWindows.Content = p2;

Fragment Navigation

By default, when you navigate to a target page, the navigation container displays only the uppermost part of the page if you have a long page. To allow navigation to a section of the page that is well below the top of the page, you have to use fragment navigation. Fragment navigation allows you to navigate to a specific element within the page.

By default, a fragment in WPF is considered a named element

  <TextBlock FontSize="20" Name="TextBlockFragment">ID: 123</TextBlock>


To navigate to a particular element on a page, you must assign a value to its
Name property. You then construct a URI that that includes the value of the Name attribute and conforms to the following format:

URI#FragmentName

 This approach can be used with a hyperlink or programmatically. For example:

MyFrame.Navigate( new Uri("HyperlinkedPage.xaml#TextBlockFragment ", UriKink.RelativeOrAbsolute));

Refreshing a Page

Refreshing a page is essentially navigating to the same page so that it gets reloaded. Refresh will reset state of all controls in the page as well:

navWindow.Refresh();

You can distinguish between a refresh and normal navigation by handling a navigation event such as Navigation and examining the event argument's NavigationMode property. For a page refresh, the NavigationMode property is set to Refresh.

Events

Once you initiate navigation to a page, the following steps occur:

  1. Locate the required page.
  2. Download the page.
  3. Download any related sources (images, etc.) 4. Parse the page and construct the visual tree.
  4. Render the page

WCF is asynchronous and this means that Navigate method returns as soon as it initiates the navigation process. To monitor progress of the navigation process, WCF provides a number of navigation-related events. These events occur in the following order:

  1. NavigationProgress
  2. Navigated
  3. LoadCompleted

Structured Navigation

Conventional navigation is based on URI strings. You navigate to a specific URI and the system loads and renders the required page. Structured navigation on the other hand, is based on objects called "page functions" represented by the PageFunction type (inherits indirectly from Page). To navigate, you create the appropriate PageFunction object and navigate to it. Rather than loading and rendering the specific page, the system initializes the PageFunction object, which controls what happens next. This approach offers several advantages over URI strings:

  1. Because you implement page function objects, you have full control over how they behave.
  2. You can define one or more constructors for a page function object, allowing you to pass any data to the target page.
  3. The system keeps track of navigation in the journal. To return from the target page function to the previous page function, just call OnReturn method. OnReturn notifies the previous page so you can pass data back to the previous page.
  4. While page functions typically have UI, you can implement UI-less page functions. Their purpose is typically to serve as a 'navigation hub' that decides where the user needs to go next.

When one page navigates to a second page, there are no mechanisms that allow the first page to detect when the user has finished with the second page. This is in part because Web applications naturally support a stateless navigation. Unstructured navigation makes it difficult to create task-based UI where one page calls another page to perform some task, and then possibly, get data returned from the called page.

For stand-alone applications, tasks are typically processed using dialog boxes that naturally support a structured call/return style of programming. Web developers have to create their own call/return infrastructure. It is possible to build a simple structured navigation implementation using Page and Hyperlink, with the help of application-scope variables Properties and NavigationService. Extending this simple implementation to support task removal and task history is non-trivial. The solution is to use WPF page functions which allow one page to call another, and then both can detect and handle when the second page returns.

PageFunction is the cornerstone of structured navigation in WPF. Essentially, page functions enable a style of navigation that is analogous to functions in procedural languages, which typically involves the following:

  1. Passing parameters to a function and calling it.
  2. Performing processing within the function.
  3. Calling other functions from within the main function.
  4. Returning results to the calling code.
  5. Cleaning up.

PageFunction essentially allows you to replicate this model to build a task using structured navigation. See the Structured Navigation example for full details.

Navigation Topologies

Pages in structured navigation are organized in a navigation topology.
This structure defines how navigation takes place between the page that make up the program. There are two approaches to constructing a navigation topology:

  1. Fixed topologies are defined in advance and do not change. This is useful when you need to navigate through a fixed sequence of pages.
  2. Adaptive topologies modify themselves in response to user interaction or other data collected at run-time.

In practice, you may implement a fixed topology for some parts of the program, and an adaptive topology for other parts.

Examples

Navigation Fundamentals

The following code illustrates fundamentals of navigation:

<!-- App.xaml -->

<!--
Unlike Navigation1 which consisted only of loose XAML pages, an XAML Browser Application(XBAPs) is a compiled .NET Framework 3.0 that runs from within IE.
 
Note here the use of StartupUri to specify which page should be shown when the application
is launched.
-->
<Application
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  StartupUri="MainWindow.xaml">
</Application>

<! -- MainWindow.xaml -->

<!-- The represents the main window of the application -->
<Window     x:Class="Navigation.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="NavigationService Sample"
            Height="600"
            Width="500"
            Loaded="MainWindow_Loaded"> 
      <!--
The one and only child element -->
      <DockPanel>
            <DockPanel DockPanel.Dock="Top">
                  <Label DockPanel.Dock="Left">Address:</Label>
                  <Button DockPanel.Dock="Right" Name="goButton" Click="goButton_Click">Go</Button>
                  <TextBox Name="addressTextBox">HyperlinkedPage.xaml</TextBox>
            </DockPanel>
 
            <!--
This list box collects naviagation events -->
            <ListBox DockPanel.Dock="Bottom" Name="navigatingEventsListBox" Height="200"></ListBox>
 
            <
Frame Name="PageFrame" NavigationUIVisibility="Hidden" Source="HomePage.xaml"></Frame>
      </DockPanel>
</Window>

// MainWindow.cs

// Code-behind for the main window
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
 
namespace Navigation
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
         NavigationService NavigationService
        {
            // Retrieve the navigation service, provided by the frame,
            // for the frame content.
            get { return NavigationService.GetNavigationService((DependencyObject)this.PageFrame.Content); }
        }
 
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            /* A Page may not know what its host will be at run time and therefore cannot integrate
             * directly with its host's navigation members. Instead, a page uses a navigation service
             * which supports browser-style navigation and is encapsulated by the NaviagationService
             * class You cannot create a NavigationService object, instead, host types such as
             * NavigationWindow, Frame, or a browser create their own NavigationService that you can
             * access from the page's NavigationService property */
         
           
            // Hook up all navigation events
            this.NavigationService.Navigating           += Page_Navigating;
            this.NavigationService.Navigated            += Page_Navigated;
            this.NavigationService.NavigationProgress   += Page_NavigationProgress;
            this.NavigationService.NavigationStopped    += Page_NavigationStopped;
            this.NavigationService.NavigationFailed     += Page_NavigationFailed;
            this.NavigationService.LoadCompleted        += Page_LoadCompleted;
        }
 
        void goButton_Click(object sender, RoutedEventArgs e)
        {
            // Navigate to the window identified by the user
            this.navigatingEventsListBox.Items.Clear();
            Uri uri = new Uri(this.addressTextBox.Text, UriKind.RelativeOrAbsolute);
            this.NavigationService.Navigate(uri);
        }
 
        #region Navigation Events
        void Page_Navigating(object sender, NavigatingCancelEventArgs e)
        {
            Log("Navigating: [" + e.Uri + "]");
        }
        void Page_Navigated(object sender, NavigationEventArgs e)
        {
            Log("Navigated: [" + e.Uri + "]");
        }
        void Page_NavigationProgress(object sender, NavigationProgressEventArgs e)
        {
            Log("Progress: " + e.BytesRead.ToString() + " of " + e.MaxBytes.ToString() + " [" + e.Uri + "]");
        }
        void Page_NavigationStopped(object sender, NavigationEventArgs e)
        {
            Log("Navigation Stopped: [" + e.Uri + "]");
        }
        void Page_NavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            Log("Navigation Failed: [" + e.Uri + " - " + e.Exception.Message + "]");
        }
        void Page_LoadCompleted(object sender, NavigationEventArgs e)
        {
            Log("Load Completed: [" + e.Uri + "]");
        }
        #endregion
 
        #region
Helper
        void Log(string item)
        {
            this.navigatingEventsListBox.Items.Add(item);
            this.navigatingEventsListBox.SelectedIndex = this.navigatingEventsListBox.Items.Count - 1;
            this.navigatingEventsListBox.Focus();
        }
        #endregion
    }
}

<!-- HomePage.xaml -->

<!-- A Page that is navigated to using the Navigate command. User enters the name of this page in the text box -->

<
Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Title="HomePage">
    <Grid>
            <TextBlock>Home Page</TextBlock>
    </Grid>
</Page>

<!-- HyperLinkedPage.xaml-->

<!-- A Page that is navigated to using a HyperLink -->
<
Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    WindowTitle="Page2"
    >
 
      <!--
The one and only one chile element of the implied Page.Content -->
      <TextBlock>
            <TextBlock FontSize="20">Name: Yazan</TextBlock>
            <LineBreak/>
            <TextBlock FontSize="20" Name="TextBlockFragment">ID: 123</TextBlock>
      </TextBlock>
</Page>

Output Screens

Clicking on Go. Navigates to the HyperLinkedPage page:

Clicking on Go. Navigates to the homepage page:

Structured Navigation

Previous navigation shown so far in unstructured:  The following example shows how to use the PageFunction function to implement structured navigation.

<!-- App.xaml -->
<Application x:Class="NavigationStructured.App
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml"
    >
    <
Application.Resources>
         
    </
Application.Resources>
</Application>

<!-- Window1.xaml -->
<
Window x:Class="NavigationStructured.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="NavigationStructured" Height="300" Width="300"
    >
    <
Grid>
            <Frame Source="CallingPage.xaml"></Frame>
    </Grid>
</Window> 

<!-- CallingPage.xaml -->
<
Page x:Class="NavigationStructured.CallingPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="CallingPage"
    >
      <
Hyperlink NavigateUri="TaskPage.xaml" Click="Hyperlink_StartTaksHandler">Start Task</Hyperlink>
</Page>

// CallingPage.cs

public partial class CallingPage : System.Windows.Controls.Page
{
    public CallingPage()
    {
        InitializeComponent();
    }

    private void Hyperlink_StartTaksHandler(object sender, RoutedEventArgs args)
    {
        // Instantiate task page function
        TaskPage task = new TaskPage("TestData");
 
        // Add a handled to PageFunction's Return event which is called by the task page
        // when it returns data to its caller
        task.Return += new ReturnEventHandler<string>(task_Return);
 
        // Calling a task that is composed from page functions is similar to calling a task
        // that is composed from pages; just navigate to it using the NavigationService
        this.NavigationService.Navigate(task);
    }
 
    void task_Return(object sender, ReturnEventArgs<string> e)
    {
        Console.WriteLine("TaskPage returned: " + e.Result);
    }
}

<!-- TaskPage.xaml -->

<!-- This file was added using the 'Page Function (WPF)' template from Add -> New Item ... The declaration of <PageFunction> element is similar to <Page>. However, because <PageFunction> element represents a generic class, <PageFunction> element requries a
type identifier as well, represented by the x:TypeArguments property, to define the
type of the value that the PageFunction will return. In this case, PageFunction is
defined to return a 'String'.

Note that you could also use a custom type from either the local assembly or a referenced
assembly. For example:
 
<PageFunction ...
    xmlns:local="clr-namespace:LocalNamespace"
    x:TypeArguments="local:CustomType">
    ...
</PageFunction>
 
-->
<
PageFunction
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    x:Class="NavigationStructured.TaskPage"
    x:TypeArguments="sys:String"
    Title="TaskPage">
      <Grid Margin="10">
 
            <
Grid.ColumnDefinitions>
                  <ColumnDefinition Width="Auto" />
                  <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                  <RowDefinition Height="Auto" />
                  <RowDefinition />
            </Grid.RowDefinitions>
 
            <!--
Task data -->
            <Label Grid.Column="0" Grid.Row="0">DataItem1:</Label>
            <TextBox Grid.Column="1" Grid.Row="0" Name="dataItem1TextBox"></TextBox>
 
            <!--
Accept/Cancel buttons -->
            <
TextBlock Grid.Column="1" Grid.Row="1" HorizontalAlignment="Right">
                  <Button Name="okButton" Click="okButton_Click" IsDefault="True" Width="50" Height="25">OK</Button>
                  <Button Name="cancelButton" Click="cancelButton_Click" IsCancel="True" Width="50" Height="25">Cancel</Button>
            </TextBlock>
 
      </
Grid>
 </PageFunction> 

// TaskPage.cs

public partial class TaskPage : System.Windows.Navigation.PageFunction<String>#
{
    private string _strData = String.Empty;
    public TaskPage(string strInitialData)
    {
        InitializeComponent();
 
        // Save data passed from the calling code
        _strData = strInitialData;
    }
 
    // Data of any kind is returned from a page function by calling the OnReturn method
    // OnReturn is a protected virtual method that you call to return your data to the
    // calling page. Data is packaged in an instance of the generic ReturnEventArgs type
    void okButton_Click(object sender, RoutedEventArgs e)
    {
        // Accept task when Ok button is clicked. The generic class ReturnEventArgs
        // takes a string type identifier because <PageFunction> element specifies 'string'
        // for its x:TypeArguments attribute
        OnReturn(new ReturnEventArgs<string>(this.dataItem1TextBox.Text));
    }
 
    // Data of any kind is returned from a page function by calling the OnReturn method
    void cancelButton_Click(object sender, RoutedEventArgs e)
    {  
       // Cancel task
       OnReturn(null);
    }
}

Output Screens