The Imaginative Universal

Studies in Virtual Phenomenology -- @jamesashley

Back-Chaining in WP7

October 09
by James Ashley 9. October 2010 14:03

Customizing App.xaml.cs

The Windows Phone 7 navigation system has certain limitations from a development perspective.  Chief among these are an inability to inspect the navigation backstack, an inability to remove items from the backstack, and an inability to navigate multiple pages on the backstack.

Because we are only able to programmatically back-navigate one page at a time, a technique has been devised in the developer community called back-chaining.  Back-chaining simply involves setting certain flags in your pages so that, if anyone ever backs into the page, it will continue to navigate backwards.  In this way, we can chain several back navigations together until we arrive at the page we want to stop at.

Here is a basic implementation that forces any back-navigation to always return to the first page in an application when this code is placed in every PhoneApplicationPage:

bool done;

protected override void OnNavigatedTo(
        NavigationEventArgs e)
{
    if (done && NavigationService.CanGoBack)
        NavigationService.GoBack();
}

protected override void OnNavigatedFrom(
    System.Windows.Navigation.NavigationEventArgs e)
{
    done = true;
}

Instead of repeating this code in every page, however, we can centralize it in the App class.  Additionally, we can provide extra methods for more complex navigation such as going back a certain number of pages or going back to a particular page wherever it is on the backstack.

We can do all of this from the App class because of an event on the PhoneApplicationFrame called Navigated that allows us to centrally hook into every navigation event in our applications.

In the default Windows Phone Application template, the PhoneApplicationFrame is instantiated in the App.InitializePhoneApplication method in the App.xaml.cs file.  We can declare our new handler for the event somewhere in there:

//boilerplate code
RootFrame.Navigated += CompleteInitializePhoneApplication;
//new code
RootFrame.Navigated += RootFrame_Navigated;

Next, we need to create some infrastructure code.  For convenience, we want to use static methods and properties in order to make our methods accessible off of the App class.  App.Current, which can be called from anywhere in your application, will return an Application type, which is the base class for App.  Consequently, to access the App class’s RootFrame property (which is boilerplate if you use one of the default project templates), you have to write code like this:

var rootFrame = ((App)App.Current).RootFrame;

A static member of the App class, on the other hand, can conveniently be called like this:

App.DoSomething();

Because we are spreading logic between several page navigations and consequently several calls to RootFrame_Navigated, our infrastructure will need to keep track of what we are trying to accomplish between all of these navigations.

The current implementation will include five methods for back navigation, a simple GoBack, GoBack for a number of pages, GoBack to a named page, GoBack to the first page, and GoBack past the first page.  The last is equivalent to exiting the application (it also doesn’t work, by the way, and I’ll talk more about that later).

Here is the implementation for the first back navigation -- which is simply a standard one page back navigation placed in a more convenient place – as well as a property to expose CanGoBack:

public static void GoBack(string pageName)
{
    CurrentMode = GoBackMode.BackToPage;
    BackPageStop = pageName;
    GoBack();
}

private static bool CanGoBack
{
    get
    {
        var navFrame = App.Current.RootVisual
            as PhoneApplicationFrame;
        return navFrame.CanGoBack;
    }
}

GoBack can now be called from anywhere in a Silverlight application like this:

App.GoBack();

Our infrastructure code also requires an enum to keep track of these navigation types:

private enum GoBackMode
{
    Default = 0,
    BackNumberOfPages,
    BackToPage,
    Home,
    Quit
}

private static GoBackMode CurrentMode 
{ get; set; }

To implement going back a given number of pages, we will create a static property to track how many pages we need to go back and expose a method to start the GoBack chain:

private static int GoBackPageCount
{ get; set;}

public static void GoBack(int numberOfPages)
{
    CurrentMode = GoBackMode.BackNumberOfPages;
    GoBackPageCount = numberOfPages;
    GoBack();
}

The magic is what happens in the RootFrame_Navigated method, which is triggered every time we arrive at a new page in the application.  We check to see how many pages we have traversed through the backstack.  If we have gone far enough, we stop.  If not, we call App.GoBack():

static void RootFrame_Navigated(object sender
    , NavigationEventArgs e)
{
    switch (CurrentMode)
    {
        case GoBackMode.BackNumberOfPages:
            if (CanGoBack && --GoBackPageCount > 0)
            {
                GoBack();
            }
            else
                CurrentMode = GoBackMode.Default;
            break;

The setup code for going back to a named page tracks the targeted page name rather than the number of pages traversed:

private static string BackPageStop
{ get; set;}
public static void GoBack(string pageName)
{
    CurrentMode = GoBackMode.BackToPage;
    BackPageStop = pageName;
    GoBack();
}

The code fragment in the switch statement in RootFrame_Navigated parses the name of the page we have arrived at (e.Content returns the full class name along with its namespace but does not return the “.xaml” extension) and then compares it against the page we are trying to reach:

case GoBackMode.BackToPage:
    var pageName = e.Content.ToString();
    var periodPosition = pageName.LastIndexOf(".");
    if (periodPosition > -1)
        pageName = pageName.Substring(periodPosition + 1);
    if (CanGoBack && pageName != BackPageStop)
        GoBack();
    else
        CurrentMode = GoBackMode.Default;
    break;

The syntax for returning to MyPhoneApplication.Page1.xaml looks like this:

    App.GoBack("Page1");

If there are multiple instances of Page1 on the backstack, this implementation will stop at the last one.  If Page1 does not exist on the backstack, the routine will not stop until it finds the first page of the application.

GoHome is fairly straightforward.  It continues stepping backwards until the CanGoBack property returns false.  CanGoBack returns false on the first page of the application but returns true for every other page:

public static void GoHome()
{
    CurrentMode = GoBackMode.Home;
    GoBack();
}
    case GoBackMode.Home:
        if (CanGoBack)
            GoBack();
        else
            CurrentMode = GoBackMode.Default;
        break;

Finally, it would be really nice to be able to implement a Quit method.  Hypothetically, we could just navigate past the first page of an application to exit.  Taking advantage of the infrastructure we have written, the code would look like this:

public static void Quit()
{
    CurrentMode = GoBackMode.Quit;
    GoBack();
}
    case GoBackMode.Quit:
        GoBack();
        break;
Sadly, however, this doesn’t work. We cannot programmatically back navigate past the first page of an application.  When we try, the navigation is automatically cancelled and a NavigationFailed error is thrown. 

Since back-chaining as an exit strategy does not work, an alternative way to exit a Silverlight application is outlined here: How to Quit a WP7 Silverlight Application .  Unfortunately, the marketplace guidelines say that an application cannot have unhandled exceptions.  I’m not currently clear on whether this would apply to throwing an exception that is understood an controlled (hence handled) but at the same time intentional.

A word of caution:  Back-chaining causes flickering, and the more pages you navigate through, the worse the flickering gets.  This is because every page tries to display itself as you navigate past it.   A quick fix for the flickering problem is to set the RootFrame’s Opacity property to zero when you begin and complex back navigation and then set it back to one when you complete the navigation [thanks go to Richard Woo for pointing this out to me.]

For copy/paste convenience, here is the entire back-chaining code base:

RootFrame.Navigated += RootFrame_Navigated;
#region go back methods

private enum GoBackMode
{
    Default = 0,
    BackNumberOfPages,
    BackToPage,
    Home,
    Quit
}

private static GoBackMode CurrentMode 
{ get; set; }

public static void GoBack()
{
    var navFrame = App.Current.RootVisual
        as PhoneApplicationFrame;
    navFrame.GoBack();
}

private static void ShowRootFrame(bool show)
{
    var navFrame = App.Current.RootVisual
        as PhoneApplicationFrame;
    if (show)
        navFrame.Opacity = 1;
    else
        navFrame.Opacity = 0;
}

private static int GoBackPageCount
{ get; set;}

public static void GoBack(int numberOfPages)
{
    CurrentMode = GoBackMode.BackNumberOfPages;
    GoBackPageCount = numberOfPages;
    ShowRootFrame(false);
    GoBack();
}

private static string BackPageStop
{
    get;
    set;
}
public static void GoBack(string pageName)
{
    CurrentMode = GoBackMode.BackToPage;
    BackPageStop = pageName;
    ShowRootFrame(false);
    GoBack();
}

private static bool CanGoBack
{
    get
    {
        var navFrame = App.Current.RootVisual
            as PhoneApplicationFrame;
        return navFrame.CanGoBack;
    }
}

public static void GoHome()
{
    CurrentMode = GoBackMode.Home;
    ShowRootFrame(false);
    GoBack();
}

public static void Quit()
{
    CurrentMode = GoBackMode.Quit;
    ShowRootFrame(false);
    GoBack();
}

static void RootFrame_Navigated(object sender
    , NavigationEventArgs e)
{
switch (CurrentMode)
{
    case GoBackMode.BackNumberOfPages:
        if (--GoBackPageCount > 0 && CanGoBack)
        {
            GoBack();
        }
        else
        {
            ShowRootFrame(true);
            CurrentMode = GoBackMode.Default;
        }
        break;
    
    case GoBackMode.Home:
        if (CanGoBack)
            GoBack();
        else
        {
            ShowRootFrame(true);
            CurrentMode = GoBackMode.Default;
        }
    break;
    case GoBackMode.BackToPage:
        var pageName = e.Content.ToString();
        var periodPosition = pageName.LastIndexOf(".");
        if (periodPosition > -1)
            pageName = pageName.Substring(periodPosition + 1);
        if (CanGoBack && pageName != BackPageStop)
            GoBack();
        else
        {
            ShowRootFrame(true);
            CurrentMode = GoBackMode.Default;
        }
        break;
    case GoBackMode.Quit:
        GoBack();
        break;

    }
}

#endregion

Tags: ,

Windows Phone | Silverlight

Comments

10/19/2010 12:24:28 PM #

trackback

Windows Phone 7 Developer Resources

Windows Phone 7 Developer Resources

Blankenblog

10/20/2010 6:05:56 AM #

tilstandsrapport

thanks for your trick James i used to repeat this code in every page. thanks for posting it.

tilstandsrapport United States

10/20/2010 7:11:03 AM #

trackback

Useful WP7 Links

Useful WP7 Links

.NET Reflections

10/22/2010 5:02:30 AM #

wood picture frames

thanks for the code i think this will help me a lot as i was looking for this in net but am thank full to you that the script has been written in very well manner.

wood picture frames United States

11/24/2010 1:18:37 PM #

Dan

Good write-up.  It's unfortunate that we don't yet have any access to the back stack and that there is no cleaner way to manage page navigations.  Combining this with a framework to manage page transition animations causes so many complications.  Hopefully there can be a smoother interface build right into the framework.

Dan United States

2/16/2011 6:12:17 AM #

Poster print

Your post is very good for blog lovers just like me.and i am fully agree with the points just what you specified here.

Poster print United States

3/23/2011 11:32:05 AM #

Frankie

great post!
I'm trying to implement it and it seems to me that the "opacity trick" doesn't works perfectly (at least on the emulator)... anyway THANK YOU VERY MUCH!

Frankie Italy

3/31/2011 6:52:54 PM #

Vladimir Lisnik

Great post!

A have modified GoBack(string pageName) method:

public static void GoBack<T>() where T : PhoneApplicationPage, new()
{
  CurrentMode = GoBackMode.BackToPage;
  BackPageStop = typeof(T);
  ShowRootFrame(false);
  GoBack();
}

Now it's more refactoring friendly.

You have to modify BackPageStop property to:
private static Type BackPageStop { get; set; }

AND

Processing code in the RootFrameNavigated method to:
case GoBackMode.BackToPage:
  var pageType = e.Content.GetType();
  if (CanGoBack && pageType != BackPageStop)
    GoBack();
  else
  {
    ShowRootFrame(true);
    CurrentMode = GoBackMode.Default;
  }
  break;

Vladimir Lisnik Ukraine

4/13/2011 12:13:21 AM #

RickH

I have a 5 page app. Page 1 is always on the stack, so it is an easy case.
If I'm on page 2 and want to go to page 3, 4, 5, they may or may not be on the stack.
Is there a way to just simply launch a new instance of the page if it ends up not being on the stack?

RickH United States

5/4/2011 11:08:59 AM #

Dan

...and it is as simple and straightforward as that!

Dan United States

6/30/2011 5:45:44 AM #

beats studio kobe bryant

WP7 looks much better than its old versions

beats studio kobe bryant People's Republic of China

6/30/2011 5:46:38 AM #

monster beats solo

WP7 is very good~~

monster beats solo People's Republic of China

7/5/2011 4:40:52 AM #

filter paper

Nowadays, there is a huge evolution in [url=www.gasgoo.com/auto-products/car_accessories_303/]car accessories[/url] industry. [url=http://www.gasgoo.com/hot/p-hand-brake.html]Hand brake[/url] is becoming safer. [url=www.gasgoo.com/auto-products/air-conditioner-561/
]Air conditioner[/url] system has greatly changed. For instance, air conditioner system used to have only air conditioner and [url=www.gasgoo.com/auto-products/air-filter-429/]air filter[/url], but now, the system also includes [url=www.gasgoo.com/auto-products/air-purifier-1003/]air purifier[/url]. Apart from, performance of [url=http://www.gasgoo.com/auto-products/fuel-pump-395/]fuel pump[/url] has enhanced as well as [url=www.gasgoo.com/auto-products/control-panel-355/]control panel[/url]. All in all, cars today are faster and easier to drive.

filter paper People's Republic of China

7/13/2011 12:21:47 PM #

tee

great post...
I used this though to work around a LongListSelector bug!

thanks

tee United Kingdom

Comments are closed