This topic shows how to implement the basic mechanics of structured navigation involving a single PageFunction<(Of <(T>)>). In this sample, a Page calls a PageFunction<(Of <(T>)>) to get a String value from the user and return it.
Creating a Calling Page
The page that calls a PageFunction<(Of <(T>)>) can be either a Page or a PageFunction<(Of <(T>)>). In this example, it is a Page, as shown in the following code.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="StructuredNavigationSample.CallingPage"
WindowTitle="Calling Page"
WindowWidth="250" WindowHeight="150">
...
</Page>
Imports System.Windows ' RoutedEventArgs, RoutedEventHandler, Visibility
Imports System.Windows.Controls ' Page
Imports System.Windows.Navigation ' ReturnEventArgs
Namespace StructuredNavigationSample
Public Class CallingPage
Inherits Page
Public Sub New()
Me.InitializeComponent()
...
End Sub
...
End Class
End Namespace
using System.Windows; // RoutedEventArgs, RoutedEventHandler, Visibility
using System.Windows.Controls; // Page
using System.Windows.Navigation; // ReturnEventArgs
namespace StructuredNavigationSample
{
public partial class CallingPage : Page
{
public CallingPage()
{
InitializeComponent();
...
}
...
}
}
Creating a Page Function to Call
Because the calling page can use the called page to collect and return data from the user, PageFunction<(Of <(T>)>) is implemented as a generic class whose type argument specifies the type of the value that the called page will return. The following code shows the initial implementation of the called page, using a PageFunction<(Of <(T>)>), which returns a 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="StructuredNavigationSample.CalledPageFunction"
x:TypeArguments="sys:String"
Title="Page Function"
WindowWidth="250" WindowHeight="150">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<!-- 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" IsDefault="True" MinWidth="50">OK</Button>
<Button Name="cancelButton" IsCancel="True" MinWidth="50">Cancel</Button>
</TextBlock>
</Grid>
</PageFunction>
Imports System ' String
Imports System.Windows ' RoutedEventArgs, RoutedEventHandler
Imports System.Windows.Navigation ' PageFunction
Namespace StructuredNavigationSample
Public Class CalledPageFunction
Inherits PageFunction(Of String)
Public Sub New()
Me.InitializeComponent()
End Sub
...
End Class
End Namespace
using System; // String
using System.Windows; // RoutedEventArgs, RoutedEventHandler
using System.Windows.Navigation; // PageFunction
namespace StructuredNavigationSample
{
public partial class CalledPageFunction : PageFunction<String>
{
public CalledPageFunction()
{
InitializeComponent();
}
...
}
}
The declaration of a PageFunction<(Of <(T>)>) is similar to the declaration of a Page with the addition of the type arguments. As you can see from the code example, the type arguments are specified in both XAML markup, using the x:TypeArguments attribute, and code-behind, using standard generic type argument syntax.
You don't have to use only .NET Framework classes as type arguments. A PageFunction<(Of <(T>)>) could be called to gather domain-specific data that is abstracted as a custom type. The following code shows how to use a custom type as a type argument for a PageFunction<(Of <(T>)>).
Public Class CustomType
...
End Class
namespace SDKSample
{
public class CustomType
{
...
}
}
<PageFunction
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.CustomTypePageFunction"
x:TypeArguments="local:CustomType">
...
</PageFunction>
Partial Public Class CustomTypePageFunction
Inherits System.Windows.Navigation.PageFunction(Of CustomType)
using System.Windows.Navigation; // PageFunction
namespace SDKSample
{
public partial class CustomTypePageFunction : PageFunction<CustomType>
{
The type arguments for the PageFunction<(Of <(T>)>) provide the foundation for the communication between a calling page and the called page, which are discussed in the following sections.
As you'll see, the type that is identified with the declaration of a PageFunction<(Of <(T>)>) plays an important role in returning data from a PageFunction<(Of <(T>)>) to the calling page.
Calling a PageFunction and Passing Parameters
To call a page, the calling page must instantiate the called page and navigate to it using the Navigate method. This allows the calling page to pass initial data to the called page, such as default values for the data being gathered by the called page.
The following code shows the called page with a non-default constructor to accept parameters from the calling page.
Imports System ' String
Imports System.Windows ' RoutedEventArgs, RoutedEventHandler
Imports System.Windows.Navigation ' PageFunction
Namespace StructuredNavigationSample
Public Class CalledPageFunction
Inherits PageFunction(Of String)
...
Public Sub New(ByVal initialDataItem1Value As String)
Me.InitializeComponent()
...
' Set initial value
Me.dataItem1TextBox.Text = initialDataItem1Value
End Sub
...
End Class
End Namespace
using System; // String
using System.Windows; // RoutedEventArgs, RoutedEventHandler
using System.Windows.Navigation; // PageFunction
namespace StructuredNavigationSample
{
public partial class CalledPageFunction : PageFunction<String>
{
...
public CalledPageFunction(string initialDataItem1Value)
{
InitializeComponent();
...
// Set initial value
this.dataItem1TextBox.Text = initialDataItem1Value;
}
...
}
}
The following code shows the calling page handling the Click event of the Hyperlink to instantiate the called page and pass it an initial string value.
<Hyperlink Name="pageFunctionHyperlink">Call Page Function</Hyperlink>
Imports System.Windows ' RoutedEventArgs, RoutedEventHandler, Visibility
Imports System.Windows.Controls ' Page
Imports System.Windows.Navigation ' ReturnEventArgs
Namespace StructuredNavigationSample
Public Class CallingPage
Inherits Page
Public Sub New()
Me.InitializeComponent()
AddHandler Me.pageFunctionHyperlink.Click, New RoutedEventHandler(AddressOf Me.pageFunctionHyperlink_Click)
End Sub
Private Sub pageFunctionHyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
...
End Sub
...
End Class
End Namespace
using System.Windows; // RoutedEventArgs, RoutedEventHandler, Visibility
using System.Windows.Controls; // Page
using System.Windows.Navigation; // ReturnEventArgs
namespace StructuredNavigationSample
{
public partial class CallingPage : Page
{
public CallingPage()
{
InitializeComponent();
this.pageFunctionHyperlink.Click += new RoutedEventHandler(pageFunctionHyperlink_Click);
}
void pageFunctionHyperlink_Click(object sender, RoutedEventArgs e)
{
...
}
...
}
}
You are not required to pass parameters to the called page. Instead, you could do the following:
But, as you'll see shortly, you'll still need use code to instantiate and navigate to the called page to collect the data returned by the called page. For this reason, the PageFunction<(Of <(T>)>) needs to be kept alive; otherwise, the next time you navigate to the PageFunction<(Of <(T>)>), WPF instantiates the PageFunction<(Of <(T>)>) using the default constructor.
Before the called page can return, however, it needs to return data that can be retrieved by the calling page.
Returning Task Result and Task Data from a Task to a Calling Page
Once the user has finished using the called page, signified in this example by pressing either the OK or Cancel buttons, the called page needs to return. Since the calling page used the called page to collect data from the user, the calling page requires two types of information:
Whether the user canceled the called page (by pressing either the OK button or the Cancel button in this example). This allows the calling page to determine whether to process the data that the calling page gathered from the user.
The data that was provided by the user.
To return information, PageFunction<(Of <(T>)>) implements the OnReturn method. The following code shows how to call it.
Imports System ' String
Imports System.Windows ' RoutedEventArgs, RoutedEventHandler
Imports System.Windows.Navigation ' PageFunction
Namespace StructuredNavigationSample
Public Class CalledPageFunction
Inherits PageFunction(Of String)
...
Private Sub okButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
' Accept when Ok button is clicked
Me.OnReturn(New ReturnEventArgs(Of String)(Me.dataItem1TextBox.Text))
End Sub
Private Sub cancelButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
' Cancel
Me.OnReturn(Nothing)
End Sub
End Class
End Namespace
using System; // String
using System.Windows; // RoutedEventArgs, RoutedEventHandler
using System.Windows.Navigation; // PageFunction
namespace StructuredNavigationSample
{
public partial class CalledPageFunction : PageFunction<String>
{
...
void okButton_Click(object sender, RoutedEventArgs e)
{
// Accept when Ok button is clicked
OnReturn(new ReturnEventArgs<string>(this.dataItem1TextBox.Text));
}
void cancelButton_Click(object sender, RoutedEventArgs e)
{
// Cancel
OnReturn(null);
}
}
}
In this example, if a user presses the Cancel button, a value of null is returned to the calling page. If the OK button is pressed instead, the string value provided by the user is returned. OnReturn is a protected virtual method that you call to return your data to the calling page. Your data needs to be packaged in an instance of the generic ReturnEventArgs<(Of <(T>)>) type, whose type argument specifies the type of value that Result returns. In this way, when you declare a PageFunction<(Of <(T>)>) with a particular type argument, you are stating that a PageFunction<(Of <(T>)>) will return an instance of the type that is specified by the type argument. In this example, the type argument and, consequently, the return value is of type String.
When OnReturn is called, the calling page needs some way of receiving the return value of the PageFunction<(Of <(T>)>). For this reason, PageFunction<(Of <(T>)>) implements the Return event for calling pages to handle. When OnReturn is called, Return is raised, so the calling page can register with Return to receive the notification.
Imports System.Windows ' RoutedEventArgs, RoutedEventHandler, Visibility
Imports System.Windows.Controls ' Page
Imports System.Windows.Navigation ' ReturnEventArgs
Namespace StructuredNavigationSample
Public Class CallingPage
Inherits Page
...
Private Sub pageFunctionHyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
' Instantiate and navigate to page function
Dim calledPageFunction As New CalledPageFunction("Initial Data Item Value")
AddHandler calledPageFunction.Return, New ReturnEventHandler(Of String)(AddressOf Me.calledPageFunction_Return)
MyBase.NavigationService.Navigate(calledPageFunction)
End Sub
Private Sub calledPageFunction_Return(ByVal sender As Object, ByVal e As ReturnEventArgs(Of String))
Me.pageFunctionResultsTextBlock.Visibility = Windows.Visibility.Visible
' Display result
Me.pageFunctionResultsTextBlock.Text = IIf((Not e Is Nothing), "Accepted", "Canceled")
' If page function returned, display result and data
If (Not e Is Nothing) Then
Me.pageFunctionResultsTextBlock.Text = (Me.pageFunctionResultsTextBlock.Text & ChrW(10) & e.Result)
End If
End Sub
End Class
End Namespace
using System.Windows; // RoutedEventArgs, RoutedEventHandler, Visibility
using System.Windows.Controls; // Page
using System.Windows.Navigation; // ReturnEventArgs
namespace StructuredNavigationSample
{
public partial class CallingPage : Page
{
...
void pageFunctionHyperlink_Click(object sender, RoutedEventArgs e)
{
// Instantiate and navigate to page function
CalledPageFunction CalledPageFunction = new CalledPageFunction("Initial Data Item Value");
CalledPageFunction.Return += pageFunction_Return;
this.NavigationService.Navigate(CalledPageFunction);
}
void pageFunction_Return(object sender, ReturnEventArgs<string> e)
{
this.pageFunctionResultsTextBlock.Visibility = Visibility.Visible;
// Display result
this.pageFunctionResultsTextBlock.Text = (e != null ? "Accepted" : "Canceled");
// If page function returned, display result and data
if (e != null)
{
this.pageFunctionResultsTextBlock.Text += "\n" + e.Result;
}
}
}
}
Removing Task Pages When a Task Completes
When a called page returns, and the user didn't cancel the called page, the calling page will process the data that was provided by the user and also returned from the called page. Data acquisition in this way is usually an isolated activity; when the called page returns, the calling page needs to create and navigate to a new calling page to capture more data.
However, unless a called page is removed from the journal, a user will be able to navigate back to a previous instance of the calling page. Whether a PageFunction<(Of <(T>)>) is retained in the journal is determined by the RemoveFromJournal property. By default, a page function is automatically removed when OnReturn is called because RemoveFromJournal is set to true. To keep a page function in navigation history after OnReturn is called, set RemoveFromJournal to false.