youhui
不以物喜,不以己悲
Windows 8 metro 学习笔记
1. 在Visual Studio 11 Beta 新建Windows metro style项目 WindowsBlogReader
创建项目时,Visual Studio 创建项目文件并在“解决方案资源管理器”中显示这些文件。让我们来看看空白应用程序模板所创建的文件。
文件名称 | 描述 |
Properties/AssemblyInfo .cs | 包含嵌入到所生成的集合中的名称和版本元数据。 |
Package.appxmanifest | 包含描述你的应用的元数据,包括显示名称、说明、徽标和功能。 |
Assets/* | 你可以替换的默认徽标和初始屏幕图像。 |
Common/LayoutAwarePage | 具有能够适应不同布局和视图的页面功能的基类。 |
Common/RichTextColumns | 具有用于在列中部署的多格式文本的功能的基类。 |
Common/StandardStyles.xaml | 包含应用的默认样式和模板。 |
App.xaml,App.xaml.cs | 这些文件指定应用级逻辑。显示用户界面需要使用应用类。 |
BlankPage.xaml | 用于创建用户界面的默认起始页。 |
BlankPage.xaml.cs | 包含默认起始页的逻辑的代码的隐藏文件 |
Metro 风格应用在安全容器中运行,对文件系统、网络资源和硬件具有有限的访问权限。 无论用户何时从 Windows 应用商店安装应用,Windows 都会查看文件中的元数据Package.appxmanifest以确定应用需要执行哪些功能。例如,某个应用可能需要访问 Internet 中的数据、用户文档库中的文档,或用户的摄像头和麦克风。当应用安装完成后,它会向用户显示所需的功能,而用户必须授予相应的权限才能让它访问这些资源。如果应用没有请求并接收所需要的某个资源的访问权限,则当用户运行它时,系统将禁止其访问该资源。
下面列出了一些常见的功能:
功能 | 名称 | 描述 |
文档库 | documentsLibrary | 允许应用访问用户的文档库,以及添加、更改或删除文件。你的应用只能访问已在清单中声明的文件类型,不能访问家庭组计算机上的文档库。 |
企业身份验证 | enterpriseAuthentication | 允许应用连接至需要域凭据的 Intranet 资源。 |
Internet(客户端和服务器) | internetClientServer | 允许你的应用访问 Internet 和公用网络,允许通过 Internet 连接到你的应用。对重要端口的入站访问始终会被阻止。这是 Internet(客户端)功能的一个超集。你不用同时声明两者。 |
Internet(客户端) | internetClient | 允许你的应用访问 Internet 和公用网络。大部分需要 Internet 访问的应用都应使用此功能。 |
位置 | location | 允许你的应用访问用户的当前位置。 |
麦克风 | microphone | 允许你的应用访问用户的麦克风。 |
音乐库 | musicLibrary | 允许你的应用访问用户的音乐库,并允许添加、更改或删除文件。还允许访问家庭组计算机上的音乐库以及本地连接的媒体服务器上的音乐文件类型。 |
图片库 | picturesLibrary | 允许你的应用访问用户的图片库,并允许添加、更改或删除文件。还允许访问家庭组计算机上的图片库,以及本地连接的媒体服务器上的图片文件类型。 |
专用网络(客户端和服务器) | privateNetworkClientServer | 允许通过你的应用对用户信任的网络(例如家庭或企业网络)进行入站和出站访问。对重要端口的入站访问始终会被阻止。 |
近程 | proximity | 允许你的应用访问用户的接近现场通信 (NFC) 设备。 |
可移动存储 | removableStorage | 允许你的应用访问可移动存储设备,例如外部硬盘驱动器或 USB 闪存驱动器,并允许添加、更改或删除文件。你的应用只能访问已在清单中声明的文件类型。你的应用不能访问家庭组计算机上的可移动存储设备。 |
共享用户证书 | sharedUserCertificates | 允许你的应用访问软件和硬件证书,例如智能卡证书。 |
文本消息传递 | sms | 允许你的应用访问文本消息传递功能。 |
视频库 | videosLibrary | 允许你的应用访问用户的视频库,并允许添加、更改或删除文件。还允许访问家庭组计算机上的视频库,以及本地连接的媒体服务器上的视频文件类型。 |
网络摄像机 | webcam | 允许你的应用访问用户的照相机。 |
向应用程序添加工程
指定某项功能时,该功能即会列在 Capabilities 元素下的 Package.appxmanifest.xml 文件中。如我们刚才看到的一样,你通常是在应用程序清单设计器中设置功能,但如果你右键单击文件,选择“打开方式…”,并在XML 编辑器中打开该文件,则可以在XML 中看到此Capabilities 元素。
介绍
在本部分中,你将学习如何:
1. 创建自定义数据类
2. 异步检索RSS或Atom数据信息提要
既然我们的应用可以从 Internet 下载数据了,我们便可以编写代码以将博客信息提要置于其中了。Windows团队博客以 RSS和 Atom两种形式展示了文章的完整文本。我们希望在阅读器应用中显示的博客数据为每篇最新博客文章的标题、作者、日期和内容。
我们可以在 Windows.Web.Syndication命名空间中可以找到可以为我们执行处理信息提要数据的类
在WindowsBlogReader项目中添加FeedData类
在我们的博客阅读器应用中,我们使用 3 个类来容纳和检索信息提要数据。我们将所有3 个类都放在一个名为FeedData.cs的文件中。FeedData 类容纳有关RSS 或Atom 信息提要的信息。FeedItem 类容纳有关信息提要所包含的单个博客文章的信息。FeedDataSource 类包含信息提要的机会以及从网络检索信息提要的方法。以下是这些类的代码。
{
public string Title { get; set; }
public string Author { get; set; }
public string Content { get; set; }
public DateTime PubDate { get; set; }
public Uri Link { get; set; }
}
public class FeedData
{
public string Title { get; set; }
public string Description { get; set; }
public DateTime PubDate { get; set; }
private List<FeedItem> _Items = new List<FeedItem>();
public List<FeedItem> Items
{
get
{
return this._Items;
}
}
}
public class FeedDataSource
{
private ObservableCollection<FeedData> _Feeds = new ObservableCollection<FeedData>();
public ObservableCollection<FeedData> Feeds
{
get
{
return this._Feeds;
}
}
public async Task GetFeedsAsync()
{
Task<FeedData> feed1 = GetFeedAsync( " http://windowsteamblog.com/windows/b/developers/atom.aspx ");
Task<FeedData> feed2 = GetFeedAsync( " http://windowsteamblog.com/windows/b/windowsexperience/atom.aspx ");
Task<FeedData> feed3 = GetFeedAsync( " http://windowsteamblog.com/windows/b/extremewindows/atom.aspx ");
Task<FeedData> feed4 = GetFeedAsync( " http://windowsteamblog.com/windows/b/business/atom.aspx ");
Task<FeedData> feed5 = GetFeedAsync( " http://windowsteamblog.com/windows/b/bloggingwindows/atom.aspx ");
Task<FeedData> feed6 = GetFeedAsync( " http://windowsteamblog.com/windows/b/windowssecurity/atom.aspx ");
Task<FeedData> feed7 = GetFeedAsync( " http://windowsteamblog.com/windows/b/springboard/atom.aspx ");
Task<FeedData> feed8 = GetFeedAsync( " http://windowsteamblog.com/windows/b/windowshomeserver/atom.aspx ");
// There is no Atom feed for this blog, so we use the RSS feed.
Task<FeedData> feed9 = GetFeedAsync( " http://windowsteamblog.com/windows_live/b/windowslive/rss.aspx ");
Task<FeedData> feed10 = GetFeedAsync( " http://windowsteamblog.com/windows_live/b/developer/atom.aspx ");
Task<FeedData> feed11 = GetFeedAsync( " http://windowsteamblog.com/ie/b/ie/atom.aspx ");
Task<FeedData> feed12 = GetFeedAsync( " http://windowsteamblog.com/windows_phone/b/wpdev/atom.aspx ");
Task<FeedData> feed13 = GetFeedAsync( " http://windowsteamblog.com/windows_phone/b/wmdev/atom.aspx ");
this.Feeds.Add(await feed1);
this.Feeds.Add(await feed2);
this.Feeds.Add(await feed3);
this.Feeds.Add(await feed4);
this.Feeds.Add(await feed5);
this.Feeds.Add(await feed6);
this.Feeds.Add(await feed7);
this.Feeds.Add(await feed8);
this.Feeds.Add(await feed9);
this.Feeds.Add(await feed10);
this.Feeds.Add(await feed11);
this.Feeds.Add(await feed12);
this.Feeds.Add(await feed13);
}
private async Task<FeedData> GetFeedAsync( string feedUriString)
{
// using Windows.Web.Syndication;
SyndicationClient client = new SyndicationClient();
Uri feedUri = new Uri(feedUriString);
try
{
SyndicationFeed feed = await client.RetrieveFeedAsync(feedUri);
// This code is executed after RetrieveFeedAsync returns the SyndicationFeed.
// Process it and copy the data we want into our FeedData and FeedItem classes.
FeedData feedData = new FeedData();
feedData.Title = feed.Title.Text;
if (feed.Subtitle.Text != null)
{
feedData.Description = feed.Subtitle.Text;
}
// Use the date of the latest post as the last updated date.
feedData.PubDate = feed.Items[ 0].PublishedDate.DateTime;
foreach (SyndicationItem item in feed.Items)
{
FeedItem feedItem = new FeedItem();
feedItem.Title = item.Title.Text;
feedItem.PubDate = item.PublishedDate.DateTime;
feedItem.Author = item.Authors[ 0].Name.ToString();
// Handle the differences between RSS and Atom feeds.
if (feed.SourceFormat == SyndicationFormat.Atom10)
{
feedItem.Content = item.Content.Text;
feedItem.Link = new Uri( " http://windowsteamblog.com " + item.Id);
}
else if (feed.SourceFormat == SyndicationFormat.Rss20)
{
feedItem.Content = item.Summary.Text;
feedItem.Link = item.Links[ 0].Uri;
}
feedData.Items.Add(feedItem);
}
return feedData;
}
catch (Exception)
{
return null;
}
}
}
Windows.Web.Syndication.SyndicationClient 类可检索完全解析的RSS 或Atom 信息提要,因此,我们不用担心解析XML 的问题,而可以继续构建应用中更加有趣的部分。SyndicationClient类只提供一种检索信息提要的方法,并且是异步的。异步编程模型在Windows 运行时中常常用于帮助应用保持响应。幸运的是,程序已经为我们处理好了在使用异步方法时可能会遇到的许多复杂问题。
在 C# 中使用await
如果在 C# 中使用await关键字,则以异步方式检索信息提要的代码将与以同步方式检索信息提要时所使用的代码相似。我们来看一下。
在 GetFeedsAsync 方法中,我们针对每个我们想要检索的博客信息提要调用 GetFeedAsync。只要可能,我们便会在 URL 中对 Atom信息提要进行传递,因为它包含我们想要显示的作者数据。当每个博客信息提要返回时,我们将其添加到FeedDataSource.Feeds 集合。
{
Task<FeedData> feed1 = GetFeedAsync( " http://windowsteamblog.com/windows/b/developers/atom.aspx ");
...
this.Feeds.Add(await feed1);
...
}
现在,让我们更详细地来看看 GetFeedAsync 方法,以了解 await 关键字是如何为我们提供帮助的。第一个要注意的是:我们将 async 关键字添加到方法签名中。
private async Task<FeedData> GetFeedAsync(string feedUriString)
{
...
}
你只能在被定义为async的方法中使用await关键字。我们将返回类型指定为Task<FeedData>。这是告诉编译器生成表示方法检索的FeedData 对象的 Task。
在方法内,我们实例化一个 SyndicationClient 并调用其 RetrieveFeedAsync 方法来获取包含我们需要的 RSS 或 Atom信息的 SyndicationFeed。
SyndicationFeed feed = await client.RetrieveFeedAsync(feedUri);
await关键字会告诉编译器在后台自动为我们执行多种处理。编译器会将该方法中位于此调用之后的其余部分作为此调用返回后将要执行的回调。它会紧接着将控制返回给调用会话(通常是UI 会话),以使应用保持响应。此时,会将表示此方法的最终输出的Task(一个FeedData对象)返回给调用者。
当 RetrieveFeedAsync 返回包含我们所需数据的SyndicationFeed 时,将执行我们的方法中其余的代码。重要的是,系统在我们从中进行原始调用的相同会话上下文(UI会话)中执行这些代码,因此当我们需要在此代码中更新 UI时不必担心使用调度程序。检索到 SyndicationFeed 后,我们将需要的部分复制到FeedData 和FeedItem 数据类中。
// Process it and copy the data we want into our FeedData and FeedItem classes.
FeedData feedData = new FeedData();
feedData.Title = feed.Title.Text;
if (feed.Subtitle.Text != null)
{
feedData.Description = feed.Subtitle.Text;
}
// Use the date of the latest post as the last updated date.
feedData.PubDate = feed.Items[ 0].PublishedDate.DateTime;
foreach (SyndicationItem item in feed.Items)
{
FeedItem feedItem = new FeedItem();
feedItem.Title = item.Title.Text;
feedItem.PubDate = item.PublishedDate.DateTime;
feedItem.Author = item.Authors[ 0].Name.ToString();
// Handle the differences between RSS and Atom feeds.
if (feed.SourceFormat == SyndicationFormat.Atom10)
{
feedItem.Content = item.Content.Text;
feedItem.Link = new Uri( " http://windowsteamblog.com " + item.Id);
}
else if (feed.SourceFormat == SyndicationFormat.Rss20)
{
feedItem.Content = item.Summary.Text;
feedItem.Link = item.Links[ 0].Uri;
}
feedData.Items.Add(feedItem);
}
return feedData;
当我们执行到 return 语句时,我们并未真正返回FeedData 对象。请记住,当方法紧随await 语句返回给调用程序时,会返回一个表示该方法的最终输出结果的Task。现在,我们就已经最终获得想要的结果了。return feedData; 将作为方法结果的FeedData 对象提供给正在等待该对象的Task。
在GetFeedsAsync 方法中在等待Task。
this.Feeds.Add(await feed1);
当 Task 获得正在等待的 FeedData 结果后,代码继续执行,FeedData 被添加到FeedDataSource.Feeds 集合中。
为了使用我们的应用中的数据,我们在 App.xaml.cs中创建了数据源的一个静态实例。我们将实例命名为DataSource。
{
// Add a static instance of FeedDataSource.
public static FeedDataSource DataSource;
public App()
{
this.InitializeComponent();
// Instantiate the data source.
DataSource = new FeedDataSource();
}
...
}
页面模板已经在它的代码隐藏文件中包含一个 OnNavigatedTo 方法的替代。我们将代码置于此方法中,以获得应用的 FeedDataSource 实例并获得源。首先,我们将 async 关键字添加到方法声明,因为我们在方法中使用await 关键字。导航到页面时,我们检查以查看 FeedDataSource 是否已包含源。如果未包含,我们调用 FeedDataSource.GetFeedsAsync 方法。然后,将页面的DataContext 设置为第一个源。 以下是 BlankPage.xaml.cs 的相关代码。
protectedoverrideasyncvoid OnNavigatedTo(NavigationEventArgs e)
{
FeedDataSource _feedDataSource =App.DataSource;
if (_feedDataSource.Feeds.Count == 0)
{
await _feedDataSource.GetFeedsAsync();
}
this.DataContext = (_feedDataSource.Feeds).First();
}
在XAML 中定义应用布局
博客阅读器应用的典型布局如下:顶部为标题,左侧是文章列表,右侧是所选文章的内容。
在我们的博客阅读器 UI 的草稿中,可以清楚地看到,我们需要显示一行文本(博客和文章标题)、多行文本(文章内容)和博客文章列表。我们通过添加TextBlock控件以显示标题,并且用一个ListView控件来显示博客文章列表。初看之下,我们似乎可以使用一个多行的TextBlock或RichTextBlock来显示文章内容。但是,当我们更深入了解时,我们发现包含文章内容的字符串不是纯文本,而是HTML 字符串。我们不想显示一堆HTML 标记,但如果我们将字符串放在一个TextBlock 中便会发生这种情况,因此我们使用WebView 控件来显示HTML。
添加控件后,我们的 UI 所用的 XAML 现在看起来类似于以下所示。
日期转换类:
{
public object Convert( object value, Type targetType, object parameter, string culture)
{
if (value == null)
throw new ArgumentNullException( " value ", " Value cannot be null. ");
if (! typeof(DateTime).Equals(value.GetType()))
throw new ArgumentException( " Value must be of type DateTime. ", " value ");
DateTime dt = (DateTime)value;
if (parameter == null)
{
// Date "7/27/2011 9:30:59 AM" returns "7/27/2011"
return DateTimeFormatter.ShortDate.Format(dt);
}
else if (( string)parameter == " day ")
{
// Date "7/27/2011 9:30:59 AM" returns "27"
DateTimeFormatter dateFormatter = new DateTimeFormatter( " {day.integer(2)} ");
return dateFormatter.Format(dt);
}
else if (( string)parameter == " month ")
{
// Date "7/27/2011 9:30:59 AM" returns "JUL"
DateTimeFormatter dateFormatter = new DateTimeFormatter( " {month.abbreviated(3)} ");
return dateFormatter.Format(dt).ToUpper();
}
else if (( string)parameter == " year ")
{
// Date "7/27/2011 9:30:59 AM" returns "2011"
DateTimeFormatter dateFormatter = new DateTimeFormatter( " {year.full} ");
return dateFormatter.Format(dt);
}
else
{
// Requested format is unknown. Return in the original format.
return dt.ToString();
}
}
public object ConvertBack( object value, Type targetType, object parameter, string culture)
{
string strValue = value as string;
DateTime resultDateTime;
if (DateTime.TryParse(strValue, out resultDateTime))
{
return resultDateTime;
}
return DependencyProperty.UnsetValue;
}
}
在App.xaml 中将DataConverter 定义成资源,以便在整个工程中有可以用到 <local:DateConverter x:Key="dateConverter"/>
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="using:WindowsBlogReader"
xmlns:d ="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable ="d" >
< Grid Background =" {StaticResource ApplicationPageBackgroundBrush} " >
< Grid.RowDefinitions >
< RowDefinition Height ="140" />
< RowDefinition Height ="*" />
</ Grid.RowDefinitions >
<!-- Title -->
< TextBlock x:Name ="TitleText" Text =" {Binding Title} " VerticalAlignment ="Center" FontSize ="48" Margin ="56,0,0,0" />
<!-- Content -->
< Grid Grid.Row ="1" >
< Grid.ColumnDefinitions >
< ColumnDefinition Width ="2*" MinWidth ="320" />
< ColumnDefinition Width ="3*" />
</ Grid.ColumnDefinitions >
<!-- Left column -->
<!-- The default value of Grid.Column is 0, so we do not need to set it
to make the ListView show up in the first column. -->
< ListView x:Name ="ItemListView" ItemsSource =" {Binding Items} " Margin ="60,0,0,10" SelectionChanged ="ItemListView_SelectionChanged" >
< ListView.ItemTemplate >
< DataTemplate >
< StackPanel >
< TextBlock Text =" {Binding Title} " FontSize ="24" Margin ="5,0,0,0" TextWrapping ="Wrap" />
< TextBlock Text =" {Binding Author} " FontSize ="16" Margin ="15,0,0,0" />
< TextBlock Text =" {Binding Path=PubDate, Converter={StaticResource dateConverter}} " FontSize ="16" Margin ="15,0,0,0" />
</ StackPanel >
</ DataTemplate >
</ ListView.ItemTemplate >
</ ListView >
<!-- Right column -->
<!-- We use a Grid here instead of a StackPanel so that the WebView sizes correctly. -->
< Grid DataContext =" {Binding ElementName=ItemListView, Path=SelectedItem} " Grid.Column ="1" Margin ="25,0,0,0" >
< Grid.RowDefinitions >
< RowDefinition Height ="Auto" />
< RowDefinition Height ="*" />
</ Grid.RowDefinitions >
< TextBlock x:Name ="PostTitleText" Text =" {Binding Title} " FontSize ="24" />
< WebView x:Name ="ContentView" Grid.Row ="1" Margin ="0,5,20,20" />
</ Grid >
</ Grid >
</ Grid >
</ Page >
我们向 BlankPage.xaml.cs 代码隐藏页添加事件处理程序。在事件处理程序中,我们将所选项目转换为一个FeedItem,并从 Content 属性获取 HTML 的字符串。然后,我们将该字符串传递给NavigateToString 方法。
privatevoid ItemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
FeedItem feedItem = e.AddedItems[0]asFeedItem;
if (feedItem !=null)
{
// Navigate the WebView to the blog post content HTML string.
ContentView.NavigateToString(feedItem.Content);
}
}
以下是该应用程序在模拟器运行时的情况。
可以看到,在此方式下我们基本页面的效果不如之前那么好。我们希望我们的应用看起来美观,并且能更好地反映 Windows 团队博客的风格。为了实现这些目标,我们使用 Microsoft Visual Studio中提供的页面模板作为开始。然后,我们使用在创建此页面的过程中学到的知识来修改模板并创建我们的完整应用。
未完,待续...
我希望你能喜欢我的文章!卤面网是目前国内最大也是最具影响力的WP7开发论坛,如果你有更多想法,请到卤面网wp7开发论坛(http://www.hugwp.com/forum.php)问答区联系我,我会很高兴知道你在想什么。同时wp7交流QQ群172765887中,也能找到我的身影,感谢大家,也欢迎大家关注我的微博.
本文源码下载: