[转] Silverlight Navigation(多页面切换、传值)
?
導航.簡單的理解可以是從一個頁面跳轉到另外一個頁面。在傳統的ASP.NET網站中這種效果很容易實現。而在Silverlight中我們也同樣可以,我們有兩種方法來實現這個效果。
???第一個選擇是使用代碼更改頁面視圖(修改容器Content屬性),移除/添加User Control來實現導航,這個方法比較簡單、直接代碼量也很少。并且在這個過程中還可以加入動畫、變形等效果。
???第二個選擇就是使用Silverlight的導航系統,導航系統包含兩個主要的控件:Frame、Page。基本的效果是可以在一個Frame里面切換多個頁面(UserControl、Page)。
?? 此次先簡單介紹下第一種方法。?? 簡單的頁面切換效果:
?? 這個例子,將頁面分成上下兩部分,上面表示菜單,下面放置一個容器空間用于承載內容。容器控件你可以旋轉Border、ScrollViewer、StackPanel、Grid。下面是主頁面的代碼:
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
?? ??? <RowDefinition Height="*"></RowDefinition>
?? ??? <RowDefinition Height="Auto"></RowDefinition> ??
?? <RowDefinition Height="6*"></RowDefinition>
</Grid.RowDefinitions>??
<ListBox Grid.Row="0" SelectionChanged="lstPages_SelectionChanged">
?? ?? <!--頁面列表-->
</ListBox>
<basics:GridSplitter Grid.Row="1" Margin="0 3" HorizontalAlignment="Stretch"
?? ?? ??? Height="2"></basics:GridSplitter> ???
<Border Grid.Row="2" BorderBrush="SlateGray" BorderThickness="1"
?? ?? ??? x:Name="mainFrame" Background="AliceBlue"></Border>
</Grid>
這個例子中使用Border作為容器命名為mainFrame,在頁面加載的時候先為容器加入一個頁面(為項目添加2個以上UserControl):
Page1 page1 = new Page1();
mainFrame.Child = page1;
這里的容器控件是多選的,你也可使用單行單列的Grid。下面為ListBox加入事件代碼:
private void lstPages_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//獲取當前的Item
string newPageName = ((ListBoxItem)e.AddedItems[0]).Content.ToString();
//根據名稱使用反射創建一個UserControl實例
Type type = this.GetType();
Assembly assembly = type.Assembly;
UserControl newPage = (UserControl)assembly.CreateInstance(
type.Namespace + "." + newPageName);
//加入到容器中.
mainFrame.Child = newPage;
}
至此一個簡單的頁面切換效果就OK了。
●使用根視圖(Root Visual)
上面是一個簡單的例子,這個方法很常見,但不是通用方式。它的小缺陷是整個頁面布局已經固定死了,工具欄或頁面始終都固定在那里,那如果你想要的是一個全新的頁面就不行了。下面對這個小例子進行一下擴展就可以達到我們的目的了。
首先在App.xaml.cs中聲明一個Grid:
??? private Grid rootGrid = new Grid();
然后修改Appliaction_Startup事件代碼:
??? this.RootVisual = rootGrid;
??? rootGrid.Children.Add(new Page1());
這樣只能保證在初始化的時候會有一個頁面而不能導航,為此在App.xaml.cs代碼中加入一個靜態方法,代碼如下:
public static void Navigation(UserControl newPage)
{
//獲取當前的Appliaction實例
App currentApp = (App)Application.Current;
//修改當前顯示頁面內容.
currentApp.rootGrid.Children.Clear();
currentApp.rootGrid.Children.Add(newPage);
}
這里要注意 方法的參數是UserControl。這樣在你的UserControl中即可添加如下的代碼(可以加到button事件中)進行頁面切換了:
App.Navigation(new Page2());
new的對象是你的目標頁面,不要寫錯了。就這幾句代碼又完成了咱們的目的!
●保存頁面狀態(Cache)
如果你想讓用戶在返回到歷史頁面的時候可以頁面的修改狀態比如用戶輸入的數據。首先對項目進行一下小小的修改,添加一個名字叫Pages的enmu,用于保存頁面名稱以便使用字符串產生不必要的問題:
public enum Pages
{
Page1,
Page2
}
下一步是在App.cs代碼中加入一個泛型集合用于保存頁面:
private static Dictionary<Pages, UserControl> pageCache = new Dictionary<Pages, UserControl>();
其中Key是Pages枚舉,Value是UserControl。然后從新定義Naviagte方法:
public static void Navigation(Pages newPage)
{
App currentApp = (App)Application.Current;
if (!pageCache.ContainsKey(newPage))
{
?? ???//根據名稱使用反射創建目標頁面實例,并加入緩存
?? ???Type type = currentApp.GetType();
?? ???Assembly assembly = type.Assembly;
?? ??? pageCache[newPage] = (UserControl)assembly.CreateInstance(
?? ??? type.Namespace + "." + newPage.ToString());
}
currentApp.rootGrid.Children.Clear();
currentApp.rootGrid.Children.Add(pageCache[newPage]);
}
這樣在其他的UserControl中調用如下代碼就可以進行切換了:
App.Navigation(Pages.Page2);
如果在頁面中放置一個文本框然后輸入值,跳轉到其他頁面再切換回來的話就會看到文本框的值依然存在,這是因為UserControl被保存在內存中了。
如果你進行切換就會發現TextBox的值依然存在.這樣就實現了簡單的緩存.
●頁面傳值:
關于頁面傳值我僅僅說一下我的方式,當讓網上也有其他的關于頁面之間傳值的方法。主要是使用獨立存儲的IsolatedStorageSettings對象,首先在UserControl中創建對象:
private IsolatedStorageSettings appSettings = IsolatedStorageSettings.ApplicationSettings;然后在Button事件中加入如下代碼,用于傳值:
if (!appSettings.Contains("Page2"))
?? appSettings.Add("Page2", "UserName");
OK了,在目標頁面獲取值的方式就簡單了.
if (appSettings.Contains("Page2"))
txbShowvalue.Text = "User Name: " + appSettings["Page2"].ToString();
需要注意的一點是這個獲取值的代碼不要寫在頁面的構造函數里面,有可能不會觸發,原因是在上面對象已經保存在內存中了,但是會觸發Loaded事件,因此可以把代碼放到這個事件里面。
你也可以使用同樣的方式來給Page1傳值。OK..關于第一種頁面導航、傳值、簡單緩存,就介紹完畢..
下篇著重說下關于Silverlight Navigation System..
關于獨立存儲請參考:http://www.bbniu.com/forum/thread-366-1-1.html
?
?
?
此次主要說一下如何使用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事件.
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?
?
此次主要是接著Frame來說的,主要是說一下Page控件以及使用模版.
在之前的例子中使用的是UserControl來做為頁面,但通常的話我們是使用Page控件的,或者自己繼承Page類來代替UserControl。因為Page提供了更方便的導航功能以及狀態管理。其實,Page類是繼承自UserControl的,然后添加了一些成員,一些可重寫的方法和四個屬性:Title、NavigationService、NavigationContext、NavigationCacheMode。其中Titel屬性比較簡單,不做多說,其他屬性在后面都會有說到。
使用Page控件很簡單,和向項目中添加UserControl一樣。
1.Page控件屬性介紹
每一個Page控件內都會有一個NavigationService屬性,這個屬性就相當于訪問Silverlight導航系統的入口,因為它提供了與Frame對象一樣的方法和屬性(Navigate()、GoBack()、GoForward(),屬性有CanGoBack、CanGoForward、CurrentSource等)。意思就是說在Page里面就可以進行導航了..
- this.NavigationService.Navigate(new Uri("/Page2.xaml", UriKind.Relative));
Page類還含有一個NavigationContext屬性用來訪問NavigationContext對象。使用這個屬性可以獲取當前的URL,使用QueryString可以獲取URL中的參數。也就是說你可以在跳轉頁面的時候使用地址欄參數傳值。如下:
- string uriText = String.Format("/Product.xaml?id={0}&type={1}",productID, productType);
- mainFrame.Navigate(new Uri(uriText), UriKind.Relative);
這樣你就可以傳兩個值到目標頁面了 ..
然后在Priduct.xaml頁面你就可以獲取到值了:
- int productID, type;
- ?? ?? ?? if (this.NavigationContext.QueryString.ContainsKey("productID"))
- ?? ?? ?? ?? productID = Int32.Parse(this.NavigationContext.QueryString["productID"]);
- ?? ?? ?? if (this.NavigationContext.QueryString.ContainsKey("type"))
- ?? ?? ?? ?? type = Int32.Parse(this.NavigationContext.QueryString["type"]);
??? 當讓你還可以使用其他方式傳值,比如在存儲在Appliaction對象中,或者是使用獨立存儲都可以實現,因為使用URL參數很容易就會被篡改..
保存頁面狀態
通常,用戶第一次進入頁面或者是使用前進后退按鈕切換頁面,都會重新創建一個對象,當用戶離開,對象就會被釋放。這種情況下,如果用戶輸入的有信息,再回到頁面就會編程默認值,頁面的其他成員也會初始化成默認值。而如果可以存儲頁面狀態的話就不會出現這種情況了。
Silverlight允許使用Page.NavigationCacheMode屬性來設置存儲策略,這個屬性的默認值是Disabled所以不會默認不會存儲頁面。把屬性設置為Required那么頁面就會保存到內存中。當用戶離開頁面再返回的時候就可以看到自己修改的內容依然存在,不過再次回到頁面不會觸發頁面的構造方法,所以如果你在構造函數里寫的有邏輯就需要注意了。不過會觸發頁面的Loaded事件。
NavigationCacheMode的另外一個值是Enabled,如果設置成這個值,那么頁面就好與Frame.CacheSize(保存頁面的數量)屬性關聯,加入CacheSize屬性設置為10,當第11個頁面存儲進來的話第一個頁面就會被釋放。而NavigationCacheMode屬性設置為Required屬性頁面就不會被計算在CacheSize中。這個可以根據自己的需要進行選擇。
Page控件的方法
??? Page類包含了幾個方法使你能更加靈活的管理導航。
- OnNavigatedTo():當頁面不再是框架中的活動頁面時調用。
- OnNavigatingFrom():當頁面成為框架中的活動頁面時調用。
- OnNavigatedFrom():在頁面即將不再是框架中的活動頁面時調用。
你可以使用這些方法在離開頁面或是訪問頁面時添加一些自己的邏輯,比如初始化一些參數或是管理其他狀態。
?? 關于Page類就說到這里,詳細的用法與說明可以查看MSDN..
Navigation Templates
現在已經學會如何使用Frome/Page控件來創建具有導航功能的應用程序,然后美中不足的是視覺效果還具有很多差距。不過你可以模擬別人的實例來不到完善,從而達到自己的效果。另外一個辦法是使用現有的模版。
如果你使用vs自帶的Silverlight Navigation Appliaction模版創建一個程序的話,會自動給你創建一套默認樣式。
??? 這個模版已經可以滿足一些基本的需求:頁面的上方為導航按鈕,下方為內容面板。這個是默認模版,而Silverlight團隊還提供了其他的幾個模版,在上一篇文章中已經展示了效果圖,你可以在本文附件中下載。具體用法是替換Style.xaml文件就OK了..
轉載于:https://www.cnblogs.com/xixifusigao/archive/2010/01/19/1651709.html
總結
以上是生活随笔為你收集整理的[转] Silverlight Navigation(多页面切换、传值)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 多生孩子,图啥呢?
- 下一篇: 如何C#中实现在TreeView查找某一