Silverlight Navigation - Part 1
This is the first in a short series of articles on navigation in Silverlight.
Navigation is an often neglected but critical part of an application’s design. From a usability perspective, structuring the application so that the user can easily and intuitively access its various features, functions and data is hugely important. From a development perspective, being able to implement this structure in a simple, flexible and testable way is critical. Sadly, Silverlight 2.0 doesn’t include any framework support for navigation. But on the bright side, missing framework features represent an opportunity to write some code!
This series of posts describes a simple but flexible navigation framework that I’ve been playing with that can hopefully fill some of the gaps in Silverlight 2.0. This first post describes the core elements of the framework and shows some of its basic features. In subsequent posts, I’ll be talking about how the navigation framework can be extended to support data linking, deep linking and other interesting and cool scenarios.
This article coincides with the recent release of the Silverlight 2.0 Release Candidate and all of the sample code is built for it. If you haven’t done so already, install RC0 and the VS2008 tools from here.
Vanilla Silverlight Navigation
If you create a new Silverlight 2.0 project in Visual Studio, you’ll get a project that contains an Application class and a UserControl ‘page’ class. The Application class creates an instance of the page class and sets it as the root visual. Unless you’re building a really small app with a very simple UI, you’ll likely need to add other pages and user controls to your application and then implement navigation by manually hiding, showing or replacing bits of the UI within the root page. In other words, you’re pretty much on your own when it comes to navigation in Silverlight. This is a bit of a pain and means that you have to write code that’s closely tied to the UI and hard to test.
WPF on the other hand provides a fairly flexible navigation system based on Pages hosted in Frames and linked together by Hyperlinks. The Frame class provides a journal and UI so the user can also navigate forward and back through a series of pages. In an XBAP application, the root journal is even hooked into the browser so you can use the browser’s forward and back buttons to navigate pages within the hosted XBAP application.
In general, I think WPF’s navigation model is pretty nice, though I find it can be a little inflexible and somewhat complicated at times. The navigation framework presented here is similar in a lot of ways to the WPF model but (I think) it is simpler and more flexible. In particular it address three of my main grievances with the WPF model – it provides an ‘open’ linking model, it makes it easier to pass state between pages, and it allows you to more easily unit test the navigational structure of your app.
However, I haven’t tried too hard to maintain any level of compatibility with WPF – some of these additional features and capabilities would be difficult to implement in WPF – but the basic model should be familiar to those who are familiar with WPF's model.
A Quick Tour Of Helix (not the one in Oregon)
When playing with or learning about a new technology, I like to build a ‘playground’ project where I can experiment, try out a few ideas, build various bits of an application, and generally see how things fit together. In this case, the playground project was called Helix (named after a small town in Oregon that I spotted on a map once and thought would make a cool code name). Over time, Helix grew into the prototype navigation framework presented here.
Just as in the WPF navigation model, there are three central concepts in the Helix navigation framework – Frames, Pages and Hyperlinks. Each of these are represented by a specific control class:
- Frame – The Frame control is the center of the navigation framework. It is essentially a content control that displays and coordinates the pages within the application. Frames can be nested and can display a UI that allows the user to navigate through a series of pages using forward and back buttons. The Frame control provides various events and methods for navigating to pages or for traversing the journal programmatically.
- Pages – Pages are really just user controls that get displayed in a frame. As the user navigates through the application, pages are created, initialized and set as the content within a Frame control. Pages can implement an optional interface that allows the page to interact and control some aspects of the navigation process. Pages are referenced by a Uri. Most of the time the Uri is a direct reference to the page, but sometimes the reference can be indirect (more on this later).
- NavigationLink – The NavigationLink control encapsulates a Uri to a page. When the user clicks on the NavigationLink, the nearest parent frame navigates to the page specified by the Uri.
I’ve provided the source to the core of the Helix navigation framework that contains these three controls along with a sample application here. I’ll be adding to this core library over the next few articles in this series, but for now let's see how these core pieces fit together...
The Sign-Up Sample
To see these classes in action, I built a simple Silverlight ‘Sign-Up’ application that allows a user to create a new account. You can run the sample application live here.
The application consists of three main pages. The first page allows the user to enter some basic information about themselves (name, age, gender, state, etc) , while the second page allows them to enter their email address, password and a secret question. The last page is a simple confirmation page with a link back to the first page.
To build this application, I started with a new Silverlight project and added a reference to the Helix library. Then I added Frame control to the root page (the control that is set as the root visual by the application class) like this:
<h:Frame x:Name="rootFrame" />
[You'll also need to add an xmlns reference to the Helix library to define the h: prefix]
Next, I added three user controls, called Page1, Page2 and (rather imaginatively) Page3. These contain the UI for each of the steps in the sign-up process. To link these pages together, I added a number of NavigationLink controls and set their NavigateUri properties like this:
<h:NavigationLink Style="{StaticResource NavLinkButtonStyle}"
Content="Step 2" NavigateUri="Page2.xaml" />
The Helix library provides a default template for the Frame and NavigationLink controls (in the generic.xaml file). The default control template for the NavigationLink control is very simple (and dull), so in the Sign-Up app I decided to jazz it up a bit by restyling it as a button (this also gave me a chance to play with the Visual State Manager feature of Silverlight!). The custom NavLinkButtonStyle style is defined in the Sign-Up application’s resources. With this style applied, the navigation link looks like this:
Next, we have to add a couple of lines of code in the root page’s code behind. First we have to tell the navigation manager where to go to find the pages that are specified in the navigation links:
NavigationManager.ApplicationAssembly = Assembly.GetExecutingAssembly();
[This is hopefully a temporary measure, due to the lack of support for loading pages by name in Silverlight 2.0.]
Finally, when the application starts up we want to show the first page, so in the Load event for the root page, we programmatically navigate to the first page.
this.rootFrame.Navigate( new Uri( "Page1.xaml", UriKind.Relative ) );
If you run the application, you will be able to navigate between the pages by clicking on the navigation link buttons. That’s essentially it as far as the basics, but there are many additional features in the core framework that we can take advantage of to make the user experience much better.
Navigation Bar
You can configure the Frame class to show a navigation bar so the user can navigate forwards or backwards through the journal.
<h:Frame x:Name="rootFrame" NavigationUIVisibility="Visible" />
The frame will now display a navigation bar like this:
If the displayed page has specified a page title (see next section), it will be displayed in the navigation bar.
Navigation Pages
When you navigate to a page (remember, a page is just a user control) Helix checks to see if it implements the INavigationPage interface. Implementing this interface allows the page to influence the navigation process. The INavigationPage interface is defined as:
public interface INavigationPage
{
string PageTitle { get; }
bool KeepAlive { get; }
bool CanNavigateAway { get; }
}
The PageTitle property very simply allows the page to specify a page title that the Frame can use in the navigation bar and in the journal.
The KeepAlive property allows the page to specify whether or not the navigation system should re-create a new instance of the page when the user navigates back or forward through the journal, or whether it should keep this instance alive and just re-show it. If you run the application described above with the frame’s navigation bar displayed, the user is able to navigate via page links or by using the forward and back buttons of the frame. You’ll notice pretty soon that if the user enters some data on page 1, navigates to page 2 and then clicks the back button, the data that they entered on page 1 is gone!
In a real application the pages would probably be associated with a presentation or view model that would be responsible for maintaining the state for the pages. Then, when the user returns to a page, a new page UI would be created but it would be re-bound to a presentation model that contained all of the state for the page. However, for simple scenarios, you probably don’t want to go to the trouble of creating and maintaining presentation or view models, so the KeepAlive flag provides a simple way to keep a page instance alive so that it is not recreated from scratch when the user returns to it via the forward or back buttons.
Finally, the CanNavigateAway property allows the page to veto any attempt to navigate away from it (for example, to make sure that all of the data is valid on a form before it is submitted).
Helix provides a base class that already implements this interface so you can simple subclass it to create your pages and set the required properties in the page xaml:
<h:NavigationPage x:Class="Microsoft.Samples.Helix.SignUpSample.Page1"
...
PageTitle="Page 1" KeepAlive="True">
...
</h:NavigationPage>
and in the code behind:
public partial class Page1 : NavigationPage
{
...
}
Navigation Error Handling
What happens if you specify a Uri to a page that doesn’t exist or you mistype the Uri? You might have noticed in the Sign-Up sample links to the privacy policy and the terms of service in the top right hand corner. If you click on these, you will see an error message saying that the pages couldn’t be found. These links were added to show how Helix handles navigation errors. If there is a problem locating or loading the page specified by the Uri, the error is bubbled up to the Frame where it can handle it in one of two ways.
First, the Frame will fire a NavigationFailed event which you can sink to provide whatever error handling your app requires. The Frame can also be configured to automatically show an error message. You can control this by setting the ShowErrorUI property. If this property is false, the frame won’t show any error messages and will just fire the NavigationFailed event.
You'll also notice that the default Frame template displays a mask panel over the frame's contents making the message box semi-modal. This is to prevent the user from navigating to other content until they have closed the message box.
Nested Frames
The first page of the Sign-Up sample shows how Helix can support nested frames. In this (somewhat contrived) example, the first page employs an embedded frame to allow the user to check whether their chosen username is unique. To implement this, the Sign-Up app has two sub pages that are displayed in a sub frame. When Page1 is first shown, the frame shows sub page 1, but when the user clicks on the Suggest a Name navigation link, this frame navigates to sub page 2 where the user can choose a suggested name.
<h:Frame x:Name="subFrame" BorderThickness="0">
<l:SubPage1></l:SubPage1>
</h:Frame>
The navigation links in the sub pages are just links to the corresponding sub pages. When the user clicks on these links, the navigation request event is bubbled to the nearest frame, in this case, the sub frame on page 1. If you need to have an inner link navigate an outer frame, you can specify the name of the target frame explicitly using the TargetName property.
<h:NavigationLink Content="Jump To Page 2" NavigateUri="Page2.xaml" TargetName="rootFrame"/>
When the navigation request event bubbles up to the nearest parent frame, Helix will check to see if a target name is specified and if it matches the current frame's name. If it doesn't match, the request continues to bubble up. If it does match, it is marked as handled and the event bubbling is halted.
Under the Covers
The sample and the features described above represent only the core part of the Helix navigation framework. In subsequent articles I'll be going into more detail on how to extend the framework to do things like data linking and deep linking. In the meantime, it's worth taking a quick look at the underlying architecture of Helix.
The Frame class has a reference to a Journal Provider (which implements the IJournal interface). The journal is responsible for maintaining forward and back lists of pages. Each entry in the journal is a JournalEntry object. This object represents the page in the journal and coordinates the keep alive and page title behavior. By default, the frame will create an instance of the SimpleStackJournal provider. This is a simple implementation of a stack based journal.
The journal in turn has a reference to a Navigation Handler. The navigation handler is responsible for processing page Uri's and for creating and initializing the pages that they refer to. Navigation handlers implement the INavigationHandler interface. The default navigation handler (PageNavigationHandler) only handles simple page Uri's that specify the page by name - e.g. "Page1.xaml".
[I should point out that the implementation of this handler is a little hacky because Silverlight doesn't really have a nice way of creating or loading controls by name. Instead you have to load the xaml as a resource and then parse it to get the corresponding class name and then create an instance of that class. Nasty. Hopefully this is a temporary limitation of Silverlight, or I can figure out a more robust way of doing this soon.]
Once the navigation handler has created and initialized the page, it is passed back to the journal provider where a corresponding journal entry object is created and added to the stack. The journal then sets the content on the target frame via the INavigationTarget interface which it implements.
In the following articles, we'll be plugging in different implementations of journal providers, navigation handlers and navigation targets to illustrate the flexibility of this design.
The Helix navigation framework is still a work in progress but I hope you find it useful and/or interesting. Let me know what you think, or if there are any features or scenarios you would like to see tackled.
Comments
Anonymous
October 08, 2008
PingBack from http://mgalinks.wordpress.com/2008/10/09/2008-october-09-links-for-today/Anonymous
October 15, 2008
Hi David, Nice work. I am wondering though - have you found a workaround so that your derived usercontrol classes can be rendered in Expression Blend? I don't know if the issue is limited to the 2.5 June 2008 preview, but with it I have not been able to render any page that does not derive directly from UserControl in Blend. I am able to use an interface instead, and implement its members in every UserControl, but obviously that's not the best solution since that is potentially a lot of code to maintain and if something changes..you get the idea. Cheers, AnyeAnonymous
October 18, 2008
Thanks a lot for publishing this framework! It helps me a lot to convert a sample app from WPF to Silverlight. I'll demonstrate this at DevTeach Montréal in December. I'll publish the details of how the sample is built on my weblog, and I'll make sure to add a reference to this post :-) I'm made some updates to your source code so that we can now navigate directly to some content (object) instead of a URI, as in WPF. Do you wish me to push you the updated source code? It would be nice to have this integrated in your code base.Anonymous
November 02, 2008
Welcome to the second in my short series of articles on navigation in Silverlight! In the first articleAnonymous
December 09, 2008
[Silverlight 2] Navigazione tra pagineAnonymous
April 28, 2009
Welcome to the third post in my series on navigation in Silverlight! The goal of this series of postsAnonymous
June 23, 2009
Silverlight 2.0 Navigation Page