首页 > 学院 > 开发设计 > 正文

用WPF构建强大的用户体验

2019-11-17 04:37:01
字体:
来源:转载
供稿:网友

  用户体验是内容(content)和内容寄宿(host)方式的综合结果。在Windows PResentation Foundation中,内容是用标准控件、2D和3D图像、动画、数据绑定、布局、样式和模板来创建的。但是,只有当我们采用某种方式把这些内容寄宿起来(答应用户看到内容并与内容进行交互操作)的时候,它们才有意义。我们必须把这些内容"包装"到一个应用程序中,并通过一个窗体来显示它们。因此应用程序模型就可以派上用场了。

  Windows Presentation Foundation应用程序模型有两种应用程序类型:标准的和浏览器的。标准应用程序通过自己的窗体、对话框和消息框显示内容,而浏览器应用程序由寄宿在浏览器中的页面组成。

  类似地,Windows Presentation Foundation也包含两种导航模式:菜单驱动的和超链接驱动的。菜单驱动的应用程序答应用户使用菜单条、工具条、窗体和对话框导航到某些内容和功能。超链接驱动的应用程序使用超链接来传递导航体验,与Web应用程序类似。

  很明显,标准的应用程序自然地支持菜单驱动导航,而浏览器应用程序自然地支持超链接导航。但是Windows Presentation Foundation应用程序模型能够让两者混合。在大多数情况下,它可以把超链接驱动的体验部分地或者全部地集成到标准应用程序中。这种基于用户体验类型的合并会使用户受益。一旦你决定了自己希望提供的用户体验,就可以使用Windows Presentation Foundation模型来建立应用程序了。

  应用程序的类型

  我们来看看示例Box应用程序,如图1所示。这是一个标准的、菜单驱动的应用程序,答应用户根据需求列举、排序、查看和删除盒子订单。为了提供这种用户体验,你必须从应用程序模型积木结构的基础开始:建立一个应用程序。

用WPF构建强大的用户体验(图一)
图1:Box应用程序

  基于Windows的应用程序由一些标准的管线(plumbing)组成,包括一个入口点和一个消息循环,也可能需要下面的通用应用程序服务中一个或多个:

  · 处理命令行参数

  · 返回退出代码

  · 应用程序范围的状态信息

  · 检测和响应未处理的异常

  · 治理应用程序的生命周期

  Windows Presentation Foundation把管线和服务集中在一个类型(System.Windows.application)中了,你可以在标记(XAML)、代码(C#或 Visual Basic)、或者两者组合(标记和后台代码)中使用它。Application类型变得如此重要,以至于Visual Studio 2005会自动地向每个新的.NET Framework 3.0(以前的WinFX)窗体应用程序项目添加该类型的一个实例:

<!--App.xaml (markup)-->
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BoxApplicationWindow.App"
/>

// App.xaml.cs (后台代码)
public partial class App : Application { ... }
  假如你使用以前的Windows显示技术编写过程序(例如Windows窗体和Win32),你可能会很惊奇。这儿根本就没有一段代码与建立标准的基于Windows的应用程序管道(包括入口点)的代码相似。这是因为Visual Studio 2005已经为你建立了应用程序管道,它是在配置应用程序标记文件的"应用程序定义"构建操作(build action)的时候完成这项工作的,如图2所示。

用WPF构建强大的用户体验(图二)
图2:设置应用程序的XAML文件

  在后台,它生成的代码与下面的类似:

// App.cs
using System;

public partial class App : Application
{
 [STAThread]
 public static void Main()
 {
  // 初始化和运行应用程序
  App application = new App();
  application.Run();
 }
}
  Visual Studio 2005到底创建了什么内容都是无关紧要的,因为你既不需要编写也不需要理解它的复杂性。作为代替的是,到此为止,你被微软显示技术中的大多数应用程序抽象给隔离了,你可以使用它和少量标记来建立一个可运行的应用程序。你所需要做的只是使用Application的服务。对于独立的应用程序来说,这包括在应用程序开始运行时显示一个窗体。窗体(Window)

  在Windows Presentation Foundation中,窗体就是Window。通常情况下,在标准的应用程序中,窗体是寄宿内容的核心单位。在Visual Studio 2005中你可以选择"项目 添加新项 WinFX Window"给项目增加一个窗体定义,它会产生下面的内容:

<!--MainWindow.xaml (markup)-->

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BoxApplication.MainWindow"
</Window>

// MainWindow.xaml.cs (codebehind)
using System.Windows;
public partial class MainWindow : Window { ... }
  在添加窗体定义之后,Visual Studio 2005自动地把标记文件的"构建类型"设置为Page(页面)。在构建的时候,该标记被转换为一种特定的资源类型,可以被统一的资源标识符(URI)唯一地识别出来。从本质上说,这使得Windows Presentation Foundation可以使用URI宣告式地载入一个窗体,你可以使用这种能力来指定一个应用程序启动时自动打开的窗体。要达到这个目的,只需要设置标记中的Application.StartupUri属性,如下所示:

<!--App.xaml (markup)-->
<Application ... StartupUri="MainWindow.xaml" />
  上面的代码建立和显示了一个如图3所示的窗体。与所有其它窗体类似,Windows Presentation Foundation窗体包含客户端区域(它里面放置Windows Presentation Foundation内容和控件)和非客户端区域(边框、标题条、以及一些与此相关的修饰部分)。

用WPF构建强大的用户体验(图三)
图3:窗体和它的各部分

  Application.StartupUri指定的窗体是非模态的,这意味着它不会阻止用户使用应用程序中的其它窗体。假如你需要显示其它的非模态窗体,你只需要调用Window.Show:

// MainWindow.xaml.cs (codebehind)
public partial class MainWindow : Window
{
 void helpContentsMenuItem_Click(object sender, RoutedEventArgs e)
 {
  HelpWindow window = new HelpWindow();
  window.Owner = this; // 确保窗体总是显示在本窗体之上
  window.Show();
 }
 ...
}
  Windows Presentation Foundation也支持显示模式窗体,即阻止用户使用应用程序中其它窗体的窗体。典型情况下(不是一定的),模式窗体一般被当作对话框使用,用来收集完成某项事务(例如创建订单)所需要的数据。在Windows Presentation Foundation中显示模式窗体,需要调用Window.ShowDialog(如下代码所示)。

  代码:调用Window.ShowDialog

// MainWindow.xaml.cs (codebehind)
public partial class MainWindow : Window
{
 void CreateOrder()
 {
  OrderABoxDialog dlg = new OrderABoxDialog();
  dlg.Owner = this; // 确保对话框一直处于本窗体之上
  bool dialogResult = dlg.ShowDialog();

  // 假如订单的细节信息都是争取的,就向订单列表添加订单
  if (dialogResult == true)
  {
   this.orders.Add(dlg.Order);
  }
 }
 ...
}
  Window类也支持典型的对话框行为,答应用户接受或终止一个对话框,并且可以把用户的选择返回给调用代码以供处理。

  消息框是一种非凡的、给用户显示信息或者询问用户的对话框,Windows Presentation Foundation的MessageBox类型支持它:

// MainWindow.xaml.cs (codebehind)
public partial class MainWindow : Window
{
 void aboutMenuItem_Click(object sender, RoutedEventArgs e)
 {
  MessageBox.Show("Box Application, Version 1.0");
 }
 ...
}
  消息框、对话框、窗体和应用程序窗体是标准的、菜单驱动的应用程序开发模型的核心。在很久以前的显示技术中也都支持这些内容。但是,Windows Presentation Foundatio通过对超链接驱动的导航支持扩展了这些内容,它从导航内容的基本单位--页面开始。Page类

  Page是一种与Html Web页面(它使Web更加普及了)类似的Windows Presentation Foundation内容。前面我提到过,Windows Presentation Foundation在标准的和浏览器应用程序中都支持超链接驱动的导航。Windows Presentation Foundation中的超链接驱动的导航体验的内容基础是Page(页面)。

  在Visual Studio 2005中,通过选择"项目 添加新文件 WinFX 页面"给项目添加标记和后台页面定义。它生成的代码与图5所示的相似。

  代码:添加标记和后台页面定义

<!--HomePage.xaml (markup)-->

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BoxApplicationNavigationWindow.HomePage" ... >
...
<!--Order Content-->
...
</Page>

// HomePage.xaml.cs (codebehind)
using System.Windows.Controls; // Page
public partial class HomePage : Page { ... }
  页面标记文件是在页面构建项中配置的。假如使用窗体,可以从URI载入它,这意味着配置Application.StartupUri之后可以在应用程序启动时自动地载入页面:

<!--App.xaml (markup)-->
<Application ... StartupUri="HomePage.xaml" />
  由于Page类不是窗体,也不是衍生自Window的,因此它不能寄宿自身。幸运的是,Application类足够智能,当StartupUri被设置为特定页面的时候,它可以检测到。Application会建立一个窗体,在窗体内寄宿该页面。

  Hyperlink类

  所有的超链接驱动的应用程序都拥有多个XAML页面,你必须为用户提供一条在页面之间进行导航的途径。你可能猜到了Windows Presentation Foundation是用超链接来实现超链接驱动的导航的。你可以采用下面的方式给页面增加超链接:

<!--HomePage.xaml (markup)-->
<Page ... >
...
<Hyperlink NavigateUri= "OrderingGuidelinesPage.xaml">
Ordering Guidelines
</Hyperlink>
...
</Page>
  上面的代码配置超链接以导航到一个XAML页面,它使用的基本编程模型与HTML HREF是一样的。你指定一个要导航到的URI(例子中是OrderingGuidelinesPage.xaml)和文本,用户可以看到文本并点击它来发起导航。

  由于太多的可浏览内容都存在于基于HTML的Web页面上,因而Windows Presentation Foundation和超链接能够让你无缝地导航到基于Web的内容。例如,"订单向导"已经存在于BOX应用程序的Web站点上,因此,我们不应该把它们复制成应用程序中的XAML文件,而只需要简单地改变NavigateUri属性的值,如下所示:

<!--HomePage.xaml (markup)-->
<Page ... >
...
<Hyperlink NavigateUri="OrderingGuidelinesPage.html">
Ordering Guidelines
</Hyperlink>
...
</Page>导航窗体(NavigationWindow)

  现在你可能对几个问题很迷惑。由于页面不是窗体,那么寄宿页面的窗体来自哪儿?当我们点击超链接的时候,到底是什么在处理导航?HTML Web页面内容是如何显示在Windows Presentation Foundation应用程序中的?所有的这些都是由NavigationWindow来处理的。

  当你把Application.StartupUri设置为XAML或HTML页面的时候,应用程序(我们知道这些页面都不会提供自己的窗口)建立一个NavigationWindow实例来寄宿它们。

  NavigationWindow衍生自Window,并扩展了它的可视化外表,使它的样子与浏览器类似,如图6所示。

用WPF构建强大的用户体验(图四)
图6:NavigationWindow

  当用户点击XAML页面上的超链接的时候,超链接要求NavigationWindow导航到特定的URI。NavigationWindow就载入URI所指向的页面,从而实现寄宿它。被载入页面的URI位置会存储在NavigationWindow.Source属性中,被载入页面的内容在NavigationWindow.Content属性中。

  当内容发生改变的时候,就进行导航,并且把之前的内容添加到导航历史中。这个过程也是由NavigationWindow治理的。导航UI为导航操作提供了两个按钮和一个下拉列表。请注重,你不仅仅可以使用NavigationWindow的默认样式,使用Windows Presentation Foundation支持的多种样子,你还可以轻易地建立自己的导航UI。

  到目前为止,我为你介绍了如何使用标记来配置需要导航的超链接URI。但是,有时候你无法宣告式地决定导航。例如,假如希望查看订单,你就必须建立一个页面实例,并把自己希望查看的订单传递给它。这是无法宣告式地完成的。作为代替,你必须使用代码,如图7所示。

  图7:用代码导航

HomePage.xaml (markup)
<Page ... >
...
<Hyperlink Click="viewHyperlink_Click">
View
</Hyperlink>
...
</Page>

HomePage.xaml.cs (codebehind)
public partial class HomePage : Page
{
 void viewHyperlink_Click(object sender, RoutedEventArgs e)
 {

  // 查看订单
  VieWorderPage page = new ViewOrderPage(GetSelectedOrder());
  NavigationWindow window = (NavigationWindow)this.Parent; // Don't do this!
  window.Navigate(page);
 }

 Order GetSelectedOrder()
 {
  return (Order)this.ordersListBox.SelectedItem;
 }
 ...
}
  当超链接被点击的时候,它的Click事件处理程序获取当前选中的订单,在实例化的过程中把它传递给ViewOrderPage,并调用它的宿主NavigationWindow的Navigate方法,接着它把页面作为对象而不是URI进行导航。

  你可能发现获取宿主NavigationWindow的引用有些古怪。这是必然的,因为页面没有任何可用于了解自己所寄宿的内容的外部信息。页面可以使用自己的Parent属性来检测宿主,但是Parent返回的是DependencyObject引用,而不是特定宿主类型的强类型(strongly typed)引用。因此,把Parent转换为特定的类型意味着页面知道谁可以寄宿它。但是,你会发现页面可以拥有多种宿主类型。因此,假如你打算让多种宿主类型寄宿自己的页面,你就需要一个与宿主无关的编程执行导航的方法。

  导航服务(NavigationService)

  在Windows Presentation Foundation中,页面和页面宿主之间的分离是由NavigationService实现的,它实现了导航引擎的基本功能,包括导航、导航历史、导航生命周期、内容、为部分内容查找导航服务。如下代码显示了NavigationService类型的基本成员。NavigationWindow并没有真正地实现自己的导航引擎;它是用自己的NavigationService实例来实现的。

  代码:NavigationService类型的基本成员

sealed class NavigationService : IContentContainer
{
 // 导航
 public bool Navigate(Uri source); // 导航到URI
 public void Refresh(); // 重新导航到当前内容
 public void StopLoading(); // 停止当前的导航 // 导航历史
 public bool CanGoBack { get; } // Content in back nav. history?
 public bool CanGoForward { get; } // Content in forward nav. history?
 public void GoBack(); // Go to previous content in nav. history
 public void GoForward(); // Go to next content in nav. history

 // 导航的生命周期
 // 导航请求
 public event NavigatingCancelEventHandler Navigating;
 // 导航到内容
 public event NavigatedEventHandler Navigated;
 // 内容载入了
 public event LoadCompletedEventHandler LoadCompleted;
 // 导航错误
 public event NavigationFailedEventHandler NavigationFailed;
 // 下载的字节数
 public event NavigationProgressEventHandler NavigationProgress;
 // 导航停止了
 public event NavigationStoppedEventHandler NavigationStopped;

 // 内容
 public object Content { get; set; } // 当前载入的内容
 public Uri CurrentSource { get; } // 当前内容的URI
 public Uri Source { get; set; } // 当前内容的URI,或者将导航到的内容的URI

 // 查找导航服务
 public static NavigationService GetNavigationService(DependencyObject dependencyObject);
}
  当你知道这些内容之后,就能使用GetNavigationService来获取寄宿页面的NavigationWindow的NavigationService引用了:

// HomePage.xaml.cs (codebehind)
public partial class HomePage : Page
{
 void viewHyperlink_Click(object sender, RoutedEventArgs e)
 {
  // 查看订单
  ViewOrderPage page = new ViewOrderPage(GetSelectedOrder());
  NavigationService ns = NavigationService.GetNavigationService(this);
  ns.Navigate(page);
 }
 Order GetSelectedOrder() { ... }
 ...
}
  这就使得页面可以执行导航而无需知道宿主的特定信息了。这种需求是如此的普遍,以至于页面提供了一个特定的辅助属性NavigationService,它提供的功能相同:

// HomePage.xaml.cs (code-behind)
public partial class HomePage : Page
{
 void viewHyperlink_Click(object sender, RoutedEventArgs e)

 {
  // 查看订单
  ViewOrderPage page = new ViewOrderPage(GetSelectedOrder());
  this.NavigationService.Navigate(page);
 }

 Order GetSelectedOrder () { ... }
 ...
}
  图9演示了NavigationWindow、NavigationService和页面(Page)之间的关系。你可以看到,NavigationWindow重新实现了自己的NavigationService的Content属性。NavigationWindow不但用这种方法实现了NavigationService的大多数成员,甚至于还增加了一些。例如,你可以通过BackStack和ForwardStack属性,枚举"向前"和"向后"导航历史的内容。

用WPF构建强大的用户体验(图五)
图9:关系

  不幸的是,你无法建立自定义的、聚合了NavigationService的类型(尽管它是一个公共类型,但是它有内部的构造函数,从而阻止了实例化)。作为代替,你必须依靠三种NavigationService聚合器(aggregator)来寄宿内容。这就是我们所知道的导航器(navigator),包括NavigationWindow、Frame和浏览器(仅包括用于Windows Presentation Foundation 1.0的Internet EXPlorer 6 和 7)。当编写代码让页面使用自己的NavigationService属性的时候,它就可以寄宿在上面的三种导航器中,而不需要做任何更改,如图10所示。

用WPF构建强大的用户体验(图六)
图10:用代码导航

  也许最令人兴奋的是,你发现寄宿在独立应用程序中的一个页面忽然就可以寄宿在任何使用Internet Explorer的地方了。 QQread.com 推出各大专业服务器评测 linux服务器的安全性能 SUN服务器 HP服务器 DELL服务器 IBM服务器 联想服务器 浪潮服务器 曙光服务器 同方服务器 华硕服务器 宝德服务器 XAML浏览器应用程序

  NavigationWindow、页面和超链接为用户在独立的应用程序中获得浏览器样式的用户体验提供了一条很好的途径。总而言之,NavigationWindow是一个浏览器,虽然它没有目前的浏览器所带有的所有功能(例如收藏夹、分页浏览等等)。由于大多数用户都有浏览器的知识,他们对提供同等能力、甚至集成浏览器的应用程序的感觉会更好。假如你的应用程序从浏览器寄宿和超链接驱动的环境中受益,那么Windows Presentation Foundation XAML浏览器应用程序(XBAP)是我们应该努力的方向。

  为了建立一个XBAP示例应用程序,需要在Visual Studio 2005中建立一个新的.NET框架组件3.0 Web浏览器应用程序,并复制示例NavigationWindow文件,就完成工作了。作为结果生成的应用程序将寄宿在Internet Explorer中运行,如图11所示。

用WPF构建强大的用户体验(图七)
图11:寄宿在Internet Explorer中的应用程序

  XBAP可以在内部网或Internet上的Web服务器上发布和载入。这使得我们能够使用ClickOnce部署(它包含在.NET框架组件3.0中)。使用ClickOnce的时候,MSBuild生成最终用户运行的可执行文件和ClickOnce用于下载和载入可执行文件的两个清单(manifest)文件。其中一个就是应用程序清单;它带有.xbap文件扩展名,是用户希望载入XBAP应用程序时实际浏览的内容。请注重,载入过程对于用户来说是无缝连接的--浏览XBAP应用程序的体验与浏览任何浏览器中运行的应用程序的体验是一样的。

  从Internet上载入一个应用程序的时候,安全性是一个重要的因素。由于这个原因,XBAP是不会被安装的。而且,XBAP利用.NET框架组件的代码访问安全性(CAS),通过强制的安全性平台防止用户受到恶意代码的攻击--对于从Internet区域载入的应用程序来说,XBAP只能执行许可的事务,是一组受限的操作。而且,假如XBAP试图执行超出Internet区域许可的功能,就会产生异常,应用程序会停止执行。

  Internet区域许可阻止了Windows Presentation Foundation 1.0中的大量功能,包括显示窗体、使用SaveFileDialog(保存文件对话框)、注册表访问、通过脚本访问HTML文档对象模型(DOM)。尽管牺牲了这么多特性来保证CAS保护的XBAP应用程序的利益,你仍然会发现自由支配Windows Presentation Foundation真的非常酷。


  框架(Frame)

  框架把浏览器样式的用户体验带入了内容之中,它可以被其它的导航器(标准的或基于浏览器的、菜单或超链接驱动的)寄宿。由于它具有很大的灵活性,因此在决定如何使用它的时候,应该由已有的用户体验来指导。

  标准的、菜单驱动的应用程序并没有提供导航文档样式内容(例如帮助文件)的最佳模型。超链接驱动的方法可能更加适合,并且链接可以轻易地嵌入标准的应用程序窗口,如图12所示。这是使用下面的标记来实现的:

<!--HelpDialog.xaml (markup)-->
<Window ... >
<DockPanel>
<TextBlock
Padding="20,20,20,20"
DockPanel.Dock="Top"
TextWrapping="Wrap"
FontSize="15"
FontWeight="Bold" >
Box Application Help
</TextBlock>
<Frame Padding="20,0,20,0" Source="HelpPageContents.xaml" />
</DockPanel>
</Window>
  作为选择,框架可以像HTML IFRAME元素一样被寄宿在Windows Presentation Foundation页面的内容中,如图13所示。它的标记类似如下:

<!--HelpPage.xaml (markup)-->
<Page ... >
<DockPanel>
<TextBlock
Padding="20,20,20,20"
DockPanel.Dock="Top"
TextWrapping="Wrap"
FontSize="15"
FontWeight="Bold" >
Box Application Help
</TextBlock>
<Frame ... Source="HelpPageContents.xaml" />
</DockPanel>
</Page>
     用WPF构建强大的用户体验(图八)
  图12:独立窗体中的可浏览内容

  在默认情况下,当一个框架寄宿在内容中的时候,它就直接或间接地寄宿在另一个导航器中,可以使用父导航器所治理的导航服务。这意味着页面的导航历史存储在它的父导航器的导航历史中,与父导航器本身的导航历史存储在一起。因此,在父导航器的导航历史能够导航之前,框架的导航历史必须向前和向后导航这些内容。但是,当父导航器寄宿的内容在多个页面之间共享的时候,这并不是坏事。这类似于asp.net的主页面/子内容。

用WPF构建强大的用户体验(图九)
图13:内容中寄宿的框架

  另一方面,假如框架中的页面只由单个逻辑内容组成(例如一个帮助文件而不是多个帮助页面),这种导航就非常痛苦了。一旦用户进入帮助并导航到适当的帮助页面,它一般不希望通过所有浏览过的页面导航回去,而是直接返回先前的父页面。在这种情况下,你可以通过设置JournalOwnership属性,指示框架使用自己的导航历史,如下所示:

<Page ... >
...
<Frame ... JournalOwnership="OwnsJournal" />
...
</Page>
  JournalOwnership属性是用于设置框架使用哪个"journal(导航记录)"的(内部的Journal类型封装了Windows Presentation Foundation中的导航历史),它可以是下面几个JournalOwnership枚举值之一:

enum JournalOwnership
{
Automatic, // 假如由框架或NavigationWindow 寄宿,是"UsesParentJournal"
// 否则是"OwnsJournal"(默认的)。
OwnsJournal, // 框架治理导航历史
UsesParentJournal // 框架的宿主治理导航历史
}
  当框架拥有自己的导航历史的时候,它会显示自己的导航用户界面,如图14所示:

用WPF构建强大的用户体验(图十)
图14:带有自己的导航历史的框架
QQread.com 推出各大专业服务器评测 Linux服务器的安全性能 SUN服务器 HP服务器 DELL服务器 IBM服务器 联想服务器 浪潮服务器 曙光服务器 同方服务器 华硕服务器 宝德服务器 Windows Presentation Foundation资源

  到目前为止,我们讨论了嵌入应用程序部件的页面。但是,内容可以从很多地方载入--它可能被嵌入了代码所使用的部件、可能被嵌入一个被引用的部件、或者由一组松散的内容文件组成,根本没有嵌入任何部件。松散的内容本身可以位于本地磁盘、文件共享、甚至于Web站点上。并且,无论内容是嵌入的还是松散的,它都不一定是页面;内容可能包括多种媒体,例如图像、视频和音频。最后,内容也不一定属于某个特定的应用程序。属于其它Web应用程序的HTML页面也是可行的。

  这种灵活性答应开发者更简单地处理大量的现实问题。有时候内容对于应用程序来说足够非凡,而且该应用程序是如此依靠这个内容,以至于需要把内容嵌入部件中来部署内容和应用程序。有些应用程序的内容经常变换,以至于重新构建部件和重新部署新内容变得不切实际,因而支持松散的内容(由于松散的内容可以位于通常的位置,基于Internet和内部网的XBAP应用程序可以避免下载不必要的部件)。此外,有些内容在多个应用程序之间共享,但是仍然需要保证它们可供使用。

  为了保证灵活性,Windows Presentation Foundation为了唯一标识和载入资源,使用了一种非凡的机制。它不考虑内容的位置或内容是嵌入还是松散的。这种机制的基础是Pack URI大纲(scheme),它是一种用不同的URI标识应用程序资源的可扩展大纲。Windows Presentation Foundation利用Pack URI大纲来支持几种用于载入内容的不同的、但是常见的情形。


  在整篇文章中,无论什么时候使用Application.StartupUri 和Hyperlink.NavigateUri,示例代码都使用Pack URI来标识和载入窗体和页面:

<!--App.xaml (markup)-->
<Application ... StartupUri="HomePage.xaml" />
  这个例子使用了Pack URI的相对版本,它是一种很好的简化操作,答应你输入更少的内容。Pack URI的完整版本如下所示:

pack://application:,,,/HomePage.xaml
  完整的Pack URI由三个要害的部分组成:大纲(pack://)、拥有者(application:)和路径(,/HomePage.xaml)。其中拥有者描述了拥有资源的容器的类型,而路径描述了资源与容器的相对位置。"application:"容器是一个真正的部件,而路径是资源相对部件的根(root)的位置。

  不管使用完整的或相对的Pack URI,它所指向的内容即可以嵌入部件的内部,也可以是存储在与应用程序执行文件相关的某个位置的松散的XMAL文件。对于一个存放在应用程序可执行文件目录中的松散XAML页面来说,其Pack URI如下所示:

pack://application:,,,/HomePage.xaml
  有趣的是,这个松散的XAML文件的Pack URI与嵌入部件的Pack URI相同。为了区分两者,Windows Presentation Foundation使用了一个基本的解析机制,在查找松散资源之间,它首先在部件中查找嵌入的资源。

  Pack URI还用于访问那些嵌入被引用的部件中的Windows Presentation Foundation资源,只是有细微的语法差别:

pack://application:,,,/BoxApplicationLibrary;component/HomePage.xaml
  相对的Pack URI等同于:

/BoxApplicationLibrary;component/HomePage.xaml
  Pack URI答应你从应用程序的原始站点(载入应用程序的位置)来定位和载入资源。对于Web服务器上载入的XBAP应用程序来说,让内容保持在当前位置同把新内容放入应用程序的发布位置一样简单。为了访问原始站点的松散资源,你必须使用另外一个特定类型的Pack URI,它只能使用完整路径:

pack://siteoforigin:,,,/HomePage.xaml
  你可以导航到页面(无论是嵌入的还是松散的)的任何一个片段(fragment)。这与Web样式的片段导航是类似的。通过指定Name属性,你就可以在页面上定义片段了,如下所示:

<Page ... >
<TextBlock Name="Paragraph1" TextWrapping="Wrap">
...
</TextBlock>
</Page>
  为了导航到页面片段,你需要使用另外一种非凡的Pack URI,在页面URI上附加"#XAMLElementName",如下所示:

HelpPage3.xaml#Paragraph3
  页面函数(PageFunction)

  由于内容来自于多个位置,而超链接驱动的应用程序又答应用户导航到任何位置,从而使完成一项事务变得非常困难。这是因为超链接驱动的应用程序不能轻易地约束用户导航到特定页面。无论应用程序提供了多少个超链接,用户仍然可以使用浏览器的地址栏导航到任何地方。其结果是,用户可以离开发起事务的页面,而不考虑事务是否完成了。在Web世界中,有很多技巧可用于建立类似对话框样式行为、依靠对话状态等的Web页面。不幸的是,这需要大量的开销。对话框的确可以解决这个问题,但是由于安全性原因,Window不能从应用程序中实例化,例如XBAP的对话框就只能在Internet区域部分信任环境中运行。

  幸运的是,Windows Presentation Foundation通过页面函数支持模式对话框样式的机制。它们被封装为泛化的PageFunction类型,直接衍生自Page。因而PageFunction的样子与页面类似,它的建立方式也相似,如下段所示。

  代码:PageFunction

<!--OrderABoxPageFunction.xaml (markup) -->
<PageFunction
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BoxApplicationXBAP"
x:Class="BoxApplicationXBAP.OrderABoxPageFunction"
x:TypeArguments="local:Order"
WindowTitle="Box Application - Order a Box" >
...
<!--Content-->
...
<PageFunction>

// OrderABoxPageFunction.cs (code-behind)
public partial class OrderABoxPageFunction:
PageFunction<Order> { ... }
  这个特定的页面函数的目标是收集新订单的信息,它被Order封装了。由于典型情况下事务都是这样操作数据的,PageFunction是一个泛型(generic),并被声明用于操作特定的数据(标记中特定的x:TypeArguments属性)。假如x:TypeArguments的值和泛化的PageFunction类型的参数不匹配,就会出现编译错误。

  调用PageFunction的页面需要实例化PageFunction并手动导航到该页面上:

// HomePage.cs (codebehind)
public partial class HomePage : Page
{
 void orderHyperlink_Click(object sender, RoutedEventArgs e)
 {
  OrderABoxPageFunction pageFunction = new OrderABoxPageFunction();

  pageFunction.Return += new ReturnEventHandler<Order>(OrderABoxPageFunction_Returned);
  this.NavigationService.Navigate(pageFunction);
 }
 ...
}
  接下来,PageFunction在给调用页面返回结果之前,必须答应用户完成页面:

// OrderABoxPageFunction.cs (codebehind)
public partial class OrderABoxPageFunction:
PageFunction<Order>
{
void orderHyperlink_Click(object sender, RoutedEventArgs e)
{
// 返回订单
this.OnReturn(new ReturnEventArgs<Order>(this.order));
}
void cancelHyperlink_Click(object sender, RoutedEventArgs e)
{
// 取消订单
this.OnReturn(null);
}
...
}
  接下来调用PageFunction.OnReturn返回,传递一个泛化的ReturnEventArgs实例。假如事务被接受了,它就包含一个PageFunction所操作的类型实例。否则,它就是空的。为了检测PageFunction的返回,并获取ReturnEventArgs和其数据,调用页面需要处理PageFunction.Returned事件,如下段代码所示。被返回的数据存放在Returned事件处理程序的ReturnEventArgs参数的Result属性中。

  代码:PageFunction.Returned

// HomePage.cs (code-behind)
public partial class HomePage : Page
{
 // 载入页面函数
 void orderHyperlink_Click(object sender, RoutedEventArgs e)
 {
  OrderABoxPageFunction pageFunction = new OrderABoxPageFunction();
  pageFunction.Return += new ReturnEventHandler<Order>(OrderABoxPageFunction_Returned);
  this.NavigationService.Navigate(pageFunction);
 }
 // 处理页面函数的返回
 void OrderABoxPageFunction_Returned(object sender, ReturnEventArgs<Order> e)
 {
  if (e != null) this.orders.Add(e.Result);
 } 
 ...
}
  你可能希望在事务完成之后,确保PageFunction从导航历史中移除。这只需要一个简单的配置:

<!--OrderABoxPageFunction.xaml (markup) -->
<PageFunction RemoveFromJournal="True" ... >
...
<!--Content-->
...
<PageFunction>
  通过从导航历史中删除页面函数,你可以阻止用户导航回该页面函数。这是非常重要的,因为假如不这样处理,用户就可能修改那些已经发生改变的数据,从而造成潜在的数据不一致风险。

  我们的位置

  Windows Presentation Foundation应用程序模型是非常灵活的。它支持标准的和浏览器寄宿的应用程序--它们两者都支持菜单驱动和超链接驱动的导航。此外,应用程序的内容可以被封装到应用程序的部件、被引用的部件或某些位置的松散文件中。总而言之,在Windows Presentation Foundation应用程序模型中建立的用户体验类型仅受个人选择的限制。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表