NavigationViewを使う3(Pageの中に配置したボタンから、別のページにNavigateしたい)

もくじ
https://tera1707.com/entry/2022/02/06/144447#winui3

やりたいこと

  • <NavigationView>の中に<Frame>を配置してPageを表示させて、
  • NavigationViewのNavigationViewItem(メニューの部分)を押したときに画面を遷移させる

ということは、以前の記事でためした。

tera1707.com

※メニューの部分とは、下記の部分。

ただ私の場合、NavigationViewのメニューの部分を押してページを遷移させることよりも、 それぞれのページの中のボタンを押したとき等に、お互いのページを行き来させたいということが多い。

例として、下は、以前作ったツールなのだが、

メイン画面の「プリセット」を押すとプリセット選択画面に行き、 プリセット画面の「選択したプリセットを使う」を押すとメイン画面に行く。


https://github.com/tera1707/WindowsMessageSenderWinui

NavigationViewを使わずに、ページ間をお手軽に行き来する処理を実現できないか?調べる。

やったこと

<Frame>を配置する「メインウインドウ」に、画面遷移をさせるためのpublicなメソッドを設けて、それをグローバル的なWindowクラスのプロパティ経由で、各ページから呼ぶことで実現する。

メインウインドウ

<?xml version="1.0" encoding="utf-8"?>
<Window
    x:Class="NavigationJikken2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:NavigationJikken2"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Frame x:Name="contentFrame" Loaded="contentFrame_Loaded"/>
</Window>
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Animation;

namespace NavigationJikken2
{
    public sealed partial class MainWindow : Window
    {
        // Pageから使うためのウインドウのプロパティ
        public static MainWindow CurrentWindow { get; private set; }

        public MainWindow()
        {
            this.InitializeComponent();
            CurrentWindow = this;
        }

        // まずBlankPage1を表示する
        private void contentFrame_Loaded(object sender, RoutedEventArgs e)
        {
            NavigateToBlankPage1();
        }

        // 画面遷移のためのメソッド①
        public void NavigateToBlankPage1()
        {
            contentFrame.Navigate(typeof(BlankPage1), null);
        }

        // 画面遷移のためのメソッド②
        public void NavigateToBlankPage2()
        {
            contentFrame.Navigate(typeof(BlankPage2), null, new DrillInNavigationTransitionInfo());
        }
    }
}

BlankPage1

<?xml version="1.0" encoding="utf-8"?>
<Page
    x:Class="NavigationJikken2.BlankPage1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:NavigationJikken2"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <StackPanel>
        <TextBlock Text="ここは BlankPage1 です"/>
        <Button Content="Page2へ行く" Click="Button_Click"/>
    </StackPanel>
</Page>
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace NavigationJikken2
{
    public sealed partial class BlankPage1 : Page
    {
        public BlankPage1()
        {
            this.InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            NavigateToBlank2Page();
        }

        private void NavigateToBlank2Page()
        {
            MainWindow.CurrentWindow.NavigateToBlankPage2();
        }
    }
}

BlankPage2

ほとんどBlankPage1と同じだが、一応。

<?xml version="1.0" encoding="utf-8"?>
<Page
    x:Class="NavigationJikken2.BlankPage2"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:NavigationJikken2"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    
    <StackPanel>
        <TextBlock Text="ここは BlankPage2 です"/>
        <Button Content="Page1に行く" Click="Button_Click"/>
    </StackPanel>
</Page>
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace NavigationJikken2
{
    public sealed partial class BlankPage2 : Page
    {
        public BlankPage2()
        {
            this.InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            NavigateToBlank1Page1();
        }

        private void NavigateToBlank1Page1()
        {
            MainWindow.CurrentWindow.NavigateToBlankPage1();
        }
    }
}

動かした結果

おわりに

今回は、画面遷移処理を持っているWindowのクラスへのグローバル的変数を使ってframe.Navigate()を呼ぶ、ということをしたが、

とにかく何らかの方法で、frame.Navigate()を呼んでやればよいと思う。もっとスマートな方法があればよいが、今のところ見つけられていない。

良い方法がありそうなら、コメントください。

備考(以前やってたやり方)

以前同じことを実現しようとしたときに、別の方法で実現していた。

その時のやり方は、Navigate()メソッドの第二引数に、

contentFrame.Navigate(typeof(BlankPage1), null);

画面遷移のためのActionを渡して、それをページ側(👆の例だとBlankPage1)のOnNavigateTo()e.Parameterで受け取って、それをページ側で呼ぶ、みたいなことをしていた。 (当然複数ページあってそれぞれの間を行き来したいので、List<Action>を渡して受け取っていた。)

ここ でやっていたようなこと。

それでも実現はできていたのだが、

  • Navigateの第二引数は、本来は遷移元ページから遷移先ページに情報を渡すために使うのに、そこにページ遷移のためのActionとか載せると、本来渡したい情報がなんなのかわかりづらくなり、コードも不自然になる。
  • ページが増えるたびにNavigate()の引数を変更しないといけなくなる。
  • で、コードがごちゃごちゃする

という感じで、なんとかならないかなーと思っていた。

今回のやり方も、グローバル変数的なもの(staticな、Windowの変数)ができてしまうのがなんかいやなのだが、以前のやり方よりはすっきりするしまぁいいかなー、、という感じ。