WPF 学习: Navigation

=========================================================

本文为转载,转载必须确保本文完整并完整保留原作者信息和本文链接

E-mail: khler@163.com

QQ:     23381103

MSN:   pragmac@hotmail.com

原文:http://www.rainsts.net/article.asp?id=737

=========================================================

 

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

 

 

 

发表评论

电子邮件地址不会被公开。