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); } } }
