第二十四章:页面导航(十五)

简介: 保存和恢复页面状态特别是当您开始使用多页面应用程序时,将应用程序的页面视为数据的主要存储库非常有用,而仅仅是作为底层数据的临时可视化和交互式视图。这里的关键词是暂时的。如果您在用户与之交互时保持基础数据是最新的,那么页面可以显示和消失而不必担心。

保存和恢复页面状态
特别是当您开始使用多页面应用程序时,将应用程序的页面视为数据的主要存储库非常有用,而仅仅是作为底层数据的临时可视化和交互式视图。这里的关键词是暂时的。如果您在用户与之交互时保持基础数据是最新的,那么页面可以显示和消失而不必担心。
本系列的最后一个程序是DataTransfer6,它在程序暂停时将AppData(和其他一些信息)的内容保存在应用程序本地存储中 - 因此当程序终止时 - 然后在程序下次启动时检索该数据起来。
除了保存用户辛苦输入的数据外,您可能还希望保存页面导航堆栈的状态。这意味着如果用户在信息页面上输入数据并且程序终止,则下次程序运行时,它将导航到该信息页面并恢复部分输入的数据。
您可能还记得,Application类定义了一个名为Properties的属性,它是一个包含字符串键和对象值的字典。您可以在App类中的OnSleep覆盖之前或期间在“属性”字典中设置项目。然后,下次App构造函数执行时,这些项将可用。
底层平台通过将对象转换为可以将对象保存到文件的形式来序列化“属性”字典中的对象。应用程序员无论是二进制形式还是字符串形式(可能是XML或JSON)都无关紧要。
对于整数或浮点数,对于DateTime值或对于字符串,序列化很简单。在某些平台上,可以将更复杂的类的实例(例如InformationViewModel)直接保存到Properties集合中。但是,这并不适用于所有平台。将类自身序列化为XML或JSON字符串,然后将结果字符串保存在Properties集合中会更安全。随着Xamarin.Forms可移植类库的.NET版本,XML序列化比JSON序列化更容易,这就是DataTransfer6使用的。
执行序列化和反序列化时,您需要注意对象引用。序列化不保持对象相等。让我们看看这可能是一个问题:
DataTransfer5中引入的AppData版本有两个属性:InfoCollection,它是InformationViewModel对象的集合,以及CurrentInfo,它是当前正在编辑的InformationViewModel对象。
该程序依赖于CurrentInfo对象也是InfoCollection中的项目的事实。 CurrentInfo成为info页面的BindingContext,用户以交互方式更改该InformationViewModel实例的属性。但只是因为同一个对象是InfoCollection的一部分,新值才会显示在ListView中。
序列化AppData的InfoCollection和CurrentInfo属性然后反序列化以创建新的AppData会发生什么?
在反序列化版本中,CurrentInfo对象将具有与InfoCollection中的一个项完全相同的属性,但它不是同一个实例。如果恢复程序以允许用户继续编辑信息页面上的项目,则这些编辑中的任何一个都不会反映在ListView集合中的对象中。
通过这种心理准备,现在是时候查看DataTransfer6中的AppData版本了。

public class AppData
{
    public AppData()
    {
        InfoCollection = new ObservableCollection<InformationViewModel>();
        CurrentInfoIndex = -1;
    }
    public ObservableCollection<InformationViewModel> InfoCollection { private set; get; }
    [XmlIgnore]
    public InformationViewModel CurrentInfo { set; get; }
    public int CurrentInfoIndex { set; get; }
    public string Serialize()
    {
        // If the CurrentInfo is valid, set the CurrentInfoIndex.
        if (CurrentInfo != null)
        {
            CurrentInfoIndex = InfoCollection.IndexOf(CurrentInfo);
        }
        XmlSerializer serializer = new XmlSerializer(typeof(AppData));
        using (StringWriter stringWriter = new StringWriter())
        {
            serializer.Serialize(stringWriter, this);
            return stringWriter.GetStringBuilder().ToString();
        }
    }
    public static AppData Deserialize(string strAppData)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(AppData));
        using (StringReader stringReader = new StringReader(strAppData))
        {
            AppData appData = (AppData)serializer.Deserialize(stringReader);
            // If the CurrentInfoIndex is valid, set the CurrentInfo.
            if (appData.CurrentInfoIndex != -1)
            {
                appData.CurrentInfo = appData.InfoCollection[appData.CurrentInfoIndex];
            }
            return appData;
        }
    }
}

此版本具有InfoCollection属性和CurrentInfo属性(如前一版本),但它还包含int类型的CurrentInfoIndex属性,并且CurrentInfo属性使用XmlIgnore属性进行标记,这意味着它不会被序列化。
该类还有两个方法,名为Serialize和Deserialize。 Serialize首先将CurrentInfoIndex属性设置为InfoCollection中CurrentInfo的索引。然后,它将类的实例转换为XML字符串并返回该字符串。
反序列化恰恰相反。它是一个带字符串参数的静态方法。假定该字符串是AppData对象的XML表示形式。在将其转换为AppData实例后,该方法基于CurrentInfoIndex属性设置CurrentInfo属性。现在,CurrentInfo再次成为InfoCollection成员之一的相同对象。该方法返回该AppData实例。
从DataTransfer5到DataTransfer6的唯一其他变化是App类。 OnSleep覆盖序列化AppData对象并使用“appData”键将其保存在Properties字典中。但如果用户导航到DataTransfer6InfoPage并且可能正在输入或编辑信息,它还会使用键“isInfoPageActive”保存布尔值。
App构造函数反序列化“appData”属性条目中可用的字符串,或者如果该字典条目不存在,则将AppData属性设置为新实例。如果“isInfoPageActive”条目为true,则它不仅必须将DataTransfer6MainPage实例化为NavigationPage构造函数的参数(像往常一样),还必须导航到DataTransfer6InfoPage:

public class App : Application
{
    public App()
    {
        // Ensure link to Toolkit library.
        Xamarin.FormsBook.Toolkit.Toolkit.Init;
        // Load previous AppData if it exists.
        if (Properties.ContainsKey("appData"))
        {
            AppData = AppData.Deserialize((string)Properties["appData"]);
        }
        else
        {
            AppData = new AppData();
        }
        // Launch home page.
        Page homePage = new DataTransfer6HomePage();
        MainPage = new NavigationPage(homePage);
        // Possibly navigate to info page.
        if (Properties.ContainsKey("isInfoPageActive") &&
 (bool)Properties["isInfoPageActive"])
        {
            homePage.Navigation.PushAsync(new DataTransfer6InfoPage(), false);
        }
    }
    public AppData AppData { private set; get; }
    protected override void OnStart()
    {
        // Handle when your app starts
    }
    protected override void OnSleep()
    {
        // Save AppData serialized into string.
        Properties["appData"] = AppData.Serialize();
        // Save Boolean for info page active.
        Properties["isInfoPageActive"] =
 MainPage.Navigation.NavigationStack.Last() is DataTransfer6InfoPage;
    }
    protected override void OnResume()
    {
        // Handle when your app resumes
    }
}

要测试此程序,必须以App类调用其OnSleep方法的方式终止程序。如果您在Visual Studio或Xamarin Studio调试器下运行该程序,请不要从调试器终止该程序。而是在手机上终止应用程序。
也许在手机和手机模拟器上终止程序的最佳方法是首先显示所有当前正在运行的程序:

  • 在iOS上,双击“主页”按钮。
  • 在Android上,点击(最右侧)MultiTask按钮。
  • 在Windows Phone上,按住(最左侧)“后退”按钮。

此操作会导致调用OnSleep方法。然后,您可以终止该程序:

  • 在iOS上,向上滑动应用程序。
  • 在Android上,将其滑动到一边。
  • 在Windows Phone上,将其向下滑动。

在窗口中运行Windows程序时,只需单击“关闭”按钮即可终止程序。在平板电脑模式下,从顶部向下滑动程序。
然后,您可以使用Visual Studio或Xamarin Studio停止调试应用程序(如有必要)。然后再次运行该程序,看它是否“记住”它停止的位置。

目录
相关文章
|
1月前
|
小程序
【微信小程序】-- 页面导航 -- 编程式导航(二十三)
【微信小程序】-- 页面导航 -- 编程式导航(二十三)
|
JavaScript Android开发
第二十六章:自定义布局(十二)
更多附加的可绑定属性附加的可绑定属性也可以在XAML中设置并使用Style设置。 为了了解它是如何工作的,让我们检查一个名为CartesianLayout的类,它模仿一个二维的,四象限的笛卡尔坐标系。
521 0
|
JavaScript Android开发
第二十六章:自定义布局(十一)
重叠的子项Layout 类可以在其子项上调用Layout方法,以便子项重叠吗?是的,但这可能会在你的脑海中提出另一个问题:什么决定孩子们的呈现顺序?哪些孩子看似坐在前台,可能部分或完全掩盖了背景中显示的其他孩子?在某些图形环境中,程序员可以访问名为Z-index的值。
606 0
|
Android开发 索引 iOS开发
第二十四章:页面导航(十七)
像现实生活中的应用程序理想情况下,用户在终止并重新启动应用程序时不应该知道。应用程序体验应该是连续且无缝的。即使程序没有一直运行,一个半月进入的条目从未完成也应该在一周后处于相同的状态。NoteTaker程序允许用户记录由标题和一些文本组成的注释。
524 0
|
JavaScript 前端开发 Android开发
第二十四章:页面导航(十六)
保存和恢复导航堆栈 许多多页面应用程序的页面体系结构比DataTransfer6更复杂,您需要一种通用的方法来保存和恢复整个导航堆栈。此外,您可能希望将导航堆栈的保存与系统方式集成,以保存和恢复每个页面的状态,特别是如果您不使用MVVM。
463 0
|
JavaScript Android开发
第二十四章:页面导航(十四)
切换到ViewModel此时应该很明显,Information类应该真正实现INotifyPropertyChanged。 在DataTransfer5中,Information类已成为InformationViewModel类。
570 0
|
Android开发 索引
第二十四章:页面导航(十二)
事件在方法调用方法和消息中心通信方法中,信息页面需要知道主页的类型。 如果可以从不同类型的页面调用相同的信息页面,这有时是不合需要的。这个问题的一个解决方案是info类实现一个事件,这就是DataTransfer3中采用的方法。
491 0
|
Android开发 索引
第二十四章:页面导航(十一)
消息中心您可能不喜欢两个页面类直接相互调用方法的想法。 它似乎适用于小样本,但对于具有大量类间通信的大型程序,您可能更喜欢一些不需要实际页面实例的更灵活的东西。这样的工具是Xamarin.Forms MessagingCenter类。
524 0
|
JavaScript Android开发 索引
第二十四章:页面导航(十三)
App类中介在Xamarin.Forms应用程序中,在公共代码项目中执行的第一个代码是通常名为App的类的构造函数,该类派生自Application。 在程序终止之前,此App对象保持不变,并且程序中的任何代码都可以通过静态Application.Current属性使用它。
490 0
|
JavaScript Android开发 索引
第二十四章:页面导航(十)
属性和方法调用调用PushAsync或PushModalAsync的页面显然可以直接访问它导航到的类,因此它可以设置属性或调用该页面对象中的方法以将信息传递给它。但是,调用PopAsync或PopModalAsync的页面还有一些工作要做,以确定它返回的页面。
523 0