Sunday, March 25, 2012

WPF Page Navigation



WPF provides support for browser style navigation inside standalone application using Page class. User can create multiple pages, navigate between those pages along with data.There are multiple ways available to Navigate through one page to another page.

Page can be implemented as root element in Xaml file and can contain single element similar to window.

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

Here you noticed that instead of Window element Page element is the root element. Similar to normal xaml file you can specify page name StartupUrl inside App.xaml to open specific page.

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

Navigate to another page using hyperlink

The simplest way to navigate from one page to another page is using hyperlink. Hyperlink requires two things one is the URL or page name to navigate and another is content on which user can click and navigate to page. Let’s have a look on below code snippet.

Page1
<Page x:Class="WpfApplication1.Page1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="Page1">
    <StackPanel>
        <TextBlock>
            Go to <Hyperlink NavigateUri="Page2.xaml"> Page 2 </Hyperlink>
        </TextBlock>
    </StackPanel>
</Page>


Page 2
<Page x:Class="WpfApplication1.Page2"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="Page2"> 
    <StackPanel>
        <TextBlock Margin="10">Welcome to Page2.</TextBlock>
        <TextBlock Margin="10">
            Go back to <Hyperlink NavigateUri="Page1.xaml"> Page 1 </Hyperlink>
        </TextBlock>
    </StackPanel>
</Page>


Navigate to page using Navigation Service

Alternatively you can use navigation service implemented with Page class to navigate to another page. The navigate method of navigation service enables the current page to be changed to another page. This method accepts page name or URI.

<Page x:Class="WpfApplication1.Page1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="Page1">
    <StackPanel>
        <TextBlock Margin="10"> Welcome to Page 1.</TextBlock>
        <TextBlock Margin="10">
            Go to <Hyperlink Click="Hyperlink_Click"> Page 2 </Hyperlink>
        </TextBlock>
    </StackPanel>
</Page>

private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
    Page2 p2 = new Page2();
    this.NavigationService.Navigate(p2);
}

In above example, navigate method has been used to navigate to page2. Navigation service is implemented in Page class by default. So In above example instead of NavigateUri property of hyperlink click event is used to navigate to page2.

You can also navigate to an external or internal page by specifying URI in navigate method. The below line of code will open Google page.

this.NavigationService.Navigate(new Uri("http://www.google.com"));

Navigate to page using Journal

Journal keeps history of navigated page and user can also navigate Back, Forward using journal. User can navigate journal using xaml code as well code behind. Let’s have a look on below code.

<StackPanel>
    <TextBlock Margin="10">
        Navigate to <Hyperlink Command="BrowseBack">previous page. </Hyperlink>
    </TextBlock>
    <TextBlock Margin="10">
        Navigate to <Hyperlink Command="BrowseForward">next page. </Hyperlink>
    </TextBlock>
</StackPanel>


or

this.NavigationService.GoForward();
this.NavigationService.GoBack();

Navigating between pages inside Frame

Sometimes, application might not need whole page hosted as navigation window but it requires some small part inside window, in such cases we can use frame control to host page navigation framework. Frame class provides inbuilt support NavigationFramework. Let’s have a look on below code.

<StackPanel>
    <TextBlock Margin="10">This is Main Window.</TextBlock>
    <Frame Margin="10"
        Source="Page1.xaml"
            JournalOwnership="OwnsJournal"></Frame>
</StackPanel>


Above example demonstrate frame control which contains Page. Frame control is capable to hold page and it also support navigation. Frame also maintains its own journal if JournalOwnership property of Frame control set to “OwnsJournal”. If you specify “UsesParentJournal” then it will use parent page’s journal.

Passing data between pages

There are multiple ways available to pass data between pages using page navigation. The simplest and easiest way to pass data between pages using page's constructor.

Page2 p2 = new Page2("This is Page2");
this.NavigationService.Navigate(p2);

public Page2(string message)
{
     Console.WriteLine(message);
}

Another way to pass data is you can send extra data with Navigate method of NavigationService and get extra data on target page.

Page1
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
    Page2 p2 = new Page2();
    this.NavigationService.Navigate(p2,10);
}

Page2
void Page2_Loaded(object sender, RoutedEventArgs e)
{
    NavigationService ns = this.NavigationService;
    if (this.NavigationService != null)
        this.NavigationService.LoadCompleted += new LoadCompletedEventHandler(NavigationService_LoadCompleted);
}
void NavigationService_LoadCompleted(object sender, NavigationEventArgs e)
{
    if (e.ExtraData != null)
        Console.WriteLine(e.ExtraData);
}

In above example, data has been pass to Navigate method on page 1 and the same handled and retrieved on page 2 using NaivgationService’s LoadCompleted method.

Another way to keep data shred across multiple pages is using Application object’s property collection which stores data shared across pages.

Page1
Page2 p2 = new Page2();
Application app = Application.Current;
app.Properties["PageData"] = 10;
this.NavigationService.Navigate(p2);

Page2
Application app = Application.Current;
if (app.Properties["PageData"] != null)
    Console.WriteLine(app.Properties["PageData"].ToString());

Returning data back from page with PageFunction

Sometimes you might require some user input for particular action so to achieve that you might need to create one user input page and return data. But WPF has inbuilt facility to do the same using PageFunction. PageFunction is special class which allows user to return data back to caller page.

Let’s have a look on below code to get some more idea on PageFunction class.

Page1
XAML
<Page x:Class="WpfApplication1.Page1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="Page1">
    <StackPanel>
        <TextBlock Margin="10"> Welcome to Page 1.</TextBlock>
        <TextBlock Margin="10">
            Go to <Hyperlink Click="Hyperlink_Click"> data entry page </Hyperlink>
        </TextBlock>
        <TextBlock Name="lblName" Margin="10"></TextBlock>
    </StackPanel>
</Page>

Code
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
    DataEntryPage userInputPage = new DataEntryPage();
    userInputPage.Return += new ReturnEventHandler<string>(userInputPage_Return);
    NavigationService.Navigate(userInputPage);
}
void userInputPage_Return(object sender, ReturnEventArgs<string> e)
{
    lblName.Text = string.Format("Your name is {0}", e.Result);
}

Data Entry Page
XAML
<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="WpfApplication1.DataEntryPage"
    x:TypeArguments="sys:String"
    Title="DataEntryPage">
    <StackPanel>
        <TextBlock Margin="10">Welcome to Data Entry Page.</TextBlock>
        <WrapPanel>
            <TextBlock Margin="10">Enter your Name:</TextBlock>
            <TextBox Name="txtName" Height="25" Width="150" />
        </WrapPanel>
        <TextBlock Margin="10">
        Return back to <Hyperlink Click="Hyperlink_Click"> Page 1 </Hyperlink>
        </TextBlock>
    </StackPanel>
</PageFunction>

Code
public partial class DataEntryPage : PageFunction<String>
{
    public DataEntryPage()
    {
        InitializeComponent();
    }
    private void Hyperlink_Click(object sender, RoutedEventArgs e)
    {
       OnReturn(new ReturnEventArgs<string>(txtName.Text));
    }
}

Output
Page1

Data Entry Page


Return Back to Page1 with Data



As per above output, When user clicks on goto data entry page link from page1 it will redirect to data entry page. User enters name in textbox and clicks on return back to page1 hyperlink it will redirect back to Page1 with entered name.

So Data entry page implements PageFunction class which is generic type where the type argument represents return type.

public partial class DataEntryPage : PageFunction<String>

<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="WpfApplication1.DataEntryPage"
    x:TypeArguments="sys:String"
    Title="DataEntryPage">
</PageFunction>


OnReturn method of PageFunction class automatically returns the data or value to the caller page. In above example it will redirect back to Page1 with data.
private void Hyperlink_Click(object sender, RoutedEventArgs e)
    {
    OnReturn(new ReturnEventArgs<string>(txtName.Text));
    }

On caller page, we need to handle the return value coming from data entry page. For that we need to register and handle return event. Return value can be retrieved from e.Result property.

void userInputPage_Return(object sender, ReturnEventArgs<string> e)
{
    lblName.Text = string.Format("Your name is {0}", e.Result);
}


See Also –