情報
要求されたトピックは次のとおりです。しかし、このトピックはこのライブラリには含まれていません。

Windows Phone 8 のバック スタックを使用したナビゲーション方法

2014/06/18

対象: Windows Phone 8 および Windows Phone Silverlight 8.1 | Windows Phone OS 7.1

 

このトピックでは、バック スタックと呼ばれるナビゲーション履歴を操作することによって、アプリのナビゲーションを変更する方法を説明します。NavigationService API を使用して、ナビゲーション履歴を調べて操作することができます。このトピックでは、NavigationService クラスのプロパティとメソッドを使用して、バック スタックを検査し、エントリを削除して、それらの変更がアプリのナビゲーションに与える影響を示します。

メモメモ:

このトピック全体で使用している用語のナビゲーション履歴バック スタックは、いずれも NavigationService.BackStack プロパティで公開されるナビゲーション履歴を指しています。

この機能を示すアプリの完全なサンプルをダウンロードできます。詳細については、「Navigate Using the Back Stack for Windows Phone (Windows Phone のバック スタックを使用して操作する)」を参照してください。このダウンロード可能なサンプルは Windows Phone OS 7.1 を対象にしています。このトピックのコードは、Windows Phone 8 を対象にして採用されました。

このトピックは、次のセクションで構成されています。

 

アプリのナビゲーション履歴は、スタックと呼ばれる後入れ先出し構造で表されます。これは、アプリの戻るナビゲーションを表すスタック構造に一連のページを格納するため、ここではバック スタックとも呼びます。

このスタックを皿が積み重ねられたものと考えてみましょう。このスタックに最後に追加された皿は、取り除くことができる最初の皿となります。一番新しい項目は、このスタックの一番上に追加されます。これをプッシュ操作と言います。スタックから何かを取り出すには、スタックの一番上から 1 つずつ項目を削除していかなければなりません。スタックの一番上の項目を取り除くことを、ポップ操作と言います。スタックの考え方を、次の図に示します。

BackStack stack representation

アプリのページが Navigate を呼び出すと、現在のページがバック スタックに入れられ、宛先のページの新しいインスタンスが作成され、表示されます。アプリのページをナビゲートすると、エントリがこのスタックに追加されます。

ページが GoBack を呼び出すか、ユーザーが電話の [戻る] ボタンを押すと、現在のページが破棄され、スタックの一番上のページがバック スタックからポップされ、表示されます。この戻るナビゲーションは、スタックにエントリがなくなるまで続けられます。この時点で、電話の [戻る] ボタンをタップすると、アプリが終了します。

ほとんどのアプリはバック スタックを操作する必要がなく、既定のナビゲーションでまったく問題なく機能します。ただし、一部のアプリは最高の操作性を実現するため、ナビゲーション履歴を調整する必要があります。たとえば、アプリでログイン ページを使用するケースがあります。ユーザーのログイン後は、このページには戻ることができないようにした方がよいでしょう。

このトピックでは、BackStack プロパティと RemoveBackEntry メソッドを使用して、ナビゲーション履歴を操作する方法を示します。

このセクションでは、アプリでナビゲーション履歴またはバック スタックを視覚化し、アプリの実行中にバック スタックを簡単に検査できるようにする方法を説明します。サンプル アプリは複数のページで構成されています。次のページに移動する場合、バック スタックにどんなエントリがあるか確認したいと考えます。また、エントリを簡単に削除し、バック スタックで、反映されたそれらの更新を参照することもできます。このアプリのバック スタックは、次の図のようにリストとして表示されます。

BackStack Initial UI

上の図の灰色の領域はサンプル アプリのバック スタックを視覚化したものです。アプリ内でナビゲーションを行うと、このリストにナビゲーション履歴からエントリが入力されます。[Pop Last] ボタンと [Pop To Selected] ボタンを使用して、ナビゲーション履歴を変更できます。

この灰色のリスト領域は、個々のページにコーディングされません。代わりに、単一の場所のリストがアプリの RootFrame に追加されます。RootFrame オブジェクトはアプリに関連付けられている PhoneApplicationFrame です。アプリごとに 1 つの RootFrame があります。ユーザーがページに移動すると、ナビゲーション フレームワークによって、アプリの各ページまたは PhoneApplicationPage のインスタンスがこのフレームの Content として設定されます。新しい Windows Phone アプリの作成時に取得する RootFrame オブジェクトの既定のテンプレートに、アプリ ページとシステム トレイやアプリのアプリ バーなどのその他の要素が表示されます。この例では、各ページを表示しますが、画面の下部にバック スタックをリスト形式で表示するスペースも残します。ページからページに移動すると、このリストが更新され、アプリのナビゲーション履歴、またはバック スタックの現在の状態が反映されます。

Windows Phone アプリの構造の詳細については、「Windows Phone 8 アプリ内ナビゲーション」を参照してください。

バック スタックを視覚化するには

  1. Visual Studio で、[ファイル] メニューの [新規]、[プロジェクト] を順にクリックして新しいプロジェクトを作成します。

  2. [新しいプロジェクト] ウィンドウが表示されます。Visual C# のテンプレートを展開し、Windows Phone のテンプレートを選択します。

  3. Windows Phone アプリ のテンプレートを選択します。[名前] に選択した名前を入力します。

  4. [OK] をクリックします。Windows Phone プラットフォームの選択ダイアログ ボックスが表示されます。ターゲット バージョンを選択するか、既定の設定をそのまま使用します。

  5. [OK] をクリックします。新しいプロジェクトが作成され、Visual Studio のデザイナー ウィンドウに MainPage.xaml が表示されます。

  6. 次の手順では、アプリの [RootFrame] によって使用されるテンプレートを変更して、バック スタックを視覚化します。これは RootFrame のカスタム ControlTemplateListBox を配置して実行します。App.xaml ファイルで、Application.Resources エントリに次のマークアップを追加します。

    テンプレートは 2 行のグリッドから構成されます。ContentPresenter はグリッドの最初の行にあります。ここには、各ページのコンテンツが表示されます。別のグリッドをこのグリッドの 2 行目に追加し、バック スタックの 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 を更新します。これは、RootFrameNavigated イベントが発生した場合に呼び出されます。このイベントはアプリで何らかのナビゲーションが行われた場合に発生します。このメソッドは BackStack プロパティのすべてのエントリを反復処理し、それらを ListBox に追加します。さらに、現在のページの URI も表示します。ナビゲーション バック スタックにエントリがある場合、[Pop Last] ボタンが有効になります。HistoryList_SelectionChanged は、ナビゲーション履歴リスト内の項目が選択されたかどうかに応じて、[Pop To Selected] ボタンを有効または無効にします。

    
    /// <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 分離コード ファイルに次のメソッドを追加します。これは、[Pop Last] ボタンのクリック イベントを処理します。ユーザーがこのボタンをタップすると、バック スタックに最後に追加されたエントリが削除されます。このメソッドは、RootFrameRemoveBackEntry メソッドを使用します。エントリの削除後、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 分離コード ファイルに次のメソッドを追加します。ユーザーがバック スタックの項目を選択し、[Pop To Selected] をタップすると、選択したエントリまでのすべてのエントリが、バック スタックから削除されます。RootFrameRemoveBackEntry メソッドを使用して、バック スタックから各エントリが削除されます。エントリの削除後、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] の 4 ページがあります。各ページは同じように構築され、同じ UI を使用します。結果として、各ページで次の手順を繰り返します。この例では、ヘルパー メソッドを使用したり、UserControl で UI をラップしたりして、コードの再利用を試みていません。

アプリにページを追加するには

  1. [プロジェクト]、[新しい項目の追加] の順にメニュー コマンドを選択して、プロジェクトに新しいページを追加します。[新しい項目の追加] ウィンドウが表示されます。 追加する項目のリストの [Windows Phone Portrait Page] を選択して、[名前] フィールドに「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 変数が設定されている場合、ここに [Next Page] ボタンが表示されます。このページのタイルが存在するかどうかに応じて、[Pin To Start] チェック ボックスが設定されます。

    
    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 メソッドが呼び出され、そのページに移動します。

    
    /// <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 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();
       }
    }
    
    
    メモメモ:

    ユーザーがページを固定できるようにする場合、ユーザーがアプリのルートにすぐに戻ることができる [ホーム] ボタンが必要かどうかを考慮します。ホーム ボタンは、アプリのホーム ページに移動し、ナビゲーション バック スタック全体をクリアします。各ナビゲーション シナリオを調査し、この機能が必要であるかどうかを判断します。

    • 固定されたページが、連絡先情報などの自己完結型である場合、ユーザーはページをタップし、情報を調べて、アプリに戻る可能性があります。この事例でアプリに戻ることは、ハードウェアの [戻る] ボタンを押して実現できます。

    • 固定されたページがアプリへのエントリ ポイントで、ユーザーがそこから深く移動していく場合には、アプリのルートにすばやく戻る方法が必要になる可能性があります。たとえば、固定されたページがショッピング カートである場合、ユーザーがショッピング カートで購入を終えた後、再度ショッピングを続けるケースが考えられます。この場合、ホーム ボタンを用意すれば、アプリの開始点に戻るために必要とされるタップの回数が減るため、ユーザーの操作性が向上します。

  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

    ページ 3 はアプリの最後のページのため、次のページに String.Empty を指定します。ページ 3 から先には移動できません。

このセクションを完了した場合のソリューション エクスプローラーのソリューションを次の図に示します。アプリには 4 つのページがあり、これらのページの "進む" ナビゲーションは、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] の 2 つのエントリが格納されます。スタックとして、一番上に最新のエントリが表示され、一番下に最も古いエントリが表示されます。これを次の図に示します。

    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] が表示されます。バック スタックには 1 つのエントリ [/MainPage.xaml] があります。

  8. [page 1] の [Next Page] ボタンをタップします。これで [page 2] が表示されます。バック スタックに [/Page1.xaml] と [/MainPage.xaml] の 2 つのエントリがあります。

  9. [page 2] の [Pin To Start] をタップします。

  10. デバイスの [スタート] 画面に、[page 2] というタイルが作成されます。

  11. 上記の手順で作成したタイルをタップします。アプリが起動し、[page 2] が表示されます。バック スタックが空であることにも注意してください。デバイスの [戻る] ボタンをタップすると、アプリが終了します。この状態の例を次の図に示します。[page 2] がデバイスの [スタート] 画面に固定されています。タイルがタップされた後、アプリが起動し、Page2 が表示されます。この図に示すバック スタックは空です。

    State of BackStack when navigation from Tile.

表示: