I've been working on a Silverlight 2 application for the past week and a half and having started my Silverlight experience with SL3, I took for granted the nice navigation features. With SL2 I was forced to create my own navigation framework, which turned out to be a pain in the butt. I read some solutions on the web but they either weren't nice and clean or they assumed that you wanted to navigate from page to page without having a "master" page. It was at this point that I decided to take a crack at it myself...
The first thing I did was create my Page.xaml, which is my master page. In here I created a StackPanel to hold the usercontrol of the current "page" that the user is on. Alternately instead of dynamically adding usercontrol objects to the StackPanel, you could create a UserControl and set its Content property to the user control of the current page.
<StackPanel x:Name="spContent" Orientation="Vertical" Margin="10,10,10,10" HorizontalAlignment="Left" />
The next thing I had to do was create a Navigate method in the code-behind of Page.xaml:
public void Navigate(UIElement page)
{
//clear old page.
spContent.Children.Clear();
//add new page.
spContent.Children.Add(page);
}
Next I created a class called Pages.cs which holds the name and object type of each of our pages. Notice the difference between the HOME_PAGE property and the REGISTER and LOGIN properties. I realized early on that if I declared all of the page properties in the fashion of HOME_PAGE, it would create only one instance and then use that one from then on. The web is stateless but switching between pages in Silverlight is not if you use this method, so if you go to the Register page and fill out the form then go to another page and then back again to Register, you expect the form to be blank again but it won't be. The REGISTER and LOGIN properties will create new instances each time the page is requested to avoid this issue.
public class Pages
{
//public pages.
public static UserControl HOME_PAGE = new Home();
public static UserControl REGISTER
{
get { return new Register(); }
}
public static UserControl LOGIN
{
get { return new Login(); }
}
}
Next I created another class called Navigation.cs, which gives us a nice clean 1-liner way to navigate to a new page. The first thing you'll notice is BasePage, which will be set to the instance of our master page. This gives us a way to call the Navigate method in the master page from here so that in our pages we'll be able to call Navigation.Navigate(myPage); The alternative is referencing the App object, then referencing the master page (App.RootVisual), then calling Navigate from there (ugly!)
We have an overloaded Navigate method that lets us pass in a State object. This is just a way to pass a parameter to the next page you go to. A common use would be if you are on a business object list page and you're going to an edit business object page, you could pass the business object in here and then use it to populate the edit form in the edit page without having to hit the database again (very cool!)
The last thing you'll notice is that we're saving the name of the current page we're on in Isolated Storage, which you can think of as Silverlight's version of a cookie. It gets saved on the end-user's computer and gives us a way to persist the value if they exit our application. An important thing to remember here is that even hitting the browser's refresh will exit our application, which by default would bring them back to our default home page. By saving the page name, we can then bring the user back to the page they were on before they hit refresh.
using System.IO.IsolatedStorage;
public static class Navigation
{
//properties.
public static Page BasePage { get; set; }
public static object State { get; set; }
public static void Navigate(UserControl page)
{
//erase state.
State = null;
//navigate.
NavigateTo(page);
}
public static void Navigate(UserControl page, object state)
{
//save state to pass between pages.
State = state;
//navigate.
NavigateTo(page);
}
private static void NavigateTo(UserControl page)
{
//save current page.
SavePersistentValue("Current Page", page.ToString());
//navigate.
BasePage.Navigate(page);
}
#region Isolated Storage
public static object GetPersistentValue(string key)
{
IsolatedStorageSettings appSettings = IsolatedStorageSettings.ApplicationSettings;
if (appSettings.Contains(key))
{
return appSettings[key] as object;
}
else
{
return null;
}
}
public static void SavePersistentValue(string key, object value)
{
IsolatedStorageSettings appSettings = IsolatedStorageSettings.ApplicationSettings;
if (appSettings.Contains(key))
{
appSettings[key] = value;
}
else
{
appSettings.Add(key, value);
}
}
#endregion
The last step is to handle the Application_Startup event in App.xaml.cs to set our Navigation.BasePage to our master page and then load up the current page we were on before the user exited the application.
private void Application_Startup(object sender, StartupEventArgs e)
{
//navigate to home page.
this.RootVisual = new Page();
Navigation.BasePage = this.RootVisual as Page;
//check which page we were on.
string pageName = Convert.ToString(Navigation.GetPersistentValue("Current Page"));
if (pageName.Length > 0)
{//navigate to current page.
Type pageType = Type.GetType(pageName);
UserControl currentPage = Activator.CreateInstance(pageType) as UserControl;
Navigation.Navigate(currentPage);
}
}
And that's all folks... If you wanted to switch to the login page you would just do Navigation.Navigate(Pages.LOGIN), nice and clean...