Win8的导航的实在是让我有点郁闷,尤其是像我这样原来做过WP7开发的真的一时难以适应。
Win8导航的问题,目前一共有两个:
下面针对第一个问题进行讨论。首先如果你的页面不需要根据不同的参数显示不同的数据的,那么完全可以启用页面缓存(页面的NavigationCacheMode设置成Enabled或者Required)。如果你的页面需要根据不同的参数显示不同数据,那么作为开发者要做的事情比起WP7的导航模型来说要多了,首先是页面数据的保存,其次是页面状态的保存(如果有滚动条你得保存滚动条的状态,如果有文本框,你得保存文本框中的文本…..)。现在拿VS2012中的自带的模版做个列子。
首先从项目模版中选择Grid APP模版,建立项目以后直接运行。进入首页,滑动滚动条,滑倒最后,然后随便选择一项进入该项详细信息页,然后返回,这时你会发现页面回到的初始状态,滚动条也回到了原点。现在解释下为什么会这样。
首先页面没有启用缓存,那么每次返回的时候页面总是会刷新,如何刷新的?其实就是返回的时候系统重新调用InitializeComponent方法,原来所有的数据和状态都不复存在,就像一个全新的页面一样(其实你应该把它当成一个全新的页面)。
如果只能这样的话我们肯定会抓狂,还好微软留了两个方法,LoadState和SaveState,其实这两个方法是项目模版中自定义的方法。
我们以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); } } }