1. Problem
You have to navigate between
pages, and you want to store a complex object that is not suitably
stored via the query string method. You need to store the object to
prevent tombstoning.
2. Solution
You should use the State dictionary provided by the PhoneApplicationService class.
3. How It Works
Sometimes you have to share data between pages that is not suitable for the QueryString property—for instance, when you have to provide a complex object such as an array or list to another page. Moreover, the QueryString property can accept up to 65,519 characters from the Uri object.
Windows Phone 7 helps developers via the State dictionary, which is provided by the PhoneApplicationService class included in the Microsoft.Mobile.Shell namespace.
The PhoneApplicationService class includes the Current static property that retrieves the PhoneApplicationService object related to the current application. By using this object, you can use the State property to store data across pages. Moreover, the PhoneApplicationService automatically manages the application's idle behavior and the application's state when it becomes either active or inactive.
Indeed, managing active
and inactive states is an important aspect of Windows Phone 7
life-cycle application management that a developer has to consider.
During the application usage, a lot of external events may occur, and
the developer has to provide the right solutions to avoid a bad user
experience. For example, imagine that a user is filling in a form on the
page you provided when that user remembers an important e-mail that
needs to be sent immediately. The user presses the hardware Start
button, and your application becomes inactive. After the e-mail is sent,
the user presses the hardware Back button until the application becomes
active again. How would the user react if all data in the form was
lost?
To provide a complex object
to another page, you could use the global application variable technique
too (see Recipe 2-3). But what is provided here for free is tombstoning
management. When the application is deactivated because of an external
event such as the hardware Start button being pressed, the State
dictionary is serialized and stored automatically by the Windows Phone 7
operating system. If the application is activated again—not by
launching the application from the Start menu but by going back via the
hardware Back button—the State dictionary is deserialized and provided to the application. That's why the State dictionary has to contain only serializable objects.
As already stated in Recipe 2-1, when the Navigate
method is called, a brand new page is created and shown. So if your
text boxes are blank by default, when the application is activated from
the Windows Phone operating system, the page will show empty text boxes.
To manage this aspect and avoid this application behavior, you can implement the code in the OnNavigatedFrom and the OnNavigatedTo event handlers provided by the PhoneApplicationPage class. The OnNavigatedFrom
event is raised just before the user is going to navigate off the page.
This event handler is the right place to insert the code that saves
your data. The OnNavigatedTo event is
raised just after the page is shown because of navigation either from
another page or from the Windows Phone 7 operating system.
Both event handlers provide a NavigationEventArgs object that provides some interesting properties such as the Uri of the target page or the target application, and the target Content object. For example, in the OnNavigatedFrom event handler, the Content property contains the MainPage object when you come back to the starting page from page 2. On the other hand, when the application is deactivated, the Content property is null and the Uri is set to the app://external/ value.
Another useful property provided by the PhoneApplicationPage class is State. Do not confuse this with the one provided by the PhoneApplicationService
class. The one provided by the page is not shared through the
application but is for the page only. This is great for storing page
data such as text-box values, check-box selections, and so on.
4. The Code
To demonstrate navigation
between pages with a complex data object exchanged, we will create a new
Windows Phone 7 Silverlight application called NavigatingWithStateApp. In this application, MainPage will show a message taking the first name, last name, and city strings provided by the Page2 page. Those strings are defined in a new class called Person.
using System;
namespace NavigatingWithStateApp
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public Person() { }
}
}
The class must be
serializable and must provide a public parameterless constructor (if you
don't specify it, the compiler will create it for you).
In MainPage.xaml, we added a TextBlock control to show the message, and a hyperlink button to navigate to the second page.
. . .
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock x:Name="tbMsg" />
<HyperlinkButton x:Name="hlNavigate" Content="Navigate to Page 2"
Click="hlNavigate_Click"/>
</Grid>
. . .
In the MainPage.xaml.cs source code, we added the Loaded event handler, which builds the string to be shown only when the State dictionary returned by the PhoneApplicationService's Context property contains the Person object saved in the Page2.
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
if (PhoneApplicationService.Current.State.ContainsKey("Person"))
{
Person p = (Person)PhoneApplicationService.Current.State["Person"];
tbMsg.Text = string.Format("Welcome {0} {1} from {2}", p.FirstName,
p.LastName, p.City);
}
}
As already done in previous recipes, we add another page called Page2.xaml. In the Page2.xaml page, we add three TextBox controls and one Button control:
. . .
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel>
<TextBox x:Name="txtName" Text="FirstName" GotFocus="txt_GotFocus" />
<TextBox x:Name="txtLast" Text="LastName" GotFocus="txt_GotFocus" />
<TextBox x:Name="txtCity" Text="City" GotFocus="txt_GotFocus" />
<Button x:Name="btnSave" Content="Save" Click="btnSave_Click" />
</StackPanel>
</Grid>
. . .
Every TextBox control implements GotFocus, so when the TextBox receives the focus, it will select its own text:
private void txt_GotFocus(object sender, RoutedEventArgs e)
{
TextBox txt = sender as TextBox;
txt.SelectAll();
}
This technique is done to avoid using labels; by showing the string, the text box indicates what its content should be.
|
|
The Save button calls the SaveOrUpdate method, which stores the text boxes' text into a Person object. First, the method checks whether the State dictionary of Page2 already contains a Person
object. If it does, the method retrieves the object and changes its
value. Otherwise, the method creates a brand new object, filling it with
the text boxes' values. In both cases, the last instruction is used to
store the Person object in the State dictionary of Page2.
Only when the SaveOrUpdate method is called via the Save button is the State dictionary (provided by the Context property of the PhoneApplicationService)populated with the Person object. In this way, the main page will show the message only when the user saves data. If you don't separate the two State dictionaries, and use the PhoneApplicationService's
one to store text-box values both on a save operation and on
tombstoning, you will have to implement a technique to know when MainPage has to manage data and when it does not.
private void SaveOrUpdate(bool isSaved)
{
Person p = null;
if (this.State.ContainsKey("Person"))
{
p = (Person) this.State["Person"];
p.FirstName = txtName.Text;
p.LastName = txtLast.Text;
p.City = txtCity.Text;
}
else
{
p = new Person();
p.FirstName = txtName.Text;
p.LastName = txtLast.Text;
p.City = txtCity.Text;
}
if (isSaved)
{
PhoneApplicationService.Current.State["Person"] = p;
}
this.State["Person"] = p;
}
The core of this example is the code in the OnNavigatedFrom and OnNavigatedTo event handlers. In the former, the SaveOrUpdate
method is called again so that when the application is deactivated, the
user doesn't lose data. The method will not be called when the user
presses the hardware Back button, because in this case its behavior is
similar to the Cancel button in a dialog box. Retrieving when the
hardware Back button is pressed is accomplished by checking the Content property of the NavigationEventArgs object passed to the event handler.
protected override void
OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
if (e.Content == null)
{
SaveOrUpdate(false);
}
base.OnNavigatedFrom(e);
}
In the OnNavigatedTo event handler, we simply check whether the State dictionary from the Page2 class contains the Person object and then we eventually fill the text boxes' text with its values.
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs
e)
{
if (this.State.ContainsKey("Person"))
{
Person p = (Person)this.State["Person"];
txtName.Text = p.FirstName;
txtLast.Text = p.LastName;
txtCity.Text = p.City;
}
base.OnNavigatedTo(e);
}
Finally, the Save button code calls the SaveOrUpdate method, passing a true value to indicate that it will have to save the Person object into the PhoneApplicationService's State dictionary.
private void btnSave_Click(object sender, RoutedEventArgs e)
{
SaveOrUpdate(true);
this.NavigationService.GoBack();
}
5. Usage
From Visual Studio 2010, run
the application after checking that the target is set to Windows Phone 7
Emulator. The main page will be shown briefly with just the link to
navigate to the second page (see Figure 1).
By pressing the hyperlink button, you go to the second page, shown in Figure 2.
Now you can insert your
credentials and press the Save button so the application returns to the
start page and shows the message (see Figure 3).
Now let's play a bit with the application and explore unexpected situations. Comment the code of the OnNavigatedFrom event handler from Page2.xaml.cs
and restart the application. Go to page 2 and add some text to the text
boxes. Press the hardware Start button, leaving the application, and
then the hardware Back button. The application is resumed, but
everything you inserted in text boxes is gone. Now redo the same steps,
this time uncommenting the OnNavigateFrom code. The initial situation will be resumed, as you left it.