首页 > 代码库 > WinPhone学习笔记(一)——页面导航与页面相关
WinPhone学习笔记(一)——页面导航与页面相关
最近学一下Windows Phone(接下来简称“WinPhone”)的开发,在很久很久前稍探究一下WinPhone中对一些传感器的开发,那么现在就从头来学学WinPhone的开发。先从WinPhone的页面入手,在我印象中比较深刻的那番话:一台WinPhone设备就好比一个Web的浏览器,应用上每个界面就是一个网页,可以点击“后退”来返回之前的页面。这个类比我觉得相当的形象。这番话能引出WinPhone开发中一个比较常见的操作——页面导航,由这个页面导航还引出了别的方面的内容,如下面所示
那下面的介绍将会从导航,启动其他应用,传参,生命周期,后退堆栈这个顺序一步步进行总结。
页面导航
从一个页面跳转到另一个页面,局限于本应用的页面而言,则是调用Page类里面NavigationService类型并且同名的属性来进行导航,一般的跳转用到的是
public bool Navigate(Uri source)
方法,里面的Uri则是跳转到页面的路径,例如在当前的项目里面除了建立项目时默认建立的MainPage.xaml外,还另外多建了页面Page1.xaml,当我们要把页面从MainPage跳转到Page1的时候,则Navigate方法,形式如下
this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative))
感觉跟我们平时写Web页面时很相似,其中Uri的构造函数中有一个UriKind枚举,我们一般填Realtive,如果填Absolute则需要把Uri改成绝对地址。
NavigationService中还有其他方法
public void GoBack();public void GoForward();
这些方法都体现着导航的特性。但在日常的使用中,一般就会使用GoBack这个方法,它的效果就如键盘上的后退键一样。但GoForward感觉没多大作用。
启动其他应用
在Android中,例如拨打电话,查看联系人,浏览某个页面,这些操作都可以使用意图(Intert)去启动相关的Activity,在Windows Phone中,启动其他应用的操作则分为两种,
- 一种是利用Launcher,通过URI的关联来启动应用,这种能启动第三方的应用和少部系统内置应用;
- 另一种是利用启动器和选择器,这个就能启动系统内置应用了;
那么先看看第一种通过URI关联的,URI关联主要利用Luancher这个类,调用LaunchUriAsync方法,传入相应的URI则启动相关的应用,这种方式与平时我们打开一个doc文件就能打开word,xls文件就能打开excel类似。他是通过关联的。例如
Launcher.LaunchUriAsync(new Uri("ms-settings-wifi:"));
就能打开wifi的设置页面;下面列举一下WinPhone内置的一些URI;
- bing:
- ms-settings-emailandaccounts: 启动电子邮件和帐户设置应用。
- callto:
- ms-settings-location: 启动位置设置应用。
- dtmf:
- ms-settings-lock: 启动锁屏设置应用。
- http: 启动 Web 浏览器并导航到特定的 URL。
- ms-settings-wifi: 启动 Wi-Fi 设置应用。
- https: 启动 Web 浏览器并导航到特定的 URL。
- ms-word:
- mailto: 启动电子邮件应用并使用“收件人”一行上特定的电子邮件地址创建新邮件。请注意,在用户点击“发送”之前,不会发送电子邮件。
- office:
- maps:
- onenote:
- ms-excel:
- tel:
- ms-powerpoint:
- wallet:
- ms-settings-airplanemode: 启动飞行模式设置应用。
- xbls:
- ms-settings-bluetooth: 启动蓝牙设置应用。
- zune:
- ms-settings-cellular: 启动手机网络设置应用。
- ms-settings-power: 启动节电模式设置应用。
- ms-settings-screenrotation: 启动屏幕旋转设置应用
- zune:navigate?appid=[app ID] 启动 Windows Phone 应用商店 并显示特定应用的详细信息页面。
- zune:reviewapp 启动商店 并显示调用应用的查看页面。
- zune:reviewapp?appid=app[app ID] 启动商店 并显示特定应用的查看页面。请注意,您必须在指定应用的 ID 前面加上“app”。例如,检查 ID 为 fdf05477-814e-41d4-86cd-25d5a50ab2d8 的应用时,URI 为 zune:reviewapp?appid=appfdf05477-814e-41d4-86cd-25d5a50ab2d8
- zune:search?keyword=[search keyword]&publisher=[publisher name]&contenttype=app 启动商店 并搜索特定的内容。所有参数都是可选的。指定“contenttype=app”将限制对应用的搜索。省略此参数将搜索所有内容。
- zune:search?keyword=[search keyword]&contenttype=app 启动商店 并按关键字搜索应用。
- zune:search?publisher=[publisher name] 启动商店 并按发布者名称搜索项目。
但是上面ms-settings-screenrotation:这个属性是只有WinPhone8 Update3才有,所以最好用一下判断,Update3的版本号是8, 0, 10492;当前版本号是Environment.OSVersion.Version
如果要启动第三方的应用,则需要找到该应用的fileToken,按以往的情况可以解开xap文件,从里面的AppManifest.xml文件可以看到,但最近从应用商店下的xap都无法解包。这个验证不了。
再看另外一种利用启动器和选择器。这种就比较接近Android的启动方式,这里会有两个新的概念——选择器和启动器,两个概念都很相像,都是启动某个内置的应用,但区别在于启动器不会向应用程序返回数据或状态,而选择器可向应用程序返回数据和状态。在编码角度上看启动器只需要对像调用方法操作;选择器则需要注册Completed事件,再事件中进行响应地操作,Completed事件也是启动器所没有的。
看看实际的例子,要用到启动器和选择器需要加上
Microsoft.Phone.Tasks;
这个命名空间,用对象浏览器能看到里面有很多以Task后缀的类,微软官网上的例子涉及到PhoneCallTask,SmsComposeTask,EmailComposeTask,PhoneNumberChooserTask等类的例子,在这里调用起来则与Android的很类似,Android中如果要打开“联系人”这个内置应用事需要在Manifest.xml里面增加相应的权限,WinPhone的也一样,大多数都需要增加相应的权限,配置权限的可以在WMAppManifest.xml的功能选项卡里设置,例如下面这段拨打电话的代码
PhoneCallTask phoneCallTask = new PhoneCallTask(); //phoneCallTask.DisplayName = "Allen"; phoneCallTask.PhoneNumber = "10086"; phoneCallTask.Show();
需要设置ID_CAP_CONTACTS和ID_CAP_PHONEDIALER才行。每次使用启动器选择器配置完相关信息之后,调用了Show()方法,应用才会启动,主调应用进入了休眠状态或者被逻辑删除。
这里有两个怪诞的地方,如果用第一种方式URI的tel:10086则不需要获取权限;只有设置了PhoneNumber属性才会真正地可以拨打电话,否则只会显示人名DisplayName,这里他不会和人脉里面的关联起来。
这里先列举WinPhone中各个选择器和启动器,还有各个功能
选择器
- CameraCaptureTask-打开照相机应用程序拍照 返回类型为 PhotoResult
- PhotoChooserTask-从Picture Gallery中选择一张图片 返回类型为 PhotoResult
- EmailAddressChooseTask-从Contacts List中选择一个电子邮件地址
- PhotneNumberChooserTask-从Contacts List中选择一个电话号码
- SaveEmailAddressTask-为联系人保存一个邮件地址
- SavePhoneNumberTask-为联系人保存一个电话号码
启动器
- EmailComposeTask-撰写新的电子邮件
- PhoneCallTask-向指定的电话号码拨打电话
- SmsComposeTask-写新信息
- SearchTask-指定关键字进行Bing搜索服务
- WebBrowserTask-启动IE浏览器打开指定URL
- MarketplaceDetaiTask-启动Marketplace并指定应用程序的详细信息
- MarketplaceHubTask-启动Marketplace并显示两个Hub其中一个的Applications或者Music
- MarketplaceReviewTask-启动Marketplace并为应用程序提供评论
- MarketplaceSearchTask-启动Marketplace并执行相关搜索
- MediaPlayerLauncher-启动MediaPlayer
功能
- ID_CAP_APPOINTMENTS 访问约会数据的应用程序。
- ID_CAP_CAMERA 使用相机功能的应用程序。该值仅供移动运营商和原始设备制造者使用。应用程序开发人员使用 ID_CAP_ISV_CAMERA。
- ID_CAP_CONTACTS 访问联系人数据的应用程序。
- ID_CAP_GAMERSERVICES 可以与 Xbox LIVE API 交互的应用程序。由于数据与 Xbox 共享,鉴于隐私问题,必须公开此信息。
- ID_CAP_IDENTITY_DEVICE 使用设备特定信息(如唯一设备 ID、制造商名称或模型名称)的应用程序。
- ID_CAP_IDENTITY_USER 使用匿名 LiveID 以匿名方式唯一识别用户的应用程序。
- ID_CAP_ISV_CAMERA 使用相机功能的应用程序。
- ID_CAP_LOCATION 可访问本地服务的应用程序。
- ID_CAP_MEDIALIB 可访问媒体库的应用程序。
- ID_CAP_MICROPHONE 使用麦克风的应用程序。可以在未显示正在记录的情况下进行记录的应用程序。
- ID_CAP_NETWORKING 可访问网络服务的应用程序。由于在手机漫游时服务可能会产生费用,因此必须公开此信息。
- ID_CAP_PHONEDIALER 可发出电话呼叫的应用程序。此操作可在未向最终用户显示信息的情况下完成。
- ID_CAP_PUSH_NOTIFICATION 可从 Internet 服务接收推送通知的应用程序。由于在使用时会产生漫游费用,因此必须公开此消息。
- ID_CAP_SENSORS 使用 Windows Phone 传感器的应用程序。
- ID_CAP_WEBBROWSERCOMPONENT 使用 Web 浏览器组件的应用程序。存在涉及脚本的安全风险。
- ID_HW_FRONTCAMERA "使用硬件正面相机的应用程序。如果需要,您必须手动添加这一功能,此功能必须与 ID_CAP_ISV_CAMERA 功能一起使用。
之所以留意这个启动应用的皆因之前用过一个Switch to iOS,它通过一个app把WinPhone换了脸一样,其实里面主要是启动各个内置应用和第三方应用的快捷方式集合。
参数传递
正常页面跳转都会碰到传递参数的问题,会有从页面A跳到页面B时传递相关的参数,也有从页面B返回页面A要返回参数。这两个方式都分别尝试一下。
像第一种情况,从页面A跳到页面B时传递相关的参数的,如使用Http的GET方法请求一样,以下面的形式
/Page1.xaml?paraName=admin
传入的这些参数在页面导航的时候,会被解析处理来并存入到一个字典里面去,到了页面B,通过NavigationContext的QueryString属性可以获得,这个QueryString属性是IDictionary<string, string>类型,那么我们如果要获取值时可以用一般的
NavigationContext.QueryString[“paraName”]
来获取,但同样也可以使用比较保险的如
string value; NavigationContext.QueryString.TryGetValue("paraName", out value);
其实这里涉及到的是字典集的使用方式了。
像第二种情况,从页面B返回页面A要返回参数,但这个需要在页面A的代码中定义个公共属性用来访问,如
public string paraName { get; set; }
那么在页面B中只需要访问到页面A的实例,设置这个属性paraName则可,那么我们在页面B的代码中重写OnNavigatedFrom方法,如
protected override void OnNavigatedFrom(NavigationEventArgs e) { (e.Content as MainPage).paraName = ""; base.OnNavigatedFrom(e); }
至于OnNavigated方法会在以后介绍。
除了用这两种方式去传递数据外,还可以同过数据共享的形式,使得数据可以跨页读写,这个其实用的是全局参数的思想,在App类里面定义相应的属性,如
public string paraName { get; set; }
由于这个App类是Application的一个子类,父类Application中有一个静态属性Current,顾名思义可以获取到当前的一个实例,App的实例同样可以用这样的方式去获取,但是属性返回的是Application类型的,所以还需要进行一个类型转换才能访问到App内定义的属性。传递参数到页面可以按以下形式
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) { //其他操作 (App.Current as App).paraName = ""; base.OnNavigatedFrom(e); }
当要获取值的时候则需要以下面的形式
protected override void OnNavigatedFrom(NavigationEventArgs e) { //其他操作 string paraName = (App.Current as App).paraName; base.OnNavigatedFrom(e); }
这里就没有分是传入参数和返回参数了,只有参数的Get和Set的时机之分。
生命周期
记得在学Android的时候,有一幅很经典的活动声明周期示意图,展示了一个活动各个阶段的状态以及会触发的事件。那么在Windows Phone里面,也有类似的图片去描述一个应用的生命周期。了解这幅图目的在于了解应用在各个情况下的状态以及会触发的事件,在适当时机能借助事件的触发进行某些操作。
在上图而言绿色部分是对整个App而言的,灰色部分是对某一个页面来说的,页面的事件比Android活动的事件少了,这样显得更简单。那下面通过一个程序来验证这几个事件的触发顺序。
在App.xaml.cs的几个方法改成下面形式
private void Application_Launching(object sender, LaunchingEventArgs e) { Debug.WriteLine("Application_Launching"); } private void Application_Activated(object sender, ActivatedEventArgs e) { Debug.WriteLine("Application_Activated"); } private void Application_Deactivated(object sender, DeactivatedEventArgs e) { Debug.WriteLine("Application_Deactivated"); } private void Application_Closing(object sender, ClosingEventArgs e) { Debug.WriteLine("Application_Closing"); }
在MainPage.xaml.cs里面加入以下两个方法
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { Debug.WriteLine("Main_Page_OnNavigatedTo"); base.OnNavigatedTo(e); } protected override void OnNavigatedFrom(NavigationEventArgs e) { Debug.WriteLine("Main_Page_OnNavigatedFrom"); base.OnNavigatedFrom(e); }
在App.xaml里面更改的方法是上图中绿色部分的事件绑定的方法,这些都是对整个App而言的,而在MainPage.xaml里面加的方法则是上图灰色部分的方法,它只是作用于某个页面。运行一下程序,查看“输出”的结果可以看出启动app时的结果
Application_Launching
Main_Page_OnNavigatedTo
然后程序就到达了正在运行的状态,这时按开始键跳转到开始界面,“输出”界面的内容
Main_Page_OnNavigatedFrom
Application_Deactivated
这时app进入了休眠状态或者已经墓碑化,从后台重新打开app,“输出”界面的内容
Application_Activated
Main_Page_OnNavigatedTo
App被唤醒重新进入运行状态,这时按一下后退键,“输出”界面的内容
Main_Page_OnNavigatedFrom
Application_Closing
程序退出了。
如果重新打开app,用Win8 Update 2新增的在后台关闭进程的方式关闭app,“输出”界面的内容是
Main_Page_OnNavigatedFrom
Application_Deactivated
而并非是正常退出app时触发的Closing事件。可见Closing事件不一定会在结束一个app时触发。
OnNavigatedFrom和OnNavigatedTo这两个方法只是针对页面而言的,所以在一个app被激活或者被启动时最迟被执行,退出app和缩到后台时最早被执行。在一个app里各个页面的切换,对应的OnNavigatedFrom和OnNavigatedTo都一样会被执行。所以在页面跳转即将离开本页面或者即将到达本页面要附加的操作,都能在这两个方法里执行。
后退堆栈
说到后退堆栈还是要重提本文开始提到的那番话,既然在一个app运行过程中从页A跳到页B,页B跳到页C,然后类似地跳转下去,当用户想返回页A或者退出app时(不考虑用其他快捷操作或从后台结束程序),必须按“原路”返回,一层一层地返回回去。我们平时浏览网页的时候也一样,这样的形式正式数据结构中栈的特性先进后出。
对于栈的操作,无非是查看栈顶的元素,弹栈,压栈。回到WinPhone的页面导航中,当app启动到达页A时,后退堆栈是空的,当页A跳到页B时,页A就被压倒栈中,页A是在栈顶;当页B跳到页C时,页B被压到栈里,栈B就在栈顶。
那么可以通过App类中有个名为RootFrame属性的,它是PhoneApplicationFrame类型,这个类封装了对后退堆栈的部分操作,这些操作顾及到整个后退堆栈的合理性,只提供了枚举所有栈元素以及弹栈这两个堆栈操作,压栈的就没有提供方法,因为压栈这个操作实际上就在页面跳转的时候完成的。而且若提供压栈操作会有导致导航混乱的危险。
获取后退堆栈元素的属性是一个名为BackStack的JournalEntry类型的枚举接口;弹栈操作通过RemoveBackEntry()方法来完成,该方法返回的是被弹出来的元素,是RemoveBackEntry类型的。
在学习这个后退堆栈的时候鄙人用过微软提供的例子。那下面的例子也是仿照微软的例子写出来的。
新建了一个项目并建立三个页面:BSPage1.xaml, BSPage2.xaml, BSPage3.xaml,
每个页面的ContentPanel定义如下
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.RowDefinitions > <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Button Content="Pop Top" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" Margin="10,21,0,0" VerticalAlignment="Top" Click="Button_Click"/> <TextBlock x:Name="lbBackStack" HorizontalAlignment="Left" Margin="33,25,0,0" Grid.Row="1" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Top"/> <Button Content="NextPage" HorizontalAlignment="Left" Margin="150,21,0,0" Grid.Row="0" Grid.Column="1" VerticalAlignment="Top" Width="162" Click="Next_Click"/> </Grid>
但是BSPage3.xaml可以删掉NextPage那个Button。每个页面的隐藏代码都添加以下内容
string title = "BSPage1"; public BSPage1() { InitializeComponent(); this.tbTitle.Text = title; } protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); this.lbBackStack.Text = BackStackTool.GetBackStackItems(); } private void Button_Click(object sender, RoutedEventArgs e) { App.RootFrame.RemoveBackEntry(); this.lbBackStack.Text = BackStackTool.GetBackStackItems(); } private void Next_Click(object sender, RoutedEventArgs e) { this.NavigationService.Navigate(new Uri("/BSPage2.xaml",UriKind.Relative)); }
对不同的页面,BSPage的需要会有所更换,最后的BSPage3里面还可以吧Next_Click这个方法给去掉,因为它是最后一页,根本不需要在往前跳转了。在这里操控到后退堆栈的就是Button_Click的App.RootFrame.RemoveBackEntry();,作用是从后退堆栈弹出一个元素,里面调用到BackStackTool的GetBackStackItems方法,它实际上是遍历整个后退堆栈的各个元素,然后转成字符串显示到TextBlock里面
public static string GetBackStackItems() { string result=string.Empty; foreach (JournalEntry item in App.RootFrame.BackStack) { result += item.Source.ToString()+"\r\n"; } return result; }
这里没用上的一个方法则是App. RootFrame.Source,它的作用是获取当前页面的Uri,运行app可以看到效果,点击Pop Top就会看到页面从后退堆栈中弹出,被弹出的页面再也不能后退到那里去了。
在实践微软的那个例子的时候误会到那个Pop To Selected 按钮的作用了,最初一直以为是只弹出选中的元素,但这个按堆栈的特性还有后退堆栈这个性质实际上不能实现的。
上面讲了挺多的,估计会有不少错漏,恳请各位批评指正,鄙人虚心接纳,谢谢!