This chapter
is taken from book "Programming Windows Phone 7" by Charles Petzold published by
Microsoft press.
http://www.charlespetzold.com/phone/index.html
We want our phones to be much like our other computers.
We want to have a lot of applications available. We want to start up a
particular application as soon as we conceive a need for it. While that
application is running, we want it to be as fast as possible and have access to
unlimited resources. But we want this application to coexist with other running
applications because we want to be able to jump among multiple applications
running on the machine.
Windows Phone 7 manages multiple active applications by
implementing a stack. In a sense, this application stack extends the page stack
within a single Silverlight program. You can think of the phone as an
old-fashioned web browser with no tab feature and no Forward button. But it does
have a Back button and it also has a Start button, which brings you to the Start
screen and allows you to launch a new program.
Page State
The SilverlightFlawedTombstoning project is a simple Silverlight program with
just one page. The program responds to taps on the screen by changing the
background of ContentGrid to a random color, and displaying the total number of
taps in its page title. Everything of interest happens in the code-behind file:
namespace
SilverlightFlawedTombstoning
{
public partial
class MainPage
: PhoneApplicationPage
{
Random rand =
new Random();
int numTaps = 0;
public MainPage()
{
InitializeComponent();
UpdatePageTitle(numTaps);
}
protected
override void OnManipulationStarted(ManipulationStartedEventArgs
args)
{
ContentPanel.Background = new
SolidColorBrush(Color.FromArgb(255,
(byte)rand.Next(256),
(byte)rand.Next(256),
(byte)rand.Next(256)));
UpdatePageTitle(++numTaps);
args.Complete();
base.OnManipulationStarted(args);
}
void UpdatePageTitle(int
numTaps)
{
PageTitle.Text = String.Format("{0}
taps total", numTaps);
}
}
}
The little
UpdatePageTitle method is called from both the
program's constructor (where it always results in displaying a value of 0) and
from the OnManipulationStarted
override.
Build and deploy the program to the phone or phone
emulator by pressing F5 (or selecting Start Debugging from the Debug menu).
Arrange Visual Studio so you can see the Output window. When the program starts
up, tap the screen several times to change the color and bump up the tap count.
Now press the phone's Start button. You can see from Visual Studio that two
threads in the program end and the program has terminated, but to the phone the
program has actually been deactivated and tombstoned.
Now press the Back button to return to the program.
You'll see a blank screen with the word "Resuming…" and the Output window in
Visual Studio shows libraries being loaded. That's the program coming back to
life.
Although Windows Phone 7 leaves much of the
responsibility for restoring a tombstoned application to the program itself, it
will cause the correct page to be loaded on activation, so it's possible that a
page-oriented Silverlight program that saves and restores page state data using
the State
property of PhoneApplicationSerivce
class during
OnNavigatedTo and
OnNavigatedFrom will
need no special processing for tombstoning. The phone operating system preserves
this State
property during the time a program is deactivated and tombstoned, but gets rid
of it when the program closes and is terminated for real.
The code-behind file for SilverlightBetterTombstoning
includes a using
directive for
Microsoft.Phone.Shell and uses this
State dictionary.
Here's the complete class:
namespace SilverlightBetterTombstoning
{
public partial
class MainPage
: PhoneApplicationPage
{
Random rand =
new Random();
int numTaps = 0;
PhoneApplicationService appService =
PhoneApplicationService.Current;
public MainPage()
{
InitializeComponent();
UpdatePageTitle(numTaps);
}
protected
override void OnManipulationStarted(ManipulationStartedEventArgs
args)
{
ContentPanel.Background = new
SolidColorBrush(Color.FromArgb(255,
(byte)rand.Next(256),
(byte)rand.Next(256),
(byte)rand.Next(256)));
UpdatePageTitle(++numTaps);
args.Complete();
base.OnManipulationStarted(args);
}
void UpdatePageTitle(int
numTaps)
{
PageTitle.Text = String.Format("{0}
taps total", numTaps);
}
protected
override void OnNavigatedFrom(NavigationEventArgs
args)
{
appService.State["numTaps"] =
numTaps;
if (ContentPanel.Background
is SolidColorBrush)
{
appService.State["backgroundColor"]
= (ContentPanel.Background as
SolidColorBrush).Color; }
base.OnNavigatedFrom(args);
}
protected
override void OnNavigatedTo(NavigationEventArgs
args)
{
// Load numTaps
if (appService.State.ContainsKey("numTaps"))
{
numTaps = (int)appService.State["numTaps"];
UpdatePageTitle(numTaps);
}
// Load background color
object obj;
if (appService.State.TryGetValue("backgroundColor",
out obj))
ContentPanel.Background = new
SolidColorBrush((Color)obj);
base.OnNavigatedTo(args);
}
}
}
Notice the
appService field set to
PhoneApplicationService.Current.
That's just for convenience for accessing the
State property. You
can use the long
PhoneApplicationService.Current.State instead if you
prefer.
Isolated Storage
Every program installed on Windows Phone 7 has access to its own area of
permanent disk storage referred to as isolated storage, which the program can
access using classes in the System.IO.IsolatedStorage namespace. Whole files can
be read and written to in isolated storage, and I'll show you how to do that in
the program that concludes this chapter. For the program that following I'm
going to focus instead on a special use of isolated storage for storing
application settings. The IsolatedStorageSettings class exists specifically for
this purpose.
For the SilverlightIsolatedStorage program, I decided
that the number of taps should continue to be treated as transient data-part of
the state of the page. But the background color should be an application setting
and shared among all instances.
App.xaml.cs has empty event handlers for all the
PhoneApplicationService
events. I gave each handler a body consisting of a single
method call and the LoadSettings
and SaveSettings
methods. Both methods obtain an
IsolatedStorageSettings
object. Like the State
property of
PhoneApplicationService, the
IsolatedStorageSettings
object is a dictionary. One method in the program loads
(and the other saves) the Color
property of the
BackgroundBrush property:
public
partial
class
App
: Application
{
//
Application settings
public
Brush
BackgroundBrush { set;
get;
}
// Easy
access to the root frame
public
PhoneApplicationFrame
RootFrame { get;
private
set;
}
//
Constructor
public
App()
{
// Global handler for uncaught exceptions.
// Note that exceptions thrown by
ApplicationBarItem.Click will not get caught here.
UnhandledException += Application_UnhandledException;
// Standard Silverlight initialization
InitializeComponent();
// Phone-specific initialization
InitializePhoneApplication();
}
// Code
to execute when the application is launching (eg, from Start)
// This code will not execute when the application is
reactivated
private
void
Application_Launching(object
sender, LaunchingEventArgs
e)
{
LoadSettings();
}
// 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)
{
LoadSettings();
}
// 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)
{
SaveSettings();
}
// Code
to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is
deactivated
private
void
Application_Closing(object
sender, ClosingEventArgs
e)
{
SaveSettings();
}
void
LoadSettings()
{
IsolatedStorageSettings settings =
IsolatedStorageSettings.ApplicationSettings;
Color clr;
if (settings.TryGetValue<Color>("backgroundColor",
out
clr))
BackgroundBrush = new
SolidColorBrush(clr);
}
void
SaveSettings()
{
IsolatedStorageSettings settings =
IsolatedStorageSettings.ApplicationSettings;
if (BackgroundBrush
is
SolidColorBrush)
{
settings["backgroundColor"]
= (BackgroundBrush as
SolidColorBrush).Color;
settings.Save();
}
}
}
And finally the new MainPage.xaml.cs file. This file-and
any other class in the program-can get access to the
App object using the
static Application.Current
property and casting it to an
App. The constructor
of MainPage
obtains the BackgroundBrush
property from the App
class, and the
OnManipulationStarted method sets that
BackgroundBrush
property.
namespace SilverlightIsolatedStorage
{
public partial
class MainPage
: PhoneApplicationPage
{
Random rand =
new Random();
int numTaps = 0;
PhoneApplicationService appService =
PhoneApplicationService.Current;
public MainPage()
{
InitializeComponent();
UpdatePageTitle(numTaps);
// Access App class for isolated storage
setting
Brush brush = (Application.Current
as App).BackgroundBrush;
if (brush !=
null)
ContentPanel.Background = brush;
}
protected
override void OnManipulationStarted(ManipulationStartedEventArgs
args)
{
SolidColorBrush brush =
new
SolidColorBrush(Color.FromArgb(255,
(byte)rand.Next(256),
(byte)rand.Next(256),
(byte)rand.Next(256)));
ContentPanel.Background = brush;
// Save to App class for isolated storage
setting
(Application.Current
as App).BackgroundBrush
= brush;
UpdatePageTitle(++numTaps);
args.Complete();
base.OnManipulationStarted(args);
}
void UpdatePageTitle(int
numTaps)
{
PageTitle.Text = String.Format("{0}
taps total", numTaps);
}
protected
override void OnNavigatedFrom(NavigationEventArgs
args)
{
appService.State["numTaps"] =
numTaps;
base.OnNavigatedFrom(args);
}
protected
override void OnNavigatedTo(NavigationEventArgs
args)
{
// Load numTaps
if (appService.State.ContainsKey("numTaps"))
{
numTaps = (int)appService.State["numTaps"];
UpdatePageTitle(numTaps);
}
}
}
}
Xna Tombstoning and Settings
XNA applications aren't normally built around pages like
Silverlight applications. If you wanted, however, you could certainly implement
your own page-like structure within an XNA program. You'll recall that the state
of the phone's Back button is checked during every call to the standard
Update override. You
can use this logic for navigational purposes as well as for terminating the
program. But that's something I'll let you work out on your own.
I gave the
XnaTombstoning project a dedicated
Settings class that
uses the more generalized features of isolated storage that involve real files
rather than just simple settings. You'll need a reference to
System.Xml.Serialization library for this class as well
using directives for
the System.IO,
System.IO.IsolatedStorage,
and System.Xml.Serialization
namespaces.
namespace
XnaTombstoning
{
public class
Settings
{
const string
filename = "settings.xml";
// Application settings
public
Color BackgroundColor {
set; get; }
public Settings()
{
BackgroundColor = Color.Navy;
}
public void
Save()
{
IsolatedStorageFile storage =
IsolatedStorageFile.GetUserStoreForApplication();
IsolatedStorageFileStream stream
= storage.CreateFile(filename);
XmlSerializer xml =
new XmlSerializer(GetType());
xml.Serialize(stream, this);
stream.Close();
stream.Dispose();
}
public static
Settings Load()
{
IsolatedStorageFile storage =
IsolatedStorageFile.GetUserStoreForApplication();
Settings settings;
if (storage.FileExists(filename))
{
IsolatedStorageFileStream
stream = storage.OpenFile("settings.xml",
FileMode.Open);
XmlSerializer xml =
new XmlSerializer(typeof(Settings));
settings = xml.Deserialize(stream) as
Settings;
stream.Close();
stream.Dispose();
}
else
{
settings = new
Settings();
}
return settings;
}
}
}
The remainder of the XnaTombstoning project lets you tap
the screen and responds by displaying a new random background color and a count
of the number of taps. The background color is treated as an application setting
(as is evident by its inclusion in the
Settings class) and the number of taps is a
transient setting.
Here's an excerpt of the
Game1 class showing
the fields, constructor, and
PhoneApplicationService events:
namespace
XnaTombstoning
{
public class
Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Settings settings;
SpriteFont segoe14;
Viewport viewport;
Random rand =
new Random();
StringBuilder text =
new StringBuilder();
Vector2 position;
int numTaps = 0;
public Game1()
{
graphics = new
GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Frame rate is 30 fps by default for
Windows Phone.
TargetElapsedTime = TimeSpan.FromTicks(333333);
TouchPanel.EnabledGestures =
GestureType.Tap;
PhoneApplicationService
appService = PhoneApplicationService.Current;
appService.Launching += OnAppServiceLaunching;
appService.Activated += OnAppServiceActivated;
appService.Deactivated += OnAppServiceDeactivated;
appService.Closing += OnAppServiceClosing;
}
protected
override void Initialize()
{
base.Initialize();
}
protected
override void LoadContent()
{
spriteBatch = new
SpriteBatch(GraphicsDevice);
segoe14 = this.Content.Load<SpriteFont>("Segoe14");
viewport = this.GraphicsDevice.Viewport;
}
protected
override void UnloadContent()
{
}
protected
override void Update(GameTime
gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back
== ButtonState.Pressed)
this.Exit();
while (TouchPanel.IsGestureAvailable)
if (TouchPanel.ReadGesture().GestureType
== GestureType.Tap)
{
numTaps++;
settings.BackgroundColor = new
Color((byte)rand.Next(255),
(byte)rand.Next(255),
(byte)rand.Next(255));
}
text.Remove(0, text.Length);
text.AppendFormat("{0} taps total",
numTaps);
Vector2 textSize =
segoe14.MeasureString(text.ToString());
position = new
Vector2((viewport.Width - textSize.X) / 2,
(viewport.Height - textSize.Y) / 2);
base.Update(gameTime);
}
protected
override void Draw(GameTime
gameTime)
{
GraphicsDevice.Clear(settings.BackgroundColor);
spriteBatch.Begin();
spriteBatch.DrawString(segoe14, text, position,
Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
protected
override void OnActivated(object
sender, EventArgs args)
{
if (PhoneApplicationService.Current.State.ContainsKey("numTaps"))
numTaps = (int)PhoneApplicationService.Current.State["numTaps"];
base.OnActivated(sender, args);
}
protected
override void OnDeactivated(object
sender, EventArgs args)
{
PhoneApplicationService.Current.State["numTaps"]
= numTaps;
base.OnDeactivated(sender, args);
}
void OnAppServiceLaunching(object
sender, LaunchingEventArgs args)
{
settings = Settings.Load();
}
void OnAppServiceActivated(object
sender, ActivatedEventArgs args)
{
settings = Settings.Load();
}
void OnAppServiceDeactivated(object
sender, DeactivatedEventArgs args)
{
settings.Save();
}
void OnAppServiceClosing(object
sender, ClosingEventArgs args)
{
settings.Save();
}
}
}
The program will get a call to
OnActivated about the
same time the Launching
and Activated
events are fired, and a call to
OnDeactivated about
the same time the Deactivated
and Closing
events are fired. The differentiation is more conceptual in
that OnActivated
and OnDeactivated
are associated with the
Game instance, so they
should be used for properties associated with the game rather than overall
application settings.
Summary
I hope this article helps you to learn Multitasking or
Tombstoning in Windows Phone 7 in Windows Phone 7.