
n my last article, I talked about the different types of applications one can build using Microsoft's new Avalon UI framework as well as XAML, the language used to build Avalon applications. In this article, I will continue my foray into Avalon application development; this time I will focus on the "navigational" nature of Avalon applications.
(Note: Since that first article, Microsoft bestowed a permanent name on Avalon: Windows Presentation Framework. Throughout the rest of this article, we will refer to Avalon as WPF.)
As discussed in the earlier article, WPF applications can be broadly classified into two types:
- Standalone Windows applications
- Applications containing a series of pages
The latter type, where a user navigates among a series of pages, is more common. The navigational element in applications of this type is provided by the Hyperlink control. However the Hyperlink control presents several shortcomings such as difficulty in passing values to the destination page, the ability to know when a destination page has returned to the calling page, as well as difficulty in passing a value back to the calling page.
And so, in this article I'll show you how to get around those problems; I'll create an WPF application that navigates from one page to another, passing values between pages.
| What You Need |
- Microsoft Visual Studio 2005
- Microsoft Pre-Release Software: Windows Presentation Framework ("Avalon") and Windows Communication Framework ("Indigo") Beta1 RC
- The WinFX SDK
|
Basics of Navigation
Before I dive into the theory of page navigation in WPF, I'd like to start by writing a simple application, explaining key features as I go. First, using Visual Studio 2005, create a new WPF project (I'm using VB.NET) and name the application AvalonNavigation (see Figure 1).

Figure 1. Getting Started: Start by creating a new WPF Navigation application in Visual Studio 2005.
|
|

Figure 2. Content in Solution Explorer: Here's what you should see after creating your new project. |
Your Solution Explorer should look like
Figure 2.
First I'll examine the MyApp.xaml file in the project. An Avalon Navigation application starts with the <NavigationApplication> root element. The StartupUri attribute specifies the page to be loaded when the application is executed; in this case the startup page is Page1.xaml:
<NavigationApplication x:Class="MyApp"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
StartupUri="Page1.xaml"
>
<NavigationApplication.Resources>
</NavigationApplication.Resources>
</NavigationApplication>
The content of
Page1.xaml is shown below:
<Page x:Class="Page1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
>
<Grid>
</Grid>
</Page>
Now I'll add a new Avalon PageFunction item to the project: right-click on the project name in Solution Explorer and then select Add New Item…. and select the Avalon PageFunction template. Change the name of the page to
Page2.xaml and click Add (see
Figure 3).

Figure 3. Pagination: Add a new Avalon PageFunction item to the project.
|
|

Figure 4. Point A to Point B: At this point you can navigate from Page1.xaml to Page2.xaml. |
Once
Page2.xaml is added to the project, double-click on it to examine its content:
<PageFunction x:Class="Page2" x:TypeArguments="String"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
>
<Grid>
</Grid>
</PageFunction>
An AvalonPageFunction is basically a WPF page, except that it can return a result (hence the PageFunction name) back to the calling page.
Figure 4 shows how you can make use of
Page2.xaml from
Page1.xaml and then get the result returned by
Page2.xaml.
There are four types of PageFunction pages:
- BooleanPageFunctionreturns a Boolean value; represented by the <BooleanPageFunction> element.
- Int32PageFunctionreturns a Integer value; represented by the <Int32PageFunction> element.
- ObjectPageFunctionreturns an object value; represented by the <ObjectPageFunction> element.
- StringPageFunctionreturns a string; represented by the <StringPageFunction> element.
In
Page2.xaml, the root element is
<PageFunction>, and not any of the four PageFunction pages listed above. The
<PageFunction> is the generic version of the PageFunction pages and you indicate its type by the x:TypeArguments attribute (set to "String" in this example). If you wish to convert the page to any of the four types described above, say, the StringPageFunction, you can simply change the root element as shown (note that the x:TypeArguments attribute is removed):
<StringPageFunction x:Class="Page2"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
>
<Grid>
</Grid>
</StringPageFunction>
For now, though, I'll stick to the original
<PageFunction> root element.
Populate Page1.xaml with the following:
<Page x:Class="Page1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
>
<StackPanel>
<Button Click="ButtonClick"
HorizontalAlignment="Left"
Margin="10,5,10,5"
Width="120">Go to Page 2
</Button>
<TextBlock Margin="10,5,10,5">
<Inline Name="txtResult"></Inline>
</TextBlock>
</StackPanel>
</Page>
Basically, this page contains a Button control to load
Page2.xaml (see
Figure 5) and also a TextBlock control to display the result returned by
Page2.xaml (note that the first time it is loaded the TextBlock control is not visible since it has no value).

Figure 5. Page1.xaml: The button control loads the second page of the application.
|
|

Figure 6. Page2.xaml: The second page of the application will ask for a user input that will be passed back to Page1.xaml. |
In the code-behind of
Page1.xaml
Page1.xaml.vbcode the following:
Partial Public Class Page1
Inherits Page
Private WithEvents p2 As Page2
Private Sub ButtonClick( _
ByVal sender As Object, _
ByVal e As RoutedEventArgs)
Dim myApp As NavigationApplication
Dim navWindow As NavigationWindow
myApp = CType( _
System.Windows.Application.Current, _
NavigationApplication)
navWindow = CType(myApp.MainWindow, _
NavigationWindow)
p2 = New Page2
p2.InitializeComponent()
navWindow.Navigate(p2)
End Sub
Private Sub return_handler( _
ByVal sender As Object, _
ByVal e As ReturnEventArgs(Of String)) _
Handles p2.Return
txtResult.Text = "Returned: " & e.Result.ToString
End Sub
End Class
In
Page1.xaml.vb, I added two methods:
- ButtonClickto service the clicking of the Button control
- return_handlerto retrieve the result returned by Page2.xaml.
To navigate to a PageFunction page, you need to create an instance of the destination page, call its
InitializeComponent() method, and then finally use the
Navigate() method to navigate to that page. When the destination page function returns a value, you capture the data by servicing the Return event of the page. The returned result is retrieved via the ReturnEventArgs argument.
Now I'll populate Page2.xaml:
<PageFunction x:Class="Page2" x:TypeArguments="String"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
>
<StackPanel HorizontalAlignment="Left">
<TextBlock Margin="10,5,10,5">
What is your name?</TextBlock>
<TextBox Name="txtName" Margin="10,5,10,5"
Width="200"></TextBox>
<Button Click="ButtonClick" Margin="10,5,10,5"
Width="60"
HorizontalAlignment="Left">Done</Button>
</StackPanel>
</PageFunction>
Page2.xaml will display a string of text followed by a TextBox and a Button control (see
Figure 6). The intention here is to prompt the user to enter a name and then pass the name back to
Page1.xaml.
Code the code-behind Page2.xaml.vb as follows:
Partial Public Class Page2
Inherits PageFunction(Of String)
Private Sub ButtonClick( _
ByVal sender As Object, _
ByVal e As RoutedEventArgs)
OnFinish(New ReturnEventArgs(Of _
String)(txtName.Text))
End Sub
End Class
Note that the Page2 class inherits from the PageFunction class (matching the corresponding
<PageFunction> element in the XAML page); you also need to specify the generic type (String, in this case).
When the Done button is clicked, you return the name entered using the OnFinish() method. The OnFinish() method will return the specified string back to the calling page (see Figure 7).

Figure 7. Flow of the Application: From page 1 to page 2 and back, grabbing a name along the way.
|
|

Figure 8. Back and Forward: You can examine the history of pages navigated through the application. |
The navigational button in the application work as they would in a browser, taking you forward and back through visited pages (see
Figure 8). The "untitled" in Figure 8 refers to
Page2.xaml.
All pages that you have loaded are stored automatically in a stack data structure known as the journal. In some cases you might not want to record certain pages in the journal (such as preventing users from inadvertently navigating to a page that is part of a transaction, thereby disrupting the flow of the application). To manually remove a page from the journal, set the RemoveFromJournal property of the current page to "true":
' Page2.xaml
Private Sub ButtonClick( _
ByVal sender As Object, _
ByVal e As RoutedEventArgs)
Me.RemoveFromJournal = True
OnFinish(New ReturnEventArgs(Of _
String)(txtName.Text))
End Sub
With that,
Page2.xaml will not be visible in the navigational buttons (see
Figure 9).
 | |
| Figure 9. Not Back There: You can remove a page from the journal, which records the page history. |
Passing Data into a Page
You have seen how a PageFunction page can pass a value back to the calling page using the OnFinish() method. What about passing a value from a calling page to a PageFunction page? In this case, you can do so by adding a constructor to the PageFunction page. Assuming you need to pass in values to Page2.xaml, you would need to add the following constructors:
Partial Public Class Page2
Inherits PageFunction(Of String)
Public Sub New()
End Sub
Public Sub New(ByVal data As String)
' process the incoming data
End Sub
Private Sub ButtonClick( _
ByVal sender As Object, _
ByVal e As RoutedEventArgs)
Me.RemoveFromJournal = True
OnFinish(New ReturnEventArgs(Of _
String)(txtName.Text))
End Sub
End Class
Note that you need to add the empty constructor or else you will get an error during compilation time. Also, you can add as many constructors as you wish, as long as they have different signatures.
In the calling page, to pass data into the destination page, you simply need to supply the necessary parameter:
p2 = New Page2("the string to pass into")
p2.InitializeComponent()
navWindow.Navigate(p2)