HTML5-和-JavaScript-的-Windows8-开发高级教程-一-

100 阅读1小时+

HTML5 和 JavaScript 的 Windows8 开发高级教程(一)

原文:Pro Windows 8 Development with HTML5 and JavaScript

协议:CC BY-NC-SA 4.0

一、将 Windows 8 放在上下文中

Windows 8 代表着微软希望突破传统桌面计算市场,并在移动世界中产生影响,移动世界一直由安卓设备,当然还有苹果产品主导。

微软的计划是为用户提供跨设备的一致性,允许相同的应用对用户的数据进行操作,而不管用户手边有哪种设备或哪种设备。这对许多用户来说很有吸引力,它利用了微软最大的素材——在桌面计算市场的领先地位——来推动平板电脑和智能手机市场的销售、接受度和可信度。

传统的 Windows 桌面对于不同类型的设备之间的一致性来说不是一个好的模型,并且为更小的屏幕添加触摸支持和重新设计界面的尝试也没有好结果。试图将旧的 Windows 模式扩展到小型设备上是微软之前进军移动世界失败的部分原因。

这就是 Windows 应用的用武之地。微软决定创建一个新的应用模型,而不是延续现有的应用模型。Windows Store 应用,通常被称为应用,可以在任何可以运行 Windows 8 及其衍生产品(Windows Phone 8、Windows RT 等)的设备上使用。).更重要的是,Windows 应用在配有鼠标和键盘的大屏幕台式机上运行得和在中等大小的触摸屏平板电脑上一样好。Windows Store 应用与常规的 Windows 桌面应用大相径庭:它们充满了屏幕,没有标题栏和按钮,具有完全不同的外观和感觉。

微软的另一个重大突破是,你可以使用网络技术来创建应用,这是我写这本书的原因,也很可能是你阅读这本书的原因。通过拥抱 HTML、CSS 和 JavaScript,微软拥抱了一个全新的开发人员社区,他们可以将自己的 web 应用开发知识应用到 Windows 应用开发中。

images 微软使用了 Windows Store App 这个术语,我觉得这个术语很别扭,我无法让自己在整本书中使用它。相反,我会提到 Windows 应用,通常只是简单的应用。我会让你在心里插入你认为合适的微软官方名称。

将应用开发置于背景中

Windows 应用是微软努力在一系列不同设备类型上提供一致用户体验的核心,包括传统的台式电脑、平板电脑和智能手机。Windows 应用提供快速流畅的用户交互,支持触摸和键盘/鼠标输入,并紧密集成到微软的云服务中,允许用户在他们工作的任何地方和他们使用的任何设备上复制他们的数据。

应用与传统的 Windows 桌面应用非常不同。默认情况下,Windows 8 应用充满屏幕,是 chromeless (这意味着没有周围的窗口、标题栏或按钮),不能像桌面应用那样调整大小或重叠。用户不关闭应用,也没有关闭或退出按钮。一次只显示一个应用,因此不需要窗口或标题栏。

需要用户输入的关键对话框,比如文件选择器,也是全屏的,就像迷你应用一样。事实上,它们的外观和感觉很像你可能在 iPhone、iPad 或 Android 设备上看到的应用——这当然不是偶然的。通过 Windows 8,微软旨在获得利润丰厚的智能手机和平板电脑市场,并希望通过在包括普通个人电脑在内的各种平台上提供应用来利用其在桌面世界的主导地位。

应用受益于一系列集成服务,称为契约,这使得创建紧密集成到 Windows 平台的应用变得容易,并且可以与其他应用共享数据。如果你刚刚安装了 Windows 8,并且一直想知道 Charm Bar 上的一些图标是干什么用的,那就不要再想了。应用通过 Charm Bar 使用契约向用户提供服务。当您刚接触 Windows 时,这可能看起来是一个笨拙的工具,但它很快就会成为您的第二天性。

使用 JavaScript 和 HTML 开发 Windows 应用

微软 Windows 8 最大的改变之一是让 JavaScript 和 HTML 成为应用开发的一等公民。这是一件大事,原因有二:第一是微软在。NET 平台多年来一直不愿意为不属于. NET 平台的工具和语言开放 Windows 开发。NET 家族,比如 C#。Windows 8 彻底改变了这一点。

第二个原因是微软坚持标准。您用来编写 web 应用的 JavaScript 和 HTML 与您用来编写 Windows 应用的 JavaScript 和 HTML 是相同的。仍然有新的库和技术需要学习——因此有了这本书——但是如果你已经开发了一个 web 应用,那么你已经拥有了应用开发所需的大量知识和经验。我在第三章中演示了这一点,我将向你展示如何使用普通的 JavaScript 和 HTML 创建你的第一个应用。这种网络驱动的主题根深蒂固:用 JavaScript/HTML 编写的应用是使用 Internet Explorer 10 执行的(尽管这对用户来说并不明显,因为他们无法判断你使用了哪种技术来创建你的应用)。你不能改变使用哪个浏览器来执行你的应用,但过一段时间后,你就不会真的想这么做了——IE10 非常好,对新的 HTML5 和 CSS3 功能有一些很好的支持。(有一些特定于微软的扩展,但它们出现在 W3C 标准仍在开发中或特性非常特定于应用的情况下。)

使用 Visual Studio 开发应用

与常规的 web 开发不同,当使用 JavaScript 和 HTML 编写应用时,您不能选择自己的开发工具:您必须使用 Visual Studio 2012,这与针对任何微软平台的开发所需的工具相同。因此,坏消息是您必须学习一个新的开发环境,但好消息是 Visual Studio 非常优秀,微软已经花时间使 JavaScript 和 HTML 支持与我用于常规 web 开发的任何工具和编辑器一样好。不过,我不得不承认,从我写 C#应用和服务的时候起,我就对 Visual Studio 情有独钟,而且你可能会发现,当你努力学习一套新的工具一种新的应用开发时,学习曲线是陡峭的。这不是一本关于 Visual Studio 的书,但是在第二章中,我会给你一个基本特性的快速浏览,帮助你入门。

发布 Windows 应用

大多数 Windows 应用都是通过 Windows 商店销售的。例外的是为企业编写的应用,它们可以像传统的桌面应用一样安装(尽管这仅在面向企业的 Windows 8 版本中可用)。Windows Store 与任何其他应用商店非常相似,用户可以搜索应用,查看成功应用的排名,并获得应用的更新。而且,和其他应用商店一样,Windows Store 通过从你的应用销售中提成来运营。我将在本书第四部分的中解释作为开发者如何使用 Windows Store,但如果你记住通过 Windows Store 发布是应用开发的最终目标,这将会很有帮助。

这本书里有什么?

在本书中,我将向您展示如何使用您的 web 应用技术知识,并应用它们来创建丰富、流畅和动态的 Windows 应用。我首先向您展示这些 web 技术可以用来创建一个简单的应用,使用的方法与您在常规 web 应用部署中遇到的方法相同,然后向您展示可用于利用 Windows 8 和应用环境的不同技术、库和功能。

这本书是给谁的?

您是一名经验丰富的 web 开发人员,已经掌握了 JavaScript、HTML 和 CSS 的基础知识,并且希望为 Windows 8 开发应用。您希望基于您的 web 体验创建超越浏览器的应用,并以常规 web 应用无法提供的方式利用 Windows 平台的功能。

在我阅读这本书之前,我需要知道些什么?

您需要知道如何使用 HTML、CSS 和 JavaScript 编写一个简单的 web 应用。您需要理解 HTML 元素和属性、CSS 样式以及函数和事件等 JavaScript 概念。你不需要成为这些技术的专家,但是你需要一些经验。在这本书里,我没有提供关于 web 开发的介绍,如果你是 web 技术领域的新手,你将很难理解这些例子。

Windows 应用开发使用 HTML5 和 CSS3,但如果您了解 HTML5 规范的最新草案,这并不重要。HTML5 和 CSS3 中的新特性在很大程度上是进化的,对 HTML4 的良好理解将为您提供足够的基础,让您了解自己不知道的内容。

images 提示我在本书中最常使用的 HTML5 相关特性其实是新的 CSS3 布局特性,它可以轻松创建流畅的界面。你可以使用新元素和 API,但在大多数情况下你不需要这样做,一些关键特性通过特定于应用的 API 更方便地暴露出来。

如果我没有这样的经历呢?

你可能仍然会从这本书里得到一些好处,但是你会发现这很难,你将不得不自己找出许多应用开发所需的基本技术。我写了几本其他的书,你可能会觉得有用。如果你是 HTML 新手,请阅读HTML 5权威指南。这解释了创建常规 web 内容和基本 web 应用所需的一切。我解释了如何使用 HTML 标记和 CSS3(包括新的 HTML5 元素),以及如何使用 DOM API 和 HTML5 APIs(如果您不熟悉这门语言,还包括一个 JavaScript 初级读本)。如果你想了解更多关于实用 web 应用开发的知识,请阅读 Pro jQuery 。jQuery 是一个非常流行的 JavaScript 库,它简化了 web 应用的开发。我在本书中不使用 jQuery,但是通过学习如何有效地使用 jQuery,您将提高您对 web 开发各个方面的理解(并且由于您可以使用 jQuery 进行 Windows 应用开发,您花费的时间将对您以后有好处)。对于更高级的主题,请阅读 Pro JavaScript for Web Apps ,其中我描述了我在自己的 Web 开发项目中使用的开发技巧和技术。这三本书都是由出版社出版的。

我不需要知道什么?

你不需要有任何 Windows 桌面开发或其他微软技术(如 C#、XAML 或。NET 框架)。使用 web 技术开发应用建立在您已经用于 web 应用开发的基础上,虽然有很多东西需要学习,但您不必担心其他编程语言或标记。

但是我不需要知道 C#的高级特性吗?

不,说实话。微软在让 JavaScript 与 C#和其他语言平起平坐方面做得很好。NET 语言,使 HTML 成为 XAML 的一个很好的替代品(这是大多数语言中定义用户界面的方式。NET 应用)。当您深入应用开发时,您会意识到您正在使用与。网络语言。这一点很明显,只是因为一些对象和属性名有点奇怪——在所有其他方面,您甚至不知道是否支持其他语言。

我用 HTML/JavaScript 和 XAML/C#编写 Windows 应用已经有一段时间了,但我还没有发现任何可用的特性。这是 web 技术程序员无法实现的。HTML 和 JavaScript 是应用开发领域的一流技术。

我需要什么工具和技术?

应用开发需要两样东西:一台运行 Windows 8 和 Visual Studio 2012 的 PC。如果你认真对待应用开发,你需要购买一份 Windows 8,但如果你只是好奇,你可以从微软获得 90 天的试用期——我将在稍后的第二章中解释如何进行。

Visual Studio 2012 是微软的开发环境。好消息是微软推出了 Visual Studio 的基础版本,可以免费获得,这是我在本书中一直使用的版本。它有一个吸引人的名字:Visual Studio 2012 Express for Windows 8,我将在本章的后面告诉你如何获得它。

Visual Studio 的付费版本是可用的,您可以将任何不同的 Visual Studio 版本与本书一起使用。微软倾向于对企业集成、版本控制和测试管理等功能收费,虽然它们都是有用的功能,但没有一个是应用开发所必需的,我不以任何方式依赖它们。

这本书的结构是怎样的?

在本章中,我将向您介绍 Visual Studio,并向您展示如何创建一个简单的项目。我将简要介绍 Visual Studio 界面的关键部分,解释 Windows 应用开发项目中每个文件的外观,并向您展示如何使用 Visual Studio 附带的应用模拟器工具运行和测试应用。

在第三章中,我将向你展示如何构建你的第一个应用。我主要使用基本的 HTML、CSS 和 JavaScript 特性来演示您现有的 web 应用开发知识有多少可以直接应用到 Windows 应用开发中。你会对你能做的事情感到惊喜。当然,你没有购买专业水平的基础书籍,这本书的其余大部分向你展示了不同的技术和功能,将一个基本的应用转变为一个提供一流应用体验的应用。在接下来的章节中,我将简要描述您将在本书的其他部分学到的内容。

第二部分:核心开发

有一些核心功能,几乎所有的应用都可以从中受益。在本书的这一部分,我解释了这些基础技术,向您展示了如何让用户浏览您的应用的内容,如何使您的应用布局适应运行它的设备的功能和配置,以及如何最好地利用广泛的异步编程支持,这些支持贯穿几乎所有的 Windows 应用开发库。当你完成这本书的这一部分时,你将知道如何创建一个动态的、自适应的、响应迅速的应用。

第三部分:UI 开发

您可以使用标准的 HTML 元素,如buttoninput,为应用创建 UI,称为布局,但您也可以访问 WinJS UI 库,其中包含的界面控件为 Windows 应用提供了独特的外观和感觉。在本书的这一部分,我将带您浏览这些控件,解释何时应该使用它们,以及如何将它们应用于常规的 HTML 元素,并给出许多许多示例,以便您可以看到它们的运行。当你完成这本书的这一部分时,你将知道如何应用独特的 Windows 外观和感觉来创建漂亮的应用。

第四部分:平台整合

一旦应用结构和布局就绪,就可以开始将应用集成到 Windows 提供的功能和服务中。这包括使你的应用成为文件和数据搜索过程的一部分,使用文件系统,告诉 Windows 你的应用支持不同类型的文件和协议,打印以及在应用之间共享数据。我将在本书的这一部分涵盖所有这些主题,并向您展示如何为您的应用创建不同类型的通知,包括低调的 live tiles 和更具侵入性的 toast 通知。当你完成这本书的这一部分时,你将知道如何使你的应用成为一个一流的 Windows 公民,完全集成到更广泛的平台和你的用户工作流中。

第五部分:销售应用

在本书的最后一部分,我将向您展示如何准备一个应用,以及如何在 Windows 应用商店中发布它。在本书的这一部分结束时,你将会看到一个 Windows 应用的完整生命周期,从最初的基本实现到高级功能,最后是它的发布。

这本书里有很多例子吗?

这本书里有个例子,我展示了创建一流应用所需的每个关键特性。在某些情况下,我回过头来组合不同的特性,向您展示它们是如何协同工作的,这些组合的好处,以及有时可能出现的问题。这本书里的例子太多了,以至于我很难把所有的代码都放进章节里。为了方便起见,我用两种方式列出了 JavaScript 代码和 HTML 标记。第一次引入新文件时,我会向您展示完整的内容。你可以在清单 1-1 中看到一个这样的例子,这是摘自第六章的代码。

清单 1-1 。新文件的完整列表

`(function () {     "use strict";

    WinJS.Namespace.define("ViewModel.State", {         appBarElement: null,         navBarContainerElement: null,         navBarControlElement: null     });

    WinJS.Namespace.define("ViewModel.UserData", {

    }); })();`

当我修改一个文件时,我倾向于只显示被修改的部分,类似于清单 1-2 。粗体代码显示了我所做的与我正在演示的技术或特性相关的更改。

清单 1-2 。修改文件的部分列表

... WinJS.Namespace.define("ViewModel.UserData", { **    word: null,** **    wordLength: {** **        get: function() {** **            return this.word ? this.word.length : null;** **        }**     } }); ...

如果我自己也想效仿呢?

你可以从Apress.com下载本书中每个例子的完整代码。代码是按章节组织的,每个项目显示每个应用的完成状态,所以你可以看到完成的结果是什么样子,如果你愿意,可以跟着做。您可以在自己的项目中使用代码,或者使用示例作为模板来创建新的应用。

图像归属

在本书的例子中,我使用了大量的图像文件。感谢以下人员善意地允许使用他们的照片:霍里亚·瓦兰、大卫·肖特、 Geishaboy500 、田中久约、梅尔维·埃斯凯利宁、花式速度女王、艾伦“克雷吉 3000 、克雷吉、诺森古德梅拉路易丝

二、入门指南

在开始开发应用之前,您需要启动并运行 Windows 8 和开发工具。

在这一章中,我将带您完成正确软件的安装过程,并带您完成为 Windows 8 应用创建 Visual Studio 项目的过程,带您浏览它包含的文件和 Visual Studio 提供的工具。

准备就绪

你不需要一台特别高端的电脑来进行应用开发——任何满足最低 Windows 8 规格的电脑都可以。在开发中,更快的机器让开发变得更愉快,但是你不需要任何强大的东西。

images 提示如果你正在购买一台用于 Windows 8 应用开发的电脑,我建议你把大部分的钱花在获得你能找到的最大的屏幕上。对于编写应用来说,没有什么屏幕是太大的——对我来说,最棒的是能够并排编辑 JavaScript 文件、HTML 文件和 CSS 文件,并且仍然有足够的空间来容纳 Visual Studio 界面的其余部分(我将很快向您展示)。你的下一个重点应该是记忆。最不重要的组件是 CPU:今天的 CPU 都非常强大,即使是更便宜的型号也能够应付 Visual Studio 的应用开发。

如果你想在承诺之前看看应用开发是什么样的,你可以获得 Windows 8 的 90 天免费试用。你可以在这里获得 Windows 8 评估版:[msdn.microsoft.com/en-us/evalcenter](http://msdn.microsoft.com/en-us/evalcenter)。点击Windows and Platform Development部分的Release链接,你会找到你需要的东西。

您需要创建一个 Microsoft 帐户来获得评估,但无论如何您都需要一个帐户来设置为开发人员。Microsoft 帐户可以免费创建,如果您还没有帐户,可以按照说明进行操作。

images 提示如果你要从早期版本的 Windows 系统升级,微软会提供相当不错的折扣。如果你是学生或者你在一家和微软有协议的公司工作,你也可以得到一份便宜的拷贝。很少有人需要支付 Windows 的全零售价,问问周围的人看看你是否在微软无数计划中的一个之内总是值得的。

准备好 Visual Studio

您还需要一份 Visual Studio 2012。正如我在前一章提到的,微软使 Visual Studio 的一个版本完全免费。它没有一些付费版本包含的所有测试和集成功能,但你不需要这些来创建应用。在所有其他方面,Visual Studio 的免费版本功能齐全,并能满足您的所有需求。如果您有不同版本的 Visual Studio 2012,请不要担心。您不需要您的版本中的所有功能,但是本书中的所有说明和示例都可以工作,没有任何问题或修改。

您可以从[www.microsoft.com/visualstudio/11/en-us/products/express](http://www.microsoft.com/visualstudio/11/en-us/products/express)下载 Visual Studio 安装程序。有几种不同的风格可供选择,每一种都可以用来开发特定类型的应用。对于应用开发,您需要 Visual Studio Express 2012 for Windows 8。这些口味的名字非常相似,所以一定要下载正确的。

像安装任何其他应用一样安装 Visual Studio。虽然该软件可以免费使用,但您需要激活它,这需要您之前创建的 Microsoft 帐户。完成这个过程后,您将得到一个 Visual Studio 的激活码。你还需要一个开发者许可,这也是免费的。当您第一次启动 Visual Studio 2012 时,系统会提示您获取许可证,这只需要一秒钟的时间,并且同样需要您之前创建的 Microsoft 帐户。

可选设备

一台 Windows 8 PC 和 Visual Studio 是你编写应用所需要的全部,但是如果你有一台带有方位传感器的设备,一些应用功能就更容易理解了。正如您将在本书的第二部分中了解到的,对用户定位设备的方式做出响应是 Windows 8 的一项重要功能,虽然您可以使用 Visual Studio 模拟这种效果,但没有什么可以替代真实的设备。同样,一个带触摸屏的设备将让你开发和测试触摸屏交互——另一个关键的应用功能。这是你可以模拟的其他东西,但是模拟不能代替真实的东西。

images 提示我用的是戴尔 Latitude Duo,它是平板电脑和笔记本电脑的奇怪混合体。你可以买到便宜的二手货,它们有一个像样的触摸屏和一个方向传感器(尽管这种驱动程序必须从传感器制造商那里找到,而不是从戴尔那里)。我不建议在 Duo 上编码,这对于舒适来说有点太慢了,而且缺少 RAM,但它是一个相当不错的测试机器,我不会没有它。

开始使用

一旦你安装了 Windows 8 和 Visual Studio,你就可以开始了。在本节中,我将向您展示如何为一个应用创建一个 Visual Studio 项目,并简要介绍您将使用的工具和功能。如果您使用过另一种集成开发工具,您将会认识到 Visual Studio 的许多关键特性。

创建 Visual Studio 项目

要创建一个新的应用项目,您可以单击 Visual Studio 起始页上的New Project链接(当您第一次启动 Visual Studio 2012 时显示),或者从File菜单中选择New Project

images 注意实际上是FILE菜单,因为微软已经决定在 Visual Studio 中以大写字母显示菜单,尽管这给人的感觉是你的开发工具在对你大喊大叫。我将只是参考正常情况下的菜单。

你会看到New Project对话框,如图图 2-1 所示。Visual Studio 包括一些模板,可以帮助您开始不同种类的项目,这些模板显示在对话框的左侧。可用的模板集因您使用的 Visual Studio 版本而异。该图显示了可用于 Visual Studio Express 2012 for Windows 的模板,该模板支持四种用于创建应用的编程语言。对于每种语言,都有一些预先填充的模板,用于创建不同的项目。

images

***图 2-1。*Visual Studio 新建项目对话框窗口

我明白为什么微软包括这些模板,但它们是相当无用的。对于新程序员来说,面对一个空项目和一个闪烁的光标可能有点令人担忧,但是他们放入这些模板的代码并不是很好,并且除了最简单和最琐碎的项目之外,很少是你想要的那种东西。

images 提示 Visual Studio 支持彩色主题。大多数 Visual Studio 版本的默认主题是深色主题,主要是黑色,在屏幕截图中显示不出来。我已经切换到灯光主题,这就是为什么图中的New Project对话框看起来和你在屏幕上看到的不一样。我通过从Tools菜单中选择Options并改变Environment部分的Color Theme设置来改变主题。

导航到Templates > JavaScript部分的Blank App模板。Blank Template创建一个几乎为空的项目,其中只有运行应用所需的文件,而不会产生任何错误。这是我将在整本书中使用的模板,当我说我创建了一个新项目时,这将永远是我使用过的模板。

在名称字段中输入NoteFlash。这是你将在第三章中创建的第一个应用的名称。在这一点上,我不打算做任何严肃的开发,但在本章结束时,你会明白 Windows 8 应用项目中的移动部分是什么,然后你会看到它们如何在第三章中使用。

接下来,单击Browse按钮为您的项目选择一个位置。你把文件保存在哪里并不重要,只要你以后能再次找到它们。最后,单击OK按钮创建项目。Visual Studio 在生成文件和配置内容时会磨蹭一会儿,然后你会看到项目的初始视图,如图图 2-2 所示。

images

***图 2-2。*新项目的初始 Visual Studio 视图

在接下来的几节中,我将向您展示将在应用开发中使用的最重要的 Visual Studio 特性。其中一些可能有点显而易见,但请继续关注我,因为 Visual Studio 提供了一个紧密集成的开发环境,了解各个部分如何协同工作是很有用的。

运行项目

开始一个新项目的最好地方是运行它,看看它看起来怎么样。在开发过程中运行应用时,您有三种选择。第一种是在你用来写代码的同一台 PC 上运行应用,即本地机器。第二种方法是在 Visual Studio 附带的模拟器中运行应用。最后一个选项是在另一台设备上运行该应用。

在这三个选项中,模拟器是最有用的。在您正在使用的设备上运行应用的问题是,Windows 8 应用是全屏的,它们覆盖了 Visual Studio 和桌面的其余部分。你可以在应用和桌面之间切换,但这是一个笨拙的过程,尤其是当你试图调试某种问题或错误时。

在另一台设备上运行该应用可能非常有用。你需要在设备上下载并安装 Visual Studio 2012 的远程工具,你可以在[msdn.microsoft.com/en-gb/windows/apps/br229516](http://msdn.microsoft.com/en-gb/windows/apps/br229516)从微软获得。当我想测试某个功能或问题与我的开发机器所不具备的硬件功能(例如触摸屏)之间的关系时,我发现远程运行应用的能力非常有用。我不在常规开发中使用它,因为 Visual Studio 需要几秒钟来打包应用并将其传输到远程设备,过一会儿这就变得令人厌倦了。

images 提示你不能像使用传统的 Windows 桌面应用那样,将一个应用复制到另一个设备上。微软非常热衷于使用 Windows Store 来部署应用,在你准备好发布之前,最简单的设备测试方法是使用远程工具。

这就剩下第三个选项:Visual Studio 模拟器。这是我在开发过程中测试应用的方式,我建议你也这样做。该模拟器是真实 Windows 8 设备的忠实再现,您可以使用它来模拟一些重要的设备特征,包括设备方向、GPS 位置、触摸和手势输入以及不同的屏幕大小和分辨率。然而,它并不完美,而且有些应用功能你无法在模拟器中正确测试。我会在适当的章节中指出这些特性,这样你就知道如何使用其他方式来运行应用。

您可以使用 Visual Studio 菜单栏选择您希望的应用运行方式。默认情况下,Visual Studio 会在本地机器上运行一个新的项目,但是你可以通过点击图 2-3 中按钮旁边的箭头来改变。该按钮显示的文本反映了您所做的选择,因此它可能与图形不完全匹配,但您不会错过该按钮,因为它旁边有一个大的绿色箭头。(您需要单击以更改设置的箭头是按钮文本右侧的小向下箭头,而不是绿色箭头)。

images

***图 2-3。*选择 Visual Studio 运行应用的方式

从下拉菜单中选择Simulator选项,如图所示。做出选择后,单击绿色箭头或按钮文本启动应用。Visual Studio 将启动模拟器,安装您的应用,并开始运行它。你用来创建项目的Blank App Visual Studio 模板确实只生成了一个应用的基本结构,所以目前没有太多东西可看——只有一个黑色的屏幕,左上角有一小行文字写着Content goes here。暂时忽略这个应用,看看模拟器窗口右边的按钮,我已经在图 2-4 中显示了。你也可以通过从 Visual Studio Debug菜单中选择Start Debugging来启动应用——效果是一样的。我将在本章的后面回到 Visual Studio 调试器。

images 注意当我告诉你启动应用时,我的意思是你应该选择Start Debugging菜单项或点击工具栏上的按钮,以便使用调试器。有几个特性需要在没有调试器的情况下进行测试,但是我会在您使用它们的时候说明这一点。在所有其他情况下,您应该确保调试器正在运行。

images

***图 2-4。*Visual Studio 模拟器

我在模拟器旁边放大显示了按钮。这些按钮允许您使用模拟器以不同的方式与应用交互,并更改模拟设备的方向。

模拟器按钮按相关功能分组。第一组改变输入法,允许你用鼠标模拟触摸输入。当我向你展示如何处理触摸手势时,你会在第十七章中看到这些按钮是如何工作的。

第二组允许您模拟顺时针和逆时针旋转设备。我在第六章中使用这些按钮向你展示如何创建在设备方向改变时适应的布局。

第三组中唯一的按钮改变屏幕分辨率和像素密度,我在第六章中也探讨了这一点。第四组中也只有一个按钮,用于模拟 GPS 数据。在《??》第二十九章中,当我向你展示如何创建具有位置感知的应用时,你会看到这是如何使用的。最后一个按钮组可以让你截屏模拟器显示的任何内容。我在这本书里不用这些按钮。

模拟器使用提示

模拟器的工作方式是在您的开发机器上创建第二个桌面会话,并将其显示在一个窗口中。这是一个巧妙的技巧,但是它有一些值得了解的副作用。如果你有任何问题,有些是有用的,有些是值得注意的。

首先,当您使用 Visual Studio 启动一个应用时,应用包会在本地计算机上安装和执行,并显示在模拟器中。这意味着,如果您愿意,您可以导航到模拟器的开始屏幕或本地计算机上的真正开始屏幕,并在没有 Visual Studio 的情况下启动应用。如果你在运行应用时遇到问题——这种情况时有发生——你通常可以通过进入开始屏幕,找到你正在开发的应用,然后卸载它(右击应用的磁贴并选择Uninstall)来解决问题。

第二,一些提供与 Windows 功能集成的应用功能最容易从桌面测试,例如文件激活,我在第二十四章中描述了它。因为 Visual Studio 模拟器正在运行常规的 Windows 会话,所以当您第一次在模拟器中激活桌面时,您设置为自动运行的所有应用都会启动。我发现这导致了一些问题,特别是对于那些希望独占存储位置或管理硬件的应用。我最讨厌的例子是让我重新映射鼠标按钮的软件——当我在模拟器中切换到桌面时,该软件的第二个实例会自动启动,并使我的鼠标无法使用,直到我杀死其中一个进程(由于缺少可用的鼠标,这项工作变得更加复杂)。这些通常不是终端问题,但是如果您在使用模拟器时开始看到奇怪的问题,请记住它运行一个完整的 Windows 会话,包括所有好的和坏的方面。

最后,模拟器创建和显示 Windows 会话的方式意味着,如果在模拟器启动时连接到 VPN,它将不起作用。您可以在模拟器启动后激活 VPN ,一切都会好的。

控制应用执行

启动应用后,Visual Studio 界面会发生变化,为您提供控制其执行的选项。工具栏上出现一排按钮,如图图 2-5 所示。

images

***图 2-5。*在 Visual Studio 中控制应用执行的按钮

这些按钮可以暂停、停止和重启应用。如果您对项目进行了任何更改,重启按钮将确保在开始执行之前更新应用。第四个按钮(其图标是一个带有两个形成圆圈的箭头的闪电)是重新加载按钮。此按钮可快速重新加载任何已更改的文件,而无需完全停止和重新启动应用。这个特性在开发过程中很有用,例如,当我微调 CSS 布局时,我发现它最有用。如果您在项目中添加或移除文件,或者进行需要重新安装应用的更改(例如更改清单,我将很快介绍),则无法重新加载—在这些情况下,Visual Studio 将提示您执行重新加载。

images 提示按钮由 Visual Studio Debug菜单上执行相同功能的项目补充。我倾向于使用工具栏按钮,但它们之间没有区别。

探索项目

默认情况下,Solution Explorer位于 Visual Studio 窗口的右上角,尽管您可以移动它,甚至完全分离它。按照 Visual Studio 的说法,一个解决方案是一个或多个项目的包装。本书中的所有示例应用都包含在单个项目中,并且在很大程度上,解决方案是 Windows 8 应用之前的开发实践的延续。

Solution Explorer提供了对项目中所有文件的访问。Solution Explorer显示的大部分条目是 Visual Studio 在您创建项目时生成的文件夹和文件,其他条目是对您的应用所依赖的文件的引用。你可以在图 2-6 中看到所有文件的更多细节,我将在接下来的章节中解释每个文件的作用和它们的初始内容。

images

***图 2-6。*解决方案浏览器,显示 Visual Studio 为空白应用模板生成的文件

要编辑项目中的文件,只需在解决方案资源管理器窗口中双击其条目。您还可以使用图标行正下方的搜索栏在解决方案资源管理器中按名称搜索文件,这在大型复杂的项目中非常有用。(图标本身允许您导航和配置解决方案资源管理器)。

images 提示Solution Explorer窗口下面的Properties窗口。这个窗口在其他种类的开发项目中很重要,但是它对于 JavaScript 应用开发没有任何价值,您可以安全地关闭它,为Solution Explorer窗口腾出更多空间,这对大型复杂的项目很有用。您可以通过View菜单重新打开任何已关闭的窗口(通过Other Windows菜单项可以打开一些不常用的窗口)。

探索项目参考

Solution Explorer中显示的第一个条目是项目参考。共有六个参考文件,在图 2-7 中的Solution Explorer中可以看到完全展开的References项。

images

***图 2-7。*解决方案浏览器中显示的项目引用

引用由 JavaScript 和 CSS 文件组成。这是很重要的一点,因为它展示了 Windows 8 应用对 web 技术的支持是如何深入人心的——以至于用 JavaScript 和 HTML 编写的应用可以使用 Internet Explorer 10 来执行。用户并不知道浏览器被用来运行应用,但它是执行你的应用的引擎。

理解 CSS 引用

References部分中的 CSS 文件包含 JavaScript 应用使用的默认样式。有两个主题可以应用到您的应用中。ui-dark.css文件包含一个在深色背景上有浅色文本的应用的样式。ui-light.css文件包含一个应用的样式,该应用在浅色背景上有深色文本。大多数 Visual Studio 版本默认使用深色主题,并且在default.html文件中选择该主题,稍后我将介绍该主题。

大多数应用以其中一个主题开始,然后出于自定义或品牌化目的覆盖选定的样式,但您不必使用任何一个文件,您可以自己创建应用中使用的所有样式。然而,这样做需要大量的工作,并且通过使用一个预定义的主题作为起点,您可以受益于与其他 Windows 8 应用和默认应用外观的一致性。

了解 JavaScript 参考

base.jsui.js文件包含创建应用的部分 API。它们组成了 WinJS API ,其中包含了特定于 JavaScript 应用开发的对象和函数。还有 Windows API ,它包含所有可用于应用开发的编程语言之间共享的对象和函数。

WinJS API 包含许多有用的功能,我在本书的第二部分和第三部分中解释和演示了这些功能。由于这些都是 JavaScript 文件,你可以打开它们,查看它们的内容,设置调试器断点,并大致了解一下微软是如何实现不同特性的。(Windows API 不是用 JavaScript 编写的,也没有源代码形式。)

粗略地说,ui.js文件包含了 WinJS UI 控件的代码,这是本书第三部分的主题,您可以使用它从标准 HTML 元素创建复杂的用户界面控件,就像您使用 jQuery UI 这样的库一样(尽管 UI 控件是专门针对 JavaScript 应用开发的)。base.js文件包含用于创建应用基本结构的对象。我将在本书的第二部分中向您介绍这些对象,因为我将涉及导航和数据绑定等主题。

另外两个文件base.strings.jsui.strings.js,包含显示给用户的文本,针对不同的语言和地区进行了本地化。您不需要直接处理这些文件,因为它们的内容是通过base.jsui.js文件自动消费的。

探索默认文件

当您使用Blank App模板创建 Visual Studio 项目时,会生成三个默认文件,它们构成了应用的基本结构和初始内容。这些文件是default.html(在项目的根文件夹中)default.css(在css文件夹中)default.js(在js文件夹中)。这些文件也描述了一个 JavaScript Windows 8 应用项目的基本组织——根文件夹中的 HTML 文件、css文件夹中的 CSS 文件和js文件夹中的 JavaScript 文件。这种结构是一种建议,并不是强制的,你可以自由地以你喜欢的任何方式组织你的文件——你将在本书中看到我这样做,以使示例应用更容易理解。在接下来的几节中,我将向您展示每个文件的初始内容,并解释关键特征,指出我将在本书后面详细讨论的章节。在本章中我不打算修改这些文件,但是在第三章中,你将使用它们来构建你的第一个真正的应用。

images 注意你可以重命名或替换这些文件,但我在本书中使用它们作为示例应用,这样你就知道哪些文件是由 Visual Studio 创建的,哪些文件是我添加的。

了解默认 HTML 文件

default.html文件在应用启动时加载,负责导入default.cssdefault.js文件,这是使用标准的 HTML linkscript元素完成的。这些元素也用于导入References文件,为您的应用提供对 WinJS API 和浅色或深色主题 CSS 样式的访问。您可以在清单 2-1 中看到这些元素,它显示了 Visual Studio 生成的default.html文件的内容。

清单 2-1 。由 Visual Studio 创建的 default.html 文件的初始内容

`

         NoteFlash

**    ** **    ** **    ** **         ** **    ** **    **

    

Content goes here

`

我已经突出显示了导入项目和引用文件的元素。当您构建一个应用时,您会想要创建新的 JavaScript 和 CSS 文件,并使用linkscript元素将它们纳入范围,就像在 web 应用中一样。这些文件是按照它们被指定的顺序加载的,这意味着你不能在一个不同的 JavaScript 文件中调用一个函数,除非那个文件的script元素已经被处理,并且在一个 CSS 文件中定义的样式可以被随后导入的文件中的样式覆盖。如果你想改变你的应用的主题,你可以改变文档中的第一个link元素,这样它就会导入ui-light.css文件。

images 提示对于大多数应用来说,default.html文件通常是一个占位符,其他内容将被导入其中,这意味着该文件往往会保持非常简短。我将在第五章中解释内容是如何导入的,以及这种方法如何适应应用开发。

了解默认 CSS 文件

当第一次创建时,default.css文件包含一些占位符,你可以在清单 2-2 中看到。这个文件中没有定义实际的样式,但是请记住,ui-dark.cssui-dark.css文件提供了许多默认的样式。甚至当你开始为你的应用定义自定义样式时,你会发现你最终使用的 CSS 比一个类似的 web 应用要少,因为主题文件包含了基本的东西。

清单 2-2 。Visual Studio 创建的 default.css 文件的初始内容

`body { }

@media screen and (-ms-view-state: fullscreen-landscape) { }

@media screen and (-ms-view-state: filled) { }

@media screen and (-ms-view-state: snapped) { }

@media screen and (-ms-view-state: fullscreen-portrait) { }`

在应用中使用@media规则来创建适应屏幕方向和分配给应用的空间量的布局(应用可以全屏显示或分配给屏幕的大部分,称为填充模式,或分配给屏幕的一小部分,称为捕捉模式)。在第六章的中,我解释了所有这些是如何工作的,并提供了不同种类的适应性应用布局的演示。

了解默认的 JavaScript 文件

default.js文件为应用带来了活力,它使用标准的 HTML DOM 和各种特定于应用的 API 来管理布局并与 Windows 集成。Visual Studio 创建的代码,如清单 2-3 所示,并没有做很多事情,正如我在第十九章中解释的那样,需要修改才能真正有用。在第十九章之前,我将在我的示例应用项目中使用该文件的简化版本,届时我将详细解释应用的生命周期,并向您展示如何以一种有用且有意义的方式响应 Windows 发送的系统事件。

清单 2-3 。Visual Studio 创建的 default.js 文件的初始内容

`// For an introduction to the Blank template, see the following documentation: // go.microsoft.com/fwlink/?Lin… (function () {     "use strict";

    WinJS.Binding.optimizeBindingReferences = true;

    var app = WinJS.Application;     var activation = Windows.ApplicationModel.Activation;

    app.onactivated = function (args) {         if (args.detail.kind === activation.ActivationKind.launch) {             if (args.detail.previousExecutionState !==                 activation.ApplicationExecutionState.terminated) {                 // TODO: This application has been newly launched. Initialize                 // your application here.             } else {                 // TODO: This application has been reactivated from suspension.                 // Restore application state here.             }             args.setPromise(WinJS.UI.processAll());         }     };

    app.oncheckpoint = function (args) {         // TODO: This application is about to be suspended. Save any state         // that needs to persist across suspensions here. You might use the         // WinJS.Application.sessionState object, which is automatically         // saved and restored across suspension. If you need to complete an         // asynchronous operation before your application is suspended, call         // args.setPromise().     };

    app.start(); })();`

您现在不必理解这个文件的内容——我将在本书的后面解释所有的对象和方法调用。然而,值得理解的是,应用是使用标准 JavaScript 编写的,因此您熟悉的基本对象和数据类型是可用的,标准 DOM API 也是如此,例如,您可以使用它来定位应用标记中的 HTML 元素并注册事件处理程序。在接下来的章节中,你会看到无数的例子。

理解清单

解决方案资源管理器中最后一个感兴趣的文件是package.appxmanifest,它是应用的清单文件。清单向 Windows 应用商店和 Windows 描述您的应用。清单中的一些信息提供了基本信息,例如用于在“开始”菜单上显示应用的图像。我会在第四章的中给你展示这些设置。

清单中的其他信息更复杂。例如,您使用清单来指示您的应用需要访问的文件系统位置,以及您的应用与哪些 Windows 服务集成。Windows 使用这些信息中的一部分来激活和启动您的应用以响应用户操作,而另一部分则在 Windows 应用商店中呈现给用户,以便他或她可以评估您的应用带来的安全风险。我在整本书中解释了更复杂的设置,但是你可以了解我在第二十二章中给出的例子(在那里我向你展示了如何声明你想要使用的文件系统位置)和在第二十三章 - 26 章中给出的例子,在那里我向你展示了如何实现 Windows 集成契约。

清单是一个 XML 文件,但是您不必直接处理 XML。如果双击package.appxmanifest文件,Visual Studio 将打开一个可视化编辑器,将清单显示为一系列要填写的表单,由一系列选项卡组织。你可以在图 2-8 的中看到其中一个标签。(如果您确实想要查看 XML,您可以右击Solution Explorer中的文件,并从弹出菜单中选择View Code

images

***图 2-8。*Visual Studio 清单编辑器的应用 UI 标签

images 提示Solution Explorer显示有一个文件我还没有提到:NoteFlash_TemporaryKey.pfx文件。此文件包含一个数字证书,用于在开发过程中自动签署应用包。在发布过程中,您用一个真实的证书替换这个文件,我在本书的第五部分中对此进行了描述。

Visual Studio 工具

在这一节中,我将简要介绍 Visual Studio 的不同部分,您将使用它们来开发应用。我不会详细介绍,因为 Visual Studio 就像任何其他 IDE 一样,您以前已经无数次使用过文本编辑器和调试器。本章的这一节向您展示了如何找到主要的 IDE 构造块,并指出了您自己可能还没有发现的有用功能。

Visual Studio 编辑器

双击Solution Explorer窗口中的文件,打开编辑器。编辑器做了所有你能想到的事情。编辑器可以适应文件格式,因此您可以无缝地编辑 HTML、CSS 和 JavaScript 文件,并且它可以自动完成所有这三种类型的文件。它对语法进行颜色编码,它知道你可以在 HTML 文件中嵌入 CSS 和 JavaScript,并做出相应的响应。它是一个非常好的程序员编辑器,它做了所有其他程序员编辑器做的事情。

您可以通过从 Visual Studio Options菜单中选择Tools来配置如何在编辑器中处理每种语言。展开Text Editor部分,您将看到一个适用于所有语言的General项和一组每种语言的项。您可以控制颜色编码、缩进、自动完成和许多其他特性,以根据您的喜好定制编辑器。

一个值得指出的特性是 Visual Studio 编辑器出色的文本搜索功能。键入Control+F会打开一个编辑器内搜索框,您可以使用它来定位当前文件中的项目。每个编辑器窗口都可以使用自己的搜索框进行独立搜索,或者您可以单击向下箭头来扩展搜索范围。我经常使用搜索功能,但当我使用其他 ide 或编辑器时,我很怀念它。

JavaScript 控制台

启动应用时,Visual Studio 界面会发生变化。其中一个变化就是会出现JavaScript Console窗口。(如果它不可见或者如果您不小心关闭了它,您可以从Visual Studio Debug菜单上的Windows项手动打开此窗口,但只能在应用运行调试器时打开。)

JavaScript Console窗口是最有用的 Visual Studio 功能之一,用于跟踪应用中的问题,它扮演着几个不同的角色。如果从 JavaScript 代码中调用console.log方法,您指定的字符串将会显示在JavaScript Console窗口中,正如您所期望的那样。

您还可以执行任意 JavaScript 代码,探索应用定义的变量和函数。作为一个简单的例子,我将修改default.html文件,使p元素具有一个id属性,如清单 2-4 所示。

清单 2-4 。向 p 元素添加 id 属性

`

` `          NoteFlash

                   

              

**    

Content goes here

** `

我突出显示了我修改的元素。启动应用,找到JavaScript Console窗口,在窗口底部的文本框中输入以下内容:


paraElem.style.fontSize = "40pt"


如果你点击回车键,你会看到模拟器窗口中显示的内容被调整为 40 磅,如图 2-9 所示。

images

***图 2-9。*使用 JavaScript 控制台窗口定位和配置 HTML 元素

在这个简单的例子中,有几个要点需要注意。首先,JavaScript 控制台为您提供了对应用实时状态的访问。你可以调用任何函数,读取或修改任何变量,改变任何样式。唯一的限制是 JavaScript 变量和函数需要成为全局名称空间的一部分——我将在第三章的中回到这个话题。

第二点是,Internet Explorer 允许您通过将 HTML 元素的id属性值视为全局变量来定位 HTML 元素。这不是一个标准功能,尽管其他一些浏览器也有类似的功能。我在这本书里经常使用这个特性,这意味着我不必使用冗长的 DOM API(尽管需要明确的是,如果你愿意,你可以在你的应用代码和窗口中使用 DOM API)。paraElem变量返回表示 HTML 文档中p元素的 DOM 对象,我可以使用标准的HTMLElement对象和属性来操作该元素——在本例中,更改style.fontSize属性的值来增加文本的大小。

在应用开发中使用浏览器怪癖

能够通过 HTML 元素的id属性值来定位它们是非常有用的,但是这不是标准的 DOM 特性,所以它被归类为浏览器怪癖。在 web 应用开发中,您将学会避免浏览器怪癖,因为您需要支持各种各样的浏览器,并且您不想将您的应用与特定于浏览器的功能绑定在一起。

在应用开发的世界里,事情是不同的。将用于运行 JavaScript Windows 8 应用的唯一浏览器是 Internet Explorer,安装新浏览器,如 Chrome 或 Firefox,对 Windows 8 应用没有任何影响。

这意味着您可以毫无畏惧地使用 Internet Explorer 的怪癖,尽管您可能会质疑这样做是否是好的做法。几个月来,我一直在这个问题上反反复复。最初,我坚持使用标准的 DOM API,尽管我觉得它冗长又烦人。我通过使用WinJS.Utilities特性让自己的生活变得更轻松,这就像是 jQuery 的简化版。(你可以在应用项目中使用 jQuery,但我不会在本书中使用——我必须承认,我一般不会在我的 Windows 8 应用项目中使用它。即使我是 jQuery 的超级粉丝,也没有太大的必要——如果你对我的 jQuery 粉丝身份有任何疑问,可以看看我的专业 jQuery 书籍,由 Apress 出版。)

不过,最终我发现我忽略了重要的一点:Windows 8 应用开发不是 web 应用开发,尽管它有着共同的根源和技术。我是出于习惯而避免那些方便省时的怪癖,而不是因为它们会带来问题。事实上,一些怪癖有助于避免问题——例如,我发现使用全局变量定位元素比使用 DOM API 更不容易出错。

你可以自由选择最适合你的方法——但我建议你审视自己的决定,以确保你不是不必要的教条主义者,就像我开始 Windows 8 应用开发时一样。

在本书中,我通过元素的全局属性来定位元素——有两个例外。当我想清楚地表明我在一个例子中定位一个 HTML 元素时,我使用 DOM API,当我想用 CSS 选择器选择多个元素时,我使用类似 jQuery 的WinJS.Utilities特性(我在第三章的中介绍了该特性,并在第十八章的中详细描述了该特性),这样我可以在一个步骤中对所有元素执行操作。

DOM Explorer

启动应用时出现的另一个窗口是DOM Explorer。这允许您探索应用的 HTML 结构,查看单个元素的框布局属性,并跟踪 CSS 优先规则如何应用于确定元素的样式。DOM Explorer窗口在 Visual Studio 的编辑器区域中显示为一个选项卡。我发现自己经常不经意地关闭DOM Explorer窗口——如果你也这样做,你可以从 Visual Studio Debug菜单的Windows项中重新打开它。

我觉得最有用的功能是能够通过在应用中点击 HTML 文档中的元素来定位它。这是 web 应用开发工具中非常标准的东西,能够在应用开发中使用也很好。在DOM Explorer窗口的顶部是Select Element按钮,我在图 2-10 中高亮显示了这个按钮。

images

***图 2-10。*使用 DOM 浏览器窗口的选择元素功能

点击此按钮并切换到模拟器窗口。当您移动到元素上时,您会看到它们被突出显示,并且它们的详细信息显示在DOM Explorer窗口中。单击您感兴趣的元素,使用 DOM Explorer 查看它的所有特征、布局细节和 CSS 样式。

调试器

Visual Studio 包含我最喜欢的调试器。我发现它比我用过的任何其他调试器都更快、更可靠,我喜欢它如此完美地集成到 Visual Studio 的其余部分的方式。我不会向您提供详细的调试教程,因为您已经知道如何在 web 应用项目中使用调试器,但是在接下来的部分中,我将向您展示 Visual Studio 提供的有用的附加功能。

在您自己的代码中设置断点

如果你想在你可以修改的代码中设置一个断点(相对于微软的 JavaScript 文件),最简单的方法是使用 JavaScript debugger关键字,如清单 2-5 所示,我已经将这种断点添加到了default.js文件中。

清单 2-5 。使用 debugger 关键字创建断点

`// For an introduction to the Blank template, see the following documentation: // go.microsoft.com/fwlink/?Lin… (function () {     "use strict";

    WinJS.Binding.optimizeBindingReferences = true;

    var app = WinJS.Application;     var activation = Windows.ApplicationModel.Activation;

    app.onactivated = function (args) {         if (args.detail.kind === activation.ActivationKind.launch) {             if (args.detail.previousExecutionState !==                 activation.ApplicationExecutionState.terminated) {                 // TODO: This application has been newly launched. Initialize                 // your application here.             } else {                 // TODO: This application has been reactivated from suspension.                 // Restore application state here.             }             args.setPromise(WinJS.UI.processAll()); **            for (var i = 0; i < 5; i++) {** **                console.log("Counter: " + i);** **                if (i > 2) {** **                    debugger** **                }** **            }**         }     };

    app.oncheckpoint = function (args) {         // TODO: This application is about to be suspended. Save any state         // that needs to persist across suspensions here. You might use the         // WinJS.Application.sessionState object, which is automatically         // saved and restored across suspension. If you need to complete an         // asynchronous operation before your application is suspended, call         // args.setPromise().     };

    app.start(); })();`

我在文件中添加了一个简单的for循环,以便稍后我可以演示另一个特性。当计数器的值大于 2 时,断点被触发,并且应用执行的控制被转移到调试器。启动 app,你会看到 Visual Studio 在文本编辑器中高亮显示执行点,如图图 2-11 所示。

images

**图 2-11。**激活用调试器关键字创建的断点

您使用标准的Step IntoStep OverStep Out命令来控制调试器的执行。Visual Studio 将这些作为工具栏上的按钮和Debug菜单中的项目提供。您可以通过键入F5或点击工具栏上的Continue按钮来恢复应用的正常执行。

在其他代码中设置断点

使用debugger关键字要求您能够修改文件,这与显示在Solution Explorer窗口的References部分的 JavaScript 文件不同。对于这种文件,您必须使用 Visual Studio 对断点的支持。通过右键单击希望调试器中断的语句,并从弹出的Breakpoint菜单中选择Insert Breakpoint,可以创建一个断点。断点在文件的空白处显示为红色圆圈。当您想要移除断点时,右键单击该语句并从断点菜单中选择Delete Breakpoint

Visual Studio 断点非常复杂,您可以创建仅在特定情况下触发的条件断点。尽管这个特性很好,但当我试图弄清楚微软是如何在base.jsui.js文件中实现一个特性时,我发现自己尽可能地使用debugger关键字并依赖 Visual Studio 断点。

监控变量

我将for循环放入清单 2-5 的代码中的原因是为了演示 Visual Studio 让您看到变量如何变化的方式——当您使用debugger关键字或 Visual Studio 断点时,这一功能的工作方式完全相同。

启动应用,当调试器取得控制权时,将鼠标悬停在源代码中的变量i上。您将看到显示的当前值。值的右边是一个小图钉图标——单击这个, Visual Studio 将创建一个显示当前值的小窗口。键入F5继续执行,等待下一次循环迭代再次触发调试器。你会看到值窗口被更新,显示变量的当前值,如图 2-12 所示。

images

***图 2-12。*使用 Visual Studio 监控变量值

如果单击窗口中的值,将出现一个文本框,允许您更改分配给变量的值。当您键入F5继续执行时,将使用新值。

使用 JavaScript 控制台

JavaScript Console窗口在调试时也非常有用。当应用的执行暂停时,JavaScript Console的范围是断点的上下文。这意味着您可以引用局部定义的变量和调用函数,而不仅仅是全局定义的。例如,启动应用,等待调试器接管控制权,在JavaScript Console窗口底部的文本框中键入字母i,然后按下Enter。您将看到显示的当前值。如果您使用控制台为变量赋值(例如,输入i = 5并按下Enter),您的值将在应用恢复执行时使用。

JavaScript Console也可以用来探索复杂的物体。例如,在文本框中键入args,然后按下Enter。单击响应左边的箭头,您将看到由args对象定义的属性和函数,该对象被传递给我放置了debugger关键字的函数。暂时不要担心这个对象——我将在第十九部分详细解释它的重要性。你可以在图 2-13 中看到JavaScript Console显示对象的方式。

images

***图 2-13。*用 JavaScript 控制台窗口探索一个对象

我经常使用这个功能。它不允许您通过单击值来修改值,但是您可以输入 JavaScript 语句,为对象或其属性分配新值,并且还可以执行其功能。

总结

在本章中,我创建了一个初始的 Visual Studio 项目,并使用它向您展示了正在开发的应用的结构,突出显示了对 Microsoft 库和默认 HTML、JavaScript 和 CSS 文件的引用。我还简要介绍了您将在应用开发中使用的 Visual Studio 关键特性。Visual Studio 是一个庞大而复杂的工具,但我向您展示的部分将让您在开始 Windows 8 应用开发时处于良好的地位。在下一章中,我将在我创建的项目的基础上,展示利用您现有的 web 应用开发技能和知识,您可以在应用开发中走多远。

三、您的第一款 Windows 8 应用

在这一章,我将开始使用 JavaScript 和 HTML 构建一个真正的应用。这不是一个特别复杂的应用,但它演示了许多你需要理解的基本技术,并为后面的章节奠定了基础。

示例应用名为NoteFlash,,是我为自己使用而构建的。我最近开始学习弹钢琴,这个过程的一部分是学习读谱。我一直很难根据音符的位置来识别它们,所以我创建了一个小应用,就像一副闪存卡一样工作。这种方法效果很好——我一天要看几次音符,我开始越来越擅长看谱了。(如果有一种基于软件的技术可以用来提高我的实际演奏水平,我就会一路领先到卡内基音乐厅。)

除了向你介绍 Windows 8 的世界,NoteFlash应用还将展示你现有的 HTML 和 JavaScript 知识在 Windows 8 中有多少用处。当然,我会教你专门针对 Windows 8 应用的技能和技术,但是当你经历创建NoteFlash应用的过程时,你会看到基本的 Windows 应用与基本的 web 应用有多少共同之处。

在这一章中,我将构建应用的基本功能,然后我将在第四章中添加一些润色和介绍一些附加功能。在这个过程中,我还将使用一些特定于应用的特性,我将在本书的后面深入讨论这些特性。

了解应用结构

如果你知道最终产品是什么样的,那么创建NoteFlash应用的过程会更有意义。该应用有两个页面。第一个允许用户选择她想要测试的一组音符,如图 3-1 所示。选项将在左侧音符、右侧音符或两者上进行测试。

images

***图 3-1。*笔记选择页面

用户点击图中所示的三个按钮中的一个,显示第二页,如图图 3-2 所示。每个音符都显示在五线谱上,用户按下按钮来识别音符。在图 3-2 中显示的音符是一个C,例如,用户可以按下C按钮。

images

***图 3-2。*闪存卡页面

当用户看到所有的注释时,页面布局会改变。笔记名称按钮被几个导航按钮取代,如图图 3-3 所示。Again按钮测试用户对同一音符的选择,Back按钮返回选择页面。

images

***图 3-3。*向用户呈现导航控件

正如我所说,这是一个简单的应用。在接下来的小节中,我将带您完成我用来创建它的过程。同时,我将介绍一些关键的 Windows 8 概念,如导航、异常处理和数据绑定。我会在第四章给你演示如何完成 app。

重温示例应用项目

我将使用的 Visual Studio 项目是我在第二章中创建的项目。这个项目是使用项目名NoteFlashBlank App模板生成的,并且只包含 Visual Studio 默认添加的默认 HTML、JavaScript 和 CSS 文件。我在这个项目中做了两处修改:我在default.html文件的 HTML 元素中添加了一个 id 属性,在default.js文件中添加了一个for循环和debugger关键字。我将很快更改这两个文件的内容,替换这些添加的内容。

创建导航基础设施

从展示成品的图中可以看出,NoteFlash应用有几个不同的内容页面向用户显示。大多数 Windows 应用依赖于单页导航的 ?? 模式,类似于许多网络应用所采用的方法。

在这个模型中,运行时加载一个 HTML 母版页,然后根据需要将其他页面的内容插入到母版页中。对于NoteFlash app,我的母版页会是default.html(项目创建时 Visual Studio 生成的那个)。你可以在清单 3-1 中看到default.html的内容,这里我对前一章做了一个小改动。

了解不同的内容模型

为了避免混淆,在描述 Windows 应用时使用正确的术语非常重要。大多数应用都有多个页面,这些页面使用母版页作为内容容器来显示,这被称为单页内容模型。这样做的好处是,无论显示什么内容,都可以保留应用的状态,这使得编写应用的 JavaScript 部分变得更加容易。这种方法的缺点是 HTML 标记和 CSS 可能会更复杂,因为您必须确保不会无意中创建一个样式,例如,该样式的范围太广,会影响其他内容文件中包含的元素。

另一种方法是将所有内容文件完全分开,包含在单独的 HTML、JavaScript 和 CSS 文件中。这种方法被称为多页面模型,它使创建应用的 HTML 和 CSS 部分变得更加容易,但也使 JavaScript 变得更加复杂,因为每次加载一页内容并显示给用户时,应用的状态都会重置。

最后一个变化是单页应用。这是一个非常简单的应用,它只包含一页内容,通常是default.html文件。不需要加载其他内容,也不需要担心应用状态。我在本书的一些章节中使用单页应用进行简单的演示和小助手应用。

在很大程度上,单页内容模型是最有用的方法。在创建 HTML,尤其是 CSS 时,它需要一些小心,但它使 JavaScript 变得更简单,而且——正如您将了解到的——Windows 应用的复杂性通常在于代码,而不是样式或标记。在第五章中,我将回到单页内容模型,以及使其更容易使用的 API 特性。

清单 3-1 。default.html 的内容

`

         NoteFlash

                   

              

**    
** `

我用一个 div 元素替换了 Visual Studio 添加到default.html中的p元素(我在第二章中为其添加了一个id属性),该 div 元素的id属性值为pageFrame。这个元素将是我在单页模型中导入内容的容器,允许我向用户显示不同的内容。

images 提示应用的初始页面不必如此简单——你可以将本地元素与其他页面的元素以任何适合你的应用的组合混合在一起。我倾向于将这个页面用于整个应用中的通用内容,当我添加一个名为 AppBar 的 Windows 8 专用控件时,你会在第七章中看到。

定义代码

正如我在《??》第二章中解释的,Visual Studio 在创建新项目时会创建js/default.js文件,并在default.html文件中添加一个script元素,以确保该文件在应用启动时被加载。在本书中,我将使用default.js文件作为我的主要 JavaScript 文件,本章也不例外。

你可以在清单 3-2 中看到我对default.js文件所做的修改。我已经简化了代码,删除了一些我在本书后面才会描述的特性,并删除了注释。

清单 3-2 。修改后的 default.js 文件

`(function () {     "use strict";

    var app = WinJS.Application;     var activation = Windows.ApplicationModel.Activation;

    window.$ = WinJS.Utilities.query;     window.showPage = function (url, options) {         WinJS.Utilities.empty(pageFrame);         WinJS.UI.Pages.render(url, pageFrame, options);     };

    app.onactivated = function (args) {         showPage("/pages/selectorPage.html");     };

    app.start(); })();`

关于这段代码有很多需要解释的地方,尽管只有几条语句。由于这是我向您展示的第一个真正的 Windows 应用 JavaScript 文件,我将在接下来的部分中首先解释该文件的结构和内容。

处理全局名称空间

JavaScript 开发中最常遇到的问题之一是全局名称空间冲突。默认情况下,任何在函数之外创建的或者定义时没有使用var关键字的 JavaScript 变量都是一个全局变量,这意味着它可以通过应用中的 JavaScript 代码进行访问。对于 web 应用和 Windows 应用来说都是如此。有意义的变量名只有这么多,定义两个类似于datauser的变量并出现问题只是时间问题,尤其是当一个应用依赖于不同程序员的库时。代码的不同部分对有争议的变量的值和意义有不同的期望,结果可能是从稍微奇怪的行为到数据损坏。

使用自动执行功能

在清单 3-2 中,你可以看到 Windows 应用的三个约定中的两个,旨在减少名称空间污染。第一个是所有的 JavaScript 语句都包含在一个*自执行函数中。*通过将一个函数放在圆括号中,然后添加另一对圆括号,函数在被定义后立即被执行:

**(function() {**     // *... statements go here ...* **})();**

使用自执行函数的好处是,函数中定义的任何变量都是函数范围的局部变量,当函数完成时将被删除,从而保持全局命名空间清晰。Visual Studio 为您创建的任何 JavaScript 文件中都添加了自执行函数,我在本书中广泛使用了它们。我建议您在自己的代码中采用这种做法,因为这是帮助减少全局名称空间问题的最简单的方法之一。

使用严格模式

有助于保持全局名称空间清晰的第二个约定是使用严格模式,这是通过将"use strict"放在函数中实现的,如下所示:

**(function() {**     "use strict";     // ... statements go here ... **})();**

严格模式对 JavaScript 的使用方式施加了一些限制。其中一个限制是,您不能通过省略var关键字来隐式创建全局变量:

... var color1 = "blue";  // OK—scope is local to function color2 = "red";       // Not OK—this is a global variable ...

如果在使用严格模式时定义了一个隐式全局变量,Windows JavaScript 运行时将生成错误。使用严格模式是可选的,但这是很好的实践,它禁用了一些更令人困惑和奇怪的 JavaScript 行为。您可以通过阅读位于[www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf)的 ECMAScript 语言规范的附录 C 来获得严格模式实施的变化的全部细节。

了解 Windows 应用命名空间

既然你已经知道管理全局名称空间的重要性,那么清单 3-2 中的第一个常规语句就有意义了:

... window.$ = WinJS.Utilities.query; ...

这种说法只是一种方便。如果你读过我的 Pro jQuery 的书,你就会知道我是 jQuery 的忠实粉丝,我习惯于使用$快捷键来查询文档中的元素。我在了解 Windows API侧栏中介绍的WinJS API(应用编程接口)包含WinJS.Utilities.query方法,该方法对当前文档进行基于 CSS 选择器的查询,并支持一些基本的类似 jQuery 的操作。它不是 jQuery,但是对于大多数简单的任务来说已经足够接近了,我将在本书中通篇使用它。

images 提示你可以继续为你的 Windows 应用使用 jQuery(或者任何你喜欢的 JavaScript 库)。我在本书中保持简单,只使用内置的工具,但由于 JavaScript Windows 应用是使用 Internet Explorer 10 执行的,所以几乎任何编写良好的 JavaScript 库都可以正常工作。只要留意 Windows 8 与网络应用不同的地方就行了。一个很好的例子是用来表示 Windows 应用生命周期的一组事件。例如,您应该优先使用这些函数,而不是 jQuery 定义的ready函数,因为它们允许您正确地集成到操作系统中。我在第十九章中解释了这些事件。您可能还想看看我在本书第三部分中描述的 WinJS UI 控件——其中一些依赖于其他 WinJS 特性,如果您为这些特性使用其他 jQuery 库,您将不会获得最佳的 UI 体验。

我希望我的$快捷方式全局可用,所以我必须将其显式定义为window对象的属性(一个鲜为人知的事实是,所有 JavaScript 全局对象实际上都是window属性)。通过将WinJS.Utilities.query函数分配给window.$,我保持在自执行函数和严格模式的约束之内——这些约定都不是为了阻止你故意定义全局变量,只是为了防止你不小心这样做。

除了自执行函数和严格模式,微软帮助减少全局名称空间污染的第三种方式是支持名称空间,比如WinJS.Utilities:

... window.$ = **WinJS.Utilities**.query; ...

名称空间允许您以结构化的方式将相关的对象和函数组合在一起。在第四章中,我将向你展示如何创建你自己的名称空间。WinJS名称空间包含整个WinJS API,其中包括一个名为Utilities的子名称空间,它是用于 DOM 操作的类似 jQuery 的对象和方法的主目录。Utilities名称空间包含了query方法,它允许我使用 CSS 选择器定位 HTML 元素。命名空间创建按用途或公共功能分组的对象层次结构。

与 C#等语言中的命名空间不同,Windows app JavaScript 命名空间不限制对其包含的代码的访问,所有函数和数据值都可以通过命名空间全局使用。Windows 应用 JavaScript 名称空间都是关于使用结构来保持全局名称空间清晰。

了解 WINDOWS API

使用 HTML 和 JavaScript 编写 Windows 8 应用时,您可以访问许多不同的 API。首先,也是最明显的,是 DOM 和标准 JavaScript APIs。这些都是可用的,因为 JavaScript Windows 应用是使用 Internet Explorer 10 运行的,这意味着您可以访问 web 浏览器的所有功能(至少是 IE10 提供的功能)。当我在本章中构建NoteFlash应用时,你会发现你可以使用标准 HTML 元素和普通 JavaScript 创建一个基本的 Windows 应用。

您还可以访问 Windows API。这些是通过以Windows开头的名称空间访问的对象。这个 API 中的对象与 C#、Visual Basic 和 C++程序员可用的对象是相同的,但它们的出现是为了便于在 JavaScript 中使用。微软在让 JavaScript 成为 Windows 应用开发的一流语言方面做得相当不错,Windows API 是这一特性的核心。

最后一个 API 是WinJS,它包含特定于使用 HTML 和 JavaScript 编写的 Windows 应用的对象。在某些情况下,这些对象使得使用 Windows API 变得更加容易,但是大多数情况下,它们提供了只有 JavaScript 程序员才需要的特性。一个很好的例子是增加标准 HTML 元素的附加 UI 控件(我在第三部分中描述过)。这些是特定于 JavaScript 的,因为其他语言使用可扩展应用标记语言(XAML)。您可以通过打开 Visual Studio 项目的References部分来阅读 WinJS API 的源代码。(Windows API 的源代码不可用。)

定义全局导航功能

我的default.html文档包含了pageFrame元素,我将在其中插入其他页面。我想将导航到其他页面的能力作为一个全局函数公开,如下所示:

... window.showPage = function (url, options) {     WinJS.Utilities.empty(pageFrame);     WinJS.UI.Pages.render(url, pageFrame, options); }; ...

我已经将showPage函数定义为window对象的一个属性,因此它是全局可用的。这个函数的好处是,它允许我避免将pageFrame元素名称硬编码到我创建的每个内容页面中。showPage函数的参数是我想要显示的页面的 URL 和应该传递给页面的任何状态信息。

images 提示在第七章中我介绍了导航特性,它简化了这一技术。

在这个函数内部,可以看到一些 WinJS API 调用。我想替换而不是添加pageFrame元素中的任何内容。我调用了WinJS.Utilities.empty方法,它移除了作为参数传递的元素的所有子元素。我将pageFrame元素称为一个全局变量,这是我在第二章讨论使用浏览器特有特性时描述的 Internet Explorer 特性。

一旦我删除了任何现有的内容,我就调用WinJS.UI.Pages.render方法。这个方法是更大的页面特性的一部分,例如,除了使用iframe元素之外,它还提供了一些有用的行为。然而,在其核心,render方法是 web 应用 Ajax 请求中常用的XMLHttpRequest对象的包装器。

images 提示我将在本章稍后向应用添加页面时向您展示WinJS.UI.Pages提供的一些功能,我将在第五章中深入介绍 API 的这一部分。你可以在第十八章的中读到关于WinJS.Utilities API 的内容。

显示初始页面

设置应用的最后一步是使用我的showPage全局函数在pageFrame元素中显示初始页面:

`... var app = WinJS.Application;

app.onactivated = function (eventObject) { **    showPage("/pages/selectorPage.html");** }; app.start(); ...`

WinJS.Application对象为 Windows 应用提供了基础——包括定义描述应用生命周期的事件。我将在第十九章中全面解释生命周期,但是通过向onactivated属性添加一个处理函数,我表达了对activated事件的兴趣,该事件在应用启动时发送。这使我有机会执行任何一次性的初始化任务——包括使用showPage函数显示我的初始页面。您可以忽略这个片段中的最后一个语句——对app.start方法的调用。目前你需要知道的是activated事件的处理函数是你初始化应用的地方(我在第十九章中解释了start方法,但是在那之前你不需要知道它是如何工作的,只需要知道它是触发分配给onactivated属性的函数所必需的)。

添加音符字体

对于NoteFlash应用,我需要能够显示音符。我发现的最简单的方法是使用一种叫做MusiQwik的字体,它是由 Robert Allgeyer 创建的,我已经将它包含在本书的源代码下载中。如果你愿意,你可以直接从luc.devroye.org/allgeyer/allgeyer.html下载字体。

我需要使字体成为 Visual Studio 项目的一部分。我创建了一个名为resources的项目文件夹,将字体下载文件中的MusiQwik.ttf文件复制到其中。因为这是我第一次在项目中添加文件夹,所以我会给你一步一步的指导。

首先,您必须确保调试器没有运行,因为当它运行时,Solution Explorer不允许您修改项目结构。点击工具栏上的停止按钮(带有红色方形图标的那个)或从Debug菜单中选择Stop Debugging来停止应用。

右击Solution Explorer窗口中的粗体NoteFlash条目,并从弹出菜单中选择AddNew Folder。一个新的文件夹将被添加到项目中,并且它的名字将被选中,这样你就可以很容易地更改它——在这个例子中是更改为resources。按回车键确认名称,您就已经创建并命名了文件夹。您现在可以将源代码下载中的MusiQwik.ttf文件复制到文件夹中。

定义应用范围的 CSS

下一步是编写 CSS,使音乐字体可以在我的 HTML 文件中使用。用于执行 JavaScript Windows 应用的 Internet Explorer 10 支持 CSS3 网络字体功能,该功能允许自定义字体。清单 3-3 显示了我添加到css/default.css文件中的 CSS,它定义了字体和相关的样式。我删除了 Visual Studio 添加的默认样式和media规则(你可以在第二章的中看到)。

清单 3-3 。css/default.css 文件的内容

`@font-face {     font-family: 'Music';     font-style: normal;     font-weight: normal;     src: url('/resources/MusiQwik.ttf'); }

*.music {     font-family: Music, cursive;     font-size: 200px;     letter-spacing: 0px; }

*.musicSmall {     font-size: 100px; }

*.musicDisabled {     color: #808080; }

#pageFrame {     height: 100%; }`

这都是标准的 CSS3。我使用@font-face规则来定义一个新的字体,使用MusiQwik.ttf作为字体的来源。这个规则产生了一个新的字体,叫做Music

我在样式中为music类使用了新的字体,这允许我将任何元素中的文本显示为一系列注释。musicSmallmusicDisabled类是我在整个应用中使用的常见样式。我会把它们和music类结合起来,创造出特定的效果。

我在default.css文件中定义了font-face规则和相关的样式,因为我在这里定义的任何东西都将自动地被加载并插入到default.html文件中的每个页面所使用。这并不是 Windows 特有的魔法——它之所以有效,是因为 Visual Studio 在创建导入css/default.css文件的文件时向default.html添加了一个link元素:

`...

...`

default.css中定义的样式将应用于我插入到default.html中的其他页面的元素,这使得default.css特别适合定义应用范围的样式和规则。

我不想过多地强调这一点,但这是一个很好的例子,说明 JavaScript Windows 应用模型在多大程度上依赖于 web 标准,因此,您的 HTML、CSS 和 JavaScript 知识能让您在应用开发的道路上走多远。即使当我开始添加更多 Windows 特有的功能时,应用仍然会受到底层 web 技术和标准的驱动。

添加选择器页面

既然导航母版页和通用样式已经就绪,我可以添加应用的内容页面了。我将从选择器页面开始,它允许用户选择她将被测试的笔记组。

我喜欢在 Visual Studio 项目中将我的内容页面分组在一起,所以我在项目中添加了一个pages文件夹。我在这个文件夹中添加了selectorPage.html,方法是在Solution Explorer中右键单击新添加的pages文件夹,从弹出菜单中选择Add images New Item,并使用Page Control项目模板。

Page Control模板是一个方便的 Visual Studio 特性,它可以在一个步骤中创建一个 HTML 文件、一个 JavaScript 文件和一个 CSS 文件。HTML 文件包含一个用于 CSS 文件的link元素和一个用于 JavaScript 文件的script元素,以便在使用 HTML 时自动加载代码和样式。

JavaScript 和 CSS 文件创建在与 HTML 文件相同的文件夹中,并自动命名,这意味着我的项目包含三个新文件:pages/selectorPage.htmlpages/selectorPage.csspages/selectorPage.js。你可以在图 3-4 中看到这些文件是如何在解决方案浏览器中显示的。

images

***图 3-4。*使用页面控制模板创建一组链接的 HTML、CSS 和 JavaScript 文件

新的 CSS 和 JavaScript 文件允许我区分特定页面的样式和代码,以及适用于整个应用的样式和代码(在default.cssdefault.js文件中定义)。清单 3-4 显示了我的selectorPage.html文件的内容,它包含了将要呈现给用户的 HTML 标记。

清单 3-4 。selectorPage.html 文件的内容

`

         selectorPage

                   

**    ** **    **

**    
** **        

Select Notes

**

**        

** **            
'&======!
** **            
'¯==F===!
** **            

Left Hand

** **        
** **        
** **            
'&==F===!
** **            
'¯==F===!
** **            

Both Hands

** **        
**

**        

** **            
'&==F===!
** **            
'¯======!
** **            

Right Hand

** **        
** **    
**

`

我突出显示了将 CSS 和 JavaScript 文件带入上下文的linkscript元素,以及我对文件所做的更改。Windows 应用真的很像 web 应用,并且没有将 Visual Studio 创建的文件与 HTML 页面相关联的魔法——您负责确保您需要的一切都链接到 HTML,尽管 Visual Studio 在从其模板创建文件时会有很大帮助。

所有三个selectorPage文件的内容将被导入到应用的主导航结构中,这意味着由default.js文件定义的功能和属性可以在特定于页面的脚本中调用,而在default.css中定义的 CSS 类将被应用到各个 HTML 文件中的元素。

我的更改删除了 Visual Studio 放入body元素中的默认内容,代之以一个简单的结构,该结构将向用户提供要测试的笔记的选择。我已经应用了我在css/default.css文件中定义的样式来应用音符字体。奇怪的字符串在字体中显示为有意义的音符,但它们本身看起来非常奇怪。

images 提示在第五章我解释了为什么default . CSS 和 default.js 文件的内容总是可用,当我解释 Windows 应用如何处理导入的 HTML 内容时。

HTML5 语义元素和划分

HTML5 有趣的一点是增加了新元素类型,比如sectionarticle。当您希望一致地将语义结构应用于内容时,这些元素非常有用,这样内容区域的重要性就可以从包含它的元素类型中明显看出。这是对 HTML 4 方法的一大改进,在 HTML 4 方法中,语义通常是应用于div元素的表达类,这很难一致地应用,并且使得与采用不同类本体的第三方共享内容变得特别困难。这很好,尤其是当你处理大量内容的时候。

另外,新的 HTML 开发人员倾向于一种被称为 divitusdiv-itus 的行为,这是指过度使用div元素来为页面布局添加结构。结果是一长串用于微观管理元素布局的 CSS 样式。这导致内容难以阅读、难以维护,并且在进行更改时容易出现显示问题。

许多程序员将这两个问题混为一谈,并开始使用 HTML5 语义元素来减少标记中的div元素的数量。用article元素替换div元素根本没有用*,除非article元素中的内容代表某种...嗯,。如果内容不是某种类型的文章,你会让世界上其他人更难处理你的内容,这与 HTML5 中新元素的意图完全相反。*

解决divitus的方法是学习如何有效地使用 CSS,这样你需要更少的 CSS 类,结果是更少的div元素——用不恰当的语义元素替换div元素不会提高你的 HTML 的质量(但是用恰当的语义元素替换div元素确实会提高你的 HTML,并且会让你因为掌握了 HTML5 的语义特性而广受赞誉)。

需要澄清的是,div元素被认为是用来帮助创建布局的,因为div元素在语义上是中性的——当一项内容有清晰明显的含义时,应用语义元素,当没有含义时,应用div元素。这就是为什么div元素在 HTML5 规范中被称为最后的元素——如果有更适合你的内容的元素,那么就使用它——但是当你需要它们的时候不要特意避开div元素。

对于前面的章节和例子,这个问题有两个实际结果。首先,我没有使用新的语义元素——不是因为我不喜欢它们,而是因为我的示例应用通常重布局轻数据。这是示例应用的本质,在某种程度上,也是 Windows 应用中 HTML 的本质。因此,在本书中,你会在我的标记中看到很多div元素。

你还会看到,我大量使用了两个新的 CSS3 布局特性,以防止我的div用法变成一种分割。这两个特性是网格布局flexbox 布局,它们都允许我创建流畅灵活的布局,而不需要大量的div元素作为基础设施。我在这一章简单解释网格布局,在第四章简单解释 flexbox 布局。这两者都值得你关注,并且会比无情地(并且没有必要地)替换div元素对你的 HTML 的简洁和质量有更大的影响。

定义选择器页面 CSS

为了管理selectorPage.html文件中标记的布局,我使用了grid布局,这是 CSS3 的一个特性。网格布局的规范还没有最终确定,所以微软使用了特定于供应商的属性名(以-ms-开头,表示一个没有最终确定或不符合最终规范的特性)。你可以在清单 3-5 中看到我是如何应用 CSS grid的,它显示了selectorPage.css文件的内容。

images 提示 Visual Studio 默认缩进 CSS,不适合我的开发风格,对书籍页面布局没有帮助。我已经禁用了层次缩进功能(使用ToolsimagesOptionsimagesText EditorimagesCSSimagesFormatting),我在本书中展示的所有 CSS 都将是平面的。

清单 3-5 。selectorPage.css 文件

`#selectorFrame { **    display: -ms-grid;** **    -ms-grid-rows: 0.25fr 0.25fr 1fr 0.5fr;** **    -ms-grid-columns: 0.5fr 1fr 1fr 1fr 0.5fr;**     text-align: center;     padding: 0px 20px; }

div.musicButton {     border: medium solid white;     margin: 25px;     padding: 10px; }

div.musicButton:hover {     background-color: #3D4C42; }

div.musicButtonPressed {     background-color: #6B997A; }

#prompt { **    -ms-grid-column: 2;** **    -ms-grid-row: 2;** **    -ms-grid-column-span: 3;**     margin: 10px; }

#leftHand { **    -ms-grid-column: 2;** **    -ms-grid-row: 3;** }

#rightHand { **    -ms-grid-column: 4;** **    -ms-grid-row: 3;** }

#bothHands { **    -ms-grid-column: 3;** **    -ms-grid-row: 3;** }`

这个文件包含了selectorPage内容的所有 CSS,所以我强调了那些与网格布局特性相关的属性。

使用 CSS 网格布局

您可能没有使用过网格布局,因为它相当新,并且没有在主流 web 浏览器中一致地实现。幸运的是,在开发 Windows 应用时,您只需要担心 Internet Explorer 10,这意味着您可以根据单个浏览器的功能来定制 HTML 和 CSS 功能的使用。在这一节中,我给你一个 CSS grid布局的快速概述。

要开始使用grid布局,您需要设置display属性,并为包含网格的元素指定行数和列数。我希望网格出现在selectorFrame元素中,所以我像这样应用属性:

... #selectorFrame { **    display: -ms-grid;** **    -ms-grid-rows: 0.25fr 0.25fr 1fr 0.5fr;** **    -ms-grid-columns: 0.5fr 1fr 1fr 1fr 0.5fr;**     text-align: center;     padding: 0px 20px; } ...

必须将display属性设置为-ms-grid-ms-grid-rows-ms-grid-columns属性指定了网格结构。这些可以指定为分数单位(表示为fr,代表可用空间的一部分),可用空间的百分比,或者使用固定的尺寸,如像素。有许多不同的方式来表达网格,但是我最常用的方法是清单中显示的方法:整个可用空间的一部分。我喜欢分数,因为我可以很容易地创建一个网格,在这个网格中,行和列相对于彼此表示和相对于容器大小表示。我也喜欢使用分数,因为它们可以很容易地在元素周围创造空间。

为了理解清单中的网格,考虑一下行。要获得整个单位的总数,将所有的fr值加在一起:0.25 + 0.25 + 1 + 0.5 = 2 个单位。为容器元素分配的空间是指定的小数单位数,因此 1 个fr单位等于容器高度的 50%。无论容器元素占据多少像素,这个比率都将保持不变,浏览器将放大和缩小每行占据的切片。浏览器使用我指定的比率分配可用空间。在我对柱子重复这个过程之后,我就有了一个网格。

下一步是使用- ms-grid-column-ms-grid-row属性在网格中放置项目,如下所示:

... #prompt { **    -ms-grid-column: 2;** **    -ms-grid-row: 2;** **    -ms-grid-column-span: 3;**     margin: 10px; } ...

这些属性使用基于 1 的索引指定元素出现在哪一行和哪一列(即,要将一个项目放在第一行或第一列,使用值1)。要使一个项目占据多行或多列,使用-ms-grid-column-span-ms-grid-row-span属性。在这个片段中,我已经在第 2 行第 2 列找到了具有promptid值的元素,并指定它应该跨越 3 列。

另一个感兴趣的网格属性是-ms-grid-column-align,我在这个例子中没有使用它。该属性指定网格正方形内元素的对齐方式,可以设置为startendcenterstretch。如果您使用的是从左到右的语言,比如英语,那么startend值将元素左右对齐。center值使元素居中,stretch值调整元素的大小,使其完全填充分配给它的空间。您可以使用网格属性创建一些非常复杂的布局。

images 提示详见[www.w3.org/TR/css3-grid](http://www.w3.org/TR/css3-grid)的 CSS 网格规范,记住这还不是一个被认可的标准。你可以在[msdn.microsoft.com/en-us/library/windows/apps/hh453256.aspx](http://msdn.microsoft.com/en-us/library/windows/apps/hh453256.aspx)阅读微软对网格 CSS 属性的描述。

添加音乐字体和风格

如果你查看清单 3-4 中文件的内容,你会发现一些元素的内容是一系列字符引用,就像这样:

`...

**'¯==F===!**
...`

这就是我如何表达音符和它周围的符号。您可以看到片段中的div元素具有musicmusicSmall样式,这意味着浏览器将应用我在本章前面的default.css文件中定义的自定义font face。并不是所有我想从字体中得到的符号都被映射成方便的字符,所以我混合使用了常规字符和 HTML 转义码来获得我需要的内容。你可以在图 3-1 中看到音乐字体和相关样式的效果,图中显示了selectorPage.html文件的最终外观。

定义选择器页面的 JavaScript 代码

selectorPage.html标记中的元素看起来像按钮,但它们只是div元素。为了让它们表现得像按钮,我需要使用 CSS 和 JavaScript 代码的组合。你已经看到了 CSS——在清单 3-5 中,我定义了div.musicButtondiv.musicButton:hover样式来设置基础样式,并在指针移动到三个div元素之一所占据的屏幕区域时做出响应。

为了补充这个基本行为,我必须切换到 JavaScript,我已经在pages/selectorPage.js文件中定义了它,以便遵循 Windows 应用惯例。你可以在清单 3-6 中看到代码。

清单 3-6 。selectorPage.js 文件的内容

`(function () {     "use strict";

    function handleMusicButtonEvents(e) {         switch (e.type) {             case "mousedown":                 this.style.backgroundColor = "#6B997A";                 break;             case "mouseup":                 this.style.backgroundColor = "";                 break;             case "click":                 showPage("/pages/flashCardsPage.html", this.id);                 break;         }     };

**    WinJS.UI.Pages.define("/pages/selectorPage.html", {** **        ready: function (element, options) {** **            var buttons = WinJS.Utilities.query("div.musicButton");** **            ["mouseup", "mousedown", "click"].forEach(function (eventType) {** **                buttons.listen(eventType, handleMusicButtonEvents);** **            });** **        }** **    });** })();`

在这个文件中,您可以看到导航模型的另一半。在default.js文件中,我使用了WinJS.UI.Pages.render方法来显示页面,比如selectorPage.html。在selectorPage.js中,我使用WinJS.UI.Pages.define方法在页面显示时做出响应。

方法的参数是页面的 URL 和一个为不同事件定义处理函数的对象。在这个例子中,我为ready事件定义了一个处理程序,每次显示页面时都会调用这个处理程序。(我在第五章中详细解释了这两个部分的功能。)

这个方法的操作方式有些微妙,第一个参数暗示了这一点:页面 URL。您不必在与页面关联的 JavaScript 文件中注册事件处理函数。您可以将它们放入任何已经整合到应用中的 JavaScript 代码中——对于我的简单应用,这意味着default.js或创建一个新文件并使用script元素导入代码。我在第五章的中深入研究了define方法,但是现在,知道这一点就足够了这是 Windows 应用模式,用于定义当指定文件中的标记被加载到应用中时应该执行的代码。

优化和加载 JAVASCRIPT

当编写一个常规的 web 应用时,有很多动机直接控制 JavaScript 代码的加载。您从缩小或最小化代码开始,以便它需要更少的带宽,您将多个文件连接在一起以减少浏览器必须建立的 HTTP 连接的数量,您使用内容交付网络(cdn)来获得流行的 JavaScript 库,希望提高性能,或者,理想情况下,从您需要的公共库的先前下载版本中受益。

如果您非常重视 web 应用的性能,您可以开始考虑使用 JavaScript 加载器来异步引入您的 JavaScript 代码。我在 web 应用中使用的两个加载器是YepNopeRequireJS。如果小心使用,一个好的 JavaScript 加载器确实可以减少用户盯着加载屏幕的时间,并且在使用像YepNope这样的条件加载器的情况下,避免下载特定用户、浏览器或平台不需要的脚本文件。

可以将这些相同的技术应用到你的 JavaScript Windows 应用中,但是这样做没有任何好处——更糟糕的是,你只会让你的应用更难测试和调试。

Windows 8 应用部署在本地,这意味着您的脚本文件加载速度非常快,快到您不太可能需要推迟执行来解决性能问题。同样,因为 JavaScript 文件是本地加载的,所以不需要缩减代码来减少带宽消耗。

有一些重要的 WinJS 特性和约定隐含地依赖于同步脚本执行,包括我在上一节中向您展示的define方法。这并不是说你不能解决这些情况——但是在这样做之前,你应该停下来问问自己你正在试图解决什么问题。自从 Windows 8 的最早发布版本以来,我一直在编写 Windows 应用,我还没有遇到任何可以通过添加 JavaScript 加载程序来解决的问题。可能有一些非常特殊的情况,加载器非常有用,但我还没有遇到过,但这些情况很可能很少发生,因此您不应该仅仅因为它是 web 应用开发工作流的一部分就自动将加载器添加到您的 Windows 应用项目中。

处理按钮点击

在我的ready事件处理程序中,我将handleMusicButtonEvents函数注册为clickmousedownmouseup事件的处理程序。我没有在标记中使用button元素,所以我通过应用和移除背景颜色来响应mousedownmouseup事件。这些是您在 web 应用开发过程中遇到的标准 DOM 事件,您可以像处理普通 HTML 页面一样处理它们。

我使用WinJS.Utilities.query方法找到了我想要的元素,这是我之前在default.js文件中别名为$的方法。我在这个文件中明确地使用了方法名,所以你可以清楚地看到发生了什么。query方法采用 CSS 选择器,并返回代表页面中匹配元素的标准 DOM HTMLElement对象的集合(这是调用该方法时应用的整个标记,而不仅仅是内容 HTML 文件中的内容)。匹配元素的集合可以像常规数组一样处理,但实际上是一个WinJS.Utilities.QueryCollection对象,它定义了许多有用的方法。其中一个方法是listen,它接受一个事件名称和一个处理函数,并对集合中的每个HTMLElements调用addEventListener方法。如果您是一名 jQuery 用户,您会认识到,WinJS.Utilities名称空间受 jQuery API 启发的程度。(我将向您介绍我使用的各种WinJS.Utilities特性,并在第十八章中详细讨论名称空间。)

images 提示您会注意到我使用标准的 DOM 事件来处理用户交互。这工作得非常好(我在本书中使用这些事件),但是如果你想处理触摸和手势输入,你需要使用微软特有的事件。我在第十七章中解释了这些事件是什么以及它们是如何工作的。对于常规的输入,比如处理按钮,即使用户触摸了屏幕而不是使用鼠标,常规的 DOM 事件也会起作用。

当用户点击或触摸其中一个元素时,我调用我之前在default.js文件中创建的全局showPage导航函数,传入所选元素的id,如下所示:

... showPage("/pages/flashCardsPage.html", **this.id**); ...

在这个showPage函数中,这个参数被传递给WinJS.UI.Pages.render方法,如下所示:

... window.showPage = function (url, options) {     var targetElem = document.getElementById("pageFrame");     WinJS.Utilities.empty(targetElem);     WinJS.UI.Pages.render(url, targetElem, options); }; ...

这是一种将信息从一个页面传递到另一个页面的简单技术,但是它需要预先的知识和页面之间的协调。这对于像这样的简单应用来说是好的,但是在实际项目中是有问题的。在本书的第二部分中,我向你展示了各种技术和特性,来帮助你去除这些依赖性,创建由松散耦合的组件组成的应用,这使得应用更容易编写和维护。

你可以在图 3-5 中看到该应用是如何出现的:允许用户选择她想要测试的音符的按钮状div元素整齐地显示在布局网格上,并对基本的鼠标交互做出响应(尽管单击时它们加载的页面尚不存在)。

images

***图 3-5。*允许用户选择一组音符

目前看起来不太像,但这一章的大部分内容都是关于 Windows 应用开发的上下文和背景。到目前为止,该应用本身仅由几个短文件和一些基本的 JavaScript 组成。下一章会加快速度,你会看到应用的其余功能很快到位。

总结

示例应用目前没有做太多工作,但是到目前为止,您已经了解了 Windows 应用开发中一些最基本的概念。您还了解了 Windows 为帮助您管理全局命名空间而提供的约定和功能。Windows 应用开发不是 web 应用开发,但是您现有的技能为您创建丰富流畅的 Windows 应用打下了良好的基础。在本章中,你看到了我如何使用标准 HTML 和 CSS 来创建应用的结构和布局,包括 CSS3 web 字体和grid布局(尽管使用了特定于供应商的前缀)以及标准 DOM 事件,如clickmouseover。在下一章中,我将添加到示例应用中并构建功能。

四、完成应用

在这一章中,我将完成我在第三章中开始的基础版本NoteFlash示例应用。我继续使用与常规 web 应用开发有很多共同之处的方法和技术,但我也开始融入更多特定于 Windows 的功能,这些功能可通过 WinJS API 获得。我简要概述了我使用的每个 Windows 应用的功能,并解释了在本书中可以获得更多详细信息的地方。

重温示例应用

在这一章中,我将直接从第三章的开始构建NoteFlash项目。正如您所记得的,我将应用的基本结构放在适当的位置,定义了导航功能,并定义了将在整个应用中应用的样式。我还使用了Page Control条目模板来生成一组相关的 HTML、CSS 和 JavaScript 文件,这些文件用于创建允许用户选择要测试的笔记的内容。你可以在图 4-1 中看到结果。在这一章中,我将创建额外的内容来根据用户的选择执行测试。

images

***图 4-1。*便签选择页面

定义 Notes 数据

构建示例应用的下一步是定义用户将要测试的笔记。为此,我在名为notes.jsjs文件夹中添加了一个新的 JavaScript 文件,其内容可以在清单 4-1 中看到。(右击Solution Explorer中的js文件夹,选择Add images New Item,使用JavaScript File项模板。)

images 提示Solution Explorer窗口中右键点击js文件夹,从弹出菜单中选择Add images New Item,添加一个 JavaScript 文件。从文件类型列表中选择JavaScript File,设置文件名为notes.js,点击Add按钮。

清单 4-1 。定义音符数据

`(function () {     "use strict";

    var Note = WinJS.Class.define(function (note, character, hand) {         this.note = note;         this.character = character;         this.hand = hand;     });

    WinJS.Namespace.define("Notes", {         leftHand: [                 new Note('C', 80, "left"), new Note('D', 81, "left"),                 new Note('E', 82, "left"), new Note('F', 83, "left"),                 new Note('G', 84, "left"), new Note('A', 85, "left"),                 new Note('B', 86, "left"), new Note('C', 87, "left"),                 new Note('D', 88, "left"), new Note('E', 89, "left"),                 new Note('F', 90, "left"), new Note('G', 91, "left"),                 new Note('A', 92, "left"), new Note('B', 93, "left"),                 new Note('C', 94, "left")         ],         rightHand: [                 new Note('C', 82, "right"), new Note('D', 83, "right"),                 new Note('E', 84, "right"), new Note('F', 85, "right"),                 new Note('G', 86, "right"), new Note('A', 87, "right"),                 new Note('B', 88, "right"), new Note('C', 89, "right"),                 new Note('D', 90, "right"), new Note('E', 91, "right"),                 new Note('F', 92, "right"), new Note('G', 93, "right"),                 new Note('A', 94, "right"),         ],     });

})();`

这个文件需要一些解释。我使用了 WinJS API 的两个有用的特性:名称空间。我将在接下来的章节中解释它们。

Windows JavaScript 类

JavaScript 是一种基于原型的面向对象语言,这意味着继承是通过克隆现有对象来工作的(这些对象被称为原型)。所有其他可用于 Windows 应用开发的语言都使用基于类的继承,其中对象的功能在单独的类中定义。对象是作为这些类的实例创建的。大多数主流编程语言使用基于类的继承,例如,如果你用 C#或 Java 编写过软件,你就会遇到类。

WinJS API 支持用 JavaScript 创建类。WinJS.Class.define方法最多接受三个参数:一个作为类构造函数的函数、一个包含类实例成员的对象和一个包含类静态成员的对象。WinJS.Class.derive方法允许您通过从现有的类派生来创建新的类。

images 提示微软曾表示,基于类的继承比标准的基于 JavaScript 原型的方法提供了性能优势,但我怀疑它更多地与 Windows API 必须向本地支持类的 JavaScript 语言公开的方式有关。

notes.js文件中,我定义了一个名为Note的基类,就像这样:

... **var Note = WinJS.Class.define(function (note, character, hand) {** **    this.note = note;** **    this.character = character;** **    this.hand = hand;** **});** ...

我只为这个类定义了构造函数,它有三个参数——音符(如C),在音乐字体中代表音符的字符,以及音符与哪只手相关(因为,当然,弹钢琴时要用两只手——这给我的音乐课带来了很多困难)。

一旦定义了一个类,就可以使用关键字new创建新的实例,如下所示:

... var myNote = new Note('C', 80, "left"); ...

这条语句创建了一个代表左手音符 C 的Note对象,由我在第三章中添加到项目中的音乐字体中的字符代码80表示。

关键字new是 JavaScript 的标准部分,但在 web 应用开发中并未广泛使用。WinJS.Class.define方法创建的类是非常基本的,它们缺少你在其他语言中期望的类的大部分特性。这种方法的主要好处是,它提供了一种机制,以一种可以在 JavaScript 中使用的方式公开 Windows API。

您不必在自己的 JavaScript 中使用类,但是理解这个特性是很重要的,因为微软已经在 WinJS API 中广泛使用了它,一旦您使用调试器单步调试代码,就会遇到它。我很少在自己的代码中使用类,因为我认为它们增加了许多基于类的继承的问题,却没有任何好处。

创建名称空间

正如我在第三章中解释的那样,名称空间是减少 Windows 应用中全局名称空间污染的技术之一(另外两个是自执行函数严格模式)。名称空间背后的思想是创建一个单独的全局变量并将数据值和函数附加到它上面,而不是使每个单独的值和函数都是全局的。

您已经看到了微软如何使用名称空间来构建 API,比如WinJS.UtilitiesWinJS.UI.Pages。如果您想使用query方法在 HTML 中搜索元素,您可以调用WinJS.Utilities.query。查询方法是WinJS.Utilities名称空间的一部分,该名称空间包含许多其他有用的函数。

名称空间可以是分层的。Utilities名称空间是WinJS名称空间的一部分。WinJS包含许多子名称空间,Utilities只是其中之一。在上一节中,我使用了WinJS.Class.define方法——该方法位于WinJS.Class名称空间中,而ClassUtilities的对等体。通过使用名称空间,微软将大量的功能打包到两个全局名称空间对象中:WinJSWindows

所有的名称空间都是:全局对象。例如,清单 4-2 展示了如何使用常规的 JavaScript 对象来重新创建WinJS.Utilities.query方法。

清单 4-2 。作为对象层次结构的名称空间

... var WinJS = {     Utilities: {         query: function (someArguments) {             // ...implementation goes here...         }     } }; ...

名称空间的层次性意味着您可以在名称空间层次结构的不同位置重用变量和方法的名称。例如,一个(假设的)WinJS.Database.query方法完全独立于WinJS.Utilities.query,尽管这两个方法都被称为query。这是名称空间的好处之一。如果所有的方法都是全局的,我会以像queryHTMLByIdqueryHTMLByTagName这样的名字结束,这和你在 DOM API 中看到的冗长的名字是一样的,其中所有的方法都是对等的。使用名称空间向代码添加结构意味着方法名称可能是有意义的,而方法操作的上下文来自其名称空间

您可以使用 WinJS API 来创建自己的名称空间,使用WinJS.Namespace名称空间的特性,我在notes.js文件中使用了如下的特性:

... **WinJS.Namespace.define("Notes"**, {     leftHand: [         new Note('C', 80, "left"), new Note('D', 81, "left"),         // ...other notes removed for brevity...     ],     rightHand: [         new Note('C', 82, "right"), new Note('D', 83, "right"),         // ...other notes removed for brevity...     ], **});** ...

WinJS.Namespace.define方法创建新的名称空间。第一个参数是要创建的命名空间的名称,第二个参数是其成员将被添加到命名空间的对象。

images 提示您可以通过将带点的名称作为第一个参数传递给 define 方法来创建名称空间的层次结构,比如MyData.Music.Notes。将自动创建层次结构中的每个级别。

在清单中,我创建了一个名为Notes的新名称空间,它包含两个数组:leftHandrightHand。每个数组包含一组Note对象,代表与那只手相关的音符序列,这些对象是使用我在本章前面描述的Note类创建的。

使用define方法创建名称空间在几个方面让生活变得更简单。首先,名称空间被自动添加到全局名称空间中。考虑到创建全局变量是多么容易(真的太容易了),这没什么大不了的,但这确实意味着我将能够在我的应用中的任何地方引用Notes.leftHandNotes.rightHand。这是我创建视图模型时所依赖的东西,我会在第八章的中解释。

使用define方法的第二个好处是它检查您正在创建的名称空间的部分是否已经存在。例如,如果我像这样定义了名称空间Notes:

... window.Notes = {    leftHand: [ ...notes... ] } ...

然后尝试添加到该命名空间,就像这样:

... window.Notes = {     rightHand: [ ...notes... ] } ...

我将最终只得到rightHand笔记,因为我的第二个window.Notes对象将完全取代第一个。然而,我可以使用define方法安全地添加名称空间,没有任何问题,如清单 4-3 所示。

清单 4-3 。通过多次调用 define 方法逐步构建名称空间

`(function () {     "use strict";

    var Note = WinJS.Class.define(function (note, character, hand) {         this.note = note;         this.character = character;         this.hand = hand;     });

**    WinJS.Namespace.define("Notes", {** **        leftHand: [** **                new Note('C', 80, "left"), new Note('D', 81, "left"),** **                new Note('E', 82, "left"), new Note('F', 83, "left"),** **                new Note('G', 84, "left"), new Note('A', 85, "left"),                 new Note('B', 86, "left"), new Note('C', 87, "left"),** **                new Note('D', 88, "left"), new Note('E', 89, "left"),** **                new Note('F', 90, "left"), new Note('G', 91, "left"),** **                new Note('A', 92, "left"), new Note('B', 93, "left"),** **                new Note('C', 94, "left")** **        ]** **    });**

**    WinJS.Namespace.define("Notes", {** **        rightHand: [** **                new Note('C', 82, "right"), new Note('D', 83, "right"),** **                new Note('E', 84, "right"), new Note('F', 85, "right"),** **                new Note('G', 86, "right"), new Note('A', 87, "right"),** **                new Note('B', 88, "right"), new Note('C', 89, "right"),** **                new Note('D', 90, "right"), new Note('E', 91, "right"),** **                new Note('F', 92, "right"), new Note('G', 93, "right"),** **                new Note('A', 94, "right"),** **        ],** **    });**

})();`

结果是一个包含leftHandrightHand注释的名称空间。当然,您可以自己执行这些检查,但是使用WinJS.Namespace.define方法更方便。对于许多 WinJS 功能来说都是如此——您可以自己编写这些功能的实现,但是 Microsoft API 通常更方便。

添加闪存卡页面

NoteFlash应用中缺失的部分是测试用户音符知识的页面。构建应用的下一步是添加这个页面,所以我使用Page Control项模板在pages文件夹中创建一个名为flashCardsPage.html的新页面。这是我在上一章用来创建selectorPage.html页面的同一个模板,当你使用这个模板时,Visual Studio 会向项目添加 HTML、CSS 和 JavaScript 文件。

为了解释我如何实现这个页面,我需要在这个页面的 HTML、CSS 和 JavaScript 之间切换。最简单的方法是列出组成页面的文件内容,然后解释各个部分是如何组合在一起的。在这个过程中,我将介绍一些重要的 WinJS 特性。

首先,你可以在清单 4-4 的中看到我添加到flashCardsPage.html文件的内容。在接下来的几节中,我将分解这个文件的每个部分是做什么的。

清单 4-4 。flashCardsPage.html 文件的内容

`

         flashCardsPage

                   

     **    **     

**    
** **        
** **            ** **            ** **        
**

**        

Title Will Go Here

** **        

** **            1 of** **            1** **            (** **                 Correct/** **             Wrong)                    ** **        

**

**        

** **            
** **                '&=====!** **            
** **            
** **                '¯=====!** **            
** **        
**

**        

** **            C** **            D** **            E** **            F** **            G** **            A** **            B** **            Back** **            Again** **        
** **    
**

`

本文档中的head元素包含您所期望的内容:WinJS API 文件的script元素、flashCardsPage.jsnotes.js文件以及flashCardsPage.css文件的link元素。

添加页面特定的 CSS

元素中的标记为应用的测试部分提供了布局。flashCardsPage.css文件包含页面特定的 CSS,它布局了flashCardsPage.html中的元素,您可以在清单 4-5 中看到 CSS 文件的内容。

清单 4-5 。flashCardsPage.css 文件

`header {     margin: 20px; }

h1.subhead {     font-size: 30pt;     margin: 10px; }

#backButtonContainer {     width: 100%;     padding: 20px; }

#backButton {     margin-left: 20px; }

#flashContainer { **    display: -ms-flexbox;** **     -ms-flex-direction: column;** **     -ms-flex-align: center;** **     -ms-flex-pack: justify;  **     height: 100%; }

#noteButtons {     margin-bottom: 50px; }

#noteButtons button {     font-size: 30pt;     margin: 5px; }

button.correct {     background-color: #4cff00; }

#noteButtons button[id] {     width: 200px; }`

我强调了 CSS 文件最重要的部分:使用flexbox布局,这是我在本书中经常使用的另一个 CSS3 布局(第一个是我在第三章中展示的grid布局)。我在flashCardsPage.css中使用的其他属性是常用的,但是flexbox布局是新的,还没有被广泛采用——部分是因为规范仍在开发中,这就是为什么我必须使用特定于供应商的属性名。

了解柔性盒布局

柔性盒布局,通常被称为柔性盒,它提供了一种流体布局,当屏幕尺寸改变时,这种布局能够很好地响应。这在 Windows 应用中很重要,因为用户可以重新定位设备或改变分配给应用的屏幕大小(我会讨论这两个功能,并在第六章的中告诉你如何适应它们)。

images 提示当我需要精确划分屏幕空间时,我倾向于使用网格布局,当我更关心流动性和居中元素时,我倾向于使用 flexbox 布局。

您可以通过将display属性设置为–ms-flexbox来启用 flexbox 布局,就像我在清单中所做的那样。最重要的属性是–ms-flex-direction,它指定了子元素的布局方式。我已经在表 4-1 中列出了该属性支持的值。

images

在清单中,我指定了列值,这意味着我的元素将按照它们在 HTML 中定义的顺序从上到下排列。

–ms-flex-pack属性指定元素如何沿–ms-flex-direction属性指定的轴对齐(沿垂直轴为column值,沿水平轴为row值)。我已经在表 4-2 中列出了该房产的价值。

images

在清单中,我使用了justify属性,这意味着flashContainer元素中的元素将被隔开,这样它们就占据了元素的整个高度。

–ms-flex-align属性指定元素沿轴的对齐方式,该轴未被–ms-flex-direction属性使用——也就是说,与元素布局所沿的轴成 90 度,称为正交轴。该属性值如表 4-3 所示。

images

使用这三个属性,你可以在布局中构建很多流动性,但是还有一些我在本书中没有用到的属性(并且经常发现没有用)。你可以在[msdn.microsoft.com/en-us/library/windows/apps/hh453474.aspx](http://msdn.microsoft.com/en-us/library/windows/apps/hh453474.aspx)看到完整的列表。

images只需将–ms-flex-align属性设置为justify并将–ms-flex-pack属性设置为center,子元素将被放置在元素的中心。

在清单中,我指定了center值,这意味着我的元素将与flashContainer元素的中心对齐。结合我指定的其他值,flashContainer元素的子元素将垂直布局,分布在元素的整个高度,并位于元素的中心。您可以在图 4-2 中看到页面的布局。

images

***图 4-2。*闪存卡页面的布局

我还没有定义 JavaScript 代码来控制这些元素,但是你已经可以看到该功能的主要组成部分:用户正确和错误回答的详细信息的占位符,显示左手和右手笔记所需的五线谱,以及用户点击或触摸来识别笔记的一组按钮。

定义闪存卡页面的代码

清单 4-6 显示了flashCardsPage.js文件的内容。没有大量的代码,但是有一些有趣的东西在进行,我将在下面的部分中解释。

清单 4-6 。flashCardsPage.js 文件

`(function () {     "use strict";

    var appState = WinJS.Binding.as({         title: "", mode: null, leftNote: "=",         rightNote: "=", currentIndex: 0, noteCount: 0,         notes: [], currentNote: null,         results: {             numberCorrect: 0,             numberWrong: 0         },     });

    WinJS.UI.Pages.define("/pages/flashCardsPage.html", {         ready: function (element, options) {

            WinJS.Binding.processAll(document.body, appState);

            backButton.addEventListener("click", function (e) {                 showPage("/pages/selectorPage.html");             });             $("#noteButtons button").listen("click", handleButtonClick);

            setState(options);             selectAndDisplayNote();         }     });

    function setState(mode) {

        appState.mode = mode;         switch (mode) {             case "leftHand":                 appState.title = "Left Hand Notes";                 break;             case "rightHand":                 appState.title = "Right Hand Notes";                 break; case "bothHands":                 appState.title = "All Notes";                 break;         }

        appState.notes = [];         if (mode == "leftHand" || mode == "bothHands") {             Notes.leftHand.slice().forEach(function (item) {                 appState.notes.push(item);             });         }         if (mode == "rightHand" || mode == "bothHands") {             Notes.rightHand.slice().forEach(function (item) {                 appState.notes.push(item);             });         }         appState.currentIndex = 0;         appState.results.numberCorrect = 0;         appState.results.numberWrong = 0;         appState.noteCount = appState.notes.length;     }

    function selectAndDisplayNote() {         if (appState.notes.length > 0) {             var index = Math.floor((Math.random() * appState.notes.length));             var note = appState.notes.splice(index, 1)[0];             appState.leftNote = (note.hand == "left" ? "&#"                 + note.character + ";" : "=");             appState.rightNote = (note.hand == "right" ? "&#"                 + note.character + ";" : "=");             appState.currentNote = note;             appState.currentIndex++;         } else {             ("#noteButtons button").forEach(function (item) {                 item.style.display = "none";             });             ("#noteButtons button[id]").setAttribute("style", "");         }     }

    function handleButtonClick(e) {         if (this.id == "restart") {             showPage("/pages/flashCardsPage.html", appState.mode);         } else if (this.id == "back") {             showPage("/pages/selectorPage.html");         } else {             ("button[datanote].correct").removeClass("correct");            ("button[data-note].correct").removeClass("correct");             ("button[data-note=" + appState.currentNote.note + "]")                 .addClass("correct");

            if (this.innerText == appState.currentNote.note) {                 appState.results.numberCorrect++;             } else {                 appState.results.numberWrong++;             }             selectAndDisplayNote();         }     } })();`

使用 WinJS 数据绑定

保持元素的内容与应用的状态同步可能是一个乏味且容易出错的过程。有两个问题:第一个是简单地确保所有的 HTML 元素在用户与应用交互时显示正确的数据。第二个问题是确定哪个元素是特定数据的权威来源——如果用户可以使用一系列不同的元素编辑相同的数据值,这是很困难的。

web 应用和 Windows 应用的解决方案是一样的:数据绑定。数据绑定将数据值保存在 JavaScript 代码中,并提供自动同步数据变化和显示给用户的 HTML 元素内容的机制。

我最喜欢的 web 应用数据绑定包是由我偶尔的合作者和全面的好人史蒂夫·桑德森编写的。我在我的《网络应用 JavaScript》一书中广泛使用了 Knockout,它是一个非常好的工具。您可以在 JavaScript Windows 应用中使用 Knockout 或其任何竞争对手,但 WinJS API 中内置了对数据绑定的支持。

WinJS 数据绑定支持不像一些 web 应用库那样功能丰富,但我还是会使用它——不仅因为它是 Windows API 的一部分,还因为它集成到了我在本书第三部分中描述的一些 UI 控件中。我在这一章给你一个关于绑定的简要概述,这样我就可以介绍这个主题并让NoteFlash应用工作起来。我在第八章中再次深入讨论这个话题。

定义数据

对于数据绑定,我首先需要一些数据,表达这些数据的最简单方式是使用一个对象,就像这样:

... var appState = **WinJS.Binding.as**({     title: "", mode: null, leftNote: "=",     rightNote: "=", currentIndex: 0, noteCount: 0,     notes: [], currentNote: null,     results: {         numberCorrect: 0,         numberWrong: 0     }, }); ...

这个代码片段最重要的部分是对WinJS.Binding.as方法的调用,它将一个对象作为参数。我传递给as方法的对象包含了代表我的应用当前状态的所有数据。通过将这个对象传递给as方法,我可以观察到对象中的数据值

可观察值是动态数据绑定所必需的,其中数据项和 HTML 的内容或属性值总是同步的,这就是我将在NoteFlash应用中使用的数据绑定类型。现在不要太担心数据——当你看到绑定系统的其他部分就位时,它会开始变得更有意义。

声明绑定

数据必须被绑定到某个东西,对于这个应用,绑定关系的另一面出现在 HTML 标记中。下面是来自flashCardsPage.html文件的一个例子:

... <span **data-win-bind="innerText: results.numberCorrect"**></span> ...

这是一个声明性绑定,这意味着我已经在 HTML 代码中包含了绑定的细节。为了创建这个绑定,我向元素添加了data-win-bind属性。

images 提示data-win-*绑定用于表示 WinJS 功能。以data-开头的属性称为数据属性。一段时间以来,它们一直是将自定义属性应用于元素的一种非正式方式。HTML5 规范使数据属性成为 HTML 的正式组成部分。

这个绑定属性的值由两部分组成。第一部分是绑定所应用到的 DOM 中的HTMLElement对象的属性名——在本例中是innerText属性,它控制元素的文本内容。属性名后跟一个冒号(:),然后是将绑定到该属性的数据值的名称。

在这种情况下,我选择了results.numberCorrect作为数据值。该属性的结果是span元素的innerText属性被绑定到results.numberCorrect属性的值。

将数据应用于绑定

剩下的步骤是将数据和声明性绑定放在一起。上一节中我的span元素知道它需要results.numberCorrect属性的值,但是它不知道如何获得该值。WinJS.Binding.as方法创建可观察的数据对象,但是它不知道这些值应该显示在哪里。我需要使用另一个WinJS方法连接数据和声明,如下所示:

`... WinJS.UI.Pages.define("/pages/flashCardsPage.html", {     ready: function (element, options) {

**        WinJS.Binding.processAll(document.body, appState);**

        backButton.addEventListener("click", function (e) {             showPage("/pages/selectorPage.html");         });         $("#noteButtons button").listen("click", handleButtonClick);

        setState(options);         selectAndDisplayNote();     } }); ...`

WinJS.Binding.processAll方法有两个参数:DOM 中的一个元素和一个数据对象。该方法处理指定元素的所有后代,并将数据对象设置为任何具有data-win-bind属性的元素的数据源。在我的示例应用中,我已经指定了document.body元素——以便处理整个布局——以及我之前创建的可观察的appState对象。

这缩小了数据和声明性绑定之间的差距。您可以看到,这个方法是我在处理函数中为WinJS.UI.Pages.ready事件调用的第一个方法。我喜欢尽快设置我的绑定,但这只是个人偏好。您可以在任何合适的时候调用processAll方法,但是如果您在 HTML 中使用数据绑定,那么调用这个方法是很重要的——否则绑定不会被激活,您的数据值也不会被 HTML 元素显示。

配置按钮和导航

当 Visual Studio 创建一个新页面时,它会在 HTML 标记中包含一个导航Back按钮,如下所示:

... <button id="backButton" class="win-backbutton" aria-label="Back"></button> ...

这是 Windows 应用中常见的布局功能,允许用户在应用中后退一步。当我更新flashCardsPage.html文件的内容时,我保留了这个元素。元素被赋予的 CSS 类win-backbutton,在ui-dark.cssui-light.css文件中定义,我在第二章中介绍过,它们定义了 Windows 应用的所有基本样式。在图 4-3 中,您可以看到按钮是如何显示的。

images

***图 4-3。*后退按钮

你不必在你的应用布局中有这个button,但是我将保留它,以允许用户返回到选择页面。处理 Windows 应用中的按钮就像处理 web 应用中的按钮一样,您可以在下面的flashCardsPage.js文件中的ready事件处理程序片段中看到这一点:

... backButton.addEventListener("click", function (e) {     showPage("/pages/selectorPage.html"); }); ...

我使用addEventListener方法注册一个函数来处理click事件。Windows 应用有一些微软特有的事件,我在第十八章中描述过,但是标准的click事件工作得非常好,可以响应鼠标和触摸事件。当按钮被点击时,我调用我的全局showPage函数(您还记得,我在default.js文件中定义了它)来显示selectorPage.html文件。

配置应答按钮

页面上的其他按钮允许用户识别显示的每个注释。在图 4-4 中可以看到这些按钮。

images

***图 4-4。*音符识别按钮

我想以同样的方式处理所有这些按钮的click事件,如下所示:

... $("#noteButtons button").listen("click", handleButtonClick); ...

我使用了我在default.js文件中为WinJS.Utilities.query方法设置的$别名。我可以将listen方法应用于来自query方法的结果,为所有匹配元素的事件注册相同的处理函数——在本例中,为所有按钮的click事件注册handleButtonClick函数。(我将在本章后面描述handleButtonClick函数是如何工作的。)

设置状态

Windows 应用单页模型中的一些奇怪之处意味着每次收到ready事件时重置状态是很重要的。我会在第五章中解释原因。在NoteFlash应用中,我创建了一个名为setState的专用设置功能,如下:

`... function setState(mode) {

    appState.mode = mode;     switch (mode) {         case "leftHand":             appState.title = "Left Hand Notes";             break;         case "rightHand":             appState.title = "Right Hand Notes";             break;         case "bothHands":             appState.title = "All Notes";             break;     }

    appState.notes = [];     if (mode == "leftHand" || mode == "bothHands") {         Notes.leftHand.slice().forEach(function (item) {             appState.notes.push(item);         });     }     if (mode == "rightHand" || mode == "bothHands") {         Notes.rightHand.slice().forEach(function (item) {             appState.notes.push(item);         });     }     appState.currentIndex = 0;     appState.results.numberCorrect = 0;     appState.results.numberWrong = 0;     appState.noteCount = appState.notes.length; } ...`

该函数的参数是由ready事件处理程序接收的值,该值指示用户想要测试哪组音符:左手音符、右手音符,或者两者都测试。我根据这个值在数据对象中设置了appState.title属性的值,这触发了对一个 HTML 数据绑定的更新:

`...

...`

该函数的下一部分清除appState.notes数组,并用来自Notes名称空间(我在本章前面创建的)的值重新填充它。在appState.notes数组中的最后一组音符取决于用户在selectorPage中点击了哪个按钮。

重置其他状态值

该函数的其余部分重置appState对象中的剩余值。所有这些值都在数据绑定中使用:

`...

    1 of     1     (      Correct/      Wrong)                    

...`

重置这些属性的值具有清除应用状态和重置用户界面的双重效果。如果没有数据绑定,我将不得不手动重置元素的内容,确保找到显示每个数据值的所有实例——对于像这样的基本应用来说,这相当简单,但对于更复杂的应用来说,这很快就会变成一个痛苦且容易出错的过程。当我更深入地回顾数据绑定并介绍视图模型时,我将在第八章回到这个主题。

每当我接收到WinJS.UI.Pages.ready事件时,我就调用setState函数,如下所示:

... WinJS.UI.Pages.define("/pages/flashCardsPage.html", {     ready: function (element, options) {         // *...other statements removed for brevity...* **        setState(options);**         selectAndDisplayNote();     } }); ...

这确保我在每次显示页面时重置应用状态并清除数据绑定值。

展示抽认卡

selectAndDisplayNote函数负责从appState.notes数组包含的集合中随机选取一个音符并显示给用户:

... function selectAndDisplayNote() {     if (appState.notes.length > 0) {         var index = Math.floor((Math.random() * appState.notes.length));         var note = appState.notes.splice(index, 1)[0];         appState.leftNote = (note.hand == "left" ? "&#"             + note.character + ";" : "=");         appState.rightNote = (note.hand == "right" ? "&#"             + note.character + ";" : "=");         appState.currentNote = note;         appState.currentIndex++;     } else { **        $("#noteButtons button").forEach(function (item) {** **            item.style.display = "none";** **        });** **        $("#noteButtons button[id]").setAttribute("style", "");**     } } ...

这是使用标准 JavaScript 代码实现的。如果没有留下测试用户的注释,那么我会修改布局——我用粗体标记的代码。这些语句隐藏答案按钮,并显示附加的导航按钮。您可以在图 4-5 中看到替代按钮组。注意,我已经使用了WinJS.Utilities.query方法,之前我将其别名化为$,使用id属性值来定位button元素。

images

***图 4-5。*附加导航按钮

我将这些按钮定义在常规的回答按钮旁边,但是将 CSS display属性的值设置为none,所以它们最初是不可见的。我这样定义按钮是为了再次强调标准的 HTML 和 CSS 特性在 Windows 应用中是如何可用的:

`...

    C     D     E     F     G     A     B **    Back** **    Again**
...`

作为一个相关的好处,所有的按钮——回答和导航——都与我使用别名$搜索的元素相匹配,并被配置为当它们被点击时调用handleButtonClick函数。

处理回答和导航按钮事件

应用功能的最后一部分包含在handleButtonClick功能中,当点击任何一个回答按钮或附加导航按钮时,就会执行该功能:

`... function handleButtonClick(e) {     if (this.id == "restart") {         showPage("/pages/flashCardsPage.html", appState.mode);     } else if (this.id == "back") {         showPage("/pages/selectorPage.html");     } else { **        ("button[datanote].correct").removeClass("correct");        ("button[data-note].correct").removeClass("correct");** **        ("button[data-note=" + appState.currentNote.note + "]")** **            .addClass("correct");**

**        if (this.innerText == appState.currentNote.note) {** **            appState.results.numberCorrect++;** **        } else {** **            appState.results.numberWrong++;** **        }**         selectAndDisplayNote();     } } ...`

该函数在很大程度上使用了标准的 JavaScript 技术,但是有几个方面值得您注意,我将在下面的部分中进行描述。请注意我是如何在回答按钮中添加和删除 CSS 类的:

... $("button[data-note]").**removeClass("correct").removeClass("normal")**; $("button[data-note=" + appState.currentNote.note + "]")     **.addClass("correct").addClass("normal").removeClass("correct")**; ...

CSS 类将绿色背景应用到一个按钮上。当用户点击一个按钮时,我从应用它的任何按钮中删除正确的类,然后将它重新应用到当前显示的便笺的正确按钮上。这允许我创建一个简单的视觉提示来指示正确的答案。

依靠数据绑定发布数据更新

我想提醒您注意动态数据绑定的使用方式:

... if (this.innerText == appState.currentNote.note) { **    appState.results.numberCorrect++;** } else { **    appState.results.numberWrong++;** } ...

每当我评估用户提供的答案时,我就增加appState对象中的数据值,记录正确和错误答案的数量。

重要的是我必须做的事情:我不必手动更新 HTML 元素来显示更新的信息。这是自动发生的,因为我更新的值是可观察的,并且布局中的元素通过数据绑定系统显示数据。

这不仅是一种更方便的方法,而且还创建了一个更具可伸缩性和可维护性的应用结构。我可以更改 HTML 元素的布局,并且可以在应用的许多地方显示相同的数据值——但是当我需要进行更新时,我必须只给数据对象分配一个新值,就像在代码片段中一样。这在任何有 UI 的应用中都是一个重要的概念,但在 Windows 应用开发中尤其重要,我将在第八章中回到这个主题。

你可以在图 4-6 中看到完整的应用,显示了正在向用户显示的笔记,以及关于正在测试哪组笔记的信息,用户的进度,以及指示正确答案的颜色提示。

images

***图 4-6。*完成的 NoteFlash 应用

正如我之前提到的,这不是NoteFlash应用的最终版本,但基本功能已经完成,用户可以测试他的视奏能力。在后面的章节中,我将回到这个应用并添加更多的功能。

更新应用清单

虽然我已经完成了应用的 HTML、CSS 和 JavaScript 组件,但还有一点工作要做。如果你转到Start屏幕并找到NoteFlash应用的磁贴,你会发现应用呈现给用户的方式相当简单,如图图 4-7 所示。

images

***图 4-7。*note flash 应用的默认磁贴

默认情况下,Visual Studio 会为应用磁贴分配一个默认图标,并使用项目作为应用名称。我们可以通过对清单进行一些简单的更改来改善应用呈现给用户的方式。

双击Solution Explorer窗口中的package.appxmanifest文件,并导航到清单编辑器中的Application UI选项卡。此页面包含应用的基本设置。

设置平铺图像和颜色

我要做的第一个改变是应用磁贴的图像和颜色。Windows 应用出于各种目的使用不同大小的图像。有一个正方形拼贴的图像,如图 3-7 所示,它必须是 150 像素乘 150 像素。

平铺也可以是宽的(右击平铺并选择Larger按钮),这需要一个 310 像素乘 150 像素的图像。有一个小图标,30 x 30 像素,当应用显示为列表的一部分时使用,最后,一个 620 像素 x 300 像素的图像,当应用启动时用作启动屏幕。

对于这个应用,我创建了一系列图像,并将它们放在 Visual Studio 项目的images文件夹中。这些文件显示高音谱号符号,但由于我使用了白色图标和透明背景,我无法在打印页面上显示图像,但当它应用到应用时,您将能够看到它们。(当然,它们在本章的源代码下载中。)

在清单编辑器的Application UI选项卡中,有许多文本字段,这样你就可以为图块指定图像,如图 4-8 中的所示。

images

***图 4-8。*设置磁贴的图像

我的图像文件的名称以谱号开头,然后详细说明分辨率,例如clef30x30.png。您可以看到我是如何为清单中的各个字段设置图像名称的。图像没有缩放,如果图像大小不合适,您将无法使用它。

还要注意,我已经为Background color选项设置了一个值。这用于设置瓷砖的颜色——对于这个应用,我选择了蓝色的阴影,由十六进制代码#528FC8指定。

images 提示虽然图中没有显示,但是我也设置了闪屏的图像。

设置应用名称

我还想更改显示在磁贴上的名称,该名称取自清单中的Display name字段。我已经把这个改成了Note Flash,如图图 4-9 所示。

images

***图 4-9。*更改应用的显示名称

我还更改了Description字段,它为用户提供了应用的摘要。更改显示名称和应用图像的结果是如图 4-10 所示的磁贴,为用户呈现一个更加完美的应用磁贴。

images 提示这是一个静态磁贴,只显示图片和应用名称。在第二十七章的中,我将向你展示如何创建动态磁贴,向用户显示有用的信息。

images

***图 4-10。*note flash 应用的更新磁贴

测试完成的应用

这就是NoteFlash应用的全部内容。这是一个简单的软件,但它展示了 Windows 应用开发的一些基本特征和功能。要提高您的基本音符识别技能,只需启动应用,选择您想要测试的音符,然后给出您的答案。在本书每一部分的结尾,我将回到这个应用,并使用我在前一章描述的 Windows 特性对它进行改进。在我继续之前,我想回顾一下本章和第三章的关键主题。

基于网络技术的 Windows 应用

我怎么强调这一点都不为过:如果你正在使用 HTML 和 JavaScript 开发 Windows 应用,那么你就是在利用你已经掌握的常规 web 应用开发技能。有一些与 HTML 世界相当大的差异,特别是当它与一些高级 Windows 8 功能集成时,但正如 NoteFlash 应用所展示的那样,你可以使用标准的 web 技术和技巧完成很多事情。

Windows 应用不是网络应用

尽管你的 web 应用开发经验非常有用,但如果不使用平台功能和遵循微软惯例,你就无法发挥应用的全部潜力。你已经看到了 Windows 应用不同于 web 应用的关键领域——在核心导航模型中——我将在本书的其余部分向你展示更多。

数据绑定简化了应用开发

我是 web 和 Windows 应用中数据绑定的忠实粉丝,我在第八章中深入讨论了这个主题。Windows 应用可能会变得非常复杂,您应该采用一切可能的技术来保持代码和标记的可管理性,包括数据绑定。如果你愿意,你可以手动设置 HTML 元素的内容,但是你会让自己的日子更难过,尤其是在维护或增强你的应用的时候。

WinJS API 是用 JavaScript 写的

WinJS API 是常规 web 应用和 Windows 应用开发之间的桥梁。最重要的是,您可以通读 WinJS 代码,了解微软如何实现不同的功能,并应用调试器来跟踪困难的问题。对于 Windows API 来说,情况并非如此,但是随着您对 Windows 应用开发的掌握,您会发现您大部分时间都在使用 WinJS 特性。

总结

在这一章中,我向你展示了如何完成 NoteFlash 应用的功能,同时,也介绍了更多的核心 WinJS 功能。特别值得注意的是 WinJS 对类、名称空间和数据绑定的支持。既然您已经看到了如何创建一个基本的 Windows 应用,那么是时候开始深入研究细节了。在本书的第二部分,我展示了 WinJS API 的核心特性,你可以用它来创建一个好的 Windows 应用的结构。

五、单页模型

当我创建NoteFlash应用时,我简要介绍了单页导航模型的思想。在这一章中,我将深入探讨这个话题。单页模型背后的基本思想是,有一个 HTML 页面(通常称为母版页或主页)总是向用户显示,并负责在应用状态改变时将其他内容导入其结构。

这个模型为允许用户浏览你的应用提供了基础。您将内容导入到主页中以响应用户输入和交互,通常会替换以前显示的内容。WinJS API 提供了导入和显示应用内容以及在其中导航所需的工具和功能。在这一章中,我将向您展示如何执行这些操作,并解释处理导航操作的不同模型。这不是最令人兴奋的话题,但单页模型是 Windows 应用的核心,正确地使用它会使应用开发的其他方面变得更简单和容易——这可能有点枯燥,但值得关注。表 5-1 对本章进行了总结。

images

创建单页项目

为了演示支撑单页模型的技术,我使用Blank Application模板创建了一个名为SinglePageNav的示例 Visual Studio 项目(这是我在第二章中为NoteFlash应用使用的同一模板,也是我在本书中使用的模板)。对于我导入其他内容的基础,我将使用 Visual Studio 创建的default.html文件。你可以在清单 5-1 中看到default.html的起点。

清单 5-1 。SinglePageNav 项目中 default.html 文件的初始版本

`

         SinglePageNav

                   

              

    
        
            

Content Controls

            Button One             Button Two         

        

            

Top Right

            
This is part of default.html
            
                This is where the content will go             
        

        

            

Bottom Right

            
This is part of default.html
            
                This is where the content will go             
        
    

`

定义该应用布局结构的三个元素是 id 为lefttopRightbottomRightdiv元素。left元素包含一些button元素,我将在本章后面使用它们来导入内容以响应用户输入。topRightbottomRight元素提供了显示导入内容的结构。

定义初始 CSS

我使用 CSS 网格布局来定位元素,使用我在css/default.css文件中定义的样式,你可以在清单 5-2 中看到。

清单 5-2 。default.css 文件

`#gridContainer {     height: 100%; **    display: -ms-grid;** **    -ms-grid-columns: 1fr 1fr;** **    -ms-grid-rows: 1fr 1fr;** }

#left { **    -ms-grid-row-span: 2;**     background-color: black;     padding: 10px; }

#topRight { **    -ms-grid-column: 2;**     background-color: #617666; }

#bottomRight { **    -ms-grid-column: 2;** **    -ms-grid-row: 2;**     background-color: #767676; }

div.contentBlock {     border: medium solid white;     padding: 5px; margin: 2px;     }

div.message {     font-size: 30px; }

button {     font-size: 30px; margin: 10px; }

#frameTarget {     width: 100%;     height: 100%; }`

我强调了与网格布局相关的 CSS 属性。这些属性创建一个两行两列的网格。left元素横跨两行,由于我没有明确指定位置,所以它会在第一列和第一行。topRightbottomRight元素在第二列,每行一个。你可以在图 5-1 中看到网格布局的效果。

images

***图 5-1。*样板工程的初始布局

定义初始 JavaScript

我在js/default.js文件中的初始 JavaScript 代码如清单 5-3 所示。除了为WinJS.Utilities.query方法创建我喜欢的$别名之外,代码还在名为leftdiv元素中找到了button元素,并为click事件注册了一个回调函数。

清单 5-3 。js/default.js 文件的初始内容

`(function () {     "use strict";

    var app = WinJS.Application;     window.$ = WinJS.Utilities.query;

    app.onactivated = function (eventObject) { **        $('#left button').listen("click", function (e) {** **            // button handler code will go here** **        });**     };     app.start(); })();`

我将在本章的后面添加处理click事件的代码。

以声明方式导入内容

将内容引入单页应用的最简单方法是以声明的方式进行,这仅仅意味着将一种机制应用于default.html文件中的 HTML 元素。清单 5-4 展示了如何将声明性技术应用到示例应用布局中的一个容器元素中。

清单 5-4 。以声明方式导入内容

`...

    

Top Right

    
This is part of default.html
    
        This is where the content will go     
...`

WinJS API 的主要部分是一组 UI 控件。这些是应用于标准 HTML 元素的增强,以提供特定于应用的功能。为了以声明方式将内容导入到我的应用中,我使用了一个非常基本的控件HtmlControl

我将HtmlControl应用到div元素中我想要插入导入内容的地方。应用控件就是将data-win-control属性设置为 WinJS API 中控件对象的全名,也就是WinJS.UI.HtmlControl

我必须配置控件,以指定我想要导入的文件的名称。格式是包含一个 JSON 片段,其中的uri属性定义了文件名,在本例中是contentBasic.html。我不喜欢像这样将 JSON 嵌入到 HTML 中,但是声明式导入需要它。

images 提示在这一章中,我不会深入探究 WinJS UI 控件的机制。我在本书的第三部分中详细介绍了它们。

最后一步是激活控件,这需要一个 JavaScript 方法调用。这在一定程度上破坏了这个示例的声明性,但是这是一个激活您添加到 HTML 中的任何控件的调用。你可以在清单 5-5 的中看到所需的方法调用,我已经将它添加到了default.js中。

清单 5-5 。激活 HTML 标记中的控件

(function () {     "use strict"; `var app = WinJS.Application;     window.$ = WinJS.Utilities.query;

    app.onactivated = function (eventObject) {         $('#left button').listen("click", function (e) {             // button handler code will go here         }); **        WinJS.UI.processAll();**     };

    app.start(); })();`

Windows 运行时不会自动搜索 DOM 来查找带有data-win-control属性的元素。我必须显式地请求这个搜索,这就是WinJS.UI.processAll方法所做的。最后一步是定义我想要导入的内容。清单 5-6 显示了contentBasic.html文件的内容。

images 提示我创建了 contentBasic.html 文件,方法是在解决方案浏览器窗口中右键单击项目条目,从弹出菜单中选择Add New Item,并使用HTML Page项模板。这个模板创建一个 HTML 页面。这不同于我之前使用的Page Control项目模板,它创建了 HTML 文件、CSS 文件和 JavaScript 文件(并在 HTML 文件中添加了linkscript元素)。Page Control模板只是为了方便,并没有赋予它创建的文件任何特殊属性。事实上,我更喜欢在需要时单独创建文件,很少在自己的项目中使用Page Control模板。

清单 5-6 。contentBasic.html 档案

`

             Basic Content                      div.message {                 font-family: serif;                 text-align: right;             }                            
            Hello from the contentBasic.html file         
     `

这是一个简单的 HTML 文件,包含一个内嵌的style元素和一些基本的 HTML。如果您启动示例应用,您将看到声明式导入的效果,如图图 5-2 所示。我在图中突出显示了导入的内容。

注意,已经在div元素中的内容仍然存在。导入内容不会替换任何现有的子元素——如果您只想要导入的内容,就必须显式删除目标元素的内容(我很快就会这么做)。

images

***图 5-2。*以声明方式导入内容

了解导入机制

如果你运行这个例子或者查看图 5-2 ,你会注意到右栏中的所有文本现在都对齐到了父元素的右边,而之前它是对齐到左边的(如图图 5-1 所示)。发生这种情况是因为内容导入应用时的处理方式。

这个例子使用了HtmlControl,但是我在本章后面描述的 Pages 特性也发生了同样的事情。为了理解发生了什么,考虑示例应用中的 CSS。default.css文件包含了message类的样式,如下所示:

... div.message {     font-size: 30px; } ...

contentBasic.html文件包含一个style元素,message类定义了一个样式:

... div.message {     font-family: serif;     text-align: right; } ...

导入文件时,它包含的script元素被添加到主 HTML 文档的head元素中。Windows 应用中的 CSS 遵循与 web 应用相同的优先级规则。这意味着应用于message类中元素的样式是来自两个文件的组合属性集,并且由于script元素被添加到主文档的head中,这个组合属性集将影响所有的message元素,而不仅仅是那些已经导入的元素。

images 提示 CSS 优先考虑属性定义的顺序。这意味着导入内容中的属性会覆盖主文件中定义的属性。

确保您的样式只影响一个文档中的元素的最简单方法是确保您缩小 CSS 样式的范围,使它们只影响导入的元素。你可以在清单 5-7 中看到我是如何为contentBasic.html做这些的。

清单 5-7 。缩小对导入内容中 CSS 样式的关注

`

             Basic Content                      **#contentBasicTop** div.message {                 font-family: serif;                 text-align: right;             }                    **        
**             
                Hello from the contentBasic.html file             
**        
**      `

我添加了一个div元素,作为将要导入到文档中的元素的父元素。div不会改变导入内容的外观或结构,但它允许我缩小样式的关注范围,这样它们就不会泄露到布局的其他部分。你可以在图 5-3 的中看到这种变化的效果。注意来自default.html文件的内容不再受右对齐的影响。

images

***图 5-3。*缩小导入内容中 CSS 样式焦点的效果

以编程方式导入内容

声明性的HtmlControl是导入内容的最简单的方式,但是这种简单性带来了一些限制。第一个问题是一旦内容被加载,就没有办法改变它——使用 JavaScript 来改变data-win-options属性的值不会加载新的内容。第二个限制是,如果您导入包含script元素的内容,您几乎肯定会遇到问题——HtmlControl只能可靠地处理简单的静态内容,就像我在前面的例子中使用的那种内容。

JavaScript 的问题取决于代码本身。导入内容时,script元素的处理方式与style元素相同,并被添加到主文档的head元素中。一旦script元素的内容被插入到head元素中,就会被执行,这发生在 HTML 元素被导入之前。由于您想要操作的元素尚不存在,代码将会失败。您不能依赖 DOM 事件或类似 jQuery ready方法的技巧,因为底层事件是在 Internet Explorer 加载主控文档时触发的。当内容被导入时,浏览器已经触发了它的就绪事件并继续前进。

只对主文档中已经存在的元素进行操作的 JavaScript 代码可以工作,但是将这种代码放入要导入的文件中是违反直觉的。这样做在主布局和导入的代码之间创建了一个紧密耦合——我将在下一节更详细地讨论这一点,但这通常不是一个好主意。

这并不意味着声明性地使用HtmlControl没有用,但这是非常基本的。如果你想把你的应用布局分成可管理的部分,并在运行时加载它们,那么声明式的HtmlControl是完美、简单和可靠的。如果您想要更复杂的东西,那么您应该看看可编程的替代方案,我将在下一节对此进行描述。

以编程方式使用 HtmlControl

灵活性的下一步是以编程方式使用HtmlControl。这就改变了对HtmlControl的使用,使其全部发生在 JavaScript 代码中,而不再嵌入到 HTML 中。现在,我必须说我在我的项目中没有以这种方式使用HtmlControl——它缺乏声明式方法的简单性,也没有我稍后描述的页面特性的灵活性。我向您展示这项技术的主要原因是为了演示一个常见的陷阱:我提到的紧耦合概念。让我从展示编程用法开始。首先,我需要重置我在default.html文件中的 HTML 元素来移除声明性属性,如清单 5-8 所示。

清单 5-8 。删除声明性 HtmlControl 属性

`...

    

Top Right

    
This is part of default.html
    
        This is where the content will go     
...`

我现在可以在我的 JavaScript 代码中添加语句到default.js文件中,以编程方式使用HtmlControl,我在清单 5-9 中就是这么做的。

清单 5-9 。以编程方式使用 HtmlControl】

`(function () {     "use strict";

    var app = WinJS.Application;     window.$ = WinJS.Utilities.query;

    app.onactivated = function (eventObject) {         ('#left button').listen("click", function (e) {` `**            var targetElem;** **            if (this.id == "button1") {** **                targetElem = ('#topRight div.contentTarget')[0];** **            } else {** **                targetElem = $('#bottomRight div.contentTarget')[0];** **            }** **            WinJS.Utilities.empty(targetElem);** **            new WinJS.UI.HtmlControl(targetElem, {uri: 'contentBasic.html'});**         });         WinJS.UI.processAll();     };     app.start(); })();`

我已经为button控件的click处理函数添加了新代码。每个按钮在文档中定位一个不同的目标元素,并将它赋给targetElem变量。

images 注意这是内容导航的基本形式,因为我为用户提供了一种改变布局组成的方式。在 Windows 应用中使用常规的 HTML 元素和事件进行导航是完全允许的,但是也有一些特定于应用的 UI 控件和 API 专用于导航,我在第七章的中对此进行了描述。

我使用WinJS.Utilities.empty方法删除目标元素中的现有内容,然后创建一个新的HtmlControl对象来导入contentBasic.html文件。一个HtmlControl对象的两个构造函数参数是目标 HTML 元素和一个包含配置信息的对象,配置信息的格式与我在上一节中声明的格式相同。您可以使用标准的 DOM API 方法(比如document.getElementById),使用将id属性值视为变量的 IE 特性,或者像我在本例中所做的那样,使用WinJS.Utilities.query方法来获得目标元素。这是我别名为$的方法,它返回一个匹配 CSS 查询字符串的元素数组,即使只有一个匹配元素。这就是为什么我必须通过将[0]附加到方法调用来提取我想要的元素。

images 提示以编程方式创建的HtmlControl的配置信息是一个对象,而不是一个 JSON 字符串。你必须记住不要把论点用引号括起来。

这些更改的结果是,在单击左侧面板中的某个按钮之前,不会导入任何内容。两个按钮导入相同的内容——contextBasic.html文件——但是内容导入到的元素不同。这些动作相互独立工作,也就是说如果你同时点击两个按钮,contentBasic.html文件的内容将被导入到文档中的两个位置,如图图 5-4 所示。

images

***图 5-4。*以编程方式将相同的内容导入两个位置

使用HtmlControl以编程方式解决了声明性使用的一些不足。首先,我获得了对何时导入内容的控制权——在本例中,我在响应按钮点击时导入内容。其次,我可以通过删除目标元素中存在的任何内容并创建另一个HtmlControl对象来更改显示的内容。这是一个很大的进步,它让我可以将我的应用分解成可管理的块,我可以按需组合并显示给用户,根据应用的变化状态重用部分布局来导入和显示内容。

进口互动内容的风险

即使以编程方式使用,HtmlControl也不能以有效的方式处理script元素——它们仍然被添加到主文档的head元素中,并在导入常规 HTML 元素之前执行。然而,HtmlControl对象构造函数接受一个回调函数的可选参数,该函数将在内容导入后执行。这提供了我以前没有的定时信号,允许我创建可以对新添加的元素进行操作的代码。至少,最初看起来是这样——但这是一个陷阱,需要谨慎。我将介绍回调函数的使用,并向您展示它所产生的问题。首先,我需要一些需要 JavaScript 才能有用的内容。清单 5-10 显示了我添加到根项目文件夹中的一个新文件的内容,名为contentButton.html

清单 5-10 。contentButton.html 档案

`

             Button Content                   
` `            
                Press Me             
        
     `

这是一个包含一个button元素的普通 HTML 文件。这给我带来了一个问题,因为我需要设置一个处理函数,这样我就可以响应button的事件。在清单 5-11 中,你可以看到我是如何使用default.js文件中的第三个HtmlControl参数来提供一个找到按钮并绑定它的函数的。

清单 5-11 。使用 HtmlControl 回调函数处理导入的元素

`(function () {     "use strict";

    var app = WinJS.Application;     window.$ = WinJS.Utilities.query;

    function loadLowerContent() {         var targetElem = $('#bottomRight div.contentTarget')[0];         WinJS.Utilities.empty(targetElem);         new WinJS.UI.HtmlControl(targetElem, { uri: "contentBasic.html" });     }

    app.onactivated = function (eventObject) {         ('#left button').listen("click", function (e) {             var targetElem = ('#topRight div.contentTarget')[0];             WinJS.Utilities.empty(targetElem);

**            new WinJS.UI.HtmlControl(targetElem, { uri: "contentButton.html" },** **                function () {** **                    contentButton.addEventListener("click", loadLowerContent);** **                });**         });         WinJS.UI.processAll();     };     app.start(); })();`

我通过id属性值定位button元素,并使用addEventListener方法设置一个事件处理程序。

这一切都如你所料:如果按下应用布局左侧的按钮,就会加载contentButton.html文件,其中包含的元素就会添加到布局中。然后调用我的回调函数,并配置我的按钮。要查看效果,启动应用并单击左侧面板中的Button One。您将看到contentButton.html文件的内容被导入到右上角的面板中,包括button元素。点击新导入的buttoncontentBasic.html文件的内容被导入到右下面板。

这种方法有两个问题。一个是可见的,并且相当容易分类。另一个是无形的,但更重要,需要时间和注意力来理解和解决。

了解 CSS 问题

可见的问题是我前面描述的 CSS 范围问题的变体。我想重温一下,因为它强调了在导入内容时缩小 CSS 选择器范围的重要性。

如果您运行示例应用并单击Button One,则会加载contentButton.html文件。出现的Press Me按钮与其父容器的左边缘对齐。如果点击Press Me按钮,contentBasic.html文件被导入到布局中,Press Me按钮的对齐被移动到父项的右侧。你可以在图 5-5 中看到效果,图中显示了点击button前后的 app。

images

***图 5-5。*由进口 CSS 驱动的按钮延时动作

这种变化的原因是在contentBasic.html文件中为message样式定义的附加属性。contentButton.html文件中的button包含在message类的div元素中,两个文件共享相同的元素结构和命名模式。当导入contentBasic.html文件时,我定义的 CSS 样式比我预期的应用得更广泛。

事实上,问题是由一系列交互触发的,这使得在开发和测试过程中很难发现问题(这种性质的大多数问题比按钮突然移动位置更加微妙)。

理解紧耦合问题

更严重的问题是,我在default.jscontentButton.html文件之间创建了一个紧密耦合。紧密耦合简单地说就是对一个应用中一个组件的更改将要求我在其他地方做一个或多个更改——也就是说,default.js文件中的代码依赖于contentButton.html文件的内容。因此,例如,如果我更改了contentButton.html文件中button元素的id或者用不同种类的元素替换它,我必须更新default.js文件,以便HtmlControl回调函数也反映这些更改。

这是一个比听起来严重得多的问题。对于一个简单的示例应用,管理紧密耦合的组件之间的依赖关系所带来的额外工作并不多。但是对于一个真正的应用,有真正的用户,真正的时间表和真正的测试计划,这就成了一个严重的负担。每次更改都需要跟踪应用中所有受影响的地方,正确应用更新,然后测试整个应用。这是一个非常痛苦的过程,并且对软件质量和开发人员的生产力有着非常不利的影响,因此在可能的情况下避免紧密耦合是非常重要的。

对于我的例子,我需要从default.js中删除关于contentButton.html文件的内容和结构的知识。这意味着使contentButton.html成为一个独立的单元,default.js可以像黑盒一样对待它,这意味着修复我前面解释的导入的script元素执行问题,以便导入的内容可以包含代码。接下来,我将解释 WinJS Pages 特性如何提供我正在寻找的解决方案。

使用 WinJS 页面功能

WinJS Pages 特性提供了解决内容导入问题所需的工具,而不会产生耦合问题。因为 Pages 特性解决了 JavaScript 计时问题,所以它适用于导入所有类型的内容。这对于将应用分成可管理的静态内容块以及创建对导入文档中的元素进行操作的 JavaScript 代码非常有用。这种灵活性是以复杂性为代价的——没有对 Pages 特性的声明性支持,导入内容至少需要两步。正如您将看到的,第一步需要添加代码来导入内容。这发生在default.js文件中。第二步是将代码添加到正在导入的文件中,遵循特定的模式,这样当导入的元素被添加到 DOM 中时,我会得到通知。

Pages 特性的功能包含在WinJS.UI.Pages名称空间中。在接下来的小节中,我将向您展示如何使用这个名称空间来实现导入过程(以及一个可选的步骤,它可以使以特定的顺序导入内容变得更加容易)。

导入内容

当然,第一步是通过WinJS.UI.Pages.render方法导入内容。对此没有声明性支持,因此任何内容导入都依赖于render方法。你可以在清单 5-12 中看到对default.js文件的修改,以使用渲染方法。

清单 5-12 。使用 WinJS。导入内容的 UI.Pages.render 方法

`(function () {     "use strict";

    var app = WinJS.Application;     window.$ = WinJS.Utilities.query;

    app.onactivated = function (eventObject) {         ('#left button').listen("click", function (e) {             var targetElem = ('#topRight div.contentTarget')[0];             WinJS.Utilities.empty(targetElem);

**            var buttonTargetElem = $('#bottomRight div.contentTarget')[0]**

**            WinJS.UI.Pages.render("contentButton.html", targetElem,** **                { content: "contentBasic.html", target: buttonTargetElem });**         });         WinJS.UI.processAll();     };     app.start(); })();`

render方法的前两个参数指定要导入的内容和内容将插入的目标元素。第三个参数是可选的,更有趣。此参数允许您指定任意数据对象,该对象可用于导入内容中的代码。这是一个很好的特性,因为它允许您创建复杂的功能块,您可以通过传入不同的数据值以不同的方式重用这些功能块。数据对象没有要求的格式,您可以将任何内容从简单的字符串传递到复杂的对象。在这个例子中,我使用了一个对象,它的属性指定了contentButton.html文件中的按钮所需的信息——单击按钮时应该导入的内容和应该插入内容的元素。

注册回拨

Pages 功能不会改变内容的加载方式。导入文件中的任何script元素仍然被添加到head元素中并立即执行。Pages 特性添加了一个回调机制,当我的内容被插入到文档中时它会通知我,这意味着我可以推迟 JavaScript 代码的执行,直到我想要操作的元素被添加到 DOM 中。回调的处理程序是使用WinJS.UI.Pages.define方法设置的,你可以在清单 5-13 中的contentButton.html文件的script元素中看到我是如何使用这个方法的。

清单 5-13 。使用 WinJS。注册回调函数的方法

`

             Button Content                            
            
                Press Me             
        
     `

define方法有两个参数。第一个是您希望得到通知的文件的名称——在这个场景中,这总是当前文件的名称,因为我希望在 HTML 元素导入后得到通知。

第二个参数更复杂—它是一个对象,其属性指定回调函数,这些函数将在响应内容生命周期的不同部分时执行。属性集由WinJS.UI.Pages.IPageControlMembers接口定义,涵盖了生命周期中的各个阶段。

images 注意 JavaScript 不支持接口,但其他 Windows app 编程语言支持。不必拘泥于细节,对于 JavaScript Windows 应用,接口定义了一组方法和属性,对象必须定义这些方法和属性才能在特定情况下使用,或者在本例中,定义了一组受支持的值或属性名。接口的概念不太适合 JavaScript,但它是让这种语言被视为一等 Windows 应用公民并访问 Windows API 的成本的一部分。

支持的属性名集合有errorinitloadprocessedready,它们可以用于在导入特定文件时对其进行监控。

当您在导入的文件中使用define方法时,最有用的属性是ready,当内容已经加载并插入到主布局中时,您分配给该属性的函数将被执行。ready属性是我支持 JavaScript 代码所需的定时信号,该代码对导入文件的 HTML 元素进行操作。

执行分配给 ready 属性的函数时,会传递两个参数。第一个参数是内容将要插入的元素。第二个参数是传递给render方法的数据对象。

在这个例子中,我使用数据对象的targetcontent属性来配置来自Press Me按钮的click事件的处理方式。通过以这种方式传递信息,我确保了contentButton.html中的代码不依赖于default.html文件中的元素结构。

images 提示这种技术并不完美,因为导入的文件需要知道自己在 Visual Studio 项目中的路径——这意味着移动或重命名文件需要更改代码。我还没有找到解决这个问题的方法,但是结果仍然比我开始时的那种深度依赖要好。

确保内容顺序

WinJS 和 Windows APIs 中的许多方法都是异步工作的。这意味着当您调用一个方法时,它需要做的工作被安排在以后执行。方法立即将控制权返回给代码,以便可以执行脚本中的下一条语句。如果这个方法有一个结果,它通常是通过一个回调函数来处理的,当你调用这个函数时,你把它传递给这个方法。

您可以在 API 文档中找到异步方法,因为它们返回一个WinJS.Promise对象。一个Promise代表了在未来某个时候执行一些工作的承诺,并定义了当Promise完成时(即工作已经完成)设置回调函数所需的所有功能。我在第九章中解释了Promise对象的工作原理,但是我现在需要介绍一些基本用法来告诉你如何处理一些内容导入问题。

微软如此广泛使用异步方法的原因是为了迫使开发人员创建响应性应用。特别是,微软希望避免困扰 Windows 以前版本的一个问题,即应用的 UI 冻结,因为它正在前台执行一些长期活动,如等待连接到服务器或保存大量数据。通过在整个 API 中驱动 Promise对象,微软确保 Windows 应用 ui 很少落入这个陷阱,尽管代价是让开发人员的生活稍微复杂一些。

我说稍微复杂一点,是因为您可能已经熟悉了 web 应用开发中异步编程的概念。Ajax 请求一个在后台执行的操作的完美示例,其结果使用回调函数发出信号。如果你是 jQuery 的粉丝,你会很高兴地得知 jQuery 延迟对象特性与 Windows Promise对象基于相同的CommonJS Promises/A规范(你可以在[wiki.commonjs.org/wiki/Promises/A](http://wiki.commonjs.org/wiki/Promises/A)了解更多)。

承诺在本章中很重要的原因是WinJS.UI.Pages.render方法是异步的。当您调用此方法时,您指定的内容不会立即加载,而是计划在以后加载。如果你用一段内容填充你的目标元素,就像我在这一章中一直做的那样,这没问题,但是当你把几个条目导入到同一个元素中时,这可能会引起问题。

问题是不能保证后台任务按照它们被调度的顺序执行。Windows 运行时可以按照它喜欢的任何顺序自由地履行承诺(异步编程中的一种常见方法),并且内容一旦可用就被插入(这意味着需要较长处理时间的内容可以比较简单的内容晚插入,即使较长内容的处理先开始)。

在清单 5-14 中,我已经展示了将多个内容项导入单个元素的两种方法中的第一种。当点击idbutton1的按钮时,我调用render方法三次,忽略方法调用返回的Promise对象。当您不关心它们的插入顺序时,您会采用这种方法。清单显示了default.js文件的内容。

清单 5-14 。使用 render 方法返回的承诺来订购内容

`(function () {     "use strict";

    var app = WinJS.Application;     window.$ = WinJS.Utilities.query;

    app.onactivated = function (eventObject) {         ('#left button').listen("click", function (e) {             var targetElem = ('#topRight div.contentTarget')[0];             WinJS.Utilities.empty(targetElem);

            if (this.id == "button1") { **                WinJS.UI.Pages.render("contentButton.html", targetElem)** **                WinJS.UI.Pages.render("contentBasic.html", targetElem);** **                WinJS.UI.Pages.render("contentButton.html", targetElem);**             }         });         WinJS.UI.processAll();     };     app.start(); })();`

我称为render方法的顺序建议内容顺序为contentButton.htmlcontentBasic.html,然后再一次为contentButton.html。但是由于我已经忽略了WinJS.Promise对象,我实际上是让顺序在运行时确定。如果运行该示例并单击Button One,您将看到如图图 5-6 所示的结果。

images

***图 5-6。*不受管理的内容顺序

如图所示,内容导入的顺序与我调用render方法的顺序不匹配。发生这种情况的原因有很多,但是对排序最重要的影响来自于render方法缓存内容这一事实。加载contentButton.html文件的第二次调用完成得非常快,因为第一次调用的结果被缓存了。

images 提示尽管缓存在这个例子中起了很大的作用,但是当你丢弃由render方法返回的Promise对象时,你仍然不能依赖特定的顺序。因为有很多因素会影响后台工作的执行顺序,所以即使第二次调用相同的方法,也不能指望得到相同的结果——每次都可能不同。

用承诺强迫内容订单

Promise对象定义了一个名为then的方法,用于指定当Promise完成时(即后台工作完成时)要执行的功能。then方法允许您将函数链接在一起,从而将一个后台任务的调度推迟到另一个任务完成之后。清单 5-15 展示了当点击标记为Button Two的按钮时,我如何使用Promise.then来强制输入内容的顺序。

清单 5-15 。使用 render 方法返回的承诺来订购内容

`(function () {     "use strict";

    var app = WinJS.Application;     window.$ = WinJS.Utilities.query;

    app.onactivated = function (eventObject) {         ('#left button').listen("click", function (e) {             var targetElem = ('#topRight div.contentTarget')[0];             WinJS.Utilities.empty(targetElem);

            if (this.id == "button1") {                 WinJS.UI.Pages.render("contentButton.html", targetElem)                 WinJS.UI.Pages.render("contentBasic.html", targetElem);                 WinJS.UI.Pages.render("contentButton.html", targetElem);             } else { **                WinJS.UI.Pages.render("contentButton.html", targetElem)** **                .then(function () {** **                    return WinJS.UI.Pages.render("contentBasic.html", targetElem);** **                }).then(function () {** **                    return WinJS.UI.Pages.render("contentButton.html", targetElem);** **                });**             }         });         WinJS.UI.processAll();     };     app.start(); })();`

注意,我返回了从render方法得到的Promise对象,作为我传递给then方法的函数的结果。这确保了在Promise完成之前不会执行后续功能。我将在第九章中向你展示不同的编排Promise的方法,但你可以通过启动应用并点击Button Two来查看这种编排的效果。图 5-7 显示了结果——如你所料,内容已经按照我调用render方法的顺序导入。

images

***图 5-7。*强制输入内容的顺序

这种方法的好处是内容按照我想要的顺序排列。缺点是我拒绝了运行时一次加载和处理多个项目的机会,这是异步编程的主要好处之一。当涉及到对布局中的内容进行排序时,为了得到您需要的结果,牺牲性能通常是值得的。

使用导航 API

在我的示例应用中,我仍然有一个问题:用户可以导航到哪里的细节必须包含在每个页面中。当您希望为用户提供不同的路线来导航到相同的内容时,这可能会有所限制。例如,想象一个以特定方式显示数据并可以通过另外两个页面访问的页面——您想为用户提供一种返回到他来自的页面的方法,但是没有办法知道那是哪个页面。

您可以使用包含在WinJS.Navigation名称空间中的 WinJS 导航 API 来解决这个问题。导航 API 帮助您跟踪您在应用中所做的导航更改,并使用这些数据来创建更灵活的布局。在接下来的小节中,我将向您展示如何使用这个 API。导航 API 实际上不做任何导航,它只是在需要导航服务的应用部分和实现您的首选导航策略的代码之间充当代理。随着导航请求的创建和完成,导航 API 会维护一个导航历史,您可以使用它在内容中创建更灵活的导航。

处理导航事件

使用导航 API 时,您必须做的第一件事是为至少一个导航事件注册回调函数。这些事件是在发出导航请求时触发的(我将在下一节演示)。对于每个导航请求,三个导航事件依次发生——我在表 5-2 中总结了这些事件。这些事件没有内置的处理程序,所以你可以在你的应用中把它们解释成最有意义的。我倾向于处理navigating事件,而忽略其他事件。

images

您可以用WinJS.Navigate.addEventListener方法或我在表中显示的便利属性为每个事件注册一个处理函数。导航 API 是一个自我组装的导航系统,所以你在应用中解释这些事件的方式完全取决于你。对于我的示例应用,我将为navigating事件注册一个处理程序,并通过使用 Pages API 将内容导入到应用布局中来进行响应。您必须安排注册您的事件处理函数,作为应用初始设置的一部分,以便您可以响应所有导航请求。在清单 5-16 中,你可以看到我已经添加到default.js文件中的事件处理程序:将代码放在这个文件中意味着当应用加载我的 HTML 主文件时,我的事件处理程序将被注册。

清单 5-16 。处理导航事件

`(function () {     "use strict";

    var app = WinJS.Application;     window.$ = WinJS.Utilities.query;

    app.onactivated = function (eventObject) {

**        WinJS.Navigation.addEventListener("navigating", function (e) {** **            var targetElem = $('#topRight div.contentTarget')[0];** **            WinJS.Utilities.empty(targetElem);**

**            var content = e.detail.location == "basic"** **                ? "contentBasic.html" : "contentButton.html";             WinJS.UI.Pages.render(content, targetElem);** **        });**     };     app.start(); })();`

在这个清单中,我为navigating事件注册了一个处理函数。当我收到这个事件时,我在布局中定位并empty一个目标元素,然后使用render方法导入内容,就像我在前面的例子中所做的一样。(我只导入了一个项目,所以忽略了 render 方法返回的Promise对象。)

我的处理函数接收一个Event对象作为参数。detail.location属性为我提供了所请求的导航位置。使用导航 API 的优点之一是,您的内容不必知道它想要导航到的文件的名称,它可以使用您创建的任何命名机制请求任何内容。在我的例子中,导航到basic将导入contentBasic.html文件,导航到button将导入contentButton.html文件。

提示这似乎是一个小功能,但却是一个有用的想法。如果项目的结构发生变化,嵌入到内容中的每个显式文件名都必须更新。通过将请求位置的名称与导入文件的名称分开,可以将需要与项目结构保持同步的代码放在一个地方。当项目结构发生变化时,您只需更新导航事件处理程序。

调用导航方法

navigate方法是导航 API 的核心。当您想要导航到应用的另一部分时,可以调用此方法。导航 API 触发我在上一节中描述的事件,这些事件反过来执行您在事件处理函数中设置的导航代码。

navigate方法的参数是您请求的位置和一个可选的状态对象,您可以用它将信息传递到您导航到的内容。清单 5-17 展示了我如何在default.js文件中使用navigate方法。

清单 5-17 。使用 default.js 文件中的 navigate 方法

`(function () {     "use strict";

    var app = WinJS.Application;     window.$ = WinJS.Utilities.query;

    app.onactivated = function (eventObject) {

        WinJS.Navigation.addEventListener("navigating", function (e) {             var targetElem = $('#topRight div.contentTarget')[0];             WinJS.Utilities.empty(targetElem);

            var content = e.detail.location == "basic"                 ? "contentBasic.html" : "contentButton.html";             WinJS.UI.Pages.render(content, targetElem);         });

**        $('#left button').listen("click", function (e) {** **            if (this.id == "button1") {** **                WinJS.Navigation.navigate("basic", "Hello from default.js");** **            } else {** **                WinJS.Navigation.navigate("button");** **            }** **        });**         WinJS.UI.processAll();     };     app.start(); })();`

在定义导航事件处理函数的同一个代码块中调用navigate方法看起来有点奇怪,但是随着我在示例应用的其余部分继续采用导航 API,这种模式开始变得更有意义。清单 5-18 显示了对contentButton.html文件的修改,这样点击按钮元素就会调用navigate方法。

清单 5-18 。使用 contentButton.html 文件中的导航方法

`

             Button Content                            
            
                Press Me             
        
     `

现在我有了一些内容,它们可以请求导航到basic内容,而不需要知道内容文件的名称或者内容将被插入到布局中的什么位置。导航事件处理函数负责处理导航,它为应用的其余部分提供服务。注意在navigate方法的调用者和事件处理函数之间没有直接的关系。当您调用navigate方法时,您依赖导航 API 来发送事件,并期望有一个处理函数愿意并能够代表您执行导航。(这就是为什么确保在应用首次启动时注册您的处理函数非常重要。)

使用导航历史

此时,我有了一个工作的导航系统,它允许我以两种方式访问contentBasic.html文件。我可以点击Button One,直接导航到内容,也可以点击Button Two,带我到contentButton.html,然后点击Press Me。现在,我已经建立了到相同内容的两条路径,我可以使用导航 API 的其他特性来确保用户获得一致的导航体验。对于我的简单示例应用,这意味着我可以设置Back按钮,以便它返回到用户来自的页面。我已经修改了contentBasic.html文件来使用导航 API,如清单 5-19 所示。

清单 5-19 。使用 contentBasic.html 文件中的导航 API

`

             Basic Content                      #contentBasicTop {                 border: medium solid white;                 margin: 10px;                 padding: 10px;                                     font-family: serif;             }                  

**                    if (WinJS.Navigation.state) {** **                        $("#contentBasicTop div.message")[0].innerText** **                            = WinJS.Navigation.state;** **                    }**                 }             });                            

            
                No Message             
**            Back**         
    

`

本文档中的重要元素是button。当我的ready处理程序被执行时,我通过id找到了button元素,并使用导航 API 来配置它。导航 API 维护已经导航到的位置的历史。如果有可能返回到先前的位置,那么WinJS.Navigation.canGoBack属性返回true

在我的清单中,如果不能返回,我禁用了button,如果可以,我为click事件设置了一个处理函数。如果您在按钮被启用时单击它,我的处理程序将调用WinJS.Navigation.back方法。这导致导航 API 使用最后访问的位置发出导航事件,为我的内容提供了一种简洁的方式来展开导航序列。导航 API 中有用于向前移动和确定当前位置的等效成员,就像在 web 应用中使用浏览器的历史 API 一样。

我在清单中包含的导航 API 的另一部分是 state 对象。在default.js文件中,我用一个简单的字符串作为状态对象参数调用了navigate方法。这个对象可以通过WinJS.Navigation.state属性获得。如果一个状态对象可用,我用它来设置导入内容中的div元素的内容。

images 提示通过Event对象的detail.state属性,导航事件处理函数可以使用状态对象。

在图 5-8 中点击Button One可以看到导航到contentBasic.html的效果。如果你在应用刚刚启动的时候以这种方式导航,没有导航历史,因此canGoBack属性返回false,这意味着Back按钮将被禁用。虽然没有历史记录,但是有一个可用的状态对象,您可以在布局中看到显示的消息。

images

图 5-8。点击按钮一导航至 contentBasic.html

作为对比,你可以看到在图 5-9 中点击Button Two然后点击Press Me按钮导航到contentBasic.html的效果。有可用的导航历史,所以Back按钮被激活,点击它将导航回contentButton.html(当然,这里的要点是在contentBasic.html文件的代码或标记中没有对contentButton.html的引用)。在这个导航序列中,我没有向navigate方法传递状态对象参数,这在消息中有所反映。

images

***图 5-9。*点击按钮一导航到 contentBasic.html,然后按我

避开浏览器导航功能

既然你已经看到了导航 API 是如何工作的,你可能想知道为什么不直接使用浏览器内置的historylocation对象。问题是这些对象不允许你根据应用的单页内容模型来定制导航。一个 JavaScript Windows 应用可以从主页面导航到一个新的顶级页面,但是当这种情况发生时,应用中的所有状态和内容都会丢失(因为它是使用简单的 JavaScript 对象维护的)。这在使用视图模型时尤其成问题,我在第八章中介绍了这一点。

你需要小心不要使用浏览器内置的导航功能——如果你这样做,你将打破单页模式。这样做不会杀死应用,但会丢失任何基于常规全局 JavaScript 对象和变量的数据,例如导航历史和自定义名称空间。一旦您导航到另一个顶级页面,这些数据就会丢失,并且导航回您的母版页不会恢复这些数据。

在我的项目中,疏忽的顶级导航最常见的原因是当我使用a元素进行导航时,忘记覆盖默认的click行为。如果您在项目中使用a元素,您必须确保处理click事件,调用事件上的preventDefault方法,并使用 WinJS 导航 API 请求导航更改。

总结

在这一章中,我已经向你展示了 Windows 应用的一个基本构件:单页布局。这种布局在 web 应用中越来越流行,但是要让它在应用中正常工作需要使用一些 WinJS 功能。我向您展示了如何使用HtmlControl导入简单的静态内容,如何使用WinJS.UI.Pages API 导入和管理更复杂的内容,最后,如何使用WinJS.Navigation API 在您的应用中创建灵活的导航。

Windows 应用处理单页布局模型的方法很复杂,需要考虑很多东西。但是不要担心——它很快就会成为你的第二天性,你很快就会熟悉我所描述的特性和技术。在下一章,我将向你展示如何让你的应用适应和响应不同的布局和方向,如果你想给你的用户提供一流的 Windows 体验,这是很重要的。