摘自 http://my.csdn.net/cc_net 的博客,写的不错,内容比较多,直接全引过来了。 Windows Phone开发(一)-- 开发环境和结构 Windows Phone开发(二)-- 框架结构和启动过程 Windows Phone开发(三)-- 导航原理分析 Windows Phone开发(四)-- 导航事件和传值 Windows Phone开发(五)-- 导航控制 Windows Phone开发(六)-- 多任务之墓碑机制

摘自 http://my.csdn.net/cc_net 的博客,写的不错,内容比较多,直接全引过来了。

 

Windows Phone开发(一)-- 开发环境和结构       

        分类:            Windows Phone 6042人阅读 评论(3) 收藏 举报

目录(?)[+]

  1. 一 Windows Phone 学习资料
  2. 二 Windows Phone 开发环境
  3. 三 第一个Windows Phone 7程序
    1. 在MainPage.xaml中添加一个按钮,并且添加触屏事件,代码如下
    2. 为项目添加一个新的页面,鼠标右键--ADD--New Item--Windows Phone Portrait Page,名字为Page1.xaml,在次页面中添加一个文本和一个返回按钮,代码如下:
    3. 为MainPage.xaml的触屏事件和Page1.xaml触屏事件添加代码(和C#其他项目一样,也是采用code-behind,代码在xaml页面的cs文件中)
    4. 编译后点F5运行程序,模拟器第一次启动需要等待(我也没有真机,所以无法进行真机调试)。然后点击Next Page按钮程序将导航进入到第二个界面。第二个界面中点击Back导航回前一个界面。可以发现Windows Phone程序同Web程序页面有点类似,都是Page导航的概念,这个和之前WM平台很不一样。但是使用过WPF或Silverlight就没有什么特别的。
    5. 进入第二个页面后,点击【<--】按钮也能退回到第一个界面,在点【<--】按钮,就会关闭程序。要注意的是,Windows Phone目前没有提供任何退出程序的Exit方法,都是通过在第一个页面点击【<--】按钮退出程序。在程序中点击中间的Home按钮返回到Home列表,此时程序会进入休眠(WP7.1)或墓碑(WP7.0)状态,这个后面会介绍。
  4. 四 Windows Phone项目结构
  5. 五 总结

又是好久没有写BLOG了,做完上一个MSN项目后都是些琐碎的事情,然后就是是玩摄影啊,旅游啊。上月休假去了趟西藏,真是不错。回来了也要收心学习了。Windows Phone 7.1的开发工具发布了,一直对WP7很关注,现在终于可以开始学习了。其实09年就学习过silverlight,看过3的SDK文档,当时因为工作,断断续续也没有坚持下来,所以这次学习WP7顺便重新学习SL。

上周安装了WP7.1的开发环境,工作之余看了一周文档。目前关于WP的学习文档大多是英文的,chian-pub上出现了一本中文的书,不过还是推荐看MSDN,上面有最新的WP7.1的内容,毕竟7.1进行了较大的更新。我写Windows Phone开发的文章主要是对自己看的文档进行一个提炼和记录,主要针对wp7.1Beta。


一 Windows Phone 学习资料

目前主要的学习资料还是来至于微软MSDN和《Programming Windows Phone 7》,其中MSDN包含了最新的7.1Beta特性的介绍和使用。一些中文网站和原创的BLOG文章基本也是来自这些地方。而MSDN杂志每期有对Windows Phone介绍的文章。关于界面开发可以参考Silverlight开发。

Windows Phone 中文开发中心

Windwos Phone Development

Silverlight for Windows Phone

Phone Platform Development

Windows Phone developer documentation

Programming Windows Phone 7

视屏学习资料

Windows Phone 7 中文开发资料概览

MSDN杂志


二 Windows Phone 开发环境

微软在5月份发布了代号Mango的最新开发工具,Windows Phone Developer Tools 7.1 Beta (点击下载ISO文件)

系统要求:

  • 操作系统:Windows Vista SP2 / Windows 7
  • 硬件要求:4G硬盘空间 3G内存
  • 手机模拟器:DirectX10以上显卡,WDDM1.1驱动

PS:我的笔记本是T2450+3G内存+HD2300显卡,07年的了,正常运行。

安装准备:

  • 如果没有安装VS2010,可以直接安装Windows Phone Developer Tools 7.1 Beta,他会自带Express版本的VS2010 for Windows Phone
  • 如果安装了VS2010专业版或者更高版本,可以直接安装,但是必须把VS2010的语言修改成同Windows Phone Developer Tools 7.1 Beta相同的语言
  • 如果安装了VS2010 RTM版本,必须先安装VS2010 SP1(点击下载ISO文件)
  • 如果安装了非英文版本的Windows Phone Developer Tools 7.0或 Expression Blend,必须先卸载在进行安装

PS:我之前安装了VS2010中文旗舰版,所以先安装了SP1(安装SP1时第一次到了一半就停住了,后来我查了说可能缺少文件,就把VS2010的ISO也加载到虚拟光驱中然后就通过了),但是因为Windows Phone Developer Tools 7.1 Beta目前只有英文版的,所以安装完成后,并没有在VS2010中集成WP7开发模板,但集成了XNA Game Studio的开发环境;而是安装了VS2010 Express for Windows Phone。


三 第一个Windows Phone 7程序

我们可以看到其中有很多模板可以选择,下面进行简单介绍,具体可以建立相应项目查看

  • Windwos Phone Application: 创建一个普通的Windows Phone应用程序
  • Windwos Phone Databound Application: 创建一个和数据绑定有关的项目,使用列表和导航控件
  • Windows Phone Class Library:创建一个类库项目
  • Windwos Phone Panorama Application: 创建一个全景应用项目,使用全景Panorama控件
  • Windwos Phone Pivto Application:创建一个枢轴应用项目,使用Pivto控件
  • Windwos Phone 3D Graphics Application: 创建一个XNA框架支持的项目(WP7.1可以silverlight和XNA集成)
  • Windwos Phone Audio Playback Application:创建一个音频播放的类库项目
  • Windwos Phone Audio Streaming Application:创建一个音频流的类库项目
  • Windwos Phone Task Scheduler Agent:创建一个后台任务代理项目(WP7.1中新增了后台多任务代理)

选择了项目模板之后,就要选择Windows Phone的开发版本,目前有7.0和7.1可以进行选择。

上图就是WP7的开发环境,如果有开发过silverlight,应该不会陌生。左边是图形的界面环境,其他.NET其他开发环境一样,可以拖控件来布局。而右侧是页面的XAML代码,可以通过编写XAML来布局。关于XAML可以参见MSDN(点击打开)

我们的第一个程序要实现的是点击页面上的一个按钮导航到下一个页面:

1 在MainPage.xaml中添加一个按钮,并且添加触屏事件,代码如下

2 为项目添加一个新的页面,鼠标右键--ADD--New Item--Windows Phone Portrait Page,名字为Page1.xaml,在次页面中添加一个文本和一个返回按钮,代码如下:

 

3 为MainPage.xaml的触屏事件和Page1.xaml触屏事件添加代码(和C#其他项目一样,也是采用code-behind,代码在xaml页面的cs文件中)

4 编译后点F5运行程序,模拟器第一次启动需要等待(我也没有真机,所以无法进行真机调试)。然后点击Next Page按钮程序将导航进入到第二个界面。第二个界面中点击Back导航回前一个界面。可以发现Windows Phone程序同Web程序页面有点类似,都是Page导航的概念,这个和之前WM平台很不一样。但是使用过WPF或Silverlight就没有什么特别的。

5 进入第二个页面后,点击【<--】按钮也能退回到第一个界面,在点【<--】按钮,就会关闭程序。要注意的是,Windows Phone目前没有提供任何退出程序的Exit方法,都是通过在第一个页面点击【<--】按钮退出程序。在程序中点击中间的Home按钮返回到Home列表,此时程序会进入休眠(WP7.1)或墓碑(WP7.0)状态,这个后面会介绍。

至此我们完成了第一个Windows Phone程序,它很简单,看起来和写一个普通的Silverlight程序没有太大的区别。


四 Windows Phone项目结构

项目建立好之后,包含了以下一些文件:

  • AppManifest.xml : 此文件中包含一个<Deployment.Parts>的节点。如果需要调用XAP文件中包含的其他Assembly的DLL文件,就会在此节点下添加一个<AssemblyPart/>节点来列举这些文件,可以参见这里
  • AssemblyInfo.cs : 这个文件包含了对当前程序集的信息,可以在属性页面进行设置
  • WMAppManifest.xml : 这个文件主要记录了程序的起始页面,APPID,作者,图标设置和程序功能设置。其中<Capabilities/>节点定义了程序的功能,比如ID_CAP_PHONEDIALER表示可以使用电话功能,如果没有这个节点,调用相应功能时就会出错,详细介绍见这里。相对于7.0,7.1增加了对相机,联系人,约会提醒功能。
  • App.xaml : 这个文件App类继承与Application类,它并没有可视化界面,<Application.Resources/>一般用来存放资源数据共全局使用。而<Application.ApplicationLifetimeObjects/>节点下定义了与执行模型相关的一些方法。
  • App.xaml.cs : App类的另一部分,代码包含了程序初始化操作和执行模型相关的一些方法的实现。
  • ApplicationIcon.jpg : 显示在程序列表中的图标,大小为62*62
  • Bcakground.jpg : 在程序启动时显示的图标,大小为173*173s
  • MainPage.xaml : 程序启动后默认显示的第一个页面,用户也可以在WMAppManifest.xml中指定启动页面。
  • SplashScreenImage.jpg : 程序启动时显示的欢迎界面,大小为480*800,这个和设备有关。

以上介绍了一个Windows Phone项目的结构,和Silverlight程序结构很类似。只是配置文件上有些区别。


五 总结

这里完成了Windows Phone 7.1环境的搭建,并完成了第一个Windows Phone程序,对于程序模板和项目结构有了一个认识。下一篇将介绍Windows Phone框架结构,以及程序启动运行方式。

源码下载

PS:对CSDN编辑器真是无语了,标题和上面竟然没有间隔,回车也没用。。。快改版升级吧

Windows Phone开发(二)-- 框架结构和启动过程       

        分类:            Windows Phone 2001人阅读 评论(4) 收藏 举报

上一篇文章介绍了Windows Phone的开发环境和一个简单的Windows Phone程序的演示和结构,这一篇文章要深入一点,介绍Windows Phone的框架结构和程序启动的过程。


一 Windows Phone 框架结构

在进行Windows Phone开发之前有必要了解一下整个全新平台的结构。对于Windows Phone平台来说,区别于之前的Windows Mobile平台最大的区别是他运行环境完全基于.NET框架,他只支持托管代码进行开发。

Windows Phone是基于.NET Compact Framework框架,所以精简了很多桌面版的功能。但是还是提供了所需的基本功能。如上图中的最下层的蓝色部分,就是.NET CF提供的BCL类库。而在他的上层就是Application Object。这一层都是基于.NET CF框架。其中包含了两种开发框架:

  • Silverlight Framework :以 Silverlight Framework 为基础的 Windows Phone 7 应用程序是由一堆的 Page (继承自 PhoneApplicationPage 类别的衍生类别) 组成的,每一个 Page 是一个扩展名为 .XAML 的文件,代表一个操作画面,程序设计师可以利用 Visual Studio 2010 Express for Windows Phone 或是 Expression Blend for Windows Phone 来设计 Page 的用户接口。WP7.0是基于Silverlight3.0,WP7.1则是基于Silverlight4。关于Silverlight for Windows Phone和Silverlight的区别(点击打开)
  • XNA Framework:主要的用途在支持开发游戏程序,提供 2D/3D 的动画,音效,及各种游戏相关的功能,协助有志于开发游戏程序的企业或个人发展 Windows Phone、Xbox 360、Zune 播放器、以及 Windows 7 平台的游戏程序。

毕竟移动手机平台还有自己特殊的一部分功能,所以在两个框架的上层Windows Phone特有的一些功能,比如相机,Windows Phone控件,感应器,多点触控屏幕,Launcher,Chooser等等来提供相应功能。

所以具体到实际开发,我们有2套开发框架进行选择,对于大多应用程序我们可以选择Silverlight进行开发,而对于游戏可以选择XNA。但是WP也提供部分功能相互调用的功能。而对以WP7.1来说可以建立一个silverlight和XNA集成的开发环境(点击查看)。

对于Windows Phone开发来说,不仅可以使用.NET CF和silverlight或XNA框架提供的这些功能,还能使用微软基于云服务的一些功能。比如Notification,Social, Location,Map ,Azure等服务。下图显示了Windows Phone上支持的的运行时框架和开发工具。下面的显示提供的运服务以及注册开发者和发布程序。他们组成了对Windows Phone开发的软件支持。

而对于硬件来说,微软统一了硬件平台,目前要求的硬件包括 800 x 480 或 480 x 320 屏幕分辨率,支持多点触控,内建 A-GPS 卫星定位系统,G-Sensor (Accelerometer),电子罗盘传感器,光源传感器,以及不需要直接接触就可以侦测到附近物体的 Proximity Sensor。最少 500 万画素的数字相机,内建 Codec 与多媒体影音播放功能,最少 256MB 的 RAM 与最少 8GB 的闪存,GPU (图形处理器),ARMv7 Cortex/Scorpion 或更佳的处理器,以及 Back、Start、Search 三个硬件按键。目前HTC和三星已经有第一代的WP7手机上市,可以机型和ROM信息可以参见:智机网

关于Windows Phone Plateform的详细介绍参见MSND(点击打开)


二 Windows Phone 7.1 新功能

目前最新版本的系统是Windows Phone 7.1Beta,相对于Windows Phone他有了比较大的改动,弄清楚每一版本的功能,可以更好的了解系统和选择开发的版本。

  • 粘贴和复制功能:1月的更新增加了粘贴复制功能
  • 执行模型快速切换:实现了新的执行模型,增加了休眠状态,使得应用程序可以快速切换。
  • 后台程序代理:增加后台代理功能,可以在程序没有在前台运行的情况下在进行提示,另外还有后台播放和后台下载功能
  • 感应器APIs:公开了感应器的API,以便程序可以对硬件进行访问。
  • 支持Socket:增加了Socket的支持,可以开发基于TCP和UDP的程序,非常有用。
  • 相机数据:增加了对相机原始数据访问的功能
  • 推送消息:增加了推送消息的功能,更加稳定
  • Silverlight和XNA集成:可以在同一个Page下使用silverlight和XNA进行开发
  • 程序配置文件:提供了对程序和游戏进行配置的功能
  • WebBrowser :浏览器控件可以支持IE9,并优化了HTML5的表现
  • 本地数据库:增加了对数据库的支持,可以使用LINQ to SQL进行操作
  • Launchers and Choosers  : 提供了新的功能,如Bing Map,Address等
  • 全球化和本地化:支持包括中文在内的16种语言

具体Windows Phone 7.1新的改进(点击打开)

关于程序的兼容性,简单说WP7.0开发的程序可以在OS7.0和7.1上运行,而7.1开放的程序在7.0上可能会失败。具体兼容性问题参见这里


三 Window Phone 程序启动过程

在我们上一篇文章中,我们写了一个简单的Demo程序。程序启动后显示的是MainPage.xaml页面。那么程序是如何成功启动的呢?

1 xap文件

打开程序后,我们对程序进行编译,然后我们可以在/PhoneApp2/Bin/Debug目录下看到名字为PhoneApp.xap文件,这个就是我们程序的包。部署到手机上就能运行。这其实是一个压缩文件,我们可以用7-ZIP来解压。

其中包含了程序使用的3个JPG文件,PhoneApp2.dll就是我们程序功能的DLL文件,而AppMainfest.xaml和WMAppMaingest.xml文件则提供了应用程序的一些信息。

xap包结构

上图是一个silverlight的XAP包的结构,WP基于silverlight开发的程序结构是相似的,不过多一个WMAppMaingest.xml文件,用来配置手机相关的一些信息,这个在上一篇有介绍过。首先来看看AppManifest.xaml 文件,它标识打包的程序集和应用程序入口点。

这个和上一篇文章相比,已经自动生成了一些内容。我们来简单看一下:

  • EntryPointAssembly : 定义了程序的入口程序集的名字,也就是开始运行时加载的程序集,这里就是我们的PhoneApp2.dll
  • EntryPointType : 表示了程序的入口点,这里指定了项目中的App对象
  • RuntimeVersion : 表示当前silverlight运行的版本,这里可以看到,WP7.1是基于SL4。
  • Deployment.Parts : 此节点中包含了xap包中的程序集列表。

关于xap包结构和此文件结构具体信息,详细可以参考silverlight的应用程序结构。对于WMAppMaingest.xml文件没和编译前没有变化,详细结构上一篇文章中有链接介绍。

2 App.xaml和App.xaml.cs文件

AppMaingest.axml中定义了程序的入口点,其中指定了App对象。通过查看这2个文件,我们发现App.xaml.cs文件中定义了App类,此类继承与Application类,而App.xaml文件的根节点Application指定了x:Class="PhoneApp2.App",这个表示把xaml文件中的内容同xaml.cs类中内容合并。这里所有内容编译后会合并到App类中。

当使用 Silverlight 的托管 API 创建应用程序时,必须创建一个从 Application 派生的类。Application 类提供应用程序通常要求的若干服务。它主要表示应用程序代码在 Silverlight 插件生命周期中的入口点。Application类的以下功能:

  • 应用程序生存期管理
  • 应用程序资源文件加载
  • 未经处理的异常处理
  • 扩展性

我们看到App.xaml文件中定义了<Application.Resources>节电,这个可以用来定义一些全局的资源别如string,style,笔刷等;<Application.ApplicationLifetimeObjects>则是提供了对Application的扩展性,这里提供了PhoneApplivationService的扩展服务,从名字我们就能知道这个是有关Phone的扩展,因为默认的Silverlight是没有的。这个是位于Micresoft.Phone.dll中,这儿服务提供了4个与手机有关的方法。关于扩展性可以参见silvrlight的扩展服务

[c-sharp] view plain copy print ?
  1. public PhoneApplicationFrame RootFrame {get; privateset; } 
  2.  
  3. public App() 
  4.     UnhandledException += Application_UnhandledException; 
  5.     InitializeComponent(); 
  6.     InitializePhoneApplication(); 
  7.     PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled; 

以上是App.xaml.cs文件中的代码,首先定义了一个PhoneApplicationFrame属性RootFrame,在构造函数中定义了UnhandledException来处理未处理异常,这是Application的一个功能;InitializeComponent是初始化方法,点击F12导航到App.g.i.cs文件,这个是系统自动根据xaml生成的。

[c-sharp] view plain copy print ?
  1. public void InitializeComponent()  
  2.     if (_contentLoaded) 
  3.     { 
  4.         return
  5.     } 
  6.     _contentLoaded = true
  7.     System.Windows.Application.LoadComponent(this,new System.Uri("/PhoneApp2;component/App.xaml", System.UriKind.Relative)); 

这里调用了Application.LoadComponent方法。这个方法的作用是 加载位于指定统一资源标识符 (URI) 处的 XAML 文件,并将其转换为由该 XAML 文件的根元素指定的对象的实例。这里就是生成App类的实例对象。用Reflector看没有代码,我们可以看看Silverlight中的实现:

首先获得了程序集相关的信息,然后获得Uri的资源,最后XcpImports是.NET部分调用core部分的接口,core部分是用C++编写的Core presentation framework,主要功能是XAML parser, UI Core, Inputs, DRM(digital rights management), Media, Deep Zoom等,多数是需直接与具体的操作系统API打交道的功能(可以参考:Silverlight CoreCLR结构浅析)。最终加载的是一个Application_LoadComponentNative的Native方法。

这个实例可以通过Application.Current属性获得。从下面的构造函数可以看出,_current被设置为this,zero指针指向了CreateObjectByTypeIndex方法根据nativeTypeIndex创建的App native对象。而在Current中获得对象实例时,先获得这个指针,然后EnsureManagedPeer方法使用Activator.CreateInstance(type) 创建了Application的实例。

以上是Silverlight和Windows Phone都有的下面看看Windows Phone中才有的部分。在构造函数中PhoneApplicationService.Current.UserIdleDetectionMode通过扩展服务设定了是否自动检测空闲,另外文件中包含了Launching,Activated,Deactivated,Closing四个方法,这4个方法是和Windows Phone执行模型有关的,后面会介绍。在构造函数中还有一个InitializePhoneApplication()方法,此方法实现如下。

此方法确保只会被执行一次,首先生成了一个PhoneApplicationFrame对象,保存到RootFrame中,然后绑定了Navigated事件。也就是在Frame导航之后执行CompleteInitializePhoneApplication。此方法中把RootFrame设置到RootVisual中。RootVisual用来获取或设置主要应用程序用户界面。只能从代码设置RootVisual 属性的值一次。

代码可以看到XcpImports.Application_SetVisualRoot方法,说明最终也还是交给了Core presentation framework去处理,开始显示我们程序的界面。

3 与Silverlight区别

[c-sharp] view plain copy print ?
  1. public App() 
  2.     this.Startup += this.Application_Startup; 
  3.     this.Exit += this.Application_Exit; 
  4.     this.UnhandledException +=this.Application_UnhandledException; 
  5.  
  6.     InitializeComponent(); 
  7.  
  8. private void Application_Startup(object sender, StartupEventArgs e) 
  9.     this.RootVisual = new MainPage(); 

以上是Silverlight4程序App类的方法。在Startup中设置了RootVisual为MainPage,那么在启动时就会显示MainPage.xaml页面。我们知道Frame和Page是Silverlight中用来导航的类。而在Windows Phone中则是使用PhoneApplicationFrame和PhoneApplicationPage,其中Frame是容器控制导航,而Page是在Frame中,用来显示用户界面。对于silverlight我们可以把代码修改为类似Windows Phone的代码:

我们同样定义一个RootFrame,生成一个Frame对象,设置到RootVisual上,然后通过Frame导航到MainPage.xaml。结果可以正常显示,但是如果我吧Navigate方法注销掉,结果空白,没有任何显示。但是我们看上面Windows Phone中并没有见到Navigate方法,但是还是显示了MainPage.xaml界面。

4 显示第一个界面

为什么没有调用Navigation还是显示了MainPage呢。不要忘记我们还有一个配置文件WMAppMaingest.xml。

其中Task节点是Application下的子节点,其中有一个属性是NavigationPage,MSDN解释是这里是指定启动时要导航到的Page页面。我们试试把这个修改为Page1.xaml。运行后发现,程序第一个界面显示为Page1.xaml。

[c-sharp] view plain copy print ?
  1. private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) 
  2.     if (RootVisual != RootFrame) 
  3.         //RootVisual = RootFrame; 
  4.         RootVisual = new MainPage(); 
  5.  
  6.     RootFrame.Navigated -= CompleteInitializePhoneApplication; 

然后我们继续做个修改,直接把RootVisual设置为MainPage。运行。。结果显示的是MainPage。所以对于RootVisual来说是设置第一个显示的元素。当我们传递给他的是一个Page或UserControl时,他直接显示此页面的内容;如果传递一个Frame时,如果设置了Source或则调用了Navigation,就会导航到指定的Page显示,如果不指定,Frame是一个容器,没有设置Page就不会显示任何东西,所以会出现上面空白的结果。对于WP7来说,程序从WMAppMaingest.xml中配置得到首页并设置到了Source中。

5 启动界面

在程序启动过程中,会执行Startup和Application_Launching方法。这里可能是一些耗时操作,如果时间超过1S,就推荐使用启动界面,改善用户体验。建议是不在在启动过程中做过多操作,因为启动时间超过10S程序会被直接终止掉。

启动画面图片名为SplashScreenImage.jpg,我们在启动的Startup或者Application_Launching中Thread.sleep(2000), 来模拟启动时的耗时操作。这样启动界面就可以显示。但是很奇怪,我在模拟器上根本不会显示,然后下载了微软的例子Splash Screen Sample运行后也没有显示出来。不知道什么原因。

在InitializePhoneApplication方法中并没有设置RootVisual,这是为了让启动画面在主界面准备呈现时仍旧有效。我们把RootVisual放到此方法最后也是可行的(和silverlight一样)。不过这里有点不明白,为什么用Navigated事件,是不是启动画面完成后会引发这个事件?

[c-sharp] view plain copy print ?
  1. private void Application_Startup(object sender, StartupEventArgs e) 
  2.     //this.RootVisual = new Page1(); 
  3.     RootFrame = new Frame(); 
  4.     RootFrame.Navigated += new System.Windows.Navigation.NavigatedEventHandler(RootFrame_Navigated); 
  5.     bool ret = RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 
  6.     //RootFrame.Source = new Uri("/Page1.xaml", UriKind.Relative); 
  7.  
  8. void RootFrame_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e) 
  9.     RootFrame.Navigated -= RootFrame_Navigated; 
  10.     this.RootVisual = RootFrame; 

比如我们吧一个silverlight修改成和WP7类似的,在Navigated中才设置VisualRoot,虽然Navigate方法返回了True,但是并没有触发此事件,而程序因为没有设置VisualRoot一直在等带状态没有任何显示。所以对于Windows Phone来说一定是触发了次事件,但是如何触发的目前不清楚。

另外启动时方法执行的顺序是App()-->Startup()-->Application_Launching();


四 总结

这一篇文章大概介绍了Windows Phone的框架结构和7.1版本的一些更新。然后稍微深入的了解了整个程序的启动过程涉及的一些内容以及和Silverligth之间的区别。总的来说就是宿主程序通过AppMainfest.xaml来获取程序的入口点,然后初始化调用Application.LoadComponent方法来生成App对象的实例,接下来初始化手机,定义一个Frame,,通过WMAppMaingest.xml文件获取启动时的Page页面设置到Frame,然后设置到RootVisual来显示此Page。

本文涉及代码较少,在上一篇文章的代码上修改既可。

 

Windows Phone开发(三)-- 导航原理分析       

        分类:            Windows Phone 1240人阅读 评论(3) 收藏 举报

前两篇文章中,我们的Demo代码都是基于页面切换的,而我们在Silverlight框架下开发的就是以XAML文件为基础的事件驱动程序。也就是说我们的程序会由一个或多个页面作成,这一点和Web程序很相似,所以页面间的切换就很重要。 这一篇文章就来将介绍Windows Phone平台上导航功能。


一 导航控件

从Silverlight3开始,提供了内置的导航框架,可以比较轻松的在 Silverlight Page之间进行切换,并且可以和浏览器的前进、后退按钮集成。在Silverlight 3之前的版本,Silverlight没有特定的导航框架,项目中页面之间的切换是通过修改RootVisual布局容器的内容而实现的
。利用SDK中提供了Frame和Page可以完成导航操纵。其中Frame是导航的框架,Page通过Frame加载显示并实现页面间的导航。

Windows Phone程序也是基于Sliverlight的Page Model进行导航,同时你也可以使用后退按钮进行后退操纵。WP上核心的导航容器控件为PhoneApplicationFrame,他可以容纳一个PhoneApplicationPage。我们可以创建多个页面,通过Frame来进行导航。

对于Windows Phone来说,只允许有一个Frame,一个Frame有一下特性:

  • 操作寄宿的Page页面的属性,比如orientation
  • 指定页面呈现的客户端区域
  • 为状态栏和程序栏保留空间
  • 监听obscured和unobscured事件

而对于Page来说,他会填充满Frame所在的空间。除此之外,程序中还有Status barApplication Bar,他们都能设置visible 属性;Window Phone也支持屏幕旋转,但是只有在转动设备时才能使之旋转,而不能通过编程的方式实现,因为orientation是只读属性;我们只能通过设置SupportedOrientations来完成;机器的后退按钮可以完成导航中的后退功能,也能关闭键盘,菜单,弹出窗体,同时还能关闭程序。

关于使用这两个空间导航,参见前两篇文章的例子。定义一个Frame,设置到VisualRoot。在配置文件中设置开始导航的页面。然后通过Frame或者NavigationService来进行导航。参考 :Frame and Page Navigation for Windows Phone


二 导航框架分析

我们还是接着上一篇文章程序启动来展开,看看Frame是如何去导航的。

1 导航框架的初始化

程序启动的的第一步是在App类中,我们创建了一个PhoneApplicationFrame实例。因为PhoneApplicationFrame是继承于Frame的,所以我们先看看Frame构造函数做了什么。

[c-sharp:nogutter] view plain copy print ?
  1. internal Frame() 
  2.     base.DefaultStyleKey =typeof(Frame); 
  3.     base.Loaded += new RoutedEventHandler(this.Frame_Loaded); 
  4.     this._hostInfo = new HostInfo(); 

构造函数中绑定一个Loaded事件和设置Host信息(如果看过SilverLight的源码,会发现和这的构造函数是有区别的)。接着就看看PhoneApplicationFrame的构造函数

这里面前面一部分是设置界面方向相关的一些内容,然后设置了可见区域,这里被省略了,这些都是上面说的Frame有的特性;接下来的工作很重要定义了一个NavigationService对象,保存在Frame的字段中。我们接着看看这个NavigationService对象构造函数

[c-sharp] view plain copy print ?
  1. internal NavigationService(PhoneApplicationFrame nav) 
  2.     this._navigationPendingLock =new object(); 
  3.     this._cacheRequiredPages = new Dictionary<string, PhoneApplicationPage>(); 
  4.     PerfUtil.BeginLogMarker(MarkerEvents.TH_INIT_NAVIGATIONSERVICE,"NavigationService started"); 
  5.     Guard.ArgumentNotNull(nav, "nav"); 
  6.     this._host = nav; 
  7.     HostFrame = nav; 
  8.     HostInfo info = new HostInfo(); 
  9.     this._shellPageManagerCallback =new ShellPageManagerCallback(); 
  10.     this._shellPageManagerCallback.OnCancelRequestEventHandler = (EventHandler) Delegate.Combine(this._shellPageManagerCallback.OnCancelRequestEventHandler,new EventHandler(this.ShellPageManager_OnCancelRequest)); 
  11.     this._shellPageManagerCallback.OnPageStackReactivatedEventHandler = (EventHandler<PageStackReactivatedEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnPageStackReactivatedEventHandler,new EventHandler<PageStackReactivatedEventArgs>(this.ShellPageManager_OnPageStackReactivated)); 
  12.     this._shellPageManagerCallback.OnResumePageRequestEventHandler = (EventHandler<ResumePageRequestEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnResumePageRequestEventHandler,new EventHandler<ResumePageRequestEventArgs>(this.ShellPageManager_OnResumePageRequest)); 
  13.     this._shellPageManagerCallback.OnInvokeReturningEventHandler = (EventHandler<InvokeReturningEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnInvokeReturningEventHandler,new EventHandler<InvokeReturningEventArgs>(this.OnInvokeReturning)); 
  14.     this._shellPageManager =new ShellPageManager(info.LastInstanceId, info.HostWnd,this._shellPageManagerCallback); 
  15.     this._shellPageManager.OnObscurityChangeEventHandler +=new EventHandler<ObscurityChangeEventArgs>(this._host.ShellPageManager_OnObscurityChange); 
  16.     this._shellPageManager.OnLockStateChangeEventHandler +=new EventHandler<LockStateChangeEventArgs>(this._host.ShellPageManager_OnLockStateChange); 
  17.     this._shellPageManager.PauseSupported =true
  18.     this._quirkShouldNotAllowBackgroundNavigation = QuirksMode.ShouldNotAllowBackgroundNavigation(); 
  19.     this._quirkShouldCallOnNavigatingFromPageForExternalNav = QuirksMode.ShouldCallOnNavigatingFromPageForExternalNavigations(); 
  20.     this._quirkShouldForceTextBindings = QuirksMode.ShouldForceTextBindings(); 
  21.     ChooserListener.Initialize(); 
  22.  
  23.   

这里主要是定义了一个_cacheRequiredPages和设置了host和Frame的值为传入的PhoneApplicationFrame对象。这时,PhoneApplicationFrame和NavigationService相互引用对方,和面绑定了一些事件。而PhoneApplicationFrame构造函数继续执行,Dispatcher.BeginInvoke(a)来执行Frame.Load方法。


我们仔细看看这个方法。

A. 执行了3个初始化的方法,InitializeJournal初始化历史记录相关的对象,InitializeNavigationCache初始化缓存相关的对象;
InitializeContentLoader初始化内容加载相关的对象

B. InitializeContentLoader中生成了一个PageResourceContentLoader对象,它是 Silverlight 框架中当前唯一的 INavigationContentLoader实现。他的作用就是用来加载要显示的xaml页面,这里是为Frame加载Page做准备,后面会继续介绍。

C. 然后我们看到一些列的Navigate方法。记得上一篇中我们谈到过为什么没有调用Navigate或者设置Source属性,还能显示MainPage.xaml的问题。是的,就在这里,在这里调用了Navigate方法。到底是那一个方法呢?我们在这里做个小实验。

[xhtml:nogutter] view plain copy print ?
  1. //NavigationPage="MainPage.xaml" 删除这个属性 
  2. <Tasks> 
  3.     <DefaultTask Name ="_default"> 
  4. </Tasks> 

修改WMAppMainfest.xml文件。这样就没有默认的页面了,我们在App中加入对Source的设置和Navigate调用。

[c-sharp:nogutter] view plain copy print ?
  1. RootFrame = new PhoneApplicationFrame(); 
  2. RootFrame.Source = new Uri("/Page1.xaml", UriKind.Relative); 
  3. RootFrame.Navigate(new Uri("/Page2.xaml", UriKind.Relative)); 
  4. RootFrame.Navigated += CompleteInitializePhoneApplication; 

运行程序发现程序正常显示了Page2.xaml页面,那么说明第一个条件判断的ApplyDeepLinks方法是从配置文件中获取NavigationPage的方法。具体如何获得的这里就不深究了,有兴趣自己看源码吧,通过调试发现是在Host对象的TaskPage属性中。这时Frame就导航到了指定的页面。此时也完成了导航框架的初始化。那么系统如何获得Page2的Uri的呢?这个后面解释。

[c-sharp:nogutter] view plain copy print ?
  1. private void InitializePhoneApplication() 
  2.     if (phoneApplicationInitialized) 
  3.         return
  4.  
  5.     RootFrame = new PhoneApplicationFrame(); 
  6.     RootFrame.Navigated += CompleteInitializePhoneApplication; 
  7.     RootFrame.NavigationFailed += RootFrame_NavigationFailed; 
  8.     phoneApplicationInitialized = true
  9.  
  10. private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) 
  11.     // Set the root visual to allow the application to render 
  12.     if (RootVisual != RootFrame) 
  13.         RootVisual = RootFrame; 
  14.  
  15.     // Remove this handler since it is no longer needed 
  16.     RootFrame.Navigated -= CompleteInitializePhoneApplication; 

我们在回头看看上一篇留下的疑问,为什么这里要绑定Navigated事件。现在应该明白了,在PhoneApplicationPhone的构造函数中,调用了Navigate方法导航到配置文件中的MainPage.xaml,当导航完成时,就吧Frame设置到VisualRoot,这时就能把Frame中的Page显示出来了。这里可能会有个疑问,在构造函数中调用Navigation,但在对象构造完才绑定Navigated事件,这能行吗? 在构造函数中调用的是下面的方法:

[c-sharp] view plain copy print ?
  1. Deployment.Current.Dispatcher.BeginInvoke(a); 

这方法实际是把操作发送UI线程执行,但是目前我们还在执行App的构造函数,所有只有构造好App对象UI才能处理这个Load操作,这是我个人看法不知道对不对,有待在研究。

2 Frame加载XAML文件

框架初始化完成后,执行Load方法中的Navigate,让Frame导航到指定页面,那么导航到这个页面是怎么加载这个页面的呢? 我们先看看Frame的Navigate方法到底是怎么执行的。

[c-sharp:nogutter] view plain copy print ?
  1. public bool Navigate(Uri source) 
  2.     if (this._loaded) 
  3.     { 
  4.         return this._navigationService.Navigate(source); 
  5.     } 
  6.     this._deferredNavigation = source; 
  7.     return true

我们发现塔实际是调用NavigationService的方法,实际上Frame和NavigationService中有很多同名的方法,实际都是在NavigationService类中实现的,他才是实际负责导航和加载页面的。这也和我们开始标注的Frame的4点特性吻合。

这里还有个_deferredNavigation字段,表示延迟导航。实际这个我们在上面的例子中留下的疑问。上面的例子我们删除掉配置文件中的MainPage.xaml,而增加了调用RootFrame.Navigate方法显示了Page2,这时并没有真的导航,应为Frame还没有初始化好,只是包Uri保存到了这个字段中。所以返回True不代表完成导航。等App构造完成,执行Load方法时没有获取配置文件,而_deferredNavigation字段又有值,就导航到了Page2。

[c-sharp:nogutter] view plain copy print ?
  1. public bool Navigate(Uri source) 
  2.     Action a = null
  3.     Uri uri = source; 
  4.     NavigationMode mode = NavigationMode.New; 
  5.     PerfUtil.BeginLogMarker(MarkerEvents.TH_PAGE_NAVIGATION, "Page navigation: " + ((uri == null) ?"" : uri.ToString())); 
  6.     try 
  7.     { 
  8.         JournalEntry journalEntry = new JournalEntry(uri.OriginalString, uri); 
  9.         this.Journal.AddHistoryPoint(journalEntry); 
  10.         return true
  11.     } 
  12.     catch (Exception exception) 
  13.     { 
  14.         if (this.RaiseNavigationFailed(uri, exception)) 
  15.         { 
  16.             throw
  17.         } 
  18.         return true
  19.     } 

以上代码是NavigationService.Navigate方法的实现,只保留了关键代码。其中将传入的Uri保存到了一个JournalEntry的实体类中,此类是表示后退或前进导航历史记录中的一个条目。我们把加载的Page都用AddHistoryPoint保存起来。

通过CreatePage创建了一个ShellPage对象,这是一个未公开的类型,所以没有资料,但是应该是和界面有关,这里不深入。然后注册了相关的事件,最后调用了NavigateTo方法。但是此方法看不到具体实现。根据这里情况推测应该是跳转到指定的ShellPage页面。这个时候会触发Journal对象的OnNavigateAwayEvent事件,执行绑定的方法:

[c-sharp] view plain copy print ?
  1. private void ShellPage_NavigatedAway(object sender, NavigateAwayEventArgs args) 
  2.     if (!args.IsExternal) 
  3.     { 
  4.         if ((args.Direction == Direction.Back) && (this._backStack.Count > 0)) 
  5.         { 
  6.             this.IsBusy =false
  7.             this._lastRemovedEntry =this._currentEntry; 
  8.             this._currentEntry =this._backStack.Pop(); 
  9.             NavigationMode back = NavigationMode.Back; 
  10.             this.UpdateObservables(this._currentEntry, back); 
  11.         } 
  12.     } 
  13.     else 
  14.     { 
  15.         this.OnNavigatedExternally("",new Uri("app://external/", UriKind.Absolute), (args.Direction == Direction.Back) ? NavigationMode.Back : NavigationMode.New); 
  16.     } 

此方法中包含一个名为UpdateObservables方法,此方法是

[c-sharp:nogutter] view plain copy print ?
  1. private void UpdateObservables(JournalEntry currentEntry, NavigationMode mode) 
  2.     bool isPaused = null == currentEntry.PageInstance; 
  3.     this.OnNavigated(currentEntry.Name, currentEntry.Source, mode, isPaused); 

OnNavigate触发了在NavigateService中InitializeJournal()注册的Journal的Navigated事件

[c-sharp:nogutter] view plain copy print ?
  1. this._journal.Navigated +=new EventHandler<JournalEventArgs>(this.Journal_Navigated) 

最后执行绑定的NativagetService中的Journal_Navigated方法:

实际上执行的就是NavigateCore_ContinueNavigation 方法,我们注意到 ._contentLoader.BeginLoad()方法,这里用到了我们前面Frame初始化说到的PageResourceContentLoader对象来加载指定XAML文件。

以上就是Frame加载XAML文件的全部过程。从Navigate到实际的NavigateCore_ContinueNavigation之间很负责,涉及了一些没有公开的类型。但实际意图很简单,就是对要导航的页面进行历史记录操作。

三 页面的显示

上面介绍了XAML被PageResourceContentLoader对象加载到Frame。那么XAML是如何显示到界面上的呢?

Silverlight 导航系统使用此类作为其默认的内容加载程序。此类是 INavigationContentLoader 的默认实现,且此类的实例是Frame.ContentLoader 属性的默认值。虽然您通常会加载Page 实例,Silverlight 导航系统要求导航目标应为 UserControl 实例。Page 类派生自UserControl 类,并提供附加导航支持

以上是MSDN对PageResourceContentLoader的解释,实际总用就是加载应用程序包(.xap 文件)中对应于给定 URI 的页。我们根据源码也看到Navigate方法最终是通过此对象来加载URI页面。

[c-sharp:nogutter] view plain copy print ?
  1. this._contentLoader.BeginLoad(this._currentNavigation.Uri,new AsyncCallback(this.ContentLoader_BeginLoad_Callback),this._currentNavigation); 

我们看看BeginLoad方法的实现

实际是调用了BeginLoad_OnUIThread方法,而里面又调用了GetLocalXaml方法来加载本地的XAML文件,而且获得了xclass属性指定的类型。最后使用Activator.CreateInstance创建了此XAML文件类对象的实例,保存到result中。

[c-sharp] view plain copy print ?
  1. private staticvoid BeginLoad_OnUIThread(AsyncCallback userCallback, PageResourceContentLoaderAsyncResult result) 
  2.     if (result.Exception !=null
  3.     { 
  4.         result.IsCompleted = true
  5.         userCallback(result); 
  6.     } 
  7.     else 
  8.     { 
  9.         try 
  10.         { 
  11.             string pagePathAndName = UriParsingHelper.InternalUriGetBaseValue(result.Uri); 
  12.             string localXaml = GetLocalXaml(pagePathAndName); 
  13.             if (string.IsNullOrEmpty(localXaml)) 
  14.             { 
  15.                 result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_NoXAMLWasFound,new object[] { pagePathAndName })); 
  16.             } 
  17.             else 
  18.             { 
  19.                 string xClass = GetXClass(localXaml); 
  20.                 if (string.IsNullOrEmpty(xClass)) 
  21.                 { 
  22.                     try 
  23.                     { 
  24.                         result.Content = XamlReader.Load(localXaml); 
  25.                     } 
  26.                     catch (Exception exception) 
  27.                     { 
  28.                         result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_XAMLWasUnloadable,new object[] { pagePathAndName }), exception); 
  29.                     } 
  30.                 } 
  31.                 else 
  32.                 { 
  33.                     Type typeFromAnyLoadedAssembly = GetTypeFromAnyLoadedAssembly(xClass); 
  34.                     if (typeFromAnyLoadedAssembly ==null
  35.                     { 
  36.                         result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_TheTypeSpecifiedInTheXClassCouldNotBeFound,new object[] { xClass, pagePathAndName })); 
  37.                     } 
  38.                     else 
  39.                     { 
  40.                         result.Content = Activator.CreateInstance(typeFromAnyLoadedAssembly); 
  41.                     } 
  42.                 } 
  43.             } 
  44.         } 
  45.         catch (Exception exception2) 
  46.         { 
  47.             result.Exception = exception2; 
  48.         } 
  49.         finally 
  50.         { 
  51.             result.IsCompleted = true
  52.             if (userCallback !=null
  53.             { 
  54.                 userCallback(result); 
  55.             } 
  56.         } 
  57.     } 

这里BeginLoad是用异步的方式来加载,避免UI线程等待。而加载完成后执行回调方法 ContentLoader_BeginLoad_Callback

加载完成后,从result中获得PhoneApplicationPage对象,也就是之前加载的XAML文件。然后设置了导航的Context内容,这个用来页面间传值的。接下来obj2.SetValue(NavigationServiceProperty,this)方法设置了NavigationServiceProperty依赖属性。这个属性和导航时的空引用异常有关。而CompleteNavigation方法用来完成导航。

[c-sharp:nogutter] view plain copy print ?
  1. this.RaiseNavigated(content, uriBeforeMapping, mode,null != existingContentPage, existingContentPage, newContentPage); 

其中一段代码RaiseNavigated出发了Frame的Navigated事件,也就是会执行我们在App中的CompleteInitializePhoneApplication方法。此时我们已经获得了要显示页面的Page对象。

我们知道在WPF中有对象树和可视数的概念,而在Silverlight上只有对象树。 RootVisual属性是设置显示的元素,把Frame设置为RootVisual,此时就会把Frame加载到对象树上,而前面提到过,Frame初始化时绑定了Loaded事件,这个事件此时会被执行。

[c-sharp:nogutter] view plain copy print ?
  1. private void Frame_Loaded(object sender, RoutedEventArgs e) 
  2.     this.Load(); 

实际上她执行的也是Load方法,这个方法已经在PhoneApplicationFrame的构造函数中执行过了,有_loaded字段标记,也就不会在执行了。

[c-sharp:nogutter] view plain copy print ?
  1. public UIElement RootVisual 
  2.     get 
  3.     { 
  4.         return (XcpImports.Application_GetVisualRoot()as UIElement); 
  5.     } 
  6.     [SecuritySafeCritical] 
  7.     set 
  8.     { 
  9.         XcpImports.CheckThread(); 
  10.         if ((value == null) || !XcpImports.DependencyObject_IsPointerValid(value)) 
  11.         { 
  12.             throw new InvalidOperationException(Resx.GetString("Application_InvalidRootVisual")); 
  13.         } 
  14.         XcpImports.Application_SetVisualRoot(value); 
  15.         this._rootVisual = value; 
  16.     } 

RootVisual的实现看不到,这里贴的是Silverlight的实现。这里用到了XcpImports,这个对象上一篇文章介绍过了,最终是使用Core presentation framework的方法设置了RootVisual,通过Frame得到当前的Page,这时Presentation Core会根据当前的对象树生成显卡可以识别的三角形,最终显示到屏幕上。这方面内容可以参考WPF Presentation


四 PhoneApplicationPage和NavigateService

前面介绍了Frame框架的初始化和第一个页面导航加载到显示的过程。我们已经知道,实际负责页面导航的是NavigateService这个类。但只有使用了Frame框架才能完成导航。而在PhoneApplicationPage中有一个NavigateService属性:

[c-sharp:nogutter] view plain copy print ?
  1. internal static NavigationService GetNavigationService(DependencyObject dependencyObject) 
  2.     Guard.ArgumentNotNull(dependencyObject, "dependencyObject"); 
  3.     return (dependencyObject.GetValue(NavigationServiceProperty)as NavigationService); 

他实际是返回了NavigationServiceProperty依赖属性。而这个属性上面提到过,是在初始化时加,BeginLoad加载了XAML后的回调函数中设置了此属性。所以我们可以在Page类中使用下面代码进行导航,也可以获得App类的RootFrame对象来导航。

[c-sharp] view plain copy print ?
  1. this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 
  2.  
  3. (Application.Current as App).RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 

另外我们在Page中还有OnNavigateTo和OnNavigateFrom等方法,来控制导航。这些内容将在下一篇文章中介绍。


五 Silverlight的不同之处

同Windows Phone导航不同,我们默认建立一个Silverlight的普通项目是没有添加Frame框架的。我们可以仿照Windows Phone修改App文件。

[c-sharp] view plain copy print ?
  1. private void Application_Startup(object sender, StartupEventArgs e) 
  2.     RootFrame = new Frame(); 
  3.     RootFrame.Navigate(new Uri("/MainPage.xaml", UriKind.Relative)); 
  4.     this.RootVisual = RootFrame; 

在程序启动的时候执行,整个过程和前面基本一样。相比Windows Phone,他们的构造函数简单了很多。

[c-sharp:nogutter] view plain copy print ?
  1. public Frame() 
  2.     base.DefaultStyleKey =typeof(Frame); 
  3.     base.Loaded += new RoutedEventHandler(this.Frame_Loaded); 
  4.     this._navigationService =new NavigationService(this); 
  5.  
  6. internal NavigationService(Frame nav) 
  7.     this._cacheRequiredPages = new Dictionary<string, Page>(); 
  8.     Guard.ArgumentNotNull(nav, "nav"); 
  9.     this._host = nav; 

Frame中注册了Loaded事件,和WP不同的是没有在Frame的构造函数中就执行。那么他只能在要显示的时候运行,也就是我们设置了RootVisual属性后才能执行Navigate方法。另外没有配置文件设置导航初始画面,所以我们也必须在这里调用Navigate方法或设置Source属性。

当页面开始呈现,加载了Frame到对象树后触发了Loaded时间, 最终调用了NavigateService.Navigate方法。

[c-sharp:nogutter] view plain copy print ?
  1. public bool Navigate(Uri source) 
  2.     return this.NavigateCore(source, NavigationMode.New,false, false); 

这里也和Windows Phone不同,NavigateService中直接调用了NavigateCore方法。此方法实现和Windows Phone一样,此方法中有用的是NavigateCore_StartNavigation,最终是通过的this._contentLoader.BeginLoad来完成的。然后显示核心把加载的页面显示到屏幕上。

以上是Silverlight的过程,相比Windows Phone简单一些。因为Loaded事件触发的时间不同,所以WP会自动根据配置文件设置在App对象建立后就导航到指定页面;而SL是在加载对象准备呈现时触发,而且没有配置文件,所以必须手动设置一下。这也就解释了我们前一篇的疑问。也是为什么SL模仿WP来创建框架,却不能在Navigated时间中才设置RootVisual。因为此事件需要设置运行了Loaded方法才能触发。


六 总结

本文主要对导航框架源代码进行了简单的解析,了解了程序启动后导航到第一个界面的过程。首先创建了PhoneApplication对象,在此对象构建完成后,调用了Frame的Loaded方法来导航到配置文件指定的URI;然后加载此XAML文件,然后触发Frame的Navigated方法设置RootVisual,此属性会调用显示框架的Native方法,将对象树呈现到屏幕上。

 
 

Windows Phone开发(四)-- 导航事件和传值       

        分类:            Windows Phone 1122人阅读 评论(1) 收藏 举报
从第一篇开始我们就看到了页面的导航切换,上一篇文章则介绍了框架实现导航的原理和过程。真正的导航功能是NavigationService类来实现的。而Frame是Page的载体,是负责导航,历史记录等功能的,相当于一个指挥官。这一篇就主要介绍一下导航的操作和相关的一些方法。

一 导航时发生错误

默认的我们建立一个Windows Phone程序,使用导航功能是不会出现这个问题的。我们先看一个列子:

  1. private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) 
  2.    // Set the root visual to allow the application to render 
  3.    if (RootVisual != RootFrame) 
  4.        //RootVisual = RootFrame; 
  5.        RootVisual = new MainPage(); 
  6.     // Remove this handler since it is no longer needed 
  7.     RootFrame.Navigated -= CompleteInitializePhoneApplication; 
private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
{
   // Set the root visual to allow the application to render
   if (RootVisual != RootFrame)
       //RootVisual = RootFrame;
       RootVisual = new MainPage();
    // Remove this handler since it is no longer needed
    RootFrame.Navigated -= CompleteInitializePhoneApplication;
}

我们新建一个项目后,不把Frame设置到RootVsual,而是用MainPage。我们从Mainpage导航到Page1. 使用两种方法:

  1. private void Button_Click(object sender, RoutedEventArgs e) 
  2.      方法1:this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 
  3.      方法2:(Application.Current as App).RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 
private void Button_Click(object sender, RoutedEventArgs e)
{
     方法1:this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
     方法2:(Application.Current as App).RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
}

第一种方法是使用Page页面的NavigationSevice属性来导航,第二种方法是使用Frame对象来导航。从上一篇我们知道,两种方法是相同的。结果是使用第一种方法发生了NullReferenceException错误,为什么NavigationService对象是空?我们不是在App构造函数中就创建过NavigationServie对象的实例吗,并且在加载了XAML文件后设置到了依赖属性中。

  1. obj2.SetValue(NavigationServiceProperty, this);   
obj2.SetValue(NavigationServiceProperty, this);  

上一篇文章介绍过,Page的NavigationService属性是通过依赖属性获得的。其实这里我要了解依赖属性的特点,虽然是一个static字段来维护,但是内部是有Hash表来存放不同对象的属性值。我们在构造函数的Load方法中,加载了配置文件中指定的MainPage,并生成了他的实例,然后设置了NavigationServiceProperty。而这里我们使用,MainPage的是一个新的实例,他的NavigationServiceProperty为Null。所以这里当然会报错了。 而且NavigationServiceProperty是internal属性,说以我们不能手动设置了。

从这个异常我们能发现,每次导航的新的页面,在加载完成Page之后,都会设置Page的NavigationServiceProperty的属性。所以并不是创建了Frame,我们就能使用NavigationService来导航。而是需要Frame来吧Page和NavigationService关联起来。而这里,我们new的Mainpage没有通过Frame加载显示的,所以无法导航。

接下来看看第二种方法。这里使用的是我们创建的RootFrame对象,因为此时Frame已经完全构造好了,NavigationSevice也创建好了。所以此时调用Navigate方法是不会报错的,而且返回的是true。因为这里不存在延迟导航,所以说明是成果了,但是为什么没显示呢?我们点击【<-】退出程序,但是点了2下才退出,这说明导航了吗?我们在做个试验,我们在Page1中加入以下的代码

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.     base.OnNavigatedTo(e); 
  3.     if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back) 
  4.     { 
  5.        bool ret = (Application.Currentas App).RootFrame.Navigate(new Uri("/Page2.xaml", UriKind.Relative)); 
  6.     } 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
    {
       bool ret = (Application.Current as App).RootFrame.Navigate(new Uri("/Page2.xaml", UriKind.Relative));
    }
}

我们在从MainPage用方法2导航到Page1后,在点【<-】退出程序,现在要点3下了。我们也可以设置断点来查看,实际完成了MainPage->Page1->Page2的导航。当时为什么没有显示呢?很简单了,因为Page要通过RootVisual来显示,导航的话Page需要Frame来加载的,当我们把Frame设置到RootVisual时,就能显示,而这里我们设置的MainPage,虽然Frame导航了,但没有被显示出来。但是RootVisual只能设置一次,所以这里没有办法让他显示出来了。

好了,到这里我们导航的原理就研究到这里,通过2个错误我们进步不理解了Frame和Page的关系。也明确了Frame导航和加载Page的这一功能。这也说明为什么必须有Frame才能导航。

二 导航的过程

在上面的列子中,我们看到了OnNavigatedTo方法,当导航到Page1时自动导航到了Page2。我们知道一个Page导航到另一个Page实际是一个卸载和装载的过程。在这个过程中会触发一系列的事件和方法,下面我们就来看下这些方法。

  1. public class Page : UserControl 
  2.     // Methods 
  3.     internal Page(); 
  4.     internal void InternalOnFragmentNavigation(FragmentNavigationEventArgs e); 
  5.     internal virtualvoid InternalOnNavigatedFrom(NavigationEventArgs e); 
  6.     internal virtualvoid InternalOnNavigatedTo(NavigationEventArgs e); 
  7.     internal void InternalOnNavigatingFrom(NavigatingCancelEventArgs e); 
  8.     protected virtualvoid OnFragmentNavigation(FragmentNavigationEventArgs e); 
  9.     protected virtualvoid OnNavigatedFrom(NavigationEventArgs e); 
  10.     protected virtualvoid OnNavigatedTo(NavigationEventArgs e); 
  11.     protected virtualvoid OnNavigatingFrom(NavigatingCancelEventArgs e); 
public class Page : UserControl
{
    // Methods
    internal Page();
    internal void InternalOnFragmentNavigation(FragmentNavigationEventArgs e);
    internal virtual void InternalOnNavigatedFrom(NavigationEventArgs e);
    internal virtual void InternalOnNavigatedTo(NavigationEventArgs e);
    internal void InternalOnNavigatingFrom(NavigatingCancelEventArgs e);
    protected virtual void OnFragmentNavigation(FragmentNavigationEventArgs e);
    protected virtual void OnNavigatedFrom(NavigationEventArgs e);
    protected virtual void OnNavigatedTo(NavigationEventArgs e);
    protected virtual void OnNavigatingFrom(NavigatingCancelEventArgs e);
}

在Page类中,我们看到了和导航相关的4个protected虚方法。对应有4个internal的方法,他们是在事件发生的时候触发的(系统已经给我们绑定了),他们实现很简单,就是调用这里的虚方法。我们可以通过重写这些虚方法来控制。而在PhoneApplicationPage类中没有重写这些方法,也没有加入其它相关的方法。

在我们Demo3的PhoneApp3中我们在MainPage和Page1重写这几个方法:

  1. protected overridevoid OnFragmentNavigation(System.Windows.Navigation.FragmentNavigationEventArgs e) 
  2.     Debug.WriteLine("{0}:OnFragmentNavigation",this.ToString()); 
  3.     base.OnFragmentNavigation(e); 
  4.  
  5. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  6.     Debug.WriteLine("{0}:OnNavigatedTo",this.ToString()); 
  7.     base.OnNavigatedTo(e); 
  8.  
  9. protected overridevoid OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e) 
  10.      Debug.WriteLine("{0}:OnNavigatingFrom",this.ToString()); 
  11.      base.OnNavigatingFrom(e); 
  12.  
  13. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  14.      Debug.WriteLine("{0}:OnNavigatedFrom",this.ToString()); 
  15.      base.OnNavigatedFrom(e); 
protected override void OnFragmentNavigation(System.Windows.Navigation.FragmentNavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnFragmentNavigation", this.ToString());
    base.OnFragmentNavigation(e);
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
    base.OnNavigatedTo(e);
}

protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatingFrom", this.ToString());
     base.OnNavigatingFrom(e);
}

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
     base.OnNavigatedFrom(e);
}

启动程序,从MainPage导航到Page1: 输出如下:

  1. PhoneApp3.MainPage:OnNavigatedTo 
  2. PhoneApp3.MainPage:OnNavigatingFrom 
  3. PhoneApp3.MainPage:OnNavigatedFrom 
  4. PhoneApp3.Page1:OnNavigatedTo 
PhoneApp3.MainPage:OnNavigatedTo
PhoneApp3.MainPage:OnNavigatingFrom
PhoneApp3.MainPage:OnNavigatedFrom
PhoneApp3.Page1:OnNavigatedTo

点击【<-】按钮,输出如下:

  1. PhoneApp3.Page1:OnNavigatingFrom 
  2. PhoneApp3.Page1:OnNavigatedFrom 
  3. PhoneApp3.MainPage:OnNavigatedTo 
PhoneApp3.Page1:OnNavigatingFrom
PhoneApp3.Page1:OnNavigatedFrom
PhoneApp3.MainPage:OnNavigatedTo

在点击【<-】按钮退出程序,输出如下:

  1. PhoneApp3.MainPage:OnNavigatingFrom 
  2. PhoneApp3.MainPage:OnNavigatedFrom 
PhoneApp3.MainPage:OnNavigatingFrom
PhoneApp3.MainPage:OnNavigatedFrom

好吧,这些方法的执行顺序很清楚了,我就不用什么介绍了。不清楚可以看MSDN。不过这里命名很容易让人产生误解。我开始就弄反了。这里To和From相对的对象都是当前的页面。OnFragmentNavigation 方法,在导航到包括片断的统一资源标识符 (URI) 时会发生。一个片断是片断分隔符 (#) 后的值。这个后面在介绍。

三 页面传值

和Web一样,我们在导航过程中可能需要传递值,对于WinForm程序来说,我们可以通过Form的构造函数传值,可以定义定义全局变量等多种方法,对于Web,我们可以有QueryString,Cookies,Session等方法,在这里我们可以用类似的方法传递,这里我们结合导航到过程来进行值的传递。这里我们导航时不能通过构造函数传递,也无法使用Cookies和Session。

我在Demo3的PhoneApp4中,MainPage和Page1放置了5个TextBox,每个对应一种传递方式:

1 全局变量传递

这个方法在WinForm很常见,我们在App类中定义一个变量,App是一个全局对象,所以可以定义在这里。

  1. public partial class App : Application 
  2.     public string StaticVar { get; set; } //全局变量 
  3.  
  4.     public PhoneApplicationFrame RootFrame { get; private set; } 
  5.  
  6.     public App() 
  7.     { 
  8.     } 
    public partial class App : Application
    {
        public string StaticVar { get; set; } //全局变量

        public PhoneApplicationFrame RootFrame { get; private set; }

        public App()
        {
        }
}

在Mainpage的OnNavigtedFrom方法中,把s1的值传递给全局变量,s1的值来源于TextBox1。当然你可以在导航前任意时刻获取TextBox的值,这里只是演示导航方法。

  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.    Debug.WriteLine("{0}:OnNavigatedFrom",this.ToString()); 
  3.    base.OnNavigatedFrom(e); 
  4.    s1 = textBox1.Text; 
  5.    //全局变量传递 
  6.    (Application.Current as App).StaticVar = s1; 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
   Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
   base.OnNavigatedFrom(e);
   s1 = textBox1.Text;
   //全局变量传递
   (Application.Current as App).StaticVar = s1;
}

在Page1的OnNavigatedTo方法中接受并显示。同样,你可以在导航到Page1后的任意时刻来获取并显示,这里也是为了掩饰导航方法。

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("{0}:OnNavigatedTo",this.ToString()); 
  3.     base.OnNavigatedTo(e); 
  4.     //从全局变量接受 
  5.     s1 = (Application.Current as App).StaticVar; 
  6.     textBox1.Text = s1; 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
    base.OnNavigatedTo(e);
    //从全局变量接受
    s1 = (Application.Current as App).StaticVar;
    textBox1.Text = s1;
}

使用全局变量很方便,但是占用内存空间,并且存在丢失的可能。这个以后文件会介绍。

2 页面QueryString传值

因为这里导航和Web相似,所以我们也可以用Uri后加上QueryString的传值方式。我们这里使用TextBox2.

首先修改导航按钮代码,我们必须在导航前,构造好导航的Uri,并且要获取TextBox2的值。QueryString格式为 Name1=Value1&Name2=Value2,多个参数用&间隔开。

  1. private void button1_Click(object sender, RoutedEventArgs e) 
  2.      //构造QueryString 
  3.      s2 = textBox2.Text; 
  4.      string uri = "/Page1.xaml?s2=" + s2; 
  5.  
  6.       //导航 
  7.        this.NavigationService.Navigate(new Uri(uri, UriKind.Relative)); 
private void button1_Click(object sender, RoutedEventArgs e)
{
     //构造QueryString
     s2 = textBox2.Text;
     string uri = "/Page1.xaml?s2=" + s2;

      //导航
       this.NavigationService.Navigate(new Uri(uri, UriKind.Relative));
}

而在Page1页面,我们通过NavigationContext属性的QueryString获得传递的值。这里是返回的是IDictionary类型,在使用前,必须检查Name对应的变量是否存在。

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("{0}:OnNavigatedTo",this.ToString()); 
  3.     base.OnNavigatedTo(e); 
  4.     //从Uri接受 
  5.     IDictionary<string, string> queryString =this.NavigationContext.QueryString; 
  6.     if (queryString.ContainsKey("s2")) 
  7.     { 
  8.        s2 = queryString["s2"]; 
  9.     } 
  10.  
  11.     //显示到UI 
  12.     textBox2.Text = s2; 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
    base.OnNavigatedTo(e);
    //从Uri接受
    IDictionary<string, string> queryString = this.NavigationContext.QueryString;
    if (queryString.ContainsKey("s2"))
    {
       s2 = queryString["s2"];
    }

    //显示到UI
    textBox2.Text = s2;
}

使用QueryString可以传递少量数据,因为Uri长度是有限制的,并且只能传递简单类型,如果要传递自定义类对象,就不适用了。另外传的是string类型,所以可能需要进行类型转化。在我们导航的时候,会建立一个JournalEntry对象,当新的xaml页面加载完后,会去解析URI中的QueryString数据存入到其中。

  1. JournalEntry.SetNavigationContext(obj2, new NavigationContext(UriParsingHelper.InternalUriParseQueryStringToDictionary 
JournalEntry.SetNavigationContext(obj2, new NavigationContext(UriParsingHelper.InternalUriParseQueryStringToDictionary


3 PhoneApplicationService类

如果我们需要传递自定义类型数据时,就需要用到这个对象。其实这个对象我们并不是很陌生,在前面我们就介绍过他实现了IApplicationService就接口,为Silverlight程序供了扩展功能,比如前面App中启动时Application_Launching,Application_Activated等四个方法。在这里他在内部提供了一个字典来维护一些全局的数据:

  1. public IDictionary<string,object> State { get; } 
public IDictionary<string, object> State { get; }

所以我们不需要自己去定义全局变量,而可以直接使用他,通过PhoneApplicationService.Current.State就能访问到这个字典。我们这里使用TextBox3来演示。在MainPage中把TextBox3获得的值,设置到字典中,注意,这里的Key需要是唯一的。另外我们使用时要引入以下命名空间。

  1. using Microsoft.Phone.Shell; 
using Microsoft.Phone.Shell;
  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.      Debug.WriteLine("{0}:OnNavigatedFrom",this.ToString()); 
  3.      base.OnNavigatedFrom(e); 
  4.      s3 = textBox3.Text; 
  5.      //使用PhoneApplicationService传递 
  6.       PhoneApplicationService.Current.State["s3"] = s3; 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
     base.OnNavigatedFrom(e);
     s3 = textBox3.Text;
     //使用PhoneApplicationService传递
      PhoneApplicationService.Current.State["s3"] = s3;
}

我们在Page1中,我们取得s3的值2,这里我们采用了TryGetValue来获得值,当然也能使用QueryString中的方法。两者都能实现。TryGetValue性能更高。

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.      Debug.WriteLine("{0}:OnNavigatedTo",this.ToString()); 
  3.      base.OnNavigatedTo(e); 
  4.      //从PhoneApplicationService获得数据 
  5.       object objS3; 
  6.      PhoneApplicationService.Current.State.TryGetValue("s3",out objS3); 
  7.      if (objS3 != null
  8.      { 
  9.          s3 = Convert.ToString(objS3); 
  10.      } 
  11.  
  12.      //显示到UI 
  13.      textBox3.Text = s3; 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
     base.OnNavigatedTo(e);
     //从PhoneApplicationService获得数据
      object objS3;
     PhoneApplicationService.Current.State.TryGetValue("s3", out objS3);
     if (objS3 != null)
     {
         s3 = Convert.ToString(objS3);
     }

     //显示到UI
     textBox3.Text = s3;
}

这里我们要注意,这里是object类型,所以在读取数据时,存在类型转换。另外,这里传递的对象,必须是可以序列化的对象。

4 独立存储传递

使用独立存储IsolatedStorageSettings类时,需要引用以下命名空间。我们在MainPage中使用她来保存TextBox4的值。

  1. using System.IO.IsolatedStorage; 
using System.IO.IsolatedStorage;
  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("{0}:OnNavigatedFrom",this.ToString()); 
  3.     base.OnNavigatedFrom(e); 
  4.     s4 = textBox4.Text; 
  5.     //使用独立存储 
  6.      IsolatedStorageSettings.ApplicationSettings["s4"] = s4; 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
    base.OnNavigatedFrom(e);
    s4 = textBox4.Text;
    //使用独立存储
     IsolatedStorageSettings.ApplicationSettings["s4"] = s4;
}

在Page1页面,我们获取S4的值,这里我们采用TryGetValue<>,这是一个泛型方法,从安全和性能上都比较好。

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.      Debug.WriteLine("{0}:OnNavigatedTo",this.ToString()); 
  3.      base.OnNavigatedTo(e); 
  4.      //从独立存储获得数据 
  5.       IsolatedStorageSettings.ApplicationSettings.TryGetValue<string>("s4",out s4); 
  6.      textBox4.Text = s4; 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
     base.OnNavigatedTo(e);
     //从独立存储获得数据
      IsolatedStorageSettings.ApplicationSettings.TryGetValue<string>("s4", out s4);
     textBox4.Text = s4;
}

看起来也没有什么特别。我们在MainPage的OnNavigatedTo也加入上面Page1中的代码,我们从MainPage导航到Page,让后在关闭程序,在重新启动。嘿嘿。竟然在Mainpage中就显示了TextBox4之前的值。这是因为独立存储把值存到了本地,所以即便程序关闭也不会丢失,而前面则存在丢失的问题。对于全局变量和QueryString,即便程序不完全关闭,也是存在丢失的可能。所以一般来说,我们使用IsolatedStorageSettings来存储程序的一些配置信息。当然我们还能选择File和Database等方式来传递数据,当然对于页面传值来说就有点不太适合。


以上是各种方式传值的结果。

四 总结

这一篇文章首先继续谈了Frame和Page的关系, 然后介绍了在导航时会触发的事件方法,我们可以通过重写这些方法来控制页面的显示,比如进行值的传递。也详细介绍了值传递的四种方法和使用的环境。在下一篇文章将继续介绍导航的其他常用方法以及回退键,还有特殊页面的回退处理。

 
 
 

Windows Phone开发(五)-- 导航控制       

        分类:            Windows Phone 1108人阅读 评论(1) 收藏 举报

前面几乎每篇文章都会涉及到页面的切换,页面导航,从程序启动开始,到结束。上一篇文章介绍了页面导航时会发生的几个事件以及页面中传值的方法。这一篇文章将介绍对导航的一些控制。

一 导航栈

从Windows Phone 手机上我们就可以看到,手机有Back键,但是没有Forward按键。而我们在前面例子中也基本都是使用GoBack()方法。看下MSDN,关于Forward的描述如下:

  1. Navigates to the most recent entry in the forward navigation history, or throws an exception if no entry exists in forward navigation. For Windows Phone this method will always throw an exception because there is no forward navigation stack.  
Navigates to the most recent entry in the forward navigation history, or throws an exception if no entry exists in forward navigation. For Windows Phone this method will always throw an exception because there is no forward navigation stack. 


上面告诉我们,在Windows Phone 中没有Forward Stack,而只有Back Stack,所以我们使用GoBack后没有办法在使用GoForward前进到上一个页面。而当我们进入到一个新的页面时,上一个页面就会被存放到Back Stack中,当我们点击Back按钮,或使用GoBack方法时就会销毁当前的页面,而从Back Stack中弹出上一页面。如果Back Stack中没有页面,此时点击Back按钮程序就会退出,而是用GoBack方法就会抛出异常,所以我们在使用时最先对CanGoForward属性进行判断。

在WP7.1版本中我们注意到,在NavigationService类下面多了一个BackStack的属性。

  1. public IEnumerable<JournalEntry> BackStack 
  2.     get 
  3.     { 
  4.         return this._journal.BackStack; 
  5.     } 
public IEnumerable<JournalEntry> BackStack
{
    get
    {
        return this._journal.BackStack;
    }
}

还记得在前面文章介绍Navigate方法的时候,见到到一下代码:

  1. JournalEntry journalEntry = new JournalEntry(uri.OriginalString, uri); 
  2. this.Journal.AddHistoryPoint(journalEntry); 
JournalEntry journalEntry = new JournalEntry(uri.OriginalString, uri);
this.Journal.AddHistoryPoint(journalEntry);


JournalEntry也是在WP7.1中公开的一个类,它表示后退或前进导航历史记录中的一个条目。这个类在silverlight和WPF中都是公开存在的,但是和WPF相比,这个类只有公开了Uri属性。这里我们不详细研究这个类,只看看它的构造函数,也是很简单。

  1. internal JournalEntry(string name, Uri uri) 
  2.     Guard.ArgumentNotNull(uri, "uri"); 
  3.     this.Name = name; 
  4.     this._source = uri; 
  5.     this.PageInstance = null
  6.     this.PausedPage = null
internal JournalEntry(string name, Uri uri)
{
    Guard.ArgumentNotNull(uri, "uri");
    this.Name = name;
    this._source = uri;
    this.PageInstance = null;
    this.PausedPage = null;
}

Journal类是用来管理这些条目的,上面的AddHistoryPoint方法并不是新的要进的页面加入到BackStack中。在前面文章也看过这个的源码,在内部创建了一个ShellPage对象,并注册了相关的事件。

  1. internal void AddHistoryPoint(JournalEntry journalEntry) 
  2.     Guard.ArgumentNotNull(journalEntry, "journalEntry"); 
  3.     this._shellPagePending = this._shellPageManager.CreatePage(journalEntry.Source.ToString()); 
  4.     if (this._shellPagePending ==null
  5.     { 
  6.         throw new InvalidOperationException("Unable to create ShellPage"); 
  7.     } 
  8.     this._shellPagePending.ShellPageCallback.OnNavigateAwayEventHandler +=new EventHandler<NavigateAwayEventArgs>(this.ShellPage_NavigatedAway); 
  9.     this._shellPagePending.ShellPageCallback.OnNavigateToEventHandler +=new EventHandler<NavigateToEventArgs>(this.ShellPage_NavigatedTo); 
  10.     this._shellPagePending.ShellPageCallback.OnRemoveEventHandler +=new EventHandler<RemoveEventArgs>(this.ShellPage_RemovedPage); 
  11.     this._shellPagePending.ShellPageCallback.OnBackKeyPressInternalEventHandler +=new EventHandler<BackKeyPressEventArgs>(this.ShellPage_BackKeyPressed); 
  12.     this._shellPageManager.NavigateTo(this._shellPagePending); 
  13.     this.IsBusy = true
internal void AddHistoryPoint(JournalEntry journalEntry)
{
    Guard.ArgumentNotNull(journalEntry, "journalEntry");
    this._shellPagePending = this._shellPageManager.CreatePage(journalEntry.Source.ToString());
    if (this._shellPagePending == null)
    {
        throw new InvalidOperationException("Unable to create ShellPage");
    }
    this._shellPagePending.ShellPageCallback.OnNavigateAwayEventHandler += new EventHandler<NavigateAwayEventArgs>(this.ShellPage_NavigatedAway);
    this._shellPagePending.ShellPageCallback.OnNavigateToEventHandler += new EventHandler<NavigateToEventArgs>(this.ShellPage_NavigatedTo);
    this._shellPagePending.ShellPageCallback.OnRemoveEventHandler += new EventHandler<RemoveEventArgs>(this.ShellPage_RemovedPage);
    this._shellPagePending.ShellPageCallback.OnBackKeyPressInternalEventHandler += new EventHandler<BackKeyPressEventArgs>(this.ShellPage_BackKeyPressed);
    this._shellPageManager.NavigateTo(this._shellPagePending);
    this.IsBusy = true;
}

这些事件绑定的方法都是实现在Journal类中,在这些方法中可以找到对BackStack的操作。所以在NavigationService的BackStack属性中就是返回了_journal.BackStack。这里具体内容就不讨论。 我们只要知道在Windows Phone 7中存在一个BackStack来存放之前访问的页面,一边我们可以回退。

二 Navigate和GoBack

看起来好像没有太大关系,一个是导航到一个页面,一个是从返回到BackStack的一个页面,但是我们在GoBack的地方改用Navigate回如何呢?前一个页面怎么处理呢?在Demo4这个程序中,我们有MainPage,Page1,Page2三个页面。导航方式为MainPage<->Page1<->Page2。2个页面间可以选择Navigate或GoBack来导航。

现在我们进行以下操作:从MainPage导航到Page2,然后从Page2调用GoBack到MainPage,以下是输出的结果,我们打印了BackStack的数量

  1. Entry the MainPage 
  2. JournalEntry number is 0 
  3. MainPage Navigate to Page1 
  4. Leave the MainPage 
  5. JournalEntry number is 1 
  6.  
  7. Entry the Page1 
  8. JournalEntry number is 1 
  9. Page1 Navigate to Page2 
  10. Leave the Page1 
  11. JournalEntry number is 2 
  12.  
  13. Entry the Page2 
  14. JournalEntry number is 2 
  15. Page2 Call GoBack 
  16. Leave the Page2 
  17. JournalEntry number is 1 
  18.  
  19. Entry the Page1 
  20. JournalEntry number is 1 
  21. Page1 Call GoBack 
  22. Leave the Page1 
  23. JournalEntry number is 0 
  24.  
  25. Entry the MainPage 
  26. JournalEntry number is 0 
Entry the MainPage
JournalEntry number is 0
MainPage Navigate to Page1
Leave the MainPage
JournalEntry number is 1

Entry the Page1
JournalEntry number is 1
Page1 Navigate to Page2
Leave the Page1
JournalEntry number is 2

Entry the Page2
JournalEntry number is 2
Page2 Call GoBack
Leave the Page2
JournalEntry number is 1

Entry the Page1
JournalEntry number is 1
Page1 Call GoBack
Leave the Page1
JournalEntry number is 0

Entry the MainPage
JournalEntry number is 0

这里的结果很明显,每当我们进入到新的一个页面时,BackStack数量就会增加一个,当我们调用GoBack返回时,就会减少。回到MainPage时BackStack为空。这个时候在点击Back按钮时就退出了。

下面我们进行另一个试验,从MainPage导航到Page2,然后在从Page2调用Navigate导航到MainPage,结果如下:

  1. Entry the MainPage 
  2. JournalEntry number is 0 
  3. MainPage Navigate to Page1 
  4. Leave the MainPage 
  5. JournalEntry number is 1 
  6.  
  7.  
  8. Entry the Page1 
  9. JournalEntry number is 1 
  10. Page1 Navigate to Page2 
  11. Leave the Page1 
  12. JournalEntry number is 2 
  13.  
  14.  
  15. Entry the Page2 
  16. JournalEntry number is 2 
  17. Page2 Nagivate to Page1 
  18. Leave the Page2 
  19. JournalEntry number is 3 
  20.  
  21.  
  22. Entry the Page1 
  23. JournalEntry number is 3 
  24. Page1 Nagivate to MainPage 
  25. Leave the Page1 
  26. JournalEntry number is 4 
  27.  
  28.  
  29. Entry the MainPage 
  30. JournalEntry number is 4 
Entry the MainPage
JournalEntry number is 0
MainPage Navigate to Page1
Leave the MainPage
JournalEntry number is 1


Entry the Page1
JournalEntry number is 1
Page1 Navigate to Page2
Leave the Page1
JournalEntry number is 2


Entry the Page2
JournalEntry number is 2
Page2 Nagivate to Page1
Leave the Page2
JournalEntry number is 3


Entry the Page1
JournalEntry number is 3
Page1 Nagivate to MainPage
Leave the Page1
JournalEntry number is 4


Entry the MainPage
JournalEntry number is 4

可以看到采用Navigate导航,目前BackStack中保存了4条记录(MainPage,Page1,Page2,Page1)。当我们使用Back会退时结果和第一个一样。但是注意到其中Page1被存放了两次,二这2个Page1是不同的实例。这个很容易证实,我们在Demo的每个页面中填入一个数字,然后造使用GoBack,你会发现两个Page1的页面的数字是不一样的。

三 控制导航行为

WP7.1中还提供了操作BackStack的方法,在NavigationService中还提供了一个RemoveBackEntry的方法,这个方法可以从BackStack中移除一个项目,如果没有可以删除的项目就会抛出异常,而要删除多个项目的话需要调用多次。而且此方法必须在UI现场调用。

Demo4中,我们从MainPage导航到Page1,然后点击移除历史记录的按钮。结果如下:

  1. Entry the MainPage 
  2. JournalEntry number is 0 
  3. MainPage Navigate to Page1 
  4. Leave the MainPage 
  5. JournalEntry number is 1 
  6.  
  7. Entry the Page1 
  8. JournalEntry number is 1 
  9. Remove History Entry 
  10. JournalEntry number is 0 
Entry the MainPage
JournalEntry number is 0
MainPage Navigate to Page1
Leave the MainPage
JournalEntry number is 1

Entry the Page1
JournalEntry number is 1
Remove History Entry
JournalEntry number is 0

此时MainPage已经从BackStack中移除,此时我们点击GoBack按钮已经不在有效,而点击Back按钮程序将直接退出。但是此时继续点删除按钮,也不会引发异常。

  1. JournalEntry entry = this.NavigationService.RemoveBackEntry(); 
JournalEntry entry = this.NavigationService.RemoveBackEntry();


方法会返回一个JournalEntry对象,我们不能做更多操作,只能通过Uri属性得到移除的页面的Uri。而除此之外还有一个JournalEntryRemoved事件,这个时间会在删除时触发,我们可以重写他绑定的OnRemovedFromJournal方法,但是我们也做不了过多的操作,只能从JournalEntryRemovedEventArgs对象中获得移除的JournalEntry对象。目前我们好像还不能对BackStack进行替换和移除到起始页之间的页面等操作,但是MSDN上好像有写WP7.1的更新。

  • Replace the current page with a new page. This can be achieved by navigating forward to the new page and then removing the top of the back stack.

  • Remove all pages up to a given page, which is typically the first page. This can be accomplished by removing all the intervening pages and navigating back to the first page.

  • Clear the entire page back stack and replace with a given page. This can be accomplished by navigating forward to the new root page and then removing all the entries in the back stack.

最后要提到的是,在PhoneApplicationFrame类中也提供了这些在NavigationService中的方法和事件,实现也是相同,应该也是调用NavigationService中的实现。

在WP7.1还有一个虚方法我们可以重写导航的行为。

  1. protected overridevoid OnNavigatingFrom(NavigatingCancelEventArgs e) 
  2.    if (e.IsCancelable && e.NavigationMode == NavigationMode.New) 
  3.    { 
  4.        e.Cancel = true
  5.    } 
  6.    base.OnNavigatingFrom(e); 
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
   if (e.IsCancelable && e.NavigationMode == NavigationMode.New)
   {
       e.Cancel = true;
   }
   base.OnNavigatingFrom(e);
}


在这个方法中,我么可以判断当前导航能否取消。而通过NavigationMode 可以判断当前是创建新页面,还是从BackStack返回一个页面,而通过Url我们可以知道程序具体访问的页面,然后使用Cancle属性决定是否取消这个导航。

四 Back按键的控制

在BackStack栈中,机器的Back按钮作用很大,我们可以对Back按钮的行为进行控制。在WP7中有以下2个方法可以控制Back按键的行为。

  1. void MainPage_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e) 
  2.     Debug.WriteLine("MainPage_BackKeyPress"); 
  3.     e.Cancel = false
  4.  
  5. protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e) 
  6.     Debug.WriteLine("OnBackKeyPress"); 
  7.     base.OnBackKeyPress(e); 
        void MainPage_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e)
        {
            Debug.WriteLine("MainPage_BackKeyPress");
            e.Cancel = false;
        }

        protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
        {
            Debug.WriteLine("OnBackKeyPress");
            base.OnBackKeyPress(e);
        }


在点击Back按键的时候,会先调用OnBackKeyPress方法,然后调用BackKeyPress事件绑定的方法。我们发现这2个方法参数都是CancelEventArgs ,所以我们可以调用e.Cancel = True 来取消Back按键的功能。

在WP7.1中,Frame也提供了一个BackKeyPress事件:

  1. (Application.Current as App).RootFrame.BackKeyPress +=new EventHandler<System.ComponentModel.CancelEventArgs>(RootFrame_BackKeyPress); 
  2.  
  3. void RootFrame_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e) 
  4.    Debug.WriteLine("RootFrame_BackKeyPress"); 
(Application.Current as App).RootFrame.BackKeyPress += new EventHandler<System.ComponentModel.CancelEventArgs>(RootFrame_BackKeyPress);

void RootFrame_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e)
{
   Debug.WriteLine("RootFrame_BackKeyPress");
}

这个事件会捕获框架中所有页面的Back按钮时间,而不仅仅是当前的页面。

注意的问题:

在重写回退键我们有一些问题需要注意,Back按键不仅可以对页面进行导航,还可以关闭一些弹出窗体,比如MessageBox,Popup或者是ListPicker和ContectMenu。比如我在MainPage点击Back按钮时弹出一个MessageBox询问是否退出的选项。在我点击Back页面之前,MainPage界面上的ListPicker或ContectMenu都是弹出的状态,这个时候系统会关闭控件弹出内容,但同时也会弹出是否退出的MessageBox,但是你点击是调用了e.Cancle = True也不会退出程序,所以在重写Back按键方法时,要注意界面上的控件状态。

五 总结

经过几篇文章的介绍,应该对于Windows Phone的页面导航有了一定认识,有一些内容没有涉及,但是整体来说还是相对比较简单。对我们来说用到最多的就是OnNavigatedToOnNavigatedFrom方法,也是最重要的。因为页面导航不仅仅是页面的切换,还涉及到页面数据的保存和恢复,页面传值,墓碑状态的数据保存和恢复,打开和关闭页面时新建和回收一些资源等等。所以很重要,而在后面介绍到墓碑机制时也会用到。所以导航虽然简单,但是到项目中,页面数据保存恢复操作还是很麻烦的。

DEMO4下载地址:http://download.csdn.net/source/3495956

 
 
 

Windows Phone开发(六)-- 多任务之墓碑机制       

        分类:            Windows Phone 1817人阅读 评论(3) 收藏 举报

目前的智能手机,硬件上已经可以媲美几年前的PC机了,1G内存,512M以上内存,3.5以上的屏幕,3G,WIFI等等都成为了新的手机的最低标准。而Windows Phone也一改以往WM手机硬件差异大的问题,设定了最低的硬件标准。相对于以前系统,性能上,操作上,流畅度上也有了很大提高。但是电池的发展远远跟不上手机的耗电量。大的也就1500MA的电池,最多也就使用1天多,大部分每天都用充电。为了节约电量,各个平台的手机都推出了一些省电的措施。

对于Windows Phone来说,刚推出时和Iphone第一版一样,不支持多任务,一方面是为了给前台程序提供更多的资源,更流畅的体验,另一方面也是为了介绍电池的消耗。同事采用了消息推送机制来完成一部分后台操作,也使用了一种名为墓碑机制来对实现所谓的了“伪多任务”。  到了最新的“芒果”系统,已经支持了多任务,但也不同于以往WM说或PC上的多任务,也改进了墓碑机制,加快了程序间的切换。我们这一篇文章就了解下Windows Phone平台的伪多任务。

一 伪多任务

在我们使用PC和WM系统时,多任务对我们来说是理所应当的。一边听歌,一边上QQ,还能上微博发照片。可到了Windows Phone上却不支持多任务了,对于一个新的智能系统来说,确实无法让人接受。但是多任务存在一个问题就是如果开的任务过多,系统用起来就会比较卡,在WM系统中这种情况比较常见。为了提供用户的体验,目前的一些智能系统都放弃了这种传统的多任务方式,而采用了伪多任务,有限制的多任务和消息推送这些方法。

所谓的伪多人他并不是真正的多任务,只是通过一些处理,让用户使用起来感觉不到和多任务有什么区别。在Windows Phone中采用了墓碑机制来实现伪多任务。墓碑这个名字也很恰当。当我们运行了一个程序后,切换到另一个程序,这时因为是单任务,第一个程序会被终止掉,但是采用墓碑机制,保存了程序当前的状态和数据,当我们在切换回的时候,虽然是启动了一个新的实例,但是可以通过墓碑来恢复到之前的状态,所以用户感觉不到有什么区别。

墓碑机制是系统自动完成的,但是数据的保存和恢复则是我们通过代码控制的。另外墓碑机制也不是万能的,因为此时程序是终止的,如果有一些下载,播放,计时的功能就无法通过墓碑机制完成到了。Windows Phone 7则通过推送机制来解决部分问题,而WP7.1 SDK中也提供了一些后台操作来解决这些问题,这一篇文章我们主要介绍墓碑机制。

二 程序执行模型

在深入了解墓碑机制之前我们必须弄清楚程序的执行模型。下面这个图就是Windows Phone 7.1中最新的执行模型。此图来自于MSDN,但是他的图有个错误,修改了下。

对于一个Windows Phone程序来说,他有启动---运行---休眠---墓碑---退出等5个状态,状态切换之间存在一些事件和方法。下面就具体介绍一个程序从启动到终止的过程。

1 启动一个程序

我们启动一个程序有多种方法,最普遍的就是在第一屏界面点击程序的Tile图标或者是在第二屏中点击程序图标,另外我们可以通过点击一个推送的Toast消息或者通过Launchers 和 Choosers来启动程序,这些后面会介绍。在启动程序是,会触发Application的Launching事件。这个事件我们前面提到过,他是PhoneApplicationService类中注册的事件,除此之外好包含了三个和程序状态相关的事件。

程序启动会会创建一个新的实例,为了保证程序能过正常快速的启动,我们不应该在Launching事件中执行过多的代码,比如读取文件,网络连接等等,这样会印象用户体验,我们可用另起一个线程来执行这些操作。

2 程序运行

程序启动以后就进入了运行状态,程序从启动到进入第一个页面的过程我们在前面讨论页面导航的文章中详细介绍过了。通过从配置文件获得要加载的第一个XAML页面,读取XAML文件并生成新的实例,通过Frame显示出这个Page。我们知道在进入一个Page时会执行OnNavigatedTo方法。当这些完成后,程序才真正进入了运行状态。而OnNavigatedTo方法对于墓碑机制来说非常重要,我们一般在这个方法中进行一些数据恢复的操作。

3 休眠状态

这个状态是在WP7.1中新加入的一个状态,是为了提升多个任务间的切换速度。当我们在程序中点击Win键进入到主界面,或者是在程序中使用了Launchers 和Choosers启动了另一个程序时就会发生(不是所有都会发生)。休眠状态时,程序停止运行,但是整个进程还是存在于内存中。当恢复这个程序时,就不需要创建一个新的实例。这样就加快了程序恢复和切换的速度。而且从休眠状态恢复时我们一不需要去恢复数据。而在WP7.1中,我们可以长按Back按钮,出现程序列表,然后选择要前台执行的程序。

在切换到其他程序,进入到休眠状态之前,会调用当前页面的OnNavigatedFrom方法,在这个方法中我们可以保存当前页面的一些数据状态;然后会触发Application中的Deactivated事件,在这个事件中,我们可以保存一些当前程序的数据。我们知道,因为进程资源还保存在内存中,所以当前台程序使用时,内存不足或者不足以让程序流畅运行,这时系统就会执行一些操作来释放内存,此时程序就可能从休眠状态变换为下面介绍的墓碑状态。

4 墓碑状态

如上面说的,在WP7.1中,只有系统资源不够前台程序使用时,后台程序才可能从休眠状态进入到墓碑状态。这也是WP7.1相对于WP7.0最大的改进。在WP7.0中,系统没有休眠状态,仍和后台程序都是直接进入到墓碑状态,而OnNavigatedFrom方法和Deactivated事件都是在进入墓碑转台前触发的。一个程序进入到墓碑状态时,他的进程被终止掉,但是程序的回退栈中的信息,以及我们保存的一些信息会保留在内存中。

为了保证系统资源和性能,Windows Phone对墓碑状态也做了一些限制。目前系统中之允许同时存在5个墓碑程序。当超过了这个数量或者因为前台程序需要更多的资源,那么系统就会完全终止掉我们的程序,包括闪存内存中保存的回退栈和数据信息。所以对于我们而言,好需要对这种情况进行处理。

5 程序恢复

当我们使用Back按钮或者长按Back从任务列表中返回程序,或者是从Launchers 或Choosers返回时,程序就会恢复执行。但是如图上看到的,我们程序可能是从休眠状态,也可能是从墓碑状态返回的。如果是从休眠状态返回时我们不需要做任何恢复的操作,而从墓碑程序中返回,我们就需要恢复程序的状态和数据,以便用户感觉程序是被重写创建了。

程序恢复时会触发Application类中的Activated 事件,我们可以通过检查IsApplicationInstancePreserved参数来判断程序是从休眠状态还是墓碑状态返回的,在此方法中我们可以用来恢复之前在Deactivated事件中保存的数据。如果是从休眠状态恢复,不会重新执行构造函数,而从墓碑状态恢复时会重新执行构造函数。因为Back Stack在墓碑状态中还是保存在内存中的,所以这是会返回到我们程序退出时所在的页面,在进入到页面之前还是会执行OnNavigatedTo方法,对于墓碑状态恢复的程序,我们可以在这个方法中来恢当前页面的状态数据。

6 程序结束

在Windows Phone的silverlight框架程序中,我们唯一退出程序的方法就是点击Back键,当Back Stack中不存在页面时程序就会退出,目前系统没有提供任何Exit方法来以代码的方式结束程序。在程序退出时会触发Application中最后一个事件,Closing。在这个事件中我们可以释放一些使用的资源,保存数据等等,要注意的是,如果一个程序从墓碑状态被结束,是不会触发此事件的。MSDN上说关闭程序的时间被限制在10s,超过这个时间程序会被终止掉。

三 数据保存和恢复

至此,我们对于Windows Phone的执行模型也有了一定了解,即便WP7.1中引入了休眠方式,但是程序还是可能进入到墓碑模式或则被终止掉。所以无论什么情况,我们都要对数据进行保存和恢复。不过我们可以通过一些系统状态来判断是否需要进行数据保存和恢复。

在进入到墓碑状态时,数据会被保存到内存中,系统为我们提供了数据字典来保存我们的数据。 对于程序来说,系统的状态和数据我们可以存放到Application.State这个字典这种,而对于单个页面来说数据可以存放到Page.State的字典中。需要注意的是这些字典中存入的数据必须是可序列化的数据。另外我们也可以把数据保存到隔离存储区中。

有了数据保存的区域下面就是数据保存的时机,在Application相关的: Launching、Deactivated、ActivatedClosing这4个事件中我们可以对系统的数据进行保存和恢复,而在每个页面进入和离开都会发生的OnNavigatedTo和OnNavigatedFrom,我们可以用来保存和恢复页面的数据。

1 单页面的情况

我们看个列子,Demo5中我们新建了2个页面。在MianPage中有一个TextBox和三个按钮。点击Add时,TextBox的值会增加(默认是1),而点击Next按钮会导航到Page1页面,点击Run Camera按钮会调用相机。代码运行环境是WP7.1 SDK Beta2。我们在Application的四个事件和页面的2个方法中只加入运行的Log信息。

  1. public MainPage() 
  2.    Debug.WriteLine("MainPage Constructor"); 
  3.    camera = new CameraCaptureTask(); 
  4.    camera.Completed += new EventHandler<PhotoResult>(camera_Completed); 
  5.    num = 1; 
  6.    InitializeComponent(); 
  7.    txtNum.DataContext = num; 
  8. private void Button_Click(object sender, RoutedEventArgs e) 
  9.    txtNum.DataContext = ++num; 
  10.    Debug.WriteLine("Add Method,TextBox is {0}", num.ToString()); 
public MainPage()
{
   Debug.WriteLine("MainPage Constructor");
   camera = new CameraCaptureTask();
   camera.Completed += new EventHandler<PhotoResult>(camera_Completed);
   num = 1;
   InitializeComponent();
   txtNum.DataContext = num;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
   txtNum.DataContext = ++num;
   Debug.WriteLine("Add Method,TextBox is {0}", num.ToString());
}

我们点击Add让TextBox值增加。然后点击Win键回到住界面,然后点击Back返回程序,并继续点Add,程序Log信息如下:

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Add Method,TextBox is 4 
  7. MainPage OnNavigatedFrom 
  8. Application_Deactivated 
  9. Application_Activated 
  10. MainPage OnNavigatedTo 
  11. Add Method,TextBox is 5 
  12. Add Method,TextBox is 6 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
MainPage OnNavigatedFrom
Application_Deactivated
Application_Activated
MainPage OnNavigatedTo
Add Method,TextBox is 5
Add Method,TextBox is 6

很明显,因为在WP7.1下,程序进入了休眠状态,所以我们TextBox不会被清空。让我们在模拟器上来模仿一下墓碑状态的情况,需要进行如下设置就可以了。

然后我们再次运行程序,结果如下:

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Add Method,TextBox is 4 
  7. MainPage OnNavigatedFrom 
  8. Application_Deactivated 
  9. The thread '<No Name>' (0xec600c2) has exited with code 0 (0x0). 
  10. The thread '<No Name>' (0xee100aa) has exited with code 0 (0x0). 
  11. The thread '<No Name>' (0xe6a00ae) has exited with code 0 (0x0). 
  12. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  13. 'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll' 
  14. 'UI Task' (Managed): Loaded 'System.dll' 
  15. 'UI Task' (Managed): Loaded 'System.Windows.dll' 
  16. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  17. 'UI Task' (Managed): Loaded 'System.Core.dll' 
  18. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  19. 'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  20. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  21. 'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll' 
  22. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  23. Application_Activated 
  24. MainPage Constructor 
  25. MainPage OnNavigatedTo 
  26. Add Method,TextBox is 2 
  27. Add Method,TextBox is 3 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
MainPage OnNavigatedFrom
Application_Deactivated
The thread '<No Name>' (0xec600c2) has exited with code 0 (0x0).
The thread '<No Name>' (0xee100aa) has exited with code 0 (0x0).
The thread '<No Name>' (0xe6a00ae) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3

很明显看到,在执行完Deactivated事件后程序的线程都结束了,而但我点击Back按钮后,重新加载了程序,并且执行了MainPage的构造函数,而我们TextBox的值也从1开始了。下面看看如何来保存这些数据。对代码修改如下:

  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("MainPage OnNavigatedFrom"); 
  3.     base.OnNavigatedFrom(e); 
  4.     State["num"] = num; //保存变量 
  5.  
  6. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  7.     Debug.WriteLine("MainPage OnNavigatedTo"); 
  8.     base.OnNavigatedTo(e); 
  9.     num = (int)State["num"];//恢复变量 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    State["num"] = num; //保存变量
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedTo");
    base.OnNavigatedTo(e);
    num = (int)State["num"]; //恢复变量
}

我们在MainPage的两个方法中保存和恢复数据,但是运行却出错了。因为OnNavigatedTo中的State["num"]为空。因为我们第一次进入页面是也会执行这个方法,这时还没有保存过num,所以就出错了。在WP7.0中我们可以使用 State.ContainsKey或者State.TryGetValue来避免这种错误的发声,任何时候我们从字典中读取数据时都必须检查数据的有效性。而在WP7.1中我们有更好的办法:

  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("MainPage OnNavigatedFrom"); 
  3.     base.OnNavigatedFrom(e); 
  4.     if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back) 
  5.     { 
  6.         Debug.WriteLine("Save data"); 
  7.         State["num"] = num; //保存变量 
  8.       } 
  9.  
  10. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  11.      Debug.WriteLine("MainPage OnNavigatedTo"); 
  12.      base.OnNavigatedTo(e); 
  13.      if (e.NavigationMode != System.Windows.Navigation.NavigationMode.New) 
  14.      { 
  15.          Debug.WriteLine("Recover data"); 
  16.          object objNum; 
  17.          if (State.TryGetValue("num",out objNum)) 
  18.          { 
  19.              num = (int)objNum; 
  20.          } 
  21.      } 
  22.      txtNum.DataContext = num; 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
    {
        Debug.WriteLine("Save data");
        State["num"] = num; //保存变量
      }
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("MainPage OnNavigatedTo");
     base.OnNavigatedTo(e);
     if (e.NavigationMode != System.Windows.Navigation.NavigationMode.New)
     {
         Debug.WriteLine("Recover data");
         object objNum;
         if (State.TryGetValue("num", out objNum))
         {
             num = (int)objNum;
         }
     }
     txtNum.DataContext = num;
}

我们在看看结果:

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Add Method,TextBox is 4 
  7. MainPage OnNavigatedFrom 
  8. Save data 
  9. Application_Deactivated 
  10. The thread '<No Name>' (0xfa800f2) has exited with code 0 (0x0). 
  11. The thread '<No Name>' (0xf9500e6) has exited with code 0 (0x0). 
  12. The thread '<No Name>' (0xe3700ee) has exited with code 0 (0x0). 
  13. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  14. 'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll' 
  15. 'UI Task' (Managed): Loaded 'System.dll' 
  16. 'UI Task' (Managed): Loaded 'System.Windows.dll' 
  17. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  18. 'UI Task' (Managed): Loaded 'System.Core.dll' 
  19. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  20. 'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  21. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  22. 'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll' 
  23. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  24. Application_Activated 
  25. MainPage Constructor 
  26. MainPage OnNavigatedTo 
  27. Recover data 
  28. Add Method,TextBox is 5 
  29. Add Method,TextBox is 6 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xfa800f2) has exited with code 0 (0x0).
The thread '<No Name>' (0xf9500e6) has exited with code 0 (0x0).
The thread '<No Name>' (0xe3700ee) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 5
Add Method,TextBox is 6

数据被正常恢复了,我们可以看到,OnNavigatedTo方法虽然被执行了两次,但是Recover data只有在从墓碑或休眠状态返回时才执行,而在新建时是没有执行的。因为在WP7.1中开放了NavigationEventArgs 的NavigationMode属性。当我们新建一个页面进入时,此属性为New,而当我们从墓碑状态或其他页面返回时则是Back,很明显我们只有在Back时才需要恢复数据。而在OnNavigatedFrom方法中,当我们导航到新的页面时,此属性为New,而当我们回退到前一个页面时,此属性为Back,我们知道Windows Phone中只有Back Stack,所以从一个页面返回到前一个页面时,这个页面就会被回收,所以就没有必要保存数据了。而在WP7中我们每次都要进行保存和恢复操作。

但是这里还有一个问题,就是当我们从休眠状态返回时,是不需要进行数据恢复的,但是此方法还是会被执行,在WP7.1中同样提供了方法,可以在休眠状态返回时不执行这些操作。我们在Application的Activated 事件中可以判断,我们修App代码如下:

  1. public static bool IsTombstoning {get; set; } 
  2.  
  3. private void Application_Activated(object sender, ActivatedEventArgs e) 
  4.    Debug.WriteLine("Application_Activated"); 
  5.    if (e.IsApplicationInstancePreserved) 
  6.    { 
  7.        IsTombstoning = false
  8.    } 
  9.    else 
  10.    { 
  11.        IsTombstoning = true
  12.    } 
 public static bool IsTombstoning { get; set; }

private void Application_Activated(object sender, ActivatedEventArgs e)
{
   Debug.WriteLine("Application_Activated");
   if (e.IsApplicationInstancePreserved)
   {
       IsTombstoning = false;
   }
   else
   {
       IsTombstoning = true;
   }
}

WP7.1中新增了IsApplicationInstancePreserved属性来判断是休眠状态还是墓碑状态恢复的。我们在App中定义了一个静态的IsTombstoning 属性。在MainPage的OnNavigatedTo方法中使用就行了。有一点要注意的是不要在App的构造函数中对IsTombstoning 赋值,否则从墓碑状态返回后会执行构造函数,另外也不要在页面中定义一个变量来指示是否是新的实例,因为你需要保存或恢复这个变量。取消Debug中的墓碑调试,我们看看结果。

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is
  5. Add Method,TextBox is
  6. Add Method,TextBox is
  7. Add Method,TextBox is
  8. Add Method,TextBox is
  9. MainPage OnNavigatedFrom 
  10. Save data 
  11. Application_Deactivated 
  12. Application_Activated 
  13. MainPage OnNavigatedTo 
  14. Add Method,TextBox is
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
Add Method,TextBox is 5
Add Method,TextBox is 6
MainPage OnNavigatedFrom
Save data
Application_Deactivated
Application_Activated
MainPage OnNavigatedTo
Add Method,TextBox is 7

2 多页面的情况

前面已经了解了单页面情况下的数据恢复,那么如果我们从主页面切换到其他页面,这时进入了休眠或墓碑状态又会怎么样呢?因为这是我们当前已经不是在MainPage页面了。还是在Demo5中,我们点击Add后,点击Next按钮,进入到Page1,然后点Win键,进入主菜单。在Page1中我们没有进行数据存储和恢复,我们只关心MainPage中的数据。我们就看看墓碑情况,运行结果如下:

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is
  5. Add Method,TextBox is
  6. Main Pgee Navigate to Page1 
  7. Page1 Constructor 
  8. MainPage OnNavigatedFrom 
  9. Save data 
  10. Page1 OnNavigatedTo 
  11. Page1 OnNavigatedFrom 
  12. Application_Deactivated 
  13. The thread '<No Name>' (0xf88012a) has exited with code 0 (0x0). 
  14. The thread '<No Name>' (0xe220156) has exited with code 0 (0x0). 
  15. The thread '<No Name>' (0xf1d014a) has exited with code 0 (0x0). 
  16. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  17. 'UI Task' (Managed): Loaded'System.Windows.RuntimeHost.dll' 
  18. 'UI Task' (Managed): Loaded 'System.dll' 
  19. 'UI Task' (Managed): Loaded'System.Windows.dll' 
  20. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  21. 'UI Task' (Managed): Loaded'System.Core.dll' 
  22. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  23. 'UI Task' (Managed): Loaded'\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  24. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  25. 'UI Task' (Managed): Loaded'Microsoft.Phone.Interop.dll' 
  26. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  27. Application_Activated 
  28. Page1 Constructor 
  29. Page1 OnNavigatedTo 
  30. Bace to MainPage 
  31. MainPage Constructor 
  32. Page1 OnNavigatedFrom 
  33. MainPage OnNavigatedTo 
  34. Recover data 
  35. Add Method,TextBox is
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Main Pgee Navigate to Page1
Page1 Constructor
MainPage OnNavigatedFrom
Save data
Page1 OnNavigatedTo
Page1 OnNavigatedFrom
Application_Deactivated
The thread '<No Name>' (0xf88012a) has exited with code 0 (0x0).
The thread '<No Name>' (0xe220156) has exited with code 0 (0x0).
The thread '<No Name>' (0xf1d014a) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
Page1 Constructor
Page1 OnNavigatedTo
Bace to MainPage
MainPage Constructor
Page1 OnNavigatedFrom
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 4

事实证明,我们前面写的代码依旧有效,没有进行任何修改就完成了任务。我们看到在导航到Page1之前就进行了数据保存。因为OnNavigatedFrom不仅在进入墓碑状态前回执行,在离开当前页面时也会执行。所以,即便是在Page1中进入了墓碑状态,当我们返回程序,并返回到MainPage页面是,还是会执行OnNavigatedTo方法来恢复数据,这时的页面Mode也是Back。所以我们不需要为多页面做特殊处理。

我们考虑下,如果从Page1页面不是GoBack,而是使用Navigate方法重新导航到MainPage呢?这有什么关系,只不过是新建了一个页面的实例,我们前一个MainPage还在Back Stack中。那么数据会恢复到这个MainPage中吗,当然不会,我们使用的State属性是每个页面实例特有的。那我我如果想在每个新建的MainPage中都能继续进行Add操作呢?

对于这种变态的需求,一般我是懒得做,肯定是你设计有问题,但是这里还是演示下如何实现。除了使用Page的State,前面我们还提到使用Application的State,这个是所有页面共享的。我们在Page中增加一个New按钮,然后修改MainPage的方法:

  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("MainPage OnNavigatedFrom"); 
  3.     base.OnNavigatedFrom(e); 
  4.     Debug.WriteLine("Save data"); 
  5.     PhoneApplicationService.Current.State["num"] = num;//保存到全局 
  6.  
  7. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  8.     Debug.WriteLine("MainPage OnNavigatedTo"); 
  9.     base.OnNavigatedTo(e); 
  10.     Debug.WriteLine("Recover data"); 
  11.     object objNum; 
  12.     if (PhoneApplicationService.Current.State.TryGetValue("num",out objNum)) 
  13.     { 
  14.         num = (int)objNum; 
  15.         txtNum.DataContext = num; 
  16.     } 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    Debug.WriteLine("Save data");
    PhoneApplicationService.Current.State["num"] = num; //保存到全局
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedTo");
    base.OnNavigatedTo(e);
    Debug.WriteLine("Recover data");
    object objNum;
    if (PhoneApplicationService.Current.State.TryGetValue("num", out objNum))
    {
        num = (int)objNum;
        txtNum.DataContext = num;
    }
}

为了一个变量能在不同实例中同时起作用,并公用,我们修改了页面的方法。这里保存和恢复数据时都不在判断是否是New或Back,因为任何情况我们都要保存和恢复,以便在所有页面中使用。另外我们可以使用隔离数据区来存储和恢复。结果就是,无论我们在Page1页面从墓碑返回后, New一个MainPage,还是Back,或者是先New然后在返回到前一个MainPage,结果都是在任何页面都能对这个数据继续的使用。

当然更好的办法是在App中定义一个全局的变量,这样我们不需要在Page的页面中对这些数据进行保存和恢复,只需要在Appliation的事件中进行。这样代码会很简洁。

  1. public static int Number { get; set; } 
  2.  
  3. private void Application_Activated(object sender, ActivatedEventArgs e) 
  4.    Debug.WriteLine("Application_Activated"); 
  5.    if (e.IsApplicationInstancePreserved) 
  6.    { 
  7.        IsTombstoning = false
  8.    } 
  9.    else 
  10.    { 
  11.        IsTombstoning = true
  12.        Number = (int)PhoneApplicationService.Current.State["number"]; 
  13.    } 
  14.  
  15. private void Application_Deactivated(object sender, DeactivatedEventArgs e) 
  16.      Debug.WriteLine("Application_Deactivated"); 
  17.      PhoneApplicationService.Current.State["number"] = Number; 
public static int Number { get; set; }

private void Application_Activated(object sender, ActivatedEventArgs e)
{
   Debug.WriteLine("Application_Activated");
   if (e.IsApplicationInstancePreserved)
   {
       IsTombstoning = false;
   }
   else
   {
       IsTombstoning = true;
       Number = (int)PhoneApplicationService.Current.State["number"];
   }
}

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
     Debug.WriteLine("Application_Deactivated");
     PhoneApplicationService.Current.State["number"] = Number;
}

App中代码修改如上,我们在Activated和Deactivated中进行数据恢复和保存,因为Activated必定是在Deactivated执行后执行,所以这里没有判断是否存在。但还是应该写的严谨一些。下面是MainPage中的代码:

  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("MainPage OnNavigatedFrom"); 
  3.     base.OnNavigatedFrom(e); 
  4.     App.Number = num; 
  5.  
  6. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  7.     Debug.WriteLine("MainPage OnNavigatedTo"); 
  8.     base.OnNavigatedTo(e); 
  9.     num = App.Number; 
  10.     txtNum.DataContext = num; 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    App.Number = num;
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedTo");
    base.OnNavigatedTo(e);
    num = App.Number;
    txtNum.DataContext = num;
}
  1. 代码是非常的简单,如果不是为了掩饰保存和恢复,我们甚至可以直接对App.Number来操作。相比前一种方法,代码简洁了很多。结果这里就不贴了。 
代码是非常的简单,如果不是为了掩饰保存和恢复,我们甚至可以直接对App.Number来操作。相比前一种方法,代码简洁了很多。结果这里就不贴了。

3 使用Chooser的情况

在程序中我们可以使用Launcher或者Chooser来运行系统提供的API功能,比如打电话,发短信,调用相机等等。具体的使用我们后面会介绍。一般使用这些API都会导致当期那程序变为后台程序。所以也存在数据恢复的问题,但是对于Chooser来说,他会返回数据,而Lanucher不会返回数据,那么这个时候就存在一个新得到的数据和被保存的数据我们应该用哪一个的问题。

看下面的列子,我们调用相机,返回后得到图片的大小,把大小填入TextBox。

  1. private CameraCaptureTask camera; 
  2.  
  3. public MainPage() 
  4.    num = 1; 
  5.    camera = new CameraCaptureTask(); 
  6.    camera.Completed += new EventHandler<PhotoResult>(camera_Completed); 
  7.    InitializeComponent(); 
  8.    txtNum.DataContext = num; 
private CameraCaptureTask camera;

public MainPage()
{
   num = 1;
   camera = new CameraCaptureTask();
   camera.Completed += new EventHandler<PhotoResult>(camera_Completed);
   InitializeComponent();
   txtNum.DataContext = num;
}

我们先定义相机的Chooser,注意这里要定义成类变量,而不能是局部变量,并且设置完成事件的处理方法。因为如果是局部变量,当系统进入到墓碑状态在返回时,这个对象就没有了,就没办法执行完成事件了,对于Chooser来说也没有办法获得得到返回的数据了。

  1. private void Button_Click_2(object sender, RoutedEventArgs e) 
  2.     Debug.WriteLine("Run Camera"); 
  3.     try 
  4.     { 
  5.        camera.Show(); 
  6.     } 
  7.     catch (System.InvalidOperationException ex) 
  8.     { 
  9.        MessageBox.Show(ex.Message); 
  10.     } 
  11.  
  12. void camera_Completed(object sender, PhotoResult e) 
  13.     if (e.TaskResult == TaskResult.OK) 
  14.     { 
  15.         BitmapImage bmp = new BitmapImage(); 
  16.         bmp.SetSource(e.ChosenPhoto); 
  17.         num = bmp.PixelHeight; 
  18.     } 
  19.      else 
  20.     { 
  21.          num = 1024; //模拟器没法拍照 
  22.      } 
private void Button_Click_2(object sender, RoutedEventArgs e)
{
    Debug.WriteLine("Run Camera");
    try
    {
       camera.Show();
    }
    catch (System.InvalidOperationException ex)
    {
       MessageBox.Show(ex.Message);
    }
}

void camera_Completed(object sender, PhotoResult e)
{
    if (e.TaskResult == TaskResult.OK)
    {
        BitmapImage bmp = new BitmapImage();
        bmp.SetSource(e.ChosenPhoto);
        num = bmp.PixelHeight;
    }
     else
    {
         num = 1024; //模拟器没法拍照
     }
}

以上我们点击Run Camera按钮时,就调用Show方法调出,拍完照片后吧照片的高度设置到TextBox,因为模拟器拍不了,就手动设置到1024。页面的数据我们采用第一种,单页面的保存方式。

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Run Camera 
  7. MainPage OnNavigatedFrom 
  8. Save data 
  9. Application_Deactivated 
  10. Application_Activated 
  11. Set carmera num 
  12. MainPage OnNavigatedTo 
  13. Add Method,TextBox is 1025 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Run Camera
MainPage OnNavigatedFrom
Save data
Application_Deactivated
Application_Activated
Set carmera num
MainPage OnNavigatedTo
Add Method,TextBox is 1025

从上面结果可见程序调用相机时进入休眠状态 ,返回后先执行了完成函数,Set carmera num后数据变成了1024。因为是休眠不需要恢复状态,所以在此加1后变为1025。我们在看看从墓碑状态返回的结果。

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Run Camera 
  7. MainPage OnNavigatedFrom 
  8. Save data 
  9. Application_Deactivated 
  10. The thread '<No Name>' (0xf3802a2) has exited with code 0 (0x0). 
  11. The thread '<No Name>' (0xe0a02da) has exited with code 0 (0x0). 
  12. The thread '<No Name>' (0xea002da) has exited with code 0 (0x0). 
  13. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  14. 'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll' 
  15. 'UI Task' (Managed): Loaded 'System.dll' 
  16. 'UI Task' (Managed): Loaded 'System.Windows.dll' 
  17. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  18. 'UI Task' (Managed): Loaded 'System.Core.dll' 
  19. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  20. 'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  21. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  22. 'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll' 
  23. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  24. Application_Activated 
  25. MainPage Constructor 
  26. Set carmera num 
  27. MainPage OnNavigatedTo 
  28. Recover data 
  29. Add Method,TextBox is 4 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Run Camera
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xf3802a2) has exited with code 0 (0x0).
The thread '<No Name>' (0xe0a02da) has exited with code 0 (0x0).
The thread '<No Name>' (0xea002da) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
Set carmera num
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 4

我们发现从墓碑状态返回后,虽然也进行了Set carmera num,但是还进行了Recover data,最新得到数据被之前恢复的数据覆盖了。我们看到返回后先执行了页面的构造函数,然后执行了相机的完成方法,这里要注意的是,不要在这里操作控件,因为此时Load事件还没有执行,这里操作控件会抛出一个空引用异常。

获得的数据和保存的数据间的选择是比较容易碰到的问题。我们要根据自己的逻辑来选择解决的方法,这里我们使用最新的数据来代替保存的数据。所以修改恢复数据的部分:

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.    Debug.WriteLine("MainPage OnNavigatedTo"); 
  3.    base.OnNavigatedTo(e); 
  4.  
  5.     //单页面情况 
  6.      if (App.IsTombstoning) 
  7.     { 
  8.        if (e.NavigationMode != System.Windows.Navigation.NavigationMode.New) 
  9.        { 
  10.           if (num == 1) 
  11.           { 
  12.             Debug.WriteLine("Recover data"); 
  13.             object objNum; 
  14.             if (State.TryGetValue("num",out objNum)) 
  15.             { 
  16.                num = (int)objNum; 
  17.             } 
  18.           } 
  19.        } 
  20.     } 
  21.     txtNum.DataContext = num; 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
   Debug.WriteLine("MainPage OnNavigatedTo");
   base.OnNavigatedTo(e);

    //单页面情况
     if (App.IsTombstoning)
    {
       if (e.NavigationMode != System.Windows.Navigation.NavigationMode.New)
       {
          if (num == 1)
          {
            Debug.WriteLine("Recover data");
            object objNum;
            if (State.TryGetValue("num", out objNum))
            {
               num = (int)objNum;
            }
          }
       }
    }
    txtNum.DataContext = num;
}

我们知道如果进入到墓碑状态返回后,num的数据会丢失,而我们在构造函数中把num设置为1。因为在调用OnNavigatedTo之前会执行方法相机的返回方法camera_Completed。如果num被设置为1024就不应该恢复数据。所以我们可以在OnNavigatedTo中通过num的值来判断是否恢复,如果是1就需要恢复,如果不是就说明有新的值。

这里需要注意,如果在 camera_Completed中得到的数据就是1,那么还是会被恢复,所以一定根据情况来。这里只是举例子。另外我们要注意构造函数中num初始化和camera.Completed事件绑定之间的顺序,一定要先初始化数据,最后在注册完成事件,否则执行完camera_Completed方法后,num又被初始化了。

下面看看执行结果:

  1. <strong>Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Run Camera 
  7. MainPage OnNavigatedFrom 
  8. Save data 
  9. Application_Deactivated 
  10. The thread '<No Name>' (0xe83037a) has exited with code 0 (0x0). 
  11. The thread '<No Name>' (0xeb50372) has exited with code 0 (0x0). 
  12. The thread '<No Name>' (0xf53033a) has exited with code 0 (0x0). 
  13. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  14. 'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll' 
  15. 'UI Task' (Managed): Loaded 'System.dll' 
  16. 'UI Task' (Managed): Loaded 'System.Windows.dll' 
  17. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  18. 'UI Task' (Managed): Loaded 'System.Core.dll' 
  19. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  20. 'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  21. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  22. 'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll' 
  23. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  24. Application_Activated 
  25. MainPage Constructor 
  26. Set carmera num 
  27. MainPage OnNavigatedTo 
  28. Add Method,TextBox is 1025 
  29. </strong> 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Run Camera
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xe83037a) has exited with code 0 (0x0).
The thread '<No Name>' (0xeb50372) has exited with code 0 (0x0).
The thread '<No Name>' (0xf53033a) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
Set carmera num
MainPage OnNavigatedTo
Add Method,TextBox is 1025

和之前的区别就在于这里没有恢复数据,最后使用的是相机得到的数据。对于没有调用相机,而是点击Win导致的程序恢复,这时恢复的数据,结果正确

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. MainPage OnNavigatedFrom 
  7. Save data 
  8. Application_Deactivated 
  9. The thread '<No Name>' (0xebe0382) has exited with code 0 (0x0). 
  10. The thread '<No Name>' (0xeb203be) has exited with code 0 (0x0). 
  11. The thread '<No Name>' (0xec103d6) has exited with code 0 (0x0). 
  12. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  13. 'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll' 
  14. 'UI Task' (Managed): Loaded 'System.dll' 
  15. 'UI Task' (Managed): Loaded 'System.Windows.dll' 
  16. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  17. 'UI Task' (Managed): Loaded 'System.Core.dll' 
  18. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  19. 'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  20. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  21. 'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll' 
  22. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  23. Application_Activated 
  24. MainPage Constructor 
  25. MainPage OnNavigatedTo 
  26. Recover data 
  27. Add Method,TextBox is 4 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xebe0382) has exited with code 0 (0x0).
The thread '<No Name>' (0xeb203be) has exited with code 0 (0x0).
The thread '<No Name>' (0xec103d6) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 4

四 禁用墓碑

如果你拥有一台真机,在升级到到芒果之前,或许总是要忍受恢复程序时的"Resuming"的提示。当然也不是没有办法,网上就流传了修改注册表来禁用墓碑状态的方法。首先你需要有一台已经越狱的机器,然后下载一个RegistryEditor的XPA软件包。运行这个软件,把\HKLM\Software\Microsoft\TaskHost\DehydrateOnPause的键值从3修改为0。再试试,程序切换的很快了,"Resuming"基本也不出现了。我买手机的时候Mango的Beta版已经出了,而且注册表也已经被卖家修改好了。所以在手机上还没见到"Resuming"就升级到Mango了。在我升级到7712之后,偶尔却会出现"Resuming",所以决定研究下修改这个注册表的作用。

搜索了好久,终于找到了一篇相关的文章:Tombstoning, Dehydration, and Windows Phone  。从文章中我们可以了解到,这个键值有4个选项:

  1. Don’t dehydrate
  2. Forcefully dehydrate
  3. Gracefully dehydrate
  4. Automatically decide

表示程序在暂停时是否执行Dehydrate,Dehydrate愿意是脱水。在进入墓碑状态后,一些系统级别的操作会停止,运行环境开始回收资源,其中就包括.NET运行时,回收完以后整个程序的运行环境也被回收了。这样一个过程就叫做Dehydrate。

系统默认是3,表示命令程序的运行环境在合适的时候优雅的执行Dehydrate。但是目前并不清楚是如何决定是否合适。但是对运行环境来说默认是执行Dehydrate,但是也可以选择不进行Dehydrate。

  1. HRESULT SHSetAutoDehydratingHostEligibility(BOOL fEligible); 
HRESULT SHSetAutoDehydratingHostEligibility(BOOL fEligible);

通过这个方法可以设置程序运行环境是否进行Dehydrate。当我们选择2时,系统总是会执行Dehydrate,而选择1时,系统会给程序发送一个WM_CLOSE消息来强制,理论上来说我们可以捕获这个消息,来自己决定是否执行Dehydrate。而当设置为0是,Dehydrate被禁用了,也就是我们程序的运行环境不会被回收,.NET运行时,程序的实例都不会被销毁。而在我们恢复程序是,主要是创建新的实例和.NET运行时,导致了出现"Resuming"。

所以我们修改注册表为0后,系统看起来就系象7.1中的休眠状态一样了。而且程序恢复也不会创建新的实例,所以并不清楚和WP7.1的休眠有什么区别。也有可能7.1的休眠也是采用这种方式,默认不执行Dehydrate,而在资源不足时执行Dehydrate进入墓碑。这样在恢复时会有Resuming字样,而在WP7.0中不执行Dehydrate,当资源不够时程序会被结束,所以不会出现Resuming字样。这是我个人的猜测。在Mango中用不了注册表工具,没法查看。也不知道此键值是否还效或者是否有修改。

另外系统规定只能有5个程序进入墓碑状态,我在真机上看了下,最多也只能有5个第三方的程序运行,而不管是休眠还是墓碑状态。当你开第六个程序时,最早使用的程序就会被关闭,大家可以长按Back按钮来观察。

五 总结

通过上面介绍我们发现对于我们程序而言,墓碑机制中对数据的保存和恢复是我们需要关注的地方。我们通过三种情况,介绍了对页面已经程序数据的保存和恢复方法,以及决定是否恢复数据的一般方法。了解了程序的执行模型。其中Page中的OnNavigatedTo和OnNavigatedFrom是最重要的方法。另外所有保存的数据必须可以被序列化。

下面是微软关于执行模型的最佳时间方法:http://msdn.microsoft.com/zh-cn/library/ff817009.aspx

其中介绍了一些文章中没有涉及的部分,比如当程序从墓碑模式被终止的时候需要保存数据到隔离区。所以我们应该在Deactivated事件中就进行这样的操作,因为我们无法预料程序可能进入的状态。另外有一点要指出,当我们程序被切换至后台,进入到休眠或墓碑状态时,此时我们点击程序图标,重新启动一个实例时,之前的实例的数据都无法恢复。想解决这个问题只能把数据存放到隔离区,然后恢复。但是会出现欢迎界面,对于有登陆界面的的还会出现登陆界面,我们需要进行更多的处理,才能让用户感觉不到是新开的程序。另外其中还建议Application中的事件以及页面中的两个方法操作的时间不要超过10s,否则程序会被终止。但是我尝试了下使用Thread.Sleep(15000),程序并没有被终止。

Demo5代码下载http://download.csdn.net/source/3500401

又是好久没有写BLOG了,做完上一个MSN项目后都是些琐碎的事情,然后就是是玩摄影啊,旅游啊。上月休假去了趟西藏,真是不错。回来了也要收心学习了。Windows Phone 7.1的开发工具发布了,一直对WP7很关注,现在终于可以开始学习了。其实09年就学习过silverlight,看过3的SDK文档,当时因为工作,断断续续也没有坚持下来,所以这次学习WP7顺便重新学习SL。

上周安装了WP7.1的开发环境,工作之余看了一周文档。目前关于WP的学习文档大多是英文的,chian-pub上出现了一本中文的书,不过还是推荐看MSDN,上面有最新的WP7.1的内容,毕竟7.1进行了较大的更新。我写Windows Phone开发的文章主要是对自己看的文档进行一个提炼和记录,主要针对wp7.1Beta。


一 Windows Phone 学习资料

目前主要的学习资料还是来至于微软MSDN和《Programming Windows Phone 7》,其中MSDN包含了最新的7.1Beta特性的介绍和使用。一些中文网站和原创的BLOG文章基本也是来自这些地方。而MSDN杂志每期有对Windows Phone介绍的文章。关于界面开发可以参考Silverlight开发。

Windows Phone 中文开发中心

Windwos Phone Development

Silverlight for Windows Phone

Phone Platform Development

Windows Phone developer documentation

Programming Windows Phone 7

视屏学习资料

Windows Phone 7 中文开发资料概览

MSDN杂志


二 Windows Phone 开发环境

微软在5月份发布了代号Mango的最新开发工具,Windows Phone Developer Tools 7.1 Beta (点击下载ISO文件)

系统要求:

  • 操作系统:Windows Vista SP2 / Windows 7
  • 硬件要求:4G硬盘空间 3G内存
  • 手机模拟器:DirectX10以上显卡,WDDM1.1驱动

PS:我的笔记本是T2450+3G内存+HD2300显卡,07年的了,正常运行。

安装准备:

  • 如果没有安装VS2010,可以直接安装Windows Phone Developer Tools 7.1 Beta,他会自带Express版本的VS2010 for Windows Phone
  • 如果安装了VS2010专业版或者更高版本,可以直接安装,但是必须把VS2010的语言修改成同Windows Phone Developer Tools 7.1 Beta相同的语言
  • 如果安装了VS2010 RTM版本,必须先安装VS2010 SP1(点击下载ISO文件)
  • 如果安装了非英文版本的Windows Phone Developer Tools 7.0或 Expression Blend,必须先卸载在进行安装

PS:我之前安装了VS2010中文旗舰版,所以先安装了SP1(安装SP1时第一次到了一半就停住了,后来我查了说可能缺少文件,就把VS2010的ISO也加载到虚拟光驱中然后就通过了),但是因为Windows Phone Developer Tools 7.1 Beta目前只有英文版的,所以安装完成后,并没有在VS2010中集成WP7开发模板,但集成了XNA Game Studio的开发环境;而是安装了VS2010 Express for Windows Phone。


三 第一个Windows Phone 7程序

我们可以看到其中有很多模板可以选择,下面进行简单介绍,具体可以建立相应项目查看

  • Windwos Phone Application: 创建一个普通的Windows Phone应用程序
  • Windwos Phone Databound Application: 创建一个和数据绑定有关的项目,使用列表和导航控件
  • Windows Phone Class Library:创建一个类库项目
  • Windwos Phone Panorama Application: 创建一个全景应用项目,使用全景Panorama控件
  • Windwos Phone Pivto Application:创建一个枢轴应用项目,使用Pivto控件
  • Windwos Phone 3D Graphics Application: 创建一个XNA框架支持的项目(WP7.1可以silverlight和XNA集成)
  • Windwos Phone Audio Playback Application:创建一个音频播放的类库项目
  • Windwos Phone Audio Streaming Application:创建一个音频流的类库项目
  • Windwos Phone Task Scheduler Agent:创建一个后台任务代理项目(WP7.1中新增了后台多任务代理)

选择了项目模板之后,就要选择Windows Phone的开发版本,目前有7.0和7.1可以进行选择。

上图就是WP7的开发环境,如果有开发过silverlight,应该不会陌生。左边是图形的界面环境,其他.NET其他开发环境一样,可以拖控件来布局。而右侧是页面的XAML代码,可以通过编写XAML来布局。关于XAML可以参见MSDN(点击打开)

我们的第一个程序要实现的是点击页面上的一个按钮导航到下一个页面:

1 在MainPage.xaml中添加一个按钮,并且添加触屏事件,代码如下

2 为项目添加一个新的页面,鼠标右键--ADD--New Item--Windows Phone Portrait Page,名字为Page1.xaml,在次页面中添加一个文本和一个返回按钮,代码如下:

 

3 为MainPage.xaml的触屏事件和Page1.xaml触屏事件添加代码(和C#其他项目一样,也是采用code-behind,代码在xaml页面的cs文件中)

4 编译后点F5运行程序,模拟器第一次启动需要等待(我也没有真机,所以无法进行真机调试)。然后点击Next Page按钮程序将导航进入到第二个界面。第二个界面中点击Back导航回前一个界面。可以发现Windows Phone程序同Web程序页面有点类似,都是Page导航的概念,这个和之前WM平台很不一样。但是使用过WPF或Silverlight就没有什么特别的。

5 进入第二个页面后,点击【<--】按钮也能退回到第一个界面,在点【<--】按钮,就会关闭程序。要注意的是,Windows Phone目前没有提供任何退出程序的Exit方法,都是通过在第一个页面点击【<--】按钮退出程序。在程序中点击中间的Home按钮返回到Home列表,此时程序会进入休眠(WP7.1)或墓碑(WP7.0)状态,这个后面会介绍。

至此我们完成了第一个Windows Phone程序,它很简单,看起来和写一个普通的Silverlight程序没有太大的区别。


四 Windows Phone项目结构

项目建立好之后,包含了以下一些文件:

  • AppManifest.xml : 此文件中包含一个<Deployment.Parts>的节点。如果需要调用XAP文件中包含的其他Assembly的DLL文件,就会在此节点下添加一个<AssemblyPart/>节点来列举这些文件,可以参见这里
  • AssemblyInfo.cs : 这个文件包含了对当前程序集的信息,可以在属性页面进行设置
  • WMAppManifest.xml : 这个文件主要记录了程序的起始页面,APPID,作者,图标设置和程序功能设置。其中<Capabilities/>节点定义了程序的功能,比如ID_CAP_PHONEDIALER表示可以使用电话功能,如果没有这个节点,调用相应功能时就会出错,详细介绍见这里。相对于7.0,7.1增加了对相机,联系人,约会提醒功能。
  • App.xaml : 这个文件App类继承与Application类,它并没有可视化界面,<Application.Resources/>一般用来存放资源数据共全局使用。而<Application.ApplicationLifetimeObjects/>节点下定义了与执行模型相关的一些方法。
  • App.xaml.cs : App类的另一部分,代码包含了程序初始化操作和执行模型相关的一些方法的实现。
  • ApplicationIcon.jpg : 显示在程序列表中的图标,大小为62*62
  • Bcakground.jpg : 在程序启动时显示的图标,大小为173*173s
  • MainPage.xaml : 程序启动后默认显示的第一个页面,用户也可以在WMAppManifest.xml中指定启动页面。
  • SplashScreenImage.jpg : 程序启动时显示的欢迎界面,大小为480*800,这个和设备有关。

以上介绍了一个Windows Phone项目的结构,和Silverlight程序结构很类似。只是配置文件上有些区别。


五 总结

这里完成了Windows Phone 7.1环境的搭建,并完成了第一个Windows Phone程序,对于程序模板和项目结构有了一个认识。下一篇将介绍Windows Phone框架结构,以及程序启动运行方式。

源码下载

PS:对CSDN编辑器真是无语了,标题和上面竟然没有间隔,回车也没用。。。快改版升级吧

Windows Phone开发(二)-- 框架结构和启动过程       

        分类:            Windows Phone 2001人阅读 评论(4) 收藏 举报

上一篇文章介绍了Windows Phone的开发环境和一个简单的Windows Phone程序的演示和结构,这一篇文章要深入一点,介绍Windows Phone的框架结构和程序启动的过程。


一 Windows Phone 框架结构

在进行Windows Phone开发之前有必要了解一下整个全新平台的结构。对于Windows Phone平台来说,区别于之前的Windows Mobile平台最大的区别是他运行环境完全基于.NET框架,他只支持托管代码进行开发。

Windows Phone是基于.NET Compact Framework框架,所以精简了很多桌面版的功能。但是还是提供了所需的基本功能。如上图中的最下层的蓝色部分,就是.NET CF提供的BCL类库。而在他的上层就是Application Object。这一层都是基于.NET CF框架。其中包含了两种开发框架:

  • Silverlight Framework :以 Silverlight Framework 为基础的 Windows Phone 7 应用程序是由一堆的 Page (继承自 PhoneApplicationPage 类别的衍生类别) 组成的,每一个 Page 是一个扩展名为 .XAML 的文件,代表一个操作画面,程序设计师可以利用 Visual Studio 2010 Express for Windows Phone 或是 Expression Blend for Windows Phone 来设计 Page 的用户接口。WP7.0是基于Silverlight3.0,WP7.1则是基于Silverlight4。关于Silverlight for Windows Phone和Silverlight的区别(点击打开)
  • XNA Framework:主要的用途在支持开发游戏程序,提供 2D/3D 的动画,音效,及各种游戏相关的功能,协助有志于开发游戏程序的企业或个人发展 Windows Phone、Xbox 360、Zune 播放器、以及 Windows 7 平台的游戏程序。

毕竟移动手机平台还有自己特殊的一部分功能,所以在两个框架的上层Windows Phone特有的一些功能,比如相机,Windows Phone控件,感应器,多点触控屏幕,Launcher,Chooser等等来提供相应功能。

所以具体到实际开发,我们有2套开发框架进行选择,对于大多应用程序我们可以选择Silverlight进行开发,而对于游戏可以选择XNA。但是WP也提供部分功能相互调用的功能。而对以WP7.1来说可以建立一个silverlight和XNA集成的开发环境(点击查看)。

对于Windows Phone开发来说,不仅可以使用.NET CF和silverlight或XNA框架提供的这些功能,还能使用微软基于云服务的一些功能。比如Notification,Social, Location,Map ,Azure等服务。下图显示了Windows Phone上支持的的运行时框架和开发工具。下面的显示提供的运服务以及注册开发者和发布程序。他们组成了对Windows Phone开发的软件支持。

而对于硬件来说,微软统一了硬件平台,目前要求的硬件包括 800 x 480 或 480 x 320 屏幕分辨率,支持多点触控,内建 A-GPS 卫星定位系统,G-Sensor (Accelerometer),电子罗盘传感器,光源传感器,以及不需要直接接触就可以侦测到附近物体的 Proximity Sensor。最少 500 万画素的数字相机,内建 Codec 与多媒体影音播放功能,最少 256MB 的 RAM 与最少 8GB 的闪存,GPU (图形处理器),ARMv7 Cortex/Scorpion 或更佳的处理器,以及 Back、Start、Search 三个硬件按键。目前HTC和三星已经有第一代的WP7手机上市,可以机型和ROM信息可以参见:智机网

关于Windows Phone Plateform的详细介绍参见MSND(点击打开)


二 Windows Phone 7.1 新功能

目前最新版本的系统是Windows Phone 7.1Beta,相对于Windows Phone他有了比较大的改动,弄清楚每一版本的功能,可以更好的了解系统和选择开发的版本。

  • 粘贴和复制功能:1月的更新增加了粘贴复制功能
  • 执行模型快速切换:实现了新的执行模型,增加了休眠状态,使得应用程序可以快速切换。
  • 后台程序代理:增加后台代理功能,可以在程序没有在前台运行的情况下在进行提示,另外还有后台播放和后台下载功能
  • 感应器APIs:公开了感应器的API,以便程序可以对硬件进行访问。
  • 支持Socket:增加了Socket的支持,可以开发基于TCP和UDP的程序,非常有用。
  • 相机数据:增加了对相机原始数据访问的功能
  • 推送消息:增加了推送消息的功能,更加稳定
  • Silverlight和XNA集成:可以在同一个Page下使用silverlight和XNA进行开发
  • 程序配置文件:提供了对程序和游戏进行配置的功能
  • WebBrowser :浏览器控件可以支持IE9,并优化了HTML5的表现
  • 本地数据库:增加了对数据库的支持,可以使用LINQ to SQL进行操作
  • Launchers and Choosers  : 提供了新的功能,如Bing Map,Address等
  • 全球化和本地化:支持包括中文在内的16种语言

具体Windows Phone 7.1新的改进(点击打开)

关于程序的兼容性,简单说WP7.0开发的程序可以在OS7.0和7.1上运行,而7.1开放的程序在7.0上可能会失败。具体兼容性问题参见这里


三 Window Phone 程序启动过程

在我们上一篇文章中,我们写了一个简单的Demo程序。程序启动后显示的是MainPage.xaml页面。那么程序是如何成功启动的呢?

1 xap文件

打开程序后,我们对程序进行编译,然后我们可以在/PhoneApp2/Bin/Debug目录下看到名字为PhoneApp.xap文件,这个就是我们程序的包。部署到手机上就能运行。这其实是一个压缩文件,我们可以用7-ZIP来解压。

其中包含了程序使用的3个JPG文件,PhoneApp2.dll就是我们程序功能的DLL文件,而AppMainfest.xaml和WMAppMaingest.xml文件则提供了应用程序的一些信息。

xap包结构

上图是一个silverlight的XAP包的结构,WP基于silverlight开发的程序结构是相似的,不过多一个WMAppMaingest.xml文件,用来配置手机相关的一些信息,这个在上一篇有介绍过。首先来看看AppManifest.xaml 文件,它标识打包的程序集和应用程序入口点。

这个和上一篇文章相比,已经自动生成了一些内容。我们来简单看一下:

  • EntryPointAssembly : 定义了程序的入口程序集的名字,也就是开始运行时加载的程序集,这里就是我们的PhoneApp2.dll
  • EntryPointType : 表示了程序的入口点,这里指定了项目中的App对象
  • RuntimeVersion : 表示当前silverlight运行的版本,这里可以看到,WP7.1是基于SL4。
  • Deployment.Parts : 此节点中包含了xap包中的程序集列表。

关于xap包结构和此文件结构具体信息,详细可以参考silverlight的应用程序结构。对于WMAppMaingest.xml文件没和编译前没有变化,详细结构上一篇文章中有链接介绍。

2 App.xaml和App.xaml.cs文件

AppMaingest.axml中定义了程序的入口点,其中指定了App对象。通过查看这2个文件,我们发现App.xaml.cs文件中定义了App类,此类继承与Application类,而App.xaml文件的根节点Application指定了x:Class="PhoneApp2.App",这个表示把xaml文件中的内容同xaml.cs类中内容合并。这里所有内容编译后会合并到App类中。

当使用 Silverlight 的托管 API 创建应用程序时,必须创建一个从 Application 派生的类。Application 类提供应用程序通常要求的若干服务。它主要表示应用程序代码在 Silverlight 插件生命周期中的入口点。Application类的以下功能:

  • 应用程序生存期管理
  • 应用程序资源文件加载
  • 未经处理的异常处理
  • 扩展性

我们看到App.xaml文件中定义了<Application.Resources>节电,这个可以用来定义一些全局的资源别如string,style,笔刷等;<Application.ApplicationLifetimeObjects>则是提供了对Application的扩展性,这里提供了PhoneApplivationService的扩展服务,从名字我们就能知道这个是有关Phone的扩展,因为默认的Silverlight是没有的。这个是位于Micresoft.Phone.dll中,这儿服务提供了4个与手机有关的方法。关于扩展性可以参见silvrlight的扩展服务

[c-sharp] view plain copy print ?
  1. public PhoneApplicationFrame RootFrame {get; privateset; } 
  2.  
  3. public App() 
  4.     UnhandledException += Application_UnhandledException; 
  5.     InitializeComponent(); 
  6.     InitializePhoneApplication(); 
  7.     PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled; 

以上是App.xaml.cs文件中的代码,首先定义了一个PhoneApplicationFrame属性RootFrame,在构造函数中定义了UnhandledException来处理未处理异常,这是Application的一个功能;InitializeComponent是初始化方法,点击F12导航到App.g.i.cs文件,这个是系统自动根据xaml生成的。

[c-sharp] view plain copy print ?
  1. public void InitializeComponent()  
  2.     if (_contentLoaded) 
  3.     { 
  4.         return
  5.     } 
  6.     _contentLoaded = true
  7.     System.Windows.Application.LoadComponent(this,new System.Uri("/PhoneApp2;component/App.xaml", System.UriKind.Relative)); 

这里调用了Application.LoadComponent方法。这个方法的作用是 加载位于指定统一资源标识符 (URI) 处的 XAML 文件,并将其转换为由该 XAML 文件的根元素指定的对象的实例。这里就是生成App类的实例对象。用Reflector看没有代码,我们可以看看Silverlight中的实现:

首先获得了程序集相关的信息,然后获得Uri的资源,最后XcpImports是.NET部分调用core部分的接口,core部分是用C++编写的Core presentation framework,主要功能是XAML parser, UI Core, Inputs, DRM(digital rights management), Media, Deep Zoom等,多数是需直接与具体的操作系统API打交道的功能(可以参考:Silverlight CoreCLR结构浅析)。最终加载的是一个Application_LoadComponentNative的Native方法。

这个实例可以通过Application.Current属性获得。从下面的构造函数可以看出,_current被设置为this,zero指针指向了CreateObjectByTypeIndex方法根据nativeTypeIndex创建的App native对象。而在Current中获得对象实例时,先获得这个指针,然后EnsureManagedPeer方法使用Activator.CreateInstance(type) 创建了Application的实例。

以上是Silverlight和Windows Phone都有的下面看看Windows Phone中才有的部分。在构造函数中PhoneApplicationService.Current.UserIdleDetectionMode通过扩展服务设定了是否自动检测空闲,另外文件中包含了Launching,Activated,Deactivated,Closing四个方法,这4个方法是和Windows Phone执行模型有关的,后面会介绍。在构造函数中还有一个InitializePhoneApplication()方法,此方法实现如下。

此方法确保只会被执行一次,首先生成了一个PhoneApplicationFrame对象,保存到RootFrame中,然后绑定了Navigated事件。也就是在Frame导航之后执行CompleteInitializePhoneApplication。此方法中把RootFrame设置到RootVisual中。RootVisual用来获取或设置主要应用程序用户界面。只能从代码设置RootVisual 属性的值一次。

代码可以看到XcpImports.Application_SetVisualRoot方法,说明最终也还是交给了Core presentation framework去处理,开始显示我们程序的界面。

3 与Silverlight区别

[c-sharp] view plain copy print ?
  1. public App() 
  2.     this.Startup += this.Application_Startup; 
  3.     this.Exit += this.Application_Exit; 
  4.     this.UnhandledException +=this.Application_UnhandledException; 
  5.  
  6.     InitializeComponent(); 
  7.  
  8. private void Application_Startup(object sender, StartupEventArgs e) 
  9.     this.RootVisual = new MainPage(); 

以上是Silverlight4程序App类的方法。在Startup中设置了RootVisual为MainPage,那么在启动时就会显示MainPage.xaml页面。我们知道Frame和Page是Silverlight中用来导航的类。而在Windows Phone中则是使用PhoneApplicationFrame和PhoneApplicationPage,其中Frame是容器控制导航,而Page是在Frame中,用来显示用户界面。对于silverlight我们可以把代码修改为类似Windows Phone的代码:

我们同样定义一个RootFrame,生成一个Frame对象,设置到RootVisual上,然后通过Frame导航到MainPage.xaml。结果可以正常显示,但是如果我吧Navigate方法注销掉,结果空白,没有任何显示。但是我们看上面Windows Phone中并没有见到Navigate方法,但是还是显示了MainPage.xaml界面。

4 显示第一个界面

为什么没有调用Navigation还是显示了MainPage呢。不要忘记我们还有一个配置文件WMAppMaingest.xml。

其中Task节点是Application下的子节点,其中有一个属性是NavigationPage,MSDN解释是这里是指定启动时要导航到的Page页面。我们试试把这个修改为Page1.xaml。运行后发现,程序第一个界面显示为Page1.xaml。

[c-sharp] view plain copy print ?
  1. private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) 
  2.     if (RootVisual != RootFrame) 
  3.         //RootVisual = RootFrame; 
  4.         RootVisual = new MainPage(); 
  5.  
  6.     RootFrame.Navigated -= CompleteInitializePhoneApplication; 

然后我们继续做个修改,直接把RootVisual设置为MainPage。运行。。结果显示的是MainPage。所以对于RootVisual来说是设置第一个显示的元素。当我们传递给他的是一个Page或UserControl时,他直接显示此页面的内容;如果传递一个Frame时,如果设置了Source或则调用了Navigation,就会导航到指定的Page显示,如果不指定,Frame是一个容器,没有设置Page就不会显示任何东西,所以会出现上面空白的结果。对于WP7来说,程序从WMAppMaingest.xml中配置得到首页并设置到了Source中。

5 启动界面

在程序启动过程中,会执行Startup和Application_Launching方法。这里可能是一些耗时操作,如果时间超过1S,就推荐使用启动界面,改善用户体验。建议是不在在启动过程中做过多操作,因为启动时间超过10S程序会被直接终止掉。

启动画面图片名为SplashScreenImage.jpg,我们在启动的Startup或者Application_Launching中Thread.sleep(2000), 来模拟启动时的耗时操作。这样启动界面就可以显示。但是很奇怪,我在模拟器上根本不会显示,然后下载了微软的例子Splash Screen Sample运行后也没有显示出来。不知道什么原因。

在InitializePhoneApplication方法中并没有设置RootVisual,这是为了让启动画面在主界面准备呈现时仍旧有效。我们把RootVisual放到此方法最后也是可行的(和silverlight一样)。不过这里有点不明白,为什么用Navigated事件,是不是启动画面完成后会引发这个事件?

[c-sharp] view plain copy print ?
  1. private void Application_Startup(object sender, StartupEventArgs e) 
  2.     //this.RootVisual = new Page1(); 
  3.     RootFrame = new Frame(); 
  4.     RootFrame.Navigated += new System.Windows.Navigation.NavigatedEventHandler(RootFrame_Navigated); 
  5.     bool ret = RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 
  6.     //RootFrame.Source = new Uri("/Page1.xaml", UriKind.Relative); 
  7.  
  8. void RootFrame_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e) 
  9.     RootFrame.Navigated -= RootFrame_Navigated; 
  10.     this.RootVisual = RootFrame; 

比如我们吧一个silverlight修改成和WP7类似的,在Navigated中才设置VisualRoot,虽然Navigate方法返回了True,但是并没有触发此事件,而程序因为没有设置VisualRoot一直在等带状态没有任何显示。所以对于Windows Phone来说一定是触发了次事件,但是如何触发的目前不清楚。

另外启动时方法执行的顺序是App()-->Startup()-->Application_Launching();


四 总结

这一篇文章大概介绍了Windows Phone的框架结构和7.1版本的一些更新。然后稍微深入的了解了整个程序的启动过程涉及的一些内容以及和Silverligth之间的区别。总的来说就是宿主程序通过AppMainfest.xaml来获取程序的入口点,然后初始化调用Application.LoadComponent方法来生成App对象的实例,接下来初始化手机,定义一个Frame,,通过WMAppMaingest.xml文件获取启动时的Page页面设置到Frame,然后设置到RootVisual来显示此Page。

本文涉及代码较少,在上一篇文章的代码上修改既可。

 

Windows Phone开发(三)-- 导航原理分析       

        分类:            Windows Phone 1240人阅读 评论(3) 收藏 举报

前两篇文章中,我们的Demo代码都是基于页面切换的,而我们在Silverlight框架下开发的就是以XAML文件为基础的事件驱动程序。也就是说我们的程序会由一个或多个页面作成,这一点和Web程序很相似,所以页面间的切换就很重要。 这一篇文章就来将介绍Windows Phone平台上导航功能。


一 导航控件

从Silverlight3开始,提供了内置的导航框架,可以比较轻松的在 Silverlight Page之间进行切换,并且可以和浏览器的前进、后退按钮集成。在Silverlight 3之前的版本,Silverlight没有特定的导航框架,项目中页面之间的切换是通过修改RootVisual布局容器的内容而实现的
。利用SDK中提供了Frame和Page可以完成导航操纵。其中Frame是导航的框架,Page通过Frame加载显示并实现页面间的导航。

Windows Phone程序也是基于Sliverlight的Page Model进行导航,同时你也可以使用后退按钮进行后退操纵。WP上核心的导航容器控件为PhoneApplicationFrame,他可以容纳一个PhoneApplicationPage。我们可以创建多个页面,通过Frame来进行导航。

对于Windows Phone来说,只允许有一个Frame,一个Frame有一下特性:

  • 操作寄宿的Page页面的属性,比如orientation
  • 指定页面呈现的客户端区域
  • 为状态栏和程序栏保留空间
  • 监听obscured和unobscured事件

而对于Page来说,他会填充满Frame所在的空间。除此之外,程序中还有Status barApplication Bar,他们都能设置visible 属性;Window Phone也支持屏幕旋转,但是只有在转动设备时才能使之旋转,而不能通过编程的方式实现,因为orientation是只读属性;我们只能通过设置SupportedOrientations来完成;机器的后退按钮可以完成导航中的后退功能,也能关闭键盘,菜单,弹出窗体,同时还能关闭程序。

关于使用这两个空间导航,参见前两篇文章的例子。定义一个Frame,设置到VisualRoot。在配置文件中设置开始导航的页面。然后通过Frame或者NavigationService来进行导航。参考 :Frame and Page Navigation for Windows Phone


二 导航框架分析

我们还是接着上一篇文章程序启动来展开,看看Frame是如何去导航的。

1 导航框架的初始化

程序启动的的第一步是在App类中,我们创建了一个PhoneApplicationFrame实例。因为PhoneApplicationFrame是继承于Frame的,所以我们先看看Frame构造函数做了什么。

[c-sharp:nogutter] view plain copy print ?
  1. internal Frame() 
  2.     base.DefaultStyleKey =typeof(Frame); 
  3.     base.Loaded += new RoutedEventHandler(this.Frame_Loaded); 
  4.     this._hostInfo = new HostInfo(); 

构造函数中绑定一个Loaded事件和设置Host信息(如果看过SilverLight的源码,会发现和这的构造函数是有区别的)。接着就看看PhoneApplicationFrame的构造函数

这里面前面一部分是设置界面方向相关的一些内容,然后设置了可见区域,这里被省略了,这些都是上面说的Frame有的特性;接下来的工作很重要定义了一个NavigationService对象,保存在Frame的字段中。我们接着看看这个NavigationService对象构造函数

[c-sharp] view plain copy print ?
  1. internal NavigationService(PhoneApplicationFrame nav) 
  2.     this._navigationPendingLock =new object(); 
  3.     this._cacheRequiredPages = new Dictionary<string, PhoneApplicationPage>(); 
  4.     PerfUtil.BeginLogMarker(MarkerEvents.TH_INIT_NAVIGATIONSERVICE,"NavigationService started"); 
  5.     Guard.ArgumentNotNull(nav, "nav"); 
  6.     this._host = nav; 
  7.     HostFrame = nav; 
  8.     HostInfo info = new HostInfo(); 
  9.     this._shellPageManagerCallback =new ShellPageManagerCallback(); 
  10.     this._shellPageManagerCallback.OnCancelRequestEventHandler = (EventHandler) Delegate.Combine(this._shellPageManagerCallback.OnCancelRequestEventHandler,new EventHandler(this.ShellPageManager_OnCancelRequest)); 
  11.     this._shellPageManagerCallback.OnPageStackReactivatedEventHandler = (EventHandler<PageStackReactivatedEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnPageStackReactivatedEventHandler,new EventHandler<PageStackReactivatedEventArgs>(this.ShellPageManager_OnPageStackReactivated)); 
  12.     this._shellPageManagerCallback.OnResumePageRequestEventHandler = (EventHandler<ResumePageRequestEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnResumePageRequestEventHandler,new EventHandler<ResumePageRequestEventArgs>(this.ShellPageManager_OnResumePageRequest)); 
  13.     this._shellPageManagerCallback.OnInvokeReturningEventHandler = (EventHandler<InvokeReturningEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnInvokeReturningEventHandler,new EventHandler<InvokeReturningEventArgs>(this.OnInvokeReturning)); 
  14.     this._shellPageManager =new ShellPageManager(info.LastInstanceId, info.HostWnd,this._shellPageManagerCallback); 
  15.     this._shellPageManager.OnObscurityChangeEventHandler +=new EventHandler<ObscurityChangeEventArgs>(this._host.ShellPageManager_OnObscurityChange); 
  16.     this._shellPageManager.OnLockStateChangeEventHandler +=new EventHandler<LockStateChangeEventArgs>(this._host.ShellPageManager_OnLockStateChange); 
  17.     this._shellPageManager.PauseSupported =true
  18.     this._quirkShouldNotAllowBackgroundNavigation = QuirksMode.ShouldNotAllowBackgroundNavigation(); 
  19.     this._quirkShouldCallOnNavigatingFromPageForExternalNav = QuirksMode.ShouldCallOnNavigatingFromPageForExternalNavigations(); 
  20.     this._quirkShouldForceTextBindings = QuirksMode.ShouldForceTextBindings(); 
  21.     ChooserListener.Initialize(); 
  22.  
  23.   

这里主要是定义了一个_cacheRequiredPages和设置了host和Frame的值为传入的PhoneApplicationFrame对象。这时,PhoneApplicationFrame和NavigationService相互引用对方,和面绑定了一些事件。而PhoneApplicationFrame构造函数继续执行,Dispatcher.BeginInvoke(a)来执行Frame.Load方法。


我们仔细看看这个方法。

A. 执行了3个初始化的方法,InitializeJournal初始化历史记录相关的对象,InitializeNavigationCache初始化缓存相关的对象;
InitializeContentLoader初始化内容加载相关的对象

B. InitializeContentLoader中生成了一个PageResourceContentLoader对象,它是 Silverlight 框架中当前唯一的 INavigationContentLoader实现。他的作用就是用来加载要显示的xaml页面,这里是为Frame加载Page做准备,后面会继续介绍。

C. 然后我们看到一些列的Navigate方法。记得上一篇中我们谈到过为什么没有调用Navigate或者设置Source属性,还能显示MainPage.xaml的问题。是的,就在这里,在这里调用了Navigate方法。到底是那一个方法呢?我们在这里做个小实验。

[xhtml:nogutter] view plain copy print ?
  1. //NavigationPage="MainPage.xaml" 删除这个属性 
  2. <Tasks> 
  3.     <DefaultTask Name ="_default"> 
  4. </Tasks> 

修改WMAppMainfest.xml文件。这样就没有默认的页面了,我们在App中加入对Source的设置和Navigate调用。

[c-sharp:nogutter] view plain copy print ?
  1. RootFrame = new PhoneApplicationFrame(); 
  2. RootFrame.Source = new Uri("/Page1.xaml", UriKind.Relative); 
  3. RootFrame.Navigate(new Uri("/Page2.xaml", UriKind.Relative)); 
  4. RootFrame.Navigated += CompleteInitializePhoneApplication; 

运行程序发现程序正常显示了Page2.xaml页面,那么说明第一个条件判断的ApplyDeepLinks方法是从配置文件中获取NavigationPage的方法。具体如何获得的这里就不深究了,有兴趣自己看源码吧,通过调试发现是在Host对象的TaskPage属性中。这时Frame就导航到了指定的页面。此时也完成了导航框架的初始化。那么系统如何获得Page2的Uri的呢?这个后面解释。

[c-sharp:nogutter] view plain copy print ?
  1. private void InitializePhoneApplication() 
  2.     if (phoneApplicationInitialized) 
  3.         return
  4.  
  5.     RootFrame = new PhoneApplicationFrame(); 
  6.     RootFrame.Navigated += CompleteInitializePhoneApplication; 
  7.     RootFrame.NavigationFailed += RootFrame_NavigationFailed; 
  8.     phoneApplicationInitialized = true
  9.  
  10. private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) 
  11.     // Set the root visual to allow the application to render 
  12.     if (RootVisual != RootFrame) 
  13.         RootVisual = RootFrame; 
  14.  
  15.     // Remove this handler since it is no longer needed 
  16.     RootFrame.Navigated -= CompleteInitializePhoneApplication; 

我们在回头看看上一篇留下的疑问,为什么这里要绑定Navigated事件。现在应该明白了,在PhoneApplicationPhone的构造函数中,调用了Navigate方法导航到配置文件中的MainPage.xaml,当导航完成时,就吧Frame设置到VisualRoot,这时就能把Frame中的Page显示出来了。这里可能会有个疑问,在构造函数中调用Navigation,但在对象构造完才绑定Navigated事件,这能行吗? 在构造函数中调用的是下面的方法:

[c-sharp] view plain copy print ?
  1. Deployment.Current.Dispatcher.BeginInvoke(a); 

这方法实际是把操作发送UI线程执行,但是目前我们还在执行App的构造函数,所有只有构造好App对象UI才能处理这个Load操作,这是我个人看法不知道对不对,有待在研究。

2 Frame加载XAML文件

框架初始化完成后,执行Load方法中的Navigate,让Frame导航到指定页面,那么导航到这个页面是怎么加载这个页面的呢? 我们先看看Frame的Navigate方法到底是怎么执行的。

[c-sharp:nogutter] view plain copy print ?
  1. public bool Navigate(Uri source) 
  2.     if (this._loaded) 
  3.     { 
  4.         return this._navigationService.Navigate(source); 
  5.     } 
  6.     this._deferredNavigation = source; 
  7.     return true

我们发现塔实际是调用NavigationService的方法,实际上Frame和NavigationService中有很多同名的方法,实际都是在NavigationService类中实现的,他才是实际负责导航和加载页面的。这也和我们开始标注的Frame的4点特性吻合。

这里还有个_deferredNavigation字段,表示延迟导航。实际这个我们在上面的例子中留下的疑问。上面的例子我们删除掉配置文件中的MainPage.xaml,而增加了调用RootFrame.Navigate方法显示了Page2,这时并没有真的导航,应为Frame还没有初始化好,只是包Uri保存到了这个字段中。所以返回True不代表完成导航。等App构造完成,执行Load方法时没有获取配置文件,而_deferredNavigation字段又有值,就导航到了Page2。

[c-sharp:nogutter] view plain copy print ?
  1. public bool Navigate(Uri source) 
  2.     Action a = null
  3.     Uri uri = source; 
  4.     NavigationMode mode = NavigationMode.New; 
  5.     PerfUtil.BeginLogMarker(MarkerEvents.TH_PAGE_NAVIGATION, "Page navigation: " + ((uri == null) ?"" : uri.ToString())); 
  6.     try 
  7.     { 
  8.         JournalEntry journalEntry = new JournalEntry(uri.OriginalString, uri); 
  9.         this.Journal.AddHistoryPoint(journalEntry); 
  10.         return true
  11.     } 
  12.     catch (Exception exception) 
  13.     { 
  14.         if (this.RaiseNavigationFailed(uri, exception)) 
  15.         { 
  16.             throw
  17.         } 
  18.         return true
  19.     } 

以上代码是NavigationService.Navigate方法的实现,只保留了关键代码。其中将传入的Uri保存到了一个JournalEntry的实体类中,此类是表示后退或前进导航历史记录中的一个条目。我们把加载的Page都用AddHistoryPoint保存起来。

通过CreatePage创建了一个ShellPage对象,这是一个未公开的类型,所以没有资料,但是应该是和界面有关,这里不深入。然后注册了相关的事件,最后调用了NavigateTo方法。但是此方法看不到具体实现。根据这里情况推测应该是跳转到指定的ShellPage页面。这个时候会触发Journal对象的OnNavigateAwayEvent事件,执行绑定的方法:

[c-sharp] view plain copy print ?
  1. private void ShellPage_NavigatedAway(object sender, NavigateAwayEventArgs args) 
  2.     if (!args.IsExternal) 
  3.     { 
  4.         if ((args.Direction == Direction.Back) && (this._backStack.Count > 0)) 
  5.         { 
  6.             this.IsBusy =false
  7.             this._lastRemovedEntry =this._currentEntry; 
  8.             this._currentEntry =this._backStack.Pop(); 
  9.             NavigationMode back = NavigationMode.Back; 
  10.             this.UpdateObservables(this._currentEntry, back); 
  11.         } 
  12.     } 
  13.     else 
  14.     { 
  15.         this.OnNavigatedExternally("",new Uri("app://external/", UriKind.Absolute), (args.Direction == Direction.Back) ? NavigationMode.Back : NavigationMode.New); 
  16.     } 

此方法中包含一个名为UpdateObservables方法,此方法是

[c-sharp:nogutter] view plain copy print ?
  1. private void UpdateObservables(JournalEntry currentEntry, NavigationMode mode) 
  2.     bool isPaused = null == currentEntry.PageInstance; 
  3.     this.OnNavigated(currentEntry.Name, currentEntry.Source, mode, isPaused); 

OnNavigate触发了在NavigateService中InitializeJournal()注册的Journal的Navigated事件

[c-sharp:nogutter] view plain copy print ?
  1. this._journal.Navigated +=new EventHandler<JournalEventArgs>(this.Journal_Navigated) 

最后执行绑定的NativagetService中的Journal_Navigated方法:

实际上执行的就是NavigateCore_ContinueNavigation 方法,我们注意到 ._contentLoader.BeginLoad()方法,这里用到了我们前面Frame初始化说到的PageResourceContentLoader对象来加载指定XAML文件。

以上就是Frame加载XAML文件的全部过程。从Navigate到实际的NavigateCore_ContinueNavigation之间很负责,涉及了一些没有公开的类型。但实际意图很简单,就是对要导航的页面进行历史记录操作。

三 页面的显示

上面介绍了XAML被PageResourceContentLoader对象加载到Frame。那么XAML是如何显示到界面上的呢?

Silverlight 导航系统使用此类作为其默认的内容加载程序。此类是 INavigationContentLoader 的默认实现,且此类的实例是Frame.ContentLoader 属性的默认值。虽然您通常会加载Page 实例,Silverlight 导航系统要求导航目标应为 UserControl 实例。Page 类派生自UserControl 类,并提供附加导航支持

以上是MSDN对PageResourceContentLoader的解释,实际总用就是加载应用程序包(.xap 文件)中对应于给定 URI 的页。我们根据源码也看到Navigate方法最终是通过此对象来加载URI页面。

[c-sharp:nogutter] view plain copy print ?
  1. this._contentLoader.BeginLoad(this._currentNavigation.Uri,new AsyncCallback(this.ContentLoader_BeginLoad_Callback),this._currentNavigation); 

我们看看BeginLoad方法的实现

实际是调用了BeginLoad_OnUIThread方法,而里面又调用了GetLocalXaml方法来加载本地的XAML文件,而且获得了xclass属性指定的类型。最后使用Activator.CreateInstance创建了此XAML文件类对象的实例,保存到result中。

[c-sharp] view plain copy print ?
  1. private staticvoid BeginLoad_OnUIThread(AsyncCallback userCallback, PageResourceContentLoaderAsyncResult result) 
  2.     if (result.Exception !=null
  3.     { 
  4.         result.IsCompleted = true
  5.         userCallback(result); 
  6.     } 
  7.     else 
  8.     { 
  9.         try 
  10.         { 
  11.             string pagePathAndName = UriParsingHelper.InternalUriGetBaseValue(result.Uri); 
  12.             string localXaml = GetLocalXaml(pagePathAndName); 
  13.             if (string.IsNullOrEmpty(localXaml)) 
  14.             { 
  15.                 result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_NoXAMLWasFound,new object[] { pagePathAndName })); 
  16.             } 
  17.             else 
  18.             { 
  19.                 string xClass = GetXClass(localXaml); 
  20.                 if (string.IsNullOrEmpty(xClass)) 
  21.                 { 
  22.                     try 
  23.                     { 
  24.                         result.Content = XamlReader.Load(localXaml); 
  25.                     } 
  26.                     catch (Exception exception) 
  27.                     { 
  28.                         result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_XAMLWasUnloadable,new object[] { pagePathAndName }), exception); 
  29.                     } 
  30.                 } 
  31.                 else 
  32.                 { 
  33.                     Type typeFromAnyLoadedAssembly = GetTypeFromAnyLoadedAssembly(xClass); 
  34.                     if (typeFromAnyLoadedAssembly ==null
  35.                     { 
  36.                         result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_TheTypeSpecifiedInTheXClassCouldNotBeFound,new object[] { xClass, pagePathAndName })); 
  37.                     } 
  38.                     else 
  39.                     { 
  40.                         result.Content = Activator.CreateInstance(typeFromAnyLoadedAssembly); 
  41.                     } 
  42.                 } 
  43.             } 
  44.         } 
  45.         catch (Exception exception2) 
  46.         { 
  47.             result.Exception = exception2; 
  48.         } 
  49.         finally 
  50.         { 
  51.             result.IsCompleted = true
  52.             if (userCallback !=null
  53.             { 
  54.                 userCallback(result); 
  55.             } 
  56.         } 
  57.     } 

这里BeginLoad是用异步的方式来加载,避免UI线程等待。而加载完成后执行回调方法 ContentLoader_BeginLoad_Callback

加载完成后,从result中获得PhoneApplicationPage对象,也就是之前加载的XAML文件。然后设置了导航的Context内容,这个用来页面间传值的。接下来obj2.SetValue(NavigationServiceProperty,this)方法设置了NavigationServiceProperty依赖属性。这个属性和导航时的空引用异常有关。而CompleteNavigation方法用来完成导航。

[c-sharp:nogutter] view plain copy print ?
  1. this.RaiseNavigated(content, uriBeforeMapping, mode,null != existingContentPage, existingContentPage, newContentPage); 

其中一段代码RaiseNavigated出发了Frame的Navigated事件,也就是会执行我们在App中的CompleteInitializePhoneApplication方法。此时我们已经获得了要显示页面的Page对象。

我们知道在WPF中有对象树和可视数的概念,而在Silverlight上只有对象树。 RootVisual属性是设置显示的元素,把Frame设置为RootVisual,此时就会把Frame加载到对象树上,而前面提到过,Frame初始化时绑定了Loaded事件,这个事件此时会被执行。

[c-sharp:nogutter] view plain copy print ?
  1. private void Frame_Loaded(object sender, RoutedEventArgs e) 
  2.     this.Load(); 

实际上她执行的也是Load方法,这个方法已经在PhoneApplicationFrame的构造函数中执行过了,有_loaded字段标记,也就不会在执行了。

[c-sharp:nogutter] view plain copy print ?
  1. public UIElement RootVisual 
  2.     get 
  3.     { 
  4.         return (XcpImports.Application_GetVisualRoot()as UIElement); 
  5.     } 
  6.     [SecuritySafeCritical] 
  7.     set 
  8.     { 
  9.         XcpImports.CheckThread(); 
  10.         if ((value == null) || !XcpImports.DependencyObject_IsPointerValid(value)) 
  11.         { 
  12.             throw new InvalidOperationException(Resx.GetString("Application_InvalidRootVisual")); 
  13.         } 
  14.         XcpImports.Application_SetVisualRoot(value); 
  15.         this._rootVisual = value; 
  16.     } 

RootVisual的实现看不到,这里贴的是Silverlight的实现。这里用到了XcpImports,这个对象上一篇文章介绍过了,最终是使用Core presentation framework的方法设置了RootVisual,通过Frame得到当前的Page,这时Presentation Core会根据当前的对象树生成显卡可以识别的三角形,最终显示到屏幕上。这方面内容可以参考WPF Presentation


四 PhoneApplicationPage和NavigateService

前面介绍了Frame框架的初始化和第一个页面导航加载到显示的过程。我们已经知道,实际负责页面导航的是NavigateService这个类。但只有使用了Frame框架才能完成导航。而在PhoneApplicationPage中有一个NavigateService属性:

[c-sharp:nogutter] view plain copy print ?
  1. internal static NavigationService GetNavigationService(DependencyObject dependencyObject) 
  2.     Guard.ArgumentNotNull(dependencyObject, "dependencyObject"); 
  3.     return (dependencyObject.GetValue(NavigationServiceProperty)as NavigationService); 

他实际是返回了NavigationServiceProperty依赖属性。而这个属性上面提到过,是在初始化时加,BeginLoad加载了XAML后的回调函数中设置了此属性。所以我们可以在Page类中使用下面代码进行导航,也可以获得App类的RootFrame对象来导航。

[c-sharp] view plain copy print ?
  1. this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 
  2.  
  3. (Application.Current as App).RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 

另外我们在Page中还有OnNavigateTo和OnNavigateFrom等方法,来控制导航。这些内容将在下一篇文章中介绍。


五 Silverlight的不同之处

同Windows Phone导航不同,我们默认建立一个Silverlight的普通项目是没有添加Frame框架的。我们可以仿照Windows Phone修改App文件。

[c-sharp] view plain copy print ?
  1. private void Application_Startup(object sender, StartupEventArgs e) 
  2.     RootFrame = new Frame(); 
  3.     RootFrame.Navigate(new Uri("/MainPage.xaml", UriKind.Relative)); 
  4.     this.RootVisual = RootFrame; 

在程序启动的时候执行,整个过程和前面基本一样。相比Windows Phone,他们的构造函数简单了很多。

[c-sharp:nogutter] view plain copy print ?
  1. public Frame() 
  2.     base.DefaultStyleKey =typeof(Frame); 
  3.     base.Loaded += new RoutedEventHandler(this.Frame_Loaded); 
  4.     this._navigationService =new NavigationService(this); 
  5.  
  6. internal NavigationService(Frame nav) 
  7.     this._cacheRequiredPages = new Dictionary<string, Page>(); 
  8.     Guard.ArgumentNotNull(nav, "nav"); 
  9.     this._host = nav; 

Frame中注册了Loaded事件,和WP不同的是没有在Frame的构造函数中就执行。那么他只能在要显示的时候运行,也就是我们设置了RootVisual属性后才能执行Navigate方法。另外没有配置文件设置导航初始画面,所以我们也必须在这里调用Navigate方法或设置Source属性。

当页面开始呈现,加载了Frame到对象树后触发了Loaded时间, 最终调用了NavigateService.Navigate方法。

[c-sharp:nogutter] view plain copy print ?
  1. public bool Navigate(Uri source) 
  2.     return this.NavigateCore(source, NavigationMode.New,false, false); 

这里也和Windows Phone不同,NavigateService中直接调用了NavigateCore方法。此方法实现和Windows Phone一样,此方法中有用的是NavigateCore_StartNavigation,最终是通过的this._contentLoader.BeginLoad来完成的。然后显示核心把加载的页面显示到屏幕上。

以上是Silverlight的过程,相比Windows Phone简单一些。因为Loaded事件触发的时间不同,所以WP会自动根据配置文件设置在App对象建立后就导航到指定页面;而SL是在加载对象准备呈现时触发,而且没有配置文件,所以必须手动设置一下。这也就解释了我们前一篇的疑问。也是为什么SL模仿WP来创建框架,却不能在Navigated时间中才设置RootVisual。因为此事件需要设置运行了Loaded方法才能触发。


六 总结

本文主要对导航框架源代码进行了简单的解析,了解了程序启动后导航到第一个界面的过程。首先创建了PhoneApplication对象,在此对象构建完成后,调用了Frame的Loaded方法来导航到配置文件指定的URI;然后加载此XAML文件,然后触发Frame的Navigated方法设置RootVisual,此属性会调用显示框架的Native方法,将对象树呈现到屏幕上。

上一篇文章介绍了Windows Phone的开发环境和一个简单的Windows Phone程序的演示和结构,这一篇文章要深入一点,介绍Windows Phone的框架结构和程序启动的过程。


一 Windows Phone 框架结构

在进行Windows Phone开发之前有必要了解一下整个全新平台的结构。对于Windows Phone平台来说,区别于之前的Windows Mobile平台最大的区别是他运行环境完全基于.NET框架,他只支持托管代码进行开发。

Windows Phone是基于.NET Compact Framework框架,所以精简了很多桌面版的功能。但是还是提供了所需的基本功能。如上图中的最下层的蓝色部分,就是.NET CF提供的BCL类库。而在他的上层就是Application Object。这一层都是基于.NET CF框架。其中包含了两种开发框架:

  • Silverlight Framework :以 Silverlight Framework 为基础的 Windows Phone 7 应用程序是由一堆的 Page (继承自 PhoneApplicationPage 类别的衍生类别) 组成的,每一个 Page 是一个扩展名为 .XAML 的文件,代表一个操作画面,程序设计师可以利用 Visual Studio 2010 Express for Windows Phone 或是 Expression Blend for Windows Phone 来设计 Page 的用户接口。WP7.0是基于Silverlight3.0,WP7.1则是基于Silverlight4。关于Silverlight for Windows Phone和Silverlight的区别(点击打开)
  • XNA Framework:主要的用途在支持开发游戏程序,提供 2D/3D 的动画,音效,及各种游戏相关的功能,协助有志于开发游戏程序的企业或个人发展 Windows Phone、Xbox 360、Zune 播放器、以及 Windows 7 平台的游戏程序。

毕竟移动手机平台还有自己特殊的一部分功能,所以在两个框架的上层Windows Phone特有的一些功能,比如相机,Windows Phone控件,感应器,多点触控屏幕,Launcher,Chooser等等来提供相应功能。

所以具体到实际开发,我们有2套开发框架进行选择,对于大多应用程序我们可以选择Silverlight进行开发,而对于游戏可以选择XNA。但是WP也提供部分功能相互调用的功能。而对以WP7.1来说可以建立一个silverlight和XNA集成的开发环境(点击查看)。

对于Windows Phone开发来说,不仅可以使用.NET CF和silverlight或XNA框架提供的这些功能,还能使用微软基于云服务的一些功能。比如Notification,Social, Location,Map ,Azure等服务。下图显示了Windows Phone上支持的的运行时框架和开发工具。下面的显示提供的运服务以及注册开发者和发布程序。他们组成了对Windows Phone开发的软件支持。

而对于硬件来说,微软统一了硬件平台,目前要求的硬件包括 800 x 480 或 480 x 320 屏幕分辨率,支持多点触控,内建 A-GPS 卫星定位系统,G-Sensor (Accelerometer),电子罗盘传感器,光源传感器,以及不需要直接接触就可以侦测到附近物体的 Proximity Sensor。最少 500 万画素的数字相机,内建 Codec 与多媒体影音播放功能,最少 256MB 的 RAM 与最少 8GB 的闪存,GPU (图形处理器),ARMv7 Cortex/Scorpion 或更佳的处理器,以及 Back、Start、Search 三个硬件按键。目前HTC和三星已经有第一代的WP7手机上市,可以机型和ROM信息可以参见:智机网

关于Windows Phone Plateform的详细介绍参见MSND(点击打开)


二 Windows Phone 7.1 新功能

目前最新版本的系统是Windows Phone 7.1Beta,相对于Windows Phone他有了比较大的改动,弄清楚每一版本的功能,可以更好的了解系统和选择开发的版本。

  • 粘贴和复制功能:1月的更新增加了粘贴复制功能
  • 执行模型快速切换:实现了新的执行模型,增加了休眠状态,使得应用程序可以快速切换。
  • 后台程序代理:增加后台代理功能,可以在程序没有在前台运行的情况下在进行提示,另外还有后台播放和后台下载功能
  • 感应器APIs:公开了感应器的API,以便程序可以对硬件进行访问。
  • 支持Socket:增加了Socket的支持,可以开发基于TCP和UDP的程序,非常有用。
  • 相机数据:增加了对相机原始数据访问的功能
  • 推送消息:增加了推送消息的功能,更加稳定
  • Silverlight和XNA集成:可以在同一个Page下使用silverlight和XNA进行开发
  • 程序配置文件:提供了对程序和游戏进行配置的功能
  • WebBrowser :浏览器控件可以支持IE9,并优化了HTML5的表现
  • 本地数据库:增加了对数据库的支持,可以使用LINQ to SQL进行操作
  • Launchers and Choosers  : 提供了新的功能,如Bing Map,Address等
  • 全球化和本地化:支持包括中文在内的16种语言

具体Windows Phone 7.1新的改进(点击打开)

关于程序的兼容性,简单说WP7.0开发的程序可以在OS7.0和7.1上运行,而7.1开放的程序在7.0上可能会失败。具体兼容性问题参见这里


三 Window Phone 程序启动过程

在我们上一篇文章中,我们写了一个简单的Demo程序。程序启动后显示的是MainPage.xaml页面。那么程序是如何成功启动的呢?

1 xap文件

打开程序后,我们对程序进行编译,然后我们可以在/PhoneApp2/Bin/Debug目录下看到名字为PhoneApp.xap文件,这个就是我们程序的包。部署到手机上就能运行。这其实是一个压缩文件,我们可以用7-ZIP来解压。

其中包含了程序使用的3个JPG文件,PhoneApp2.dll就是我们程序功能的DLL文件,而AppMainfest.xaml和WMAppMaingest.xml文件则提供了应用程序的一些信息。

xap包结构

上图是一个silverlight的XAP包的结构,WP基于silverlight开发的程序结构是相似的,不过多一个WMAppMaingest.xml文件,用来配置手机相关的一些信息,这个在上一篇有介绍过。首先来看看AppManifest.xaml 文件,它标识打包的程序集和应用程序入口点。

这个和上一篇文章相比,已经自动生成了一些内容。我们来简单看一下:

  • EntryPointAssembly : 定义了程序的入口程序集的名字,也就是开始运行时加载的程序集,这里就是我们的PhoneApp2.dll
  • EntryPointType : 表示了程序的入口点,这里指定了项目中的App对象
  • RuntimeVersion : 表示当前silverlight运行的版本,这里可以看到,WP7.1是基于SL4。
  • Deployment.Parts : 此节点中包含了xap包中的程序集列表。

关于xap包结构和此文件结构具体信息,详细可以参考silverlight的应用程序结构。对于WMAppMaingest.xml文件没和编译前没有变化,详细结构上一篇文章中有链接介绍。

2 App.xaml和App.xaml.cs文件

AppMaingest.axml中定义了程序的入口点,其中指定了App对象。通过查看这2个文件,我们发现App.xaml.cs文件中定义了App类,此类继承与Application类,而App.xaml文件的根节点Application指定了x:Class="PhoneApp2.App",这个表示把xaml文件中的内容同xaml.cs类中内容合并。这里所有内容编译后会合并到App类中。

当使用 Silverlight 的托管 API 创建应用程序时,必须创建一个从 Application 派生的类。Application 类提供应用程序通常要求的若干服务。它主要表示应用程序代码在 Silverlight 插件生命周期中的入口点。Application类的以下功能:

  • 应用程序生存期管理
  • 应用程序资源文件加载
  • 未经处理的异常处理
  • 扩展性

我们看到App.xaml文件中定义了<Application.Resources>节电,这个可以用来定义一些全局的资源别如string,style,笔刷等;<Application.ApplicationLifetimeObjects>则是提供了对Application的扩展性,这里提供了PhoneApplivationService的扩展服务,从名字我们就能知道这个是有关Phone的扩展,因为默认的Silverlight是没有的。这个是位于Micresoft.Phone.dll中,这儿服务提供了4个与手机有关的方法。关于扩展性可以参见silvrlight的扩展服务

[c-sharp] view plain copy print ?
  1. public PhoneApplicationFrame RootFrame {get; privateset; } 
  2.  
  3. public App() 
  4.     UnhandledException += Application_UnhandledException; 
  5.     InitializeComponent(); 
  6.     InitializePhoneApplication(); 
  7.     PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled; 

以上是App.xaml.cs文件中的代码,首先定义了一个PhoneApplicationFrame属性RootFrame,在构造函数中定义了UnhandledException来处理未处理异常,这是Application的一个功能;InitializeComponent是初始化方法,点击F12导航到App.g.i.cs文件,这个是系统自动根据xaml生成的。

[c-sharp] view plain copy print ?
  1. public void InitializeComponent()  
  2.     if (_contentLoaded) 
  3.     { 
  4.         return
  5.     } 
  6.     _contentLoaded = true
  7.     System.Windows.Application.LoadComponent(this,new System.Uri("/PhoneApp2;component/App.xaml", System.UriKind.Relative)); 

这里调用了Application.LoadComponent方法。这个方法的作用是 加载位于指定统一资源标识符 (URI) 处的 XAML 文件,并将其转换为由该 XAML 文件的根元素指定的对象的实例。这里就是生成App类的实例对象。用Reflector看没有代码,我们可以看看Silverlight中的实现:

首先获得了程序集相关的信息,然后获得Uri的资源,最后XcpImports是.NET部分调用core部分的接口,core部分是用C++编写的Core presentation framework,主要功能是XAML parser, UI Core, Inputs, DRM(digital rights management), Media, Deep Zoom等,多数是需直接与具体的操作系统API打交道的功能(可以参考:Silverlight CoreCLR结构浅析)。最终加载的是一个Application_LoadComponentNative的Native方法。

这个实例可以通过Application.Current属性获得。从下面的构造函数可以看出,_current被设置为this,zero指针指向了CreateObjectByTypeIndex方法根据nativeTypeIndex创建的App native对象。而在Current中获得对象实例时,先获得这个指针,然后EnsureManagedPeer方法使用Activator.CreateInstance(type) 创建了Application的实例。

以上是Silverlight和Windows Phone都有的下面看看Windows Phone中才有的部分。在构造函数中PhoneApplicationService.Current.UserIdleDetectionMode通过扩展服务设定了是否自动检测空闲,另外文件中包含了Launching,Activated,Deactivated,Closing四个方法,这4个方法是和Windows Phone执行模型有关的,后面会介绍。在构造函数中还有一个InitializePhoneApplication()方法,此方法实现如下。

此方法确保只会被执行一次,首先生成了一个PhoneApplicationFrame对象,保存到RootFrame中,然后绑定了Navigated事件。也就是在Frame导航之后执行CompleteInitializePhoneApplication。此方法中把RootFrame设置到RootVisual中。RootVisual用来获取或设置主要应用程序用户界面。只能从代码设置RootVisual 属性的值一次。

代码可以看到XcpImports.Application_SetVisualRoot方法,说明最终也还是交给了Core presentation framework去处理,开始显示我们程序的界面。

3 与Silverlight区别

[c-sharp] view plain copy print ?
  1. public App() 
  2.     this.Startup += this.Application_Startup; 
  3.     this.Exit += this.Application_Exit; 
  4.     this.UnhandledException +=this.Application_UnhandledException; 
  5.  
  6.     InitializeComponent(); 
  7.  
  8. private void Application_Startup(object sender, StartupEventArgs e) 
  9.     this.RootVisual = new MainPage(); 

以上是Silverlight4程序App类的方法。在Startup中设置了RootVisual为MainPage,那么在启动时就会显示MainPage.xaml页面。我们知道Frame和Page是Silverlight中用来导航的类。而在Windows Phone中则是使用PhoneApplicationFrame和PhoneApplicationPage,其中Frame是容器控制导航,而Page是在Frame中,用来显示用户界面。对于silverlight我们可以把代码修改为类似Windows Phone的代码:

我们同样定义一个RootFrame,生成一个Frame对象,设置到RootVisual上,然后通过Frame导航到MainPage.xaml。结果可以正常显示,但是如果我吧Navigate方法注销掉,结果空白,没有任何显示。但是我们看上面Windows Phone中并没有见到Navigate方法,但是还是显示了MainPage.xaml界面。

4 显示第一个界面

为什么没有调用Navigation还是显示了MainPage呢。不要忘记我们还有一个配置文件WMAppMaingest.xml。

其中Task节点是Application下的子节点,其中有一个属性是NavigationPage,MSDN解释是这里是指定启动时要导航到的Page页面。我们试试把这个修改为Page1.xaml。运行后发现,程序第一个界面显示为Page1.xaml。

[c-sharp] view plain copy print ?
  1. private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) 
  2.     if (RootVisual != RootFrame) 
  3.         //RootVisual = RootFrame; 
  4.         RootVisual = new MainPage(); 
  5.  
  6.     RootFrame.Navigated -= CompleteInitializePhoneApplication; 

然后我们继续做个修改,直接把RootVisual设置为MainPage。运行。。结果显示的是MainPage。所以对于RootVisual来说是设置第一个显示的元素。当我们传递给他的是一个Page或UserControl时,他直接显示此页面的内容;如果传递一个Frame时,如果设置了Source或则调用了Navigation,就会导航到指定的Page显示,如果不指定,Frame是一个容器,没有设置Page就不会显示任何东西,所以会出现上面空白的结果。对于WP7来说,程序从WMAppMaingest.xml中配置得到首页并设置到了Source中。

5 启动界面

在程序启动过程中,会执行Startup和Application_Launching方法。这里可能是一些耗时操作,如果时间超过1S,就推荐使用启动界面,改善用户体验。建议是不在在启动过程中做过多操作,因为启动时间超过10S程序会被直接终止掉。

启动画面图片名为SplashScreenImage.jpg,我们在启动的Startup或者Application_Launching中Thread.sleep(2000), 来模拟启动时的耗时操作。这样启动界面就可以显示。但是很奇怪,我在模拟器上根本不会显示,然后下载了微软的例子Splash Screen Sample运行后也没有显示出来。不知道什么原因。

在InitializePhoneApplication方法中并没有设置RootVisual,这是为了让启动画面在主界面准备呈现时仍旧有效。我们把RootVisual放到此方法最后也是可行的(和silverlight一样)。不过这里有点不明白,为什么用Navigated事件,是不是启动画面完成后会引发这个事件?

[c-sharp] view plain copy print ?
  1. private void Application_Startup(object sender, StartupEventArgs e) 
  2.     //this.RootVisual = new Page1(); 
  3.     RootFrame = new Frame(); 
  4.     RootFrame.Navigated += new System.Windows.Navigation.NavigatedEventHandler(RootFrame_Navigated); 
  5.     bool ret = RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 
  6.     //RootFrame.Source = new Uri("/Page1.xaml", UriKind.Relative); 
  7.  
  8. void RootFrame_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e) 
  9.     RootFrame.Navigated -= RootFrame_Navigated; 
  10.     this.RootVisual = RootFrame; 

比如我们吧一个silverlight修改成和WP7类似的,在Navigated中才设置VisualRoot,虽然Navigate方法返回了True,但是并没有触发此事件,而程序因为没有设置VisualRoot一直在等带状态没有任何显示。所以对于Windows Phone来说一定是触发了次事件,但是如何触发的目前不清楚。

另外启动时方法执行的顺序是App()-->Startup()-->Application_Launching();


四 总结

这一篇文章大概介绍了Windows Phone的框架结构和7.1版本的一些更新。然后稍微深入的了解了整个程序的启动过程涉及的一些内容以及和Silverligth之间的区别。总的来说就是宿主程序通过AppMainfest.xaml来获取程序的入口点,然后初始化调用Application.LoadComponent方法来生成App对象的实例,接下来初始化手机,定义一个Frame,,通过WMAppMaingest.xml文件获取启动时的Page页面设置到Frame,然后设置到RootVisual来显示此Page。

本文涉及代码较少,在上一篇文章的代码上修改既可。

前两篇文章中,我们的Demo代码都是基于页面切换的,而我们在Silverlight框架下开发的就是以XAML文件为基础的事件驱动程序。也就是说我们的程序会由一个或多个页面作成,这一点和Web程序很相似,所以页面间的切换就很重要。 这一篇文章就来将介绍Windows Phone平台上导航功能。


一 导航控件

从Silverlight3开始,提供了内置的导航框架,可以比较轻松的在 Silverlight Page之间进行切换,并且可以和浏览器的前进、后退按钮集成。在Silverlight 3之前的版本,Silverlight没有特定的导航框架,项目中页面之间的切换是通过修改RootVisual布局容器的内容而实现的
。利用SDK中提供了Frame和Page可以完成导航操纵。其中Frame是导航的框架,Page通过Frame加载显示并实现页面间的导航。

Windows Phone程序也是基于Sliverlight的Page Model进行导航,同时你也可以使用后退按钮进行后退操纵。WP上核心的导航容器控件为PhoneApplicationFrame,他可以容纳一个PhoneApplicationPage。我们可以创建多个页面,通过Frame来进行导航。

对于Windows Phone来说,只允许有一个Frame,一个Frame有一下特性:

  • 操作寄宿的Page页面的属性,比如orientation
  • 指定页面呈现的客户端区域
  • 为状态栏和程序栏保留空间
  • 监听obscured和unobscured事件

而对于Page来说,他会填充满Frame所在的空间。除此之外,程序中还有Status barApplication Bar,他们都能设置visible 属性;Window Phone也支持屏幕旋转,但是只有在转动设备时才能使之旋转,而不能通过编程的方式实现,因为orientation是只读属性;我们只能通过设置SupportedOrientations来完成;机器的后退按钮可以完成导航中的后退功能,也能关闭键盘,菜单,弹出窗体,同时还能关闭程序。

关于使用这两个空间导航,参见前两篇文章的例子。定义一个Frame,设置到VisualRoot。在配置文件中设置开始导航的页面。然后通过Frame或者NavigationService来进行导航。参考 :Frame and Page Navigation for Windows Phone


二 导航框架分析

我们还是接着上一篇文章程序启动来展开,看看Frame是如何去导航的。

1 导航框架的初始化

程序启动的的第一步是在App类中,我们创建了一个PhoneApplicationFrame实例。因为PhoneApplicationFrame是继承于Frame的,所以我们先看看Frame构造函数做了什么。

[c-sharp:nogutter] view plain copy print ?
  1. internal Frame() 
  2.     base.DefaultStyleKey =typeof(Frame); 
  3.     base.Loaded += new RoutedEventHandler(this.Frame_Loaded); 
  4.     this._hostInfo = new HostInfo(); 

构造函数中绑定一个Loaded事件和设置Host信息(如果看过SilverLight的源码,会发现和这的构造函数是有区别的)。接着就看看PhoneApplicationFrame的构造函数

这里面前面一部分是设置界面方向相关的一些内容,然后设置了可见区域,这里被省略了,这些都是上面说的Frame有的特性;接下来的工作很重要定义了一个NavigationService对象,保存在Frame的字段中。我们接着看看这个NavigationService对象构造函数

[c-sharp] view plain copy print ?
  1. internal NavigationService(PhoneApplicationFrame nav) 
  2.     this._navigationPendingLock =new object(); 
  3.     this._cacheRequiredPages = new Dictionary<string, PhoneApplicationPage>(); 
  4.     PerfUtil.BeginLogMarker(MarkerEvents.TH_INIT_NAVIGATIONSERVICE,"NavigationService started"); 
  5.     Guard.ArgumentNotNull(nav, "nav"); 
  6.     this._host = nav; 
  7.     HostFrame = nav; 
  8.     HostInfo info = new HostInfo(); 
  9.     this._shellPageManagerCallback =new ShellPageManagerCallback(); 
  10.     this._shellPageManagerCallback.OnCancelRequestEventHandler = (EventHandler) Delegate.Combine(this._shellPageManagerCallback.OnCancelRequestEventHandler,new EventHandler(this.ShellPageManager_OnCancelRequest)); 
  11.     this._shellPageManagerCallback.OnPageStackReactivatedEventHandler = (EventHandler<PageStackReactivatedEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnPageStackReactivatedEventHandler,new EventHandler<PageStackReactivatedEventArgs>(this.ShellPageManager_OnPageStackReactivated)); 
  12.     this._shellPageManagerCallback.OnResumePageRequestEventHandler = (EventHandler<ResumePageRequestEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnResumePageRequestEventHandler,new EventHandler<ResumePageRequestEventArgs>(this.ShellPageManager_OnResumePageRequest)); 
  13.     this._shellPageManagerCallback.OnInvokeReturningEventHandler = (EventHandler<InvokeReturningEventArgs>) Delegate.Combine(this._shellPageManagerCallback.OnInvokeReturningEventHandler,new EventHandler<InvokeReturningEventArgs>(this.OnInvokeReturning)); 
  14.     this._shellPageManager =new ShellPageManager(info.LastInstanceId, info.HostWnd,this._shellPageManagerCallback); 
  15.     this._shellPageManager.OnObscurityChangeEventHandler +=new EventHandler<ObscurityChangeEventArgs>(this._host.ShellPageManager_OnObscurityChange); 
  16.     this._shellPageManager.OnLockStateChangeEventHandler +=new EventHandler<LockStateChangeEventArgs>(this._host.ShellPageManager_OnLockStateChange); 
  17.     this._shellPageManager.PauseSupported =true
  18.     this._quirkShouldNotAllowBackgroundNavigation = QuirksMode.ShouldNotAllowBackgroundNavigation(); 
  19.     this._quirkShouldCallOnNavigatingFromPageForExternalNav = QuirksMode.ShouldCallOnNavigatingFromPageForExternalNavigations(); 
  20.     this._quirkShouldForceTextBindings = QuirksMode.ShouldForceTextBindings(); 
  21.     ChooserListener.Initialize(); 
  22.  
  23.   

这里主要是定义了一个_cacheRequiredPages和设置了host和Frame的值为传入的PhoneApplicationFrame对象。这时,PhoneApplicationFrame和NavigationService相互引用对方,和面绑定了一些事件。而PhoneApplicationFrame构造函数继续执行,Dispatcher.BeginInvoke(a)来执行Frame.Load方法。


我们仔细看看这个方法。

A. 执行了3个初始化的方法,InitializeJournal初始化历史记录相关的对象,InitializeNavigationCache初始化缓存相关的对象;
InitializeContentLoader初始化内容加载相关的对象

B. InitializeContentLoader中生成了一个PageResourceContentLoader对象,它是 Silverlight 框架中当前唯一的 INavigationContentLoader实现。他的作用就是用来加载要显示的xaml页面,这里是为Frame加载Page做准备,后面会继续介绍。

C. 然后我们看到一些列的Navigate方法。记得上一篇中我们谈到过为什么没有调用Navigate或者设置Source属性,还能显示MainPage.xaml的问题。是的,就在这里,在这里调用了Navigate方法。到底是那一个方法呢?我们在这里做个小实验。

[xhtml:nogutter] view plain copy print ?
  1. //NavigationPage="MainPage.xaml" 删除这个属性 
  2. <Tasks> 
  3.     <DefaultTask Name ="_default"> 
  4. </Tasks> 

修改WMAppMainfest.xml文件。这样就没有默认的页面了,我们在App中加入对Source的设置和Navigate调用。

[c-sharp:nogutter] view plain copy print ?
  1. RootFrame = new PhoneApplicationFrame(); 
  2. RootFrame.Source = new Uri("/Page1.xaml", UriKind.Relative); 
  3. RootFrame.Navigate(new Uri("/Page2.xaml", UriKind.Relative)); 
  4. RootFrame.Navigated += CompleteInitializePhoneApplication; 

运行程序发现程序正常显示了Page2.xaml页面,那么说明第一个条件判断的ApplyDeepLinks方法是从配置文件中获取NavigationPage的方法。具体如何获得的这里就不深究了,有兴趣自己看源码吧,通过调试发现是在Host对象的TaskPage属性中。这时Frame就导航到了指定的页面。此时也完成了导航框架的初始化。那么系统如何获得Page2的Uri的呢?这个后面解释。

[c-sharp:nogutter] view plain copy print ?
  1. private void InitializePhoneApplication() 
  2.     if (phoneApplicationInitialized) 
  3.         return
  4.  
  5.     RootFrame = new PhoneApplicationFrame(); 
  6.     RootFrame.Navigated += CompleteInitializePhoneApplication; 
  7.     RootFrame.NavigationFailed += RootFrame_NavigationFailed; 
  8.     phoneApplicationInitialized = true
  9.  
  10. private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) 
  11.     // Set the root visual to allow the application to render 
  12.     if (RootVisual != RootFrame) 
  13.         RootVisual = RootFrame; 
  14.  
  15.     // Remove this handler since it is no longer needed 
  16.     RootFrame.Navigated -= CompleteInitializePhoneApplication; 

我们在回头看看上一篇留下的疑问,为什么这里要绑定Navigated事件。现在应该明白了,在PhoneApplicationPhone的构造函数中,调用了Navigate方法导航到配置文件中的MainPage.xaml,当导航完成时,就吧Frame设置到VisualRoot,这时就能把Frame中的Page显示出来了。这里可能会有个疑问,在构造函数中调用Navigation,但在对象构造完才绑定Navigated事件,这能行吗? 在构造函数中调用的是下面的方法:

[c-sharp] view plain copy print ?
  1. Deployment.Current.Dispatcher.BeginInvoke(a); 

这方法实际是把操作发送UI线程执行,但是目前我们还在执行App的构造函数,所有只有构造好App对象UI才能处理这个Load操作,这是我个人看法不知道对不对,有待在研究。

2 Frame加载XAML文件

框架初始化完成后,执行Load方法中的Navigate,让Frame导航到指定页面,那么导航到这个页面是怎么加载这个页面的呢? 我们先看看Frame的Navigate方法到底是怎么执行的。

[c-sharp:nogutter] view plain copy print ?
  1. public bool Navigate(Uri source) 
  2.     if (this._loaded) 
  3.     { 
  4.         return this._navigationService.Navigate(source); 
  5.     } 
  6.     this._deferredNavigation = source; 
  7.     return true

我们发现塔实际是调用NavigationService的方法,实际上Frame和NavigationService中有很多同名的方法,实际都是在NavigationService类中实现的,他才是实际负责导航和加载页面的。这也和我们开始标注的Frame的4点特性吻合。

这里还有个_deferredNavigation字段,表示延迟导航。实际这个我们在上面的例子中留下的疑问。上面的例子我们删除掉配置文件中的MainPage.xaml,而增加了调用RootFrame.Navigate方法显示了Page2,这时并没有真的导航,应为Frame还没有初始化好,只是包Uri保存到了这个字段中。所以返回True不代表完成导航。等App构造完成,执行Load方法时没有获取配置文件,而_deferredNavigation字段又有值,就导航到了Page2。

[c-sharp:nogutter] view plain copy print ?
  1. public bool Navigate(Uri source) 
  2.     Action a = null
  3.     Uri uri = source; 
  4.     NavigationMode mode = NavigationMode.New; 
  5.     PerfUtil.BeginLogMarker(MarkerEvents.TH_PAGE_NAVIGATION, "Page navigation: " + ((uri == null) ?"" : uri.ToString())); 
  6.     try 
  7.     { 
  8.         JournalEntry journalEntry = new JournalEntry(uri.OriginalString, uri); 
  9.         this.Journal.AddHistoryPoint(journalEntry); 
  10.         return true
  11.     } 
  12.     catch (Exception exception) 
  13.     { 
  14.         if (this.RaiseNavigationFailed(uri, exception)) 
  15.         { 
  16.             throw
  17.         } 
  18.         return true
  19.     } 

以上代码是NavigationService.Navigate方法的实现,只保留了关键代码。其中将传入的Uri保存到了一个JournalEntry的实体类中,此类是表示后退或前进导航历史记录中的一个条目。我们把加载的Page都用AddHistoryPoint保存起来。

通过CreatePage创建了一个ShellPage对象,这是一个未公开的类型,所以没有资料,但是应该是和界面有关,这里不深入。然后注册了相关的事件,最后调用了NavigateTo方法。但是此方法看不到具体实现。根据这里情况推测应该是跳转到指定的ShellPage页面。这个时候会触发Journal对象的OnNavigateAwayEvent事件,执行绑定的方法:

[c-sharp] view plain copy print ?
  1. private void ShellPage_NavigatedAway(object sender, NavigateAwayEventArgs args) 
  2.     if (!args.IsExternal) 
  3.     { 
  4.         if ((args.Direction == Direction.Back) && (this._backStack.Count > 0)) 
  5.         { 
  6.             this.IsBusy =false
  7.             this._lastRemovedEntry =this._currentEntry; 
  8.             this._currentEntry =this._backStack.Pop(); 
  9.             NavigationMode back = NavigationMode.Back; 
  10.             this.UpdateObservables(this._currentEntry, back); 
  11.         } 
  12.     } 
  13.     else 
  14.     { 
  15.         this.OnNavigatedExternally("",new Uri("app://external/", UriKind.Absolute), (args.Direction == Direction.Back) ? NavigationMode.Back : NavigationMode.New); 
  16.     } 

此方法中包含一个名为UpdateObservables方法,此方法是

[c-sharp:nogutter] view plain copy print ?
  1. private void UpdateObservables(JournalEntry currentEntry, NavigationMode mode) 
  2.     bool isPaused = null == currentEntry.PageInstance; 
  3.     this.OnNavigated(currentEntry.Name, currentEntry.Source, mode, isPaused); 

OnNavigate触发了在NavigateService中InitializeJournal()注册的Journal的Navigated事件

[c-sharp:nogutter] view plain copy print ?
  1. this._journal.Navigated +=new EventHandler<JournalEventArgs>(this.Journal_Navigated) 

最后执行绑定的NativagetService中的Journal_Navigated方法:

实际上执行的就是NavigateCore_ContinueNavigation 方法,我们注意到 ._contentLoader.BeginLoad()方法,这里用到了我们前面Frame初始化说到的PageResourceContentLoader对象来加载指定XAML文件。

以上就是Frame加载XAML文件的全部过程。从Navigate到实际的NavigateCore_ContinueNavigation之间很负责,涉及了一些没有公开的类型。但实际意图很简单,就是对要导航的页面进行历史记录操作。

三 页面的显示

上面介绍了XAML被PageResourceContentLoader对象加载到Frame。那么XAML是如何显示到界面上的呢?

Silverlight 导航系统使用此类作为其默认的内容加载程序。此类是 INavigationContentLoader 的默认实现,且此类的实例是Frame.ContentLoader 属性的默认值。虽然您通常会加载Page 实例,Silverlight 导航系统要求导航目标应为 UserControl 实例。Page 类派生自UserControl 类,并提供附加导航支持

以上是MSDN对PageResourceContentLoader的解释,实际总用就是加载应用程序包(.xap 文件)中对应于给定 URI 的页。我们根据源码也看到Navigate方法最终是通过此对象来加载URI页面。

[c-sharp:nogutter] view plain copy print ?
  1. this._contentLoader.BeginLoad(this._currentNavigation.Uri,new AsyncCallback(this.ContentLoader_BeginLoad_Callback),this._currentNavigation); 

我们看看BeginLoad方法的实现

实际是调用了BeginLoad_OnUIThread方法,而里面又调用了GetLocalXaml方法来加载本地的XAML文件,而且获得了xclass属性指定的类型。最后使用Activator.CreateInstance创建了此XAML文件类对象的实例,保存到result中。

[c-sharp] view plain copy print ?
  1. private staticvoid BeginLoad_OnUIThread(AsyncCallback userCallback, PageResourceContentLoaderAsyncResult result) 
  2.     if (result.Exception !=null
  3.     { 
  4.         result.IsCompleted = true
  5.         userCallback(result); 
  6.     } 
  7.     else 
  8.     { 
  9.         try 
  10.         { 
  11.             string pagePathAndName = UriParsingHelper.InternalUriGetBaseValue(result.Uri); 
  12.             string localXaml = GetLocalXaml(pagePathAndName); 
  13.             if (string.IsNullOrEmpty(localXaml)) 
  14.             { 
  15.                 result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_NoXAMLWasFound,new object[] { pagePathAndName })); 
  16.             } 
  17.             else 
  18.             { 
  19.                 string xClass = GetXClass(localXaml); 
  20.                 if (string.IsNullOrEmpty(xClass)) 
  21.                 { 
  22.                     try 
  23.                     { 
  24.                         result.Content = XamlReader.Load(localXaml); 
  25.                     } 
  26.                     catch (Exception exception) 
  27.                     { 
  28.                         result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_XAMLWasUnloadable,new object[] { pagePathAndName }), exception); 
  29.                     } 
  30.                 } 
  31.                 else 
  32.                 { 
  33.                     Type typeFromAnyLoadedAssembly = GetTypeFromAnyLoadedAssembly(xClass); 
  34.                     if (typeFromAnyLoadedAssembly ==null
  35.                     { 
  36.                         result.Exception = new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_TheTypeSpecifiedInTheXClassCouldNotBeFound,new object[] { xClass, pagePathAndName })); 
  37.                     } 
  38.                     else 
  39.                     { 
  40.                         result.Content = Activator.CreateInstance(typeFromAnyLoadedAssembly); 
  41.                     } 
  42.                 } 
  43.             } 
  44.         } 
  45.         catch (Exception exception2) 
  46.         { 
  47.             result.Exception = exception2; 
  48.         } 
  49.         finally 
  50.         { 
  51.             result.IsCompleted = true
  52.             if (userCallback !=null
  53.             { 
  54.                 userCallback(result); 
  55.             } 
  56.         } 
  57.     } 

这里BeginLoad是用异步的方式来加载,避免UI线程等待。而加载完成后执行回调方法 ContentLoader_BeginLoad_Callback

加载完成后,从result中获得PhoneApplicationPage对象,也就是之前加载的XAML文件。然后设置了导航的Context内容,这个用来页面间传值的。接下来obj2.SetValue(NavigationServiceProperty,this)方法设置了NavigationServiceProperty依赖属性。这个属性和导航时的空引用异常有关。而CompleteNavigation方法用来完成导航。

[c-sharp:nogutter] view plain copy print ?
  1. this.RaiseNavigated(content, uriBeforeMapping, mode,null != existingContentPage, existingContentPage, newContentPage); 

其中一段代码RaiseNavigated出发了Frame的Navigated事件,也就是会执行我们在App中的CompleteInitializePhoneApplication方法。此时我们已经获得了要显示页面的Page对象。

我们知道在WPF中有对象树和可视数的概念,而在Silverlight上只有对象树。 RootVisual属性是设置显示的元素,把Frame设置为RootVisual,此时就会把Frame加载到对象树上,而前面提到过,Frame初始化时绑定了Loaded事件,这个事件此时会被执行。

[c-sharp:nogutter] view plain copy print ?
  1. private void Frame_Loaded(object sender, RoutedEventArgs e) 
  2.     this.Load(); 

实际上她执行的也是Load方法,这个方法已经在PhoneApplicationFrame的构造函数中执行过了,有_loaded字段标记,也就不会在执行了。

[c-sharp:nogutter] view plain copy print ?
  1. public UIElement RootVisual 
  2.     get 
  3.     { 
  4.         return (XcpImports.Application_GetVisualRoot()as UIElement); 
  5.     } 
  6.     [SecuritySafeCritical] 
  7.     set 
  8.     { 
  9.         XcpImports.CheckThread(); 
  10.         if ((value == null) || !XcpImports.DependencyObject_IsPointerValid(value)) 
  11.         { 
  12.             throw new InvalidOperationException(Resx.GetString("Application_InvalidRootVisual")); 
  13.         } 
  14.         XcpImports.Application_SetVisualRoot(value); 
  15.         this._rootVisual = value; 
  16.     } 

RootVisual的实现看不到,这里贴的是Silverlight的实现。这里用到了XcpImports,这个对象上一篇文章介绍过了,最终是使用Core presentation framework的方法设置了RootVisual,通过Frame得到当前的Page,这时Presentation Core会根据当前的对象树生成显卡可以识别的三角形,最终显示到屏幕上。这方面内容可以参考WPF Presentation


四 PhoneApplicationPage和NavigateService

前面介绍了Frame框架的初始化和第一个页面导航加载到显示的过程。我们已经知道,实际负责页面导航的是NavigateService这个类。但只有使用了Frame框架才能完成导航。而在PhoneApplicationPage中有一个NavigateService属性:

[c-sharp:nogutter] view plain copy print ?
  1. internal static NavigationService GetNavigationService(DependencyObject dependencyObject) 
  2.     Guard.ArgumentNotNull(dependencyObject, "dependencyObject"); 
  3.     return (dependencyObject.GetValue(NavigationServiceProperty)as NavigationService); 

他实际是返回了NavigationServiceProperty依赖属性。而这个属性上面提到过,是在初始化时加,BeginLoad加载了XAML后的回调函数中设置了此属性。所以我们可以在Page类中使用下面代码进行导航,也可以获得App类的RootFrame对象来导航。

[c-sharp] view plain copy print ?
  1. this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 
  2.  
  3. (Application.Current as App).RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 

另外我们在Page中还有OnNavigateTo和OnNavigateFrom等方法,来控制导航。这些内容将在下一篇文章中介绍。


五 Silverlight的不同之处

同Windows Phone导航不同,我们默认建立一个Silverlight的普通项目是没有添加Frame框架的。我们可以仿照Windows Phone修改App文件。

[c-sharp] view plain copy print ?
  1. private void Application_Startup(object sender, StartupEventArgs e) 
  2.     RootFrame = new Frame(); 
  3.     RootFrame.Navigate(new Uri("/MainPage.xaml", UriKind.Relative)); 
  4.     this.RootVisual = RootFrame; 

在程序启动的时候执行,整个过程和前面基本一样。相比Windows Phone,他们的构造函数简单了很多。

[c-sharp:nogutter] view plain copy print ?
  1. public Frame() 
  2.     base.DefaultStyleKey =typeof(Frame); 
  3.     base.Loaded += new RoutedEventHandler(this.Frame_Loaded); 
  4.     this._navigationService =new NavigationService(this); 
  5.  
  6. internal NavigationService(Frame nav) 
  7.     this._cacheRequiredPages = new Dictionary<string, Page>(); 
  8.     Guard.ArgumentNotNull(nav, "nav"); 
  9.     this._host = nav; 

Frame中注册了Loaded事件,和WP不同的是没有在Frame的构造函数中就执行。那么他只能在要显示的时候运行,也就是我们设置了RootVisual属性后才能执行Navigate方法。另外没有配置文件设置导航初始画面,所以我们也必须在这里调用Navigate方法或设置Source属性。

当页面开始呈现,加载了Frame到对象树后触发了Loaded时间, 最终调用了NavigateService.Navigate方法。

[c-sharp:nogutter] view plain copy print ?
  1. public bool Navigate(Uri source) 
  2.     return this.NavigateCore(source, NavigationMode.New,false, false); 

这里也和Windows Phone不同,NavigateService中直接调用了NavigateCore方法。此方法实现和Windows Phone一样,此方法中有用的是NavigateCore_StartNavigation,最终是通过的this._contentLoader.BeginLoad来完成的。然后显示核心把加载的页面显示到屏幕上。

以上是Silverlight的过程,相比Windows Phone简单一些。因为Loaded事件触发的时间不同,所以WP会自动根据配置文件设置在App对象建立后就导航到指定页面;而SL是在加载对象准备呈现时触发,而且没有配置文件,所以必须手动设置一下。这也就解释了我们前一篇的疑问。也是为什么SL模仿WP来创建框架,却不能在Navigated时间中才设置RootVisual。因为此事件需要设置运行了Loaded方法才能触发。


六 总结

本文主要对导航框架源代码进行了简单的解析,了解了程序启动后导航到第一个界面的过程。首先创建了PhoneApplication对象,在此对象构建完成后,调用了Frame的Loaded方法来导航到配置文件指定的URI;然后加载此XAML文件,然后触发Frame的Navigated方法设置RootVisual,此属性会调用显示框架的Native方法,将对象树呈现到屏幕上。

从第一篇开始我们就看到了页面的导航切换,上一篇文章则介绍了框架实现导航的原理和过程。真正的导航功能是NavigationService类来实现的。而Frame是Page的载体,是负责导航,历史记录等功能的,相当于一个指挥官。这一篇就主要介绍一下导航的操作和相关的一些方法。

一 导航时发生错误

默认的我们建立一个Windows Phone程序,使用导航功能是不会出现这个问题的。我们先看一个列子:

  1. private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) 
  2.    // Set the root visual to allow the application to render 
  3.    if (RootVisual != RootFrame) 
  4.        //RootVisual = RootFrame; 
  5.        RootVisual = new MainPage(); 
  6.     // Remove this handler since it is no longer needed 
  7.     RootFrame.Navigated -= CompleteInitializePhoneApplication; 
private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
{
   // Set the root visual to allow the application to render
   if (RootVisual != RootFrame)
       //RootVisual = RootFrame;
       RootVisual = new MainPage();
    // Remove this handler since it is no longer needed
    RootFrame.Navigated -= CompleteInitializePhoneApplication;
}

我们新建一个项目后,不把Frame设置到RootVsual,而是用MainPage。我们从Mainpage导航到Page1. 使用两种方法:

  1. private void Button_Click(object sender, RoutedEventArgs e) 
  2.      方法1:this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 
  3.      方法2:(Application.Current as App).RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative)); 
private void Button_Click(object sender, RoutedEventArgs e)
{
     方法1:this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
     方法2:(Application.Current as App).RootFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
}

第一种方法是使用Page页面的NavigationSevice属性来导航,第二种方法是使用Frame对象来导航。从上一篇我们知道,两种方法是相同的。结果是使用第一种方法发生了NullReferenceException错误,为什么NavigationService对象是空?我们不是在App构造函数中就创建过NavigationServie对象的实例吗,并且在加载了XAML文件后设置到了依赖属性中。

  1. obj2.SetValue(NavigationServiceProperty, this);   
obj2.SetValue(NavigationServiceProperty, this);  

上一篇文章介绍过,Page的NavigationService属性是通过依赖属性获得的。其实这里我要了解依赖属性的特点,虽然是一个static字段来维护,但是内部是有Hash表来存放不同对象的属性值。我们在构造函数的Load方法中,加载了配置文件中指定的MainPage,并生成了他的实例,然后设置了NavigationServiceProperty。而这里我们使用,MainPage的是一个新的实例,他的NavigationServiceProperty为Null。所以这里当然会报错了。 而且NavigationServiceProperty是internal属性,说以我们不能手动设置了。

从这个异常我们能发现,每次导航的新的页面,在加载完成Page之后,都会设置Page的NavigationServiceProperty的属性。所以并不是创建了Frame,我们就能使用NavigationService来导航。而是需要Frame来吧Page和NavigationService关联起来。而这里,我们new的Mainpage没有通过Frame加载显示的,所以无法导航。

接下来看看第二种方法。这里使用的是我们创建的RootFrame对象,因为此时Frame已经完全构造好了,NavigationSevice也创建好了。所以此时调用Navigate方法是不会报错的,而且返回的是true。因为这里不存在延迟导航,所以说明是成果了,但是为什么没显示呢?我们点击【<-】退出程序,但是点了2下才退出,这说明导航了吗?我们在做个试验,我们在Page1中加入以下的代码

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.     base.OnNavigatedTo(e); 
  3.     if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back) 
  4.     { 
  5.        bool ret = (Application.Currentas App).RootFrame.Navigate(new Uri("/Page2.xaml", UriKind.Relative)); 
  6.     } 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
    {
       bool ret = (Application.Current as App).RootFrame.Navigate(new Uri("/Page2.xaml", UriKind.Relative));
    }
}

我们在从MainPage用方法2导航到Page1后,在点【<-】退出程序,现在要点3下了。我们也可以设置断点来查看,实际完成了MainPage->Page1->Page2的导航。当时为什么没有显示呢?很简单了,因为Page要通过RootVisual来显示,导航的话Page需要Frame来加载的,当我们把Frame设置到RootVisual时,就能显示,而这里我们设置的MainPage,虽然Frame导航了,但没有被显示出来。但是RootVisual只能设置一次,所以这里没有办法让他显示出来了。

好了,到这里我们导航的原理就研究到这里,通过2个错误我们进步不理解了Frame和Page的关系。也明确了Frame导航和加载Page的这一功能。这也说明为什么必须有Frame才能导航。

二 导航的过程

在上面的列子中,我们看到了OnNavigatedTo方法,当导航到Page1时自动导航到了Page2。我们知道一个Page导航到另一个Page实际是一个卸载和装载的过程。在这个过程中会触发一系列的事件和方法,下面我们就来看下这些方法。

  1. public class Page : UserControl 
  2.     // Methods 
  3.     internal Page(); 
  4.     internal void InternalOnFragmentNavigation(FragmentNavigationEventArgs e); 
  5.     internal virtualvoid InternalOnNavigatedFrom(NavigationEventArgs e); 
  6.     internal virtualvoid InternalOnNavigatedTo(NavigationEventArgs e); 
  7.     internal void InternalOnNavigatingFrom(NavigatingCancelEventArgs e); 
  8.     protected virtualvoid OnFragmentNavigation(FragmentNavigationEventArgs e); 
  9.     protected virtualvoid OnNavigatedFrom(NavigationEventArgs e); 
  10.     protected virtualvoid OnNavigatedTo(NavigationEventArgs e); 
  11.     protected virtualvoid OnNavigatingFrom(NavigatingCancelEventArgs e); 
public class Page : UserControl
{
    // Methods
    internal Page();
    internal void InternalOnFragmentNavigation(FragmentNavigationEventArgs e);
    internal virtual void InternalOnNavigatedFrom(NavigationEventArgs e);
    internal virtual void InternalOnNavigatedTo(NavigationEventArgs e);
    internal void InternalOnNavigatingFrom(NavigatingCancelEventArgs e);
    protected virtual void OnFragmentNavigation(FragmentNavigationEventArgs e);
    protected virtual void OnNavigatedFrom(NavigationEventArgs e);
    protected virtual void OnNavigatedTo(NavigationEventArgs e);
    protected virtual void OnNavigatingFrom(NavigatingCancelEventArgs e);
}

在Page类中,我们看到了和导航相关的4个protected虚方法。对应有4个internal的方法,他们是在事件发生的时候触发的(系统已经给我们绑定了),他们实现很简单,就是调用这里的虚方法。我们可以通过重写这些虚方法来控制。而在PhoneApplicationPage类中没有重写这些方法,也没有加入其它相关的方法。

在我们Demo3的PhoneApp3中我们在MainPage和Page1重写这几个方法:

  1. protected overridevoid OnFragmentNavigation(System.Windows.Navigation.FragmentNavigationEventArgs e) 
  2.     Debug.WriteLine("{0}:OnFragmentNavigation",this.ToString()); 
  3.     base.OnFragmentNavigation(e); 
  4.  
  5. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  6.     Debug.WriteLine("{0}:OnNavigatedTo",this.ToString()); 
  7.     base.OnNavigatedTo(e); 
  8.  
  9. protected overridevoid OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e) 
  10.      Debug.WriteLine("{0}:OnNavigatingFrom",this.ToString()); 
  11.      base.OnNavigatingFrom(e); 
  12.  
  13. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  14.      Debug.WriteLine("{0}:OnNavigatedFrom",this.ToString()); 
  15.      base.OnNavigatedFrom(e); 
protected override void OnFragmentNavigation(System.Windows.Navigation.FragmentNavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnFragmentNavigation", this.ToString());
    base.OnFragmentNavigation(e);
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
    base.OnNavigatedTo(e);
}

protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatingFrom", this.ToString());
     base.OnNavigatingFrom(e);
}

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
     base.OnNavigatedFrom(e);
}

启动程序,从MainPage导航到Page1: 输出如下:

  1. PhoneApp3.MainPage:OnNavigatedTo 
  2. PhoneApp3.MainPage:OnNavigatingFrom 
  3. PhoneApp3.MainPage:OnNavigatedFrom 
  4. PhoneApp3.Page1:OnNavigatedTo 
PhoneApp3.MainPage:OnNavigatedTo
PhoneApp3.MainPage:OnNavigatingFrom
PhoneApp3.MainPage:OnNavigatedFrom
PhoneApp3.Page1:OnNavigatedTo

点击【<-】按钮,输出如下:

  1. PhoneApp3.Page1:OnNavigatingFrom 
  2. PhoneApp3.Page1:OnNavigatedFrom 
  3. PhoneApp3.MainPage:OnNavigatedTo 
PhoneApp3.Page1:OnNavigatingFrom
PhoneApp3.Page1:OnNavigatedFrom
PhoneApp3.MainPage:OnNavigatedTo

在点击【<-】按钮退出程序,输出如下:

  1. PhoneApp3.MainPage:OnNavigatingFrom 
  2. PhoneApp3.MainPage:OnNavigatedFrom 
PhoneApp3.MainPage:OnNavigatingFrom
PhoneApp3.MainPage:OnNavigatedFrom

好吧,这些方法的执行顺序很清楚了,我就不用什么介绍了。不清楚可以看MSDN。不过这里命名很容易让人产生误解。我开始就弄反了。这里To和From相对的对象都是当前的页面。OnFragmentNavigation 方法,在导航到包括片断的统一资源标识符 (URI) 时会发生。一个片断是片断分隔符 (#) 后的值。这个后面在介绍。

三 页面传值

和Web一样,我们在导航过程中可能需要传递值,对于WinForm程序来说,我们可以通过Form的构造函数传值,可以定义定义全局变量等多种方法,对于Web,我们可以有QueryString,Cookies,Session等方法,在这里我们可以用类似的方法传递,这里我们结合导航到过程来进行值的传递。这里我们导航时不能通过构造函数传递,也无法使用Cookies和Session。

我在Demo3的PhoneApp4中,MainPage和Page1放置了5个TextBox,每个对应一种传递方式:

1 全局变量传递

这个方法在WinForm很常见,我们在App类中定义一个变量,App是一个全局对象,所以可以定义在这里。

  1. public partial class App : Application 
  2.     public string StaticVar { get; set; } //全局变量 
  3.  
  4.     public PhoneApplicationFrame RootFrame { get; private set; } 
  5.  
  6.     public App() 
  7.     { 
  8.     } 
    public partial class App : Application
    {
        public string StaticVar { get; set; } //全局变量

        public PhoneApplicationFrame RootFrame { get; private set; }

        public App()
        {
        }
}

在Mainpage的OnNavigtedFrom方法中,把s1的值传递给全局变量,s1的值来源于TextBox1。当然你可以在导航前任意时刻获取TextBox的值,这里只是演示导航方法。

  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.    Debug.WriteLine("{0}:OnNavigatedFrom",this.ToString()); 
  3.    base.OnNavigatedFrom(e); 
  4.    s1 = textBox1.Text; 
  5.    //全局变量传递 
  6.    (Application.Current as App).StaticVar = s1; 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
   Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
   base.OnNavigatedFrom(e);
   s1 = textBox1.Text;
   //全局变量传递
   (Application.Current as App).StaticVar = s1;
}

在Page1的OnNavigatedTo方法中接受并显示。同样,你可以在导航到Page1后的任意时刻来获取并显示,这里也是为了掩饰导航方法。

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("{0}:OnNavigatedTo",this.ToString()); 
  3.     base.OnNavigatedTo(e); 
  4.     //从全局变量接受 
  5.     s1 = (Application.Current as App).StaticVar; 
  6.     textBox1.Text = s1; 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
    base.OnNavigatedTo(e);
    //从全局变量接受
    s1 = (Application.Current as App).StaticVar;
    textBox1.Text = s1;
}

使用全局变量很方便,但是占用内存空间,并且存在丢失的可能。这个以后文件会介绍。

2 页面QueryString传值

因为这里导航和Web相似,所以我们也可以用Uri后加上QueryString的传值方式。我们这里使用TextBox2.

首先修改导航按钮代码,我们必须在导航前,构造好导航的Uri,并且要获取TextBox2的值。QueryString格式为 Name1=Value1&Name2=Value2,多个参数用&间隔开。

  1. private void button1_Click(object sender, RoutedEventArgs e) 
  2.      //构造QueryString 
  3.      s2 = textBox2.Text; 
  4.      string uri = "/Page1.xaml?s2=" + s2; 
  5.  
  6.       //导航 
  7.        this.NavigationService.Navigate(new Uri(uri, UriKind.Relative)); 
private void button1_Click(object sender, RoutedEventArgs e)
{
     //构造QueryString
     s2 = textBox2.Text;
     string uri = "/Page1.xaml?s2=" + s2;

      //导航
       this.NavigationService.Navigate(new Uri(uri, UriKind.Relative));
}

而在Page1页面,我们通过NavigationContext属性的QueryString获得传递的值。这里是返回的是IDictionary类型,在使用前,必须检查Name对应的变量是否存在。

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("{0}:OnNavigatedTo",this.ToString()); 
  3.     base.OnNavigatedTo(e); 
  4.     //从Uri接受 
  5.     IDictionary<string, string> queryString =this.NavigationContext.QueryString; 
  6.     if (queryString.ContainsKey("s2")) 
  7.     { 
  8.        s2 = queryString["s2"]; 
  9.     } 
  10.  
  11.     //显示到UI 
  12.     textBox2.Text = s2; 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
    base.OnNavigatedTo(e);
    //从Uri接受
    IDictionary<string, string> queryString = this.NavigationContext.QueryString;
    if (queryString.ContainsKey("s2"))
    {
       s2 = queryString["s2"];
    }

    //显示到UI
    textBox2.Text = s2;
}

使用QueryString可以传递少量数据,因为Uri长度是有限制的,并且只能传递简单类型,如果要传递自定义类对象,就不适用了。另外传的是string类型,所以可能需要进行类型转化。在我们导航的时候,会建立一个JournalEntry对象,当新的xaml页面加载完后,会去解析URI中的QueryString数据存入到其中。

  1. JournalEntry.SetNavigationContext(obj2, new NavigationContext(UriParsingHelper.InternalUriParseQueryStringToDictionary 
JournalEntry.SetNavigationContext(obj2, new NavigationContext(UriParsingHelper.InternalUriParseQueryStringToDictionary


3 PhoneApplicationService类

如果我们需要传递自定义类型数据时,就需要用到这个对象。其实这个对象我们并不是很陌生,在前面我们就介绍过他实现了IApplicationService就接口,为Silverlight程序供了扩展功能,比如前面App中启动时Application_Launching,Application_Activated等四个方法。在这里他在内部提供了一个字典来维护一些全局的数据:

  1. public IDictionary<string,object> State { get; } 
public IDictionary<string, object> State { get; }

所以我们不需要自己去定义全局变量,而可以直接使用他,通过PhoneApplicationService.Current.State就能访问到这个字典。我们这里使用TextBox3来演示。在MainPage中把TextBox3获得的值,设置到字典中,注意,这里的Key需要是唯一的。另外我们使用时要引入以下命名空间。

  1. using Microsoft.Phone.Shell; 
using Microsoft.Phone.Shell;
  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.      Debug.WriteLine("{0}:OnNavigatedFrom",this.ToString()); 
  3.      base.OnNavigatedFrom(e); 
  4.      s3 = textBox3.Text; 
  5.      //使用PhoneApplicationService传递 
  6.       PhoneApplicationService.Current.State["s3"] = s3; 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
     base.OnNavigatedFrom(e);
     s3 = textBox3.Text;
     //使用PhoneApplicationService传递
      PhoneApplicationService.Current.State["s3"] = s3;
}

我们在Page1中,我们取得s3的值2,这里我们采用了TryGetValue来获得值,当然也能使用QueryString中的方法。两者都能实现。TryGetValue性能更高。

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.      Debug.WriteLine("{0}:OnNavigatedTo",this.ToString()); 
  3.      base.OnNavigatedTo(e); 
  4.      //从PhoneApplicationService获得数据 
  5.       object objS3; 
  6.      PhoneApplicationService.Current.State.TryGetValue("s3",out objS3); 
  7.      if (objS3 != null
  8.      { 
  9.          s3 = Convert.ToString(objS3); 
  10.      } 
  11.  
  12.      //显示到UI 
  13.      textBox3.Text = s3; 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
     base.OnNavigatedTo(e);
     //从PhoneApplicationService获得数据
      object objS3;
     PhoneApplicationService.Current.State.TryGetValue("s3", out objS3);
     if (objS3 != null)
     {
         s3 = Convert.ToString(objS3);
     }

     //显示到UI
     textBox3.Text = s3;
}

这里我们要注意,这里是object类型,所以在读取数据时,存在类型转换。另外,这里传递的对象,必须是可以序列化的对象。

4 独立存储传递

使用独立存储IsolatedStorageSettings类时,需要引用以下命名空间。我们在MainPage中使用她来保存TextBox4的值。

  1. using System.IO.IsolatedStorage; 
using System.IO.IsolatedStorage;
  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("{0}:OnNavigatedFrom",this.ToString()); 
  3.     base.OnNavigatedFrom(e); 
  4.     s4 = textBox4.Text; 
  5.     //使用独立存储 
  6.      IsolatedStorageSettings.ApplicationSettings["s4"] = s4; 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("{0}:OnNavigatedFrom", this.ToString());
    base.OnNavigatedFrom(e);
    s4 = textBox4.Text;
    //使用独立存储
     IsolatedStorageSettings.ApplicationSettings["s4"] = s4;
}

在Page1页面,我们获取S4的值,这里我们采用TryGetValue<>,这是一个泛型方法,从安全和性能上都比较好。

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.      Debug.WriteLine("{0}:OnNavigatedTo",this.ToString()); 
  3.      base.OnNavigatedTo(e); 
  4.      //从独立存储获得数据 
  5.       IsolatedStorageSettings.ApplicationSettings.TryGetValue<string>("s4",out s4); 
  6.      textBox4.Text = s4; 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("{0}:OnNavigatedTo", this.ToString());
     base.OnNavigatedTo(e);
     //从独立存储获得数据
      IsolatedStorageSettings.ApplicationSettings.TryGetValue<string>("s4", out s4);
     textBox4.Text = s4;
}

看起来也没有什么特别。我们在MainPage的OnNavigatedTo也加入上面Page1中的代码,我们从MainPage导航到Page,让后在关闭程序,在重新启动。嘿嘿。竟然在Mainpage中就显示了TextBox4之前的值。这是因为独立存储把值存到了本地,所以即便程序关闭也不会丢失,而前面则存在丢失的问题。对于全局变量和QueryString,即便程序不完全关闭,也是存在丢失的可能。所以一般来说,我们使用IsolatedStorageSettings来存储程序的一些配置信息。当然我们还能选择File和Database等方式来传递数据,当然对于页面传值来说就有点不太适合。


以上是各种方式传值的结果。

四 总结

这一篇文章首先继续谈了Frame和Page的关系, 然后介绍了在导航时会触发的事件方法,我们可以通过重写这些方法来控制页面的显示,比如进行值的传递。也详细介绍了值传递的四种方法和使用的环境。在下一篇文章将继续介绍导航的其他常用方法以及回退键,还有特殊页面的回退处理。

前面几乎每篇文章都会涉及到页面的切换,页面导航,从程序启动开始,到结束。上一篇文章介绍了页面导航时会发生的几个事件以及页面中传值的方法。这一篇文章将介绍对导航的一些控制。

一 导航栈

从Windows Phone 手机上我们就可以看到,手机有Back键,但是没有Forward按键。而我们在前面例子中也基本都是使用GoBack()方法。看下MSDN,关于Forward的描述如下:

  1. Navigates to the most recent entry in the forward navigation history, or throws an exception if no entry exists in forward navigation. For Windows Phone this method will always throw an exception because there is no forward navigation stack.  
Navigates to the most recent entry in the forward navigation history, or throws an exception if no entry exists in forward navigation. For Windows Phone this method will always throw an exception because there is no forward navigation stack. 


上面告诉我们,在Windows Phone 中没有Forward Stack,而只有Back Stack,所以我们使用GoBack后没有办法在使用GoForward前进到上一个页面。而当我们进入到一个新的页面时,上一个页面就会被存放到Back Stack中,当我们点击Back按钮,或使用GoBack方法时就会销毁当前的页面,而从Back Stack中弹出上一页面。如果Back Stack中没有页面,此时点击Back按钮程序就会退出,而是用GoBack方法就会抛出异常,所以我们在使用时最先对CanGoForward属性进行判断。

在WP7.1版本中我们注意到,在NavigationService类下面多了一个BackStack的属性。

  1. public IEnumerable<JournalEntry> BackStack 
  2.     get 
  3.     { 
  4.         return this._journal.BackStack; 
  5.     } 
public IEnumerable<JournalEntry> BackStack
{
    get
    {
        return this._journal.BackStack;
    }
}

还记得在前面文章介绍Navigate方法的时候,见到到一下代码:

  1. JournalEntry journalEntry = new JournalEntry(uri.OriginalString, uri); 
  2. this.Journal.AddHistoryPoint(journalEntry); 
JournalEntry journalEntry = new JournalEntry(uri.OriginalString, uri);
this.Journal.AddHistoryPoint(journalEntry);


JournalEntry也是在WP7.1中公开的一个类,它表示后退或前进导航历史记录中的一个条目。这个类在silverlight和WPF中都是公开存在的,但是和WPF相比,这个类只有公开了Uri属性。这里我们不详细研究这个类,只看看它的构造函数,也是很简单。

  1. internal JournalEntry(string name, Uri uri) 
  2.     Guard.ArgumentNotNull(uri, "uri"); 
  3.     this.Name = name; 
  4.     this._source = uri; 
  5.     this.PageInstance = null
  6.     this.PausedPage = null
internal JournalEntry(string name, Uri uri)
{
    Guard.ArgumentNotNull(uri, "uri");
    this.Name = name;
    this._source = uri;
    this.PageInstance = null;
    this.PausedPage = null;
}

Journal类是用来管理这些条目的,上面的AddHistoryPoint方法并不是新的要进的页面加入到BackStack中。在前面文章也看过这个的源码,在内部创建了一个ShellPage对象,并注册了相关的事件。

  1. internal void AddHistoryPoint(JournalEntry journalEntry) 
  2.     Guard.ArgumentNotNull(journalEntry, "journalEntry"); 
  3.     this._shellPagePending = this._shellPageManager.CreatePage(journalEntry.Source.ToString()); 
  4.     if (this._shellPagePending ==null
  5.     { 
  6.         throw new InvalidOperationException("Unable to create ShellPage"); 
  7.     } 
  8.     this._shellPagePending.ShellPageCallback.OnNavigateAwayEventHandler +=new EventHandler<NavigateAwayEventArgs>(this.ShellPage_NavigatedAway); 
  9.     this._shellPagePending.ShellPageCallback.OnNavigateToEventHandler +=new EventHandler<NavigateToEventArgs>(this.ShellPage_NavigatedTo); 
  10.     this._shellPagePending.ShellPageCallback.OnRemoveEventHandler +=new EventHandler<RemoveEventArgs>(this.ShellPage_RemovedPage); 
  11.     this._shellPagePending.ShellPageCallback.OnBackKeyPressInternalEventHandler +=new EventHandler<BackKeyPressEventArgs>(this.ShellPage_BackKeyPressed); 
  12.     this._shellPageManager.NavigateTo(this._shellPagePending); 
  13.     this.IsBusy = true
internal void AddHistoryPoint(JournalEntry journalEntry)
{
    Guard.ArgumentNotNull(journalEntry, "journalEntry");
    this._shellPagePending = this._shellPageManager.CreatePage(journalEntry.Source.ToString());
    if (this._shellPagePending == null)
    {
        throw new InvalidOperationException("Unable to create ShellPage");
    }
    this._shellPagePending.ShellPageCallback.OnNavigateAwayEventHandler += new EventHandler<NavigateAwayEventArgs>(this.ShellPage_NavigatedAway);
    this._shellPagePending.ShellPageCallback.OnNavigateToEventHandler += new EventHandler<NavigateToEventArgs>(this.ShellPage_NavigatedTo);
    this._shellPagePending.ShellPageCallback.OnRemoveEventHandler += new EventHandler<RemoveEventArgs>(this.ShellPage_RemovedPage);
    this._shellPagePending.ShellPageCallback.OnBackKeyPressInternalEventHandler += new EventHandler<BackKeyPressEventArgs>(this.ShellPage_BackKeyPressed);
    this._shellPageManager.NavigateTo(this._shellPagePending);
    this.IsBusy = true;
}

这些事件绑定的方法都是实现在Journal类中,在这些方法中可以找到对BackStack的操作。所以在NavigationService的BackStack属性中就是返回了_journal.BackStack。这里具体内容就不讨论。 我们只要知道在Windows Phone 7中存在一个BackStack来存放之前访问的页面,一边我们可以回退。

二 Navigate和GoBack

看起来好像没有太大关系,一个是导航到一个页面,一个是从返回到BackStack的一个页面,但是我们在GoBack的地方改用Navigate回如何呢?前一个页面怎么处理呢?在Demo4这个程序中,我们有MainPage,Page1,Page2三个页面。导航方式为MainPage<->Page1<->Page2。2个页面间可以选择Navigate或GoBack来导航。

现在我们进行以下操作:从MainPage导航到Page2,然后从Page2调用GoBack到MainPage,以下是输出的结果,我们打印了BackStack的数量

  1. Entry the MainPage 
  2. JournalEntry number is 0 
  3. MainPage Navigate to Page1 
  4. Leave the MainPage 
  5. JournalEntry number is 1 
  6.  
  7. Entry the Page1 
  8. JournalEntry number is 1 
  9. Page1 Navigate to Page2 
  10. Leave the Page1 
  11. JournalEntry number is 2 
  12.  
  13. Entry the Page2 
  14. JournalEntry number is 2 
  15. Page2 Call GoBack 
  16. Leave the Page2 
  17. JournalEntry number is 1 
  18.  
  19. Entry the Page1 
  20. JournalEntry number is 1 
  21. Page1 Call GoBack 
  22. Leave the Page1 
  23. JournalEntry number is 0 
  24.  
  25. Entry the MainPage 
  26. JournalEntry number is 0 
Entry the MainPage
JournalEntry number is 0
MainPage Navigate to Page1
Leave the MainPage
JournalEntry number is 1

Entry the Page1
JournalEntry number is 1
Page1 Navigate to Page2
Leave the Page1
JournalEntry number is 2

Entry the Page2
JournalEntry number is 2
Page2 Call GoBack
Leave the Page2
JournalEntry number is 1

Entry the Page1
JournalEntry number is 1
Page1 Call GoBack
Leave the Page1
JournalEntry number is 0

Entry the MainPage
JournalEntry number is 0

这里的结果很明显,每当我们进入到新的一个页面时,BackStack数量就会增加一个,当我们调用GoBack返回时,就会减少。回到MainPage时BackStack为空。这个时候在点击Back按钮时就退出了。

下面我们进行另一个试验,从MainPage导航到Page2,然后在从Page2调用Navigate导航到MainPage,结果如下:

  1. Entry the MainPage 
  2. JournalEntry number is 0 
  3. MainPage Navigate to Page1 
  4. Leave the MainPage 
  5. JournalEntry number is 1 
  6.  
  7.  
  8. Entry the Page1 
  9. JournalEntry number is 1 
  10. Page1 Navigate to Page2 
  11. Leave the Page1 
  12. JournalEntry number is 2 
  13.  
  14.  
  15. Entry the Page2 
  16. JournalEntry number is 2 
  17. Page2 Nagivate to Page1 
  18. Leave the Page2 
  19. JournalEntry number is 3 
  20.  
  21.  
  22. Entry the Page1 
  23. JournalEntry number is 3 
  24. Page1 Nagivate to MainPage 
  25. Leave the Page1 
  26. JournalEntry number is 4 
  27.  
  28.  
  29. Entry the MainPage 
  30. JournalEntry number is 4 
Entry the MainPage
JournalEntry number is 0
MainPage Navigate to Page1
Leave the MainPage
JournalEntry number is 1


Entry the Page1
JournalEntry number is 1
Page1 Navigate to Page2
Leave the Page1
JournalEntry number is 2


Entry the Page2
JournalEntry number is 2
Page2 Nagivate to Page1
Leave the Page2
JournalEntry number is 3


Entry the Page1
JournalEntry number is 3
Page1 Nagivate to MainPage
Leave the Page1
JournalEntry number is 4


Entry the MainPage
JournalEntry number is 4

可以看到采用Navigate导航,目前BackStack中保存了4条记录(MainPage,Page1,Page2,Page1)。当我们使用Back会退时结果和第一个一样。但是注意到其中Page1被存放了两次,二这2个Page1是不同的实例。这个很容易证实,我们在Demo的每个页面中填入一个数字,然后造使用GoBack,你会发现两个Page1的页面的数字是不一样的。

三 控制导航行为

WP7.1中还提供了操作BackStack的方法,在NavigationService中还提供了一个RemoveBackEntry的方法,这个方法可以从BackStack中移除一个项目,如果没有可以删除的项目就会抛出异常,而要删除多个项目的话需要调用多次。而且此方法必须在UI现场调用。

Demo4中,我们从MainPage导航到Page1,然后点击移除历史记录的按钮。结果如下:

  1. Entry the MainPage 
  2. JournalEntry number is 0 
  3. MainPage Navigate to Page1 
  4. Leave the MainPage 
  5. JournalEntry number is 1 
  6.  
  7. Entry the Page1 
  8. JournalEntry number is 1 
  9. Remove History Entry 
  10. JournalEntry number is 0 
Entry the MainPage
JournalEntry number is 0
MainPage Navigate to Page1
Leave the MainPage
JournalEntry number is 1

Entry the Page1
JournalEntry number is 1
Remove History Entry
JournalEntry number is 0

此时MainPage已经从BackStack中移除,此时我们点击GoBack按钮已经不在有效,而点击Back按钮程序将直接退出。但是此时继续点删除按钮,也不会引发异常。

  1. JournalEntry entry = this.NavigationService.RemoveBackEntry(); 
JournalEntry entry = this.NavigationService.RemoveBackEntry();


方法会返回一个JournalEntry对象,我们不能做更多操作,只能通过Uri属性得到移除的页面的Uri。而除此之外还有一个JournalEntryRemoved事件,这个时间会在删除时触发,我们可以重写他绑定的OnRemovedFromJournal方法,但是我们也做不了过多的操作,只能从JournalEntryRemovedEventArgs对象中获得移除的JournalEntry对象。目前我们好像还不能对BackStack进行替换和移除到起始页之间的页面等操作,但是MSDN上好像有写WP7.1的更新。

  • Replace the current page with a new page. This can be achieved by navigating forward to the new page and then removing the top of the back stack.

  • Remove all pages up to a given page, which is typically the first page. This can be accomplished by removing all the intervening pages and navigating back to the first page.

  • Clear the entire page back stack and replace with a given page. This can be accomplished by navigating forward to the new root page and then removing all the entries in the back stack.

最后要提到的是,在PhoneApplicationFrame类中也提供了这些在NavigationService中的方法和事件,实现也是相同,应该也是调用NavigationService中的实现。

在WP7.1还有一个虚方法我们可以重写导航的行为。

  1. protected overridevoid OnNavigatingFrom(NavigatingCancelEventArgs e) 
  2.    if (e.IsCancelable && e.NavigationMode == NavigationMode.New) 
  3.    { 
  4.        e.Cancel = true
  5.    } 
  6.    base.OnNavigatingFrom(e); 
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
   if (e.IsCancelable && e.NavigationMode == NavigationMode.New)
   {
       e.Cancel = true;
   }
   base.OnNavigatingFrom(e);
}


在这个方法中,我么可以判断当前导航能否取消。而通过NavigationMode 可以判断当前是创建新页面,还是从BackStack返回一个页面,而通过Url我们可以知道程序具体访问的页面,然后使用Cancle属性决定是否取消这个导航。

四 Back按键的控制

在BackStack栈中,机器的Back按钮作用很大,我们可以对Back按钮的行为进行控制。在WP7中有以下2个方法可以控制Back按键的行为。

  1. void MainPage_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e) 
  2.     Debug.WriteLine("MainPage_BackKeyPress"); 
  3.     e.Cancel = false
  4.  
  5. protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e) 
  6.     Debug.WriteLine("OnBackKeyPress"); 
  7.     base.OnBackKeyPress(e); 
        void MainPage_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e)
        {
            Debug.WriteLine("MainPage_BackKeyPress");
            e.Cancel = false;
        }

        protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
        {
            Debug.WriteLine("OnBackKeyPress");
            base.OnBackKeyPress(e);
        }


在点击Back按键的时候,会先调用OnBackKeyPress方法,然后调用BackKeyPress事件绑定的方法。我们发现这2个方法参数都是CancelEventArgs ,所以我们可以调用e.Cancel = True 来取消Back按键的功能。

在WP7.1中,Frame也提供了一个BackKeyPress事件:

  1. (Application.Current as App).RootFrame.BackKeyPress +=new EventHandler<System.ComponentModel.CancelEventArgs>(RootFrame_BackKeyPress); 
  2.  
  3. void RootFrame_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e) 
  4.    Debug.WriteLine("RootFrame_BackKeyPress"); 
(Application.Current as App).RootFrame.BackKeyPress += new EventHandler<System.ComponentModel.CancelEventArgs>(RootFrame_BackKeyPress);

void RootFrame_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e)
{
   Debug.WriteLine("RootFrame_BackKeyPress");
}

这个事件会捕获框架中所有页面的Back按钮时间,而不仅仅是当前的页面。

注意的问题:

在重写回退键我们有一些问题需要注意,Back按键不仅可以对页面进行导航,还可以关闭一些弹出窗体,比如MessageBox,Popup或者是ListPicker和ContectMenu。比如我在MainPage点击Back按钮时弹出一个MessageBox询问是否退出的选项。在我点击Back页面之前,MainPage界面上的ListPicker或ContectMenu都是弹出的状态,这个时候系统会关闭控件弹出内容,但同时也会弹出是否退出的MessageBox,但是你点击是调用了e.Cancle = True也不会退出程序,所以在重写Back按键方法时,要注意界面上的控件状态。

五 总结

经过几篇文章的介绍,应该对于Windows Phone的页面导航有了一定认识,有一些内容没有涉及,但是整体来说还是相对比较简单。对我们来说用到最多的就是OnNavigatedToOnNavigatedFrom方法,也是最重要的。因为页面导航不仅仅是页面的切换,还涉及到页面数据的保存和恢复,页面传值,墓碑状态的数据保存和恢复,打开和关闭页面时新建和回收一些资源等等。所以很重要,而在后面介绍到墓碑机制时也会用到。所以导航虽然简单,但是到项目中,页面数据保存恢复操作还是很麻烦的。

DEMO4下载地址:http://download.csdn.net/source/3495956

目前的智能手机,硬件上已经可以媲美几年前的PC机了,1G内存,512M以上内存,3.5以上的屏幕,3G,WIFI等等都成为了新的手机的最低标准。而Windows Phone也一改以往WM手机硬件差异大的问题,设定了最低的硬件标准。相对于以前系统,性能上,操作上,流畅度上也有了很大提高。但是电池的发展远远跟不上手机的耗电量。大的也就1500MA的电池,最多也就使用1天多,大部分每天都用充电。为了节约电量,各个平台的手机都推出了一些省电的措施。

对于Windows Phone来说,刚推出时和Iphone第一版一样,不支持多任务,一方面是为了给前台程序提供更多的资源,更流畅的体验,另一方面也是为了介绍电池的消耗。同事采用了消息推送机制来完成一部分后台操作,也使用了一种名为墓碑机制来对实现所谓的了“伪多任务”。  到了最新的“芒果”系统,已经支持了多任务,但也不同于以往WM说或PC上的多任务,也改进了墓碑机制,加快了程序间的切换。我们这一篇文章就了解下Windows Phone平台的伪多任务。

一 伪多任务

在我们使用PC和WM系统时,多任务对我们来说是理所应当的。一边听歌,一边上QQ,还能上微博发照片。可到了Windows Phone上却不支持多任务了,对于一个新的智能系统来说,确实无法让人接受。但是多任务存在一个问题就是如果开的任务过多,系统用起来就会比较卡,在WM系统中这种情况比较常见。为了提供用户的体验,目前的一些智能系统都放弃了这种传统的多任务方式,而采用了伪多任务,有限制的多任务和消息推送这些方法。

所谓的伪多人他并不是真正的多任务,只是通过一些处理,让用户使用起来感觉不到和多任务有什么区别。在Windows Phone中采用了墓碑机制来实现伪多任务。墓碑这个名字也很恰当。当我们运行了一个程序后,切换到另一个程序,这时因为是单任务,第一个程序会被终止掉,但是采用墓碑机制,保存了程序当前的状态和数据,当我们在切换回的时候,虽然是启动了一个新的实例,但是可以通过墓碑来恢复到之前的状态,所以用户感觉不到有什么区别。

墓碑机制是系统自动完成的,但是数据的保存和恢复则是我们通过代码控制的。另外墓碑机制也不是万能的,因为此时程序是终止的,如果有一些下载,播放,计时的功能就无法通过墓碑机制完成到了。Windows Phone 7则通过推送机制来解决部分问题,而WP7.1 SDK中也提供了一些后台操作来解决这些问题,这一篇文章我们主要介绍墓碑机制。

二 程序执行模型

在深入了解墓碑机制之前我们必须弄清楚程序的执行模型。下面这个图就是Windows Phone 7.1中最新的执行模型。此图来自于MSDN,但是他的图有个错误,修改了下。

对于一个Windows Phone程序来说,他有启动---运行---休眠---墓碑---退出等5个状态,状态切换之间存在一些事件和方法。下面就具体介绍一个程序从启动到终止的过程。

1 启动一个程序

我们启动一个程序有多种方法,最普遍的就是在第一屏界面点击程序的Tile图标或者是在第二屏中点击程序图标,另外我们可以通过点击一个推送的Toast消息或者通过Launchers 和 Choosers来启动程序,这些后面会介绍。在启动程序是,会触发Application的Launching事件。这个事件我们前面提到过,他是PhoneApplicationService类中注册的事件,除此之外好包含了三个和程序状态相关的事件。

程序启动会会创建一个新的实例,为了保证程序能过正常快速的启动,我们不应该在Launching事件中执行过多的代码,比如读取文件,网络连接等等,这样会印象用户体验,我们可用另起一个线程来执行这些操作。

2 程序运行

程序启动以后就进入了运行状态,程序从启动到进入第一个页面的过程我们在前面讨论页面导航的文章中详细介绍过了。通过从配置文件获得要加载的第一个XAML页面,读取XAML文件并生成新的实例,通过Frame显示出这个Page。我们知道在进入一个Page时会执行OnNavigatedTo方法。当这些完成后,程序才真正进入了运行状态。而OnNavigatedTo方法对于墓碑机制来说非常重要,我们一般在这个方法中进行一些数据恢复的操作。

3 休眠状态

这个状态是在WP7.1中新加入的一个状态,是为了提升多个任务间的切换速度。当我们在程序中点击Win键进入到主界面,或者是在程序中使用了Launchers 和Choosers启动了另一个程序时就会发生(不是所有都会发生)。休眠状态时,程序停止运行,但是整个进程还是存在于内存中。当恢复这个程序时,就不需要创建一个新的实例。这样就加快了程序恢复和切换的速度。而且从休眠状态恢复时我们一不需要去恢复数据。而在WP7.1中,我们可以长按Back按钮,出现程序列表,然后选择要前台执行的程序。

在切换到其他程序,进入到休眠状态之前,会调用当前页面的OnNavigatedFrom方法,在这个方法中我们可以保存当前页面的一些数据状态;然后会触发Application中的Deactivated事件,在这个事件中,我们可以保存一些当前程序的数据。我们知道,因为进程资源还保存在内存中,所以当前台程序使用时,内存不足或者不足以让程序流畅运行,这时系统就会执行一些操作来释放内存,此时程序就可能从休眠状态变换为下面介绍的墓碑状态。

4 墓碑状态

如上面说的,在WP7.1中,只有系统资源不够前台程序使用时,后台程序才可能从休眠状态进入到墓碑状态。这也是WP7.1相对于WP7.0最大的改进。在WP7.0中,系统没有休眠状态,仍和后台程序都是直接进入到墓碑状态,而OnNavigatedFrom方法和Deactivated事件都是在进入墓碑转台前触发的。一个程序进入到墓碑状态时,他的进程被终止掉,但是程序的回退栈中的信息,以及我们保存的一些信息会保留在内存中。

为了保证系统资源和性能,Windows Phone对墓碑状态也做了一些限制。目前系统中之允许同时存在5个墓碑程序。当超过了这个数量或者因为前台程序需要更多的资源,那么系统就会完全终止掉我们的程序,包括闪存内存中保存的回退栈和数据信息。所以对于我们而言,好需要对这种情况进行处理。

5 程序恢复

当我们使用Back按钮或者长按Back从任务列表中返回程序,或者是从Launchers 或Choosers返回时,程序就会恢复执行。但是如图上看到的,我们程序可能是从休眠状态,也可能是从墓碑状态返回的。如果是从休眠状态返回时我们不需要做任何恢复的操作,而从墓碑程序中返回,我们就需要恢复程序的状态和数据,以便用户感觉程序是被重写创建了。

程序恢复时会触发Application类中的Activated 事件,我们可以通过检查IsApplicationInstancePreserved参数来判断程序是从休眠状态还是墓碑状态返回的,在此方法中我们可以用来恢复之前在Deactivated事件中保存的数据。如果是从休眠状态恢复,不会重新执行构造函数,而从墓碑状态恢复时会重新执行构造函数。因为Back Stack在墓碑状态中还是保存在内存中的,所以这是会返回到我们程序退出时所在的页面,在进入到页面之前还是会执行OnNavigatedTo方法,对于墓碑状态恢复的程序,我们可以在这个方法中来恢当前页面的状态数据。

6 程序结束

在Windows Phone的silverlight框架程序中,我们唯一退出程序的方法就是点击Back键,当Back Stack中不存在页面时程序就会退出,目前系统没有提供任何Exit方法来以代码的方式结束程序。在程序退出时会触发Application中最后一个事件,Closing。在这个事件中我们可以释放一些使用的资源,保存数据等等,要注意的是,如果一个程序从墓碑状态被结束,是不会触发此事件的。MSDN上说关闭程序的时间被限制在10s,超过这个时间程序会被终止掉。

三 数据保存和恢复

至此,我们对于Windows Phone的执行模型也有了一定了解,即便WP7.1中引入了休眠方式,但是程序还是可能进入到墓碑模式或则被终止掉。所以无论什么情况,我们都要对数据进行保存和恢复。不过我们可以通过一些系统状态来判断是否需要进行数据保存和恢复。

在进入到墓碑状态时,数据会被保存到内存中,系统为我们提供了数据字典来保存我们的数据。 对于程序来说,系统的状态和数据我们可以存放到Application.State这个字典这种,而对于单个页面来说数据可以存放到Page.State的字典中。需要注意的是这些字典中存入的数据必须是可序列化的数据。另外我们也可以把数据保存到隔离存储区中。

有了数据保存的区域下面就是数据保存的时机,在Application相关的: Launching、Deactivated、ActivatedClosing这4个事件中我们可以对系统的数据进行保存和恢复,而在每个页面进入和离开都会发生的OnNavigatedTo和OnNavigatedFrom,我们可以用来保存和恢复页面的数据。

1 单页面的情况

我们看个列子,Demo5中我们新建了2个页面。在MianPage中有一个TextBox和三个按钮。点击Add时,TextBox的值会增加(默认是1),而点击Next按钮会导航到Page1页面,点击Run Camera按钮会调用相机。代码运行环境是WP7.1 SDK Beta2。我们在Application的四个事件和页面的2个方法中只加入运行的Log信息。

  1. public MainPage() 
  2.    Debug.WriteLine("MainPage Constructor"); 
  3.    camera = new CameraCaptureTask(); 
  4.    camera.Completed += new EventHandler<PhotoResult>(camera_Completed); 
  5.    num = 1; 
  6.    InitializeComponent(); 
  7.    txtNum.DataContext = num; 
  8. private void Button_Click(object sender, RoutedEventArgs e) 
  9.    txtNum.DataContext = ++num; 
  10.    Debug.WriteLine("Add Method,TextBox is {0}", num.ToString()); 
public MainPage()
{
   Debug.WriteLine("MainPage Constructor");
   camera = new CameraCaptureTask();
   camera.Completed += new EventHandler<PhotoResult>(camera_Completed);
   num = 1;
   InitializeComponent();
   txtNum.DataContext = num;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
   txtNum.DataContext = ++num;
   Debug.WriteLine("Add Method,TextBox is {0}", num.ToString());
}

我们点击Add让TextBox值增加。然后点击Win键回到住界面,然后点击Back返回程序,并继续点Add,程序Log信息如下:

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Add Method,TextBox is 4 
  7. MainPage OnNavigatedFrom 
  8. Application_Deactivated 
  9. Application_Activated 
  10. MainPage OnNavigatedTo 
  11. Add Method,TextBox is 5 
  12. Add Method,TextBox is 6 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
MainPage OnNavigatedFrom
Application_Deactivated
Application_Activated
MainPage OnNavigatedTo
Add Method,TextBox is 5
Add Method,TextBox is 6

很明显,因为在WP7.1下,程序进入了休眠状态,所以我们TextBox不会被清空。让我们在模拟器上来模仿一下墓碑状态的情况,需要进行如下设置就可以了。

然后我们再次运行程序,结果如下:

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Add Method,TextBox is 4 
  7. MainPage OnNavigatedFrom 
  8. Application_Deactivated 
  9. The thread '<No Name>' (0xec600c2) has exited with code 0 (0x0). 
  10. The thread '<No Name>' (0xee100aa) has exited with code 0 (0x0). 
  11. The thread '<No Name>' (0xe6a00ae) has exited with code 0 (0x0). 
  12. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  13. 'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll' 
  14. 'UI Task' (Managed): Loaded 'System.dll' 
  15. 'UI Task' (Managed): Loaded 'System.Windows.dll' 
  16. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  17. 'UI Task' (Managed): Loaded 'System.Core.dll' 
  18. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  19. 'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  20. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  21. 'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll' 
  22. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  23. Application_Activated 
  24. MainPage Constructor 
  25. MainPage OnNavigatedTo 
  26. Add Method,TextBox is 2 
  27. Add Method,TextBox is 3 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
MainPage OnNavigatedFrom
Application_Deactivated
The thread '<No Name>' (0xec600c2) has exited with code 0 (0x0).
The thread '<No Name>' (0xee100aa) has exited with code 0 (0x0).
The thread '<No Name>' (0xe6a00ae) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3

很明显看到,在执行完Deactivated事件后程序的线程都结束了,而但我点击Back按钮后,重新加载了程序,并且执行了MainPage的构造函数,而我们TextBox的值也从1开始了。下面看看如何来保存这些数据。对代码修改如下:

  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("MainPage OnNavigatedFrom"); 
  3.     base.OnNavigatedFrom(e); 
  4.     State["num"] = num; //保存变量 
  5.  
  6. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  7.     Debug.WriteLine("MainPage OnNavigatedTo"); 
  8.     base.OnNavigatedTo(e); 
  9.     num = (int)State["num"];//恢复变量 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    State["num"] = num; //保存变量
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedTo");
    base.OnNavigatedTo(e);
    num = (int)State["num"]; //恢复变量
}

我们在MainPage的两个方法中保存和恢复数据,但是运行却出错了。因为OnNavigatedTo中的State["num"]为空。因为我们第一次进入页面是也会执行这个方法,这时还没有保存过num,所以就出错了。在WP7.0中我们可以使用 State.ContainsKey或者State.TryGetValue来避免这种错误的发声,任何时候我们从字典中读取数据时都必须检查数据的有效性。而在WP7.1中我们有更好的办法:

  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("MainPage OnNavigatedFrom"); 
  3.     base.OnNavigatedFrom(e); 
  4.     if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back) 
  5.     { 
  6.         Debug.WriteLine("Save data"); 
  7.         State["num"] = num; //保存变量 
  8.       } 
  9.  
  10. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  11.      Debug.WriteLine("MainPage OnNavigatedTo"); 
  12.      base.OnNavigatedTo(e); 
  13.      if (e.NavigationMode != System.Windows.Navigation.NavigationMode.New) 
  14.      { 
  15.          Debug.WriteLine("Recover data"); 
  16.          object objNum; 
  17.          if (State.TryGetValue("num",out objNum)) 
  18.          { 
  19.              num = (int)objNum; 
  20.          } 
  21.      } 
  22.      txtNum.DataContext = num; 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
    {
        Debug.WriteLine("Save data");
        State["num"] = num; //保存变量
      }
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("MainPage OnNavigatedTo");
     base.OnNavigatedTo(e);
     if (e.NavigationMode != System.Windows.Navigation.NavigationMode.New)
     {
         Debug.WriteLine("Recover data");
         object objNum;
         if (State.TryGetValue("num", out objNum))
         {
             num = (int)objNum;
         }
     }
     txtNum.DataContext = num;
}

我们在看看结果:

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Add Method,TextBox is 4 
  7. MainPage OnNavigatedFrom 
  8. Save data 
  9. Application_Deactivated 
  10. The thread '<No Name>' (0xfa800f2) has exited with code 0 (0x0). 
  11. The thread '<No Name>' (0xf9500e6) has exited with code 0 (0x0). 
  12. The thread '<No Name>' (0xe3700ee) has exited with code 0 (0x0). 
  13. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  14. 'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll' 
  15. 'UI Task' (Managed): Loaded 'System.dll' 
  16. 'UI Task' (Managed): Loaded 'System.Windows.dll' 
  17. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  18. 'UI Task' (Managed): Loaded 'System.Core.dll' 
  19. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  20. 'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  21. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  22. 'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll' 
  23. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  24. Application_Activated 
  25. MainPage Constructor 
  26. MainPage OnNavigatedTo 
  27. Recover data 
  28. Add Method,TextBox is 5 
  29. Add Method,TextBox is 6 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xfa800f2) has exited with code 0 (0x0).
The thread '<No Name>' (0xf9500e6) has exited with code 0 (0x0).
The thread '<No Name>' (0xe3700ee) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 5
Add Method,TextBox is 6

数据被正常恢复了,我们可以看到,OnNavigatedTo方法虽然被执行了两次,但是Recover data只有在从墓碑或休眠状态返回时才执行,而在新建时是没有执行的。因为在WP7.1中开放了NavigationEventArgs 的NavigationMode属性。当我们新建一个页面进入时,此属性为New,而当我们从墓碑状态或其他页面返回时则是Back,很明显我们只有在Back时才需要恢复数据。而在OnNavigatedFrom方法中,当我们导航到新的页面时,此属性为New,而当我们回退到前一个页面时,此属性为Back,我们知道Windows Phone中只有Back Stack,所以从一个页面返回到前一个页面时,这个页面就会被回收,所以就没有必要保存数据了。而在WP7中我们每次都要进行保存和恢复操作。

但是这里还有一个问题,就是当我们从休眠状态返回时,是不需要进行数据恢复的,但是此方法还是会被执行,在WP7.1中同样提供了方法,可以在休眠状态返回时不执行这些操作。我们在Application的Activated 事件中可以判断,我们修App代码如下:

  1. public static bool IsTombstoning {get; set; } 
  2.  
  3. private void Application_Activated(object sender, ActivatedEventArgs e) 
  4.    Debug.WriteLine("Application_Activated"); 
  5.    if (e.IsApplicationInstancePreserved) 
  6.    { 
  7.        IsTombstoning = false
  8.    } 
  9.    else 
  10.    { 
  11.        IsTombstoning = true
  12.    } 
 public static bool IsTombstoning { get; set; }

private void Application_Activated(object sender, ActivatedEventArgs e)
{
   Debug.WriteLine("Application_Activated");
   if (e.IsApplicationInstancePreserved)
   {
       IsTombstoning = false;
   }
   else
   {
       IsTombstoning = true;
   }
}

WP7.1中新增了IsApplicationInstancePreserved属性来判断是休眠状态还是墓碑状态恢复的。我们在App中定义了一个静态的IsTombstoning 属性。在MainPage的OnNavigatedTo方法中使用就行了。有一点要注意的是不要在App的构造函数中对IsTombstoning 赋值,否则从墓碑状态返回后会执行构造函数,另外也不要在页面中定义一个变量来指示是否是新的实例,因为你需要保存或恢复这个变量。取消Debug中的墓碑调试,我们看看结果。

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is
  5. Add Method,TextBox is
  6. Add Method,TextBox is
  7. Add Method,TextBox is
  8. Add Method,TextBox is
  9. MainPage OnNavigatedFrom 
  10. Save data 
  11. Application_Deactivated 
  12. Application_Activated 
  13. MainPage OnNavigatedTo 
  14. Add Method,TextBox is
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
Add Method,TextBox is 5
Add Method,TextBox is 6
MainPage OnNavigatedFrom
Save data
Application_Deactivated
Application_Activated
MainPage OnNavigatedTo
Add Method,TextBox is 7

2 多页面的情况

前面已经了解了单页面情况下的数据恢复,那么如果我们从主页面切换到其他页面,这时进入了休眠或墓碑状态又会怎么样呢?因为这是我们当前已经不是在MainPage页面了。还是在Demo5中,我们点击Add后,点击Next按钮,进入到Page1,然后点Win键,进入主菜单。在Page1中我们没有进行数据存储和恢复,我们只关心MainPage中的数据。我们就看看墓碑情况,运行结果如下:

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is
  5. Add Method,TextBox is
  6. Main Pgee Navigate to Page1 
  7. Page1 Constructor 
  8. MainPage OnNavigatedFrom 
  9. Save data 
  10. Page1 OnNavigatedTo 
  11. Page1 OnNavigatedFrom 
  12. Application_Deactivated 
  13. The thread '<No Name>' (0xf88012a) has exited with code 0 (0x0). 
  14. The thread '<No Name>' (0xe220156) has exited with code 0 (0x0). 
  15. The thread '<No Name>' (0xf1d014a) has exited with code 0 (0x0). 
  16. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  17. 'UI Task' (Managed): Loaded'System.Windows.RuntimeHost.dll' 
  18. 'UI Task' (Managed): Loaded 'System.dll' 
  19. 'UI Task' (Managed): Loaded'System.Windows.dll' 
  20. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  21. 'UI Task' (Managed): Loaded'System.Core.dll' 
  22. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  23. 'UI Task' (Managed): Loaded'\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  24. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  25. 'UI Task' (Managed): Loaded'Microsoft.Phone.Interop.dll' 
  26. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  27. Application_Activated 
  28. Page1 Constructor 
  29. Page1 OnNavigatedTo 
  30. Bace to MainPage 
  31. MainPage Constructor 
  32. Page1 OnNavigatedFrom 
  33. MainPage OnNavigatedTo 
  34. Recover data 
  35. Add Method,TextBox is
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Main Pgee Navigate to Page1
Page1 Constructor
MainPage OnNavigatedFrom
Save data
Page1 OnNavigatedTo
Page1 OnNavigatedFrom
Application_Deactivated
The thread '<No Name>' (0xf88012a) has exited with code 0 (0x0).
The thread '<No Name>' (0xe220156) has exited with code 0 (0x0).
The thread '<No Name>' (0xf1d014a) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
Page1 Constructor
Page1 OnNavigatedTo
Bace to MainPage
MainPage Constructor
Page1 OnNavigatedFrom
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 4

事实证明,我们前面写的代码依旧有效,没有进行任何修改就完成了任务。我们看到在导航到Page1之前就进行了数据保存。因为OnNavigatedFrom不仅在进入墓碑状态前回执行,在离开当前页面时也会执行。所以,即便是在Page1中进入了墓碑状态,当我们返回程序,并返回到MainPage页面是,还是会执行OnNavigatedTo方法来恢复数据,这时的页面Mode也是Back。所以我们不需要为多页面做特殊处理。

我们考虑下,如果从Page1页面不是GoBack,而是使用Navigate方法重新导航到MainPage呢?这有什么关系,只不过是新建了一个页面的实例,我们前一个MainPage还在Back Stack中。那么数据会恢复到这个MainPage中吗,当然不会,我们使用的State属性是每个页面实例特有的。那我我如果想在每个新建的MainPage中都能继续进行Add操作呢?

对于这种变态的需求,一般我是懒得做,肯定是你设计有问题,但是这里还是演示下如何实现。除了使用Page的State,前面我们还提到使用Application的State,这个是所有页面共享的。我们在Page中增加一个New按钮,然后修改MainPage的方法:

  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("MainPage OnNavigatedFrom"); 
  3.     base.OnNavigatedFrom(e); 
  4.     Debug.WriteLine("Save data"); 
  5.     PhoneApplicationService.Current.State["num"] = num;//保存到全局 
  6.  
  7. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  8.     Debug.WriteLine("MainPage OnNavigatedTo"); 
  9.     base.OnNavigatedTo(e); 
  10.     Debug.WriteLine("Recover data"); 
  11.     object objNum; 
  12.     if (PhoneApplicationService.Current.State.TryGetValue("num",out objNum)) 
  13.     { 
  14.         num = (int)objNum; 
  15.         txtNum.DataContext = num; 
  16.     } 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    Debug.WriteLine("Save data");
    PhoneApplicationService.Current.State["num"] = num; //保存到全局
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedTo");
    base.OnNavigatedTo(e);
    Debug.WriteLine("Recover data");
    object objNum;
    if (PhoneApplicationService.Current.State.TryGetValue("num", out objNum))
    {
        num = (int)objNum;
        txtNum.DataContext = num;
    }
}

为了一个变量能在不同实例中同时起作用,并公用,我们修改了页面的方法。这里保存和恢复数据时都不在判断是否是New或Back,因为任何情况我们都要保存和恢复,以便在所有页面中使用。另外我们可以使用隔离数据区来存储和恢复。结果就是,无论我们在Page1页面从墓碑返回后, New一个MainPage,还是Back,或者是先New然后在返回到前一个MainPage,结果都是在任何页面都能对这个数据继续的使用。

当然更好的办法是在App中定义一个全局的变量,这样我们不需要在Page的页面中对这些数据进行保存和恢复,只需要在Appliation的事件中进行。这样代码会很简洁。

  1. public static int Number { get; set; } 
  2.  
  3. private void Application_Activated(object sender, ActivatedEventArgs e) 
  4.    Debug.WriteLine("Application_Activated"); 
  5.    if (e.IsApplicationInstancePreserved) 
  6.    { 
  7.        IsTombstoning = false
  8.    } 
  9.    else 
  10.    { 
  11.        IsTombstoning = true
  12.        Number = (int)PhoneApplicationService.Current.State["number"]; 
  13.    } 
  14.  
  15. private void Application_Deactivated(object sender, DeactivatedEventArgs e) 
  16.      Debug.WriteLine("Application_Deactivated"); 
  17.      PhoneApplicationService.Current.State["number"] = Number; 
public static int Number { get; set; }

private void Application_Activated(object sender, ActivatedEventArgs e)
{
   Debug.WriteLine("Application_Activated");
   if (e.IsApplicationInstancePreserved)
   {
       IsTombstoning = false;
   }
   else
   {
       IsTombstoning = true;
       Number = (int)PhoneApplicationService.Current.State["number"];
   }
}

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
     Debug.WriteLine("Application_Deactivated");
     PhoneApplicationService.Current.State["number"] = Number;
}

App中代码修改如上,我们在Activated和Deactivated中进行数据恢复和保存,因为Activated必定是在Deactivated执行后执行,所以这里没有判断是否存在。但还是应该写的严谨一些。下面是MainPage中的代码:

  1. protected overridevoid OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) 
  2.     Debug.WriteLine("MainPage OnNavigatedFrom"); 
  3.     base.OnNavigatedFrom(e); 
  4.     App.Number = num; 
  5.  
  6. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  7.     Debug.WriteLine("MainPage OnNavigatedTo"); 
  8.     base.OnNavigatedTo(e); 
  9.     num = App.Number; 
  10.     txtNum.DataContext = num; 
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    App.Number = num;
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedTo");
    base.OnNavigatedTo(e);
    num = App.Number;
    txtNum.DataContext = num;
}
  1. 代码是非常的简单,如果不是为了掩饰保存和恢复,我们甚至可以直接对App.Number来操作。相比前一种方法,代码简洁了很多。结果这里就不贴了。 
代码是非常的简单,如果不是为了掩饰保存和恢复,我们甚至可以直接对App.Number来操作。相比前一种方法,代码简洁了很多。结果这里就不贴了。

3 使用Chooser的情况

在程序中我们可以使用Launcher或者Chooser来运行系统提供的API功能,比如打电话,发短信,调用相机等等。具体的使用我们后面会介绍。一般使用这些API都会导致当期那程序变为后台程序。所以也存在数据恢复的问题,但是对于Chooser来说,他会返回数据,而Lanucher不会返回数据,那么这个时候就存在一个新得到的数据和被保存的数据我们应该用哪一个的问题。

看下面的列子,我们调用相机,返回后得到图片的大小,把大小填入TextBox。

  1. private CameraCaptureTask camera; 
  2.  
  3. public MainPage() 
  4.    num = 1; 
  5.    camera = new CameraCaptureTask(); 
  6.    camera.Completed += new EventHandler<PhotoResult>(camera_Completed); 
  7.    InitializeComponent(); 
  8.    txtNum.DataContext = num; 
private CameraCaptureTask camera;

public MainPage()
{
   num = 1;
   camera = new CameraCaptureTask();
   camera.Completed += new EventHandler<PhotoResult>(camera_Completed);
   InitializeComponent();
   txtNum.DataContext = num;
}

我们先定义相机的Chooser,注意这里要定义成类变量,而不能是局部变量,并且设置完成事件的处理方法。因为如果是局部变量,当系统进入到墓碑状态在返回时,这个对象就没有了,就没办法执行完成事件了,对于Chooser来说也没有办法获得得到返回的数据了。

  1. private void Button_Click_2(object sender, RoutedEventArgs e) 
  2.     Debug.WriteLine("Run Camera"); 
  3.     try 
  4.     { 
  5.        camera.Show(); 
  6.     } 
  7.     catch (System.InvalidOperationException ex) 
  8.     { 
  9.        MessageBox.Show(ex.Message); 
  10.     } 
  11.  
  12. void camera_Completed(object sender, PhotoResult e) 
  13.     if (e.TaskResult == TaskResult.OK) 
  14.     { 
  15.         BitmapImage bmp = new BitmapImage(); 
  16.         bmp.SetSource(e.ChosenPhoto); 
  17.         num = bmp.PixelHeight; 
  18.     } 
  19.      else 
  20.     { 
  21.          num = 1024; //模拟器没法拍照 
  22.      } 
private void Button_Click_2(object sender, RoutedEventArgs e)
{
    Debug.WriteLine("Run Camera");
    try
    {
       camera.Show();
    }
    catch (System.InvalidOperationException ex)
    {
       MessageBox.Show(ex.Message);
    }
}

void camera_Completed(object sender, PhotoResult e)
{
    if (e.TaskResult == TaskResult.OK)
    {
        BitmapImage bmp = new BitmapImage();
        bmp.SetSource(e.ChosenPhoto);
        num = bmp.PixelHeight;
    }
     else
    {
         num = 1024; //模拟器没法拍照
     }
}

以上我们点击Run Camera按钮时,就调用Show方法调出,拍完照片后吧照片的高度设置到TextBox,因为模拟器拍不了,就手动设置到1024。页面的数据我们采用第一种,单页面的保存方式。

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Run Camera 
  7. MainPage OnNavigatedFrom 
  8. Save data 
  9. Application_Deactivated 
  10. Application_Activated 
  11. Set carmera num 
  12. MainPage OnNavigatedTo 
  13. Add Method,TextBox is 1025 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Run Camera
MainPage OnNavigatedFrom
Save data
Application_Deactivated
Application_Activated
Set carmera num
MainPage OnNavigatedTo
Add Method,TextBox is 1025

从上面结果可见程序调用相机时进入休眠状态 ,返回后先执行了完成函数,Set carmera num后数据变成了1024。因为是休眠不需要恢复状态,所以在此加1后变为1025。我们在看看从墓碑状态返回的结果。

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Run Camera 
  7. MainPage OnNavigatedFrom 
  8. Save data 
  9. Application_Deactivated 
  10. The thread '<No Name>' (0xf3802a2) has exited with code 0 (0x0). 
  11. The thread '<No Name>' (0xe0a02da) has exited with code 0 (0x0). 
  12. The thread '<No Name>' (0xea002da) has exited with code 0 (0x0). 
  13. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  14. 'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll' 
  15. 'UI Task' (Managed): Loaded 'System.dll' 
  16. 'UI Task' (Managed): Loaded 'System.Windows.dll' 
  17. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  18. 'UI Task' (Managed): Loaded 'System.Core.dll' 
  19. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  20. 'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  21. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  22. 'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll' 
  23. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  24. Application_Activated 
  25. MainPage Constructor 
  26. Set carmera num 
  27. MainPage OnNavigatedTo 
  28. Recover data 
  29. Add Method,TextBox is 4 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Run Camera
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xf3802a2) has exited with code 0 (0x0).
The thread '<No Name>' (0xe0a02da) has exited with code 0 (0x0).
The thread '<No Name>' (0xea002da) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
Set carmera num
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 4

我们发现从墓碑状态返回后,虽然也进行了Set carmera num,但是还进行了Recover data,最新得到数据被之前恢复的数据覆盖了。我们看到返回后先执行了页面的构造函数,然后执行了相机的完成方法,这里要注意的是,不要在这里操作控件,因为此时Load事件还没有执行,这里操作控件会抛出一个空引用异常。

获得的数据和保存的数据间的选择是比较容易碰到的问题。我们要根据自己的逻辑来选择解决的方法,这里我们使用最新的数据来代替保存的数据。所以修改恢复数据的部分:

  1. protected overridevoid OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
  2.    Debug.WriteLine("MainPage OnNavigatedTo"); 
  3.    base.OnNavigatedTo(e); 
  4.  
  5.     //单页面情况 
  6.      if (App.IsTombstoning) 
  7.     { 
  8.        if (e.NavigationMode != System.Windows.Navigation.NavigationMode.New) 
  9.        { 
  10.           if (num == 1) 
  11.           { 
  12.             Debug.WriteLine("Recover data"); 
  13.             object objNum; 
  14.             if (State.TryGetValue("num",out objNum)) 
  15.             { 
  16.                num = (int)objNum; 
  17.             } 
  18.           } 
  19.        } 
  20.     } 
  21.     txtNum.DataContext = num; 
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
   Debug.WriteLine("MainPage OnNavigatedTo");
   base.OnNavigatedTo(e);

    //单页面情况
     if (App.IsTombstoning)
    {
       if (e.NavigationMode != System.Windows.Navigation.NavigationMode.New)
       {
          if (num == 1)
          {
            Debug.WriteLine("Recover data");
            object objNum;
            if (State.TryGetValue("num", out objNum))
            {
               num = (int)objNum;
            }
          }
       }
    }
    txtNum.DataContext = num;
}

我们知道如果进入到墓碑状态返回后,num的数据会丢失,而我们在构造函数中把num设置为1。因为在调用OnNavigatedTo之前会执行方法相机的返回方法camera_Completed。如果num被设置为1024就不应该恢复数据。所以我们可以在OnNavigatedTo中通过num的值来判断是否恢复,如果是1就需要恢复,如果不是就说明有新的值。

这里需要注意,如果在 camera_Completed中得到的数据就是1,那么还是会被恢复,所以一定根据情况来。这里只是举例子。另外我们要注意构造函数中num初始化和camera.Completed事件绑定之间的顺序,一定要先初始化数据,最后在注册完成事件,否则执行完camera_Completed方法后,num又被初始化了。

下面看看执行结果:

  1. <strong>Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. Run Camera 
  7. MainPage OnNavigatedFrom 
  8. Save data 
  9. Application_Deactivated 
  10. The thread '<No Name>' (0xe83037a) has exited with code 0 (0x0). 
  11. The thread '<No Name>' (0xeb50372) has exited with code 0 (0x0). 
  12. The thread '<No Name>' (0xf53033a) has exited with code 0 (0x0). 
  13. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  14. 'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll' 
  15. 'UI Task' (Managed): Loaded 'System.dll' 
  16. 'UI Task' (Managed): Loaded 'System.Windows.dll' 
  17. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  18. 'UI Task' (Managed): Loaded 'System.Core.dll' 
  19. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  20. 'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  21. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  22. 'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll' 
  23. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  24. Application_Activated 
  25. MainPage Constructor 
  26. Set carmera num 
  27. MainPage OnNavigatedTo 
  28. Add Method,TextBox is 1025 
  29. </strong> 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Run Camera
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xe83037a) has exited with code 0 (0x0).
The thread '<No Name>' (0xeb50372) has exited with code 0 (0x0).
The thread '<No Name>' (0xf53033a) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
Set carmera num
MainPage OnNavigatedTo
Add Method,TextBox is 1025

和之前的区别就在于这里没有恢复数据,最后使用的是相机得到的数据。对于没有调用相机,而是点击Win导致的程序恢复,这时恢复的数据,结果正确

  1. Application_Launching 
  2. MainPage Constructor 
  3. MainPage OnNavigatedTo 
  4. Add Method,TextBox is 2 
  5. Add Method,TextBox is 3 
  6. MainPage OnNavigatedFrom 
  7. Save data 
  8. Application_Deactivated 
  9. The thread '<No Name>' (0xebe0382) has exited with code 0 (0x0). 
  10. The thread '<No Name>' (0xeb203be) has exited with code 0 (0x0). 
  11. The thread '<No Name>' (0xec103d6) has exited with code 0 (0x0). 
  12. 'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll' 
  13. 'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll' 
  14. 'UI Task' (Managed): Loaded 'System.dll' 
  15. 'UI Task' (Managed): Loaded 'System.Windows.dll' 
  16. 'UI Task' (Managed): Loaded 'System.Net.dll' 
  17. 'UI Task' (Managed): Loaded 'System.Core.dll' 
  18. 'UI Task' (Managed): Loaded 'System.Xml.dll' 
  19. 'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded. 
  20. 'UI Task' (Managed): Loaded 'Microsoft.Phone.dll' 
  21. 'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll' 
  22. 'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll' 
  23. Application_Activated 
  24. MainPage Constructor 
  25. MainPage OnNavigatedTo 
  26. Recover data 
  27. Add Method,TextBox is 4 
Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xebe0382) has exited with code 0 (0x0).
The thread '<No Name>' (0xeb203be) has exited with code 0 (0x0).
The thread '<No Name>' (0xec103d6) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 4

四 禁用墓碑

如果你拥有一台真机,在升级到到芒果之前,或许总是要忍受恢复程序时的"Resuming"的提示。当然也不是没有办法,网上就流传了修改注册表来禁用墓碑状态的方法。首先你需要有一台已经越狱的机器,然后下载一个RegistryEditor的XPA软件包。运行这个软件,把\HKLM\Software\Microsoft\TaskHost\DehydrateOnPause的键值从3修改为0。再试试,程序切换的很快了,"Resuming"基本也不出现了。我买手机的时候Mango的Beta版已经出了,而且注册表也已经被卖家修改好了。所以在手机上还没见到"Resuming"就升级到Mango了。在我升级到7712之后,偶尔却会出现"Resuming",所以决定研究下修改这个注册表的作用。

搜索了好久,终于找到了一篇相关的文章:Tombstoning, Dehydration, and Windows Phone  。从文章中我们可以了解到,这个键值有4个选项:

  1. Don’t dehydrate
  2. Forcefully dehydrate
  3. Gracefully dehydrate
  4. Automatically decide

表示程序在暂停时是否执行Dehydrate,Dehydrate愿意是脱水。在进入墓碑状态后,一些系统级别的操作会停止,运行环境开始回收资源,其中就包括.NET运行时,回收完以后整个程序的运行环境也被回收了。这样一个过程就叫做Dehydrate。

系统默认是3,表示命令程序的运行环境在合适的时候优雅的执行Dehydrate。但是目前并不清楚是如何决定是否合适。但是对运行环境来说默认是执行Dehydrate,但是也可以选择不进行Dehydrate。

  1. HRESULT SHSetAutoDehydratingHostEligibility(BOOL fEligible); 
HRESULT SHSetAutoDehydratingHostEligibility(BOOL fEligible);

通过这个方法可以设置程序运行环境是否进行Dehydrate。当我们选择2时,系统总是会执行Dehydrate,而选择1时,系统会给程序发送一个WM_CLOSE消息来强制,理论上来说我们可以捕获这个消息,来自己决定是否执行Dehydrate。而当设置为0是,Dehydrate被禁用了,也就是我们程序的运行环境不会被回收,.NET运行时,程序的实例都不会被销毁。而在我们恢复程序是,主要是创建新的实例和.NET运行时,导致了出现"Resuming"。

所以我们修改注册表为0后,系统看起来就系象7.1中的休眠状态一样了。而且程序恢复也不会创建新的实例,所以并不清楚和WP7.1的休眠有什么区别。也有可能7.1的休眠也是采用这种方式,默认不执行Dehydrate,而在资源不足时执行Dehydrate进入墓碑。这样在恢复时会有Resuming字样,而在WP7.0中不执行Dehydrate,当资源不够时程序会被结束,所以不会出现Resuming字样。这是我个人的猜测。在Mango中用不了注册表工具,没法查看。也不知道此键值是否还效或者是否有修改。

另外系统规定只能有5个程序进入墓碑状态,我在真机上看了下,最多也只能有5个第三方的程序运行,而不管是休眠还是墓碑状态。当你开第六个程序时,最早使用的程序就会被关闭,大家可以长按Back按钮来观察。

五 总结

通过上面介绍我们发现对于我们程序而言,墓碑机制中对数据的保存和恢复是我们需要关注的地方。我们通过三种情况,介绍了对页面已经程序数据的保存和恢复方法,以及决定是否恢复数据的一般方法。了解了程序的执行模型。其中Page中的OnNavigatedTo和OnNavigatedFrom是最重要的方法。另外所有保存的数据必须可以被序列化。

下面是微软关于执行模型的最佳时间方法:http://msdn.microsoft.com/zh-cn/library/ff817009.aspx

其中介绍了一些文章中没有涉及的部分,比如当程序从墓碑模式被终止的时候需要保存数据到隔离区。所以我们应该在Deactivated事件中就进行这样的操作,因为我们无法预料程序可能进入的状态。另外有一点要指出,当我们程序被切换至后台,进入到休眠或墓碑状态时,此时我们点击程序图标,重新启动一个实例时,之前的实例的数据都无法恢复。想解决这个问题只能把数据存放到隔离区,然后恢复。但是会出现欢迎界面,对于有登陆界面的的还会出现登陆界面,我们需要进行更多的处理,才能让用户感觉不到是新开的程序。另外其中还建议Application中的事件以及页面中的两个方法操作的时间不要超过10s,否则程序会被终止。但是我尝试了下使用Thread.Sleep(15000),程序并没有被终止。

Demo5代码下载http://download.csdn.net/source/3500401

猜你喜欢

转载自blog.csdn.net/xujunfeng000/article/details/8270438