我们紧接着上篇,开始我们的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.程序截图:
点击其中一个转到下一个页面,如图:
未完待续,敬请期待....