How to preserve and restore page state for Windows Phone 8

[ This article is for Windows Phone 8 developers. If you’re developing for Windows 10, see the latest documentation. ]

 

On Windows Phone, apps are typically put into a dormant state when the user navigates forward, away from the app. In the dormant state, execution of the app’s code is stopped, but the app remains intact in memory. If an app is dormant and the user navigates back to the app, the app resumes and the state of each page in the app is automatically restored to the state before the user navigated away.

It is possible, however, for an app to be tombstoned after the user navigates away. In this case, the app is not preserved in memory, but some information about the app is stored – most importantly for this topic, the State dictionary of the PhoneApplicationPage object is stored. If the user navigates back to an app that has been tombstoned, the app should use the data stored in the State dictionary to restore page state so that it appears to be in the same state it was when the user navigated away. This primarily consists of setting the values of any controls present on the page to their last value before the app was tombstoned.

This topic shows you how to use data binding to easily preserve and restore the state of your page’s user interface. For more information about tombstoning and how apps should manage state throughout their lifecycle, see App activation and deactivation for Windows Phone 8.

This topic contains the following sections.

 

Creating an app that persists page state

The following steps walk you through creating an app that saves and restores the state of multiple controls when the app is deactivated and reactivated.

To create an app that persists the state of its UI

  1. In Visual Studio, create a new **Windows Phone App ** project. This template is in the Windows Phone category.

  2. Add some basic controls to MainPage.xaml. In this example, TextBox, Checkbox, Slider, and RadioButton controls are used. Some of the more complex controls are discussed in a later section.

    In the XAML code below, each control has a property that uses the Binding keyword. These are the properties of the controls that will be preserved and restored during the lifecycle of the app. For the TextBox control, the Text property is bound. For the Slider control, the Value property is bound. For the RadioButton and CheckBox controls, the IsChecked property is bound. The term after the Binding keyword, “Slider1Value” for example, is the name of the field to which the control is bound. These fields will be defined in the next step of this walkthrough. The final part of the binding expression, “Mode=TwoWay”, indicates that the binding should be bidirectional. In other words, if the field to which the control is bound changes, the control will be updated. If the value of the control changes, the bound field will be updated.

    In the MainPage.xaml file, place the following XAML code in the Grid element named “ContentPanel”.

    <TextBox Text="{Binding TextBox1Text, Mode=TwoWay}" Height="72"
     HorizontalAlignment="Left" Margin="20,20,0,0" Name="textBox1"
     VerticalAlignment="Top" Width="440" />
    <CheckBox IsChecked="{Binding CheckBox1IsChecked, Mode=TwoWay}"
     Content="CheckBox" Height="71" Name="checkBox1" Margin="20,100,0,0"
      VerticalAlignment="Top"/>
    <Slider Value="{Binding Slider1Value, Mode=TwoWay}" Height="84" Name="slider1"
     Width="440" Margin="20,180,0,0"  VerticalAlignment="Top"/>
    <RadioButton IsChecked="{Binding RadioButton1IsChecked, Mode=TwoWay}"
     Content="RadioButton 1" Height="71" Name="radioButton1"
     GroupName="RadioButtonGroup"  Margin="20,260,0,0" VerticalAlignment="Top"/>
    <RadioButton IsChecked="{Binding RadioButton1IsChecked, Mode=TwoWay}"
     Content="RadioButton 2" Height="71" Name="radioButton2" 
    GroupName="RadioButtonGroup"  Margin="20,340,0,0" VerticalAlignment="Top"/>
    
  3. Next, add a class that will contain the fields to which the controls defined in XAML are bound. From the Project menu in Visual Studio, select Add Class…. Select Class in the list of items to add and type ViewModel.cs in the Name field. Click Add to add the class to your project.

  4. At the top of the file, add using directives for the Serialization and ComponentModel namespaces which provide support for serialization and data binding, respectively.

    using System.Runtime.Serialization;
    using System.ComponentModel;
    
  5. In the ViewModel.cs file, add the DataContract attribute before the class definition. This attribute signifies that this class will be used in serialization and deserialization operations by the DataContractSerializer. All data that is saved in the State property of a PhoneApplicationPage, as this class will be, must be data contract serializable. For more information about this type of serialization, see Using Data Contracts.

    You should also declare that the class implements the INotifyPropertyChanged interface. This interface is what allows the user interface controls to automatically change their values when the bound data changes.

    [DataContract]
    public class ViewModel : INotifyPropertyChanged
    {
    }
    
  6. Add private member variables to the class for each control property that is bound.

    private string _textBox1Text;
    private bool _checkBox1IsChecked;
    private bool _radioButton1IsChecked;
    private bool _radioButton2IsChecked;
    private double _slider1Value;
    
  7. Next, add the INotifyPropertyChanged functionality to the class. This includes adding a PropertyChanged event and implementing a method called NotifyPropertyChanged, which raises the PropertyChanged event.

    public event PropertyChangedEventHandler PropertyChanged;
    
    private void NotifyPropertyChanged(string propertyName)
    {
      if (null != PropertyChanged)
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    
  8. Next, add a public property for each bound control property. Note that these property names are the names used in the Binding expressions in the XAML file. For each property, the get accessor simply returns the value of the corresponding private member variable. The put accessor sets the value of the member variable and calls NotifyPropertyChanged, effectively notifying the corresponding control that its value should be updated. Each property is also labeled with the DataMember attribute to let the serializer know that the property should be serialized.

    Paste the following code inside the ViewModel class definition.

    [DataMember]
    public string TextBox1Text
    {
      get { return _textBox1Text; }
      set
      {
        _textBox1Text = value;
        NotifyPropertyChanged("TextBox1Text");
      }
    }
    
    [DataMember]
    public bool CheckBox1IsChecked
    {
      get { return _checkBox1IsChecked; }
      set
      {
        _checkBox1IsChecked = value;
        NotifyPropertyChanged("CheckBox1IsChecked");
      }
    }
    
    [DataMember]
    public double Slider1Value
    {
      get { return _slider1Value; }
      set
      {
        _slider1Value = value;
        NotifyPropertyChanged("Slider1Value");
      }
    }
    
    [DataMember]
    public bool RadioButton1IsChecked
    {
      get { return _radioButton1IsChecked; }
      set
      {
        _radioButton1IsChecked = value;
        NotifyPropertyChanged("RadioButton1IsChecked");
      }
    }
    
    [DataMember]
    public bool RadioButton2IsChecked
    {
      get { return _radioButton2IsChecked; }
      set
      {
        _radioButton2IsChecked = value;
        NotifyPropertyChanged("RadioButton2IsChecked");
      }
    }
    
  9. The final step in preserving the UI state is to handle changes in page state in the code-behind page. In MainPage.xaml.cs, add the following member variables to the MainPage class definition. The first variable is of type ViewModel. This is the class that was created in the previous steps, which stores the values of the UI controls. The second variable is a bool called _isNewPageInstance. This variable will be used later to determine if the page is a new instance, such as when the app is initially launched or relaunched after being tombstoned, or if the page is an existing instance, such as when the user navigates backward to the page from within the app.

    public partial class MainPage : PhoneApplicationPage
    {
      ViewModel _viewModel;
      bool _isNewPageInstance = false;
    
  10. In the page constructor, set _isNewPageInstance to true.

    // Constructor
    public MainPage()
    {
      InitializeComponent();
    
      _isNewPageInstance = true;      
    }
    
  11. Next, override the OnNavigatedFrom(NavigationEventArgs) method inherited from the base Page class. This method is called whenever the user navigates away from the page. In this method, the ViewModel object containing the values of the UI controls is saved to the page’s State dictionary. The State dictionary is preserved when the app is tombstoned, so that it can be used to restore state when the app is reactivated. If the navigation is backward, the page will be disposed of when the navigation is complete, making it unnecessary to save the state. Copy the following method definition into the MainPage class definition.

    protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
    {
      // If this is a back navigation, the page will be discarded, so there
      // is no need to save state.
      if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
      {
        // Save the ViewModel variable in the page's State dictionary.
        State["ViewModel"] = _viewModel;
      }
    }
    
  12. Finally, override the OnNavigatedTo(NavigationEventArgs) method. This method is called whenever the user navigates to the page. This includes when the app is first launched and the page is first displayed, when the app was tombstoned and the user returns to the app, and when the user navigates among the pages within a single instance.

    First, check to see if the _isNewPageInstance is true. If it is not, then you know that the page instance already exists in memory. This means that the UI state is intact and there is no need to restore the control values. If it is a new page instance, check to see if the ViewModel object containing the UI values is stored in the page’s State dictionary. If so, assign the saved object to the _viewModel member variable; otherwise, there is no saved state and you should create a new ViewModel instance. Set the DataContext for the page to the ViewModel object. This updates all of the UI controls to display the values saved in the object. Finally, this method sets the _isNewPageInstance variable to false so that if the user navigates back to this page while it is still in memory, the UI will not be restored in OnNavigatedTo.

    protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
      // If _isNewPageInstance is true, the page constuctor has been called, so
      // state may need to be restored.
      if (_isNewPageInstance)
      {
        if (_viewModel == null)
        {
          if (State.Count > 0)
          {
            _viewModel = (ViewModel)State["ViewModel"];
          }
          else
          {
            _viewModel = new ViewModel();
          }
        }
        DataContext = _viewModel;
      }
      // Set _isNewPageInstance to false. If the user navigates back to this page
      // and it has remained in memory, this value will continue to be false.
      _isNewPageInstance = false;
    }
    

Debugging page state

In Windows Phone OS 7.1, apps are made dormant when the user navigates away, as long as sufficient memory is available for the foreground app to run smoothly. When an app is dormant and then restored, the UI state is automatically preserved. To verify that your page state is restored properly after tombstoning, you need to enable automatic tombstoning in the debugger.

Open the Project Properties dialog by selecting [Application Name] Properties… from the Project menu or by right-clicking the project in Solution Explorer and selecting Properties. In the Debug tab, select the check box labeled Tombstone upon deactivation while debugging. With this check box selected, you can start debugging your app, click the Start button to cause your app to be tombstoned immediately, and then press the Back button to return to your app and verify that your page state was saved and restored correctly.

Note

For XNA Framework apps, the Tombstone upon deactivation while debugging check box is on the XNA Game Studio tab in the Project Properties dialog.