此次主要说一下如何使用Navigation System进行导航。它包含了两个重要的控件:Frame、Page。其中Frame控件是主要控件因为它负责导航以及显示内容。而Page空间是一个可选的控件,它可以使用普通的UserControl来代替,但两者之间稍有差别,后面会简单说一下。
1.Frame控件
Frame控件也是一个容器控件,它通过Content属性进行修改内容,当然最好是使用Navigate()方法来代替Content属性,因为它既会修改Content属性也会触发事件和保存Frame的日志(历史记录,更改当前浏览器的地址)。下面看一个简单的例子,首先定义一个两行一列的Grid,上面是一个包含了Frame的Border,下面是用来触发导航的按钮:
<UserControl x:Class="FramNavigation.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Border Margin="10" Padding="10" BorderBrush="DarkOrange"
BorderThickness="2" CornerRadius="4">
<navigation:Frame x:Name="mainFrame"></navigation:Frame>
</Border>
<Button Grid.Row="1" Margin="5" Padding="5" HorizontalAlignment="Center"
Content="Go to Page1" Click="btnNavigate_Click"></Button>
</Grid>
</Grid>
</UserControl>
使Frame记得添加System.Windows.Controls.Navigation.dll引用。
然后在项目中创建一个UserControl命名为Page1,并在Main.xaml.cs的按钮事件中加入如下代码:
mainFrame.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
Uri前的“/”表示应用程序的根目录..不可以使用Navigate()方法访问应用程序以外的页面,比如其他网站的页面。同样也可以使用如下代码代替Navigate()方法:
mainFrame.Content = new Page1();
如果你观察就会发现前后的浏览器地址栏是不一样的,你也可以使用Frame.Source属性查看当前Uri..如图:
这是因为只修改Content属性不会触发Navigation事件..这个DEMO最终的效果图如下:
2.与浏览器集成
当你使用Navigate()方法更新Frame控件内容时那么目标的xaml页面就会以#隔开附加到当前的地址中,所以前后的地址就如上图所示的,多出了#Page1.xaml部分。
这个特点即有好处也有潜在的危害。本来当你使用Frame来创建导航系统,每个页面都会有一个独立的名称标识,唯一的历史记录以及使用每次重新访问。比如当你重新打开浏览器在初始地址后面加入#Page1.xaml那么你就可以进入到这个页面,同样你也可以把这个完整的地址加入到标签中以便下次访问。这个特性叫做Deep linking.因此使得Silverlight对搜索引擎是友好的。比如你可以创建多个HTML/ASP.NET页面,然后让他们指向同一个XAP,不过URI连接到不同的页面,那么搜索引擎就会为你的程序创建多个索引(嘿嘿)。
这只是它的一个好处那么下面看问题是什么:
- 如果一个页面中存在多个Frame控件会怎么样?
使用URI片段可以标识一个页面,但是真个URI中并没有标识Frmae,这说明在程序中同事只有一个Frame控件在工作。(程序中包含多个Frame的做法很少见)
但是如果不止一个Frame控件,它们将会同时相应浏览器地址,如果你重写URI进行访问或调用Navigate()方法那么请求的页面就会添加到每一个Frame中。为了避免这种情况,你需要选择一个Frame控件来做为主控件,这个控件会受到浏览器历史地址的影响,而其他的Frame则会负责跟踪自己的导航而不受浏览器影响。为了达到这个目的你需要为每一个副Frame的JournalOwnership 属性设置为OwnJournal,这样的话这些Frame就只能通过代码调用Navigate方法进行导航了。下面是JournalOwnership 属性值的说明(或看MSDN中更详细的介绍): - 如果页面中并不存在Frame控件会怎么样?
使用URI访问多Frame的程序并不是唯一的问题,还有一个问题是是否能正确的处理用户请求,因为有可能Root visual中并不包含Frame。如果你用代码动态生成用户界面。比如使用代码创建Frame对象或者在进入另外一个页面的时候页面中包含了一个Frame。这种情况下程序会正常运行,不过因为没有frame可用,URI片段会被忽略。
为了避免此类问题你可以简化应用程序确保在启动的时候frame是可用的,并且在Application.Startup中验证请求的地址中是否含有URI片段,验证代码如下:
string fragment = System.Windows.Browser.HtmlPage.Document.DocumentUri.Fragment;
3.安全问题如何?
从另一个角度考虑,也给你的程序留了一个很多的后门。比如用户输入URI访问了一个你不想使用Navigate()方法访问的页面。Silverlight本身并没有提供措施来避免此类问题,因此在你使用导航系统的时候潜在的也给你带来了URI访问的问题。
不过幸好你可以使用以下方法来人为避免。第一个像前面一样设置Frame的JournalOwnership为OwnJournal,那样就可以避免使用URL访问你应用程序的任何页面,同时地址也不会集成到浏览器的历史记录列表中。另外一个更好的方法是使用Navigating事件,这个方法可以验证请求的URI从而有选择性的进行导航,验证代码如下:
void mainFrame_Navigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e)
{
if (e.Uri.ToString().ToLower().Contains("Page1.xaml")) {
e.Cancel = true; }
}
这样就可以在程序执行Navigate()方法后来验证URI是否合法。从而避免用户访问到禁止请求的页面。
4.对历史记录的支持
Frame控件的导航也可以与浏览器进行集成。你每次调用Navigate()方法,Silverlight就会添加一个地址到浏览器的历史记录中(如下图)。使用浏览器的前进/后台按钮或者在历史记录列表中选择一个页面都可以正常的访问历史页面。你可以复制地址,然后重新打开浏览器进行访问,依然是可以的。这样Silverlgiht的Appliaction.Startup事件会重新触发,并加载相应的页面到Frame中。
如果你在按钮事件中连续多次调用Navigate()方法那么页面会加载你最后一个方法访问的页面,不过其他页面会加入浏览器的历史记录中。另外需要注意一下,如果你没有使用UriMapper设置初始内容,那么你点击后台按钮回到初始页面很有可能会产生一个错误“No XAML found at the location”.
5.URI Mapping
正如我们看到的,页面是以URI片段方式显示在URI中的,那么你可能不想让用户看到具体的页面名称,并且也不希望.XAML结尾让人感觉到混淆,而使用更简单、易记的名称。那么为了解决这个问题可以使用URI Mapping来定义简单的URI片段。首先需要在资源中加入UriMapper对象,一般是定义在App.xaml中的
<!--xmlns:navigation="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"-->
<Application.Resources>
<navigation:UriMapper x:Key="PageMapper">
</navigation:UriMapper>
</Application.Resources>
同时在页面Frame控件设置属性UriMapper:
<navigation:Frame x:Name="mainFrame" UriMapper="{StaticResource PageMapper}"></navigation:Frame>
现在你就可以在UriMapper中添加映射了:
<navigation:UriMapping Uri="Home" MappedUri="/Page1.xaml" />
再次访问页面然后看看地址栏结尾就会显示成#Home,对了,记得修改导航按钮的事件:
mainFrame.Navigate(new Uri("Home", UriKind.Relative));
否则地址栏是不会变化的。并且在映射地址前面不再需要加入一个“/”,它同样能正常工作并且访问到目标页面。另外一个还可以设置一个页面作为初始页面。
<navigation:UriMapping Uri="" MappedUri="/InitialPage.xaml" />
那么这样的话,当你使用后台按钮回到首页就不会再出现刚才说的那个“No XAML found at the location”的错了。现在是强制的设置初始页面,从某种意义上讲也算是Silverlight的一个BUG。
URI地址映射是支持地址栏参数,在说Page控件的时候会再专门介绍一下..
<navigation:UriMapping Uri="Home/{name}" MappedUri="/Page1.xaml?name={name}" />
6.支持自定义 前进/后退导航按钮.
我们可以设置了Frame控件的JournalOwnership属性来确定它如何与浏览器集成,管理日志。如果设置成OwnJournal那么Frame将自己来管理历史记录,不会与浏览器集成。这种情况就需要你自己来提供前进/后退功能了,一般是使用两个按钮来代替前进/后退。
而如果你的程序需要支持out-ofbrowser application,那么设置自定义的前进后台按钮就很有比较了,因为它是运行在普通的windws窗口中的,不会有浏览器的导航按钮,那怕你并没有设置JournalOwnership属性,并且你可以判断是否是OOB来显示你的导航按钮,你可以将以下代码添加到程序中:
if (App.Current.IsRunningOutOfBrowser)
btnNavigate.Visibility = Visibility.Visible;
这样就可以根据是否为OOB来控制按钮了。并且自定义按钮你也可以加你喜欢的样式或动画效果,并且还可以根据Frame的两个属性来判断当前页面是否是第一页/最后一页来禁用按钮或修改按钮样式:
void mainFrame_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
if (mainFrame.CanGoBack)
btnBack.Visibility = Visibility.Collapsed;
else
btnBack.Visibility = Visibility.Visible;
if (mainFrame.CanGoForward)
btnForward.Visibility = Visibility.Collapsed;
else
btnForward.Visibility = Visibility.Visible;
}
这只是判断按钮显示与否。你也可以修改按钮视觉效果或禁用它们(比如更改颜色、透明度、图片、添加动画),具体怎么做就看自己喜欢了。
7.HyperlinkButton
在前面我们是使用普通按钮来触发导航事件的。一般情况下Silverlight是使用HyperlinkButton来进行导航的,这个按钮使用起来相当的简单,只要设置按钮的NavigateUri属性指向页面的URI或者是在UriMapper中配置的地址都行,代码参考如下:
<StackPanel Margin="5" HorizontalAlignment="Center" Orientation="Horizontal">
<HyperlinkButton NavigateUri="/Page1.xaml" Content="Page 1" Margin="3" />
<HyperlinkButton NavigateUri="/Page2.xaml" Content="Page 2" Margin="3" />
<HyperlinkButton NavigateUri="Home" Content="Home" Margin="3" />
</StackPanel>
OK…..限于篇幅,就主要说这些.这次主要说的是使用Frame来创建导航系统..由于个人水平问题,如果什么地方有错误的地方,还麻烦各位提出,我会及时改正..
下次注重说一下关于使用Page来代替UserControl以及推荐几个不错的Navigation Templates..先看下效果: