导出 (0) 打印
全部展开
信息
您所需的主题如下所示。但此主题未包含在此库中。

如何使用 Windows Phone 8 的后退堆栈进行导航

2014/6/18

适用于:Windows Phone 8 和 Windows Phone Silverlight 8.1 | Windows Phone OS 7.1

 

本主题介绍如何通过操作应用的导航历史记录(称为后退堆栈)来修改其导航。您可以使用 NavigationService API 来检查和使用导航历史记录。本主题将使用 NavigationService 类的属性和方法来检测后退堆栈、删除条目,然后观察这些更改在应用程序导航上产生的效果。

说明注意:

在本主题中,导航历史记录一词和和后退堆栈一词可互换使用,它们都是指由 NavigationService.BackStack 属性公开的导航历史记录。

演示该功能的应用的完整示例可供下载。有关更多信息,请参见使用 Windows Phone 的后退堆栈导航。这个可下载的示例面向 Windows Phone OS 7.1。本主题中的代码已经过调整,以面向 Windows Phone 8。

本主题包括以下部分。

 

应用的导航历史记录表示为后进先出结构,称为堆栈。在这里,该结构还称为后退堆栈,因为它包含表示应用后退导航的堆栈结构中的一组页面。

可以将该堆栈看成是一叠盘子。添加到该堆栈的最后一个盘子是可以移除的第一个盘子。最新项被添加到该堆栈的顶部。此操作称为推送操作。通过从堆栈顶部一次删除一个项目,可以检索堆栈中的某些内容。删除堆栈顶部项的操作称为弹出操作。下图显示了堆栈这一概念。

BackStack stack representation

当应用中的页面调用 Navigate 时,当前页面会被放到后退堆栈上,并且系统将创建并显示目标页的新实例。当您在应用的页面之间进行导航时,系统会将多个条目添加到此堆栈。

当页面调用 GoBack 时,或者当用户按手机的“返回”按钮时,将放弃当前页面,并将堆栈顶部的页面从后退堆栈中弹出并进行显示。此后退导航会继续弹出并显示,直到堆栈中不再有条目。此时,点按手机的“返回”按扭将退出该应用。

大多数应用从来不必操作后退堆栈即可使用默认导航完美地运行。其他应用需要调整导航历史记录,以便提供最佳的用户体验。例如,您的应用可能有一个登录页面。用户登录后,您可能不希望用户能够导航回登录页面。

本主题演示如何使用 BackStack 属性和 RemoveBackEntry 方法操作导航历史记录。

本节介绍如何在应用中使导航历史记录或后退堆栈可视化,以便您可以在应用运行时轻松地检查它。此示例应用包含多个页面。当从一个页面导航到下一个页面时,您可以查看后退堆栈上的条目。您也可以轻松地删除条目并查看这些在后退堆栈中反映的更新。在此应用中,后退堆栈将显示为一个列表,如下图所示。

BackStack Initial UI

上图中的灰色区域是示例应用中后退堆栈的可视化效果。当您导航应用时,该列表将使用导航历史记录中的条目进行填充。您可以使用“弹出上一个”“弹出至所选项”按钮更改导航历史记录。

该灰色列表区域并未在每个单独页面上进行编码。而是会将该列表添加到应用的 RootFrame 的单个位置中。RootFrame 对象是与应用关联的 PhoneApplicationFrame。每个应用都有一个 RootFrame。当用户导航到该页面时,导航框架会将应用的每个页面或 PhoneApplicationPage 的实例设置为框架的 Content。在创建新 Windows Phone 应用时获取的 RootFrame 对象的默认模板会显示应用页面和其他元素(例如该应用的系统托盘和应用栏)。在此示例中,您将创建一个模板,显示每个页面,但也会在屏幕底部留下一些空间用于以列表形式显示后退堆栈。在逐页进行导航时,将更新该列表以反映应用的导航历史记录或后退堆栈的当前状态。

有关 Windows Phone 应用剖析的更多信息,请参见 Windows Phone 8 的应用内购买

使后退堆栈视化

  1. 在 Visual Studio 中,通过选择“文件” | “新建” | “项目”菜单命令创建新的项目。

  2. 将显示“新建项目”窗口。展开“Visual C#”模板,然后选择“Windows Phone”模板。

  3. 选择 Windows Phone 应用  模板。用您选择的名称填写“名称”

  4. 单击“确定”。将显示 Windows Phone 平台选择对话框。选择面向的版本或接受默认版本。

  5. 单击“确定”。将创建一个新的项目,并且“MainPage.xaml”将在 Visual Studio 设计器窗口中打开。

  6. 下一步是更改应用的 RootFrame 使用的模板以使后退堆栈可视化。这是通过在 RootFrame 的自定义 ControlTemplate 中放置 ListBox来实现的。在 App.xaml 文件中,将以下标记添加到 Application.Resources 条目。

    此模板由两行网格组成。ContentPresenter 位于网格第一行。这是显示每个页面内容的地方。向第二行网格添加另一个网格以包含后退堆栈的 UI。此 UI 包含 ListBox,用于显示后退堆栈中的每个条目以及操作后退堆栈的一些按钮。

    
    <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>
    
    
  7. App.xaml.cs 代码隐藏文件中,添加下面的 using 语句。

    using System.Windows.Controls;
    

    然后,将下列声明添加到 App 类的顶部。将使用这些声明引用从自定义模板创建的 UI 元素。

    
    // 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
    
    
  8. App.xaml.cs 代码隐藏文件中,添加下面的 using 语句。

    using System.Windows.Media;
    

    然后,在调用 InitializePhoneApplication 之后,直接将以下代码行添加在构造函数中。首先,用于 RootFrame 的模板设置为 NewFrameTemplate,这是您在步骤 6 中定义的模板名称。用于模板按钮的事件处理程序也在此处挂钩。最后,为 RootFrameNavigated 事件定义委托以更新历史记录。

    请注意,尚未创建事件处理程序。

    
    // 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(); }); };
    
    
  9. App.xaml.cs 隐藏文件添加以下方法。UpdateHistory 刷新导航后退堆栈的 UI。当在 RootFrame 上激发 Navigated 事件时调用此方法,每当在应用中发生导航时便会激发该事件。此方法循环访问 BackStack 属性中的所有条目并将其添加到 ListBox。它还显示当前页面的 URI。如果导航后退堆栈中有条目,“弹出上一个”按钮将处于启用状态。HistoryList_SelectionChanged 根据导航历史记录列表中是否有选定项来启用或禁用“弹出至所选项”按钮。

    
    /// <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.BackStack)
       {
          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;
       }
    }
    
    
  10. App.xaml.cs 隐藏文件添加以下方法。这会处理“弹出上一个”按钮的点按事件。当用户点按此按钮时,将删除添加到后退堆栈中的最后一个条目。此方法使用 RootFrame 上的 RemoveBackEntry 方法。在删除条目后,通过调用 UpdateHistory 刷新后退堆栈条目列表。

    
    /// <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();
    }
    
    
  11. App.xaml.cs 隐藏文件添加以下方法。如果用户选择后退堆栈中的某一项并点按“弹出至所选项”,则从后退堆栈中删除选定条目前的所有条目。RootFrame 上的 RemoveBackEntry 方法用于删除后退堆栈中的每个条目。在删除条目之后,通过调用 UpdateHistory 刷新后退堆栈 UI。

    
    /// <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 重新使用代码。

向应用添加页面

  1. 通过选择“项目” | “添加新项”菜单命令向项目添加一个新页面。将显示“添加新项”窗口。 在待添加项列表中选择“Windows Phone 纵向页面”,并在“名称”字段中键入 Page1.xaml。单击“添加”以将新页面添加到您的项目。现在名为 Page1.xaml 的新页面已添加到项目中。

  2. 首先,定义页面 UI。在 Page1.xaml 中,使用以下代码替换名为 ContentPanel 的网格。这会创建一个用于切换是否将页面固定到“开始”屏幕的 CheckBox和一个用于导航到应用中下一页(如果下一页存在)的 Button。将页面固定到手机的“开始”屏幕是一件非常有趣的事情,在此演示此过程是为了显示点按“开始”屏幕上页面“磁贴”以启动应用时后退堆栈的状态。以下代码中显示的事件处理程序将在后续步骤中进行介绍。

    
    <!--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>
    
    
  3. 将名称分配给 2 XAML TextBlock 控件,以便通过编程方式设置其值。

            <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>
    
    
  4. Page1.xaml.cs 代码隐藏文件中,在类顶部添加以下变量声明。

    
    // 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;
    
    
  5. 在类构造函数的 InitializeComponent() 之后添加以下代码行。

    说明注意:

    此过程中的步骤将在多个其他页面中重复。对于每个添加的其他页面,切记在此代码中更新页面标题和 nextPage 变量的值。

    
    // 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";
    
    
  6. 添加以下方法以重写 OnNavigatedTo 事件处理程序。如果此页设置了 nextPage 变量,则此处会显示“下一页”按钮。根据此页面的磁贴是否存在设置“固定到‘开始’屏幕”复选框。

    
    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;
    }
    
    
  7. 添加以下方法以处理该页面上“下一页”按钮的 Click 事件。如果已经为此页定义了 nextpage 变量,则调用 Navigate 方法以导航到该页面。

    
    /// <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));
         }
    }
    
    
  8. 添加以下方法以处理该页上“固定到‘开始’屏幕”复选框的 Click 事件。该操作类似于切换操作。如果此页的磁贴已经存在于“开始”屏幕上,则将其删除。如果“开始”屏幕上不存在此页的磁贴,则添加一个磁贴。有关磁贴的更多信息,请参见 Windows Phone 8 的磁贴

    
    /// <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 按钮可以改善用户的体验,因为这减少了用户返回应用开始位置所需执行的点按次数。

  9. 上述步骤介绍如何添加页面和更新页面以便在此应用中使用。若要完成应用,请对 MainPage、Page2 和 Page3 重复以下步骤,如下所示。

    页名称

    新建页面?

    步骤 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,因为 page 3 是应用中的最后一页。您无法从 page 3 向前导航。

完成本节后,解决方案资源管理器中的解决方案如下图所示。该应用程序有四个页面,在这些页面之间的前进导航如下所示:MainPage.xaml - Page1.xaml - Page2.xaml - Page3.xaml

Solution view for BackStack HowTo

本节介绍如何运行本主题中生成的应用。

测试应用的步骤

  1. 通过选择“调试” | “启动调试”菜单命令运行应用。

  2. 将启动应用,并显示 MainPage.xaml 页。该页如下图所示。屏幕下半部分,显示后退堆栈可视化。此时导航历史记录中没有任何内容,因此列表为空。当前页显示为“[/MainPage.xaml]”

    BackStack Initial UI
  3. 点按“下一页”按钮。当前页面变为“page 1”,且后退堆栈列表现包含“/MainPage.xaml”

  4. 再次点按“下一页”按钮。当前页面为“page 2”。后退堆栈现在包含两个条目:“/Page1.xaml”“/MainPage.xaml”。作为堆栈,它会将最新条目显示在顶部并将最旧条目显示在底部。下图对此进行了阐释。

    Interim UI for BackStack HowTo
  5. 点按“弹出上一个”按钮。将从后退堆栈列表中删除“/Page1.xaml”条目。

  6. 点按的手机上的硬件“返回”按钮。将显示“main page”,并且后退堆栈为空。发生这种情况是因为您在前一个步骤中从后退堆栈删除了“/Page1.xaml”,因此会将后退导航从“Page 2 -> Page1 - MainPage”更改为“Page2 - MainPage”

  7. 点按“main page”上的“Next Page”按钮。将显示“page 1”页。后退堆栈中有一个条目:“/MainPage.xaml”

  8. 点按“page 1”上的“下一页”按钮。此时将显示“page 2”页。后退堆栈包含两个条目:“/Page1.xaml”“/MainPage.xaml”

  9. 点按“page 2”上的“固定到‘开始’屏幕”

  10. 将在手机的“开始”屏幕上创建名为“page 2”的磁贴。

  11. 点按在上一个步骤中创建的磁贴。将启动该应用,并显示“page 2”。还请注意后退堆栈为空。如果在手机上点按“返回”按钮,则应用会终止。下图显示了此层次结构的一个示例。page 2 已固定到设备的“开始”屏幕。点按磁贴后,应用将启动,并显示 Page2。此图形中显示的后退堆栈为空。

    State of BackStack when navigation from Tile.

显示:
© 2015 Microsoft