主要說(shuō)一下如何使用Navigation System進(jìn)行導(dǎo)航。它包含了兩個(gè)重要的控件:Frame、Page。其中Frame控件是主要控件因?yàn)樗?fù)責(zé)導(dǎo)航以及顯示內(nèi)容。而Page空間是一個(gè)可選的控件,它可以使用普通的UserControl來(lái)代替,但兩者之間稍有差別,后面會(huì)簡(jiǎn)單說(shuō)一下。
1.Frame控件Frame控件也是一個(gè)容器控件,它通過(guò)Content屬性進(jìn)行修改內(nèi)容,當(dāng)然最好是使用Navigate()方法來(lái)代替Content屬性,因?yàn)樗葧?huì)修改Content屬性也會(huì)觸發(fā)事件和保存Frame的日志(歷史記錄,更改當(dāng)前瀏覽器的地址)。下面看一個(gè)簡(jiǎn)單的例子,首先定義一個(gè)兩行一列的Grid,上面是一個(gè)包含了Frame的Border,下面是用來(lái)觸發(fā)導(dǎo)航的按鈕:
<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引用。
然后在項(xiàng)目中創(chuàng)建一個(gè)UserControl命名為Page1,并在Main.xaml.cs的按鈕事件中加入如下代碼:
mainFrame.Navigate(
new Uri(
"/Page1.xaml",
UriKind.Relative));
Uri前的“/”表示應(yīng)用程序的根目錄..不可以使用Navigate()方法訪問(wèn)應(yīng)用程序以外的頁(yè)面,比如其他網(wǎng)站的頁(yè)面。同樣也可以使用如下代碼代替Navigate()方法:
mainFrame.Content =
new Page1();
如果你觀察就會(huì)發(fā)現(xiàn)前后的瀏覽器地址欄是不一樣的,你也可以使用Frame.Source屬性查看當(dāng)前Uri..如圖:
這是因?yàn)橹恍薷腃ontent屬性不會(huì)觸發(fā)Navigation事件..這個(gè)DEMO最終的效果圖如下:
2.與瀏覽器集成
當(dāng)你使用Navigate()方法更新Frame控件內(nèi)容時(shí)那么目標(biāo)的xaml頁(yè)面就會(huì)以#隔開附加到當(dāng)前的地址中,所以前后的地址就如上圖所示的,多出了#Page1.xaml部分。
這個(gè)特點(diǎn)即有好處也有潛在的危害。本來(lái)當(dāng)你使用Frame來(lái)創(chuàng)建導(dǎo)航系統(tǒng),每個(gè)頁(yè)面都會(huì)有一個(gè)獨(dú)立的名稱標(biāo)識(shí),唯一的歷史記錄以及使用每次重新訪問(wèn)。比如當(dāng)你重新打開瀏覽器在初始地址后面加入#Page1.xaml那么你就可以進(jìn)入到這個(gè)頁(yè)面,同樣你也可以把這個(gè)完整的地址加入到標(biāo)簽中以便下次訪問(wèn)。這個(gè)特性叫做Deep linking.因此使得Silverlight對(duì)搜索引擎是友好的。比如你可以創(chuàng)建多個(gè)HTML/ASP.NET頁(yè)面,然后讓他們指向同一個(gè)XAP,不過(guò)URI連接到不同的頁(yè)面,那么搜索引擎就會(huì)為你的程序創(chuàng)建多個(gè)索引(嘿嘿)。
這只是它的一個(gè)好處那么下面看問(wèn)題是什么:
如果一個(gè)頁(yè)面中存在多個(gè)Frame控件會(huì)怎么樣?
使用URI片段可以標(biāo)識(shí)一個(gè)頁(yè)面,但是真?zhèn)€URI中并沒(méi)有標(biāo)識(shí)Frmae,這說(shuō)明在程序中同事只有一個(gè)Frame控件在工作。(程序中包含多個(gè)Frame的做法很少見(jiàn))
但是如果不止一個(gè)Frame控件,它們將會(huì)同時(shí)相應(yīng)瀏覽器地址,如果你重寫URI進(jìn)行訪問(wèn)或調(diào)用Navigate()方法那么請(qǐng)求的頁(yè)面就會(huì)添加到每一個(gè)Frame中。為了避免這種情況,你需要選擇一個(gè)Frame控件來(lái)做為主控件,這個(gè)控件會(huì)受到瀏覽器歷史地址的影響,而其他的Frame則會(huì)負(fù)責(zé)跟蹤自己的導(dǎo)航而不受瀏覽器影響。為了達(dá)到這個(gè)目的你需要為每一個(gè)副Frame的JournalOwnership 屬性設(shè)置為OwnJournal,這樣的話這些Frame就只能通過(guò)代碼調(diào)用Navigate方法進(jìn)行導(dǎo)航了。下面是JournalOwnership 屬性值的說(shuō)明(或看MSDN中更詳細(xì)的介紹):
如果頁(yè)面中并不存在Frame控件會(huì)怎么樣?
使用URI訪問(wèn)多Frame的程序并不是唯一的問(wèn)題,還有一個(gè)問(wèn)題是是否能正確的處理用戶請(qǐng)求,因?yàn)橛锌赡躌oot visual中并不包含F(xiàn)rame。如果你用代碼動(dòng)態(tài)生成用戶界面。比如使用代碼創(chuàng)建Frame對(duì)象或者在進(jìn)入另外一個(gè)頁(yè)面的時(shí)候頁(yè)面中包含了一個(gè)Frame。這種情況下程序會(huì)正常運(yùn)行,不過(guò)因?yàn)闆](méi)有frame可用,URI片段會(huì)被忽略。
為了避免此類問(wèn)題你可以簡(jiǎn)化應(yīng)用程序確保在啟動(dòng)的時(shí)候frame是可用的,并且在Application.Startup中驗(yàn)證請(qǐng)求的地址中是否含有URI片段,驗(yàn)證代碼如下:
string fragment = System.Windows.Browser.HtmlPage.Document.DocumentUri.Fragment;
3.安全問(wèn)題如何?
從另一個(gè)角度考慮,也給你的程序留了一個(gè)很多的后門。比如用戶輸入U(xiǎn)RI訪問(wèn)了一個(gè)你不想使用Navigate()方法訪問(wèn)的頁(yè)面。Silverlight本身并沒(méi)有提供措施來(lái)避免此類問(wèn)題,因此在你使用導(dǎo)航系統(tǒng)的時(shí)候潛在的也給你帶來(lái)了URI訪問(wèn)的問(wèn)題。
不過(guò)幸好你可以使用以下方法來(lái)人為避免。第一個(gè)像前面一樣設(shè)置Frame的JournalOwnership為OwnJournal,那樣就可以避免使用URL訪問(wèn)你應(yīng)用程序的任何頁(yè)面,同時(shí)地址也不會(huì)集成到瀏覽器的歷史記錄列表中。另外一個(gè)更好的方法是使用Navigating事件,這個(gè)方法可以驗(yàn)證請(qǐng)求的URI從而有選擇性的進(jìn)行導(dǎo)航,驗(yàn)證代碼如下:
void mainFrame_Navigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e)
{
if (e.Uri.ToString().ToLower().Contains("Page1.xaml")) {
e.Cancel = true; }
}
這樣就可以在程序執(zhí)行Navigate()方法后來(lái)驗(yàn)證URI是否合法。從而避免用戶訪問(wèn)到禁止請(qǐng)求的頁(yè)面。
4.對(duì)歷史記錄的支持
Frame控件的導(dǎo)航也可以與瀏覽器進(jìn)行集成。你每次調(diào)用Navigate()方法,Silverlight就會(huì)添加一個(gè)地址到瀏覽器的歷史記錄中(如下圖)。使用瀏覽器的前進(jìn)/后臺(tái)按鈕或者在歷史記錄列表中選擇一個(gè)頁(yè)面都可以正常的訪問(wèn)歷史頁(yè)面。你可以復(fù)制地址,然后重新打開瀏覽器進(jìn)行訪問(wèn),依然是可以的。這樣Silverlgiht的Appliaction.Startup事件會(huì)重新觸發(fā),并加載相應(yīng)的頁(yè)面到Frame中。

如果你在按鈕事件中連續(xù)多次調(diào)用Navigate()方法那么頁(yè)面會(huì)加載你最后一個(gè)方法訪問(wèn)的頁(yè)面,不過(guò)其他頁(yè)面會(huì)加入瀏覽器的歷史記錄中。另外需要注意一下,如果你沒(méi)有使用UriMapper設(shè)置初始內(nèi)容,那么你點(diǎn)擊后臺(tái)按鈕回到初始頁(yè)面很有可能會(huì)產(chǎn)生一個(gè)錯(cuò)誤“No XAML found at the location”.
5.URI Mapping
正如我們看到的,頁(yè)面是以URI片段方式顯示在URI中的,那么你可能不想讓用戶看到具體的頁(yè)面名稱,并且也不希望.XAML結(jié)尾讓人感覺(jué)到混淆,而使用更簡(jiǎn)單、易記的名稱。那么為了解決這個(gè)問(wèn)題可以使用URI Mapping來(lái)定義簡(jiǎn)單的URI片段。首先需要在資源中加入U(xiǎn)riMapper對(duì)象,一般是定義在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>同時(shí)在頁(yè)面Frame控件設(shè)置屬性UriMapper:
<navigation:Frame x:Name="mainFrame" UriMapper="{StaticResource PageMapper}"></navigation:Frame>現(xiàn)在你就可以在UriMapper中添加映射了:
<navigation:UriMapping Uri="Home" MappedUri="/Page1.xaml" />再次訪問(wèn)頁(yè)面然后看看地址欄結(jié)尾就會(huì)顯示成#Home,對(duì)了,記得修改導(dǎo)航按鈕的事件:
mainFrame.Navigate(
new Uri(
"Home",
UriKind.Relative));
否則地址欄是不會(huì)變化的。并且在映射地址前面不再需要加入一個(gè)“/”,它同樣能正常工作并且訪問(wèn)到目標(biāo)頁(yè)面。另外一個(gè)還可以設(shè)置一個(gè)頁(yè)面作為初始頁(yè)面。
<navigation:UriMapping Uri="" MappedUri="/InitialPage.xaml" />那么這樣的話,當(dāng)你使用后臺(tái)按鈕回到首頁(yè)就不會(huì)再出現(xiàn)剛才說(shuō)的那個(gè)“No XAML found at the location”的錯(cuò)了。現(xiàn)在是強(qiáng)制的設(shè)置初始頁(yè)面,從某種意義上講也算是Silverlight的一個(gè)BUG。
URI地址映射是支持地址欄參數(shù),在說(shuō)Page控件的時(shí)候會(huì)再專門介紹一下..
<navigation:UriMapping Uri="Home/{name}" MappedUri="/Page1.xaml?name={name}" />6.支持自定義 前進(jìn)/后退導(dǎo)航按鈕.我們可以設(shè)置了Frame控件的JournalOwnership屬性來(lái)確定它如何與瀏覽器集成,管理日志。如果設(shè)置成OwnJournal那么Frame將自己來(lái)管理歷史記錄,不會(huì)與瀏覽器集成。這種情況就需要你自己來(lái)提供前進(jìn)/后退功能了,一般是使用兩個(gè)按鈕來(lái)代替前進(jìn)/后退。
而如果你的程序需要支持out-ofbrowser application,那么設(shè)置自定義的前進(jìn)后臺(tái)按鈕就很有比較了,因?yàn)樗沁\(yùn)行在普通的windws窗口中的,不會(huì)有瀏覽器的導(dǎo)航按鈕,那怕你并沒(méi)有設(shè)置JournalOwnership屬性,并且你可以判斷是否是OOB來(lái)顯示你的導(dǎo)航按鈕,你可以將以下代碼添加到程序中:
if (
App.Current.IsRunningOutOfBrowser)
btnNavigate.Visibility =
Visibility.Visible;
這樣就可以根據(jù)是否為OOB來(lái)控制按鈕了。并且自定義按鈕你也可以加你喜歡的樣式或動(dòng)畫效果,并且還可以根據(jù)Frame的兩個(gè)屬性來(lái)判斷當(dāng)前頁(yè)面是否是第一頁(yè)/最后一頁(yè)來(lái)禁用按鈕或修改按鈕樣式:
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;
}
這只是判斷按鈕顯示與否。你也可以修改按鈕視覺(jué)效果或禁用它們(比如更改顏色、透明度、圖片、添加動(dòng)畫),具體怎么做就看自己喜歡了。
7.HyperlinkButton
在前面我們是使用普通按鈕來(lái)觸發(fā)導(dǎo)航事件的。一般情況下Silverlight是使用HyperlinkButton來(lái)進(jìn)行導(dǎo)航的,這個(gè)按鈕使用起來(lái)相當(dāng)?shù)暮?jiǎn)單,只要設(shè)置按鈕的
NavigateUri屬性指向頁(yè)面的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…..限于篇幅,就主要說(shuō)這些.這次主要說(shuō)的是使用Frame來(lái)創(chuàng)建導(dǎo)航系統(tǒng)..由于個(gè)人水平問(wèn)題,如果什么地方有錯(cuò)誤的地方,還麻煩各位提出,我會(huì)及時(shí)改正..
下次注重說(shuō)一下關(guān)于使用Page來(lái)代替UserControl以及推薦幾個(gè)不錯(cuò)的Navigation Templates..先看下效果: