1. Problem
You have to manage tombstoning
and store application data permanently so that when the application is
either activated or executed, the application can reload data.
2. Solution
You have to use IsolatedStorage classes together with Application class event handlers: Application_Launching, Application_Activated, Application_Deactivated, and Application_Closing.
3. How It Works
The Windows Phone 7
operating system doesn't provide support for executing concurrent
applications concurrently in the foreground. The application that you
run on your phone is the only one executed in foreground, and no
third-party application can run in the background. When you press the
hardware Start button and run another application, the previous
application is actually killed. This behavior is called tombstoning.
Let's take a look at an
example. Say you press the hardware Start button and launch the Calendar
application. You start to note an appointment with Company X, but you
don't remember the name of the street where Company X is located. So you
press the hardware Search button (in your application, the Deactivated
event is raised), type the name of the company, and press the Enter
key. The company site appears, and you click on it so Internet Explorer
runs and the site is shown. You click the Contact Us link and read the
company's address. Now you have two options:
In the former case, you run a
fresh new copy of the Calendar application, and everything you wrote
before searching for the company's address is gone. In this case, the Launching event is raised.
In the latter case, you run a
fresh new copy of the Calendar application, but the Windows Phone 7
operating system provides a collection of serializable objects in the State
dictionary . So you will see your data again
in the text boxes because Calendar developers have managed the
tombstoning. In this case, the Activated event is raised.
There is another way to manage your application. You could store your data permanently. Indeed, the State
dictionary is provided only when you activate the application with the
hardware Back button and not when you launch it from the Start menu. You
can use the four Application event handlers together with isolated storage in order to save data in the memory space reserved for the application.
NOTE
There is no limited
quota size for the storage of a Windows Phone 7 application. The
application itself has to take care of the disk space avoiding useless
information. When the application is removed from the phone, the
isolated storage is removed as well.
4. The Code
To demonstrate how to manage the four Application
event handlers in conjunction with the isolated storage. The 7Drum
application provides useful tools to drummers who, for instance, want
to plan their practices, record a groove they have in mind, or use a
metronome during their drumming.
In this particular case, we will focus our attention on the Training menu of the 7Drum
application, where the user can create a training plan specifying an
exercise name, a description, and a duration. The application will
provide three TextBox controls to contain that information. The code is contained in the Exercise.xaml page. It defines a new Grid with two columns and three rows within the ContentPanel grid. Finally, it adds three TextBox controls and three TextBlock controls as labels. It is worth noting the Description text box, which accepts more than one line of text thanks to the AcceptReturns property being set to true:
. . .
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Name:"
Style="{StaticResource PhoneTextNormalStyle}"
VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" x:Name="txtName" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Description:"
Style="{StaticResource PhoneTextNormalStyle}"
VerticalAlignment="Center" />
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtDescription"
AcceptsReturn="True" Height="300"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Duration:"
Style="{StaticResource PhoneTextNormalStyle}"
VerticalAlignment="Center" />
<TextBox Grid.Row="2" Grid.Column="1" x:Name="txtDuration"
InputScope="TelephoneNumber" KeyDown="txtDuration_KeyDown"/>
</Grid>
</StackPanel>
</Grid>
. . .
In the Exercise.xaml.cs page, we have defined the code to save exercise planning. The Exercise page uses the ApplicationIconBar button (see more on this in Recipe 3-2), which calls the ApplicationBarIconSaveButton_Click
event handler when clicked. The code first checks whether the page has
to be shown in edit mode or whether the user is going to create a new
exercise. This is accomplished by the QueryString property check. If the QueryString
property contains an exercise identifier selected in the previous
page—the Training page where all exercises are listed—then the user is
going to modify the exercise. After that, if the Name and Duration text
boxes contain values, a new ExerciseSettings
object is created. It will contain values from the related page text
boxes. The exercise identifier will be either created by calling the NewGuid static method from the Guid class or set equal to the one that has to be modified. As you will see shortly, the ExerciseSettings class contains everything necessary to store the exercise's information.
Finally, when the Save button
is in modify mode, it retrieves the old exercise from the Exercises
list, which is stored as a global variable in the Application
class by using a LINQ query. This Exercise object is a reference to the
stored exercise, so we can change its properties without changing the Guid identifier to modify the exercise.
private void ApplicationBarIconSaveButton_Click(object sender, EventArgs e)
{
bool bNew = true;
if (this.NavigationContext.QueryString.ContainsKey("Id"))
bNew = false;
else
bNew = true;
if (txtName.Text != string.Empty && txtDuration.Text != string.Empty)
{
ExerciseSettings exercise = null;
if (!bNew)
{
exercise = (from ex in (Application.Current as App).Exercises
where ex.id == new
Guid(this.NavigationContext.QueryString["Id"])
select ex).SingleOrDefault<ExerciseSettings>();
}
else
{
exercise = new ExerciseSettings();
exercise.id = Guid.NewGuid();
}
exercise.Description = txtDescription.Text;
exercise.Name = txtName.Text;
exercise.Duration = int.Parse(txtDuration.Text);
if (bNew)
(Application.Current as App).Exercises.Add(exercise);
Clean();
}
else
MessageBox.Show("Name and duration fields are mandatory.", "7Drum",
MessageBoxButton.OK);
}
The ExerciseSettings class is used to store exercise data, and a collection of these objects is stored in the Application page.
public class ExerciseSettings
{
public Guid id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Duration { get; set; }
}
public partial class App : Application
{
/// <summary>
/// Provides easy access to the root frame of the Phone Application.
/// </summary>
/// <returns>The root frame of the Phone Application.</returns>
. . .
public List<ExerciseSettings> Exercises { get; private set; }
/// <summary>
. . .
Let's discuss of the main part of the code, which handles tombstoning management. We created the ExerciseManager class, which contains the Load and Save
methods. These methods, as their names indicate, are used to load
exercises stored in isolated storage and to save them, respectively.
The Load static method returns a List<> collection of ExerciseSettings objects when exercises.xml is present in the application isoloated storage. Otherwise, the method returns an empty list.
public static List<ExerciseSettings> Load()
{
List<ExerciseSettings> exercises = new List<ExerciseSettings>();
IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
if (storage.FileExists("exercises.xml"))
{
IsolatedStorageFileStream stream = storage.OpenFile("exercises.xml",
FileMode.Open);
XmlSerializer xml = new XmlSerializer(typeof(List<ExerciseSettings>));
exercises = xml.Deserialize(stream) as List<ExerciseSettings>;
stream.Close();
stream.Dispose();
}
return exercises;
}
The Save static method accepts a List<ExerciseSettings> objects collection that is stored in the exercises.xml file.
public static void Save(List<ExerciseSettings> exercises)
{
IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForApplication();
IsolatedStorageFileStream stream = iso.CreateFile("exercises.xml");
StreamWriter writer = new StreamWriter(stream);
XmlSerializer ser = new XmlSerializer(typeof(List<ExerciseSettings>));
ser.Serialize(writer, exercises);
writer.Close();
writer.Dispose();
}
The final step in managing tombstoning in our application is to call those two methods in the four Application event handlers. When the application is launched from the Start menu, the application calls the Load method from the ExerciseManager
class in order to load stored exercises. The same occurs when the
application is reactivated from tombstoning. On the other hand, when the
application is closed by pressing the hardware Back button from the
application's main page, the application calls the Save static method from the ExerciseManager
class. The same occurs when the application is deactivated because of
tombstoning (that is, the hardware Start button is pressed).
public partial class App : Application
{
/// <summary>
/// Provides easy access to the root frame of the Phone Application.
/// </summary>
/// <returns>The root frame of the Phone Application.</returns>
. . .
// Code to execute when the application is launching (e.g., from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
Exercises = ExerciseManager.Load();
}
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
Exercises = ExerciseManager.Load();
}
// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
ExerciseManager.Save(Exercises);
}
// Code to execute when the application is closing (e.g., user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
ExerciseManager.Save(Exercises);
}
// Code to execute if a navigation fails
. . .
5. Usage
From Visual Studio 2010, open the 7Drum project and press Ctrl+F5. The application starts, briefly showing the main page and the menu shown in Figure 1.
Select the Training menu and then tap the Plus button on the application bar, as shown in Figure 2.
The Exercise page is shown with the three empty text boxes. Write something, as shown in Figure 3,
and then click the Save button. The text boxes will be cleaned in order
to let the user add another exercise. Press the hardware Back button to
come back to the Training page.
The Training page will show a
list of exercises you added in the previous step. Now let's produce the
tombstoning. Press the hardware Start button and navigate to the
application list on the right. Run the 7Drum
application again. Select the Training menu, and you will hopefully see
the list of exercises, because you never closed the application (see Figure 4).
NOTE
When you close the Windows Phone 7 Emulator, the isolated storage is deleted along with your application.