保存和恢復頁面狀態
特別是當您開始使用多頁面應用程序時,將應用程序的頁面視爲數據的主要存儲庫非常有用,而僅僅是作爲底層數據的臨時可視化和交互式視圖。這裏的關鍵詞是暫時的。如果您在用戶與之交互時保持基礎數據是最新的,那麼頁面可以顯示和消失而不必擔心。
本系列的最後一個程序是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停止調試應用程序(如有必要)。然後再次運行該程序,看它是否“記住”它停止的位置。