作者:马宁
示例代码下载地址:https://files.cnblogs.com/aawolf/ContosoCookbook.zip
Windows 8的Release Preview版已经在2012年的儿童节正式发布了。虽然不如外界期望的那么成熟,Windows 8开始慢慢的学步了。作为开发者,我们面临的挑战要大于之前Windows的每一次升级。Windows 8对于开发者的挑战,可能仅次于当年从DOS升到Windows,别忘了,无数在DOS下非常成功的软件因为无法支持Windows而在一夜间烟消云散。
这次Windows 8的挑战主要来自于Metro UI,Metro UI是从Windows Phone衍生而来,适合移动设备多点触摸输入方式的UI Framework。而Metro UI的编程方式类似于Silverlight,结合了Windows Phone中的一些特点,但是Metro Style UI Framework却和两者都不相同。
Metro UI支持三种编程语言:.NET(C#,VB.NET)、C++和JavaScript。在这些编程语言的下面,是统一的WinRT Framework。
我们在这里就不多聊WinRT了,如果有兴趣的,可以去看微软的官方文档:
http://msdn.microsoft.com/library/windows/apps/
最近讨论C++和编程的比较多,反而是.NET编程有点冷清。所以,我在这里改写了一个例子,供大家参考。这个例子原本是JavaScript版的Contoso Cookbook,在Windows 8训练营时作为例子,应该很多人已JavaScript经拿到了。因为看不懂JavaScript,所以,我决定将其改写成C#版的。其中的图片的版权属于微软,如果有任何侵权问题,请通知我。我会进行更换。
好了,接下来,言归正传。
开发环境和创建工程
我们的开发环境是Windows 8 Release Preview和Visual Studio Ultimate 2012 RC版。如果没有Visual Studio Ultimate 2012 RC版的朋友,也可以下载Visual Studio 2012 Express RC for Windows 8版,来开发Windows 8 Metro style应用。下载地址如下:
http://msdn.microsoft.com/en-US/windows/apps/br229516.aspx
进入到Visual Studio 2012后,发现整个界面都很Metro,还能够切换黑背景和白背景,虽然图标变化较大,但是操作模式基本没有什么变化。所以,我们选择Visual C#下的Windows Metro style项目,其中包括了Grid App(XAML)选项,我们将工程名改为ContosoCookbook,点击OK。
初次创建Windows 8的应用,需要申请开发者许可。目前是免费的,不知道将来是否要收费。点击“我同意”后,输入Live ID的用户名和密码即可。
然后,我们就进入到了Visual Studio 2012的主界面。主界面变化不大,我们选择直接运行应用程序,会进入到全屏的Metro UI界面,如下图所示:
这个形式也是Metro style里使用较多的一种展现形式。好坏就不评价了,大家慢慢适应吧。
刚开始有一件郁闷的事情,不知道该怎么返回Visual Studio,在尝试了N多方法后,发现按Win键+D,可以直接返回Visual Studio。
除了本机调试外,我们还能够使用模拟器来进行调试,想必这就是Windows 8平板以后的调试方式了。在工具栏的运行按钮后边有一个下拉菜单,选择“Simulator”,点击运行,会打开一个模拟器,如图:
这个模拟器中的状态和本机是一致的,包括安装的应用程序,但我们可以使用一些附加功能,来调试平板上特有的功能,比如:旋转、定位、摄像头等。最逗的是,这个模拟器没有关闭按钮,你唯一的办法就是,在模拟器中找到关机界面,然后将其关闭。
修改应用名称和图标
接下来,我们修改应用程序的启动界面和图标。在Solution Explorer中,找到Assets目录,其中需要修改的图标文件有:Logo.png(150x150), SmallLogo.png(30x30)和StoreLogo.png(50x50),启动界面是SplashScreen.png(620x300)。右键选中Assets,选择添加已有项目“Add - Existing Item”,将准备好的图片添加进来,覆盖掉默认的图标即可。
接下来,双击Package.appxmanifest文件,打开的是一个编辑器,可以设置与应用程序相关的属性。修改其中的Display name和Description属性,
除此之外,还有很多属性可以设置,在这里就不一一介绍了。Packaging属性页里,有一个Guid的属性,估计后边还可能出现Windows Phone上几个应用用一个Guid的情况了。
修改完了Logo和显示名称之后,再次运行,可以看到新的界面。
加载数据和图片
接下来就到了重头戏,要加载数据和图片了。首先在Solution Explorer中创建两个文件夹:data和images。data文件夹中添加Recipes.txt文件,其中包括了一组Json数据,包含了世界各地的美食介绍;images文件夹中,要包含所有的图片文件,直接将图片文件拖拽进去即可。
然后,打开DataModel目录下的SampleDataSource.cs文件,在文件顶部添加引用:
using Windows.Storage; using Windows.ApplicationModel; using Windows.Data.Json;
然后在SampleDataSource类中增加一个新的方法来加载数据文件:
public static async System.Threading.Tasks.Task LoadFile() { StorageFolder folder = await Package.Current.InstalledLocation.GetFolderAsync("data"); ; StorageFile file = await folder.GetFileAsync("Recipes.txt"); string text = await FileIO.ReadTextAsync(file); }
Windows 8文件访问方式发生了很大的变化,我花了一上午时间才搞定,最后的代码却很简单。调用Package.Current.InstalledLocation属性,就可以找到程序当前运行的路径,该属性返回一个StorageFolder对象。因为Recipes.txt文件放在data目录中,所以还要调用GetFolderAsync方法,来获取data子文件夹。GetFolderAsync方法是一个异步调用方法,所以我们需要使用一个新的关键字:await来等方法调用结束返回结果。await关键字只能在异步调用的方法中被调用,所以LoadFile方法声明时,增加了async关键字,表示该方法是一个异步调用方法。关于await和async关键字,我们以后有机会详细来说。
获取了StorageFolder对象后,我们可以调用StorageFolder的GetFileAsync方法,来获取文件的StorageFile对象。然后再使用FileIO的ReadTextAsync方法,将文件读取到字符串中。
好了,与文件相关的操作就介绍到这里。接下来,我们看一下,如何分析JSON数据。
分析JSON数据
随着JSON数据格式的流行,Metro style Framework也增加了对于JSON数据的支持。我们先来看看数据大概是什么样的:
{"group":{"key":"Chinese","title":"Chinese","shortTitle":"Chinese","recipesCount":0,"description":"Lorem ipsum dolor sit amet","rank":"","backgroundImage":"images/Chinese/chinese_group_detail.png", "headerImage":"images/Chinese/chinese_group_header.png"}, "key":1000, "title":"Chinese Salad", "shortTitle" : "Chinese Salad", "preptime":30, "favorite":false, "rating": 3 , "directions":"Preheat the broiler.", "backgroundImage":"images/Chinese/Chinese Salad.jpg", "ingredients":["1 head Napa or bok choy cabbage","1/4 cup sugar","1/4 teaspoon salt","3 tablespoons white vinegar","3 green onions","1 (3-ounce) package ramen noodles with seasoning pack","1 (6-ounce) package slivered almonds","1 tablespoon sesame seeds","1/2 cup vegetable oil"]},
每项数据分属不同的Group,而我们需要的Group数据有:ID、标题、子标题、描述和标题图片;数据项的数据包括:Key、标题、子标题、背景图片和菜谱。
所以,我们在LoadFile方法里增加下面的代码,代码略微有点长:
JsonArray parsedResponse = JsonArray.Parse(text); if (parsedResponse.Count > 1) { string groupid = ""; SampleDataGroup group1 = null; foreach (JsonValue value in parsedResponse) { var obj = value.GetObject(); var group = obj["group"].GetObject(); string id = group["key"].GetString(); if (id != groupid) { groupid = id; group1 = new SampleDataGroup( group["key"].GetString(), group["title"].GetString(), group["shortTitle"].GetString(), group["headerImage"].GetString(), group["description"].GetString() ); _sampleDataSource.AllGroups.Add(group1); } group1.Items.Add(new SampleDataItem( obj["key"].GetNumber().ToString(), obj["title"].GetString(), obj["shortTitle"].GetString(), obj["backgroundImage"].GetString(), obj["directions"].GetString(), ITEM_CONTENT, group1) ); }
我们首先调用JsonArray.Parse方法来解析刚刚获取到的Json数据,返回一个JsonArray的数组对象。接下来,通过foreach来遍历JsonArray中的数据项,获取到的对象是JsonValue对象。因为获取到的数据是对象,所以,我们使用JsonValue的GetObject方法来获取一个对象,虽然代码里用的是var,实际的数据类型是JsonObject。然后用obj["group"].GetObject()方法来获取到Group信息的JsonObject,中括号中必须填写Key的名称。然后取出Group的Id值,该值是一个字符串,所以调用GetString方法来获取Id值。如果读取出的Id值与之前不同,则创建新的SampleDataGroup对象,对象中所需的数据则通过Group对象的GetString方法来获取;然后,将其添加到SampleDataSource 的AllGroups属性中。
接下来,就是创建SampleDataItem对象,这次要从obj对象里取到所需信息,如果JSON数据中是数字,则调用GetNumber方法。最后将创建好的SampleDataItem对象添加到SampleDataGroup的Items列表中。
到这里,我们关于JSON的代码就写完了。接下来,摆在我们面前的还有一个问题。
解决异步调用的问题
接下来的代码似乎顺理成章了。我们在SampleDataSource的构造函数中,将里边的示例代码全部删除,然后添加LoadFile方法,如下图:
public SampleDataSource()
{
LoadFile();
}
运行之后的效果却是如下:
Group的数据被加载了,但是其中的数据项未被加载。而我们点击Chinese项目时,则能够看到中国美食的情况,再退回主界面,所有的数据就能够显示出来了。这是典型的异步调用错误。原因也很简单,是因为SampleDataSource的构造函数是同步调用,不会等LoadFile结束后再返回,所以,当界面上显示数据时,显示的只是未加载完成的数据列表。
头疼的问题啊,我试了几种方案,最后采取的这种方案未必是最好的,但确实能够解决这个问题。也许大家有更好的解决方法,留一个悬疑吧。接下来,我说我的办法。
由于构造函数无法变成async调用方法,所以只能用另外的一个显式初始化函数来替代构造函数的作用,在数据显示之前,显式地调用该初始化函数。所以,我就直接将LoadFile作为这个初始化函数,为LoadFile增加public,static和async关键字。
然后,我们打开GroupItemsPage.xaml.cs文件,找到LoadState方法。该方法用来设置当前视图的数据源,所以,我们在LoadState方法的顶部显式调用SampleDataSource.LoadFile方法。当然,我们会得到一个编译错误,因为LoadState方法也是一个同步调用函数,简单地为LoadState方法增加一个async的关键字就可以解决这个问题。代码如下:
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState) { await SampleDataSource.LoadFile(); var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter); this.DefaultViewModel["Groups"] = sampleDataGroups; }
好了,当我们终于做完这一切之后,我们就可以看到完整的用户界面了:
写在最后
看起来很简单的代码,却整整写了一天,期间试过的方法也很多,一言难尽。目前Windows 8的开发资料也比较少,我在开发过程中,找到了下面的例子“Windows 8 Release Preview Metro style app samples - C#, VB.NET, C++, JavaScript”,在其中找到很多有用的代码:
http://code.msdn.microsoft.com/windowsapps/Windows-8-Modern-Style-App-Samples
好了,这次的例子包括了数据绑定、JSON数据分析和文件读取,希望后边还有时间能够写更多的例子出来。