现实世界的-JavaScript-Windows8-应用开发教程-一-

68 阅读1小时+

现实世界的 JavaScript Windows8 应用开发教程(一)

原文:Real World Windows 8 App Development with JavaScript

协议:CC BY-NC-SA 4.0

一、Windows 8 的精神和环境

Abstract

欢迎来到 Windows 8 的美丽新世界。在这一介绍性章节中,您将了解全新的、完全不同的用户界面。你会对“重新想象的窗户”有所了解当你去掉标志性的桌面概念,用完全的应用集成取而代之时,你就开始探索这对 Windows 应用开发人员意味着什么。技术和社会的影响被认为是你,开发者,通过解释、例子和检查技术与商业生活的融合做准备。

欢迎来到 Windows 8 的美丽新世界。在这一介绍性章节中,您将了解全新的、完全不同的用户界面。你会对“重新想象的窗户”有所了解当你去掉标志性的桌面概念,用完全的应用集成取而代之时,你就开始探索这对 Windows 应用开发人员意味着什么。技术和社会的影响被认为是你,开发者,通过解释、例子和检查技术与商业生活的融合做准备。

目前,Windows 8 基本上是唯一可以在从手机到个人电脑的任何设备上运行的操作系统,具有 Windows 8 开发原生支持的各种语言。这本书是关于一种这样的语言——JavaScript——以及作为一名 JavaScript 开发人员,您如何使用您的语言知识(以及作为 UI 布局引擎的 HTML 和 CSS)来构建 Windows 8 应用,让用户感觉像使用。NET 甚至原生 C++。

不太简短的介绍

首先,请允许我们阐明我们是谁,这样你,亲爱的读者,可能会理解这本书的独特视角。这不是一本微软粉丝写的书,而是一本常识性的、完成工作的技术爱好者写的书。我们也喜欢在做一些酷的、前沿的、有意义的事情的同时赚钱的机会,所以当我们说 Windows 8 是开发者有史以来最重要的机会时,请注意。尽管这听起来可能像是重复的微软营销宣传,但我们一直站在 API 的前沿,我们大胆地声称 Windows 8 中的一切都是从头开始重新想象、检查和创新的。

在开始之前,从开发人员的角度介绍一下 Windows 8 是很重要的,特别要关注应用如何工作以及如何由系统管理。讨论并不详尽,因为该主题可能跨越多本书,但它应该为您提供基本信息,以便了解使用 Windows 运行时开发 Windows 8 和 Windows 8 应用的基础知识。

出于解释的目的,让我们浏览一下 Windows 8 UI 工件——不是因为你还不了解 Windows 如何工作,而是为了那些可能还没有接触过该操作系统的人。

Windows 8 外壳是一种数字反转鲻鱼:前面是派对时间,后面是老式的好生意。在第一眼看到 Windows 8 时,你会立刻被这个类比中的派对元素所打动:图 1-1 中显示的 Windows 8 开始屏幕。

A978-1-4302-5081-4_1_Fig1_HTML.jpg

图 1-1。

Windows 8 Start screen

Windows 8 开始屏幕是新的——更确切地说,是重新设计的——应用的启动板。它取代了 Windows 开始菜单。这意味着 Windows 8 上没有开始菜单。因此,不要期望找到一些注册表黑客或设置来启用它:没有。

Note

可以购买一些第三方应用来增强 Windows 8 的开始菜单体验:例如 Stardock 的 Start8。

Windows 8 中省略了标准的 Windows 开始菜单。事实是,这只是一个语义上的移除。如果人们冷静下来,他们会意识到你喜欢的开始菜单的每个功能都存在于开始屏幕中,只有一个区别:旧的开始菜单不会占据整个屏幕。从任何意义上来说,新的开始屏幕都是其前身的超集和自然演变,一旦人们开始使用它,他们很快就会认识到这一点。

Note

微软发布了一个全面的博客,概述了 Windows 8 许多功能背后的细节或原理。一篇这样的博客文章强调了导致微软在 Windows 8 中使用开始屏幕而不是旧的开始菜单的复杂研究。如果你想了解这方面的更多信息,请访问 http://blogs.msdn.com/b/b8/ 的 Building Windows 8 博客。浏览 Windows 开始屏幕演变的具体博文可以在 http://blogs.msdn.com/b/b8/archive/2011/10/03/evolving-the-start-menu.aspx 找到。

在图 1-1 中,带有图像的彩色矩形是应用的一种组合启动点。这些神奇的矩形表面通常被称为动态磁贴,结合了应用快捷方式(尤其是您可能在 Windows 桌面上找到的快捷方式版本)、通常在任务栏上找到的应用运行状态的表示(当您的应用正在运行时),以及在系统托盘区域找到的应用通知机制的表示。图 1-2 显示了日历应用 live tile 的两种状态。左边的图像是默认状态;当会议临近时,瓷砖呈现出不同的外观(右图)。

A978-1-4302-5081-4_1_Fig2_HTML.jpg

图 1-2。

A Windows 8 app tile

像 Windows Essentials 这样的应用(Windows Live 适用于仍然拥有该应用早期版本的用户)可能有一个系统托盘图标,当应用从联机切换到脱机时,该图标的外观会发生变化;它可能在 Windows 桌面上有一个大而漂亮的快捷方式,当应用不是活动窗口时,当向您发送即时消息时,它会闪烁黄色(在 Windows 7 上)。Windows 8 live 磁贴集这三种功能于一身。通过 live tile,你当然可以启动应用;但是如图 1-2 所示,磁贴也可以根据应用中发生的事情,甚至是应用未运行时发生的事情来显示通知。

请注意,并非开始屏幕上的所有互动程序都是实时互动程序。Visual Studio、Microsoft Word 和 Adobe Photoshop 等传统应用也可以作为平铺显示在开始屏幕上,但这些平铺并不是“实时的”,它们不具备在其表面呈现动态内容的能力。传统的 Windows 应用磁贴的功能或多或少类似于旧的应用图标(我们说“或多或少”,是因为 Windows 8 为这些磁贴提供了一些快捷功能,这些磁贴遵循与其实时替代产品相似的模式,例如能够在管理员模式下启动)。使用新范式构建的应用可以通过动态磁贴来表达自己,被微软称为 Windows 8 应用。在本书的剩余部分,我们使用这个术语来指代它们。图 1-3 显示了已启动的 Windows 8 modern 应用的外观。

A978-1-4302-5081-4_1_Fig3_HTML.jpg

图 1-3。

A Windows 8 app

注意到少了什么吗?它是无处不在的关闭、最小化和最大化/恢复按钮。Windows 8 应用会一直占据整个屏幕。这条规则没有例外:即使计划构建一个简单的实用程序窗口,作为开发人员,您也必须考虑如何以减少负面空间的方式进行布局。这是一个棘手的问题,而且你的应用必须支持多种屏幕分辨率。在后面的章节中,当我们开始讨论风格指南和如何通过认证时,我们会对此进行更深入的探讨。

另一个在查看一个已启动的应用时可能出现的问题是,如何关闭它?传统的 Windows 开发通常将应用生命周期管理委托给用户,这意味着用户必须明确地单击右上角的关闭按钮。如果没有,应用将继续运行。Windows Essentials 等应用依赖于此。因为系统没有提供自动关闭不再使用(或已有一段时间未使用)的应用的机制,所以像 Windows Essentials 和 Skype 这样的应用可以将用户的关闭请求视为隐藏请求,并在后台以不可见的窗口继续运行。如果每个人都带着荣誉和同情心行事,这不会是一个问题,但它确实会产生安全问题并消耗系统资源——如果不是不必要的,那么至少在没有用户同意的情况下。

Windows 8 试图通过引入一种考虑用户和系统资源的应用生命周期管理模式来重新想象这种情况(见图 1-4 )。

A978-1-4302-5081-4_1_Fig4_HTML.jpg

图 1-4。

A Windows 8 app’s lifecycle

在 Windows 8 的“party side”中,在任何给定的时间,只有当前运行的应用以及用户选择在后台运行的应用是活动的。所有其他应用都被挂起,这意味着它们的内存是完整的,并且按照指定的顺序运行,但是没有属于这些应用的活动线程正在运行。与保存的文件一样,暂停的应用就像离开时一样完好无损。此外,就像保存的文件一样,可以随时打开并从停止的地方继续,切换回 Windows 8 应用(或从开始屏幕再次启动它)会带你回到它。在这两种状态之间,Windows 8 系统还提供了关闭应用的功能,如果它确定应用需要关闭的话。未来的章节将更多地讨论 Windows 8 应用的生命周期。

Note

你可能在 Windows 8 的演示中见过这一点(或者在我们希望未来版本中包含的教程中),但现代应用可以通过从应用屏幕顶部拖到屏幕底部(“扔掉”)或使用 Alt+F4 来强制关闭。一个高级用户也可以使用旧的控制面板来停止一个现代的应用。

这个应用还缺少一个东西——可能不像关闭和最小化/最大化按钮那样普遍,但肯定是一个众所周知的 Windows 应用功能——就是菜单栏。假设应用占据了整个屏幕,那么命令如何表示可能是任何开发人员都关心的问题。显而易见的选择是把它放在屏幕上,当然,许多应用就是这么做的。但这种模式违背了微软为 Windows 8 应用规定的风格准则。相反,Windows 8 系统提供了两个区域,底部的应用栏和顶部的应用栏,从中可以启动应用命令。

图 1-5 显示了应用如何使用应用栏概念在应用的中心位置隔离命令功能。从这里,最终用户可以启动搜索、按类别对录像进行分组、清除他们的整个库,或者将开始录像的活动直接固定到 Windows 开始屏幕。(在第六章中更详细地讨论了固定和二级瓷砖的概念。)

A978-1-4302-5081-4_1_Fig5_HTML.jpg

图 1-5。

Windows 8 app with the bottom app bar enabled

在任何 Windows 8 应用中,如果启用了触摸,可以通过分别从设备屏幕的底部或顶部向上或向下滑动来激活底部/顶部应用栏(可见)。

Note

并非所有的 Windows 8 设备都支持触摸功能,或者有鼠标,因此在升级到 Windows 8 时,传统设备继续可用非常重要。因此,Windows 8 为所有与触摸相关的功能提供了鼠标和键盘支持。要使用鼠标激活任何应用的应用栏,请右键单击该应用。使用键盘,按 Windows 徽标键+ Z。

无论从哪个方向滑动,两个应用栏都会显示。在所有情况下,设备屏幕的顶部和底部(如果使用鼠标和键盘,还包括右键单击)都属于应用。屏幕的左边和右边属于 Windows。

从左边滑动(Windows 徽标键+ Tab)可以让用户访问当前未被查看的暂停应用。从右边滑动(Windows 徽标键+ C)会显示出魅力。在图 1-6 中,你可以看到 Windows 8 魅力的显露。请注意屏幕左侧的信息框,它显示日期时间信息以及网络和电池状态。

A978-1-4302-5081-4_1_Fig6_HTML.jpg

图 1-6。

Windows 8 charms displayed. Displaying charms also reveals the date-time, wifi signal status, and battery status in a block at lower left onscreen

Windows charms 功能是一项极其重要的创新。它使用户能够搜索系统上的所有应用(甚至在应用内部),轻松地在应用之间共享数据,访问设备以及管理设置。每个 charm 都有一个公开的编程接口,作为 Windows 8 开发者,你可以使用它。

此外,开发现代 Windows 8 应用的开发人员必须应对各种系统环境变化,而使用旧框架开发的应用根本不必担心这些变化。这是因为使用 Windows 8 APIs 构建的应用具有与本机操作系统的连接级别,而这在默认情况下是不存在的。

这种情况的一个实例是应用视图状态。在支持旋转的设备上,Windows 8 应用可以查询系统的当前布局,并相应地调整其 UI。这样,应用可以使用横向模式下可能不可用的垂直空间和纵向模式下可能不可用的水平空间。在视图以横向模式锁定的系统上运行时,检查图 1-7 所示的网飞应用。

A978-1-4302-5081-4_1_Fig7_HTML.jpg

图 1-7。

Netflix app in landscape mode

同一系统上的同一应用,唯一的变化是从横向模式转换到纵向模式,改变了 UI,如图 1-8 所示。

A978-1-4302-5081-4_1_Fig8_HTML.jpg

图 1-8。

Netflix app in portrait mode

构建 Windows 8 应用

Windows 8 应用可以使用 HTML/JavaScript 构建。NET 语言(C#和 VB),或者通过称为 C++/Cx 的 C++扩展的原生 C/C++。不管开发应用使用什么技术,Windows 团队在提供一组核心 API 方面都做得很好,这些 API 被投射到每种目标语言中。图 1-9 提供了 Windows 8 现代应用编程界面的分层视图。

A978-1-4302-5081-4_1_Fig9_HTML.jpg

图 1-9。

Window 8 app API landscape

你有没有见过另一个平台能如此灵活地为其开发者提供这种能力?我们不这么认为。使用这些设计的 API 构建的应用最终被打包成一个包含应用代码(二进制或文本格式)的包;资源;图书馆;以及描述应用(名称、徽标等)、其功能(如文件系统的区域或特定设备,如相机)以及使应用工作所需的一切(如文件关联、后台任务声明等)的清单。清单向 Windows 应用商店以及从商店下载应用的潜在 Windows 客户端描述应用。应用清单的确切工作方式(因为它涉及到发布您的应用和设置其权限)将在第二章中讨论。

到目前为止,我们已经讨论了 Windows 8 和你可以用它构建的应用。到目前为止,无论您选择什么样的开发环境,功能、过程和划分本质上都是相同的。本地开发和。NET 是构建 Windows 应用的伟大平台,并且为构建 Windows 8 应用提供了一些独特的优势。最值得注意的是对遗留 Win32 APIs 子集的访问。对于那些可能不知道的人,Win32 是以前用于构建 Windows 应用的编程平台。Adobe Photoshop、Microsoft Word 和 Internet Explorer 10 等程序都是使用这种技术构建的,它仍然可以用于构建在 Windows 8 的桌面(业务端)视图上运行的 Windows 8 应用。

Windows 8 与 Win32

这本书的重点是使用 HTML 开发 Windows 8 应用,因此谨慎起见,我们在这一点上强调了这些技术之间的一些差异。

首先,需要注意的是。基于. NET 和本机 C++/Cx 的应用在构建时编译。编译是将给定程序的代码部分转换成机器处理器易于读取的中间格式的过程。C++/Cx 被直接编译成特定于处理器的本机代码。(这意味着选择使用这种技术构建应用需要开发人员为他们打算支持的每个平台编译一个版本。Windows 8 目前支持 64 位(x64)、32 位(x86)和基于 ARM 的处理器。) .NET 将代码编译成一种称为字节码的伪二进制格式。字节码是一种中间状态,它允许应用代码比本机代码更具可移植性。这是因为字节码与处理器架构无关,所以相同的字节码可以毫无问题地用在 x64、x86 和 ARM 处理器上。(字节码可以实现这一点,因为它在运行时在运行它的目标平台上被动态编译成本机代码。)

使用 JavaScript 构建的 Windows 8 应用遵循的模式与使用。NET,但是没有中间步骤。Windows 8 JavaScript 应用中的 HTML、CSS 和 JavaScript 代码总是在运行时进行解析、编译和渲染,因此您的应用代码总是完整地传输到运行它的每个客户端。此外,因为这些文件类型不是直接可执行的,所以 Windows 系统必须提供一个宿主进程来运行它们(类似于这些文件类型通常在 web 浏览器环境中的运行方式)。

因为这两者的区别(原生/。NET 和 JavaScript),并且因为设计者想要为目标平台构建 Windows 开发人员体验,这种体验对于给定的平台开发人员来说就像他们可能在该领域中进行的任何其他活动一样真实和自然,所以作为 JavaScript 开发人员构建 Windows 8 应用的许多 API 和控件都是通过专门为 JavaScript 开发定制的补充库来提供的:用于 JavaScript 的 Windows 库(WinJS)。例如,使用 C#或 C++/Cx 的开发人员使用一种称为可扩展应用标记语言(XAML)的技术来设计他们的用户界面并构建或访问系统控件,如清单 1-1 所示。

Listing 1-1. XAML Markup

<Page x:Class="AllLearnings.Samples.ApplicationBars.AppBarSamples"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml

xmlns:d="http://schemas.microsoft.com/expression/blend/2008

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006

mc:Ignorable="d"

>

<Page.BottomAppBar>

<AppBar x:Name="appbar_bottom" VerticalAlignment="Bottom" Height="100" >

<Grid>

<Button x:Name="btn_bottomone" Visibility="Visible" Content="+" ↩

AutomationProperties.Name="Add" HorizontalAlignment="Right"

VerticalAlignment="Top" Style="{StaticResource AppBarButtonStyle}"

/>

</Grid>

</AppBar>

</Page.BottomAppBar>

<Grid x:Name="LayoutRoot" >

<TextBlock>This sample tests the app bar functionality, right click or swipe from

the bottom to open the bottom app bar.</TextBlock>

</Grid>

</Page>

另一方面,作为 HTML/JavaScript 开发人员,您可以使用 HTML/CSS 作为布局引擎来设计您的 UI,并使用 JavaScript 来操作它,就像操作 web 应用一样(见清单 1-2)!

Listing 1-2. HTML Markup

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>TestAppBars</title>

<!-- WinJS references -->

<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />

<script src="//Microsoft.WinJS.1.0/js/base.js"></script>

<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>

<link href="TestAppBars.css" rel="stylesheet" />

<script src="TestAppBars.js"></script>

</head>

<body>

<section aria-label="Main content" role="main" style="margin-left: 100px;">

<p>This sample tests the app bar functionality, right click or

swipe from the bottom to open the bottom app bar.</p>

</section>

<div data-win-control="WinJS.UI.AppBar" >

<button id="btn_bottomone" data-win-control="WinJS.UI.AppBarCommand"

data-win-options="{id:'cmdAdd',label:'Add',icon:'add',ⅵ

section:'global',tooltip:'Add item'}">

</button>

</div>

</body>

</html>

此外,作为开发人员,您可以自由地使用任何您想要的第三方模块(同样以 HTML CSS/ JavaScript 开发人员习惯的方式)。清单 1-3 中的例子使用非常流行且几乎无处不在的 jQuery 选择并着色了一个指定的元素类。

Listing 1-3. Using jQuery with HTML in a Web Application

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml

<head>

<title></title>

<script type="text/javascript" src="/Scripts/jquery-1.7.2.js"></script>

<script type="text/javascript">

$(document).ready(function ()

{

$("#btn_clickme")

.mouseenter(function (e)

{

$(this).css("background-color", "lightblue");

})

.mouseout(function (e)

{

$(this).css("background-color", "slategray");

})

.click(function (e)

{

var txt_alerttext = document.getElementById("txt_alerttext");

if (txt_alerttext != null)

{

alert(txt_alerttext.value);

}

});

});

</script>

<style>

* {

font-family: 'Segoe UI';

}

.canvas {

width: 100%;

height: 100%;

position: absolute;

left: 0px;

top: 0px;

background-color: white;

}

.dtable {

display: table;

margin: 200px auto 0px auto;

width: 800px;

background-color: ghostwhite;

height: 300px;

padding-left: 40px;

padding-top: 10px;

}

.drow {

display: table-row;

}

.dcell {

display: table-cell;

vertical-align: top;

margin-left: auto;

margin-right: auto;

}

#btn_clickme {

background-color: slategray;

border: none;

width: 100px;

height: 30px;

}

</style>

</head>

<body>

<div class="canvas">

<div class="dtable">

<div class="drow">

<div class="dcell">

</div>

<div class="dcell">

<div style="font-size: 24pt; font-family: Calibri">

Sample Application running on the web

</div>

<div style="height: 10px"></div>

<div>

<input id="txt_alerttext" type="text" style="height: 25px" />

<button id="btn_clickme">Click Me</button>

</div>

</div>

</div>

</div>

</div>

</body>

</html>

该示例使用 jQuery 选择网页文档结构中的项目,并对其中的事件做出反应。它还展示了如何使用 CSS 来控制一个非常简单的页面的布局。在 web 浏览器中运行该应用应该会产生如图 1-10 所示的布局。

A978-1-4302-5081-4_1_Fig10_HTML.jpg

图 1-10。

Simple web application layout

请注意,如果您将鼠标放在“单击我”按钮上,该按钮会改变颜色。这是通过清单 1-4 中的 jQuery 指令实现的。

Listing 1-4. jQuery Handling Events on Elements That It Selects

.mouseenter(function (e)

{

$(this).css("background-color", "lightblue");

})

.mouseout(function (e)

{

$(this).css("background-color", "slategray");

})

jQuery 只是监听用户鼠标输入目标元素的边界,在本例中是带有标识符#btn_clickme的按钮。当鼠标进入后,按钮的背景颜色变为lightblue;当鼠标退出时,颜色变回slategray。jQuery 代码还监听按钮被单击的情况,当按钮被单击时,会显示一个对话框,其中包含文本框中输入的文本。图 1-11 显示了发生这种情况时用户界面的样子。

A978-1-4302-5081-4_1_Fig11_HTML.jpg

图 1-11。

Result of a simple web application button click

清单 1-3 中的代码可以一字不差地(大部分)复制到一个以 JavaScript 为目标的 Windows 8 应用中,只需很少的修改,就可以运行了。清单 1-5 说明了这一点,粗体文本表示已经进行了更改的区域。

Listing 1-5. Windows 8 App Using jQuery

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml

<head>

<title></title>

<script type="text/javascript" src="/Scripts/jquery-1.7.2.js"></script>

<script type="text/javascript">

$(document).ready(function ()

{

$("#btn_clickme")

.mouseenter(function (e)

{

$(this).css("background-color", "lightblue");

})

.mouseout(function (e)

{

$(this).css("background-color", "slategray");

})

.click(function (e)

{

var txt_alerttext = document.getElementById("txt_alerttext");

if (txt_alerttext != null)

{

//alert(txt_alerttext.value);

var v = new Windows.UI.Popups.MessageDialog(txt_alerttext.value);

v.showAsync();

}

});

});

</script>

<style>

* {

font-family: 'Segoe UI';

}

.canvas {

width: 100%;

height: 100%;

position: absolute;

left: 0px;

top: 0px;

background-color: white;

}

.dtable {

display: table;

margin: 200px auto 0px auto;

width: 800px;

background-color: ghostwhite;

height: 300px;

padding-left: 40px;

padding-top: 10px;

}

.drow {

display: table-row;

}

.dcell {

display: table-cell;

vertical-align: top;

margin-left: auto;

margin-right: auto;

}

#btn_clickme {

background-color: slategray;

border: none;

width: 100px;

height: 30px;

}

</style>

</head>

<body>

<div class="canvas">

<div class="dtable">

<div class="drow">

<div class="dcell">

</div>

<div class="dcell">

<div style="font-size: 24pt; font-family: Calibri;">

Sample Application running on Windows 8

</div>

<div style="height: 10px"></div>

<div>

<input id="txt_alerttext" type="text" style="height: 25px" />

<button id="btn_clickme">Click Me</button>

</div>

</div>

</div>

</div>

</div>

</body>

</html>

从这个例子中,您可以看到,为了让代码运行,您只需要更改很少的几项。您显式设置应用标题的背景颜色(并更改文本)。此外,因为 Windows 8 APIs 不支持 HTML alert特性,所以您必须注释掉该行,而使用专门为 Windows 8 设计的类似 API。运行该示例会产生图 1-12 和图 1-13 中的行为。这两幅图分别代表点击“点击我”按钮之前(图 1-12 )和之后(图 1-13 )的应用。

A978-1-4302-5081-4_1_Fig13_HTML.jpg

图 1-13。

Windows 8 app using a jQuery MessageDialog alert

A978-1-4302-5081-4_1_Fig12_HTML.jpg

图 1-12。

Windows 8 app using jQuery

正如您在清单 1-5 中看到的,在这两种场景中,您以相同的方式包含了 jQuery 库,并且以相同的方式调用和使用它们。即使一个是 web 应用,另一个是 Windows 8 应用,它们也会产生相同的结果。

为 Windows 8 应用开发

现在,您已经对 Windows 8 有了基本的介绍,Windows 8 应用是什么样子,有哪些技术可以构建 Windows 8 应用,以及 JavaScript 在等式中的位置,您已经准备好开始构建样本,并开始成为下一个 Instagram。

设置您的环境

在你开始之前,你需要一些东西。首先,要构建 Windows 应用并完成本书中的示例,您需要一份 Windows 8。您还需要 Windows 8 SDK,并且至少需要一份 Visual Studio Express 2012。所需工具的下载链接在表 1-1 中列出。请注意,安装 Visual Studio Express 会安装您需要的所有东西(包括必要的 jQuery 库)。表格中还包括其他项目,以防您需要某个特定的组件。

表 1-1。

Windows 8 Development Environment Setup

| 工具 | 位置 | | --- | --- | | 框架 | [`http://jquery.com/download/`](http://jquery.com/download/) | | Windows 8 | [`http://windows.microsoft.com/en-US/windows-8/release-preview`](http://windows.microsoft.com/en-US/windows-8/release-preview) | | 免费版 | [`www.microsoft.com/visualstudio/eng/products/visual-studio-express-products`](http://www.microsoft.com/visualstudio/eng/products/visual-studio-express-products) | | Windows 8 SDK | [`http://msdn.microsoft.com/en-us/windows/hardware/hh852363.aspx`](http://msdn.microsoft.com/en-us/windows/hardware/hh852363.aspx) |

当然,因为这是 JavaScript 开发,很多布局和功能都可以在没有这些的情况下实现。如果你有一个简单的文本编辑器和网络浏览器,你可以在一定程度上跟随。当您开始使用控件并与 Windows 系统集成时,这种情况会发生变化,因此我们建议在这些情况下下载 bits 并使用 SDK。

HTML 入门

毫无疑问,许多阅读本书的人对 HTML、CSS 和 JavaScript 在 Windows 8 应用开发中的应用有一定的了解。这是 JavaScript 被作为 Windows 8 应用开发范例之一的主要原因。HTML/CSS 和 JavaScript 在这一点上实在是太受欢迎了,有太多的支持者和日常用户,以至于它们不再像以前一样被平台游戏中的主要玩家所忽视。作为在这个游戏中多年的技术专家,我们继续对 JavaScript 的弹性和纯粹的优雅感到惊讶。如果你在 20 世纪 90 年代末问任何人 JavaScript 是否会成为此时的王者,答案会是响亮的“不”

尽管有工作知识和预先存在的技能,但为了完整起见,在这里提供一个关于 HTML/CSS 的简短复习课程是很重要的。已经知道主题,不需要复习?太好了:你可以跳过两个章节“为 Windows 8 扩展 HTML5”继续阅读。其他人,欢迎!

HTML5 是一个笼统的术语,描述了一组用于制作现代、丰富的 web 内容的相关技术。这些技术数不胜数,但本文主要讨论三个核心组件:

  • HTML5 本身,它定义了用于标记内容的实际元素
  • 级联样式表 3 (CSS 3.0),允许对标记元素的外观进行离散控制
  • JavaScript,作为一个编程接口,用于在 HTML 文档中构建交互性和程序逻辑

HTML 允许对内容应用某种标记。在这个定义中,标记通常是一个元素(我们很快就会知道元素是什么),内容可以是文本。在上一节中,您已经看到了两个 HTML 示例。清单 1-6 中的清单 1-3 的摘录展示了一个简单 HTML 文档的基本特性。

Listing 1-6. Two HTML Elements

<input id="txt_alerttext" type="text" style="height: 25px" />

<button id="btn_clickme">Click Me</button>

<title class="site-heading">Sample Document Title</title>

HTML 中的元素有三个主要部分:开始标记、内容和结束标记。从这个例子可以明显看出,开始标记总是以小于尖括号开始,后面跟着元素名。(参见清单 1-7 中的开始标签。你可以在 www.w3.org/TR/html-markup/elements.html 找到元素名称列表。)结束标签的开始和开始标签一样,但是在标签名前有一个正斜杠(见清单 1-8)。在清单 1-6 的第一行中,input是一个空的 HTML 元素类型,所以它没有结束标签。这种 HTML 元素以正斜杠和大于号结束。

Listing 1-7. Start Tag for an HTML Element

<title class="site-heading">

Listing 1-8. End Tag for an HTML Element

</title>

作为开发人员,元素可以帮助您以平台无关的方式从语义上描述内容。因此,每个元素都有相关的含义。在这个例子中,使用title标签包装内容向任何读者(无论是人还是其他人)表明元素中包含的内容是一个文档标题。web 浏览器可能会阅读此内容,并以粗体显示文本“示例文档标题”。搜索引擎机器人可能会根据样本、文档、标题和样本文档标题等术语对页面进行索引,这样,如果您在 Google 中键入样本文档标题,就会出现样本页面。表 1-2 中列出了一些常见元素及其含义。

表 1-2。

Elements and Their Meanings

| 元素名称 | 描述 | | --- | --- | | `html` | 指示文本是 HTML 的最外层标签 | | `head` | 提供关于文档的信息 | | `title` | 提供文档的标题 | | `script` | 确保向上兼容性的内联脚本 | | `style` | 确保向上兼容性的样式信息 | | `body` | 包含文档内容的文档正文 | | `input` | 生成按钮、输入字段和复选框 | | `a` | 超文本文档的锚标签 | | `hr` | 在浏览器窗口中绘制水平标尺 |

元素可以是空的(意味着标签不需要关闭)。一个完美的例子是hr标签,它可以用来表示容器宽度的水平线。如今,许多开发人员将 void 元素编写为自结束的空元素。于是,<hr>就变成了<hr />。关闭 void 元素标记允许 XML 解析器(一种称为 XHTML 的文档类型)将 HTML 文档作为 XML 文档(HTML 的超集)进行处理。

通过使用开始标记中的属性来配置元素,可以进一步定制元素。属性本质上是一个名称-值对,用元素开始标记中的 name=value 语法表示。input标签使用type元素来指定期望的输入类型。输入类型button绘制按钮,输入类型text绘制文本框。清单 1-9 说明了这一点。

Listing 1-9. Changing the type Attribute of the input Element

<input type="text" value="input is text" />

<input type="button" value="input is button" />

清单 1-9 展示了两个input元素,一个将type属性设置为text,另一个将type属性设置为button。如果在浏览器或 Windows 8 上运行,应用显示如图 1-14 所示。

A978-1-4302-5081-4_1_Fig14_HTML.jpg

图 1-14。

Two input elements with different type attributes

如果元素支持它们,您可以添加任意数量的属性来实现所需的效果。通常,添加到元素中的属性有两种。全局属性可以添加到每个元素中。这些包括id属性、Style标签和Class标签。您在清单 1-3 中看到了所有这三种全局属性类型。

元素还可以定义特定于它们的单个属性。例如,image元素具有src属性,该属性用于指示要显示的图像相对于根的位置(src也可以指向绝对位置)。

HTML 还提供了作者定义属性的创建,在这种情况下,您希望向元素添加比 HTML 标准定义的更多的元数据。浏览器会忽略作者定义的属性,但它们是 JavaScript 注入的重要途径,因为可以通过浏览器 DOM 发现它们。这种技术通常用于在文档的特定位置将 HTML 注入 DOM。您可以通过在属性前添加data-来表明属性是作者定义的(见清单 1-10)。

Listing 1-10. Using Author-Defined Attributes as Placeholders for Controls

window.onload = function () {

var list = document.getElementsByTagName("div");

for(var i = 0; i < list.length; i++) {

var node = list.item(i);

if(node != null) {

var islogincontrol = node.hasAttribute(``"data-controls"

if(islogincontrol) {

var control_type = node.getAttribute(``"data-controls"

if(control_type == "logincontrol") {

var title_panel = document.createElement("div");

var title = document.createElement("span");

title_panel.appendChild(title);

var username_panel = document.createElement("div");

var username_label = document.createElement("span");

username_label.innerText = "Username: ";

username_panel.appendChild(username_label);

var username = document.createElement("input");

username.type = "text";

username_panel.appendChild(username);

var spacer = document.createElement("div");

spacer.style.height = "5px";

var password_panel = document.createElement("div");

var password_label = document.createElement("span");

password_label.innerText = "Password: ";

password_panel.appendChild(password_label);

var password = document.createElement("input");

password.type = "password";

password_label.appendChild(password);

node.appendChild(title);

node.appendChild(username_panel);

node.appendChild(spacer);

node.appendChild(password_label);

} else {

if(control_type == "labeledinput") {

var label_name = node.getAttribute(``"data-labelname"

var username_panel = document.createElement("div");

var username_label = document.createElement("span");

username_label.innerText = label_name;

username_panel.appendChild(username_label);

var username = document.createElement("input");

username.type = "text";

username_panel.appendChild(username);

node.appendChild(username_panel);

}

}

}

}

}

};

清单 1-10 中应用的模式是一种简单但常见的方法,使用作者定义的属性将复合控件附加到 HTML 页面上。复合控件是提高开发效率的极好工具,因为它们将多个简单控件和相关的固定行为封装到一个可控制的实体中,该实体可以通过集中机制进行配置。例如,如果您正在开发的应用需要带有标识标签的用户名和密码文本字段,您可以创建一个登录控件。该控件可以将登录到您的站点所需的所有功能封装到一个代码库中,然后可以在您需要该功能的任何地方重用它。

这就是控件的力量,通过使用作者定义的属性实现的 expando 控件允许 JavaScript 开发人员做到这一点。清单 1-10 中的例子在 DOM 树中搜索div控件。对document.getElementsByTagName("div")的调用实现了这一点,并返回与参数中指定的标记名匹配的每个元素的列表(在本例中为div)。一旦返回,您只需遍历列表,查询每个元素以查看它是否定义了属性data-controls。如果该属性是在控件上定义的,则读取其值以确定要生成哪种类型的控件。当控件类型为logincontrol时,您会生成一个文本框、一个密码框和两个区域——用户名和密码——分别与它们相关联。当控件类型为labeledinput时,您将生成一个文本框和范围组合。在labeledinput的情况下,您期望定义另一个属性data-labelname,并且您使用它的值作为标注文本框的跨度的innerText。(注意,为了简洁起见,并不是所有的检查都应用于示例代码。在现实世界的场景中,您会希望检查是否定义了data-labelname,而不仅仅是期望它在那里。)

清单 1-11 显示了这个应用的 HTML 可能的样子。

Listing 1-11. Using Author-Defined Attributes to Create Composite Controls

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml

<head>

<title></title>

<script type="text/javascript" src="Scripts/ExpandoFile.js"></script>

<style>

* {

font-family: 'Segoe UI';

}

.canvas {

width: 100%;

height: 100%;

position: absolute;

left: 0px;

top: 0px;

background-color: white;

}

.dtable {

display: table;

margin: 200px auto 0px auto;

width: 800px;

background-color: ghostwhite;

height: 300px;

padding-left: 40px;

padding-top: 10px;

}

.drow {

display: table-row;

}

.dcell {

display: table-cell;

vertical-align: top;

margin-left: auto;

margin-right: auto;

}

</style>

</head>

<body>

<div class="canvas">

<div class="dtable">

<div class="drow">

<div class="dcell">

</div>

<div class="dcell">

<div style="font-size: 24pt; font-family: Calibri">

Sample Application using custom controls

</div>

<div style="height: 10px"></div>

<hr />

<div data-controls="logincontrol" >

</div>

<div data-controls="labeledinput"

data-labelname="Some Information:"></div>

</div>

</div>

</div>

</div>

</body>

</html>

在您的浏览器中运行清单 1-11 中的代码,您应该会看到如图 1-15 所示的页面。

A978-1-4302-5081-4_1_Fig15_HTML.jpg

图 1-15。

Custom controls injected onto a page dynamically

右键单击 web 页面并打开该文件的源代码,您应该会注意到一些有趣的事情:这个 HTML 文档的源代码不包含您在浏览器视图中看到的任何元素或内容。这是因为内容是动态创建的,并被注入到 DOM 中。

CSS 入门

HTML 引以为豪的一个架构哲学是将语义标记从标记的外观中分离出来(当然,也有例外)。在 HTML 的世界中,CSS 为您提供了在最初或以后执行语义标记与标记外观分离的能力。早在清单 1-3 和最近的清单 1-11 中,您已经看到了样式表可以做什么。如果您从图 1-15 中移除与 HTML 页面相关联的内嵌样式表,它看起来如图 1-16 所示。

A978-1-4302-5081-4_1_Fig16_HTML.jpg

图 1-16。

Application without styles

这个没有样式的 HTML 的例子只是触及了表面。考虑一下网站 www.csszengarden.com/ (见图 1-17 和 1-18 ),你会开始感觉到 CSS 有多强大,它对 HTML 文档的布局有多大的控制力(当你构建 HTML 文档时,它放弃了布局和外观的考虑,只专注于语义标记)。

A978-1-4302-5081-4_1_Fig18_HTML.jpg

图 1-18。

CSS Zen garden Under the Sea! template

A978-1-4302-5081-4_1_Fig17_HTML.jpg

图 1-17。

CSS Zen garden default style

正如您在这两个图中看到的,Zen garden 站点中的相同内容可以通过简单地修改与之相关联的 CSS 样式获得完全不同的呈现。CSS 是如此广泛的一个主题,以至于这本书只能让你对它的威力有个大概的了解,而不会让你不知所措。所以,我们建议通过其他方式研究 CSS。除了像《HTML5 入门》和《CSS3:Web Evolved》(a press,2012)这样的书籍之外,网上还有很多资源可以使用。

为 Windows 8 扩展 HTML5

回想一下“HTML 入门”一节中关于作者创建的属性的讨论。HTML5 主机通常会忽略它们不理解的属性,这可以用作添加到 DOM 的新内容的插入点。在示例场景中,一个登录控件和带标签的文本框被添加到文档中的一个div中,这是基于在整个 DOM 树中搜索该特殊属性并将新的 HTML 注入到定义该属性的任何元素中。如前所述,这是一种将 UI 功能作为自定义控件构建到 HTML 中的古老技术。

现在,还记得我们说过 Windows 8 应用开发的 HTML/CSS/JavaScript 实现是专门为 HTML/CSS/JavaScript 开发人员量身定制开发流程的吗?我们讨论了 WinJS,以及它如何成为 JavaScript 和 Windows 运行时(WinRT)之间的天然桥梁,在必要的地方注入自身,但只是以 JavaScript 兼容的方式。JavaScript 和 WinRT 的这种融合在技术领域中实现控件的选择上最为明显。

在 Windows 8 开发的 HTML 方法中,对用于支持 HTML 开发的语言的扩展,比如你构建的控件,是使用作者创建的属性来实现的,这些属性应用于预先存在的控件,比如div s,非常类似于清单 1-11 中的例子。这是通过两个属性来实现的:data-win-control,它向运行时指示您想要将什么控件扩展到已标识的div区域中;和data-win-options,它允许您将单个配置参数(很像属性)传递给所选的控件。更具体地说,在data-win-control属性中,必须指定创建实际控件的公共构造函数的完全限定名;在data-win-options属性中,您必须指定一个 JSON 字符串,该字符串可用于配置该控件对象。

此外,在实际的 JavaScript 代码中,您必须调用WinJS.UI.processWinJS.UI.processAll()来启动将控件展开到其容器中的过程(在本例中,是一个div)。Windows 8 应用栏提供了一个使用控件的基本而清晰的例子。图 1-5 显示了应用的应用栏,清单 1-12 提供了创建应用的 HTML。

Listing 1-12. AppBar Control Added to the Bottom of the Page

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>TestAppBars</title>

<!-- WinJS references -->

<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />

<script src="//Microsoft.WinJS.1.0/js/base.js"></script>

<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>

<link href="TestAppBars.css" rel="stylesheet" />

<script src="TestAppBars.js"></script>

</head>

<body>

<section aria-label="Main content" role="main" style="margin-left: 100px;">

<p>This sample tests the app bar functionality</p>

</section>

<div data-win-control="``WinJS.UI.AppBar

<button id="btn_add" data-win-control="``WinJS.UI.AppBarCommand``" data-win- options``=

"{id:'cmdAdd',label:'Add',icon:'add',section:'global',tooltip:'Add item'} ">

</button>

</div>

</body>

</html>

页面/导航模型

前一节谈到了如何通过作者生成的属性来扩展 Windows 8 开发中使用的 HTML,以包含针对 Windows 8 的强大新功能。HTML 的一个主要(也是最容易识别的)特性是能够通过使用给定文档中的链接来链接文档,或者让 JavaScript 调用 API 来改变浏览器当前指向的位置。在 web 应用中,应用上下文通常在服务器端进行管理,HTML/JavaScript 表示层主要作为用户可以用来启动交互的一个薄薄的无状态皮肤。Windows 8 应用,至少是它们的本地部分,不是基于服务器的。相反,如前所述,执行和管理执行所需的所有显示和代码都是在运行时下载和解释的。这包括代码中内置的任何状态管理机制。不像我们的表亲。NET 和本机方面,没有一个内在地内置于 HTML 中的一致的、内存中的、全局的应用状态。它收集变量,呈现视图,计算值,但是一旦您离开当前页面并移动到另一个页面,所有的状态、所有的内容,更糟糕的是,您的整个脚本上下文都将丢失。下一个页面必须重新构建上下文(即使之前已经对其进行了 JIT)。使用 Windows 8 的一些内置功能,如访问文件系统或应用的设置容器,您可以创建该全局上下文的 IO 绑定版本(从变量而非上下文共享的角度来看)。但是对于它提供的一点点功能来说,这太复杂了。

JavaScript 的 Windows 运行时引入了一个新的导航范例来帮助解决这个问题。使用它,您可以保证跨页面共享内存上下文的概念,并在应用在视图之间切换时保持视觉保真度。运行时通过放弃全屏重新呈现的概念而支持 DOM 注入来实现这一点。如果您还记得的话,清单 1-10 展示了如何根据函数确定的插入点将新元素注入到预先存在的 HTML 文档中。上一节展示了在 Windows 8 中运行时,如何使用强大的新控件来扩展 HTML。清单 1-13 展示了如何使用WinJS.Navigation对象实现导航(它使用了类似的技术)。

Listing 1-13. Navigation Using Partial Documents

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>AllLearnings_html</title>

<!-- WinJS references -->

<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />

<script src="//Microsoft.WinJS.1.0/js/base.js"></script>

<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>

<!-- AllLearnings_html references -->

<link href="/css/default.css" rel="stylesheet" />

<script src="/js/default.js"></script>

</head>

<body>

<div class="TestAppBars fragment" style="height: 150px; background-color: #6E1313;">

<div style="height: 50px;"></div>

<header aria-label="Header content" role="banner"

style="margin-top: 0px;

vertical-align: bottom;">

<h1 class="titlearea win-type-ellipsis"

style="margin-left: 100px;">

<button id="btn_back"

class="win-backbutton" aria-label="Back"

type="button"

style="margin-left: 0px; margin-right: 20px;

visibility: collapse;"></button>

<span id="txt_title" class="pagetitle">Select a sample</span>

</h1>

</header>

</div>

<div id="frame" style="margin-top: 10px;">

</div>

</body>

</html>

示例的第一部分创建了应用的总体布局。注意frameiddiv。这是您执行的 HTML 注入的目标。使用 WinJS Navigation类导航使用基于调用navigate命令的回调模型。让我们看看清单 1-14 中的 JavaScript 代码。

Listing 1-14. Code-Behind for Navigation

(function ()

{

"use strict";

WinJS.Binding.optimizeBindingReferences = true;

var app = WinJS.Application;

var activation = Windows.ApplicationModel.Activation;

var stack = new Stack();

app.onactivated = function (args)

{

if (args.detail.kind === activation.ActivationKind.launch)

{

args.setPromise(WinJS.UI.processAll().then(function ()

{

WinJS.Navigation.navigate("samplelist.html");

}));

}

var btn_back = document.getElementById("btn_back");

btn_back.onclick = function ()

{

WinJS.Navigation.back();

};

};

WinJS.Navigation.addEventListener("navigated", function (args)

{

//find the frame

var frame = document.getElementById("frame");

//clear the frame

WinJS.Utilities.empty(frame);

//stack.Push(args.detail.location);

if (WinJS.Navigation.canGoBack)

btn_back.style.visibility = "visible";

else

btn_back.style.visibility = "collapse";

if (args.detail.state != null)

txt_title.textContent = args.detail.state;

//render the location onto the frame

args.detail.setPromise(WinJS.UI.Pages.render(args.detail.location, frame,ⅵ

args.detail.state));

});

app.start();

})();

使用 WinJS 框架在页面之间导航有两个组件。首先是调用WinJS.Navigation类的navigate()函数——传入您想要导航到的目标页面的名称。不过,这个电话只是等式的一部分。在进行这个调用之前,应用应该为navigated事件提供一个处理程序。这个处理程序让您的应用接收任何导航请求,并将适当的内容加载到清单 1-13 中指定的目标div上。导航难题的另一部分是WinJS.UI.Pages级。Pages类有一个render函数,它获取任意的 HTML“页面”(本质上是松散耦合的 HTML 文件),并将它们插入 DOM,给人一种导航的错觉。执行导航的过程相对简单:

Find the insertion point (in this case, the div name’s frame).   Clear the insertion point of any child content from a previous navigation.   Pass the page name (through the args.detail.location property, which is retrieved from the event arguments passed into the navigated function), frame instance name, and any navigation state through the args.detail.state property. args.detail.state is accessed through the same event argument object, navigated. (Chapter 2 goes over navigation in greater detail.)  

清单 1-15 展示了 JavaScript 中处理导航的模式。

Listing 1-15. Handling Page Navigation

//find the frame

var frame = document.getElementById("frame");

//clear the frame

WinJS.Utilities.empty(frame);

//navigate to the page

args.detail.setPromise(WinJS.UI.Pages.render(args.detail.location, frame, args.detail.state));

承诺和新的异步编程模型

传统上,在 JavaScript 中调用函数是以同步的方式发生的,这意味着调用方在被调用的函数完成后等待,然后再继续。在被调用的函数长时间运行的情况下——例如,如果它需要从服务器下载额外的 HTML 来注入 DOM 整个应用可能看起来没有响应。这是因为 JavaScript 是单线程的,这意味着 UI、UI 事件和代码共享同一个线程。如果这个线程在等待某个长时间运行的函数调用的响应时被冻结,用户就不能与屏幕上的任何 UI 元素进行交互。

回调是解决这一限制的流行方法。多种应用都使用这种方法来解决这个问题,而且效果很好。事实上,如果技术实现是唯一的关注点,就没有必要在这个领域进一步创新。

回调的问题在于,由于其执行固有的不确定性,它们可能会彻底改变程序流。检查清单 1-16 中的应用。调用一个异步的、长时间运行的函数,它接受两个回调。每个回调也执行自己的长时间运行的操作。

Listing 1-16. Long-Running Asynchronous Process Test

function LongRunning(callback_one, callback_two)

{

//perform long-running process

var response = callback_one();

callback_two(response);

}

//call method

LongRunning(function ()

{

//do something async here

}, function (args)

{

//then do something async here as well

});

即使像示例中一样,callback_onecallback_two之前被调用,也不能保证回调将以什么顺序执行,因此没有办法以串行方式重新同步结果。这是异步的基本问题:它必须以同步方式编程,以保持对程序员的意义,因为它存在于同步逻辑流和编程结构中。回调将程序员与底层语言的同步结构断开。在小的情况下,这可能没有影响,但是一旦您开始转向异步必须同步的场景(如示例中所示),您就会遇到问题。

WinJS 使用承诺的概念来降低异步编程的复杂性。承诺创建了一个结构,在这个结构上异步回调的执行可以按顺序同步。让我们看看清单 1-16 中的例子是如何使用承诺的。首先,必须修改LongRunning函数,以便在完成时返回一个通用的承诺,而不是要求预先定义和传递所有回调。请参见清单 1-17。

Listing 1-17. Long-Running Function That Returns a Promise

function LongRunning()

{

//perform long-running process

//promise function does not exist but is used to denote the conceptual

//return of a promise instance

return promise();

}

现在,您不再需要显式地传递在完成LongRunning函数时执行的回调,您可以根据需要自由地链接尽可能多的回调来完成任务。让我们看看这个函数在清单 1-18 中是如何被调用的。

Listing 1-18. Consuming a Long-Running Function That Returns a Promise

LongRunning().then(function (args)

{

return (promise(callback_one()));

}).then(function (args)

{

return (promise(callback_two(args)));

});

如您所见,回调的显式定义已经从函数参数中移除。现在,只有在前一次执行完成时,才会执行它们。在清单 1-18 中,很容易看出代码是如何面向顺序的。代码不仅更容易阅读和理解,而且以确定性的方式呈现。使用then方法以可控的方式将回调附加到LongRunning函数。当函数完成时,执行then回调(将之前执行的结果传递给它)。这将递归地继续下去,直到不再有回调要执行。使用then保证了几件事:

  • 当目标异步操作完成时,您可以指定一个回调函数来处理then
  • 该回调在其参数列表中提供一个值,该值表示先前完成的异步操作的结果
  • then操作也是一个返回承诺的异步操作

因为then返回一个期望回调的承诺,该回调包含先前执行的then的结果,所以您可以通过在任何then调用的结果上调用then来链接相关的异步调用(如示例所示)。不用说,互相嵌套承诺会让一切回到过去,所以如果可能的话,你应该避免这样做。

在某些情况下,你可以用承诺来做一些事情:

  • 你可能会在 JavaScript 代码的事件处理程序中发现使用承诺的机会,特别是因为许多微软 API 已经被重写为异步的,以提高整体系统性能。由于系统对待事件处理程序的方式,您需要告诉它您的承诺仍在运行。系统事件处理程序的 WinJS 实现为此提供了一个函数:setPromise。你在清单 1-14 中看到了一个例子。第二章深入探讨了这种方法的工作原理。
  • 你可能在某个时候需要同步多个承诺。例如,假设您有两个不相关的承诺链:一个检索用户信息,另一个从文件系统读取设置数据。如果您需要在两个操作都完成后执行某个动作,您可以使用join操作来实现。

摘要

毫无疑问,Windows 8 背离了你所习惯的一切。为它开发也有些如此。本章对 Windows 8 的广泛回顾应该为下一章涉及的基础知识做好准备,并为您提供 Windows 8 应用开发所需的基础知识。你学会了

  • 与 Windows 8 用户界面交互的特点、功能和属性
  • 如何在 Windows 8 开发中使用 HTML
  • Windows 8 开发中可以使用的各种技术

二、正确掌握基础知识

Abstract

如果您打算构建 Windows 8 应用,无论您选择使用哪种技术,都必须习惯某些基本的开发人员工作流程。从根本上说,对如何访问公开的文件系统有一个很好的理解,即使不是至关重要的,也是对给定系统中的开发非常重要的。本章通过向您展示用于处理这些关键领域的 API,向您介绍了这些领域。提供了详细的示例来帮助您了解这些 API 是如何工作的。在本章中,您将探索文件系统访问,以及 Windows 8 开发中生态系统的一些新的重要元素。在本章结束时,你应该了解如何进行文件访问,如何与锁定屏幕交互,如何用文件对话框提示用户,以及如何在应用继续在后台加载时使用闪屏来吸引用户,并随后通知用户。

如果您打算构建 Windows 8 应用,无论您选择使用哪种技术,都必须习惯某些基本的开发人员工作流程。从根本上说,对如何访问公开的文件系统有一个很好的理解,即使不是至关重要的,也是对给定系统中的开发非常重要的。本章通过向您展示用于处理这些关键领域的 API,向您介绍了这些领域。提供了详细的示例来帮助您了解这些 API 是如何工作的。在本章中,您将探索文件系统访问,以及 Windows 8 开发中生态系统的一些新的重要元素。在本章结束时,你应该了解如何进行文件访问,如何与锁定屏幕交互,如何用文件对话框提示用户,以及如何在应用继续在后台加载时使用闪屏来吸引用户,并随后通知用户。

文件输入输出

构建了服务器端风格代码甚至桌面客户端的开发人员可能非常熟悉文件访问的一般模式。如果你一直在构建 HTML 用户体验,你可能会有一点劣势,但只是一个小劣势。事实是,我们在使用计算机时都处理过文件,我们当然都了解它们的本质。可以打开、编辑、关闭、删除、列出、搜索、复制和移动它们。

尽管在逻辑上是相似的(如果不是相同的话),但是这个讨论分为三个核心领域。首先,这一章谈到了存储位置(指存储文件的地方)。这一对话非常重要,因为它对您的应用的构建、交付以及最终部署到 Windows 应用商店的方式有着巨大的影响。然后你进入实际处理文件的本质。这是第二步,因为通过 Windows Runtime for JavaScript 访问文件的方法要求您首先理解存储位置的含义。如果没有这种核心的理解,你很可能会焦虑不安,不知道为什么一个给定的功能会莫名其妙地失败,并出现一个完全无用的神秘错误。

存储文件夹

StorageFolder类包含了存储位置的一般含义。如前所述,Windows 8 文件可以驻留在任意数量的位置。您开发的应用可以访问这些文件,或者是通过在应用的清单中声明权限。您的应用具有内在访问权限的一些存储位置包括应用的包安装位置和独立存储区域。这些存储位置将在以下章节中详细讨论。

隔离存储区

应用的独立存储区域是 Windows 系统上安装该应用的未发布位置。它充当应用读取和写入的缓存。通过添加或删除文件、创建文件夹等方式对独立存储区域所做的更改会在应用的更新中保持不变。这是这个位置和下一节讨论的包安装位置之间的主要区别。清单 2-1 显示了如何创建、编辑、删除和读取应用的独立存储中的文件。

Listing 2-1. File Access from the Isolated Storage Area

//write the file to isolated storage

btn_createfile_iso.onclick = function ()

{

if (txt_display.value.length > 0)

{

Windows.Storage.ApplicationData.current.localFolder.createFileAsync("testfile.txt",ⅵ

Windows.Storage.CreationCollisionOption.replaceExisting).then(function (file)

{

Windows.Storage.FileIO.writeTextAsync(file,ⅵ

"The quick brown fox jumped over the lazy dog,ⅵ

and [" + txt_display.value + "]");

});

}

};

//read file

btn_readfile_iso.onclick = function ()

{

Windows.Storage.ApplicationData.current.localFolder。*本文件迟交

getFileAsync("testfile.txt").then(function (file)

{

if (file == null)

{

} else

{

Windows.Storage.FileIO.readTextAsync(file)

.then(function (text)

{

txt_display.value = text;

});

}

});

};

//edit file

btn_editfile_iso.onclick = function ()

{

Windows.Storage.ApplicationData.current.localFolder。*本文件迟交

getFileAsync("testfile.txt").then(function (file)

{

if (file == null)

{

} else

{

Windows.Storage.FileIO.writeTextAsync(file, txt_display.value);

}

});

};

//delete file

btn_deletefile_iso.onclick = function ()

{

Windows.Storage.ApplicationData.current.localFolder

getFileAsync("testfile.txt").then(function (file)

{

if (file == null)

{

var v = new Windows.UI.Popups.MessageDialog("File was deleted");

v.showAsync();

} else

{

file.deleteAsync();

}

});

};

清单 2-1 展示了如何使用Windows.Storage.ApplicationData.current.localFolder.createFileAsync函数在独立存储中创建一个文件。您调用的这个函数的版本需要两个参数:一个表示文件名(在本例中为testfile.txt),另一个称为Windows.Storage.CreationCollisionOption——确定当您要创建的文件与目标文件夹(在本例中为独立存储文件夹)中已经存在的文件同名时该如何处理。这个函数有两个版本:一个期望传入单个参数(文件名),另一个也期望包含CreationCollisionOption。当您使用第一个版本时,会自动使用failIfExists CreationCollisionOption。表 2-1 显示了Windows.Storage.CreationCollisionOption的所有可能选项。

表 2-1。

Windows.Storage.CreationCollisionOption Members

| 成员 | 价值 | 描述 | | --- | --- | --- | | `generateUniqueName` | Zero | 用所需的名称创建新文件或文件夹,如果已经存在同名的文件或文件夹,则自动附加一个编号 | | `replaceExisting` | one | 用所需的名称创建新文件或文件夹,并用该名称替换任何已存在的文件或文件夹 | | `failIfExists` | Two | 用所需的名称创建新的文件或文件夹,如果已经存在同名的文件或文件夹,则返回错误 | | `openIfExists` | three | 用所需的名称创建新的文件或文件夹,如果已经存在同名的文件或文件夹,则返回现有项目 |

继续讨论清单 2-1,注意第一章中讨论的then承诺的使用。如上所述,许多 Windows JavaScript 库(WinJS)函数使用异步编程模型,这需要使用承诺。

这个例子的其余部分很简单。清单 2-2 是清单 2-1 的一个片段,重点是读取文件的过程。

Listing 2-2. Reading a File from Isolated Storage

btn_readfile_iso.onclick = function ()

{

Windows.Storage.ApplicationData.current.localFolder

getFileAsync("testfile.txt").then(function (file)

{

if (file == null)

{

} else

{

Windows.Storage.FileIO.readTextAsync(file).then(function (text)

{

txt_display.value = text;

});

}

});

};

从清单 2-2 可以看出,文件访问是一个两步过程。您首先需要获取文件,然后,一旦您有了一个表示您想要访问的文件的对象(一个StorageFile对象),您就可以在后续调用中使用该句柄(在本例中,您将它用作readTextAsync函数中的一个参数)。你将在本章后面了解StorageFile

软件包安装位置

与独立存储位置中的文件不同,当安装应用的新更新时,对应用的包安装位置(使用Windows.ApplicationModel.Package类访问)所做的修改将被清除。Package包含一个属性InstalledLocation,该属性返回对一个StorageFolder的引用,该引用表示最终用户计算机上应用被解包的位置。如前所述,StorageFolder是存储位置的抽象容器,特别是因为它是一个抽象,您可以使用相同的实例类型来表示两个位置(即使访问文件的底层机制不同)。例如,应用的安装位置可能是用户系统上的文件夹,但独立存储位置可能是压缩和加密的结构。尽管如此,相同的编程接口对两者都适用。

让我们重做清单 2-1 中的例子,这样它就足够一般化,任何StorageFolder都可以使用它。首先,让我们创建清单 2-1 所示的创建、读取、编辑和删除功能的通用版本(参见清单 2-3)。

Listing 2-3. Generic Reading and Writing to a Storage Location

var createFileAsync = function (store, file_name, file_content) {

return store.createFileAsync(file_name,ⅵ

Windows.Storage.CreationCollisionOption.replaceExisting)

.then(function (file) {

Windows.Storage.FileIO.writeTextAsync(file, file_content);

});

};

//write the file to isolated storage

var readFileAsync = function (store, file_name, completeFunction) {

return store.getFileAsync(file_name)↩

.then(function (file) {

if (file != null) {

Windows.Storage.FileIO.readTextAsync(file).then(function (text) {

if(completeFunction != null)

completeFunction(text);

});

}

});

};

var editFileAsync = function (store, file_name, new_content) {

return store.getFileAsync(file_name)↩

.then(function (file) {

if (file != null) {

Windows.Storage.FileIO.writeTextAsync(file, new_content);

}

});

};

var deleteFileAsync = function (store, file_name) {

return store.getFileAsync(file_name)↩

.then(function (file) {

if (file != null) {

file.deleteAsync();

}

});

};

前面的示例概括了在目标存储位置操作文件的活动。您允许用户传入存储位置、文件名,以及在编辑或创建文件的情况下,他们想要添加到文件中的内容。您创建了四个私有函数:createFileAsynceditFileAsyncreadFileAsyncdeleteFileAsync。(注意在函数名中使用了Async。您这样做是因为这些函数都返回了其内部文件访问方法调用所返回的承诺。像这样的异步函数名称中不需要有Async,但是它可以帮助下游的开发人员快速识别哪些函数是异步的,哪些不是。)每个按钮点击事件中的文件访问模式被封装到这四个函数中,并被一般化,以便任何存储位置都可以作为参数传入,并且行为将保持不变(假设应用可以访问该存储位置)。

假设用户界面有创建、读取、编辑和删除文件的按钮,清单 2-4 展示了如何使用按钮的点击处理程序来操作应用的包安装位置中的文件。

Listing 2-4. File Access Using the Application’s Install Location

//read file in package

btn_readfile_iso.onclick = function () {

readFileAsync(Windows.ApplicationModel.Package.current。*本文件迟交

installedLocation, "testfile.txt", function (text)

{

txt_diplay.value = text;

});

};

要点是,不管底层实现如何,您都可以以相同的方式针对StorageFolder进行编程。因此,这一章不再关注更多的例子,而是给出了一个开发者可以使用的各种位置的概述,以及哪些类的哪些属性公开了它们。

关于存储的更多信息:ApplicationData 类

如果您想要加载和读取数据的只读缓存,那么访问应用的包安装位置是很好的选择。但是因为它非常不稳定(当应用更新时,它会被删除),所以您可能永远也不应该写入它(或者至少您应该预期在每次应用更新后,写入其中的内容都会消失)。如清单 2-1 所示,更好的解决方案是使用Windows.Storage名称空间中的类,特别是ApplicationData类。ApplicationData提供对应用虚拟文件存储的直接访问。它可用于访问本地、漫游或临时的存储位置。表 2-2 提供了值得注意的ApplicationData类成员的视图。

表 2-2。

ApplicationData Notable Members

| 财产 | 描述 | | --- | --- | | `current` | 提供对与应用包关联的应用数据存储的访问。这是静态的,也是访问当前`ApplicationData`对象实例的唯一方式。 | | `localFolder` | 获取本地应用数据存储区中的根文件夹。 | | `localSettings` | 获取本地应用数据存储区中的应用设置容器。 | | `roamingFolder` | 获取漫游应用数据存储中的根文件夹。 | | `roamingSettings` | 获取漫游应用数据存储区中的应用设置容器。 | | `roamingStorageQuota` | 获取可从漫游应用数据存储同步到云的最大数据大小。 | | `temporaryFolder` | 获取临时应用数据存储区中的根文件夹。 |

如果您想要创建和管理与当前登录用户相关的文件和文件夹,而不是用户所在的特定机器,那么roamingFolder属性是合适的选择。漫游文件夹在用户登录的所有设备之间同步。(请注意,对可漫游的总存储配额有限制,对用于文件以便文件漫游的命名约定也有限制。)这种方法的好处在于,它允许应用的用户在不同设备之间无缝流动,而无需显式同步回服务器。将内容存储在文件夹中,让 Windows 8 处理剩下的事情。

应用的临时存储甚至比包存储更不稳定。存储在此位置的项目只能保证与应用当前运行的会话一样长。这里的文件可以由系统随时删除,甚至可以由用户使用磁盘清理工具手动删除。因此,这个位置的理想用途是作为应用中的会话缓存。

用户的已知文件夹

您的应用并不局限于处理应用的独立数据存储、包安装位置或刚才讨论的本地/漫游/临时位置中的文件。使用 Windows Runtime(WinRT)for JavaScript 构建的应用也可以通过Windows.Storage.KnownFolders类访问文件夹。表 2-3 列出了为访问它们所代表的底层文件夹而定义的KnownFolders属性。

表 2-3。

KnownFolders Properties

| 财产 | 描述 | | --- | --- | | `documentsLibrary` | 获取文档库 | | `homeGroup` | 获取`HomeGroup`文件夹 | | `mediaServerDevices` | 获取`Media Server Devices`(数字生活网络联盟[DLNA])文件夹 | | `musicLibrary` | 获取音乐库 | | `picturesLibrary` | 获取图片库 | | `removableDevices` | 获取`Removable Devices`文件夹 | | `videosLibrary` | 获取视频库 |

为了让应用访问这些文件夹,它需要通过声明性地指示它打算使用一个存储位置而不是它可以自动访问的私有位置来询问用户。在 Windows 8 中,这是一个两步过程。首先,将您想要访问的目标 Windows 8 库启用为您的应用声明使用的功能。您可以通过应用清单的Capabilities部分做到这一点。在一个标准的 WinRT for JavaScript Windows 8 应用中,应用的清单位于项目的根文件夹中,名为package.appxmanifest(见图 2-1 )。

A978-1-4302-5081-4_2_Fig1_HTML.jpg

图 2-1。

Application manifest’s location in the project structure

当您打开这个文件时,您应该会看到五个选项卡。选择 Capabilities 选项卡将打开如图 2-2 所示的屏幕。

A978-1-4302-5081-4_2_Fig2_HTML.jpg

图 2-2。

Capabilities tab

选择任何一个库功能(文档库、音乐库、图片库或视频库)可通过适当的ApplicationData属性访问相关的存储位置。“外部已知文件夹”一节讨论了如何为其他三个KnownFolders属性(homeGroupmediaServerDevicesremovableDevices)启用功能。

接下来,必须显式声明应用读取和写入的文件类型。您可以通过应用清单的“声明”选项卡来完成(参见图 2-3 )。

A978-1-4302-5081-4_2_Fig3_HTML.jpg

图 2-3。

Declarations tab

您需要向您的应用添加一个新的文件类型关联。关联必须包括文件扩展名(扩展名前面带有点),并且可以选择提供 MIME 类型。图 2-3 显示了为文本文件(.txt)添加一个文件关联。您可以通过该接口读写与您的应用相关联的任何类型的文件(只要您的应用可以访问该文件夹)。

外部已知文件夹

如果您请求文档库、图片库、音乐库或视频库,您还可以通过使用KnownFoldersmediaServerDevices属性访问任何连接的媒体服务器上作为存储位置的文件。请注意,您在设备上看到的文件的范围基于您指定的功能,这意味着如果您仅声明音乐库功能,则您只能看到您所连接的媒体服务器上的音乐文件。另请注意,无论您指定什么功能,您都不会在所连接的服务器的“文稿”库中看到文稿。

您也可以将您的家庭组视为存储位置。与mediaServerDevices的结果一样,您的应用只能看到在其清单中声明为功能的库。这也不提供对家庭组文档库的访问。

最后,您的应用可以通过KnownFoldersremovableDevices属性访问存储在可移动介质中的文件。removableDevices要求您的应用具有为其显式定义的功能,并且应用只能看到这个位置中已经在前面显示的声明部分的清单中声明为受支持的文件类型关联的文件。

下载文件夹

Downloads文件夹是一个特例;它提供自己的类作为KnownFolders的对等体,用于只写访问。表 2-4 描述了DownloadsFolder的功能。

表 2-4。

DownloadsFolder Methods

| 方法 | 描述 | | --- | --- | | `CreateFileAsync(String)` | 在`Downloads`文件夹中创建新文件 | | `CreateFileAsync(String, CreationCollisionOption)` | 在`Downloads`文件夹中创建一个新文件,并指定如果文件夹中已经存在同名文件该如何处理 | | `CreateFolderAsync(String)` | 在`Downloads`文件夹中创建新的子文件夹 | | `CreateFolderAsync(String, CreationCollisionOption)` | 在`Downloads`文件夹中创建一个新的子文件夹,并指定如果文件夹中已经存在同名子文件夹时该如何操作 |

KnownFolders类中的位置不同,所有应用都可以访问用户的Downloads文件夹;但是正如你在表 2-5 中看到的,他们只有写权限。还要注意,写入这个文件夹的文件不会直接写入终端用户机器的Downloads文件夹根目录。相反,它们的作用域是直接为创建文件或文件夹的应用创建的文件夹。因此,文件夹名称就是应用的名称。以示例应用AllLearnings_html为例。清单 2-5 显示了一个叫做btn_createfile_download的 UI 按钮的click事件的事件处理程序。点击按钮在我的机器上的以下位置创建一个文件:Edward Moemeka\Downloads\AllLearnings_html\testfile.txt(见图 2-4 )。

A978-1-4302-5081-4_2_Fig4_HTML.jpg

图 2-4。

Downloads folder root for the AllLearnings_html application Note

文件夹位置AllLearnings_html是实际文件夹名称的别名。如果将鼠标或键盘焦点放在导航栏上(图中显示当前文件夹位置的区域),就会显示文件的实际路径。在我的机器上是C:\Users\Edward\Downloads\8a2843c4-6e36-40f7-8966-85789a855fa8_xctxhvdp4nrje!App

Listing 2-5. Writing to the Downloads Folder

btn_createfile_download.onclick = function ()

{

Windows.Storage.DownloadsFolder.createFileAsync("testfile.txt")

.then(function (file)

{

Windows.Storage.FileIO.writeTextAsync(file,ⅵ

"this is the content of the file written to the downloads folder");

});

};

清单 2-4 中的代码使用在Windows.Storage名称空间中找到的DownloadsFolder类在用户的Downloads文件夹中创建一个文本文件testfile.txt

关于通过 ApplicationData 类存储的最终想法

不管您为文件 I/O 函数使用什么存储,ApplicationData对象提供了额外的服务来帮助管理它。例如,ApplicationData.SetVersionAsync允许您更改应用未来版本中使用的应用数据格式,而不会导致与应用以前版本的兼容性问题。

你也可以通过调用ApplicationData.ClearAsync()来使用ApplicationData清空你的 Windows 缓存。请注意,此函数会清除与应用相关的所有存储位置的数据。此函数存在一个针对指定位置的重载方法。

文件

通过新的 Windows APIs 的文件访问通过StorageFile类进行。这个类提供了与给定文件交互的异步机制。正如您在上一节中看到的,它与StorageFolder类协同工作,作为创建、修改、复制或删除文件的工具。你也看到了在大多数情况下你不必直接使用它。相反,WinRT for JavaScript 提供了一个助手类FileIOFileIO是一个静态类(意味着你不能创建它的实例),它为读写由IStorageFile接口的对象表示的文件提供了帮助方法。该界面是StorageFile功能的概括。表 2-5 提供了你可以用这个类做什么的完整列表。

表 2-5。

FileIO Methods

| 方法 | 描述 | | --- | --- | | `AppendLinesAsync(IStorageFile, IIterable(String))` | 向指定文件追加文本行 | | `AppendLinesAsync(IStorageFile, IIterable(String), UnicodeEncoding)` | 使用指定的字符编码向指定的文件追加文本行 | | `AppendTextAsync(IStorageFile, String)` | 向指定文件追加文本 | | `AppendTextAsync(IStorageFile, String, UnicodeEncoding)` | 使用指定的字符编码向指定的文件追加文本 | | `ReadBufferAsync` | 读取指定文件的内容并返回一个缓冲区 | | `ReadLinesAsync(IStorageFile)` | 读取指定文件的内容并返回文本行 | | `ReadLinesAsync(IStorageFile, UnicodeEncoding)` | 使用指定的字符编码读取指定文件的内容,并返回文本行 | | `ReadTextAsync(IStorageFile)` | 读取指定文件的内容并返回文本 | | `ReadTextAsync(IStorageFile, UnicodeEncoding)` | 使用指定的字符编码读取指定文件的内容,并返回文本 | | `WriteBufferAsync` | 将数据从缓冲区写入指定的文件 | | `WriteBytesAsync` | 将数据字节数组写入指定文件 | | `WriteLinesAsync(IStorageFile, IIterable(String))` | 将文本行写入指定文件 | | `WriteLinesAsync(IStorageFile, IIterable(String), UnicodeEncoding)` | 使用指定的字符编码将文本行写入指定的文件 | | `WriteTextAsync(IStorageFile, String)` | 将文本写入指定文件 | | `WriteTextAsync(IStorageFile, String, UnicodeEncoding)` | 使用指定的字符编码将文本写入指定的文件 |

窗口 8 锁定屏幕

您应该熟悉的另一个开发人员工作流元素是锁屏访问。锁屏可用于向用户呈现带有等待光标或消息的用户界面,同时具有长时间运行的启动序列的应用继续在后台加载。建议您在应用中包含一个锁定屏幕,作为与用户清晰交流的一种方式。鉴于锁屏的复杂性以及对应用可见性和用户与应用交互能力的潜在影响,我们认为 Windows 8 的设计者和 Windows 8 Store experience for developers 使其变得如此容易使用是一件好事。事实上,我们可以有把握地说,这是本书讨论的最不复杂的话题。

在开始编写代码之前,我们先来谈谈 Windows 8 应用寻求与锁屏交互的功能。图 2-5 显示了一个 Windows 8 锁定屏幕,屏幕上显示了您的应用信息可以出现的区域。

A978-1-4302-5081-4_2_Fig5_HTML.jpg

图 2-5。

Windows 8 lock screen

正如你所看到的,Windows 8 应用以小图标和相关数字(范围从 1 到 99)的形式出现在通知区域。它们也可能出现在时间部分的旁边,提供更详细的信息。此外,作为开发人员,您可以更改锁定屏幕的背景图像。

Note

用户可以决定允许哪些应用显示详细的状态信息,哪些显示简单的通知。有八个插槽:七个用于基本通知,一个用于详细状态。如果用户没有选择应用进行锁屏通知,它不会出现在锁屏屏幕上。

Windows 8 锁屏在你的应用中表现为静态的LockScreen类。锁屏交互也用BackgroundExecutionManagerBadgeUpdateManager。与LockScreen配合使用,这些类公开了为你的应用改变通知徽章(旁边有数字的小图标)的机制,请求访问锁定屏幕,读写锁定屏幕的背景图像,并向其发布通知更新。表 2-6 显示了LockScreen类的成员。

表 2-6。

LockScreen Class Members

| 方法 | 描述 | | --- | --- | | `getImageStream()` | 以数据流形式获取当前锁屏图像 | | `getImageFileAsync()` | 从一个`StorageFile`对象设置锁屏图像 | | `getImageStreamAsync()` | 从数据流中设置锁屏图像 | | `OriginalImageFile` | 获取当前的锁屏图像 |

使用 Windows 8 时,您必须习惯的一件事是将用户选择集成到功能展示中。在之前关于 Windows 8 工作原理的讨论以及“用户已知文件夹”部分中,当需要开始处理应用可用的本地数据存储之外的文件和文件夹时,您已经看到了这一点。这没有什么不同,尽管用户工作流要清晰得多。所有的锁屏交互都需要用户注册。要访问背景图像权限(用于显示图像或应用您选择的图像),您首先需要向用户请求权限。您可以通过调用静态类Windows.ApplicationModel.Background.BackgroundExecutionManager来实现。

使用requestAccessAsync功能,您可以提示用户在锁屏访问场景中注册您的应用,重点是它的背景图像。然而,为了让你的应用出现在锁定屏幕上,终端用户必须做更多的事情。您对此无能为力—用户需要更改系统设置,并专门选择您的应用(作为七个应用之一)来接收来自它的通知。图 2-6 显示了用户进行选择的 PC 设置屏幕。

A978-1-4302-5081-4_2_Fig6_HTML.jpg

图 2-6。

Lock screen personalization by an end user

清单 2-6 展示了一个应用如何请求访问锁定屏幕。

Listing 2-6. Requesting Access to the Lock Screen and Reading the Result of the Request

Windows.ApplicationModel.Background.BackgroundExecutionManager.requestAccessAsync()

.then(function (status)

{

switch (status)

{

case Windows.ApplicationModel.Background。*本文件迟交

BackgroundAccessStatus.allowedWithAlwaysOnRealTimeConnectivity:

txt_display.innerText = "This app is on the lock screen and has

access to Real Time Connectivity (always on).";

break;

case Windows.ApplicationModel.Background.BackgroundAccessStatus。*本文件迟交

allowedMayUseActiveRealTimeConnectivity:

txt_display.innerText = "This app is on the lock screen,ⅵ

and may have access to Real Time Connectivity.";

break;

case Windows.ApplicationModel.Background.BackgroundAccessStatus。*本文件迟交

denied:

txt_display.innerText = "This app is not on the lock screen.";

break;

case Windows.ApplicationModel.Background.BackgroundAccessStatus。*本文件迟交

unspecified:

txt_display.innerText = "The user has not yet taken any action。*本文件迟交

This is the default setting and the app is not on the

lock screen.";

break;

}

});

锁定屏幕上的任何应用都可以收到通知。徽章通知是在开始屏幕中显示通知的技术术语。要使用锁屏通知功能,您需要使用BadgeUpdateManager类。这个类可以更新应用的锁屏图标的内容,甚至可以显示文本(如果应用已经被配置为主要的锁屏应用,默认情况下是用户的日历)。清单 2-7 展示了如何使用BadgeUpdateManager的通知机制来更新应用的徽章。在本例中,您更新了显示在应用锁屏旁边的数字。第七章对这个主题有更详细的介绍。

Listing 2-7. Lock-Screen Badge Notification

var count = 0;

btn_updatebadge.onclick = function ()

{

count++;

var badge_xml = Windows.UI.Notifications.BadgeUpdateManager。*本文件迟交

getTemplateContent(Windows.UI.Notifications.BadgeTemplateType.BadgeNumber);

badge_xml.documentElement.setAttribute("value", count.toString());

var badge = new Windows.UI.Notifications.BadgeNotification(badge_xml);

var badge_updater = Windows.UI.Notifications.BadgeUpdateManager。*本文件迟交

createBadgeUpdaterForApplication();

badge_updater.update(badge);

};

应用栏

如果你过去使用过 Internet Explorer 和 Microsoft Word 等 Windows 应用,你可能会注意到 Windows 8 应用缺少了一些东西:主菜单。第一章谈到了这一点:Windows 8 应用中的主菜单已经被移除,取而代之的是应用栏。如前所述,应用栏是位于屏幕顶部和底部的区域,可以通过右键单击(如果你有鼠标)或触摸屏幕顶部或底部的滑动来激活。图 2-7 提供了一个应用栏外观的例子。

A978-1-4302-5081-4_2_Fig7_HTML.jpg

图 2-7。

App bar activated in an application

旧的 Windows 编程模型(如 Visual Studio 2012、Microsoft Word 和 Internet Explorer 等原生 Windows 应用)中的菜单对功能的公开方式没有任何限制。在很大程度上,顶层菜单具有高级导航和应用功能,而上下文菜单侧重于上下文感知功能。微软的指导方针是在现代 Windows 8 应用中保持同样的方法。你可以在 Sports 应用中看到这一点(它安装在默认的 Windows 8 安装中)。

第一章介绍了作者创建属性的概念,您创建了一个通常称为 expando 控件的简单实现。这是一个动态创建并注入占位符容器的 JavaScript 控件。在清单 1-11 和 1-12 的例子中,您使用这种技术创建了一个完全由 JavaScript 代码生成的 UI。Expando 控件是在应用中包含 UI 元素和行为组合的一种方式。

通过WinJS.UI名称空间公开的AppBar控件的功能如下。它抽象出了响应用户滑动和基于这些场景从顶部或底部向上或向下滑动的功能。清单 2-8 展示了如何使用data-win-control属性在页面上放置一个应用栏。

Listing 2-8. AppBar Control in a Document

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>TestAppBars</title>

<!-- WinJS references -->

<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />

<script src="//Microsoft.WinJS.1.0/js/base.js"></script>

<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>

<link href="TestAppBars.css" rel="stylesheet" />

<script src="TestAppBars.js"></script>

</head>

<body>

<section aria-label="Main content" role="main" style="margin-left: 100px;">

<p>This sample tests the app bar functionality</p>

</section>

<div data-win-control="WinJS.UI.AppBar" >

<button id="btn_add" data-win-control="WinJS.UI.AppBarCommand"

data-win-options="{id:'cmdAdd',label:'Add',icon:'add',section:'global',ⅵ

tooltip:'Add item'}">

</button>

</div>

</body>

</html>

注意在AppBar控件中,您使用了一个已经添加了控件装饰器的按钮。虽然理论上你可以在应用的应用栏中放置任何东西,但惯例是使用遵循 Windows 8 风格指南的按钮。您当然可以构建自己的 UI 控件来模仿用户期望的外观和感觉,但是为了简化,Windows Library for JavaScript(WinJS)框架包括了一个与AppBar一起工作的附加控件,称为AppBarCommand控件。AppBarCommand通过其data-win-options属性提供选项,允许您指定给定按钮的命令类型。清单 2-8 使用options属性为你添加到应用栏的按钮设置标签、名称和默认图标。该页面的 JavaScript 代码如清单 2-9 所示

Listing 2-9. Handling AppBarCommand Events

(function () {

"use strict";

WinJS.UI.Pages.define("/samples/AppBarSample/TestAppBars.html", {

ready: function (element, options) {

btn_add.onclick = function ()

{

var v = new Windows.UI.Popups.MessageDialog("this is a test");

v.showAsync();

};

}

unload: function () {

}

updateLayout: function (element, viewState, lastViewState) {

}

});

})();

对话

对话框是 Windows 应用中的中流砥柱。它们提供了一个简单,优雅,但功能强大的选择来启动完全成熟的窗口。对话框通常用于提示用户进行某些操作。但是在一些特殊的情况下(例如,当你希望将文件保存到用户选择的位置或者从用户选择的位置加载文件时),你可以使用几种对话框类型中的一种。

在 Windows 8 中,仅仅为了通知用户而使用的对话框是不被允许的。这是因为对话框本质上是模态的,这意味着当它们打开时,不允许与应用进行其他交互。这是 Windows 8 咒语中的一股入侵力量,如果你过度使用MessageDialog这样的模态对话框,你就有可能无法通过认证。(注意,构建自己的模态对话框和使用微软提供的内置控件一样糟糕。如果用户交互不再可能,因为你有一个打开的提示,这对微软的测试人员来说很容易失败。)

尽管如此,在某些情况下,模态对话框是必要的,比如当用户没有进一步的动作来防止应用失败时。在这种情况下,MessageDialog可能是一笔宝贵的资产。这个类的伟大之处在于,尽管它向最终用户呈现了一个接口,但是不需要在 UI 中布置任何东西来支持它。您在清单 2-9 中看到了一个使用MessageDialog类的例子。清单 2-10 扩展了MessageDialog的简单用法,这次展示了如何用标题和选择选项来定制它。

Listing 2-10. Using MessageDialog

(function ()

{

"use strict";

WinJS.UI.Pages.define("/samples/DialogSample/TestDialogs.html", {

// This function is called whenever a user navigates to this page. It

// populates the page elements with the app's data

ready: function (element, options)

{

btn_showdialog.onclick = function ()

{

var v = new Windows.UI.Popups.MessageDialog

("simple dialog with no options");

v.showAsync();

};

btn_showdialog_full.onclick = function ()

{

var v = new Windows.UI.Popups.MessageDialog

("Dialog with options", "Modal dialog");

var txt_dialogchoice =

document.getElementById("txt_dialogchoice");

v.commands.append(new Windows.UI.Popups。*本文件迟交

UICommand("Yes", function (command)

{

txt_dialogchoice.innerText = "User selected yes.";

}));

v.commands.append(new Windows.UI.Popups。*本文件迟交

UICommand("No", function (command)

{

txt_dialogchoice.innerText = "User selected no.";

}));

v.commands.append(new Windows.UI.Popups。*本文件迟交

UICommand("Maybe", function (command)

{

txt_dialogchoice.innerText = "User selected maybe.";

}));

v.showAsync();

};

}

});

})();

图 2-8 显示了当用户点击与btn_showdialog关联的 UI 中的按钮时代码的结果。

A978-1-4302-5081-4_2_Fig8_HTML.jpg

图 2-8。

Modal dialog using MessageDialog through AppBar

当用户点击btn_showdialog_full时,结果略有不同。图 2-9 显示模态对话框显示三个按钮和一个标题。

Note

API 将您可以添加到ModalDialog命令列表的命令数量限制为三个。试图追加更多会导致异常。有趣的是,文本本身并没有限制,所以当您创建对话框时,确保界面不会显得混乱和令人困惑是您的责任,这些对话框还可以提示用户进行进一步的操作。

A978-1-4302-5081-4_2_Fig9_HTML.jpg

图 2-9。

Advanced modal dialog using MessageDialog

消费组件

冒着对任何给定语言的能力进行激烈辩论的风险,我将在本节开始时声明,并非所有语言都生来平等。这个短语的流行用法通常被认为是对一种语言或另一种语言的一种隐藏的轻视,因为某种程度上它是次要的,但我指的是字面上的意思。语言被设计用来解决设计者所理解的特定问题。JavaScript 擅长于它擅长的领域。NET 语言在它们擅长的领域表现出色,当然,当与驱动它们发展的设计目标进行权衡时,本地语言也非常出色。因为这个事实,存在一种语言或技术可以覆盖而另一种语言不能覆盖的缺口。这与现代的 Windows 8 编程界面没有什么不同。

因为 Windows 主要是本机的(所有其他语言都是本机接口的投影,所以使用特定语言的开发人员可以继续使用他们选择的语言和技术),API 环境中的许多特性只保留在本机环境中。例如,音频缓冲数据目前只能通过本地编程呈现。因为。NET 平台提供了一个成熟的基于类型的系统,它是 C++之上的第二原生层,共享许多库和一个公共的 UI 范例。它不包含本机应用可用的所有功能,但足以造成一些重大损害。在 WinRT 的当前实现中,基于 JavaScript 的应用提供了最少的本机编程外围应用。尽管 JavaScript 提供了 WinRT 的许多功能,但它缺乏进行低级编程的能力,并且不能将功能作为组件公开给其他平台。

微软设计了现代的 Window 8 应用,以便应用可以共享组件,而不管组件是用什么技术开发的(目前,仅。NET 和 C++/Cx 可以创建可共享的组件)或开发消费者的技术。所有 Windows 8 应用都可以使用已定义为 Windows 组件的库。通过在应用引用区域添加对 Windows 组件的引用,可以在应用中使用 windows 组件。

让我们在一个例子中看看这是如何工作的。清单 2-11 从创建一个简单的。NET web 服务应用,它将两个数相加并将总和返回给调用者。

Listing 2-11. Web Service Written in .NET

using System;

using System.Web;

using System.Web.Services;

namespace NormalWeb

{

[WebService(Namespace = "http://tempuri.org/

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[System.ComponentModel.ToolboxItem(false)]

public class MathService : System.Web.Services.WebService

{

[WebMethod]

public CalculationResult AddTwoNumbers(int x, int y)

{

var start = DateTime.Now;

var result = x + y;

var duration = DateTime.Now.Subtract(start);

var calc_time_in_seconds = duration.TotalSeconds;

return new CalculationResult

{

CalculationTime = calc_time_in_seconds

Result = result

};

}

}

public class CalculationResult

{

public double CalculationTime { get; set; }

public int Result { get; set; }

}

}

与专门为 JavaScript 设计的普通 web 服务不同——或者至少是在考虑 web 标准的情况下设计的——该服务将结果作为复杂的 SOAP 类型返回。SOAP 是一种重型技术,用于在通过 HTTP 连接的两个系统之间来回传递数据。为了。NET 应用,这是一个很好的工具,因为。早在 21 世纪初,NET 的通信框架和 IDE 工具就是在考虑 SOAP 的基础上设计的。现在我们使用 REST 和 JSON 等技术与服务器通信。因为 SOAP 是一个重协议,它会发回相对较大的 XML 文档,所以您不必使用 JavaScript 应用来与该服务通信。所以,您创建了一个基于. NET 的 WinRT 组件,连接到它,并将基于AddTwoNumbers HTTP 的服务调用封装到一个您可以轻松调用的函数中;请参见清单 2-12。

Note

如果您有 Visual Studio,您可以通过使用该工具的添加服务引用功能,使用 C#轻松地连接到 web 服务。在这种情况下,您必须将连接的服务添加到MathServer名称空间。

Listing 2-12. C# Component to Encapsulate a Legacy SOAP Service Call

using System;

using System.Runtime.InteropServices.WindowsRuntime;

using System.Threading;

using System.Threading.Tasks;

using Windows.Foundation;

namespace MathComponent

{

public sealed class MathManager

{

public IAsyncOperation<int> AddTwoNumbers(int x, int y)

{

MathServer.MathServiceSoapClient client = new MathServer。*本文件迟交

MathServiceSoapClient();

return AsyncInfo.Run<int>(new Func<CancellationToken,ⅵ

Task<int>>(async delegate(CancellationToken token)

{

var resp = await client.AddTwoNumbersAsync(x, y);

return resp.Body.AddTwoNumbersResult.Result;

}));

}

}

}

注意,通过解开响应对象的内容并将其作为整数返回,而不是从AddTwoNumbers返回的基于 SOAP 的复杂类型,您抽象出了服务调用的更多复杂性。

最后一步是向您的 JavaScript 应用添加对新创建的MathComponent WinRT 组件的引用。添加后,项目中的所有 JavaScript 都可以使用该引用。您可以在 JavaScript 中调用AddTwoNumbers,如清单 2-13 所示。注意函数AddTwoNumbers在 JavaScript 中是如何被称为addTwoNumbers的;这说明。NET 版本的类以与 JavaScript 开发方法一致的方式自动投射到 JavaScript 中。这就是 Windows 8 开发的天才之处!

Listing 2-13. Calling a WinRT Component in JavaScript

var v = new MathComponent.MathManager();

v.addTwoNumbers(4, 17).then(function (answer)

{

if (answer > 0)

{

}

});

摘要

本章介绍了开发人员在开发 Windows 8 应用时必须掌握的一些核心工作流程。您应该已经了解了文件系统的读写以及存储位置。以下是您在第二章中所学内容的概述:

  • 存储位置的作用,以及某些位置在默认情况下对应用可用,而其他位置要求您通过应用清单显式声明功能。
  • 应用栏及其用途。请随意返回并查看说明如何创建和使用权限来控制屏幕的详细示例。
  • 对话框,它们在 Windows 8 中如何工作,以及在应用中何时使用和不使用这些设备。
  • 当具有长时间运行的启动序列的应用在后台加载时,锁定屏幕呈现具有等待光标或消息的用户界面。如果应用加载时间过长,锁屏还可以防止应用关闭。
  • 本地通知可用于通知用户应用中发生的事件。
  • Windows 运行时组件,以及如何使用它们来扩展 JavaScript 应用,使其具有可能无法使用的功能。