互联网的兴起,造就和培养了一种新的用户交互界面 —— Page & Navigation。无论是前进、后退还是页面,都完全是一个全新的门类,不同于以往的 SDI/MDI。WPF 或者是它的简化版 Silverlight 都不可避免地遵从了这种改良的 B/S 模式,使用 URI 来串接 UI 流程。
NavigationService、 Page、Hyperlink、Journal(日志/历史记录) 是 WPF 整个导航体系的核心。NavigationService 提供了类似 IE Host 的控制环境,Journal 可以记录和恢复相关 Page 的状态,我们通常会选用的宿主方式包括:Browser(XBAP) 和 NavigationWindow。
1. NavigationWindow
NavigationWindow 继承自 Window,不知什么原因,我并没有在 VS2008 "New Item..." 中找到相关的条目,只好自己动手将一个 Window 改成 NavigationWindow。
Window1.xaml
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" WindowStartupLocation="CenterScreen"
Source="Page1.xaml">
</NavigationWindow>
Source 属性指定了该窗口的默认页面,当然,我们还要修改一下 Window1.xaml.cs 里的基类。
{
public Window1()
{
InitializeComponent();
}
}
创建一个 Page1.xaml,我们就可以像普通 Window 那样添加相关的控件和操作。
2. Hyperlink
超链接应该是我们最熟悉的一种导航方式。
Page1.xaml
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page1">
<Grid>
<TextBlock>
<Hyperlink NavigateUri="Page2.xaml">Page2</Hyperlink>
</TextBlock>
</Grid>
</Page>
NavigateUri 相当于 "Html a.href",当然我们也可以使用 Hyperlink.Click 事件,然后使用 NavigationService 来完成导航操作。
Page1.xaml
Page1.xaml.cs
{
this.NavigationService.Navigate(new Uri("Page2.xaml", UriKind.Relative));
}
Hyperlink 还支持 "test.htm#name" 这样的导航定位方式,滚动页面直到某个特定名称的控件被显示。Hyperlink 的另外一个实用属性是 Command,我们可以使用 NavigationCommands 中创建的一系列静态成员来执行一些常用操作。
<Hyperlink Command="NavigationCommands.BrowseBack">BrowseBack</Hyperlink>
<Hyperlink Command="NavigationCommands.BrowseForward">BrowseForward</Hyperlink>
3. NavigationService
很 多时候我们都需要使用 NavigationService 代替 Hyperlink.NavigateUri,比如非默认构造的 Page,动态确定目标页面等等。我们可以使用 Page.NavigationService 或者 NavigationService.GetNavigationService() 获得 NavigationService 的实例引用 (别忘了添加 using System.Windows.Navigation)。
{
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
var page2 = new Page2();
page2.label1.Content = "Beijing 2008!";
this.NavigationService.Navigate(page2);
}
}
除了 Navigate(),还可以使用 NavigationService 的两个属性完成导航切换操作。
this.NavigationService.Source = new Uri("Page2.xaml", UriKind.Relative);
NavigationService 提供了大量的方法和时间来管理相关导航操作。
日志: AddBackEntry、RemoveBackEntry。
载入: Navigate、Refresh、StopLoading。
切换: GoBack、GoForward。
事件: Navigating(新导航请求时触发,可取消导航)……
我们也可以使用 Application 的相关事件来处理导航过程。
4. Journal
Journal 相当于 WebBrowser.History,它包含两个数据栈用来记录前进和后退页面的显示状态,每个相关 Page 都会对应一个 JournalEntry。日志状态自动恢复仅对单击导航条上前进后退按钮有效。
5. Page
有 关 Page 本身的使用并不是本文的内容,我们此处关心的是它在导航过程中的生命周期。在 WPF 中,Page 注定是个短命鬼,无论我们使用导航还是后退按钮都会重新创建 Page 对象实例,然后可能是日志对其恢复显示状态。也就是说日志只是记录了 Page 相关控件的状态数据,而不是 Page 对象引用(默认情况下)。
有两种方式来维持一个 Page 引用。第一种就是我们自己维持一个 Page 引用,比如使用某个类似 Application.Properties 这样的容器。
{
var page2 = Application.Current.Properties["page2"] as Page2;
if (page2 == null)
{
page2 = new Page2();
page2.label1.Content = DateTime.Now.ToString();
Application.Current.Properties["page2"] = page2;
}
this.NavigationService.Navigate(page2);
//this.NavigationService.Content = page2;
}
另外一种就是设置 Page.KeepAlive 属性,这样一来日志会记录该 Page 的引用,当我们使用前进后退按钮时,将不会再次创建该 Page 的对象实例。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page2" Loaded="Page_Loaded"
KeepAlive="True">
</page>
有 一点需要注意:该方法仅对前进后退等日志操作有效。如果我们使用 HyperLink.NavigateUri 或 NavigationService.Navigate() 导航时依旧会生成新的页面实例,并可能代替日志中最后一个同类型的对象引用记录。另外,当多个页面存在循环链接时,会导致多个页面实例被日志记录,造成一 定的内存浪费。
6. Frame
Frame 的作用和 HTML 中的 IFrame 类似,我们可以用它在一个普通的 Window 或 Page 中嵌套显示其他的 Page。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Frame Source="Page1.xaml"></Frame>
</Grid>
</Window>
默认情况下,Frame 会尝试使用上层页面(Page)或窗体(NavigationWindow)的日志,当然我们也可以使用 JournalOwnership 属性强行让 Frame 使用自己的日志导航。
Frame 的另外一个作用就是可以导航到 HTML 页面,我们可以把它当作一个嵌入式 IE WebBrowser 来使用。
7. PageFunction<T>
WPF 提供了一个称之为 PageFunction 的 Page 继承类来实现类似 HTML showModal 的功能。我们可以用它来收集某些数据并返回给调用页,当然这个封装其实非常简单,我们完全可以自己实现,无非是提供一个类似 OnReturn 的方法实现而已。泛型参数 T 表示返回数据类型。
Page1.xaml.cs
{
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
var modal = new PageFunction1();
modal.Return += (s, ex) => this.label1.Content = ex.Result.ToString();
this.NavigationService.Navigate(modal);
}
}
PageFunction1.xaml.cs
{
private void button1_Click(object sender, RoutedEventArgs e)
{
OnReturn(new ReturnEventArgs<int>(DateTime.Now.Millisecond));
}
}
使用步骤:
(1) 创建 PageFunction<T> 对象实例,当然我们可以使用含参构造传递额外的数据;
(2) 调用 PageFunction<T>.OnReturn() 方法用来返回一个特定的结果包装对象 —— ReturnEventArgs<T>;
(3) 调用者通过订阅 PageFunction<T>.Return 事件获取这个返回结果。
MSDN 中还提到用 OnReturn(null) 来表示 Cancel, ~~~~ 说实话,个人觉得这个 PageFunction 从命名到执行逻辑都有点别扭,难道仅仅是因为 Page 特殊的实例构造逻辑?我们也可以使用 Application.Properties + Page 来实现一个非关联耦合的 showModel 逻辑,只不过不那么 "标准" 罢了。
有一点需要提醒一下:我们应该及时解除对 FunctionPage<T>.Return 的订阅,我上面的例子和 MSDN 一样偷懒了。