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.
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)
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.Navigate(new Uri("/odd.xaml?method=cancel%20navigation&time=" + time.ToLongTimeString(), UriKind.Relative));
RootFrame.Navigate(new Uri("/even.xaml?method=cancel%20navigation&time=" + time.ToLongTimeString(), UriKind.Relative));
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:UriMapping Uri="/MainPage.xaml" />
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
mapper.UriMappings.MappedUri = new Uri("/odd.xaml?method=UriMapper&time=" + time.ToLongTimeString(), UriKind.Relative);
mapper.UriMappings.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.