导航概述

Windows Presentation Foundation (WPF) 支持可以在两种类型的应用程序中使用的浏览器样式导航:独立应用程序和 XAML 浏览器应用程序 (XBAP)。 为打包内容以用于导航,WPF 提供了 Page 类。 可以通过使用 Hyperlink 以声明方式或通过使用 NavigationService 以编程方式从一个 Page 导航到另一个。 WPF 使用日志记住从其导航和导航回它们的页。

PageHyperlinkNavigationService 和日志组成 WPF 所提供的导航支持的核心。 本概述详细介绍这些功能,然后介绍高级导航支持,包括导航到松散 Extensible Application Markup Language (XAML) 文件、HTML 文件和对象。

注意

在此主题中,术语“浏览器”仅指可以托管 WPF 应用程序的浏览器,当前包括 Microsoft Internet Explorer 和 Firefox。 特定 WPF 功能仅受特定浏览器(指出浏览器版本)支持。

本主题提供 WPF 中键导航功能的概述。 这些功能对独立应用程序和 XBAP 都可用,不过本主题在 XBAP 的上下文中呈现它们。

注意

本主题不讨论如何生成和部署 XBAP。 有关 XBAP 的详细信息,请参阅 WPF XAML 浏览器应用程序概述

本节解释并演示导航的以下方面:

实现页

在 WPF 中,可以导航到包含 .NET Framework 对象、自定义对象、枚举值、用户控件、XAML 文件和 HTML 文件的多种内容类型。 但是,你会发现打包内容最常见和最简便的方式是使用 Page。 此外,Page 会实现特定于导航的功能来增强外观并简化开发。

使用 Page,可以通过使用类似于以下标记的标记,以声明方式实现 XAML 内容的可导航页。

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />

在 XAML 标记中实现的 PagePage 作为其根元素并需要 WPF XML 命名空间声明。 Page 元素包含要导航到并显示的内容。 通过设置 Page.Content 属性元素来添加内容,如以下标记所示。

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <Page.Content>
    <!-- Page Content -->
    Hello, Page!
  </Page.Content>
</Page>

Page.Content 只能包含一个子元素,在前面的实例中,内容是单个字符串“Hello, Page!”。实际上通常使用布局控件作为子元素(请参阅布局)来包含并创建内容。

Page 元素的子元素被视为 Page 的内容,因此,无需使用显式 Page.Content 声明。 以下标记和前面的示例在声明上是等效的。

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <!-- Page Content -->
  Hello, Page!
</Page>

在此情况下,Page.ContentPage 元素的子元素自动设置。 有关详细信息,请参阅 WPF 内容模型

仅标记 Page 对显示内容很有用。 但是,Page 也可以显示允许用户与页进行交互的控件,而且可以通过处理事件和调用应用程序逻辑来响应用户交互。 交互式 Page 通过使用标记和代码隐藏的组合来实现,如以下示例中所示。

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.HomePage">
  Hello, from the XBAP HomePage!
</Page>
using System.Windows.Controls;

namespace SDKSample
{
    public partial class HomePage : Page
    {
        public HomePage()
        {
            InitializeComponent();
        }
    }
}

Imports System.Windows.Controls

Namespace SDKSample
    Partial Public Class HomePage
        Inherits Page
        Public Sub New()
            InitializeComponent()
        End Sub
    End Class
End Namespace

要允许标记文件和代码隐藏文件协同工作,需要进行以下配置:

  • 在标记中,Page 元素必须包含 x:Class 属性。 生成应用程序时,标记文件中存在 x:Class 会使 Microsoft 生成引擎 (MSBuild) 创建派生自 Pagepartial 类,并且名称由 x:Class 属性指定。 这要求为 XAML 架构 (xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml") 添加 XML 命名空间声明。 已生成的 partial 类实现 InitializeComponent,调用它可注册事件并设置标记中实现的属性。

  • 在代码隐藏中,类必须是 partial 类、名称必须是标记中 x:Class 属性指定的相同名称,并且它必须派生自 Page。 这样,代码隐藏文件就可以与生成应用程序时为标记文件生成的 partial 类(请参阅生成 WPF 应用程序)相关联。

  • 在代码隐藏中,Page 类必须实现调用 InitializeComponent 方法的构造函数。 InitializeComponent 由标记文件已生成的 partial 类实现,用以注册事件并设置标记中定义的属性。

注意

使用 Visual Studio 将新的 Page 添加到项目时,Page 通过同时使用标记和代码隐藏实现,并且包括必要的配置来创建此处所述的标记文件和代码隐藏文件之间的关联。

一旦具有 Page,就可以导航到它。 要指定应用程序导航到的第一个 Page,需要配置起始 Page

配置起始页

XBAP 需要在浏览器中托管一定数量的应用程序基础结构。 在 WPF 中,Application 类是建立所需应用程序基础结构的应用程序定义部分(请参阅应用程序管理概述)。

应用程序定义通常同时使用标记和代码隐藏来实现,其中标记文件配置为 MSBuildApplicationDefinition 项。 以下内容是 XBAP 的应用程序定义。

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.App" />
using System.Windows;

namespace SDKSample
{
    public partial class App : Application { }
}

Imports System.Windows

Namespace SDKSample
    Partial Public Class App
        Inherits Application
    End Class
End Namespace

XBAP 可以使用其应用程序定义指定起始 Page,即启动 XBAP 时自动加载的 Page。 为此,可使用所需 Page 的统一资源标识符 (URI) 设置 StartupUri 属性。

注意

在大多数情况下,Page 编译到应用程序中或使用应用程序部署。 在这些情况下,标识 Page 的 URI 是 pack URI,它是符合 pack 方案的 URI。 WPF 中的 Pack URI 进一步讨论了 pack URI。 也可使用 http 方案导航到内容,这将在以下内容中讨论。

可以在标记中以声明方式设置 StartupUri,如以下示例所示。

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.App"
    StartupUri="PageWithHyperlink.xaml" />

在此示例中,StartupUri 属性使用标识 HomePage.xaml 的相对 pack URI 设置。 当 XBAP 启动时,自动导航到并显示 HomePage.xaml。 这由下图演示,其中显示了从 Web 服务器启动的 XBAP。

XBAP page

注意

有关 XBAP 开发和部署的详细信息,请参阅 WPF XAML 浏览器应用程序概述部署 WPF 应用程序

配置主机窗口的标题、宽度和高度

从上图可能已经注意到对于 XBAP,浏览器和选项卡面板的标题都是 URI。 除了长,标题既没什么吸引力也没什么帮助。 出于这一原因,Page 提供了通过设置 WindowTitle 属性来更改标题的方法。 此外,可以通过分别设置 WindowWidthWindowHeight 来配置浏览器窗口的宽度和高度。

可以在标记中以声明方式设置 WindowTitleWindowWidthWindowHeight,如以下示例所示。

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.HomePage"
    WindowTitle="Page Title"
    WindowWidth="500"
    WindowHeight="200">
  Hello, from the XBAP HomePage!
</Page>

结果如下图所示。

Window title, height, width

典型 XBAP 包含多页。 若要从一页导航到另一页,最简单的方法是使用 Hyperlink。 可以使用 Hyperlink 元素以声明方式将 Hyperlink 添加到 Page,如以下标记所示。

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  WindowTitle="Page With Hyperlink"
  WindowWidth="250"
  WindowHeight="250">
<Hyperlink NavigateUri="UriOfPageToNavigateTo.xaml">
  Navigate to Another Page
</Hyperlink>
</Page>

Hyperlink 元素需要以下内容:

  • 要导航到的 Page 的 pack URI,如 NavigateUri 属性所指定。

  • 用户可以单击以启动导航的内容,如文本和图像(有关 Hyperlink 元素可以获取的内容,请参阅 Hyperlink)。

下图显示了一个 XBAP,其 Page 带有 Hyperlink

Page with Hyperlink

如你所料,单击 Hyperlink 使得 XBAP 导航到由 NavigateUri 属性标识的 Page。 此外,XBAP 将上一个 Page 的条目添加到 Internet Explorer 中的最新页列表。 如下图所示。

Back and Forward buttons

除了支持从一个 Page 导航到另一个,Hyperlink 也支持片段导航。

片段导航

片段导航是在当前 Page 或另一个 Page 的内容片段中导航。 在 WPF 中,内容片段是命名元素所包含的内容。 命名元素是设置了其 Name 属性的元素。 以下标记显示包含内容片段的命名 TextBlock 元素。

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    WindowTitle="Page With Fragments" >
<!-- Content Fragment called "Fragment1" -->
<TextBlock Name="Fragment1">
  Ea vel dignissim te aliquam facilisis ...
</TextBlock>
</Page>

若要使 Hyperlink 导航到内容片段,NavigateUri 属性必须包括以下内容:

  • 具有要导航到的内容片段的 Page 的 URI。

  • “#”字符。

  • Page 上包含内容片段的元素的名称。

片段 URI 具有以下格式。

PageURI#ElementName

以下内容显示配置为导航到内容片段的 Hyperlink 的示例。

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  WindowTitle="Page That Navigates To Fragment" >
<Hyperlink NavigateUri="PageWithFragments.xaml#Fragment1">
  Navigate To pack Fragment
</Hyperlink>
</Page>

注意

本节描述 WPF 中的默认片段导航实现。 WPF 还允许实现自己的片段导航方案,该方案在一定程度上需要处理 NavigationService.FragmentNavigation 事件。

重要

仅当可以通过 HTTP 浏览页时才能导航到松散 XAML 页中(将 Page 作为根元素的仅标记 XAML 文件)的片段。

但是,松散 XAML 页可以导航到自己的片段。

尽管 Hyperlink 允许用户启动到特定 Page 的导航,不过查找并下载页的工作由 NavigationService 类执行。 实质上,NavigationService 提供代表客户端代码处理导航请求的能力,如 Hyperlink。 此外,NavigationService 实现更高级别支持来跟踪并影响导航需求。

单击 Hyperlink 时,WPF 调用 NavigationService.Navigate 以在指定 pack URI 处查找并下载 Page。 已下载的 Page 转换为对象的树,其根对象是已下载的 Page 的实例。 对根 Page 对象的引用存储在 NavigationService.Content 属性中。 导航到的内容的 pack URI 存储在 NavigationService.Source 属性中,而 NavigationService.CurrentSource 存储导航到的最后一页的 pack URI。

注意

WPF 应用程序可能拥有多个当前处于活动状态的 NavigationService。 有关详细信息,请参阅本主题后面的导航主机

使用导航服务以编程方式导航

如果导航使用 Hyperlink 在标记中以声明方式实现,则无需了解 NavigationService,因为 Hyperlink 以你的名义使用 NavigationService。 这意味着只要 Hyperlink 的直接或间接父级是导航主机(请参阅导航主机),Hyperlink 就能查找并使用导航主机的导航服务来处理导航请求。

但是,有些情况需要直接使用 NavigationService,包括以下情况:

  • 需要使用非无参数构造函数实例化 Page 时。

  • 需要在导航到它之前设置 Page 上的属性时。

  • 需要导航到的 Page 只能在运行时确定时。

在这些情况下,需要编写代码,通过调用 NavigationService 对象的 Navigate 方法以编程方式启动导航。 这需要获取对 NavigationService 的引用。

获取对 NavigationService 的引用

出于导航主机一节中描述的原因,WPF 应用程序可以拥有多个 NavigationService。 这意味着代码需要查找 NavigationService(通常是导航到当前 PageNavigationService)的方法。 可以通过调用 staticNavigationService.GetNavigationService 方法来获取对 NavigationService 的引用。 若要获取导航到特定 PageNavigationService,可将对 Page 的引用作为 GetNavigationService 方法的参数进行传递。 下面的代码演示如何获取当前 PageNavigationService

using System.Windows.Navigation;
// Get a reference to the NavigationService that navigated to this Page
NavigationService ns = NavigationService.GetNavigationService(this);
' Get a reference to the NavigationService that navigated to this Page
Dim ns As NavigationService = NavigationService.GetNavigationService(Me)

作为查找 PageNavigationService 的快捷方式,Page 实现了 NavigationService 属性。 这在下面的示例中显示。

using System.Windows.Navigation;
// Get a reference to the NavigationService that navigated to this Page
NavigationService ns = this.NavigationService;
' Get a reference to the NavigationService that navigated to this Page
Dim ns As NavigationService = Me.NavigationService

注意

Page 引发 Loaded 事件时,Page 只能获取对其 NavigationService 的引用。

以编程方式导航到页对象

下面的示例演示如何使用 NavigationService 以编程方式导航到 Page。 以编程方式导航是必需的,因为导航到的 Page 只能使用单个非无参数构造函数进行实例化。 具有非无参数构造函数的 Page 在以下标记和代码中显示。

<Page
    x:Class="SDKSample.PageWithNonDefaultConstructor"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="PageWithNonDefaultConstructor">
  
  <!-- Content goes here -->
  
</Page>
using System.Windows.Controls;

namespace SDKSample
{
    public partial class PageWithNonDefaultConstructor : Page
    {
        public PageWithNonDefaultConstructor(string message)
        {
            InitializeComponent();

            this.Content = message;
        }
    }
}

Namespace SDKSample
    Partial Public Class PageWithNonDefaultConstructor
        Inherits Page
        Public Sub New(ByVal message As String)
            InitializeComponent()

            Me.Content = message
        End Sub
    End Class
End Namespace

使用非无参数构造函数导航到 PagePage 在以下标记和代码中显示。

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.NSNavigationPage">

  <Hyperlink Click="hyperlink_Click">
    Navigate to Page with Non-Default Constructor
  </Hyperlink>

</Page>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;

namespace SDKSample
{
    public partial class NSNavigationPage : Page
    {
        public NSNavigationPage()
        {
            InitializeComponent();
        }

        void hyperlink_Click(object sender, RoutedEventArgs e)
        {
            // Instantiate the page to navigate to
            PageWithNonDefaultConstructor page = new PageWithNonDefaultConstructor("Hello!");

            // Navigate to the page, using the NavigationService
            this.NavigationService.Navigate(page);
        }
    }
}

Namespace SDKSample
    Partial Public Class NSNavigationPage
        Inherits Page
        Public Sub New()
            InitializeComponent()
        End Sub

        Private Sub hyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Instantiate the page to navigate to
            Dim page As New PageWithNonDefaultConstructor("Hello!")

            ' Navigate to the page, using the NavigationService
            Me.NavigationService.Navigate(page)
        End Sub
    End Class
End Namespace

单击此 Page 上的 Hyperlink 时,通过使用非无参数构造函数实例化要导航到的 Page 并调用 NavigationService.Navigate 方法来启动导航。 Navigate 接受对 NavigationService 要导航到的对象的引用(而不是 pack URI)。

使用 Pack URI 以编程方式导航

如果需要以编程方式构造 pack URI(例如只能在运行时确定 pack URI 时),可以使用 NavigationService.Navigate 方法。 这在下面的示例中显示。

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.NSUriNavigationPage">
  <Hyperlink Click="hyperlink_Click">Navigate to Page by Pack URI</Hyperlink>
</Page>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;

namespace SDKSample
{
    public partial class NSUriNavigationPage : Page
    {
        public NSUriNavigationPage()
        {
            InitializeComponent();
        }

        void hyperlink_Click(object sender, RoutedEventArgs e)
        {
            // Create a pack URI
            Uri uri = new Uri("AnotherPage.xaml", UriKind.Relative);

            // Get the navigation service that was used to
            // navigate to this page, and navigate to
            // AnotherPage.xaml
            this.NavigationService.Navigate(uri);
        }
    }
}

Namespace SDKSample
    Partial Public Class NSUriNavigationPage
        Inherits Page
        Public Sub New()
            InitializeComponent()
        End Sub

        Private Sub hyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Create a pack URI
            Dim uri As New Uri("AnotherPage.xaml", UriKind.Relative)

            ' Get the navigation service that was used to 
            ' navigate to this page, and navigate to 
            ' AnotherPage.xaml
            Me.NavigationService.Navigate(uri)
        End Sub
    End Class
End Namespace

刷新当前页

如果具有与存储在 NavigationService.Source 属性中的 pack URI 相同的 pack URI,则不下载 Page。 要再次强制 WPF 下载当前页,可以调用 NavigationService.Refresh 方法,如以下示例中所示。

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.NSRefreshNavigationPage">
 <Hyperlink Click="hyperlink_Click">Refresh this page</Hyperlink>
</Page>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;

namespace SDKSample
{
    public partial class NSRefreshNavigationPage : Page
    {

Namespace SDKSample
    Partial Public Class NSRefreshNavigationPage
        Inherits Page
        void hyperlink_Click(object sender, RoutedEventArgs e)
        {
            // Force WPF to download this page again
            this.NavigationService.Refresh();
        }
    }
}
        Private Sub hyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Force WPF to download this page again
            Me.NavigationService.Refresh()
        End Sub
    End Class
End Namespace

如你所见,有很多方法初始化导航。 启动导航后且导航正在进行时,可以使用由 NavigationService 实现的以下事件跟踪和影响导航:

  • Navigating。 请求新导航时发生。 可用于取消导航。

  • NavigationProgress。 在下载过程中定期发生,用于提供定位进度信息。

  • Navigated。 已定位并下载页时发生。

  • NavigationStopped。 停止导航时发生(通过调用 StopLoading),或当前导航正在进行期间请求新导航时发生。

  • NavigationFailed。 在导航到所需内容的同时遇到错误时发生。

  • LoadCompleted。 导航到的内容已加载和分析,并开始呈现时发生。

  • FragmentNavigation。 导航到内容片段开始时发生,具体如何发生如下所述:

    • 立即,如果所需片段位于当前内容中。

    • 源内容加载之后,如果所需片段在不同内容中。

引发导航事件的顺序如下图所示。

Page navigation flow chart

通常情况下,Page 不涉及这些事件。 更有可能的是它们涉及到应用程序,而且由于该原因这些事件也由 Application 类引发:

每次 NavigationService 引发事件时,Application 类都引发相应事件。 FrameNavigationWindow 提供相同事件使其在各自范围中检测导航。

在某些情况下,Page 可能会对这些事件感兴趣。 例如,Page 可能会处理 NavigationService.Navigating 事件来确定是否取消离开自己的导航。 这在下面的示例中显示。

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.CancelNavigationPage">
  <Button Click="button_Click">Navigate to Another Page</Button>
</Page>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;

namespace SDKSample
{
    public partial class CancelNavigationPage : Page
    {
        public CancelNavigationPage()
        {
            InitializeComponent();

            // Can only access the NavigationService when the page has been loaded
            this.Loaded += new RoutedEventHandler(CancelNavigationPage_Loaded);
            this.Unloaded += new RoutedEventHandler(CancelNavigationPage_Unloaded);
        }

        void button_Click(object sender, RoutedEventArgs e)
        {
            // Force WPF to download this page again
            this.NavigationService.Navigate(new Uri("AnotherPage.xaml", UriKind.Relative));
        }

        void CancelNavigationPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.NavigationService.Navigating += new NavigatingCancelEventHandler(NavigationService_Navigating);
        }

        void CancelNavigationPage_Unloaded(object sender, RoutedEventArgs e)
        {
            this.NavigationService.Navigating -= new NavigatingCancelEventHandler(NavigationService_Navigating);
        }

        void NavigationService_Navigating(object sender, NavigatingCancelEventArgs e)
        {
            // Does the user really want to navigate to another page?
            MessageBoxResult result;
            result = MessageBox.Show("Do you want to leave this page?", "Navigation Request", MessageBoxButton.YesNo);

            // If the user doesn't want to navigate away, cancel the navigation
            if (result == MessageBoxResult.No) e.Cancel = true;
        }
    }
}

Namespace SDKSample
    Partial Public Class CancelNavigationPage
        Inherits Page
        Public Sub New()
            InitializeComponent()

            ' Can only access the NavigationService when the page has been loaded
            AddHandler Loaded, AddressOf CancelNavigationPage_Loaded
            AddHandler Unloaded, AddressOf CancelNavigationPage_Unloaded
        End Sub

        Private Sub button_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Force WPF to download this page again
            Me.NavigationService.Navigate(New Uri("AnotherPage.xaml", UriKind.Relative))
        End Sub

        Private Sub CancelNavigationPage_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            AddHandler NavigationService.Navigating, AddressOf NavigationService_Navigating
        End Sub

        Private Sub CancelNavigationPage_Unloaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            RemoveHandler NavigationService.Navigating, AddressOf NavigationService_Navigating
        End Sub

        Private Sub NavigationService_Navigating(ByVal sender As Object, ByVal e As NavigatingCancelEventArgs)
            ' Does the user really want to navigate to another page?
            Dim result As MessageBoxResult
            result = MessageBox.Show("Do you want to leave this page?", "Navigation Request", MessageBoxButton.YesNo)

            ' If the user doesn't want to navigate away, cancel the navigation
            If result = MessageBoxResult.No Then
                e.Cancel = True
            End If
        End Sub
    End Class
End Namespace

如果用来自 Page 的导航事件注册处理程序,就像前面的示例那样,就必须同时取消注册事件处理程序。 如果不这样做,就可能发生有关 WPF 如何使用日志记住 Page 导航的副作用。

使用日志记住导航

WPF 使用两个堆栈来记住导航过的页:后退堆栈和前进堆栈。 从当前 Page 导航到新 Page 或前进到现有 Page 时,当前 Page 会添加到后退堆栈中。 从当前 Page 导航到上一个 Page 时,当前 Page 会添加到前进堆栈中。 后退堆栈、前进堆栈和管理它们的功能统称为日志。 后退堆栈和前进堆栈中的每一项都是 JournalEntry 类的实例,称为日志条目

从概念上来说,日志的操作方式和 Internet Explorer 中的“后退”和“前进”按钮一样。 这些在下图中显示。

Back and Forward buttons

对于由 Internet Explorer 托管的 XBAP,WPF 将日志集成到 Internet Explorer 的导航 UI 中。 这使用户可以使用 Internet Explorer 中的“后退”、“前进”和“最近访问的页面”按钮在 XBAP 中导航页

重要

在 Internet Explorer 中,当用户导航离开并返回到 XBAP 时,只有不保持活动状态的页的日志条目保留在日志中。 有关使页保持活动状态的讨论,请参阅本主题后面的页生存期和日志

默认情况下,出现在的 Internet Explorer“最近访问的页面”列表中的每个 Page 都是 Page 的 URI。 很多情况下这对用户并没有什么特殊的意义。 幸运的是,可以使用以下选项更改文本:

  1. 附加的 JournalEntry.Name 属性值。

  2. Page.Title 属性值。

  3. Page.WindowTitle 属性值和当前 Page 的 URI。

  4. 当前 Page 的 URI。 (默认值)

选项列出的顺序和查找文本的优先级顺序一致。 例如,如果设置了 JournalEntry.Name,就忽略其他值。

以下示例使用 Page.Title 属性来更改日志条目中出现的文本。

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.PageWithTitle"
    Title="This is the title of the journal entry for this page.">
</Page>
using System.Windows.Controls;

namespace SDKSample
{
    public partial class PageWithTitle : Page
    {

Namespace SDKSample
    Partial Public Class PageWithTitle
        Inherits Page
    }
}
    End Class
End Namespace

虽然用户可以通过使用 Internet Explorer 中的“后退”、“前进”和“最近访问的页面”导航日志,但也可以使用 WPF 提供的声明和编程机制导航日志。 这样做的原因之一是在页中提供自定义导航 UI。

可以通过使用 NavigationCommands 公开的导航命令以声明方式添加日志导航支持。 以下示例演示如何使用 BrowseBack 导航命令。

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.NavigationCommandsPage">
<Hyperlink Command="NavigationCommands.BrowseBack">Back</Hyperlink>
<Hyperlink Command="NavigationCommands.BrowseForward">Forward</Hyperlink>
</Page>

可以使用 NavigationService 类的以下成员之一以编程方式导航日志:

还可以以编程方式操作日志,如本主题后面的保留导航历史记录的内容状态中所讨论。

页生存期和日志

请考虑 XBAP 有包含丰富内容(包括图形、动画和媒体)的很多页。 这类页的内存占用量可能相当大,尤其是使用视频和音频媒体的时候。 考虑到日志“记住”导航到的页,此类 XBAP 可能会快速消耗大量明显的内存量。

出于此原因,日志的默认行为是存储每个日志条目的 Page 元数据,而不是引用 Page 对象。 导航到日志条目时,Page 元数据用于创建指定 Page 的新实例。 因此,每个导航的 Page 都有下图演示的生存期。

Page lifetime

尽管使用默认的日志记录行为可以节省内存消耗,但可能降低每页的呈现性能,重新实例化 Page 可能很耗时,尤其当它有很多内容时。 如果需要在日志中保留 Page 实例,有两项技术可以实现。 首先,可通过调用 NavigationService.Navigate 方法,以编程方式导航到 Page 对象。

其次,可以通过将 KeepAlive 属性设置为 true(默认值为 false),来指定 WPF 在日志中保留 Page 的实例。 如以下示例所示,可以在标记中以声明方式设置 KeepAlive

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.KeepAlivePage"
    KeepAlive="True">
  
  An instance of this page is stored in the journal.
  
</Page>

保持为活动状态的 Page 的生存期和不保持为活动状态的稍有不同。 首次导航到保持为活动状态的 Page 时,它会像不保持为活动状态的 Page 一样进行实例化。 但是,由于 Page 的实例保留在日志中,它只要留在日志中就不会再次实例化。 因此,如果 Page 具有需要每次导航到 Page 时调用的初始化逻辑,就应将其从构造函数移动到 Loaded 事件的处理程序。 如下图所示,每次导航到 Page 和从它进行导航时仍然会分别引发 LoadedUnloaded 事件。

When the Loaded and Unloaded events are raised

Page 不保持为活动状态时,不应执行以下任一操作:

  • 存储对它或它的任何部分的引用。

  • 将事件处理程序注册到并非由其实现的事件。

执行任一操作都将创建引用,将 Page 强制保留在内存中,即便在已从日志中移除后。

通常情况下,应优先使用默认 Page 行为,即不使 Page 处于活动状态。 但是,这会存在将在下一节中讨论的状态影响。

保留导航历史记录的内容状态

如果 Page 不保持为活动状态,并且还具有从用户收集数据的控件,那么如果用户导航从 Page 离开以及导航回来,数据会发生什么? 从用户体验角度,用户应该会希望看到他们以前输入的数据。 很遗憾,因为 Page 的新实例是随每个导航创建的,所以收集数据的控件会重新实例化,而且数据会丢失。

幸运的是,日志提供跨 Page 导航记住数据的支持,包括控件数据。 具体而言,每个 Page 的日志条目充当已关联 Page 状态的临时容器。 以下步骤概述了从 Page 导航时如何使用这一支持:

  1. 当前 Page 的一个条目添加到日志。

  2. Page 的状态与该页的日志条目一同存储,添加到后退堆栈。

  3. 导航到新的 Page

当使用日志导航回页 Page 时,发生以下步骤:

  1. Page(后退堆栈上的最顶部日志条目)进行实例化。

  2. Page 使用与 Page 的日志条目一同存储的状态进行刷新。

  3. 导航回到 Page

Page 上使用以下控件时,WPF 自动使用这一支持:

如果 Page 使用这些控件,将跨 Page 导航记住输入的数据,如下图中的 Favorite ColorListBox 所示。

Page with controls that remember state

Page 具有不同于前面列表中的控件,或当状态存储在自定义对象中时,需要编写代码来使日志跨 Page 导航记住状态。

如果需要跨 Page 导航记住小段状态,可以使用由 FrameworkPropertyMetadata.Journal 元数据标志配置的依赖属性(请参阅 DependencyProperty)。

如果 Page 需要跨导航记住的状态包含多个数据段,可能会发现将状态封装在单个类中并实现 IProvideCustomContentState 接口会降低代码强度。

如果需要在单个 Page 的不同状态中导航,而无需从 Page 自身导航,可以使用 IProvideCustomContentStateNavigationService.AddBackEntry

Cookie

WPF 应用程序可以存储数据的另一种方法是使用 cookie,可使用 SetCookieGetCookie 方法来创建、更新和删除它。 可在 WPF 中创建的 cookie 与其他类型的 Web 应用程序使用的 cookie 相同;cookie 是应用程序在应用程序会话过程中或会话之间存储在客户端计算机上的任意数据片段。 Cookie 数据通常采用以下格式的名称/值对的形式。

名称=

当将数据连同应为其设置 cookie 的位置的 Uri 一起传递到 SetCookie,会在内存中创建 cookie,它仅在当前应用程序会话持续时间内可用。 此类 cookie 称为会话 cookie

要跨应用程序会话存储 cookie,必须使用以下格式将到期日期添加到 cookie。

NAME=VALUE; expires=DAY, DD-MMM-YYYY HH:MM:SS GMT

带有到期日期的 cookie 存储在当前 Windows 安装的 Internet 临时文件夹中,直至到期。 这样的 cookie 称为永久性 cookie,因为它可以跨应用程序会话保持。

可通过调用 GetCookie 方法、传递使用 SetCookie 方法设置 cookie 之处的 Uri 来检索会话和永久性 cookie。

以下是 WPF 中支持 cookie 的一些方式:

  • WPF 独立应用程序和 XBAP 都可以创建和管理 cookie。

  • 由 XBAP 创建的 cookie 可以从浏览器访问。

  • 来自相同域的 XBAP 可以创建和共享 cookie。

  • 来自相同域的 XBAP 和 HTML 页可以创建和共享 cookie。

  • XBAP 和松散 XAML 页发出 Web 请求时调度 cookie。

  • 顶级 XBAP 和 IFRAMES 中托管的 XBAP 可以访问 cookie。

  • WPF 中支持的 cookie 对所有受支持的浏览器相同。

  • 在 Internet Explorer 中,适用于 cookie 的 P3P 策略遵循 WPF,特别是涉及第一方和第三方 XBAP。

结构化导航

如需将数据从一个 Page 传递到另一个,可以将数据作为参数传递给 Page 的非无参数构造函数。 如果使用此技术,请注意,必须保持 Page 处于活动状态;否则,下次导航到 Page 时,WPF 会使用无参数构造函数重新实例化 Page

或者,Page 可以实现使用需要传递的数据设置的属性。 但当 Page 需要将数据传递回导航到的 Page 时,事情就变得复杂。 问题是导航本身并不支持保证从 Page 导航之后会返回到它的机制。 实质上,导航不支持调用/返回语义。 为了解决此问题,WPF 提供可以用来确保 Page 按可预计的结构化方式返回的 PageFunction<T> 类。 有关详细信息,请参阅结构化导航概述

NavigationWindow 类

到目前为止,你已全面了解最有可能用可导航内容生成应用程序的导航服务。 这些服务在 XBAP 的上下文中进行了讨论,不过它们并不局限于 XBAP。 现代操作系统和 Windows 应用程序利用现代用户的浏览器体验将浏览器样式导航融入独立应用程序中。 常见示例包括:

  • Word 同义词库:导航字选择。

  • 文件资源管理器:导航文件和文件夹。

  • 向导:将复杂任务分为多页,可以在它们之间导航。 一个示例是处理添加和移除 Windows 功能的 Windows 组件向导。

要将浏览器样式导航并入独立应用程序,可以使用 NavigationWindow 类。 NavigationWindow 派生自 Window,并用 XBAP 所提供的相同导航支持进行了扩展。 可以使用 NavigationWindow 作为独立应用程序的主窗口或者辅助窗口(如对话框)。

要在 WPF 中实现和大多数顶级类(WindowPage 等等)相同的 NavigationWindow,可以使用标记和代码隐藏的组合。 这在下面的示例中显示。

<NavigationWindow
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="SDKSample.MainWindow" 
    Source="HomePage.xaml"/>
using System.Windows.Navigation;

namespace SDKSample
{
    public partial class MainWindow : NavigationWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

Namespace SDKSample
    Partial Public Class MainWindow
        Inherits NavigationWindow
        Public Sub New()
            InitializeComponent()
        End Sub
    End Class
End Namespace

此代码创建 NavigationWindow,它在打开 NavigationWindow 时自动导航到 Page (HomePage.xaml)。 如果 NavigationWindow 是主应用程序窗口,则可以使用 StartupUri 属性启动。 这在以下标记中显示。

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    StartupUri="MainWindow.xaml" />

下图显示作为独立应用程序主窗口的 NavigationWindow

A main window

从图中可以看出,NavigationWindow 具有标题,尽管并非在来自前述示例的 NavigationWindow 实现代码中设置。 相反,标题是使用 WindowTitle 属性设置的,如以下代码中所示。

<Page 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Title="Home Page"
    WindowTitle="NavigationWindow">
</Page>

设置 WindowWidthWindowHeight 属性也会影响 NavigationWindow

通常情况下,需要自定义其行为或外观时就需要实现自己的 NavigationWindow。 如果没有执行上述操作,则可以使用快捷方式。 如果将 Page 的 pack URI 指定为独立应用程序中的 StartupUri,则 Application 自动创建 NavigationWindow 来托管 Page。 以下标记显示如何实现此功能。

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    StartupUri="HomePage.xaml" />

如果希望辅助应用程序窗口(如对话框)成为 NavigationWindow,可以使用以下示例中的代码来打开它。

// Open a navigation window as a dialog box
NavigationWindowDialogBox dlg = new NavigationWindowDialogBox();
dlg.Source = new Uri("HomePage.xaml", UriKind.Relative);
dlg.Owner = this;
dlg.ShowDialog();
' Open a navigation window as a dialog box
Dim dlg As New NavigationWindowDialogBox()
dlg.Source = New Uri("HomePage.xaml", UriKind.Relative)
dlg.Owner = Me
dlg.ShowDialog()

下图显示结果。

A dialog box

如你所见,NavigationWindow 显示 Internet Explorer 样式的“后退”和“前进”按钮,允许用户导航日志。 这些按钮提供相同的用户体验,如下图所示。

Back and Forward buttons in a NavigationWindow

如果页面提供自己的日志导航支持和 UI,可以通过将 ShowsNavigationUI 属性的值设置为 false 来隐藏 NavigationWindow 显示的“后退”和“前进”按钮

或者,可以使用 WPF 中的自定义支持替换 NavigationWindow 自己的 UI。

框架类

浏览器和 NavigationWindow 都是托管可导航内容的窗口。 在某些情况下,应用程序具有无需整个窗口托管的内容。 相反,此类内容在其他内容中托管。 可以使用 Frame 类将可导航内容插入到其他内容中。 Frame 提供与 NavigationWindow 和 XBAP 相同的支持。

下面的示例演示如何使用 Frame 元素以声明方式将 Frame 添加到 Page

<Page 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  WindowTitle="Page that Hosts a Frame"
  WindowWidth="250"
  WindowHeight="250">
<Frame Source="FramePage1.xaml" />
</Page>

此标记使用 Frame 应最初导航到的 Page 的 pack URI 来设置 Frame 元素的 Source 属性。 下图显示具有 Page(具有在多页间导航的 Frame)的 XBAP。

A frame that has navigated between multiple pages

不仅需要在 Page 的内容中使用 Frame。 在 Window 的内容中托管 Frame 也很常见。

默认情况下,Frame 在缺少其他日志时仅使用它自己的日志。 如果 FrameNavigationWindow 或 XBAP 内所托管的部分内容,则 Frame 使用属于 NavigationWindow 或 XBAP 的日志。 不过有时 Frame 可能需要对它自己的日志负责。 这样做的原因之一是允许在 Frame 托管的页内进行日志导航。 这由下图说明。

Frame and Page diagram

在这种情况下,可以通过将 FrameJournalOwnership 属性设置为 OwnsJournal,配置 Frame 以使用其自己的日记。 这在以下标记中显示。

<Page 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  WindowTitle="Page that Hosts a Frame"
  WindowWidth="250"
  WindowHeight="250">
<Frame Source="FramePage1.xaml" JournalOwnership="OwnsJournal" />
</Page>

下图说明在使用自己日志的 Frame 中导航的效果。

A frame that uses its own journal

请注意,日志条目由 Frame(而不是 Internet Explorer)中的导航 UI来显示。

注意

如果 FrameWindow 中托管内容的一部分,Frame 使用自己的日志,并继而显示自己的导航 UI。

如果用户体验需要 Frame 提供自己的日志而不显示导航 UI,可以通过将 NavigationUIVisibility 设置为 Hidden 来隐藏导航 UI。 这在以下标记中显示。

<Page 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  WindowTitle="Page that Hosts a Frame"
  WindowWidth="250"
  WindowHeight="250">
<Frame 
  Source="FramePage1.xaml" 
  JournalOwnership="OwnsJournal" 
  NavigationUIVisibility="Hidden" />
</Page>

FrameNavigationWindow 是被称为导航主机的类。 导航主机是可以导航到并显示内容的类。 要实现此目的,每个导航主机都使用自己的 NavigationService 和日志。 导航主机的基本构造在下图中显示。

Navigator diagrams

实质上,这允许 NavigationWindowFrame 提供 XBAP 在浏览器中托管时所提供的相同导航支持。

除了使用 NavigationService 和日志,导航主机和 NavigationService 实现相同的成员。 这由下图说明。

A journal in a Frame and in a NavigationWindow

这就允许直接对它们进行导航支持编程。 如果需要为 Window 中托管的 Frame 提供自定义导航 UI,则可以考虑。 此外,两种类型都实现其他导航相关的成员,包括 BackStackNavigationWindow.BackStackFrame.BackStack)和 ForwardStackNavigationWindow.ForwardStackFrame.ForwardStack),使你可以分别枚举后退堆栈和前进堆栈中的日志条目。

如之前提及,应用程序中可以存在不止一个日志。 下图提供何时可能发生这种情况的示例。

Multiple journals within one application

贯穿本主题,Page 和 pack XBAP 被用来演示 WPF 的各种导航功能。 但是,编译到应用程序中的 Page 并非唯一一类可以导航到的内容,pack XBAP 并非识别内容的唯一方法。

如本节所示,也可以导航到松散 XAML 文件、HTML 文件和对象。

松散 XAML 文件具有以下特征:

  • 仅包含 XAML(即无代码)。

  • 具有适当的命名空间声明。

  • 具有 .xaml 文件扩展名。

例如,假设以下内容存储为松散 XAML 文件 Person.xaml。

<!-- Person.xaml -->
<TextBlock xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <TextBlock FontWeight="Bold">Name:</TextBlock>
  <TextBlock>Nancy Davolio</TextBlock>
  <LineBreak />
  <TextBlock FontWeight="Bold">Favorite Color:</TextBlock>
  <TextBlock>Yellow</TextBlock>
</TextBlock>

双击该文件时,浏览器打开、导航到并显示内容。 如下图所示。

Display of the content in the Person.XAML file

也可从以下位置显示松散 XAML 文件:

  • 本地计算机上的网站、Intranet 或 Internet。

  • 通用命名约定 (UNC) 文件共享。

  • 本地磁盘。

松散 XAML 文件可以添加到浏览器收藏夹或浏览器主页。

注意

有关发布和启动松散 XAML 页的详细信息,请参阅部署 WPF 应用程序

松散 XAML 的限制之一是只能托管可安全运行于部分信任的内容。 例如,Window 不能是松散 XAML 文件的根元素。 有关详细信息,请参阅 WPF 部分信任安全性

你可能希望还可以导航到 HTML。 仅需提供使用 http 方案的 URI。 例如,以下 XAML 显示导航到 HTML 页的 Frame

<Frame Source="http://www.microsoft.com/default.aspx" />

导航到 HTML 需要特殊权限。 例如,不能从 Internet 区域部分信任安全沙盒中运行的 XBAP 导航。 有关详细信息,请参阅 WPF 部分信任安全性

WebBrowser 控件支持 HTML 文档托管、导航和脚本/托管代码互操作性。 有关 WebBrowser 控件的详细信息,请参阅 WebBrowser

Frame 一样,使用 WebBrowser 导航到 HTML 需要特殊权限。 例如,从部分信任应用程序只能导航到位于源站点的 HTML。 有关详细信息,请参阅 WPF 部分信任安全性

如果有存储为自定义对象的数据,显示数据的一种方法是创建具有绑定到这些对象的内容的 Page(请参阅数据绑定概述)。 如果无需创建整个页面而只要显示对象,则可以直接导航到它们。

请考虑在以下代码中实现的 Person 类。

using System.Windows.Media;

namespace SDKSample
{
    public class Person
    {
        string name;
        Color favoriteColor;

        public Person() { }
        public Person(string name, Color favoriteColor)
        {
            this.name = name;
            this.favoriteColor = favoriteColor;
        }

        public string Name
        {
            get { return this.name; }
            set { this.name = value; }
        }

        public Color FavoriteColor
        {
            get { return this.favoriteColor; }
            set { this.favoriteColor = value; }
        }
    }
}

Namespace SDKSample
    Public Class Person
        Private _name As String
        Private _favoriteColor As Color

        Public Sub New()
        End Sub
        Public Sub New(ByVal name As String, ByVal favoriteColor As Color)
            Me._name = name
            Me._favoriteColor = favoriteColor
        End Sub

        Public Property Name() As String
            Get
                Return Me._name
            End Get
            Set(ByVal value As String)
                Me._name = value
            End Set
        End Property

        Public Property FavoriteColor() As Color
            Get
                Return Me._favoriteColor
            End Get
            Set(ByVal value As Color)
                Me._favoriteColor = value
            End Set
        End Property
    End Class
End Namespace

要导航到它,可以调用 NavigationWindow.Navigate 方法,如以下代码所示。

<Page 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="SDKSample.HomePage"
  WindowTitle="Page that Navigates to an Object">
<Hyperlink Name="hyperlink" Click="hyperlink_Click">
  Navigate to Nancy Davolio
</Hyperlink>
</Page>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace SDKSample
{
    public partial class HomePage : Page
    {
        public HomePage()
        {
            InitializeComponent();
        }

        void hyperlink_Click(object sender, RoutedEventArgs e)
        {
            Person person = new Person("Nancy Davolio", Colors.Yellow);
            this.NavigationService.Navigate(person);
        }
    }
}

Namespace SDKSample
    Partial Public Class HomePage
        Inherits Page
        Public Sub New()
            InitializeComponent()
        End Sub

        Private Sub hyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim person As New Person("Nancy Davolio", Colors.Yellow)
            Me.NavigationService.Navigate(person)
        End Sub
    End Class
End Namespace

下图显示结果。

A page that navigates to a class

从此图可以看出,没有显示任何有用信息。 实际上,显示的值是 Person 对象 ToString 方法的返回值;默认情况下,这是 WPF 可以用来表示对象的唯一值。 可以重写 ToString 方法来返回更多有用信息,尽管它仍将仅为字符串值。 可以使用的利用 WPF 演示功能的一种技术是使用数据模板。 可以实现 WPF 可以将其与特殊类型对象关联的数据模板。 以下代码显示 Person 对象的数据模板。

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SDKSample" 
    x:Class="SDKSample.App"
    StartupUri="HomePage.xaml">

  <Application.Resources>

    <!-- Data Template for the Person Class -->
    <DataTemplate DataType="{x:Type local:Person}">
      <TextBlock xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
        <TextBlock FontWeight="Bold">Name:</TextBlock>
        <TextBlock Text="{Binding Path=Name}" />
        <LineBreak />
        <TextBlock FontWeight="Bold">Favorite Color:</TextBlock>
        <TextBlock Text="{Binding Path=FavoriteColor}" />
      </TextBlock>
    </DataTemplate>
    
  </Application.Resources>

</Application>

此处,数据模板通过在 DataType 属性中使用 x:Type 标记扩展与 Person 类型关联。 数据模板随后将 TextBlock 元素(请参阅 TextBlock)绑定到 Person 类的属性。 下图显示了更新后 Person 对象的外观。

Navigating to a class that has a data template

此技术的优势之一在于能够通过重复使用数据模板以在应用程序任意位置一致地显示对象而获得一致性。

有关数据模板的详细信息,请参阅数据模板化概述

安全性

WPF 导航支持允许跨 Internet 导航 XBAP,并允许应用程序托管第三方内容。 为了保护应用程序和用户不受有害行为影响,WPF 提供在安全性WPF 部分信任安全中讨论的各种安全功能。

另请参阅