WPF,Silverlight与XAML读书笔记第十五 - 页间导航 页间数据传递

?说明:本系列基本上是《WPF揭秘》的读书笔记。在结构安排与文章内容上参照《WPF揭秘》的编排,对内容进行了总结并加入一些个人理解。

导航

有关导航的话题在介绍NavigationWindow与Page等元素时有提及。这篇文章将详细分析导航相关话题。同其它话题,针对WPF,Silverlight与WP 7,导航特性大致相似又有着些许不同。在介绍此特性时相同的特性将合在一起,每个框架独有的特性也将独立介绍并有明显标识。

导航的功能及目的就是从一个页面转向另一个页面,可能是前进亦或是后退/返回。以下几种实现导航的方法:

  • 调用Navigate方法
  • 使用Hyperlink
  • 使用导航日志
  1. 调用Navigate方法

导航容器支持通过使用Navigate方法改变其中的内容页。Navigate方法有两个重载,分别接受目标页的实例或指向目标页的URI。另外提示,这两种重载分别有等效的属性设置的实现,这两个属性设置方式主要用于在Xaml中以声明方式设置这两个值,在代码中应使用Navigate的两个重载,在下面的代码示例中演示所有这些方式的C#代码实现:

//通过页面实例进行导航PhotoPage npage =   new PhotoPage();this.NavigationService.Navigate(npage);//通过属性导航到页面实例this.NavigationService.Content   = npage;//通过URI进行导航this.NavigationService.Navigate(new   Uri(“PhotoPage.xaml”,UriKind.Relative));//通过属性导航到URIthis.NavigationService.Source   = new Uri(“PhotoPage.xaml”,UriKind.Relative);

?

Uri指向的Page可以是一般的xaml文件,也可以是编译后的内容。必须的要求就是根元素为Page。如果要导航至一个Html页,需要使用Navigate接收Uri的重载,如;

this.NavigationService.Navigate(new   Uri(“http://www.microsoft.com”));
  1. 使用Hyperlink

WPF中的Hyperlink与Html中的超链接非常相似,其提供了一种容易使用的导航方案。Hyperlink元素可以嵌在TextBlock元素中,会被自动呈现为可以被点击的超链接样式(就如Html中<a>标签被自动格式化那样)。Hyperlink通过NavigateUri属性指定导航的目标页(就如Html中<a>标签的href属性)。

提示:

类似Html的超链接可以通过使用Target属性指定目标页在哪个Frame中打开,WPF中的Hyperlink也可以通过TargetName属性指定目标页在那个Frame中打开,TargetName属性为Frame的名称。

另外WPF中Hyperlink也支持Html中锚点的效果,方式与Html也类似,即在Uri后面跟上一个#,并在后面接上Page中某个元素的名称,表示要导航到的部分。

如果要在导航的同时做一些样式等处理,可以给NavigateUri属性指定一个假的路径,并处理Click事件,在这个事件中完成样式设置并调用Navigate方法手工完成导航。

  1. 使用导航日志

导航日志是所有导航容器都记录的导航历史信息。Web浏览器也有这个特征。导航日志通过两个栈 – 前进栈与后退栈来提供后退与前进的功能。

这两个栈具体工作方式如下:


动 作


结 果


后退


把当前页推入前进栈,从后退栈弹出一个页,并导航至这个页


前进


把当前页推入后退栈,从前进栈弹出一个页,并导航至这个页


进入新页面


把当前页推入到后退栈,并把前进栈清空

要完成前进与后退动作,可以调用导航容器的GoBack和GoForward方法,注意,在调用到这两个方法之前需要先调用CanGoBack或CanGoForward来确保后退栈或前进栈不为空,从而确保这个导航行为正确无误的执行。

NavigationWindow总会保存导航日志,而Frame是否保存导航日志取决于其JournalOwnership属性值的设置,有如下几种设置:

  • OwnsJournal:Frame有自己的导航日志
  • UsesParentJournal:在父容器中保存导航日志,如果父容器不支持导航日志,则Frame也不会保存导航日志。
  • Automatic(默认值):当Frame寄宿于NavigationWindow或Frame,使用同UsesParentJournal的设置,否则使用同OwnsJournal的设置。

在Frame拥有导航日志的情况下,Frame中就会出现内置的导航按钮,当然可以通过将NavigationUIVisibility设为Hidden来隐藏这个导航按钮。

提示:

当 通过URI导航至一个Page时(无论是通过调用Navigate方法,还是通过Hyperlink控件),即使这个页面已经被访问过,仍然会创建此 page的一个实例(当然,通过调用Navigate接受page实例的重载构造方法可以控制是否创建Page的新实例)。这是如果需要在同一个page 多个实例间维持共享数据,可以通过使用静态变量或者使用Application.Properties属性。

另外在使用导航日志导航时,把JournalEntry.KeepAlive附加属性设置为true,可强迫Page重用同一个示例。

提示:使用导航日志时怎么实现停止与刷新?

这两个功能没有内建的按钮支持,可以通过调用导航容器中的方法来实现这个目的:

  • StopLoading方法:可以在任意事件停止一个正在处理的导航操作。
  • Refresh方法:可以刷新容器中当前page对象。(这与向Navigate方法传入当前页面的URI实例是等价的,唯一区别是传给Navigating事件处理函数的值中包含一个NavigationMode.Refresh,这样事件处理函数可以区别处理)

这两个方法都是没有参数的。

提示:

通过设置Page的RemoveFromJournal属性为true,可以将该页从导航日志中移除。通过这个属性可以实现按顺序访问每一页(即将访问过的页面的此属性置为true,从而保证用户无法执行后退操作)。

提示:可以利用导航日志进行其它操作

导航日志中允许用户添加自定义项,从而轻松实现(得益于导航日志维护的两个栈)一些如应用程序级的撤销/重做功能而不单单是用于导航。

下 面详细介绍利用导航日志实现一个自定义撤销动作(撤销图像旋转)的过程。首先我们需要创建一个派生自CustomContentState抽象类的类,实 现父类中的Replay方法,并重写JournalEntryName属性(这是可选的)。这个Replay方法会在前进或后退操作发生时被调用。这个子 类的实现如下:

[Serializable]class RotateState : CustomContentState{      FrameworkElement element;      double rotation;

      public RotateState(FrameworkElement element, double rotation)      {        this.element   = element;        this.rotation   = rotation;      }

      public override   string JournalEntryName      {        get {   return "Rotate   " + rotation + "。¡ê"; }      }

      public override   void Replay(NavigationService navigationService, NavigationMode mode)      {        element.LayoutTransform = new RotateTransform(rotation);      }}

?然后在需要撤销操作的地方调用NavigationService的AddBackEntry方法,并以上面这个子类的一个实例作为参数。这样就可以在导航日志中添加一条自定义的记录。代码如:

void fix_RotateClockwise_Click(object   sender, RoutedEventArgs e){      this.NavigationService.AddBackEntry(new RotateState(image,   (image.LayoutTransform as RotateTransform).Angle));      (image.LayoutTransform as RotateTransform).Angle += 90;}

?

这样在撤销操作发生时,将调用导航日志中这条自定义记录的RotateState对象的Replay方法,来实现操作的撤销。

与内置页面导航对比,初次执行对象旋转操作相当于导航到一个新页面。这个时候自定义项被添加到导航日志的后退栈中,清空前进栈。当撤销操作发生时,这条自定义项由后退栈弹出,执行其中定义的Replay方法,后被压入前进栈。这时候再调用前进操作,则过程相反。

导航事件

上文介绍的3种导航方式都是以异步方式执行的,在这个过程中,许多事件依次被触发,你可以处理这些事件来完成如显示进度UI等,甚至可以在事件处理中取消导航。下面两个图展示了导航过程中顺序触发的事件:

页面第一次被加载时,所有触发的导航事件如下

页面之间导航时触发的导航事件:

如上面流程图可看出,在Navigated事件触发前,NavigationProcess会被周期性调用。这个过程中如果发生错误或者取消导航,则将会触发NavigationStopped事件(而不是LoadCompleted事件)。

提示:

在Application中定义了与导航容器中同名的这些导航事件(见上图),这样可以在统一的地方(Application对象中)处理所有导航容器(包括子导航容器)的导航事件。

注意:导航事件触发的场合

当WPF页面之间导航或WPF页面与Html页面间导航时,WPF的导航事件会被触发,而Html页面间导航时怒会触发这些事件,也不会被记录在导航日志内。

数据传递

类似于基于Html的Web应用程序使用将数据编码到Url的方式进行页间数据传递,WPF的导航过程也很需要页间传递数据,下面介绍WPF中夜间数据传递的方式:

向页面传递数据

  • 方法1:

    WPF可以在导航过程中传递参数,这是通过Navigate方法的重载来支持的。对于接收Page实例的Navigate重载与接收Uri的重载又分别有一个重载接收另一个参数作为导航过程中传递的数据。如:

    int photoId = 10;//导航到页面实例并传递参数this.NavigationService.Navigate(new PhotoPage(), photoId);//通过URI进行导航并传递参数this.NavigationService.Navigate(new   Uri(“PhotoPage.xaml”,UriKind.Relative), photoId);

    ?

在目标页中需要处理导航容器的LoadCompleted事件,并由事件参数(NavigationEventArgs类型的参数)的ExtraData属性中得到原始页传递的数据。代码即:

This.NavigationService.LoadCompleted += new    LoadCompletedEventHandler(container_LoadCompleted);…void container_LoadCompleted(object sender, NavigationEventArgs e){  if(e.ExtraData != null)     LoadPhoto((int)e.ExtraData);}

?

  • 方法2:

    此方法更为简单,为导航的目标页面增加一个属性来存储希望在导航过程中传递的数据,并提供一个构造函数可以方便的设置这个属性。然后通过调用接收Page实例的Navigate方法的重载,传入使用待传递数据初始化的目标页面的实例。这样就实现了数据的传递。代码如:

    //目标页面构造函数Public PhotoPage(int id){  LoadPhoto(id);         //或将id存储到Property中}//导航到Page实例PhotoPage next = new PhotoPage(3);this.NavigationService.Navigate(next);

    ?

这种传递方式一个显而易见的好处即参数是强类型的,而不用再做是否为null或类型的判断。

  • 方法3:

    利用Application对象的Properties集合共享全局数据,具体做法可以参见对Application对象的介绍。如果方法1,这种方法也不是类型安全的。

通过PageFunction从页面里返回数据

为了满足如下这种需求(导航至每个页,进行一些操作,然后自动返回到前一页,同时把新数据传回,下图是其中一个例子):

WPF提供了一个叫做PageFunction的类来完成以类型安全的方式把数据返回前一页,并自动后退到前一页的机制。下图展示了新的流程。

PageFunction继承自Page(即其本身也是一个Page),其主要功能是用来返回数据。Visual Studio提供了创建PageFunction的模版:

通过此模版建立的PageFunction形如:

 1 <PageFunction
 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4 xmlns:sys="clr-namespace:System;assembly=mscorlib"
 5 x:Class="WpfApplication1.PageFunction1"
 6 x:TypeArguments="sys:String"
 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 8 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 9 mc:Ignorable="d"
10 d:DesignHeight="300" d:DesignWidth="300"
11 Title="PageFunction1">
12 <Grid>
13 </Grid>
14 </PageFunction>

PageFunction是一个泛型类(PageFunction<T>),其中类型参数表示返回数据的类型。上面xaml中x:TypeArguments即定义了这个类型参数。由于PageFunction的返回值必须是字符串,所以PageFunction中x:TypeArguments值需为sys:String。通过泛型PageFunction也保证了可以类型安全的使用这个类。

而通过PageFunction实现本节初的流程方式如:

//创建PageFunction的实例PageFunction1 next = new   PageFunction1<string>();//像导航到一般页面导航到这个PageFunction    this.NavigationService.Navigate(next);         //在源页面中处理PageFunction的Return事件得到返回值//(PageFunction中)next.Return +=  new   ReturnEventHandler<string>(nextPage_Return);    void next_Return(object   sender, ReturnEventArgs<string> e)    {       string returnValue =   e.Result;}

?

由上面代码可以看出,事件处理函数参数的Result属性与PageFunction的返回类型是一致的。在内部,返回的数据被包装在ReturnEventArgs的对象中。返回时,PageFunction基类中的OnReturn方法,触发事件,返回数据:

OnReturn(new   ReturnEventArgs<string>("the data"));

?

本文完

参考:

《WPF揭秘》

时间: 2024-12-25 07:21:37

WPF,Silverlight与XAML读书笔记第十五 - 页间导航 页间数据传递的相关文章

WPF,Silverlight与XAML读书笔记第七 - WPF新概念之二依赖属性

说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. WPF引入了一种新的属性类型 – 依赖属性.依赖属性用于整个WPF平台,用来实现样式化,自动属性绑定,动画等.详细说即使用属性替代方法和事件处理对象的行为,通过属性驱动来加强系统的行为.如将属性绑定到数据源来驱动用户界面的显示. 依赖属性可以发挥作用的场合如:将一个属性绑定到另一个对象的某属性,要求当被绑定的属性改变时,依赖于那个属性的属性会自动改变(

《android开发艺术探索》读书笔记(十五)--Android性能优化

接上篇<android开发艺术探索>读书笔记(十四)--JNI和NDK编程 No1: 如果<include>制定了这个id属性,同时被包含的布局文件的根元素也制定了id属性,那么以<include>指定的id属性为准 No2: 绘制优化 1)onDraw中不要创建新的局部对象 2)onDraw方法中不要做耗时的任务 No3: 内存泄露优化 场景一:静态变量导致的内存泄露: 如果静态变量持有了一个Activity,会导致Activity无法及时释放. 解决办法:1使用Ap

《Programming in Lua 3》读书笔记(二十五)

日期:2014.8.11 PartⅣ The C API 29 User-Defined Types in C 在之前的例子里,已经介绍过如果通过用C写函数来扩展Lua.在本章,将会介绍通过用C写新的类型来扩展Lua,将会使用到元方法等特性来实现这个功能. 以一个例子来介绍本章将要介绍的,例子实现的功能是实现了一个简单的类型:boolean arrays.实现这个功能主要是这种方法不需要太复杂的算法,因此可以将精力放在API的讨论上.当然我们可以在Lua中用一个table来实现,但是用一个C来实

《C#高级编程》读书笔记(十五):任务、线程和同步之二 任务

1,任务 为了更好地控制并行动作,可以使用System.Threading.Tasks名称空间中的Task类.任务表示应完成的某个工作单元.这个工作单元可以在单独的线程中运行,也可以以同步方式启动一个任务,这需要等待主调线程. 启动任务的几种方法:可以使用TaskFactory类或Task类的构造函数和Start()方法. 第一种方式:实例化TaskFactory类,在其中把TaskMetho方法传递给StartNew方法,就会立即启动任务. 第二种方式:使用Task类的静态熟悉Factory来

[hadoop读书笔记] 第十五章 sqoop1.4.6小实验 - 将mysq数据导入hive

安装hive 1.下载hive-2.1.1(搭配hadoop版本为2.7.3) 2.解压到文件夹下 /wdcloud/app/hive-2.1.1 3.配置环境变量 4.在mysql上创建元数据库hive_metastore编码选latin,并授权 grant all on hive_metastore.* to 'root'@'%' IDENTIFIED BY 'weidong' with grant option; flush privileges; 5.新建hive-site.xml,内容

[hadoop读书笔记] 第十五章 sqoop1.4.6小实验 - 数据在mysq和hdfs之间的相互转换

P573 从mysql导入数据到hdfs 第一步:在mysql中创建待导入的数据 1.创建数据库并允许所有用户访问该数据库 mysql -h 192.168.200.250 -u root -p CREATE DATABASE sqoop; GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'; 或 GRANT SELECT, INSERT, DELETE,UPDATE ON *.* TO 'root'@'%'; FLUSH PRIVILEGES; 查看权限:sel

MYSQL必知必会读书笔记 第十五和十六章 联结表

为什么要使用联结? 如果数据存储在多个表中,怎样使用单条SELECT语句检索出数据?答案就是使用联结.简单地说,可以联结多个表返回一组输出,联结在运行时关联表中正确的行. 1.创建联结 SELECT vend_name,prod_name,prod_price from vendors,products WHERE vendors.vend_id=products.vend_id ORDER BY vend_name,prod_name; 注意:在引用列可能出现二义性时,必须使用完全限定列名.

MYSQL必知必会读书笔记 第十和十一章 使用函数处理数据

拼接字段 存储在数据库表中的数据一般不是应用程序所需要的格式.我们需要直接从数据库中检索出转换.计算或格式化过的数据:而不是检索出数据,然后再在客户机应用程序或报告程序中重新格式化. 计算字段(字段 = 列,不过数据库列一般称为列,而字段通常用于计算字段中)并不实际存在于数据库表中,计算字段是运行时在select语句内创建的. 拼接 concatenate 将值联结到一起构成单个值 在MySQL的select语句中,可使用Concat()函数来拼接两个列. 如创建由两列组成的标题:生成一个供应商

《Linux内核设计与实现》读书笔记(十五)- 进程地址空间(kernel 2.6.32.60)

进程地址空间也就是每个进程所使用的内存,内核对进程地址空间的管理,也就是对用户态程序的内存管理. 主要内容: 地址空间(mm_struct) 虚拟内存区域(VMA) 地址空间和页表 1. 地址空间(mm_struct) 地址空间就是每个进程所能访问的内存地址范围. 这个地址范围不是真实的,是虚拟地址的范围,有时甚至会超过实际物理内存的大小. 现代的操作系统中进程都是在保护模式下运行的,地址空间其实是操作系统给进程用的一段连续的虚拟内存空间. 地址空间最终会通过页表映射到物理内存上,因为内核操作的