I think it is a very tricky issue to deal with tombstone. once the program returns the current page from the tombstone and all data on the current page is empty, the constructor of the page is called again to create a new page, if the UI created by the background code is not used on this page, it is easier. If the UI created by the background code is used, you need to re-Add the UI and restore the UI status. I think this process will be very painful. My idea is to make the part that requires dynamic UI creation into a usercontrol and process the UI creation logic in usercontrol. first, define the dependency attribute in usercontrol, and then bind the dependency attribute using data binding technology. In viewmodel, control usercontrol through parameter settings to dynamically generate the UI. if it succeeds, you can use the following database to process the tombstone. In addition, I think the prerequisite for the smooth processing of the tombstone is that the mvvm mode is well used by the program.
Next I will introduce the tombstone Processing Method of the mvvm mode.
First, we will introduce a library called
Mvvm tombstone For Windows Phone
Http://mvvmtombstone.codeplex.com/SourceControl/latest#WindowsPhone.MVVM.Tombstone/WindowsPhone.MVVM.Tombstone/TombstoneAttribute.cs
1. First, use nuget to install mvvm tombstone For Windows Phone
2 Add the following code in onnavigatedfrom and onnavigatedto respectively.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
TombstoneHelper.page_OnNavigatedTo(this, e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
TombstoneHelper.page_OnNavigatedFrom(this, e);
}
3. Add the following code to the application_activated method of APP. CS:
1 |
TombstoneHelper.Application_Activated(sender, e); |
4. Add [tombstone ()] to the attributes you want to save.
[Tombstone()]
public SomeClass MyObject {
get { return _myobject; }
set {
_myobject = value;
RaisePropertyChanged("MyObject");
}
}
OK. it's that simple. I want to know how to implement it. The code is on codeplex. let me introduce it. the Code mainly uses reflection. When the program switches to the background, it traverses the attributes of the current viewmodel with the [tombstone ()] tag and uses the values of these attributes in JSON. and save the serialized result to phoneapplicationpage. in the state dictionary, when the program returns from the background to the foreground, it traverses the attribute with the [tombstone ()] tag of the current viewmodel. If the value of this attribute is null, read the value stored in the state dictionary and assign it to the corresponding attribute of viewmodel.
The following describes the source code.
1. The following class defines an attribute tag. The tag is similar to the annotation we use, but this label is for the compiler. When saving the attributes in viewmodel, it is to save the attributes with this label by judging.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class TombstoneAttribute : Attribute
{
}
2. The following class is the core class.
internal class ApplicationState
{
static JsonSerializerSettings settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects
};
/// <summary>
/// Runs over all the properties in the page's viewmodel and saves the state of those that have the tombstone attribute applied
/// </summary>
static internal void Save(PhoneApplicationPage page)
{
foreach (PropertyInfo tombstoneProperty in GetTombstoneProperties(page.DataContext))
{
string key = GetKey(tombstoneProperty);
object value = tombstoneProperty.GetValue(page.DataContext, null);
page.State[key] = JsonConvert.SerializeObject(value, Formatting.None, settings);
}
}
/// <summary>
/// Runs over all the properties in the page's viewmodel and restores the state of those that have the tombstone attribute applied and have no value
/// </summary>
static internal void Restore(PhoneApplicationPage page)
{
foreach (PropertyInfo tombstoneProperty in GetTombstoneProperties(page.DataContext))
{
if (tombstoneProperty.GetValue(page.DataContext, null) == null)
{
string key = GetKey(tombstoneProperty);
if (page.State.ContainsKey(key))
{
tombstoneProperty.SetValue(page.DataContext, JsonConvert.DeserializeObject((string)page.State[key], tombstoneProperty.PropertyType, settings), null);
}
}
}
}
/// <summary>
/// Defines the key we are saving in the page's state bag
/// </summary>
private static string GetKey(PropertyInfo Prop)
{
return "tshelper.viewmodel." + Prop.Name;
}
/// <summary>
/// Obtains all the propertyinfos that have the TombstoneAttribute applied
/// <param name="ViewModel">The viewmodel to check. This is set to the page's DataContext</param>
private static IEnumerable<PropertyInfo> GetTombstoneProperties(object ViewModel)
{
if (ViewModel != null) {
IEnumerable<PropertyInfo> tsProps = from p in ViewModel.GetType().GetProperties()
where p.GetCustomAttributes(typeof(TombstoneAttribute), false).Length > 0
select p;
foreach (PropertyInfo tsProp in tsProps) {
if (!tsProp.CanRead || !tsProp.CanWrite) {
throw new TombstoneException(string.Format("Cannot restore value of property {0}. Make sure the getter and setter are public", tsProp.Name));
}
}
return tsProps;
} else {
return new List<PropertyInfo>();
}
}
}
Gettombstoneproperties returns a set of attributes with the [tombstone ()] tag.
The restore method is used to restore data during the tombstone, traverse the attributes with the tombstone () tag, and retrieve the attribute values from the current viewmodel Using Reflection. If the value is empty, read the attribute value from the State dictionary. After deserialization
Set the value to the attribute.
The Save method is used to save data, traverse the attributes with the tombstone tag, serialize the attribute values, and save them to the state dictionary.
3. The following class is the interface we use.
public sealed class TombstoneHelper
{
private static bool _hasbeentombstoned = false;
public static void Application_Activated(object sender, ActivatedEventArgs e)
{
if (!e.IsApplicationInstancePreserved)
{
_hasbeentombstoned = true;
}
}
private static List<PhoneApplicationPage> restoredpages = new List<PhoneApplicationPage>();
public static void page_OnNavigatedTo(PhoneApplicationPage sender, NavigationEventArgs e)
{
if (sender != null && _hasbeentombstoned && !restoredpages.Contains(sender))
{
restoredpages.Add(sender);
ApplicationState.Restore(sender);
}
}
public static void page_OnNavigatedFrom(PhoneApplicationPage sender, NavigationEventArgs e)
{
if (!(e.NavigationMode == NavigationMode.Back))
{
if (sender != null)
{
ApplicationState.Save(sender);
}
}
}
}
When you exit the current page, the Framework calls onnavigateedfrom. There are two ways to exit the current page. One is to press the back key, and the value of navigationmode is back. the other way is that you press the START key and the program enters the later stage. when the program is switched to the backend, it is uncertain whether the program enters the tombstone. Therefore, the data is saved as long as the program enters the step.
if (!(e.NavigationMode == NavigationMode.Back)) {}
If you press the back key, the program is switched to the background.
In the page_onnavigatedto method, determine whether _ hasbeentombstoned is true. If it is true and the recovered page does not contain the current page, restore data. (I always think it is a bit redundant to set the restoredpages list. Why do I need to save the pages that have recovered data)
Application_activated this method is to set the tombstone mark.
I don't know if there is any better way to deal with the tombstone?
If the program does not handle the tombstone, I think it is always not perfect. If any of you have a better way to handle the tombstone, please kindly advise. We look forward to your reply.