结构化导航概述
可由 XAML 浏览器应用程序 (XBAP)、Frame 或 NavigationWindow 托管的内容由多个可以通过 pack 统一资源标识符 (URI) 标识并且可以通过超链接导航到的页面组成。 页面的结构以及导航页面的方式(通过超链接来定义)称为导航拓扑。 此类拓扑适合各种应用程序类型,尤其适合在文档之间导航的应用程序类型。 对于此类应用程序,用户可以从一个页面导航到另一个页面,并且其中任一页面都无需了解另一页面的任何信息。
但是,对于其他类型的应用程序,在其页面之间导航时,确实需要了解这些页面信息。 例如,假设一个人力资源应用程序,它具有一个列出组织中所有员工的页面,即“员工列表”页。 此页还允许用户通过单击超链接添加新员工。 单击超链接后,页面会导航到“添加员工”页以收集新员工的详细信息,并将其返回到“员工列表”页以创建新员工并更新列表。 这种样式的导航与调用方法来执行某些处理并返回值(称为结构化编程)类似。 同样,这种样式的导航称为结构化导航。
Page 类未实现对结构化导航的支持。 相反,PageFunction<T> 类派生自 Page,并使用结构化导航所需的基本构造来对它进行扩展。 本主题演示如何使用 PageFunction<T> 建立结构化导航。
结构化导航
在结构化导航中,当一个页面调用另一个页面时需要以下部分或全部行为:
调用页导航到被调用页,并且可以选择性地传递被调用页所需的参数。
当用户已使用完调用页时,被调用页将专门返回到调用页,并且可以:
返回描述调用页是如何完成(例如,用户按的是“确定”按钮还是“取消”按钮)的状态信息。
返回从用户那里收集的数据(例如,新员工的详细信息)。
当调用页返回到被调用页时,被调用页会从导航历史记录中删除,以便将被调用页的一个实例与另一个实例隔离开。
下图阐释了这些行为:
可以通过将 PageFunction<T> 用作被调用页来实现这些行为。
使用 PageFunction 进行结构化导航
本主题演示如何实现包含一个 PageFunction<T> 的结构化导航的基本机制。 在此示例中,Page 调用 PageFunction<T>,以便从用户那里获取 String 值并返回该值。
创建调用页
调用 PageFunction<T> 的页可以是 Page,也可以是 PageFunction<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> 实现为一个泛型类,它的类型参数会指定被调用页将返回的值的类型。 以下代码示例演示了使用 PageFunction<T> 的被调用页的初始实现,这将返回 String。
<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
下面的代码演示调用页如何处理 Hyperlink 的 Click 事件,以实例化被调用页并向其传递初始字符串值。
<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>。
将参数存储到 Properties 中。
导航到被调用的 PageFunction<T>。
从被调用的 PageFunction<T>:
- 检索并使用存储在 Properties 中的参数。
但是,你不久就会看到,你仍然需要使用代码来实例化并导航到被调用页,以收集被调用页返回的数据。 为此,PageFunction<T> 需要保持活动状态;否则,下次导航到 PageFunction<T> 时,WPF 将使用无参数构造函数来实例化 PageFunction<T>。
但是,在被调用页返回前,需要返回可以由调用页检索的数据。
将任务的任务结果和任务数据返回到调用页
在用户使用完被调用页后(在本示例中通过按“确定”或“取消”按钮来表示),被调用页需要返回。 因为调用页已使用被调用页来从用户那里收集数据,所以调用页需要两种类型的信息:
用户是否取消了被调用页(在此示例中通过按“确定”或“取消”按钮)。 这使得调用页可以确定是否处理调用页从用户那里收集的数据。
用户提供的数据。
为了返回信息,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> 的最基本用法,以支持调用/返回结构化导航。 这一基础使你能够创建更复杂的结构化导航类型。
例如,有时调用页需要多个页面来从用户那里收集足够的数据或者执行任务。 多个页面的使用称为“向导”。
在其他情况下,应用程序可能具有依赖于结构化导航来有效操作的复杂导航拓扑。 有关详细信息,请参阅导航拓扑概述。