我們緊接着上篇,開始我們的Metro風格應用開發。
-----------------------------------我是華麗的分割線-----------------------------------------
17.添加頁面和導航
a)為了使我們的博客閱讀器能夠適用於所有的博客,我們必須向應用添加更多的頁面並處理如何在這些頁面之間進行導航。
首先,我們需要一個能夠列出所有博客的頁面。當閱讀器從該頁面中選擇某個博客時,我們將加載該博客的文章列表。
我們已創建的分頁閱讀器也可以完成此功能,但我們希望對它做一點改進。最后,我們需要添加一個詳細信息頁面,
以便閱讀單個博客文章,而不至於讓列表視圖占用空間。
b)頁面模板
我們不需要從空白模板開始創建每個頁面。Visual Studio 12 附帶了一個頁面模板的集合,
這些模板對於各種情形都很有用。以下是可用的頁面模板。
頁面類型 描述
組詳細信息頁 顯示單個組的詳細信息以及組中每個項目的預覽。
分組項頁 顯示分組的集合。
項詳細信息頁 詳細顯示一個項目,並允許導航到相鄰的項目。
項頁 顯示項目的集合。
拆分頁 顯示項目的列表以及所選項目的詳細信息。
基本頁 可以適應不同方向和視圖的空白頁面,並且包含一個標題和返回按鈕。
空白頁 用於 Metro 風格應用的空白頁面。
c)向應用添加頁面。
選擇“項目”>“添加新項”。“添加新項”對話框即會打開。
在“已安裝”窗格中,展開“Visual C#”或“Visual Basic”。
選擇“Windows Metro 風格”模板類型。
在中心窗格中,選擇要添加到項目中的頁面類型。
為該頁面輸入名稱ItemsPage。
單擊“添加”。XAML 和你的頁面的代碼隱藏文件即被添加到項目中。
如圖:
彈出提示,選擇"是",如圖:
針對我們的博客閱讀器,我們將添加一個項目頁面來顯示所有博客的列表。我們將此頁面命名為 ItemsPage。
我們添加一個拆分頁面來顯示每個博客的文章。我們將此頁面命名為 SplitPage。
拆分頁面模板與我們為簡單博客閱讀器應用創建的頁面相似,但更加精煉。我們針對詳細信息頁面
(將其命名為 DetailPage)使用基本頁面模板。它只有返回按鈕、頁面標題和一個用於顯示文章內容的 WebView 控件。
但並不是像我們在拆分頁面中那樣將來自 HTML 字符串的文章內容加載到 WebView,
我們導航到文章的 URL 並顯示實際的 Web 頁面。
d)模板頁面全部從 LayoutAwarePage 類派生而來,默認情況下,這些模板頁面能夠比我們使用的初始
MainPage執行更多的功能。LayoutAwarePage 是 Page 的一個實現,為 Metro 風格應用開發啟用了重要的功能:
應用程序視圖狀態到視覺狀態的映射使頁面能夠適應不同的分辨率、方向和視圖。
GoBack 和 GoHome 事件處理程序支持基本的導航。默認的視圖模型為你提供了一個簡單的可綁定數據源。
頁面模板還使用 StandardStyles.xaml 中的樣式和模板,這些樣式和模板應用 Metro 風格應用的設計指南。
我們將使用其中一些樣式作為開始,並修改它們的副本來自定義應用的外觀。
18.在頁面之間導航
XAMLUI 框架提供了使用 Frame 和 Page 的內置導航模型,其工作方式與在 Web 瀏覽器中的導航方式非常相似。
Frame控件可托管 Page,並且具有導航歷史記錄,你可以通過該歷史記錄在訪問過的頁面中前進和后退。
在導航時,你可以在頁面之間傳遞數據。在 Visual Studio 項目模板中,名為 rootFrame 的 Frame 被設置為應用窗口的內容。
我們來看看 App.xaml.cs中的代碼:

/// <summary> /// Invoked when the application is launched normally by the end user. Other entry points /// will be used when the application is launched to open a specific file, to display /// search results, and so forth. /// </summary> /// <param name="args">Details about the launch request and process.</param> protected override void OnLaunched(LaunchActivatedEventArgs args) { // Do not repeat app initialization when already running, just ensure that // the window is active if (args.PreviousExecutionState == ApplicationExecutionState.Running) { Window.Current.Activate(); return; } if (args.PreviousExecutionState == ApplicationExecutionState.Terminated) { //TODO: Load state from previously suspended application } // Create a Frame to act navigation context and navigate to the first page var rootFrame = new Frame(); if (!rootFrame.Navigate(typeof(MainPage))) { throw new Exception("Failed to create initial page"); } // Place the frame in the current Window and ensure that it is active Window.Current.Content = rootFrame; Window.Current.Activate(); }
這些代碼用於創建框架,將其設置為 Window 的內容,並導航到 MainPage。由於我們的完整應用的首頁是 ItemsPage,
因此我們將調用更改為導航方法並在此處所示的 ItemsPage 中進行傳遞。修改的代碼如下:
// Create a Frame to act navigation context and navigate to the first page var rootFrame = new Frame(); if (!rootFrame.Navigate(typeof(ItemsPage))) { throw new Exception("Failed to create initial page"); }
加載 ItemsPage 時,我們需要獲得數據源的一個實例,並檢索要顯示的源數據,
就像我們在使用應用中的數據部分中使用 MainPage 一樣。我們將代碼置於頁面模板中包括的 OnNavigatedTo 方法替代中,
如果尚未檢索源,我們將調用 FeedDataSource.GetFeedsAsync 方法。以下是 ItemsPage.xaml.cs代碼:
protected override async void OnNavigatedTo(NavigationEventArgs e) { FeedDataSource feedDataSource = App.DataSource; if (feedDataSource.Feeds.Count == 0) { await feedDataSource.GetFeedsAsync(); } DefaultViewModel["Items"] = feedDataSource.Feeds; }
當用戶從集合中選取博客時,我們從項目頁導航到拆分頁。為了執行此導航,我們希望 GridView 項目響應單擊(如按鈕)操作,
而不是被選定。為了使 GridView 項目可單擊,我們按如下所示設置 SelectionMode 和 IsItemClickEnabled 屬性。
然后我們為 GridView 的 ItemClick 事件添加一個事件處理程序。以下是 ItemsPage.xaml 中用於 GridView 的 XAML,其中已設置屬性,
並已添加 ItemClick 事件。
項目頁面還包含一個名為 itemListView 的列表視圖,如果“調整”了應用,則會顯示該列表視圖來代替網格。
我們將在適應不同的布局部分中對此進行更詳細的討論。目前,我們只需對 ListView 進行與對 GridView 所做更改相同的更改,
以確保它們的行為相同。整個Xaml代碼如下:

<!-- 在大多數視圖狀態中使用的水平滾動網格--> <GridView x:Name="itemGridView" AutomationProperties.AutomationId="ItemsGridView" AutomationProperties.Name="Items" TabIndex="1" Grid.Row="1" Margin="0,-4,0,0" Padding="116,0,116,46" ItemsSource="{Binding Source={StaticResource itemsViewSource}}" ItemTemplate="{StaticResource Standard250x250ItemTemplate}" SelectionMode="None" IsItemClickEnabled="True" ItemClick="itemView_ItemClick"/> <!-- 垂直滾動列表僅在對齊后使用--> <ListView x:Name="itemListView" AutomationProperties.AutomationId="ItemsListView" AutomationProperties.Name="Items" TabIndex="1" Grid.Row="1" Visibility="Collapsed" Margin="0,-10,0,0" Padding="10,0,0,60" ItemsSource="{Binding Source={StaticResource itemsViewSource}}" ItemTemplate="{StaticResource Standard80ItemTemplate}" SelectionMode="None" IsItemClickEnabled="True" ItemClick="itemView_ItemClick"/>
itemView_ItemClick事件如下:

private void itemView_ItemClick(object sender, ItemClickEventArgs e) { // Navigate to the split page, configuring the new page // by passing the clicked item (FeedItem) as a navigation parameter this.Frame.Navigate(typeof(SplitPage), e.ClickedItem); }
若要在頁面之間導航,你可以使用 Frame 控件的 Navigate、GoForward 和 GoBack 方法。
通過 Navigate(TypeName, Object) 方法可以導航並將數據對象傳遞到新頁面。我們將使用此方法在我們的頁面之間傳遞數據。
第一個參數 typeof(MainPage) 是我們將要導航到的頁面的 Type。第二個參數是我們傳遞給將要導航到的頁面的數據對象。
在本例中,我們傳遞 clicked 項。在 SplitPage.xaml.cs代碼隱藏頁面中,
我們需要使用剛剛從項目頁面傳遞的 FeedData 對象執行某些操作。為此,我們將覆蓋Page的OnNavigatedTo方法。
該方法已添加到頁面模板代碼中,因此我們只需要對其進行修改以便與我們的數據關聯。
模板頁面包含一個名為 DefaultViewModel 的內置視圖模型,我們可以將數據與之關聯。
NavigationEventArgs.Parameter 屬性包含從項目頁面傳遞的數據對象。 我們將其轉換回 FeedData 對象,
並將信息提要數據添加至具有關鍵字 Feed 的 DefaultViewModel,
將 FeedData.Items 屬性添加至具有關鍵字 Items 的 DefaultViewModel。以下是更新的 OnNavigatedTo 方法:
protected override void OnNavigatedTo(NavigationEventArgs e) { // TODO: Assign a bindable group to this.DefaultViewModel["Group"] // TODO: Assign a collection of bindable items to this.DefaultViewModel["Items"] FeedData feedData = e.Parameter as FeedData; if (feedData!=null) { DefaultViewModel["Feed"] = feedData; DefaultViewModel["Items"] = feedData.Items; if (!UsingLogicalPageNavigation()) { itemsViewSource.View.MoveCurrentToFirst(); } } }
在 Visual Studio 頁面模板中,TODO 注釋表示我們從何處將我們的數據對象添加至具有關鍵字 Group 的 DefaultViewModel。
由於我們使用的是關鍵字 Feed,因此我們需要更改頁面標題中的綁定,以綁定到 Feed 屬性,而不是 Group。
在 SplitPage.xaml 中,更改名為 pageTitle 的 TextBlock 的 Text 綁定以綁定到 Feed.Title,
如下所示: <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding Feed.Title}" Style="{StaticResource PageHeaderTextStyle}"/>
要導航回項目頁面,Visual Studio 頁面模板包含相應的代碼來處理 BackButton 的 Click 事件,並調用 Frame.GoBack 方法。
我們需要再做一些更改才能完成向我們添加到組中的新頁面添加功能的操作。將這些代碼添加到應用中后,
我們便可以繼續對我們的應用進行風格和動畫設置。
在 ItemsPage.xaml 中,頁面標題綁定到具有關鍵字 AppName 的靜態資源。將此資源中的文本更新到博客中,如下所示。
<x:String x:Key="AppName">Refactor's Blog</x:String>
更新SplitPage.xaml 中,將名為 titlePanel 的網格更改為跨 2 個列,Xaml代碼如下:

<!-- 后退按鈕和頁標題--> <Grid x:Name="titlePanel" Grid.ColumnSpan="2"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding DefaultViewModel.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/> <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding Feed.Title}" Style="{StaticResource PageHeaderTextStyle}"/> </Grid>
同樣在 SplitPage.xaml 中,我們需要更改用來顯示所選博客文章的標題和內容的布局。Xaml代碼如下:

<!-- 選定項的詳細信息--> <ScrollViewer x:Name="itemDetail" AutomationProperties.AutomationId="ItemDetailScrollViewer" Grid.Column="1" Grid.Row="1" Padding="70,0,120,0" DataContext="{Binding SelectedItem, ElementName=itemListView}" Style="{StaticResource VerticalScrollViewerStyle}"> <Grid x:Name="itemDetailGrid" > <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock x:Name="itemTitle" Margin="0,-10,0,0" Text="{Binding Title}" Style="{StaticResource SubheaderTextStyle}"/> <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" Grid.Row="1" Margin="0,15,0,20"> <Grid> <WebView x:Name="contentView" /> <Rectangle x:Name="contentViewRect" /> </Grid> </Border> </Grid> </ScrollViewer>
在 SplitPage.xaml.cs 中,向 ItemListView_SelectionChanged 事件處理程序添加代碼,
使用所選博客文章的內容填充 WebView,代碼如下:

private void itemView_ItemClick(object sender, ItemClickEventArgs e) { // Navigate to the split page, configuring the new page // by passing the clicked item (FeedItem) as a navigation parameter this.Frame.Navigate(typeof(SplitPage), e.ClickedItem); }
在 DetailPage.xaml 中,我們需要將標題文本綁定到博客文章標題,並添加一個 WebView 控件以顯示博客頁面。
Xaml代碼如下:

<!-- 后退按鈕和頁標題--> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/> <TextBlock x:Name="pageTitle" Text="{Binding Title}" Style="{StaticResource PageHeaderTextStyle}" Grid.Column="1"/> <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" Grid.Row="1" Margin="120,15,20,20"> <WebView x:Name="contentView" /> </Border> </Grid>
在 DetailPage.xaml.cs 中,將代碼添加到 OnNavigatedTo 方法中,代碼如下:

protected override void OnNavigatedTo(NavigationEventArgs e) { // Add this code to navigate the web view to the selected blog post. FeedItem feedItem = e.Parameter as FeedItem; if (feedItem != null) { this.contentView.Navigate(feedItem.Link); this.DataContext = feedItem; } }
19.以上操作基本完成,為了能使程序正常運行,請按如下操作:
a)注意注釋掉LayoutAwarePage中的OnNavigatedFrom方法:
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
/*var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
var pageState = new Dictionary<String, Object>();
this.SaveState(pageState);
frameState[_pageKey] = pageState;*/
}
b)更改StandardStyles.xaml文件中鍵值為VerticalScrollViewerStyle的屬性:
<Style x:Key="VerticalScrollViewerStyle" TargetType="ScrollViewer">
<Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.HorizontalScrollMode" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollMode" Value="Enabled"/>
<Setter Property="ScrollViewer.ZoomMode" Value="Disabled"/>
</Style>
c)Standard250x250ItemTemplate的屬性:
<!-- Grid-appropriate 250 pixel square item template as seen in the GroupedItemsPage and ItemsPage -->
<DataTemplate x:Key="Standard250x250ItemTemplate">
<Grid HorizontalAlignment="Left" Width="250" Height="250">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image Source="../Assets/myLogo.jpg" Stretch="UniformToFill"/>
</Border>
<StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"
Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/>
<TextBlock Text="{Binding Subtitle}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}"
Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
</StackPanel>
</Grid>
</DataTemplate>
20.程序截圖:
點擊其中一個轉到下一個頁面,如圖:
未完待續,敬請期待....