转自:http://msdn.microsoft.com/zh-cn/library/windowsphone/develop/ff402561(v=vs.105).aspx
源码:Navigate Using the Back Stack for Windows Phone.zip
适用于: Windows Phone 8 | Windows Phone OS 7.1
本主题介绍如何通过操作应用的导航历史记录(称为后退堆栈),以便修改其导航。您可以使用 NavigationService API 来检查及处理导航历史记录。本主题将使用NavigationService 类的属性和方法来检测后退堆栈、删除条目,然后观察这些更改对应用导航所产生的影响。
注意: |
---|
在本主题中,导航历史记录一词和后退堆栈一词可换用,它们都是指由NavigationService.BackStack 属性公开的导航历史记录。 |
演示该功能的应用的完整示例可以下载。有关更多信息,请参阅使用 Windows Phone 的后退堆栈进行导航。这一可下载的示例面向 Windows Phone OS 7.1。此主题中的代码已调整为面向 Windows Phone 8。
应用的导航历史记录表示为后进先出结构,称为堆栈。此处该结构还称为后退堆栈,因为它在表示应用后退导航的堆栈结构中,包含一组页面。
可以将该堆栈看成是一叠盘子。添加到该堆栈的最后一个盘子就是可以移除的第一个盘子。最新项被添加到此堆栈的顶部。此操作称为推送操作。通过从堆栈顶部一次删除一个项目,可以检索堆栈中的某些内容。从堆栈中删除顶部项的操作称为弹出操作。下图显示了堆栈的概念。
当应用中的页面调用 Navigate 时,当前页面会被放到后退堆栈上,并且系统将创建并显示目标页的新实例。当您在应用的页面之间进行导航时,系统会将多个条目添加到此堆栈。
当页面调用 GoBack 时,或者当用户按手机的“返回”按键时,将放弃当前页面,并将堆栈顶部的页面从后退堆栈中弹出并进行显示。此后退导航会继续弹出并显示,直到堆栈中不再有条目。此时,点按手机的“返回”按钮将退出应用。
大多数应用都无需处理后退堆栈,并且在默认导航中可以发挥完整功能。其它应用则需要调整导航历史记录,以提供最佳用户体验。例如,应用中可能有一个登录页面。您可能不希望用户在登录后能够导航回登录页面 。
本主题演示如何使用 BackStack 属性和RemoveBackEntry 方法操作导航历史记录。
本节介绍如何在应用中使导航历史记录或后退堆栈可视化,以便在应用运行时轻松地对其进行检测。示例应用包含多个页面。从一个页面导航到下一个页面时,您可以查看后退堆栈上有哪些条目。您还可以轻松移除条目,并查看后退堆栈中反映的这些更新。在此应用中,后退堆栈将显示为列表,如下图所示。
上图中的灰色区域是示例应用中的后退堆栈可视化。在导航应用时,该列表将使用导航历史记录中的条目进行填充。您可以使用“Pop Last”和“Pop To Selected”按钮来更改导航历史记录。
此灰色列表并不是在每个单独页面上都存在。而是会将该列表添加到应用的 RootFrame 中的某个单独位置。RootFrame 对象是与应用关联的PhoneApplicationFrame。每个应用都有一个RootFrame。当用户导航到该页面时,导航框架会将应用的每个页面或PhoneApplicationPage 的实例设置为框架的Content。在创建新 Windows Phone 应用时获取的RootFrame 对象的默认模板会显示应用页面和其他元素(例如该应用的系统托盘和应用栏)。在此示例中,您将创建一个模板以显示每个页面,但会在屏幕底部留下一些空间用于以列表形式显示后退堆栈。在逐页进行导航时,将更新此列表以反映应用的导航历史记录或后退堆栈的当前状态。
有关 Windows Phone 应用剖析的更多信息,请参见 Windows Phone 应用内导航。
使后退堆栈视化
-
在 Visual Studio 中,通过选择“文件 | 新建 | 项目”菜单命令创建一个新项目。
-
将显示“新建项目”窗口。展开“Visual C#”模板,然后选择“Windows Phone”模板。
-
选择 Windows Phone 应用 模板。在“名称”中填入您选择的名称。
-
单击“确定”。将显示 Windows Phone 平台选择对话框。选择面向的版本或接受默认版本。
-
单击“确定”。将创建一个新的项目,并且“MainPage.xaml”将在 Visual Studio 设计器窗口中打开。
-
下一步是更改应用的 RootFrame 使用的模板以使后退堆栈可视化。这是通过在 RootFrame 的自定义ControlTemplate 中放置ListBox 来实现的。在App.xaml 文件中,使用以下标记替换Application.Resources 条目。
此模板由两行网格组成。ContentPresenter 位于网格第一行。这是显示每个页面内容的地方。向第二行网格添加另一个网格以包含后退堆栈的 UI。此 UI 包含ListBox,用于显示后退堆栈中的每个条目以及操作后退堆栈的一些按键。
XAML<Application.Resources> <ControlTemplate x:Name="NewFrameTemplate"> <Grid x:Name="ClientArea"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ContentPresenter Grid.Row="0"/> <Border Grid.Row="1" BorderBrush="{StaticResource PhoneForegroundBrush}" BorderThickness="{StaticResource PhoneBorderThickness}" Height="300"> <Grid x:Name="ContentPanel" Background="{StaticResource PhoneSemitransparentBrush}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition /> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" x:Name="CurrentPage" Style="{StaticResource PhoneTextSubtleStyle}" HorizontalAlignment="Center"/> <ListBox Grid.Row="1" ItemsSource="{Binding}" x:Name="HistoryList" HorizontalAlignment="Center" Height="300"> <ListBox.ItemTemplate> <DataTemplate> <Border BorderBrush="{StaticResource PhoneForegroundBrush}" BorderThickness="{StaticResource PhoneBorderThickness}" Width="300" Margin="5" Background="DarkGray" HorizontalAlignment="Center"> <TextBlock Text="{Binding}"/> </Border> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center"> <Button Content="Pop Last" x:Name="btnPopLast" IsEnabled="false"/> <Button Content="Pop To Selected" x:Name="btnPopToSelected" IsEnabled="false"/> </StackPanel> </Grid> </Border> </Grid> </ControlTemplate> </Application.Resources>
-
在 App.xaml.cs 代码隐藏文件中,添加以下 using 语法。
C#using System.Windows.Controls;
然后,将以下声明添加到 App 类的顶部。将使用这些声明引用从自定义模板创建的 UI 元素。
C#// UI controls on the RootFrame template. ListBox historyListBox; // ListBox for listing the navigation history Button popLastButton; // Button to pop the newest entry from the back stack Button popToSelectedButton; // Button to pop all entries in the back stack up to the selected entry TextBlock currentPageTextBlock; // TextBlock to display the current page the user is on
-
在 App.xaml.cs 代码隐藏文件中,添加以下 using 语法。
C#using System.Windows.Media;
然后在构造函数中的 InitializePhoneApplication 调用后面添加以下代码行。首先,将RootFrame 的模板设置为NewFrameTemplate,这是您在步骤 6 中定义的模板名称。此处还挂钩模板上按键的事件处理程序。最后,为RootFrame 的Navigated 事件定义委托以更新历史记录。
请注意,事件处理程序尚未创建。
C#// Set the template for the RootFrame to the new template you created in the Application.Resources in App.xaml RootFrame.Template = Resources["NewFrameTemplate"] as ControlTemplate; RootFrame.ApplyTemplate(); popToSelectedButton = (VisualTreeHelper.GetChild(RootFrame, 0) as FrameworkElement).FindName("btnPopToSelected") as Button; popToSelectedButton.Click += new RoutedEventHandler(PopToSelectedButton_Click); popLastButton = (VisualTreeHelper.GetChild(RootFrame, 0) as FrameworkElement).FindName("btnPopLast") as Button; popLastButton.Click += new RoutedEventHandler(PopLastButton_Click); currentPageTextBlock = (VisualTreeHelper.GetChild(RootFrame, 0) as FrameworkElement).FindName("CurrentPage") as TextBlock; historyListBox = (VisualTreeHelper.GetChild(RootFrame, 0) as FrameworkElement).FindName("HistoryList") as ListBox; historyListBox.SelectionChanged += new SelectionChangedEventHandler(HistoryList_SelectionChanged); // Update the navigation history listbox whenever a navigation happens in the application RootFrame.Navigated += delegate { RootFrame.Dispatcher.BeginInvoke(delegate { UpdateHistory(); }); };
-
向 App.xaml.cs 代码隐藏文件添加以下方法。UpdateHistory 刷新导航后退堆栈的 UI。当在RootFrame 上激发Navigated 事件时调用此方法,每当在应用中发生导航时便会激发该事件。此方法循环访问BackStack 属性中的所有条目并将其添加到ListBox。它还显示当前页面的 URI。如果导航后退堆栈中存在多个条目,则会启用“Pop Last”按键。HistoryList_SelectionChanged 根据是否选择了导航历史记录列表中的项目,启用或禁用“Pop To Selected”按键。
C#/// <summary> /// Use the BackStack property to refresh the navigation history list box with the latest history. /// </summary> void UpdateHistory() { historyListBox.Items.Clear(); int i = 0; foreach (JournalEntry journalEntry in RootFrame) { historyListBox.Items.Insert(i, journalEntry.Source); i++; } currentPageTextBlock.Text = "[" + RootFrame.Source + "]"; if (popLastButton != null) { popLastButton.IsEnabled = (historyListBox.Items.Count > 0); } } /// <summary> /// Handle the SelectionChanged event for navigation history list. /// </summary> private void HistoryList_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (historyListBox != null && popToSelectedButton != null) { popToSelectedButton.IsEnabled = (historyListBox.SelectedItems.Count > 0) ? true : false; } }
-
将以下方法添加到 App.xaml.cs 代码隐藏文件。这会处理“Pop Last”按键的点按事件。当用户点按此按键时,将删除添加到后退堆栈中的最后一个条目。此方法使用RootFrame 上的RemoveBackEntry 方法。在删除条目后,通过调用UpdateHistory 刷新后退堆栈条目列表。
C#/// <summary> /// Remove the last entry from the back stack. /// </summary> private void PopLastButton_Click(object sender, RoutedEventArgs e) { RootFrame.RemoveBackEntry(); // Refresh the history list since the back stack has been modified. UpdateHistory(); }
-
将以下方法添加到 App.xaml.cs 代码隐藏文件。如果用户选择后退堆栈中的某一项并点按“Pop To Selected”,则从后退堆栈中删除选定条目前的所有条目。RootFrame 上的RemoveBackEntry 方法用于删除后退堆栈中的每个条目。在删除条目之后,通过调用UpdateHistory 刷新后退堆栈 UI。
C#/// <summary> /// Remove all entries from the back stack up to the selected item, but not including it. /// </summary> private void PopToSelectedButton_Click(object sender, RoutedEventArgs e) { // Make sure something has been selected. if (historyListBox != null && historyListBox.SelectedIndex >= 0) { for (int i = 0; i < historyListBox.SelectedIndex; i++) { RootFrame.RemoveBackEntry(); } // Refresh the history list since the back stack has been modified. UpdateHistory(); } }
本节介绍如何为应用的 RootFrame 添加自定义模板,以便在导航应用时可以检测并修改后退堆栈。为了演示此功能,您必须在应用中添加多个页面。这将在下一节中介绍。
为了演示导航历史记录检测和操作,您必须在应用中添加多个页面。本节介绍如何将这些页面添加到应用。此示例包括四个页面:MainPage.xaml、Page1.xaml、Page2.xaml 和Page3.xaml。每个页面的结构都相同并使用相同的 UI。因此,将对每个页面重复以下步骤。在此示例中,未尝试通过帮助器方法或通过包装UserControl 中的 UI 重新使用代码。
向应用添加页面
-
通过选择“项目 | 添加新项”菜单命令向项目添加一个新页面。将显示“添加新项”窗口。在项目列表中选择“Windows Phone 纵向页面”并在“名称”字段中键入Page1.xaml。单击“添加”以将新页面添加到您的项目。现在名为 Page1.xaml 的新页面已添加到项目中。
-
首先,定义页面 UI。在 Page1.xaml 中,使用以下代码替换名为 ContentPanel 的网格。这会创建一个用于切换是否将页面固定到“开始”屏幕的 CheckBox 和一个用于导航到应用中下一页(如果下一页存在)的Button。将页面固定到手机的“开始”屏幕是一件非常有趣的事情,在此演示此过程是为了显示点按“开始”屏幕上页面“图块”以启动应用时后退堆栈的状态。以下代码中显示的事件处理程序将在后续步骤中进行介绍。
XAML<!--ContentPanel - place additional content here--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <CheckBox x:Name="PinToStartCheckBox" Content="Pin To Start" IsChecked="False" HorizontalAlignment="Center" VerticalAlignment="Center" Click="PinToStartCheckBox_Click"/> <Button x:Name="btnNext" Content="Next Page" Height="80" VerticalAlignment="Bottom" Click="btnNext_Click"/> </Grid>
-
为 2 个 XAML TextBlock 控件指定名称,以便能够以编程方式设置它们的值。
XAML<StackPanel Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="AppName" Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/> <TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/> </StackPanel>
-
在 Page1.xaml.cs 代码隐藏文件中,将下列变量声明添加到类的顶部。
C#// The URI string of the next page to navigate to from this page. // String.Empty here means that there is no next page. private string nextPage;
-
在类构造函数的 InitializeComponent() 之后添加以下代码行。
注意: 为其他几个页面重复此过程的步骤。对于您添加的每个附加页面,切记在此代码中更新页面标题和 nextPage 变量的值。
C#// Set the application title - use the same application title on each page. AppName.Text = "SDK BACKSTACK SAMPLE"; // Set a unique page title. In this example, you will use "page 1", "page 2", and so on. PageTitle.Text = "page 1"; // Set the URI string of the next page, or String.Empty if there is no next page. nextPage = "/Page2.xaml";
-
添加以下方法以重写 OnNavigatedTo 事件处理程序。如果此页设置了nextPage 变量,则此处会显示“Next Page”按键。根据此页面的“图块”是否存在设置“Pin To Start”复选框。
C#protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { base.OnNavigatedTo(e); // Show the Next button, if you have defined a next page. btnNext.Visibility = (String.IsNullOrWhiteSpace(nextPage)) ? Visibility.Collapsed : Visibility.Visible; if (ShellTile.ActiveTiles.FirstOrDefault(o => o.NavigationUri.ToString().Contains(NavigationService.Source.ToString())) == null) PinToStartCheckBox.IsChecked = false; else PinToStartCheckBox.IsChecked = true; }
-
添加以下方法以处理该页面上“Next Page”按键的 Click 事件。如果已经为此页定义了nextpage 变量,则调用 Navigate 方法以导航到该页面。
C#/// <summary> /// Navigate to the next page. /// </summary> private void btnNext_Click(object sender, RoutedEventArgs e) { // Make sure to attempt navigation only if you have defined a next page. if (!String.IsNullOrWhiteSpace(nextPage)) { this.NavigationService.Navigate(new Uri(nextPage, UriKind.Relative)); } }
-
添加以下方法以处理该页上“Pin To Start”复选框的 Click 事件。该操作类似于切换操作。如果此页的“图块”已经存在于“开始”屏幕上,则将其删除。如果“开始”屏幕上不存在此页的“图块”,则添加一个图块。有关“图块”的更多信息,请参见Windows Phone 的图块。
C#/// <summary> /// Toggle pinning a Tile for this page on the Start screen. /// </summary> private void PinToStartCheckBox_Click(object sender, RoutedEventArgs e) { // Try to find a Tile that has this page's URI. ShellTile tile = ShellTile.ActiveTiles.FirstOrDefault(o => o.NavigationUri.ToString().Contains(NavigationService.Source.ToString())); if (tile == null) { // No Tile was found, so add one for this page. StandardTileData tileData = new StandardTileData { Title = PageTitle.Text }; ShellTile.Create(new Uri(NavigationService.Source.ToString(), UriKind.Relative), tileData); } else { // A Tile was found, so remove it. tile.Delete(); } }
注意: 如果您的应用允许用户固定页面,请考虑是否需要使用“Home”按键来允许用户快速返回到应用的根目录。Home 按键将导航到应用的主页,然后清除整个导航后退堆栈。检查每种导航方案并确定是否需要此功能。
-
如果固定的页面是自包含页面(例如联系人信息页面),则用户可能需要点按该页面、查看信息,然后退出应用。在这种情况下,可以使用硬件“返回”按键来退出应用。
-
如果固定的页面是用户从中执行深入导航应用的入口点,则可能需要返回应用根目录的快捷方法。例如,如果固定的页面是购物车,则用户可能要在购物车中完成购买,然后再次开始购物。在此情况下,为用户提供一个 Home 按键可以改善用户的体验,因为这减少了用户返回应用开始位置所需执行的点按次数。
-
-
上述步骤介绍如何添加页面和更新页面以便在此应用中使用。要完成应用,请为以下所示的主页、页面 2 和页面 3 重复这些步骤。
页面名称
新建页面?
步骤 5 的更改
MainPage.xaml
否。此页在创建应用时创建。对此页重复步骤 2 至 8。
PageTitle.Text = “main”;
nextPage = “/Page1.xaml”;
Page2.xaml
是。对此页重复步骤 1 至 8。
PageTitle.Text = “page 2”;
nextPage = “/Page3.xaml”;
Page3.xaml
是。对此页重复步骤 1 至 8。
PageTitle.Text = “page 3”;
nextPage = String.Empty
为下一页指定 String.Empty,因为页面 3 是应用中的最后一页。不能从页面 3 继续向前导航。
完成本节后,解决方案资源管理器中的解决方案如下图所示。该应用有四个页面,在这些页面之间的前进导航如下所示:MainPage.xaml -> Page1.xaml -> Page2.xaml -> Page3.xaml。
本节介绍如何运行本主题中生成的应用。
测试应用的步骤
-
通过选择“调试 | 启动调试”菜单命令运行应用。
-
将启动应用,并显示 MainPage.xaml 页。该页如下图所示。屏幕下半部分,显示后退堆栈可视化。此时导航历史记录中没有任何内容,因此列表为空。当前页显示为“[/MainPage.xaml]”
-
点按“Next Page”按键。观察对“page 1”的页更改,后退堆栈列表现在包含“/MainPage.xaml”。
-
再次点按“Next Page”按键。当前页面为“page 2”。后退堆栈现在包含两个条目:/Page1.xaml 和/MainPage.xaml。作为堆栈,它会将最新条目显示在顶部并将最旧条目显示在底部。下图对此进行了阐释。
-
点按“Pop Last”按键。将从后退堆栈列表中删除“/Page1.xaml”条目。
-
点按的手机上的硬件“返回”按键。将显示“main page”,并且后退堆栈为空。发生这种情况是因为我们在前一个步骤中从后退堆栈删除了“/Page1.xaml”,因此会将后退导航从“Page 2 -> Page1 -> MainPage”更改为“Page2 -> MainPage”。
-
点按“main page”上的“Next Page”按键。将显示“page 1”页。后退堆栈中有一个条目:“/MainPage.xaml”。
-
点按“page 1”上的“Next Page”按键。此时将显示“page 2”页。后退堆栈包含两个条目:“/Page1.xaml”和“/MainPage.xaml”。
-
点按“page 2”上的“Pin To Start”。
-
将在手机的“开始”屏幕上创建名为“page 2”的“图块”。
-
点按在上一个步骤中创建的“图块”。将启动该应用,并显示“page 2”。还请注意后退堆栈为空。如果在手机上点按“返回”按扭,则应用会终止。下图显示了此状态的一个示例。“page 2”被固定到手机的“开始”屏幕。点按“图块”后,应用将启动,并显示 Page2。此图形中显示的后退堆栈为空。