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 |
|
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. |
First I’ll examine the MyApp.xaml file in the project. An Avalon Navigation application starts with the
The content of Page1.xaml is shown below:
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. |
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:
- BooleanPageFunction?returns a Boolean value; represented by the
element. - Int32PageFunction?returns a Integer value; represented by the
element. - ObjectPageFunction?returns an object value; represented by the
element. - StringPageFunction?returns a string; represented by the
element.
In Page2.xaml, the root element is
For now, though, I’ll stick to the original
Populate Page1.xaml with the following:
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. |
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 SubEnd Class
In Page1.xaml.vb, I added two methods:
- ButtonClick?to service the clicking of the Button control
- return_handler?to 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:
What is your name?
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 SubEnd Class
Note that the Page2 class inherits from the PageFunction class (matching the corresponding
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. |
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.xamlPrivate Sub ButtonClick( _ ByVal sender As Object, _ ByVal e As RoutedEventArgs) Me.RemoveFromJournal = TrueOnFinish(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 SubEnd 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)
Linear Navigation
Now that you've had an introduction to page navigation in WPF applications, it's time for some vital specifics. There are basically two main types of navigations in WPF:
- Linear
- Hierarchical
![]() Figure 10. Linear Navigation: The three pages of the application have a linear relationship. | ? | ![]() Figure 11. Page1.xaml: Here's the first page of the new application, which will use linear navigation. |
Populate Page1.xaml as follows:
Page 1 Enter your name
Figure 11 shows how Page1.xaml will look like when loaded.
In the code-behind, Page1.xaml.vb, code the following:
Partial Public Class Page1 Inherits Page Public WithEvents nextPage As Page2 '---used for preserving the name entered in this page Private Shared _Name As String Private Sub OnLoaded( _ ByVal sender As Object, _ ByVal e As RoutedEventArgs) Handles Me.Loaded txtName.Text = _Name End Sub 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) '---navigate to next page--- If sender.Equals(btnNextPage) Then '---save the name entered _Name = txtName.Text nextPage = New Page2 nextPage.InitializeComponent() navWindow.Navigate(nextPage) End If '---exit the application--- If sender.Equals(btnExit) Then System.Windows.Application.Current.Shutdown() End If End Sub Private Sub nextPage_Return( _ ByVal sender As Object, _ ByVal args As StringReturnEventArgs) Handles _ nextPage.Return '---display the string entered in the next window returnString.Text = "From Page 2: " & _ args.Result.ToString End SubEnd Class
Here are the important points to note for this page:
- The ButtonClick() method is fired when either the Next Page button or the Exit button is clicked.
- If the Next Page button is clicked, the application navigates to Page2.xaml. If the Exit button is clicked, the application quits.
- Before the application navigates to the Page2.xaml, the name entered by the user must be preserved using a shared variable. This is because when you navigate between pages (either programmatically or using the navigational buttons) the state of the page is not preserved. Therefore you need to maintain your own state.
- The OnLoaded() method is fired whenever the page is loaded, so this is the place to set the value of controls to their original values/state.
- The nextPage_Return() method is fired when Page2.xaml returns a value to it.
Populate Page2.xaml as follows:
Page 2 Enter your company name
Note that Page2.xaml is a StringPageFunction, which means it will return a string value back to the calling page. Figure 12 shows how Page2.xaml will look like when loaded.
![]() |
|
Figure 12. Page2.xaml: Here's the second page of the application, which returns a string to the first page. |
In the code-behind, Page2.xaml.vb, code the following:
Partial Public Class Page2 Inherits StringPageFunction Public WithEvents nextPage As Page3'---used for preserving the company name entered ' in this page Private Shared _CompanyName As String Private Sub OnLoaded(ByVal sender As Object, _ ByVal e As RoutedEventArgs) Handles Me.Loaded txtCompanyName.Text = _CompanyName End Sub 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) '---navigate to next page--- If sender.Equals(btnNextPage) Then _CompanyName = txtCompanyName.Text nextPage = New Page3 nextPage.InitializeComponent() navWindow.Navigate(nextPage) End If If sender.Equals(btnPreviousPage) Then OnFinish(txtCompanyName.Text) End If '---exit the application--- If sender.Equals(btnExit) Then System.Windows.Application.Current.Shutdown() End If End Sub Private Sub nextPage_Return(ByVal sender As Object, _ ByVal args As BooleanReturnEventArgs) Handles _ nextPage.Return '---display the string entered in the next window returnString.Text = "From Page 3: " & _ args.Result.ToString End SubEnd Class
Basically, Page2.xaml.vb is very similar to Page1.xaml.vb, except that when the Previous Page button is clicked, it returns the company name entered back to the calling page using the OnFinish() method. The Next Page button will cause the application to navigate to Page3.xaml.
Finally, populate Page3.xaml as follows:
Page 3 Single? True False
Note that Page3.xaml is a BooleanPageFunction, which returns a Boolean result back to the calling page. Figure 13 shows how Page3.xaml will look like when loaded.
![]() Figure 13. Page3.xaml: The final page in this three-page application asks for a simple Boolean, which is returned to page 2. |
? | ![]() Figure 14. Flow of the Application: The three pages of this linear application go forward through pages 1, 2, and 3, sequentially, and then back in reverse order. |
Partial Public Class Page3 Inherits BooleanPageFunction'---used for preserving the maritial status ' selected in this page Private Shared _Status As Boolean ' Sample event handlers: Private Sub OnLoaded(ByVal sender As Object, _ ByVal e As RoutedEventArgs) Handles Me.Loaded If _Status = True Then rbTrue.IsChecked = True Else rbFalse.IsChecked = True End If End Sub Private Sub ButtonClick(ByVal sender As Object, _ ByVal e As RoutedEventArgs) If sender.Equals(btnPreviousPage) Then If rbTrue.IsChecked = True Then _Status = True Else _Status = False End If OnFinish(_Status) End If '---exit the application--- If sender.Equals(btnExit) Then System.Windows.Application.Current.Shutdown() End If End SubEnd Class
Now you can run the application and trace the flow (see Figure 14).
Hierarchical Navigation
![]() |
|
Figure 15. Hierarchy of Three: This hierarchical navigation application has pages 2 and 3 returning always to page 1. |
In a WPF app with hierarchical navigation, users can branch out to other pages based on their choice. Figure 15 shows a typical hierarchical navigation application. Note that each page can branch out to as many pages as required, but as each page exits it will return to its calling page.
Figure 16 shows an example implementation of the hierarchical navigation application shown in Figure 15. In the first page, the user can enter his name and to select his country, he can click on the Select Country button to load the second page. To select his industry, he can click on the Select Industry button to load the third page. The pages for selecting country and industry both return to Page 1.
Once the selections are made, the results are shown on the main page, which serves as the master page for the entire application (see Figure 17).
Listing 1 is the code?broken into six sections?of the code for the three pages. Please be careful to heed the breaks in this code. Subheads are given.
![]() Figure 16. Two Child Pages: An implementation of a hierarchical navigation application is shown. |
? | ![]() Figure 17. Back to the Beginning: Data entered from the other pages are shown in Page1.xaml. |