已經有一段時間沒有寫博客來記錄自己的學習點滴了。現在回想起來確實有些慚愧,期間經歷了一些事情,到目前為止算是平息了,是時候該收收心來充實自己了。
在本篇繆文中,樓主打算給UWP開發的初學者講述一個在開發中經常遇到的很現實的問題:頁面回退邏輯 。
眾所周知,UWP的應用程序理論上是可以運行在Windows上的各種設備上,其中包括Windows PC、WindowsMobile、XBox、IOT等。當我們的UWP應用程序運行在不同的設備上時,不同設備間的頁面回退邏輯我們就要考慮周全,要考慮不同設備間的頁面回退操作該如何設計才能更好的滿足用戶的使用需求。因此,我們有必要將不同設備間的頁面回退邏輯進行統一封裝,這樣一來不僅有利於代碼的維護,而且也有利於回退功能的擴充,實現了實現了“高內聚低耦合“。為了方便,樓主這里只簡單論述一下當我們的UWP應用程序運行在PC上和Mobile上時該如何處理不同平台的頁面回退邏輯。當應用程序運行在PC上時,頁面回退常常是通過用戶點擊應用程序提供的一個回退按鈕來進行頁面回退,但是當我們的應用程序運行在Mobile上時,用戶更願意使用手機設備上提供的物理后退鍵來進行頁面回退,這樣一來,我們就需要使用封裝的思想來進行封裝。
1、理論分析:
在新的MSDN中,微軟為我們提供了一套新的API:SystemNavigationManager 。當UWP應用程序在PC上運行的時候,通過此API,我們可以為應用程序提供一個回退按鈕來向用戶暗示此頁面是可以回退的,當用戶點擊該按鈕后,頁面成功回退。但是當我們的UWP應用程序運行在Mobile上時,如果還是用這種方法來進行頁面回退的的話,對用戶來說就可能不是很友好,因此,我們要投其說好,用手機設備上的物理后退鍵來實現相應的頁面回退邏輯,其對應的API為:HardwareButtons.BackPressed。分析到這,我們基本上明白該如何處理這兩中設備間的回退邏輯的差異。So,問題來了:我們該把這套邏輯放到哪里合適?何時使用這套邏輯較為合適? 這是兩道主觀題,仁者見仁智者見智。樓主這里拋磚引玉,為初學者論述一種方法。
由於應用程序剛啟動的時候會觸發App.OnLaunched()函數,所以我們需要修改OnLaunched()函數;其次,為了保證頁面的唯一性,我們這里使用“框架頁”的方法來承載不同的頁面,通過Frame來完成頁面的跳轉;最后,我們還需要實現一個用戶控件來方式應用程序的主題框架。
總結一句話就是:讓應用程序來加載我們的用戶控件,讓用戶控件來承載我們的框架頁,讓框架頁來完成應用程序的頁面跳轉。
是不是感覺很繞口??沒關系,接下來我們看看實際的代碼該如何寫………………
2、代碼實現:
首先:
我們需要為我們的應用程序創建一個頁面跳轉服務類:NavigationService,該類封裝頁面的回退邏輯。需要指出的是:由於SystemNavigationManager 可以實現不同平台的回退邏輯,因此我們沒必要再單獨將Mobile的物理后退鍵封裝(謝謝yan_xiaodi的糾正)。代碼很簡單,我相信你看一下就會的。

1 public class NavigationService 2 { 3 public static NavigationService Instance { get; protected set; } 4 5 private Frame frame; 6 7 public Stack<Type> PageStack { get; protected set; } 8 9 public NavigationService(Frame frame) 10 { 11 if (Instance != null) 12 { 13 throw new Exception("Only one navigation service can exist in a App."); 14 } 15 Instance = this; 16 this.frame = frame; 17 this.PageStack = new Stack<Type>(); 18 19 SystemNavigationManager.GetForCurrentView().BackRequested += NavigationService_BackRequested; 20 } 21 22 public void NavigateTo(Type pageType, object parameter) 23 { 24 if (PageStack.Count > 0) 25 { 26 //返回位於Stack頂部的對象但不將其移除。 27 if (PageStack.Peek() == pageType) 28 { 29 return; 30 } 31 } 32 PageStack.Push(pageType); 33 frame.Navigate(pageType, parameter); 34 UpdateBackButtonVisibility(); 35 } 36 37 public void NavigateToHome() 38 { 39 var type = frame.BackStack.FirstOrDefault().SourcePageType; 40 frame.Navigate(type, null); 41 frame.BackStack.Clear(); 42 UpdatePageStack(); 43 UpdateBackButtonVisibility(); 44 } 45 private void UpdatePageStack() 46 { 47 if (PageStack.Count > 0) 48 { 49 PageStack.Pop(); 50 } 51 } 52 53 private void UpdateBackButtonVisibility() 54 { 55 SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = frame.CanGoBack ? 56 AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed; 57 } 58 private void NavigationService_BackRequested(object sender, BackRequestedEventArgs e) 59 { 60 if (frame.CanGoBack) 61 { 62 frame.GoBack(); 63 UpdatePageStack(); 64 e.Handled = true; 65 } 66 UpdateBackButtonVisibility(); 67 } 68 }
其次:
頁面跳轉服務類算是已經封裝完成,接下來我們就需要創建一個用戶控件來承載應用程序的主體框架。

1 <UserControl 2 x:Class="NavigationDemo.ShellView" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="using:NavigationDemo" 6 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 mc:Ignorable="d" 9 d:DesignHeight="300" 10 d:DesignWidth="400"> 11 <UserControl.Resources> 12 <x:String x:Key="home">首頁</x:String> 13 <x:String x:Key="article">文章</x:String> 14 <x:String x:Key="question">問題</x:String> 15 <x:String x:Key="thing">東西</x:String> 16 </UserControl.Resources> 17 <Grid> 18 <SplitView IsPaneOpen="True" DisplayMode="CompactInline" OpenPaneLength="100"> 19 <SplitView.Pane> 20 <ListView> 21 <ListViewItem x:Name="homeCmd" Content="{StaticResource home}"> 22 <ListViewItem x:Name="articleCmd" Content="{StaticResource article}"/> 23 <ListViewItem x:Name="questionCmd" Content="{StaticResource question}"/> 24 <ListViewItem x:Name="thingCmd" Content="{StaticResource thing}"/> 25 </ListView> 26 </SplitView.Pane> 27 <SplitView.Content> 28 <Frame x:Name="frame" x:FieldModifier="public"/> 29 </SplitView.Content> 30 </SplitView> 31 32 </Grid> 33 </UserControl>
然后:
主體框架控件已經設計完成,接下來我們就修改改造App類。我們需要為應用程序提供一個全局的頁面跳轉,這樣方便使用;其次我們需要將應用程序的初始頁面改造為一個用戶控件,這樣就保證引用程序始終加載的是一個用戶控件。

1 public static NavigationService NavService { get; set; } 2 protected override void OnLaunched(LaunchActivatedEventArgs e) 3 { 4 //ShellView是我們創建的用戶控件 5 ShellView rootFrame = Window.Current.Content as ShellView; 6 7 // Do not repeat app initialization when the Window already has content, 8 // just ensure that the window is active 9 if (rootFrame == null) 10 { 11 // Create a Frame to act as the navigation context and navigate to the first page 12 rootFrame = new ShellView(); 13 if (rootFrame.frame == null)//frame是我們在用戶控件中創建的框架頁面 14 { 15 rootFrame.frame = new Frame(); 16 } 17 NavService = new NavigationService(rootFrame.frame); 18 19 rootFrame.frame.NavigationFailed += OnNavigationFailed; 20 21 if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) 22 { 23 //TODO: Load state from previously suspended application 24 } 25 26 // Place the frame in the current Window 27 Window.Current.Content = rootFrame; 28 } 29 30 if (rootFrame.frame.Content == null) 31 { 32 // When the navigation stack isn't restored navigate to the first page, 33 // configuring the new page by passing required information as a navigation 34 // parameter 35 //確保應用程序初始加載的是指定的首頁 36 NavService.NavigateTo(typeof(MainPage), e.Arguments); 37 } 38 // Ensure the current window is active 39 Window.Current.Activate(); 40 }
最后:
代碼敲到這兒算是已經完成的差不多,現在萬事俱備,只欠東風,注冊我們的跳轉事件,我這里只簡單跳轉4個頁面,腦洞大的朋友可以多設計幾個。在我們的用戶控件對應的后台代碼中為應用程序的全局菜單注冊頁面跳轉事件。

1 private void homeCmd_Tapped(object sender, TappedRoutedEventArgs e) 2 { 3 App.NavService.NavigateToHome(); 4 } 5 6 private void articleCmd_Tapped(object sender, TappedRoutedEventArgs e) 7 { 8 App.NavService.NavigateTo(typeof(BlankPage1), null); 9 } 10 11 private void questionCmd_Tapped(object sender, TappedRoutedEventArgs e) 12 { 13 App.NavService.NavigateTo(typeof(BlankPage2), null); 14 } 15 16 private void thingCmd_Tapped(object sender, TappedRoutedEventArgs e) 17 { 18 App.NavService.NavigateTo(typeof(BlankPage3), null); 19 }
好了,寫到這了算是已經大功告成了。我們還是看一下實際的運行效果吧。
這是在PC上運行的效果,在手機上運行的效果和這類似,但是頁面回退是使用物理后退鍵來完成的,感興趣的朋友可以自行嘗試一下。
需要指出的是,如果你在手機上運行的話,你會發現這種方法會給你額外贈送一個彩蛋:當我們需要對系統標題欄的顏色進行設置的時候,我們完全可以在我們的用戶控件中實現,哪怕我們需要填充一種圖片或者其他復雜的元素都可以通過簡單幾行XAML代碼都是可以搞定的。
3、總結:
這種處理方法不知能否滿足各位的某種實際需求? 需求千千萬,代碼改不斷,所以作為一個程序猿,我們不僅要提高我們的編碼能力,同時解決問題的能力也要不斷提高。這里簡要總結一下使用到的知識:
封裝的思想;
用戶控件;
框架頁;
好像也沒啥了:)
廢話說來這么多,不知各位看官是否看懂???俗話說得好:實踐出真知。所以建議感興趣的朋友還是親自嘗試一下比較好。
再次感謝 youngytj,Leandro指正代碼中寫的不好的部分,歡迎各位大大繼續拍磚。代碼已修改。