Windows 8 Metro開發疑難雜症(一)——導航


Win8的導航的實在是讓我有點郁悶,尤其是像我這樣原來做過WP7開發的真的一時難以適應。

Win8導航的問題,目前一共有兩個:

  1. 如果在不啟用頁面緩存的時候,每次返回的時候都會刷新頁面(在WP7中頁面的所有狀態自動保存,離開的時候什么樣的,返回的時候還是那樣),這就導致用戶狀態需要開發者自己去保存。你可能會說既然這樣可以啟動頁面緩存啊,是啊,你當然可以啟用頁面緩存,但是一旦啟用了頁面的緩存,你這個頁面同一時間只能有一個實例,這樣對於對應不同的參數顯示不同的數據的頁面來說就很困難了,因為你同一時間只有一個實例。
  2. 導航的時候用戶是可以傳遞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);
            }
        }

    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM