现在的位置: 首页 > 综合 > 正文

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

2013年10月15日 ⁄ 综合 ⁄ 共 12133字 ⁄ 字号 评论关闭

转自: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。

介绍导航历史记录或后退堆栈


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

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

BackStack stack representation

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

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

大多数应用都无需处理后退堆栈,并且在默认导航中可以发挥完整功能。其它应用则需要调整导航历史记录,以提供最佳用户体验。例如,应用中可能有一个登录页面。您可能不希望用户在登录后能够导航回登录页面 。

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

 

创建示例应用以可视化后退堆栈


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

BackStack Initial UI

上图中的灰色区域是示例应用中的后退堆栈可视化。在导航应用时,该列表将使用导航历史记录中的条目进行填充。您可以使用“Pop Last”“Pop To Selected”按钮来更改导航历史记录。

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

有关 Windows Phone 应用剖析的更多信息,请参见 Windows Phone 应用内导航

 

创建示例应用


使后退堆栈视化

  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,用于显示后退堆栈中的每个条目以及操作后退堆栈的一些按键。

    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>
    
    
  7. 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
    
    
  8. 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(); }); };
    
    
  9. 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;
       }
    }
    
    
  10. 将以下方法添加到 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();
    }
    
    
  11. 将以下方法添加到 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.xamlPage1.xamlPage2.xamlPage3.xaml。每个页面的结构都相同并使用相同的 UI。因此,将对每个页面重复以下步骤。在此示例中,未尝试通过帮助器方法或通过包装UserControl
中的 UI 重新使用代码。

向应用添加页面

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

  2. 首先,定义页面 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>
    
    
  3. 为 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>
    
    
  4. 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;
    
    
  5. 在类构造函数的 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";
    
    
  6. 添加以下方法以重写 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;
    }
    
    
  7. 添加以下方法以处理该页面上“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));
         }
    }
    
    
  8. 添加以下方法以处理该页上“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 按键可以改善用户的体验,因为这减少了用户返回应用开始位置所需执行的点按次数。

  9. 上述步骤介绍如何添加页面和更新页面以便在此应用中使用。要完成应用,请为以下所示的主页、页面 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

Solution view for BackStack HowTo

 

测试应用


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

测试应用的步骤

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

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

    BackStack Initial UI

  3. 点按“Next Page”按键。观察对“page 1”的页更改,后退堆栈列表现在包含“/MainPage.xaml”

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

    Interim UI for BackStack HowTo

  5. 点按“Pop Last”按键。将从后退堆栈列表中删除“/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”上的“Next Page”按键。此时将显示“page 2”页。后退堆栈包含两个条目:“/Page1.xaml”“/MainPage.xaml”

  9. 点按“page 2”上的“Pin To Start”

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

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

    State of BackStack when navigation from Tile.

 

抱歉!评论已关闭.