Silverlight 3 Beta 新特性解析(6) - Navigation和Deep Linking篇

前提条件:

阅读本文之前请确认你已经安装了如下软件

本篇主要内容:

Navigation

  • Frame和Page控件
  • 导航历史记录
  • NavigationService和NavigationContext

DeepLinking

  • UriMapper和UriMapping

导航(Navigation):

在Silverlight 2时代,如何从一个控件页面导航到另外一个控件页面是需要费很大功夫的事情

以至于国外有不少人研究并制作了自己的导航控件,如Peter BrownGerard Leblanc

但是可用性还是比较差,于是大家在进行Silverlight 2开发的时候碰到导航问题经常就卡壳了

Silverlight 3终于将导航框架引进了Silverlight

其主要依赖的两个控件是Frame和Page控件(和WPF一样)

  • Frame和Page控件

Frame控件用来放置Page控件并执行导航功能,其主要的属性和方法如下:

  • Source

用于设置第一次加载Frame时加载那个Page控件

如下就是加载Views目录下的EmployeePage.xaml页面控件

<navigation:Frame x:Name="NavFrame" Source="/Views/EmployeePage.xaml"/>
  • Navigate(Uri uri)

这个方法用于在不同页面间进行导航,其中Uri就类似于ASP.Net中的页面路径

只是这里的页面是.xaml而不是.aspx

如下表示名字为NavFrame的这个Frame控件现在将导航至Views目录下的ContactPage.xaml页面

this.NavFrame.Navigate(new Uri("/Views/ContactPage.xaml", UriKind.Relative));

而作为Frame控件的亲密战友,其和普通的用户控件事实上别无二致

除了其能被Frame调用,而且其有个Title属性用来设置这个页面标题并显示在浏览器上,如下

<navigation:Page x:Class="SL3Beta.Nav.Views.EmployeePage" 
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
           Title="Employee Page">

 

SL3Beta_nav01

    • 导航历史记录

    现在我们有了新的导航机制,就可以在浏览器上记录下曾经浏览过的页面了,如下图所示

    SL3Beta_nav02

    其主要是靠NavigationService中的GoBack以及GoForward来实现后退以及前进功能的

    • NavigationService和NavigationContext

    有了Frame来控制导航还是不够的

    比如下图是一个Page控件,我先点击查看联系信息按钮来查看雇员的联系信息

    SL3Beta_nav03

    而由于页面空间有限,所以我想把雇员信息放置到另外一个页面中

    而在这个Page控件如何导航到下一个页面呢

    答案是使用NavigationService类来导航(只有在Page控件中才起作用),如下

    private void ViewContactButton_Click(object sender, RoutedEventArgs e)
    {
        Employee employee = this.EmployeeGrid.SelectedItem as Employee;
        if (employee != null)
        {
            this.NavigationService.Navigate(new Uri(String.Format("/Views/ContactPage.xaml?ContactID={0}", employee.ContactID), UriKind.Relative));
        }
    }

    我们可以导航,而且还可以在页面间传参数

    大家是不是有点似曾相识的感觉啊,没错,这就是借鉴了ASP.Net采用QueryString来传递参数的机制

    现在留下来的问题就是如何在另外一个页面中获取得到传递过来的参数了

    这个工作是由NavigationContext来完成的,如下

    if (this.NavigationContext.QueryString.ContainsKey("ContactID"))
    {
        int contactID = Int32.Parse(this.NavigationContext.QueryString["ContactID"]);
    }

    下面我用一个实际的例子把这些知识串起来

    由于本篇将采用ADO.Net Entity Framework还有.Net RIA Services技术来获取并处理数据

    所以如果不了解的,请先查看下Silverlight 3 Beta 新特性解析(5) - Data篇

    也就是前面的如何创建项目以及采用ADO.Net Entity Data Model来获取数据

    并采用Domain Service类来提供网络服务的将一概略过

    本篇依然采用AdventureWorks数据库来示范

    本文将采用的是Employee和Contact表

    SL3Beta_nav04

    其通过ContactID将两个表关联起来

    本文的项目结构如下

    SL3Beta_nav05

    修改PersonDomainService.cs类如下

    public IQueryable<Contact> GetContactByContactID(int contactID)
    {
        return this.Context.Contact.Where(e=>e.ContactID==contactID);
    }

    也就是我们将通过输入contactID来获取雇员的联系信息

    而在SL3Beta.Nav项目下创建的Views目录用来存放页面控件

    MainPage.xaml是普通的用户控件,其代码如下

    <UserControl  x:Class="SL3Beta.Nav.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation">
        <Grid x:Name="LayoutRoot" Background="#606060">
            <navigation:Frame x:Name="NavFrame" Source="/Views/EmployeePage.xaml">
            </navigation:Frame>
        </Grid>
    </UserControl>

    在初始的状态下加载/Views/EmployeePage.xaml页面来显示雇员信息如下:

    <navigation:Page x:Class="SL3Beta.Nav.Views.EmployeePage" 
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
               xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
               xmlns:web="clr-namespace:SL3Beta.Nav.Web"
               xmlns:riaData="clr-namespace:System.Windows.Data;assembly=System.Windows.Ria.Controls"
               xmlns:dataControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm"
               xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
               xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"
               Title="Employee Page">
        <Grid x:Name="LayoutRoot">
            <StackPanel Width="900" Margin="10">
                <riaControls:DomainDataSource x:Name="EmployeeDataSource" LoadSize="10" LoadMethodName="LoadEmployee">
                    <riaControls:DomainDataSource.DomainContext>
                        <web:PersonDomainContext/>
                    </riaControls:DomainDataSource.DomainContext>
                    <riaControls:DomainDataSource.SortDescriptors>
                        <riaData:SortDescriptor Direction="Ascending" PropertyPath="EmployeeID"/>
                    </riaControls:DomainDataSource.SortDescriptors>
                </riaControls:DomainDataSource>
     
                <data:DataGrid x:Name="EmployeeGrid" ItemsSource="{Binding Data,ElementName=EmployeeDataSource}" MinHeight="100" SelectionChanged="EmployeeGrid_SelectionChanged"></data:DataGrid>
     
                <dataControls:DataPager PageSize="10" Source="{Binding Data,ElementName=EmployeeDataSource}"></dataControls:DataPager>
                
                <Button Content="查看联系信息" x:Name="ViewContactButton" HorizontalAlignment="Right" Margin="20,5" Click="ViewContactButton_Click" IsEnabled="False"/>
            </StackPanel>
        </Grid>
    </navigation:Page>

    SL3Beta_nav03

    其中查看联系信息按钮用来触发导航到选中的雇员的联系信息,其代码如下

    private void ViewContactButton_Click(object sender, RoutedEventArgs e)
    {
        Employee employee = this.EmployeeGrid.SelectedItem as Employee;
        if (employee != null)
        {
            this.NavigationService.Navigate(new Uri(String.Format("/Views/ContatctPage.xaml?ContactID={0}", employee.ContactID), UriKind.Relative));
        }
    }

    而ContactPage.xaml文件

    <navigation:Page xmlns:dataControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm"  x:Class="SL3Beta.Nav.Views.ContactPage" 
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
               xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
               Title="Contact Page" Width="400" Height="400">
        <Grid x:Name="LayoutRoot">
            <dataControls:DataForm x:Name="ContactForm"></dataControls:DataForm>
        </Grid>
    </navigation:Page>

    将接收由HomePage.xaml传过来的参数如下,并通过服务来获取得到详细的联系信息并用DataForm显示出来

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        if (this.NavigationContext.QueryString.ContainsKey("ContactID"))
        {
            int contactID = Int32.Parse(this.NavigationContext.QueryString["ContactID"]);
     
            _personContext.Loaded += (sender, e2) =>
                {
                    if (_personContext.Contacts.Count>0)
                        this.ContactForm.CurrentItem = _personContext.Contacts[0];
                };
            _personContext.LoadContactByContactID(contactID);
        }
    }
    SL3Beta_nav06
    你也可以通过后退键来查看原来所有的雇员的详细信息

    Deep Linking:

    从上面的范例,我们可以看到,我们已经不知是能查看初始的载入的雇员页面

    我们也可以输入类似的网址如http://localhost:3066/Default.aspx#/ContactID=1006

    来直接查看联系编号为1006的雇员的联系方式,如下

    SL3Beta_nav07

    这就是传说中的深度链接了(Deep Linking)

    这样搜索引擎就可以搜索到下一级的页面了,改善了SEO效果

    但是这样可能会暴露网站的目录结构
    我们可以使用Uri映射来解决这个问题如下
    • UriMapper和UriMapping

    其中上述两个控件都位于System.Windows.Navigation这个名字空间中

    所以我们在MainPage.xaml文件中引用其如下

    xmlns:windowsNav="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"

    并修改MainPage.xaml的Frame控件如下

    <navigation:Frame x:Name="NavFrame" Source="Employee" HorizontalAlignment="Center" VerticalAlignment="Center">
        <navigation:Frame.Resources>
            <windowsNav:UriMapper x:Name="uriMapper">
                <windowsNav:UriMapping MappedUri="{}/Views/EmployeePage.xaml" Uri="Employee"/>
                <windowsNav:UriMapping MappedUri="{}/Views/ContactPage.xaml?ContactID={contactID}" Uri="ContactID={contactID}"/>
            </windowsNav:UriMapper>
        </navigation:Frame.Resources>
    </navigation:Frame>
    这样我们就将/Views/EmployeePage.xaml映射成Employee
    而/Views/ContactPage.xaml?ContactID={contactID}被映射成ContactID={contactID}
    对映射前和映射后的网络路径比较如下
    SL3Beta_nav10 SL3Beta_nav08
    SL3Beta_nav07 SL3Beta_nav09
    这样我们就隐藏了页面的位置,而且网络路径也更加美观,一举两得
     

    代码下载:


    作者:ibillguo
    出处:http://ibillguo.cnblogs.com/
    本文版权由作者和博客园共同所有,转载请注明出
    posted @ 2009-03-31 16:24 ibillguo 阅读(3563) 评论(16) 编辑 收藏

     回复 引用 查看   
    #1楼2009-03-31 17:50 | Windie Chai(笑煞天)      
    太强大了。
     回复 引用 查看   
    #2楼[楼主]2009-03-31 18:15 | ibillguo      
    @Windie Chai(笑煞天)
    是哦,asp.net能做的他都能搞定
    asp.net不能做的,他也能搞定,呵呵

     回复 引用   
    #3楼2009-03-31 19:34 | dragonWorrior
    嗯,确实不错。解决了我的一个疑问。还有跟一个跟本文不大相干的疑问,riaservice的查询方法返回的都是IQuery<T>类型,T为特定表。如果我返回的是两个表中的数据呢?返回值怎么定义?具体怎么实现连表查询?困扰我两天了。新手请教。
     回复 引用 查看   
    #4楼2009-03-31 23:22 | 拖鞋不脱      
    太赞了,是不是前进后退也同时支持了?
     回复 引用 查看   
    #5楼[楼主]2009-03-31 23:25 | ibillguo      
    @拖鞋不脱
    都支持

     回复 引用 查看   
    #6楼[楼主]2009-03-31 23:26 | ibillguo      
    @dragonWorrior
    这个是ADO.Net Entity Framework的知识
    你需要查看下相关文章或者书籍

     回复 引用 查看   
    #7楼2009-04-01 09:01 | 代震军      
    先看一下
     回复 引用 查看   
    #8楼2009-04-01 14:58 | 大熊(BigBear)      
    Microsoft JScript 运行时错误: Unhandled Error in Silverlight Application Navigation is only supported to relative Uri's that are fragments, or begin with '/' or which contain ';component/'
    参数名: uri 位于 System.Windows.Navigation.NavigationService.RaiseNavigationFailed(Uri uri, Exception exception)
    位于 System.Windows.Navigation.NavigationService.NavigateCore(Uri uri, NavigationMode mode, Boolean suppressJournalAdd)

    Demo代码编译通过,但页面中浏览时出现上边的错误. 请教

     回复 引用 查看   
    #9楼[楼主]2009-04-01 15:25 | ibillguo      
    @大熊(BigBear)
    谢谢提醒
    我把代码改了一下,忘记改回去了
    请在MainPage.xaml文件的第10行加入
    <windowsNav:UriMapping MappedUri="{}/Views/EmployeePage.xaml" Uri="Employee"/>

    或者可以重新下载下代码(重新更新了)

     回复 引用   
    #10楼2009-04-01 23:17 | trueneter[未注册用户]
    代码下载的链接呢?
     回复 引用 查看   
    #11楼[楼主]2009-04-02 00:12 | ibillguo      
    @trueneter
    好了,居然莫名其妙没了链接。。。

     回复 引用 查看   
    #12楼2009-06-24 15:51 | 苏梦枕      
    您好,为什么我的页面配置里引入命名空间后再写<navigation:Page></navigation:Page>后会编译不过呢
    Error 1 Partial declarations of '_09_06_24.View.ProductDetailsView' must not specify different base classes
    Error 2 Partial declarations of '_09_06_24.View.ProductView' must not specify different base classes
    是不是建View里面的页面时有什么细节 小弟刚接触SL,还望博主告知一二……

     回复 引用 查看   
    #13楼2009-06-25 09:01 | 苏梦枕      
    呃 自己弄出来了 呵呵 顶楼主
     回复 引用   
    #14楼2009-08-04 16:15 | yellow.tim[未注册用户]
    var frame=new Frame();
    frame.Source=new Uri("/my.xaml",UriKind.Relative);
    this.RootVisual=frame;
    以上的写在App.cs里面的启动项Application_Startup事件里
    my.xaml里面有个事件触发下面的导航
    frame.NavigationService.Navigate("/You.xaml",UriKind.Relative);
    如果You.xaml是个Page,在You.xaml中又定义了一个Frame.
    You.xaml有个Button触发Frame的导航,就是页面还是You.xaml而You.xaml中Frame里的内容换成了导航后的内容,问题就在这里,你一刷新页面,导航的后新的内容就不见了,又回到原始的You.xaml页面了。如何让You.xaml里面的导航进去的新内容不会因为刷新而变回到原来的样子,要保持跟新后的内容!

     回复 引用   
    #15楼2009-08-11 20:27 | 路过一下[未注册用户]
    楼主昏头昏脑的,代码写得也是乱糟糟的!
     回复 引用   
    #16楼2009-09-21 09:53 | 3564564564564[未注册用户]
    sad