Redirecting an initial navigation
One more post on the subject of keeping screens out of the backstack. A common scenario we see is a variation of the login screen scenario I mentioned the other week. In this case, the first page the user is supposed to see depends on some state saved in by the application (maybe a user preference for which screen to see first, or maybe based on trial mode, etc.) and whilst you want that first page to be in the back stack (ie, it is a legitimate place) you don’t want any additional “landing pages” taking up a slot in the backstack. Another situation where this arises is if your application is extending the Music + Videos hub or is registered as a Photo Extension and you need to show a different page when launched from these experiences.
There are (at least) two ways of doing this; I’ll present two of them here along with a sample app that shows them in action. For my simple example, the trigger for showing a particular page is going to be whether the current time has an even or odd number of seconds (this is a silly example, but makes it easy to test both startup cases). In a real application the trigger might be based on incoming parameters, settings stored in IsoStore, etc.
Cancelling Navigation
The first way to change your initial page is to cancel the incoming navigation to MainPage.xaml and then fire off the desired navigation instead. This is easily done in a few lines of code; firstly, at the end of the App constructor we add an event handler to the Navigating event:
RootFrame.Navigating += new NavigatingCancelEventHandler(RootFrame_Navigating);
In the event handler, we do several things. First, we check if the navigation is to MainPage.xaml (the NavigationPage specified in the WMAppManifest.xml file) and if not, we bail:
// Only care about MainPage
if (e.Uri.ToString().Contains("/MainPage.xaml") != true)
return;
Next we perform a simple check to see which page to load (in this case, are the seconds odd or even?), but this is where you’d do your own logic for switching pages:
// Our dummy check -- does the current time have an odd or even number of seconds?
DateTime time = DateTime.Now;
int seconds = time.Second;
bool isOdd = (seconds % 2) == 1;
Finally we kick off a navigation to the desired page, being careful to avoid overlapping navigations (which will fail):
// Cancel current navigation and schedule the real navigation for the next tick
// (we can't navigate immediately as that will fail; no overlapping navigations
// are allowed)
e.Cancel = true;
RootFrame.Dispatcher.BeginInvoke(delegate
{
if (isOdd)
RootFrame.Navigate(new Uri("/odd.xaml?method=cancel%20navigation&time=" + time.ToLongTimeString(), UriKind.Relative));
else
RootFrame.Navigate(new Uri("/even.xaml?method=cancel%20navigation&time=" + time.ToLongTimeString(), UriKind.Relative));
});
Using UriMapper
The second method you can use is based on the UriMapper class, which is a standard part of the Silverlight 3 navigation framework. UriMapper was slightly easier to use in the Mix-era CTP when we set the RootVisual of the application to a frame inside of App.xaml, but it’s still easy to do with the new delayed approach. First of all we add a new UriMapper to the Resources section of App.xaml:
<!--Simple UriMapper that will be programmatically updated to point to the right page at runtime—>
<UriMapper:UriMapper x:Name="mapper">
<UriMapper:UriMapping Uri="/MainPage.xaml" />
</UriMapper:UriMapper>
Then instead of handling the Navigating event (as before), we simply attach the UriMapper to the RootFrame …
// Get the UriMapper from the app.xaml resources, and assign it to the root frame
UriMapper mapper = Resources["mapper"] as UriMapper;
RootFrame.UriMapper = mapper;
and re-write the rule for MainPage based on the desired page:
// Update the mapper as appropriate
if (isOdd)
mapper.UriMappings[0].MappedUri = new Uri("/odd.xaml?method=UriMapper&time=" + time.ToLongTimeString(), UriKind.Relative);
else
mapper.UriMappings[0].MappedUri = new Uri("/even.xaml?method=UriMapper&time=" + time.ToLongTimeString(), UriKind.Relative);
As you can see, the code is mostly the same, the difference is whether it is an explicit cancel-then-navigate or whether it is a simple URL-rewrite that happens. If you don’t know which one to use, I would recommend the UriMapper solution first (because you can easily extend it for other, non-dynamically-re-mapped scenarios) but it does require that you know what the final URI should be in a synchronous fashion. If for whatever reason you need to perform an asynchronous operation to determine which page to load – or if you just prefer a 100% code solution – you can use the “cancel navigation” approach. Just be sure to navigate within 10 seconds (or include a splash screen) or the system will terminate you for being unresponsive.
The attached sample lets you pick which approach you want to try by modifying the static USE_CANCEL_NAVIGATION flag set at the top of App.xaml.cs.
RedirectingInitialNavigation.zip
Comments
Anonymous
August 28, 2010
You mention that apps should behave differently when launched from the Music + Video or the Picture hub. However, I didn't find any way to determine this. Can you point me to the right direction?Anonymous
August 28, 2010
Thanks Peter, very helpful, I just added this to my app as I was having this issue.Anonymous
September 01, 2010
Will the application fail the Microsoft Certification test if you remove the startup page from the WMAppManifest.xml? when removed from the file the application will not load any page by default and you are free to handle the navigation in the App.xaml.cs file without canceling the navigation event. We are storing in isolated storage the last page the user was on before the application was closed/deactivated, then on next load of the application we obtain the page location from isolated storage then navigate to the page on startup.Anonymous
September 01, 2010
Christopher, I don't know if that will fail validation or not, but I wouldn't recommend it. It may cause issues in the future and you might forget that you removed the property and then wonder why your code doesn't work. In addtion, what you are doing is against the UI guidelines - a launch of the application from the Start menu should always be a new instance of the application; you should not try and re-create the last view the user was on. That might be how some other phones work, but it's not the guideline for Windows Phone (since we have the "back" key to return to previous application state). On your first screen you are of course free to add links for the user to continue what they were doing, but it should be obvious to them that they have a "clean slate" from which to start.Anonymous
September 04, 2010
Main List with direct access to any detail page on the list Detail page with back, up (one detail page up), down (one detail page down) Problem: using the detail up or down nav results in lots of not-wanted details pages on the back page stack. In other words, to get back to the main list one has to back, back, back,... for each of the up/down navs done. Question: How do I get BACK to skip all those details pages and return directly to the main list. Further, I want the next back, when at the main list, to exit, not to return to some details page on the stack.Anonymous
September 04, 2010
In Serial Page Deep - please see my previous post, blogs.msdn.com/.../introducing-the-concept-of-places.aspx, which discusses how to solve exactly this design problem.Anonymous
September 04, 2010
so instead of //NavigationService.Navigate(new Uri("/DetailsPage.xaml?selectedItem=" + index, UriKind.Relative)); do DataContext = App.ViewModel.Items[index]; on up.down. Seems to work so long as I maintain a currIndex. I'd have thought of that...last.Anonymous
September 07, 2010
Hi. I have Just downloaded the project.but it not work for me .It Throw error system.object not defined or imported.Can u tel me what is the problem.Anonymous
September 09, 2010
Niks, there were some versions of the tools that did not correctly add mscorlib.dll to the project. Easist thing to do is probably to create a new project and just copy in the XAML and CS files.Anonymous
September 10, 2010
OK, short-circuit. Instead of trying doing this (mess) in the About page, I do it in the Details page: save currItem in NavigateFrom and load it back in NavigateTo. Lucky roll, there.Anonymous
September 26, 2010
Great article! Question though -- in the first option, how would I navigate from odd.xaml or even.xaml to something else? In my equivalent odd.xaml, when I called NavigationService.Navigate(), I get a NavigationFailed exception: "No Fragment support right now" Any ideas?Anonymous
September 26, 2010
Noah, the key is to do a Dispatcher.BeginInvoke so that your code is executed on the next tick, after the current navigation has completed.Anonymous
October 15, 2010
I wanted to use this solution but couldn't because I don't have access to NavigationContext from RootFrame, which I need to see if a querystring was passed in the initial call, which is done for photo extensibilityAnonymous
October 15, 2010
I wanted to use this solution but couldn't because I don't have access to NavigationContext from RootFrame, which I need to see if a querystring was passed in the initial call, which is done for photo extensibilityAnonymous
October 15, 2010
irhetoric - the Uri contains the full querystring; NavigationContext is just a nice wrapper around it.Anonymous
October 24, 2010
The comment has been removed