WPF 学习笔记 - 3. Navigation

互联网的兴起,造就和培养了一种新的用户交互界面 —— 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

<NavigationWindow x:Class="Window1"
  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 partial class Window1 : NavigationWindow
{
  public Window1()
  {
    InitializeComponent();
  }
}


创建一个 Page1.xaml,我们就可以像普通 Window 那样添加相关的控件和操作。

2. Hyperlink

超链接应该是我们最熟悉的一种导航方式。

Page1.xaml

<Page x:Class="Learn.WPF.Page1"
  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

<Hyperlink Click="Hyperlink_Click">Page2</Hyperlink>


Page1.xaml.cs

private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
  this.NavigationService.Navigate(new Uri("Page2.xaml", UriKind.Relative));
}


Hyperlink 还支持 "test.htm#name" 这样的导航定位方式,滚动页面直到某个特定名称的控件被显示。Hyperlink 的另外一个实用属性是 Command,我们可以使用 NavigationCommands 中创建的一系列静态成员来执行一些常用操作。

<Hyperlink Command="NavigationCommands.Refresh">Refresh</Hyperlink>
<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)。

public partial class Page1 : Page
{
  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.Content = page2;
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 这样的容器。

private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
  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 的对象实例。

<Page x:Class="Learn.WPF.Page2"
  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。

<Window x:Class="Learn.WPF.Window1"
  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 Source="Page1.xaml" JournalOwnership="OwnsJournal"></Frame>


Frame 的另外一个作用就是可以导航到 HTML 页面,我们可以把它当作一个嵌入式 IE WebBrowser 来使用。

<Frame Source="http://www.rainsts.net" />


7. PageFunction<T>

WPF 提供了一个称之为 PageFunction 的 Page 继承类来实现类似 HTML showModal 的功能。我们可以用它来收集某些数据并返回给调用页,当然这个封装其实非常简单,我们完全可以自己实现,无非是提供一个类似 OnReturn 的方法实现而已。泛型参数 T 表示返回数据类型。

Page1.xaml.cs

public partial class Page1 : Page
{
  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

public partial class PageFunction1 : PageFunction<int>
{
  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, [sweat] ~~~~ 说实话,个人觉得这个 PageFunction 从命名到执行逻辑都有点别扭,难道仅仅是因为 Page 特殊的实例构造逻辑?我们也可以使用 Application.Properties + Page 来实现一个非关联耦合的 showModel 逻辑,只不过不那么 "标准" 罢了。

有一点需要提醒一下:我们应该及时解除对 FunctionPage<T>.Return 的订阅,我上面的例子和 MSDN 一样偷懒了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值