实现两个页面之间的导航

了解如何使用框架和页面在你的应用中实现基本对等导航。

对等导航

几乎每个应用都需要在页面之间导航。 即使是包含单个内容页面的简单应用,通常也会有一个需要导航的设置页面。 在本文中,我们将演练向应用添加 XAML Page 和使用 Frame 在页面之间导航的基础知识。

重要

本示例使用 Microsoft Visual Studio 中的 空白应用 模板。 Windows 应用 SDK/WinUI 3 应用和 UWP 应用的模板存在差异,因此请务必为应用类型选择正确的选项卡。

1.创建空白应用

若要在 Visual Studio 中创建空白应用,请执行以下操作:

  1. 若要设置开发计算机,请参阅安装适用于 Windows App SDK 的工具
  2. 在 Microsoft Visual Studio 开始窗口中,选择“ 创建新项目”,或者,在 Visual Studio 菜单上,选择 “文件>新建>项目”。
  3. “创建新项目 ”对话框的下拉筛选器中,分别选择 C#C++WindowsWinUI
  4. 选择“打包的空白应用(桌面版 WinUI 3)”项目模板,然后单击“下一步”。 该模板创建一个具有基于 WinUI 3 的用户界面的桌面应用。
  5. 在“ 项目名称 ”框中,输入 BasicNavigation,然后单击“ 创建”。
  6. 若要运行程序,请从菜单中依次选择“调试”>“启动调试”,或按 F5。 在开发计算机上生成并运行解决方案,确认应用运行时不会出错。 将显示一个空白页面。
  7. 要停止调试并返回到 Visual Studio,退出该应用,或从菜单中单击“停止调试”。
  8. MainWindow.xamlMainWindow 代码隐藏文件中删除模板中包含的任何示例代码。

提示

有关详细信息,请参阅创建第一个 WinUI 3 (Windows 应用 SDK) 项目

2. 使用 Frame 在页面之间导航

当应用具有多个页面时,可以使用 Frame 在它们之间导航。 类 Frame 支持各种导航方法(例如 NavigateGoBackGoForward)以及 BackStackForwardStackBackStackDepth 等属性。

在 Visual Studio 中创建新的Windows 应用 SDK项目时,项目模板会创建 Microsoft.UI.Xaml.WindowMainWindow) 类型的类 (。 但是,它不会创建 FramePage ,也不提供任何导航代码。

若要启用页面之间的导航,请添加 Frame 作为 的 MainWindow根元素。 可以在代码隐藏文件中的 Application.OnLaunched 方法重写中 App.xaml 执行此操作。 App打开代码隐藏文件,更新OnLaunched重写,并处理 NavigationFailed 事件,如下所示。

// App.xaml.cs

protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    m_window = new MainWindow();

    // Create a Frame to act as the navigation context and navigate to the first page
    Frame rootFrame = new Frame();
    rootFrame.NavigationFailed += OnNavigationFailed;
    // Navigate to the first page, configuring the new page
    // by passing required information as a navigation parameter
    rootFrame.Navigate(typeof(MainPage), args.Arguments);

    // Place the frame in the current Window
    m_window.Content = rootFrame;
    // Ensure the MainWindow is active
    m_window.Activate();
}

void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
    throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
// App.xaml.h

// Add after OnLaunched declaration.
void OnNavigationFailed(IInspectable const&, Microsoft::UI::Xaml::Navigation::NavigationFailedEventArgs const&);

///////////////
// App.xaml.cpp

void App::OnLaunched(LaunchActivatedEventArgs const& e)
{
    window = make<MainWindow>();
    Frame rootFrame = Frame();
    rootFrame.NavigationFailed({ this, &App::OnNavigationFailed });
    rootFrame.Navigate(xaml_typename<BasicNavigation::MainPage>(), box_value(e.Arguments()));
    window.Content(rootFrame);
    window.Activate();
}

void App::OnNavigationFailed(IInspectable const&, NavigationFailedEventArgs const& e)
{
    throw hresult_error(E_FAIL, hstring(L"Failed to load Page ") + e.SourcePageType().Name);
}

注意

对于具有更复杂的导航的应用,通常使用 NavigationView 作为 MainWindow 的根,并将 放置 Frame 为导航视图的内容。 有关详细信息,请参阅 导航视图

Navigate 方法用于显示此 Frame中的内容。 此处, MainPage.xamlNavigate传递给 方法,因此该方法在 中Frame加载MainPage

如果导航到应用的初始窗口失败,则会发生事件 NavigationFailed ,并且此代码会在事件处理程序中引发异常。

3. 添加基本页面

空白应用模板不会为你创建多个应用页面。 在页面之间导航之前,需要向应用添加一些页面。

若要向应用添加新项,请执行以下操作:

  1. “解决方案资源管理器”中BasicNavigation,右键单击项目节点以打开上下文菜单。
  2. 从上下文菜单中选择 “添加新> ”。
  3. 在“ 添加新项 ”对话框中,选择左窗格中的 “WinUI ”节点,然后在中间窗格中选择“ 空白页 (WinUI 3) ”。
  4. 在“ 名称 ”框中,输入 MainPage 并按 “添加” 按钮。
  5. 重复步骤 1-4 以添加第二页,但在“ 名称 ”框中输入 Page2

现在,这些文件应作为项目的 BasicNavigation 一部分列出。

C# C++
  • MainPage.xaml
  • MainPage.xaml.cs
  • Page2.xaml
  • Page2.xaml.cs
  • MainPage.xaml
  • MainPage.xaml.cpp
  • MainPage.xaml.h
  • Page2.xaml
  • Page2.xaml.cpp
  • Page2.xaml.h

重要

对于 C++ 项目,必须在引用另一页 #include 的每个页面的头文件中添加指令。 对于此处提供的页间导航示例,mainpage.xaml.h 文件包含 #include "Page2.xaml.h",而 page2.xaml.h 包含 #include "MainPage.xaml.h"

C++ 页面模板还包括一个示例 Button 和单击处理程序代码,你需要从页面的 XAML 和代码隐藏文件中删除这些代码。

向页面添加内容

在 中 MainPage.xaml,将现有页面内容替换为以下内容:

<Grid>
    <TextBlock x:Name="pageTitle" Text="Main Page"
               Margin="16" Style="{StaticResource TitleTextBlockStyle}"/>
    <HyperlinkButton Content="Click to go to page 2"
                     Click="HyperlinkButton_Click"
                     HorizontalAlignment="Center"/>
</Grid>

此 XAML 添加:

MainPage代码隐藏文件中,添加以下代码来处理Click你为启用导航Page2.xaml而添加的 HyperlinkButton 的事件。

// MainPage.xaml.cs

private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
{
    Frame.Navigate(typeof(Page2));
}
// pch.h
// Add this include in pch.h to support winrt::xaml_typename

#include <winrt/Windows.UI.Xaml.Interop.h>

////////////////////
// MainPage.xaml.h

void HyperlinkButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);

////////////////////
// MainPage.xaml.cpp

void winrt::BasicNavigation::implementation::MainPage::HyperlinkButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e)
{
    Frame().Navigate(winrt::xaml_typename<BasicNavigation::Page2>());
}

MainPage 是 Page 类的子类。 类 Page 具有只读 Frame 属性,该属性获取 Frame 包含 的 Page。 当 中 Click 的事件处理程序调用 Frame.Navigate(typeof(Page2))时, Frame 将显示 的内容Page2.xamlHyperlinkButtonMainPage

每当页面加载到框架中时,该页面将作为 PageStackEntry 添加到 FrameBackStackForwardStack,从而允许历史记录和向后导航

现在,在 中 Page2.xaml执行相同的操作。 将现有页面内容替换为以下内容:

<Grid>
    <TextBlock x:Name="pageTitle" Text="Page 2"
               Margin="16" Style="{StaticResource TitleTextBlockStyle}"/>
    <HyperlinkButton Content="Click to go to main page"
                     Click="HyperlinkButton_Click"
                     HorizontalAlignment="Center"/>
</Grid>

Page2 代码隐藏文件中,添加以下代码以处理 ClickHyperlinkButton 的 事件,以导航到 MainPage.xaml

// Page2.xaml.cs

private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
{
    Frame.Navigate(typeof(MainPage));
}
// Page2.xaml.h

void HyperlinkButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);

/////////////////
// Page2.xaml.cpp

void winrt::BasicNavigation::implementation::Page2::HyperlinkButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e)
{
    Frame().Navigate(winrt::xaml_typename<BasicNavigation::MainPage>());
}

生成并运行应用。 单击显示“单击以转到第 2 页”的链接。 在顶部显示“第 2 页”的第二个页面应加载并显示在框架中。 现在,单击第 2 页上的链接以返回到主页。

4. 在页面之间传递信息

你的应用现在在两个页面之间导航,但它实际上还没有执行任何有趣的操作。 当一个应用包含多个页面时,这些页面经常需要共享信息。 现在,你将将一些信息从第一页传递到第二页。

在 中 MainPage.xamlHyperlinkButton 将之前添加的 替换为以下 StackPanel。 这将添加 一个 TextBlock 标签和一个用于输入文本字符串的 TextBoxname

<StackPanel VerticalAlignment="Center">
    <TextBlock HorizontalAlignment="Center" Text="Enter your name"/>
    <TextBox HorizontalAlignment="Center" Width="200" x:Name="name"/>
    <HyperlinkButton Content="Click to go to page 2"
                              Click="HyperlinkButton_Click"
                              HorizontalAlignment="Center"/>
</StackPanel>

现在,你将使用 方法的第二个 Navigate 重载,并将文本框中的文本作为第二个参数传递。 下面是此 Navigate 重载的签名:

public bool Navigate(System.Type sourcePageType, object parameter);
bool Navigate(TypeName const& sourcePageType, IInspectable const& parameter);

HyperlinkButton_Click 代码隐藏文件的事件处理程序中 MainPage ,将第二个参数添加到 Navigate 引用 Text 文本框的 属性的方法 name

// MainPage.xaml.cs

private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
{
    Frame.Navigate(typeof(Page2), name.Text);
}
// MainPage.xaml.cpp

void winrt::BasicNavigation::implementation::MainPage::HyperlinkButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e)
{ 
    Frame().Navigate(xaml_typename<BasicNavigation::Page2>(), winrt::box_value(name().Text()));
}

Page2.xaml中, HyperlinkButton 将前面添加的 替换为以下 StackPanel。 这将添加 一个 TextBlock ,用于显示从 MainPage传递的文本字符串。

<StackPanel VerticalAlignment="Center">
    <TextBlock HorizontalAlignment="Center" x:Name="greeting"/>
    <HyperlinkButton Content="Click to go to page 1"
                     Click="HyperlinkButton_Click"
                     HorizontalAlignment="Center"/>
</StackPanel>

Page2 代码隐藏文件中,添加以下代码以替代 OnNavigatedTo 方法:

// Page2.xaml.cs

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    if (e.Parameter is string && !string.IsNullOrWhiteSpace((string)e.Parameter))
    {
        greeting.Text = $"Hello, {e.Parameter.ToString()}";
    }
    else
    {
        greeting.Text = "Hello!";
    }
    base.OnNavigatedTo(e);
}
// Page2.xaml.h

void Page2::OnNavigatedTo(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e)
{
	auto propertyValue{ e.Parameter().as<Windows::Foundation::IPropertyValue>() };
	if (propertyValue.Type() == Windows::Foundation::PropertyType::String)
	{
		auto name{ winrt::unbox_value<winrt::hstring>(e.Parameter()) };
		if (!name.empty())
		{
			greeting().Text(L"Hello, " + name);
			__super::OnNavigatedTo(e);
			return;
		}
	}
	greeting().Text(L"Hello!");
	__super::OnNavigatedTo(e);
}

运行应用,在文本框中键入你的姓名,然后单击显示 Click to go to page 2的链接。

Click当 中 MainPageHyperlinkButton 事件调用 Frame.Navigate(typeof(Page2), name.Text)时, name.Text 属性将传递给 Page2,并且事件数据中的值用于页面上显示的消息。

5.缓存页面

默认情况下不缓存页面内容和状态,因此如果你要缓存信息,必须在应用的每个页面中启用它。

在我们的基本对等示例中,单击 Click to go to page 1 上的 Page2链接时, TextBox (以及) MainPage 的任何其他字段都设置为其默认状态。 解决此问题的一种方法是使用 NavigationCacheMode 属性指定需要添加到框架的页面缓存中的页面。

默认情况下,每次导航时,都会使用其默认值创建新的页面实例。 在 MainPage.xaml中,设置为EnabledNavigationCacheMode (在开始Page标记中) 缓存页面,并保留页面的所有内容和状态值,直到超出帧的页面缓存。 如果要忽略 CacheSize 限制,请将 NavigationCacheMode 设置为“必需”,这将指定导航历史记录中可为帧缓存的页数。 但是,请记住,根据设备的内存限制,缓存大小限制可能非常重要。

<Page
    x:Class="BasicNavigation.MainPage"
    ...
    mc:Ignorable="d"
    NavigationCacheMode="Enabled">

现在,单击返回main页时,在文本框中输入的名称仍然存在。

6.自定义页面过渡动画

默认情况下,导航发生时,每个页面都会动画化到框架中。 默认动画是“入口”动画,会导致页面从窗口底部向上滑动。 但是,你可以选择更适合应用导航的不同动画选项。 例如,可以使用“钻取”动画来让用户更深入地了解你的应用,或使用水平幻灯片动画来感觉两个页面是对等的。 有关详细信息,请参阅 页面转换

这些动画由 NavigationTransitionInfo 的子类表示。 若要指定用于页面过渡的动画,请使用 方法的第三个重载 Navigate ,并将子类作为第三个 NavigationTransitionInfo 参数传递 (infoOverride) 。 下面是此 Navigate 重载的签名:

public bool Navigate(System.Type sourcePageType, 
                     object parameter,
                     NavigationTransitionInfo infoOverride);
bool Navigate(TypeName const& sourcePageType, 
              IInspectable const& parameter, 
              NavigationTransitionInfo const& infoOverride);

HyperlinkButton_Click 代码隐藏文件的事件处理程序中 MainPage ,向 方法添加第三个参数 Navigate ,该方法将 infoOverride 参数设置为 SlideNavigationTransitionInfo ,其 Effect 属性设置为 FromRight

// MainPage.xaml.cs

private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
{
    Frame.Navigate(typeof(Page2), 
                   name.Text,
                   new SlideNavigationTransitionInfo() 
                       { Effect = SlideNavigationTransitionEffect.FromRight});
}
// pch.h

#include <winrt/Microsoft.UI.Xaml.Media.Animation.h>

////////////////////
// MainPage.xaml.cpp

using namespace winrt::Microsoft::UI::Xaml::Media::Animation;

// ...

void winrt::BasicNavigation::implementation::MainPage::HyperlinkButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e)
{   
    // Create the slide transition and set the transition effect to FromRight.
    SlideNavigationTransitionInfo slideEffect = SlideNavigationTransitionInfo();
    slideEffect.Effect(SlideNavigationTransitionEffect(SlideNavigationTransitionEffect::FromRight));
    Frame().Navigate(winrt::xaml_typename<BasicNavigation::Page2>(),
        		     winrt::box_value(name().Text()),
                     slideEffect);
}

HyperlinkButton_Click 代码隐藏文件的事件处理程序中 Page2 ,将 infoOverride 参数设置为 SlideNavigationTransitionInfo ,其 Effect 属性设置为 FromLeft

// Page2.xaml.cs

private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
{
    Frame.Navigate(typeof(MainPage),
                   null,
                   new SlideNavigationTransitionInfo() 
                       { Effect = SlideNavigationTransitionEffect.FromLeft});
}
// Page2.xaml.cpp

using namespace winrt::Microsoft::UI::Xaml::Media::Animation;

// ...

void winrt::BasicNavigation::implementation::MainPage::HyperlinkButton_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e)
{   
    // Create the slide transition and set the transition effect to FromLeft.
    SlideNavigationTransitionInfo slideEffect = SlideNavigationTransitionInfo();
    slideEffect.Effect(SlideNavigationTransitionEffect(SlideNavigationTransitionEffect::FromLeft));
    Frame().Navigate(winrt::xaml_typename<BasicNavigation::MainPage>(),
        		     nullptr,
                     slideEffect);
}

现在,当你在页面之间导航时,页面向左和向右滑动,这为这种过渡提供了更自然的感觉,并强化了页面之间的连接。