导航框架组件
核心组件:Frame控件和Page类,导航操作类:NavigationService类和NavigationContext类。
Frame控件
Frame控件是view的主容器。通常用于应用程序的外壳,用于承载view作为内容。当提供一个URI指定视图去加载和显示时,就需要通过Frame控件导航到该URL所指向的视图上。Frame控件提供了浏览历史功能的集成,可以处理由浏览器发起的导航事件,比如点击向前、向后按钮,在地址栏中输入新URI等。Visual Studio提供的SilverLight业务应用程序模板的MainPage.xaml文件,就包含一个Frame控件,充当应用程序的外壳。
Page类
View必须继承于Page类,SilverLight业务应程序模板的Home.xaml与About.xaml文件都继承自Page类。在每个View里,可以设置Title属性(继承于Page类),将显示在浏览器的标题栏上;
NavigationService对象
每个Frame控件实例创建了NavigationService对象,该对象可以在各个view上共享,可以通过Page类的属性进行访问。该对象用于保持各个View与Host(Frame控件)的独立性,并且包含了可以用于通过编程在应用程序内进行导航的各种方法。
NavigationContext对象
NavigationContext对象包含了有关导航操作的信息,这些操作包括显示view,处理查询字符串等。
在View间进行导航
导航操作即可以通过代码实现,也可以通过控件(如HyperLinkButton控件),属性绑定或浏览器事件(向前,向后按钮以及地址栏URI变更)来实现 。
使用View URI
例如模板里的Home.xaml视图就需要通过/Views/Home.xaml这样的URI进行访问。在尝试进行加载View时,需要使用URI mapper进行解析,这种解析类似于MVC里的地址路由,这需要进行详细的配置。如果需要导航到其他程序集里的View,需要在路径前加上/[assembly];componet/[viewpath],比如,Home.xaml视衅专卖店装在MyViews.dll程序集时的Views文件夹里,就需要使用”/MyViews;componet/Views/Home.xaml”来进行访问。
使用Frame控件进行导航
想要在Frame控件内进行视图的导航,只需要调用Frame控件的如下方法即可:
- Navigate
ContentFrame.Navigate(new Uri("/Views/Home.xaml",UriKind.Relativ));
- GoBack:模拟用户点击Back按钮;
- GoForward:模拟用户点击Forward按钮;
- StopLoading:用于取消视图加载时能够完成异步工作,使用内容能够定制加载;
- Refresh:用于强制重新加载视图,因为使用Navigate方法进行导航并不会重新加载视图;
使用NavigationService对象进行导航
适用情况:需要通过代码在视图本身进行初始化导航操作,而此时又没有对Frame控件的引用.这种情况下,当前视图关闭,而被导航的视图在其位置上显示.该对象由派生于Page类的属性进行访问.使用方法如下:
NavigationService.Navigate(new Uri("/Home",UriKind.Relative));
使用HyperlinkButton控件
使用方法如下:
<HyperlinkButton Name="HomeLink" NavigateUri="/Home" TargetName="ContentFrame" Content="home" />
- 指定元素名:Name
- 指定URI:NavigationUri
- 指[定Frame:TargetName,如果只有一个Frame,可以不指定该属性;如果设置为_blank(打开新网页)或_self(打开新网页,关闭SIlverlight应用程序),可以用于Html网页的导航,但不能用于Page的导航;
- 指定超级连接上显示的文本:Content
使用Frame控件的Source属性进行导航
适用情况:绑定到包含有待显示视图的URI的数据源上.需要注意的是,Source属性需要Uri类型的值 ,如果想要绑定到字符串,需要通过值转换器将其转换为Uri值;
在视图间传递数据
通过查询字符串参数传递数据
/Views/ProductDetailsView.xaml?ProductId=879&SupplierId=437
/Views/ProductDetails/879
后者需要设置URI mapping才能实现
读取查询字符串串参数
object productId = NavigationContext.QueryString["ProductId"];
注意需要将返回值转换为所需要的格式.如果查询字符串没有传值,则返回值为null,因此在数值转换前要检查是否为null值,否则会引发异常;
通常转换的步骤如下:
- 确保参数存在
- 检查值是否为正确的类型
- 将值转换化为所需的类型
处理这种转换最好的办法是在视图的后置代码里为所有的查询字符串设置只读的强类型属性,返回正确类型的值.如果所传参数类型不正确或未进行传递,则返回默认值或抛出异常.将查询字符串参数进行属性封装使得代码清晰可维护.比如,如下代码用于处理ProductId参数,当需要ProductId查询值时,只需要从ParamProductId属性获取即可:
private int ParamProductId { get { int paramValue=0; const string paramName="ProductId"; if(NavigationContext.QueryString.ContansKey(paramName)) int.TryParse(NaviagetionContext.QueryString[paramName],out paramValue); return paramValue; } }
使用Deep Links(纵深链接)
园友菩提树下的杨过对此有过精深的见解:http://www.cnblogs.com/yjmyzz/archive/2009/11/06/1597493.html,出于对于他的尊重,就不再罗嗦了.
只要使用导航应用程序模板,自动实现这一功能.
使用URI映射
可以使用如下代码进行映射:
<navigation:Frame.UriMapper> <uriMapper:UriMapper> <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/> <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/> </uriMapper:UriMapper> </navigation:Frame.UriMapper>
这样设置以后,
/Views/Home.xaml就可以使用/来访问了,而/Views/ProductDetail.xaml就可以使用/ProductDetail来访问了;
如果这样映射:
<uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}View.xaml"/>
则/Views/ProductDetailView.xaml就可以使用/ProductDetail来访问了;
如果这样映射:
<navigation:UriMapping Uri="ProductDetails/{id}" MappedUri="/Views/ProductDetailsPage.xaml?ProductID={id}" />
则/Views/ProductDetailsPage.xaml?ProductId=210就可以使用/ProductDetails/210来访问了;
如果这样映射:
<uriMapper:UriMapping Uri="/AboutUs" MappedUri="/Views/AboutView.xaml"/>
则/Views/AboutView.xaml就可以使用/AboutUs来访问了;而且也支持deep link:#AboutUs;
设置Uri映射是有原则的,应该把特定的映射,比如对AboutUs的映射放在前面,通用的映射放在后面;另外如果定义以大括号开头的映射,比如(Uri="{pageName}"),是不符合XAML语法的,这种情况应该使用这样的代码替换:Uri="{}{pageName}".
如果传递的URI与映射的路由不匹配,会引发Frame控件的NavigationFailed事件,可以进行相应的处理.默认情况下会显示一个错误,也可以通过该事件进行修改,比如导航到另一个视衅上去.
也可以将URI映射定义为资源,使用如下代码在Frame控件中引用:
UriMapper="{StaticResource uriMapperKey}"
处理导航事件
Frame事件
Frame控件提供如下事件
- Navigating:发生于导航操作开始前,如果想要停止操作时,需要设置e.Cancel为true;
- Navigated:发生于导航操作完成后
- NavigationStopped:发生于Frame控件或NavigationService对象的StopLoading方法调用时
- NavigationFailed:发生于应用程序导航到无效的deep link时(可能是无法映射到正确的地址,也可能是用户在浏览器里输入了错误的网址)
- FragmentNavigation:在导航到包括片断的统一资源标识符 (URI) 时,调用 OnFragmentNavigation 方法。 一个片断是片断分隔符 (#) 后的值。 例如,在某一框架内导航时,URI/Views/AboutPage.xaml#contact 指定导航到位于 /Views/AboutPage.xaml 的 Silverlight 页并且传递 contact 的片断值。 您重写 OnFragmentNavigation 方法以便在响应包括片断的导航请求时执行任何操作。
View事件
有两种方法用于在视图中响应导航操作.第一个方法是在NavigationService对象上添加事件处理方法:
NavigationService.Navigating += NavigationService_Navigating;
不能在视图的构造器里为NavigationService事件添加事件处理函数,因为此时NavigationService对象尚且为null,会抛出NullReferenceException.应该在视图的Loaded事件处理函数中添加事件代码.或者在视图类中覆写如下虚拟方法(继承自Page类):
- OnNavigatedTo
- OnNavigationFrom
- OnNaviagtedFrom
- OnFragmentNavigation
注意无对应于NavigatingTo,NavigationStopped或NavigationFailed事件的可覆写方法,但是有两个新事件处理函数:OnNavigatingFrom和OnNavigatedFrom. OnNavigatingFrom方法用于检查当前数据表单是否被改写(尚未保存),以便询问用户是否在退出对话框前是否保存数据,如果将e.Cancel的值设置为true,导航操作就可以取消.
缓存视图
默认情况下,导航到另一个视图以后,所有对原视图的引用都被清空.如果想保存视图状态,需要通过设置视图的NavigationCacheMode属性来保存其状态,NavigationCacheMode是Page类的属性,可以在XAML中进行设置,有三个选项:
- Disabled:默认值
- Enabled:由Frame维护View添加到Cache.当用户再次导航到原视图时,将会显示原数据信息而不是创建新实例.
- Required:缓存视图需要大量内存,如果需要控制视图是否从缓存中移除,可以设置为Required属性,可以动态地设置缓存;
如果想要保存视图以便数据不必重复加载,设置Enabled不错,如果不想丢失尚未保存的数据,Required是不二之选.强制将缓存移除,只需要设置NavigationCacheMode属性至Disabled.
创建定制的内容加载器
在盒子外部,导航框架简单地从项目中加载XAML文件或从引用的程序集中加载到Frome控件中.但是可能想添加一些定制的逻辑到这一过程中,比如从服务器中动态下载XAP或者检查用户是否授权访问需要导航到的视图.SilverLight4在导航框架中引入了内容加载器的概念.定制的内容加载器可以在加载内容到Frame控件的过程中添加定制的逻辑代码.
导航框架默认的内容加载器称之为PageResourceContentLoader,但仅允许加载存在当前XAP文件中的视图.如果想要按需动态下载XAP文件,加载视图,从而可以将应用程序分成小块,只有在需要时才进行加载,减小的网络资源占用,减少了程序的加载时间.在程序很大,用户只需要很少一部分内容时这很重要.
当导航事件发生时,导航框架执行如下步骤:
- Frame控件的UriMapper执行URI映射
- NavigationService向内容加载器传递当前视图的URI与映射后的URI,内容加载器异步处理内容加载;
- 一旦内容加载器载入完成,会通知NavigationService.随后NavigationService调用内容加载器的EndLoad方法,返回在Frame控件显示的内容,通常是继承自Page类的视图.
如要创建定制的内容加载器,需要创建一个实现INavigationContentLoader接口的类,该接口需要实现BeginLoad,CanLoad,EndLoad和CancelLoad方法,然后赋给Frame控件的ContentLoader属性.