Win8的導航的實在是讓我有點鬱悶,尤其是像我這樣原來做過WP7開發的真的一時難以適應。
Win8導航的問題,目前一共有兩個:
- 如果在不啟用頁面緩衝的時候,每次返回的時候都會重新整理頁面(在WP7中頁面的所有狀態自動儲存,離開的時候什麼樣的,返回的時候還是那樣),這就導致使用者狀態需要開發人員自己去儲存。你可能會說既然這樣可以啟動頁面緩衝啊,是啊,你當然可以啟用頁面緩衝,但是一旦啟用了頁面的緩衝,你這個頁面同一時間只能有一個執行個體,這樣對於對應不同的參數顯示不同的資料的頁面來說就很困難了,因為你同一時間只有一個執行個體。
- 導航的時候使用者是可以傳遞object參數的,我剛看到可以傳遞object參數的時候我還以開心,覺得微軟終於把WP7的導航模型改進了下,可是當我調試掛起狀態的時候我發現我錯了,直接crash。於是我在微軟的官網論壇問了下,給我的回答是,如果傳遞的參數是複雜類型(即使你已經標記了DataContract和DataMember)那麼程式掛起的時候依然會crash,那也就是說你必須把你的複雜類型在傳遞之前序列化成字串,然後在目標頁把字串還原序列化成相應對象。我覺得微軟你這麼做有意思嗎?尼瑪你還不如用WP7導航模型啊!
下面針對第一個問題進行討論。首先如果你的頁面不需要根據不同的參數顯示不同的資料的,那麼完全可以啟用頁面緩衝(頁面的NavigationCacheMode設定成Enabled或者Required)。如果你的頁面需要根據不同的參數顯示不同資料,那麼作為開發人員要做的事情比起WP7的導航模型來說要多了,首先是頁面資料的儲存,其次是頁面狀態的儲存(如果有捲軸你得儲存捲軸的狀態,如果有文字框,你得儲存文字框中的文本…..)。現在拿VS2012中的內建的模版做個列子。
首先從項目模版中選擇Grid APP模版,建立項目以後直接運行。進入首頁,滑動捲軸,滑倒最後,然後隨便選擇一項進入該項詳細資料頁,然後返回,這時你會發現頁面回到的初始狀態,捲軸也回到了原點。現在解釋下為什麼會這樣。
首先頁面沒有啟用緩衝,那麼每次返回的時候頁面總是會重新整理,如何重新整理的?其實就是返回的時候系統重新調用InitializeComponent方法,原來所有的資料和狀態都不複存在,就像一個全新的頁面一樣(其實你應該把它當成一個全新的頁面)。
如果只能這樣的話我們肯定會抓狂,還好微軟留了兩個方法,LoadState和SaveState,其實這兩個方法是項目模版中自訂的方法。
- LoadState方法,當該頁面第一次載入(注意不是返回)的時候,參數pageState為null。當頁面是從前一頁返回的時候載入的,那麼參數pageState參數包含了你在SaveState方法裡儲存的資料。這時你就可以恢複頁面的資料和狀態了。
- SaveState方法,當從當前頁導航進入其他頁的時候,會在當前頁調用SaveSate方法,這時候你需要把頁面的資料和狀態儲存到pageState中,供LoadState的時候使用儲存的 資料
我們以GroupedItemsPage為例,首先我們需要在SaveState裡面添加一些代碼:
protected override void SaveState(Dictionary<string, object> pageState) { base.SaveState(pageState); //把DefaultViewModel中的所有資料轉移到pageState中 foreach (var item in this.DefaultViewModel) { pageState[item.Key] = item.Value; } //儲存捲軸滾動狀態,這裡只儲存HorizontalOffset var scroll = itemGridView.GetVisualDescendants<ScrollViewer>().FirstOrDefault(); pageState["HorizontalOffset"] = scroll == null ? 0 : scroll.HorizontalOffset; }
然後在LoadState裡面添加如下代碼:
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState) { //pageState不為null,那麼表明是返回狀態,這時候需要恢複資料和狀態 if (pageState != null) { itemGridView.LayoutUpdated += itemGridView_LayoutUpdated; if (pageState != null) { foreach (var item in pageState) { this.DefaultViewModel[item.Key] = item.Value; } } } else { // pageState為null,表明是第一次載入頁面,需要初始化資料 var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter); this.DefaultViewModel["Groups"] = sampleDataGroups; } }
後面添加itemGridView_LayoutUpdated事件的方法:
//頁面的捲軸 ScrollViewer scroll; void itemGridView_LayoutUpdated(object sender, object e) { if (scroll == null) scroll = itemGridView.GetVisualDescendants<ScrollViewer>().FirstOrDefault(); if (scroll != null) { if (this.DefaultViewModel.ContainsKey("HorizontalOffset")) { double d = (double)this.DefaultViewModel["HorizontalOffset"]; scroll.ScrollToHorizontalOffset(d); //捲軸的狀態恢複不是一下子就恢複的,可能需要多次調用ScrollToHorizontalOffset才能準確恢複 if (d == scroll.HorizontalOffset) itemGridView.LayoutUpdated -= itemGridView_LayoutUpdated; } else//如果DefaultViewModel中沒有HorizontalOffset資料,那麼就不需要恢複捲軸狀態了 itemGridView.LayoutUpdated -= itemGridView_LayoutUpdated; } }
添加完這些代碼就可以直接運行了,這時你會發現,不管是頁面資料還是頁面狀態都能準確的恢複到上一次的狀態。
上面代碼中涉及到的GetVisualDescendants方法是一個擴充方法,具體代碼如下:
public static class VisualTreeExtensions { public static IEnumerable<T> GetVisualDescendants<T>(this DependencyObject element) where T : DependencyObject { return element.GetVisualDescendants().OfType<T>(); } /// <summary> /// 擷取某個元素的所有子孫節點 /// </summary> public static IEnumerable<DependencyObject> GetVisualDescendants(this DependencyObject element) { if (element == null) { throw new ArgumentNullException("element"); } return GetVisualDescendantsAndSelfIterator(element).Skip(1); } private static IEnumerable<DependencyObject> GetVisualDescendantsAndSelfIterator(DependencyObject element) { Queue<DependencyObject> remaining = new Queue<DependencyObject>(); remaining.Enqueue(element); while (remaining.Count > 0) { DependencyObject obj = remaining.Dequeue(); yield return obj; foreach (DependencyObject child in obj.GetVisualChildren()) { remaining.Enqueue(child); } } } public static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject element) { if (element == null) { throw new ArgumentNullException("element"); } return GetVisualChildrenAndSelfIterator(element).Skip(1); } private static IEnumerable<DependencyObject> GetVisualChildrenAndSelfIterator(this DependencyObject element) { yield return element; int count = VisualTreeHelper.GetChildrenCount(element); for (int i = 0; i < count; i++) { yield return VisualTreeHelper.GetChild(element, i); } } }