结构化导航概述

由 XAML 浏览器应用程序(XBAP)、FrameNavigationWindow 承载的内容 是由通过包统一资源标识符(URI)识别并通过超链接导航的页面组成的。 页面的结构及其导航方式(由超链接定义)称为导航拓扑。 此类拓扑适用于各种应用程序类型,尤其是浏览文档的应用程序类型。 对于此类应用程序,用户可以从一个页面导航到另一个页面,而无需了解其他任何页面。

然而,其他类型的应用程序有些页面需要知道它们被导航到何时。 例如,考虑一个人力资源应用程序,该应用程序有一页可列出组织中的所有员工,即“列出员工”页面。 此页面还可以允许用户通过单击超链接来添加新员工。 单击后,页面将导航到“添加员工”页以收集新员工的详细信息,并将其返回到“列出员工”页以创建新员工并更新该列表。 这种导航样式类似于调用方法来执行某些处理并返回一个称为结构化编程的值。 因此,这种导航样式称为 结构化导航

Page 类不实现对结构化导航的支持。 相反,PageFunction<T> 类派生自 Page,并使用结构化导航所需的基本构造对其进行扩展。 本主题演示如何使用 PageFunction<T>建立结构化导航。

结构化导航

当一个页面调用结构化导航中的另一个页面时,需要以下一些或全部行为:

  • 调用页导航到所调用页面,可以选择传递被调用页面所需的参数。

  • 当用户使用呼叫页面完成时,调用页会专门返回给呼叫页面,可以选择:

    • 返回描述呼叫页面完成方式的状态信息(例如,用户按下了“确定”按钮还是“取消”按钮)。

    • 返回从用户收集的数据(例如,新员工详细信息)。

  • 当调用页返回到被调用的页面时,将从导航历史记录中删除被调用的页面,以将调用页面的一个实例与另一个实例隔离开来。

下图说明了这些行为:

屏幕截图显示了调用页面和被调用页面之间的流。

可以通过使用 PageFunction<T> 作为调用页来实现这些行为。

使用 PageFunction 进行结构化导航

本主题介绍如何实现涉及单个 PageFunction<T>的结构化导航过程的基本原理。 在此示例中,Page 调用 PageFunction<T>,从用户获取 String 值并返回该值。

创建呼叫页面

调用 PageFunction<T> 的页面可以是 PagePageFunction<T>。 在这个示例中,它是一个Page,具体情况请参见以下代码。

<Page 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="StructuredNavigationSample.CallingPage"
    WindowTitle="Calling Page" 
    WindowWidth="250" WindowHeight="150">
</Page>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;

namespace StructuredNavigationSample
{
    public partial class CallingPage : Page
    {
        public CallingPage()
        {
            InitializeComponent();
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Navigation

Namespace StructuredNavigationSample

Public Class CallingPage
    Inherits Page
    Public Sub New()
        Me.InitializeComponent()
}
End Sub
    }
}
End Class

End Namespace

创建要调用的页面函数

由于调用页可以使用调用页来收集和返回用户的数据,因此 PageFunction<T> 实现为泛型类,其类型参数指定调用页将返回的值的类型。 以下代码使用返回 StringPageFunction<T>显示调用页的初始实现。

<PageFunction
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib" 
    x:Class="StructuredNavigationSample.CalledPageFunction"
    x:TypeArguments="sys:String"
    Title="Page Function" 
    WindowWidth="250" WindowHeight="150">

  <Grid Margin="10">

    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition />
    </Grid.RowDefinitions>

    <!-- Data -->
    <Label Grid.Column="0" Grid.Row="0">DataItem1:</Label>
    <TextBox Grid.Column="1" Grid.Row="0" Name="dataItem1TextBox"></TextBox>

    <!-- Accept/Cancel buttons -->
    <TextBlock Grid.Column="1" Grid.Row="1" HorizontalAlignment="Right">
      <Button Name="okButton" IsDefault="True" MinWidth="50">OK</Button>
      <Button Name="cancelButton" IsCancel="True" MinWidth="50">Cancel</Button>
    </TextBlock>

  </Grid>

</PageFunction>
using System;
using System.Windows;
using System.Windows.Navigation;

namespace StructuredNavigationSample
{
    public partial class CalledPageFunction : PageFunction<String>
    {
        public CalledPageFunction()
        {
            InitializeComponent();
        }
Imports System.Windows
Imports System.Windows.Navigation

Namespace StructuredNavigationSample

Public Class CalledPageFunction
    Inherits PageFunction(Of String)
    Public Sub New()
        Me.InitializeComponent()
    End Sub
    }
}
End Class

End Namespace

PageFunction<T> 的声明与 Page 的声明类似,只是增加了类型参数。 如您从代码示例中所见,类型参数既在 XAML 标记中通过 x:TypeArguments 属性指定,又在后台代码中使用标准的泛型类型参数语法指定。

无需仅使用 .NET Framework 类作为类型参数。 可以调用 PageFunction<T> 以收集作为自定义类型抽象的特定于域的数据。 以下代码演示如何将自定义类型用作 PageFunction<T>的类型参数。

namespace SDKSample
{
    public class CustomType
    {
Public Class CustomType
    }
}
End Class
<PageFunction
    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.CustomTypePageFunction"
    x:TypeArguments="local:CustomType">
</PageFunction>
using System.Windows.Navigation;

namespace SDKSample
{
    public partial class CustomTypePageFunction : PageFunction<CustomType>
    {
Partial Public Class CustomTypePageFunction
    Inherits System.Windows.Navigation.PageFunction(Of CustomType)
    }
}
End Class

PageFunction<T> 的类型参数为调用页和被调用页之间的通信提供了基础,这些通信在以下部分中进行了讨论。

如你所看到的,使用 PageFunction<T> 声明标识的类型在将数据从 PageFunction<T> 返回到调用页方面发挥了重要作用。

调用 PageFunction 并传递参数

若要调用页面,调用页必须实例化调用的页面,并使用 Navigate 方法导航到该页面。 这样,调用页就可以将初始数据传递到被调用页,例如被调用页收集的数据的默认值。

以下代码显示调用页,其中包含一个非无参数构造函数,用于接受来自调用页的参数。

using System;
using System.Windows;
using System.Windows.Navigation;

namespace StructuredNavigationSample
{
    public partial class CalledPageFunction : PageFunction<String>
    {
Imports System.Windows
Imports System.Windows.Navigation

Namespace StructuredNavigationSample

Public Class CalledPageFunction
    Inherits PageFunction(Of String)
public CalledPageFunction(string initialDataItem1Value)
{
    InitializeComponent();

Public Sub New(ByVal initialDataItem1Value As String)
    Me.InitializeComponent()
    // Set initial value
    this.dataItem1TextBox.Text = initialDataItem1Value;
}
    ' Set initial value
    Me.dataItem1TextBox.Text = initialDataItem1Value
End Sub
    }
}
End Class

End Namespace

以下代码显示了处理 HyperlinkClick 事件的调用页,以实例化被调用的页面并向其传递初始字符串值。

<Hyperlink Name="pageFunctionHyperlink">Call Page Function</Hyperlink>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;

namespace StructuredNavigationSample
{
    public partial class CallingPage : Page
    {
        public CallingPage()
        {
            InitializeComponent();
            this.pageFunctionHyperlink.Click += new RoutedEventHandler(pageFunctionHyperlink_Click);
        }
        void pageFunctionHyperlink_Click(object sender, RoutedEventArgs e)
        {

            // Instantiate and navigate to page function
            CalledPageFunction CalledPageFunction = new CalledPageFunction("Initial Data Item Value");
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Navigation

Namespace StructuredNavigationSample

Public Class CallingPage
    Inherits Page
    Public Sub New()
        Me.InitializeComponent()
        AddHandler Me.pageFunctionHyperlink.Click, New RoutedEventHandler(AddressOf Me.pageFunctionHyperlink_Click)
    End Sub
    Private Sub pageFunctionHyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
}
End Sub
    }
}
End Class

End Namespace

无需将参数传递给调用页。 相反,可以执行以下操作:

但是,正如你很快就会看到的那样,你仍然需要使用代码来实例化并导航到被调用的页面来收集被调用页面返回的数据。 因此,PageFunction<T> 需要保持活动状态;否则,下次导航到 PageFunction<T>时,WPF 将使用无参数构造函数实例化 PageFunction<T>

但是,在调用页可以返回之前,它需要返回可由调用页检索的数据。

将任务结果和任务数据从任务返回到调用页面

用户在调用页面上操作完成后,在此示例中可以通过按下“确定”或“取消”按钮来表示完成,之后调用的页面需要返回。 由于呼叫页使用调用页从用户收集数据,调用页需要两种类型的信息:

  1. 用户是否取消了被调用的页面(通过按本示例中的“确定”按钮或“取消”按钮)。 这样,调用页就可以确定是否要处理从用户收集的数据。

  2. 用户提供的数据。

若要返回信息,PageFunction<T> 实现 OnReturn 方法。 以下代码演示如何调用它。

using System;
using System.Windows;
using System.Windows.Navigation;

namespace StructuredNavigationSample
{
    public partial class CalledPageFunction : PageFunction<String>
    {
Imports System.Windows
Imports System.Windows.Navigation

Namespace StructuredNavigationSample

Public Class CalledPageFunction
    Inherits PageFunction(Of String)
        void okButton_Click(object sender, RoutedEventArgs e)
        {
            // Accept when Ok button is clicked
            OnReturn(new ReturnEventArgs<string>(this.dataItem1TextBox.Text));
        }

        void cancelButton_Click(object sender, RoutedEventArgs e)
        {
            // Cancel
            OnReturn(null);
        }
    }
}
    Private Sub okButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        ' Accept when Ok button is clicked
        Me.OnReturn(New ReturnEventArgs(Of String)(Me.dataItem1TextBox.Text))
    End Sub

    Private Sub cancelButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        ' Cancel
        Me.OnReturn(Nothing)
    End Sub
End Class

End Namespace

在此示例中,如果用户按下“取消”按钮,则会将 null 值返回到调用页。 如果改为按下“确定”按钮,则返回用户提供的字符串值。 OnReturn 是一种 protected virtual 方法,可以调用该方法将数据返回到调用页。 数据需要打包在泛型 ReturnEventArgs<T> 类型的实例中,其类型参数指定 Result 返回的值的类型。 这样,当你声明具有特定类型参数的 PageFunction<T> 时,你指出 PageFunction<T> 将返回类型参数指定的类型的实例。 在此示例中,类型参数,因此,返回值的类型为 String

调用 OnReturn 时,调用页需要某种方式接收 PageFunction<T>的返回值。 因此,PageFunction<T> 实现 Return 事件,以便调用要处理的页面。 调用 OnReturn 时,将引发 Return,以便调用页面可以在 Return 上注册以接收通知。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;

namespace StructuredNavigationSample
{
    public partial class CallingPage : Page
    {
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Navigation

Namespace StructuredNavigationSample

Public Class CallingPage
    Inherits Page
        void pageFunctionHyperlink_Click(object sender, RoutedEventArgs e)
        {

            // Instantiate and navigate to page function
            CalledPageFunction CalledPageFunction = new CalledPageFunction("Initial Data Item Value");
            CalledPageFunction.Return += pageFunction_Return;
            this.NavigationService.Navigate(CalledPageFunction);
        }
        void pageFunction_Return(object sender, ReturnEventArgs<string> e)
        {
            this.pageFunctionResultsTextBlock.Visibility = Visibility.Visible;

            // Display result
            this.pageFunctionResultsTextBlock.Text = (e != null ? "Accepted" : "Canceled");

            // If page function returned, display result and data
            if (e != null)
            {
                this.pageFunctionResultsTextBlock.Text += "\n" + e.Result;
            }
        }
    }
}
    Private Sub pageFunctionHyperlink_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        ' Instantiate and navigate to page function
        Dim calledPageFunction As New CalledPageFunction("Initial Data Item Value")
        AddHandler calledPageFunction.Return, New ReturnEventHandler(Of String)(AddressOf Me.calledPageFunction_Return)
        MyBase.NavigationService.Navigate(calledPageFunction)
    End Sub
    Private Sub calledPageFunction_Return(ByVal sender As Object, ByVal e As ReturnEventArgs(Of String))

        Me.pageFunctionResultsTextBlock.Visibility = Windows.Visibility.Visible

        ' Display result
        Me.pageFunctionResultsTextBlock.Text = IIf((Not e Is Nothing), "Accepted", "Canceled")

        ' If page function returned, display result and data
        If (Not e Is Nothing) Then
            Me.pageFunctionResultsTextBlock.Text = (Me.pageFunctionResultsTextBlock.Text & ChrW(10) & e.Result)
        End If

    End Sub
End Class

End Namespace

任务完成时删除任务页

当被调用的页面返回并且用户未取消被调用的页面时,调用页将处理用户提供的数据,并从调用的页面返回。 以这种方式获取数据通常是独立的活动;当调用页返回时,调用页需要创建并导航到新的调用页来捕获更多数据。

但是,除非从日记中删除了被调用的页面,否则用户将能够导航回调用页的上一个实例。 PageFunction<T> 是否保留在日志中由 RemoveFromJournal 属性决定。 默认情况下,调用 OnReturn 时会自动删除页面函数,因为 RemoveFromJournal 设置为 true。 若要在调用 OnReturn 后将页面函数保留在导航历史记录中,请将 RemoveFromJournal 设置为 false

其他类型的结构化导航

本主题演示了支持调用/返回结构化导航的 PageFunction<T> 的最基本用法。 此基础使你能够创建更复杂的结构化导航类型。

例如,有时调用页需要多个页面,以便从用户收集足够的数据或执行任务。 使用多个页面的方式被称为“向导”。

在其他情况下,应用程序可能具有复杂的导航拓扑,这些拓扑依赖于结构化导航来有效运行。 有关详细信息,请参阅 导航拓扑概述

另请参阅