При написании приложений WP7 (и новых приложений Metro UI в Windows 8) вам придется иметь дело с такими понятиями, как «Tombstoning» или «Suspension»; Если вы следуете шаблону MVVM, очень вероятно, что статус страницы вашего приложения представлен самой ViewModel. Таким образом, быстрый и грязный способ обработки этих приостановленных состояний состоит в том, чтобы «сохранить» ViewModel (или его часть, имеющую фактическое значение) в состоянии приложения или в файловом хранилище и извлечь его позднее, когда приложение будет повторно активировано. ,
Если вы поместите что-то, что не является примитивным типом, в словари «Состояние приложения» или «Состояние страницы», оно будет сериализовано с использованием стандартного DataContractSerializer (по вопросу сериализации и десериализации в WP7 вы можете прочесть мои предыдущие посты: WP7. Понимание сериализации. и http://www.primordialcode.com/blog/post/wp7-datacontractserializer-bug ).
Хороший способ избежать головной боли при использовании этого подхода для обработки захоронения / приостановки — это проверить не только поведение ваших ViewModels, но также и то, являются ли ваши ViewModels сериализуемыми; Вы можете сделать это, используя вспомогательный класс, подобный этому:
public static class DataContractSerializerHelpers { public static string ToXml(object obj) { return ToXml(obj, null); } public static string ToXml(object obj, IEnumerable<Type> knownTypes) { Type objType = obj.GetType(); DataContractSerializer ser = new DataContractSerializer(objType, knownTypes); { using (StringWriter sw = new StringWriter()) { XmlWriterSettings settings = new XmlWriterSettings(); settings.OmitXmlDeclaration = true; settings.Indent = true; //settings.NewLineOnAttributes = true; using (XmlWriter writer = XmlWriter.Create(sw, settings)) { ser.WriteObject(writer, obj); } return sw.ToString(); } } } public static object FromXml(string data, Type type) { return FromXml(data, type, null); } public static object FromXml(string data, Type type, IEnumerable<Type> knownTypes) { using (StringReader sr = new StringReader(data)) { XmlReaderSettings settings = new XmlReaderSettings(); using (XmlReader reader = XmlReader.Create(sr, settings)) { DataContractSerializer ser = new DataContractSerializer(type, knownTypes); return ser.ReadObject(reader); } } } public static T FromXml<T>(string data) where T : class { return FromXml(data, typeof(T), null) as T; } public static T FromXml<T>(string data, IEnumerable<Type> extraTypes) where T : class { return FromXml(data, typeof(T), extraTypes) as T; } }
По сути, это простая оболочка для функции DataContractSerializer, которая позволяет вызывать ее удобным способом, который имитирует работу инфраструктуры.
Используя стандартные DataContractAttribute и DataMemberAttribute в ViewModels, вы можете точно настроить то, что будет сохраняться (очевидно, вы должны ограничить это свойствами, которые имеют некоторое значение для состояния вашего приложения, вам также нужно будет разработать ViewModels, чтобы обеспечить ленивую инициализацию для всего, что зависит от этих данных). Ваши тесты будут выглядеть примерно так:
[TestClass] public class SerializableTypes : SilverlightTest { // Test if we can save the VM to the state dictionaries [TestMethod] public void ViewModel_Serialize() { // todo: add proper members initialization MainViewModel vm = new MainViewModel(); DataContractSerializerHelpers.ToXml(vm); } // Test if we can retrieve the VM from the state dictionaries [TestMethod] public void ViewModel_DeSerialize() { // todo: add proper members initialization MainViewModel vm = new MainViewModel(); var data = DataContractSerializerHelpers.ToXml(vm); // test var loaded = DataContractSerializerHelpers.FromXml<MainViewModel>(data); Assert.IsNotNull(loaded); // todo: add proper testing on members values } }
При таком подходе код, который вы должны написать в представлениях для обработки надгробий, станет тривиальным; в ваших взглядах у вас будет нечто похожее на это:
... private SingleFeedViewModel Vm { get { return (SingleFeedViewModel)DataContext; } set { DataContext = value; } } protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { base.OnNavigatedTo(e); // if we came up after a dormant state we do not need to do anything at this stage // I also have to check if the Vm is unassigned (we can come here from a normal navigation, after a resume) if (ApplicationHelper.IsApplicationInstancePreserved && Vm != null) return; // recover from tombstoning if (State.ContainsKey(StateKeys.SingleFeedVm)) { //clear prev value SingleFeedViewModel itm = State[StateKeys.SingleFeedVm] as SingleFeedViewModel; if (itm != null) Vm = itm; } // nothing was retrieved from the state dictionary, create an instance // of the ViewModel if (Vm == null) { Vm = new SingleFeedViewModel(); Vm.LoadData(); } } protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) { base.OnNavigatedFrom(e); // save state to handle tombstoning if (State.ContainsKey(StateKeys.SingleFeedVm)) { //clear previous value State.Remove(StateKeys.SingleFeedVm); } State.Add(StateKeys.SingleFeedVm, Vm); } ...
ПРЕДУПРЕЖДЕНИЕ! Я собирался забыть важный момент: события не могут быть сериализованы (еще раз ссылка на другой мой старый пост: исключение сериализации: PropertyChangedEventManager не сериализуем , он связан с двоичной сериализацией или объектами, но концепция та же), поэтому не забудьте пометить события с помощью DataMemberAttribute или, если ваш ViewModel имеет все общедоступное, и вы не использовали DataContractAttribute для выбора того, что вы хотите сериализовать, лучше пометить события с помощью [field: IgnoreDataMember] для исключить их из процесса сериализации.
Источник:
http://www.primordialcode.com/blog/post/wp7-test-if-your-viewmodel-is-serializable