Tombstoning Pivot Controls in Silverlight for Windows Phone

If you’re a Windows phone developer, you’ve figured out by now that tombstoning is an essential part of the application lifecycle, and that every application must be architected with tombstoning in mind.

What is tombstoning? In short, while Windows phones are perfectly capable of running several applications at once, only OEMs can write apps that run in the background. Apps written by ordinary mortals can only run one at a time. If the user switches away from your app by, say, pressing the phone’s Start button, your app is terminated. To be more precise, the app is deactivated, and if the user next presses the Back button, the app is reactivated. But because deactivation really does mean the app was terminated, all state – including the state of the app’s controls – is lost. We refer to the deactivation-reactivation cycle as tombstoning.

Silverlight for Windows Phone provides three different mechanisms that you can use to preserve state between the Deactivated and Activated events signaling the death and rebirth of your app. John Garland has recently written about all three in a series of blog entries. To provide a decent user experience, you must include tombstoning support in phone apps. Charles Petzold recently mused that tombstoning impacts phone development as profoundly as WM_PAINT impacts Windows development. I don’t disagree.

As a simple example of incorporating tombstoning support into a phone app, suppose your app’s UI contains a ListBox control. If the user runs the app, selects an item in the ListBox, and then clicks the phone’s Start button, the app gets tombstoned. Now let’s say the user clicks the Back button and returns to your app. Because the app was terminated when the user switched away from it, the ListBox item that the user selected is no longer selected – unless you provide tombstoning logic to reselect it. Per Microsoft’s recommendations, apps typically use the page’s OnNavigatedFrom and OnNavigatedTo overrides to persist state such as selected-item indexes, and they typically use page state as the repository for such state. Here’s tombstoning code to preserve the ListBox selection:

protectedoverridevoid OnNavigatedFrom(NavigationEventArgs e)

{

// Save the ListBox control’s SelectedIndex in page state

State["Index"] = LB.SelectedIndex;

}

protectedoverridevoid OnNavigatedTo(NavigationEventArgs e)

{

// Restore the ListBox control’s SelectedIndex

if (State.ContainsKey("Index"))

LB.SelectedIndex = (int)State["Index"];

}

LB in this example is the x:Name assigned to the ListBox. So far, so good…nothing hard here.

In theory, the strategy of using the page’s OnNavigatedFrom and OnNavigatedTo methods to tombstone state works with any page and any control. However, there is a notable exception: pages with Pivot controls. Due to a bug in the Pivot control that ships with Silverlight for Windows Phone, if the control contains more than three items, its SelectedIndex property can’t be set to certain perfectly legitimate values until after the Pivot control has fired a Loaded event. The problem is that the control’s Loaded event fires after the page’s OnNavigatedTo method is called. As a result, the following code, while perfectly reasonable and crafted in accordance with recommended guidelines and best practices, will throw exceptions from the run-time after OnNavigatedTo returns if, for example, the Pivot control contains five items and you attempt to set SelectedIndex to 2 or 3:

protectedoverridevoid OnNavigatedFrom(NavigationEventArgs e)

{

// Save the Pivot control’s SelectedIndex in page state

State["Index"] = PivotControl.SelectedIndex;

}

protectedoverridevoid OnNavigatedTo(NavigationEventArgs e)

{

// Restore the Pivot control’s SelectedIndex

if (State.ContainsKey("Index"))

PivotControl.SelectedIndex = (int)State["Index"];

}

The good news is that a simple fix exists. First, wire up an event handler for the Pivot control’s Loaded event:

Then untombstone the control in OnPivotControlLoaded rather than in OnNavigatedTo:

protectedoverridevoid OnNavigatedFrom(NavigationEventArgs e)

{

// Save the Pivot control’s SelectedIndex in page state

State["Index"] = PivotControl.SelectedIndex;

}

privatevoid OnPivotControlLoaded(object sender, RoutedEventArgs e)

{

// Restore the Pivot control’s SelectedIndex

if (State.ContainsKey("Index"))

PivotControl.SelectedIndex = (int)State["Index"];

}

Now your code will work just fine and you won’t suffer inexplicable exceptions.

Waiting until the Pivot control’s Loaded event fires avoids another bug, too. When a Pivot control is first created, SelectedIndex is correctly set to 0, but SelectedItem is null until after the Loaded event fires. Therefore, you can’t do this in OnNavigatedTo to figure out which PivotItem is currently selected:

Microsoft is aware of these problems and will undoubtedly have them fixed in the next update to the operating system. Until then, be aware of the Pivot control’s nuances and structure your code to work around them.