现实世界的-JavaScript-Windows8-应用开发教程-全-
现实世界的 JavaScript Windows8 应用开发教程(全)
一、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 开始屏幕。
图 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 的两种状态。左边的图像是默认状态;当会议临近时,瓷砖呈现出不同的外观(右图)。
图 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 应用的外观。
图 1-3。
A Windows 8 app
注意到少了什么吗?它是无处不在的关闭、最小化和最大化/恢复按钮。Windows 8 应用会一直占据整个屏幕。这条规则没有例外:即使计划构建一个简单的实用程序窗口,作为开发人员,您也必须考虑如何以减少负面空间的方式进行布局。这是一个棘手的问题,而且你的应用必须支持多种屏幕分辨率。在后面的章节中,当我们开始讨论风格指南和如何通过认证时,我们会对此进行更深入的探讨。
另一个在查看一个已启动的应用时可能出现的问题是,如何关闭它?传统的 Windows 开发通常将应用生命周期管理委托给用户,这意味着用户必须明确地单击右上角的关闭按钮。如果没有,应用将继续运行。Windows Essentials 等应用依赖于此。因为系统没有提供自动关闭不再使用(或已有一段时间未使用)的应用的机制,所以像 Windows Essentials 和 Skype 这样的应用可以将用户的关闭请求视为隐藏请求,并在后台以不可见的窗口继续运行。如果每个人都带着荣誉和同情心行事,这不会是一个问题,但它确实会产生安全问题并消耗系统资源——如果不是不必要的,那么至少在没有用户同意的情况下。
Windows 8 试图通过引入一种考虑用户和系统资源的应用生命周期管理模式来重新想象这种情况(见图 1-4 )。
图 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 开始屏幕。(在第六章中更详细地讨论了固定和二级瓷砖的概念。)
图 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 魅力的显露。请注意屏幕左侧的信息框,它显示日期时间信息以及网络和电池状态。
图 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 所示的网飞应用。
图 1-7。
Netflix app in landscape mode
同一系统上的同一应用,唯一的变化是从横向模式转换到纵向模式,改变了 UI,如图 1-8 所示。
图 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 现代应用编程界面的分层视图。
图 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 所示的布局。
图 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 显示了发生这种情况时用户界面的样子。
图 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 )的应用。
图 1-13。
Windows 8 app using a jQuery MessageDialog
alert
图 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 所示。
图 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 所示的页面。
图 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 所示。
图 1-16。
Application without styles
这个没有样式的 HTML 的例子只是触及了表面。考虑一下网站 www.csszengarden.com/
(见图 1-17 和 1-18 ),你会开始感觉到 CSS 有多强大,它对 HTML 文档的布局有多大的控制力(当你构建 HTML 文档时,它放弃了布局和外观的考虑,只专注于语义标记)。
图 1-18。
CSS Zen garden Under the Sea! template
图 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.process
或WinJS.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>
示例的第一部分创建了应用的总体布局。注意frame
的id
和div
。这是您执行的 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.st
ate 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_one
在callback_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
继续讨论清单 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();
}
});
};
前面的示例概括了在目标存储位置操作文件的活动。您允许用户传入存储位置、文件名,以及在编辑或创建文件的情况下,他们想要添加到文件中的内容。您创建了四个私有函数:createFileAsync
、editFileAsync
、readFileAsync
和deleteFileAsync
。(注意在函数名中使用了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
如果您想要创建和管理与当前登录用户相关的文件和文件夹,而不是用户所在的特定机器,那么roamingFolder
属性是合适的选择。漫游文件夹在用户登录的所有设备之间同步。(请注意,对可漫游的总存储配额有限制,对用于文件以便文件漫游的命名约定也有限制。)这种方法的好处在于,它允许应用的用户在不同设备之间无缝流动,而无需显式同步回服务器。将内容存储在文件夹中,让 Windows 8 处理剩下的事情。
应用的临时存储甚至比包存储更不稳定。存储在此位置的项目只能保证与应用当前运行的会话一样长。这里的文件可以由系统随时删除,甚至可以由用户使用磁盘清理工具手动删除。因此,这个位置的理想用途是作为应用中的会话缓存。
用户的已知文件夹
您的应用并不局限于处理应用的独立数据存储、包安装位置或刚才讨论的本地/漫游/临时位置中的文件。使用 Windows Runtime(WinRT)for JavaScript 构建的应用也可以通过Windows.Storage.KnownFolders
类访问文件夹。表 2-3 列出了为访问它们所代表的底层文件夹而定义的KnownFolders
属性。
表 2-3。
KnownFolders
Properties
为了让应用访问这些文件夹,它需要通过声明性地指示它打算使用一个存储位置而不是它可以自动访问的私有位置来询问用户。在 Windows 8 中,这是一个两步过程。首先,将您想要访问的目标 Windows 8 库启用为您的应用声明使用的功能。您可以通过应用清单的Capabilities
部分做到这一点。在一个标准的 WinRT for JavaScript Windows 8 应用中,应用的清单位于项目的根文件夹中,名为package.appxmanifest
(见图 2-1 )。
图 2-1。
Application manifest’s location in the project structure
当您打开这个文件时,您应该会看到五个选项卡。选择 Capabilities 选项卡将打开如图 2-2 所示的屏幕。
图 2-2。
Capabilities tab
选择任何一个库功能(文档库、音乐库、图片库或视频库)可通过适当的ApplicationData
属性访问相关的存储位置。“外部已知文件夹”一节讨论了如何为其他三个KnownFolders
属性(homeGroup
、mediaServerDevices
和removableDevices
)启用功能。
接下来,必须显式声明应用读取和写入的文件类型。您可以通过应用清单的“声明”选项卡来完成(参见图 2-3 )。
图 2-3。
Declarations tab
您需要向您的应用添加一个新的文件类型关联。关联必须包括文件扩展名(扩展名前面带有点),并且可以选择提供 MIME 类型。图 2-3 显示了为文本文件(.txt
)添加一个文件关联。您可以通过该接口读写与您的应用相关联的任何类型的文件(只要您的应用可以访问该文件夹)。
外部已知文件夹
如果您请求文档库、图片库、音乐库或视频库,您还可以通过使用KnownFolders
的mediaServerDevices
属性访问任何连接的媒体服务器上作为存储位置的文件。请注意,您在设备上看到的文件的范围基于您指定的功能,这意味着如果您仅声明音乐库功能,则您只能看到您所连接的媒体服务器上的音乐文件。另请注意,无论您指定什么功能,您都不会在所连接的服务器的“文稿”库中看到文稿。
您也可以将您的家庭组视为存储位置。与mediaServerDevices
的结果一样,您的应用只能看到在其清单中声明为功能的库。这也不提供对家庭组文档库的访问。
最后,您的应用可以通过KnownFolders
的removableDevices
属性访问存储在可移动介质中的文件。removableDevices
要求您的应用具有为其显式定义的功能,并且应用只能看到这个位置中已经在前面显示的声明部分的清单中声明为受支持的文件类型关联的文件。
下载文件夹
Downloads
文件夹是一个特例;它提供自己的类作为KnownFolders
的对等体,用于只写访问。表 2-4 描述了DownloadsFolder
的功能。
表 2-4。
DownloadsFolder
Methods
与KnownFolders
类中的位置不同,所有应用都可以访问用户的Downloads
文件夹;但是正如你在表 2-5 中看到的,他们只有写权限。还要注意,写入这个文件夹的文件不会直接写入终端用户机器的Downloads
文件夹根目录。相反,它们的作用域是直接为创建文件或文件夹的应用创建的文件夹。因此,文件夹名称就是应用的名称。以示例应用AllLearnings_html
为例。清单 2-5 显示了一个叫做btn_createfile_download
的 UI 按钮的click
事件的事件处理程序。点击按钮在我的机器上的以下位置创建一个文件:Edward Moemeka\Downloads\AllLearnings_html\testfile.txt
(见图 2-4 )。
图 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 提供了一个助手类FileIO
。FileIO
是一个静态类(意味着你不能创建它的实例),它为读写由IStorageFile
接口的对象表示的文件提供了帮助方法。该界面是StorageFile
功能的概括。表 2-5 提供了你可以用这个类做什么的完整列表。
表 2-5。
FileIO
Methods
窗口 8 锁定屏幕
您应该熟悉的另一个开发人员工作流元素是锁屏访问。锁屏可用于向用户呈现带有等待光标或消息的用户界面,同时具有长时间运行的启动序列的应用继续在后台加载。建议您在应用中包含一个锁定屏幕,作为与用户清晰交流的一种方式。鉴于锁屏的复杂性以及对应用可见性和用户与应用交互能力的潜在影响,我们认为 Windows 8 的设计者和 Windows 8 Store experience for developers 使其变得如此容易使用是一件好事。事实上,我们可以有把握地说,这是本书讨论的最不复杂的话题。
在开始编写代码之前,我们先来谈谈 Windows 8 应用寻求与锁屏交互的功能。图 2-5 显示了一个 Windows 8 锁定屏幕,屏幕上显示了您的应用信息可以出现的区域。
图 2-5。
Windows 8 lock screen
正如你所看到的,Windows 8 应用以小图标和相关数字(范围从 1 到 99)的形式出现在通知区域。它们也可能出现在时间部分的旁边,提供更详细的信息。此外,作为开发人员,您可以更改锁定屏幕的背景图像。
Note
用户可以决定允许哪些应用显示详细的状态信息,哪些显示简单的通知。有八个插槽:七个用于基本通知,一个用于详细状态。如果用户没有选择应用进行锁屏通知,它不会出现在锁屏屏幕上。
Windows 8 锁屏在你的应用中表现为静态的LockScreen
类。锁屏交互也用BackgroundExecutionManager
和BadgeUpdateManager
。与LockScreen
配合使用,这些类公开了为你的应用改变通知徽章(旁边有数字的小图标)的机制,请求访问锁定屏幕,读写锁定屏幕的背景图像,并向其发布通知更新。表 2-6 显示了LockScreen
类的成员。
表 2-6。
LockScreen
Class Members
使用 Windows 8 时,您必须习惯的一件事是将用户选择集成到功能展示中。在之前关于 Windows 8 工作原理的讨论以及“用户已知文件夹”部分中,当需要开始处理应用可用的本地数据存储之外的文件和文件夹时,您已经看到了这一点。这没有什么不同,尽管用户工作流要清晰得多。所有的锁屏交互都需要用户注册。要访问背景图像权限(用于显示图像或应用您选择的图像),您首先需要向用户请求权限。您可以通过调用静态类Windows.ApplicationModel.Background.BackgroundExecutionManager
来实现。
使用requestAccessAsync
功能,您可以提示用户在锁屏访问场景中注册您的应用,重点是它的背景图像。然而,为了让你的应用出现在锁定屏幕上,终端用户必须做更多的事情。您对此无能为力—用户需要更改系统设置,并专门选择您的应用(作为七个应用之一)来接收来自它的通知。图 2-6 显示了用户进行选择的 PC 设置屏幕。
图 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 提供了一个应用栏外观的例子。
图 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 中的按钮时代码的结果。
图 2-8。
Modal dialog using MessageDialog through AppBar
当用户点击btn_showdialog_full
时,结果略有不同。图 2-9 显示模态对话框显示三个按钮和一个标题。
Note
API 将您可以添加到ModalDialog
命令列表的命令数量限制为三个。试图追加更多会导致异常。有趣的是,文本本身并没有限制,所以当您创建对话框时,确保界面不会显得混乱和令人困惑是您的责任,这些对话框还可以提示用户进行进一步的操作。
图 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 应用,使其具有可能无法使用的功能。
三、整合布局和控制
Abstract
现代 Windows 用户界面编程总是涉及某种形式的控件,即以用户体验的形式封装可预测行为的可重用用户界面元素。Windows 8 并没有改变 Windows 开发的这一原则。在这一章中,您将了解到作为一名 Windows 8 JavaScript 开发人员,控件是如何以按钮、列表框等交互控件的形式向您展示的;和布局控件,您可以使用它们来构建应用用户界面的组织方式。
现代 Windows 用户界面编程总是涉及某种形式的控件,即以用户体验的形式封装可预测行为的可重用用户界面元素。Windows 8 并没有改变 Windows 开发的这一原则。在这一章中,您将了解到作为一名 Windows 8 JavaScript 开发人员,控件是如何以按钮、列表框等交互控件的形式向您展示的;和布局控件,您可以使用它们来构建应用用户界面的组织方式。
如第二章中所述,Windows 8 JavaScript 开发不同于 Windows 8 开发者可用的其他用户界面渲染引擎,它基于 HTML5。这意味着 Windows 8 应用的布局和控件首先要使用纯 HTML +CSS3。此外,使用前一章中概述的控件集成模式,应用开发人员可以使用新功能扩展 HTML5 布局引擎,将使用 WinJS 构建的 JavaScript 应用与 Windows 8 原生外观紧密联系起来。特别是,第二章讨论了应用栏,并展示了如何将它整合到你的应用用户界面中。
本章介绍了如何在 Windows 8 应用开发中整合布局和控件,您首先要创建一个项目。这个示例项目将作为一个模板,您可以从中构建基本的控件。本章讨论并解释了这些标准控件,然后讨论了数据驱动控件及其数据绑定的使用。
设置项目
第二章讨论了 Windows 8 开发的许多基础元素。本章开始时,您将快速浏览一个 Windows Runtime(WinRT)for JavaScript 项目。您创建了众所周知的“Hello World”应用——在本例中是“Hello Windows 8 with JavaScript”尽管您可以使用任何 HTML 兼容的应用(或者记事本,如果您愿意)来完成大部分开发和用户界面布局,但是您需要 Visual Studio 2012 来将您的应用编译到第二章中讨论的应用包中。Visual Studio 还为您提供了一些部署工作流自动化工具,对于为 Windows 应用商店构建应用的 Windows 8 开发人员来说,这些工具非常有价值。
首先,启动 Visual Studio 2012。图 3-1 显示了 Visual Studio 2012 IDE 在我的机器上的外观。如果您更改了 Visual Studio 2012 的配色方案或安装了各种其他技术的加载项,则它在您的计算机上可能会有所不同。
图 3-1。
Visual Studio 2012
本章没有花时间介绍 IDE 的各个部分。详细讨论可以参考 Adam Freeman (Apress 2012)的《Pro Visual Studio 2012》之类的书。出于本练习的目的,需要注意的重要部分是中心内容区域、左侧的工具箱以及右侧的解决方案资源管理器。稍后,您将创建一个新项目,并查看当项目在范围内时,屏幕上的这些区域是如何“点亮”的。这是因为,像 Visual Studio 2010 中的许多视图一样,它们是上下文感知的:根据所选择的内容(在某些情况下,中心内容区域中的文档当前是活动的),其他关联窗口的视图可能会发生变化。在继续下一步之前,花点时间体验一下 IDE 布局。您可以分离和重新附加几乎任何内容,也可以取消固定默认的侧边部分(解决方案资源管理器、工具箱),以最大化内容区域的空间。您甚至可以将整个内容区域拖到另一个屏幕并最大化它,有效地将整个屏幕用于内容,没有菜单、工具栏或干扰。
您可以通过选择文件➤新➤项目来创建新项目。在我的机器上,结果对话框看起来如图 3-2 所示。
图 3-2。
New Project dialog in Visual Studio 2012
Visual Studio 2012 使用使用率配置文件机制来突出用户感兴趣的活动,而不是用户不感兴趣的功能。根据您第一次运行 Visual Studio 2012 时的设置方式,您可能会被锁定为 C#开发人员、Web 开发人员、数据库开发人员或任何其他配置文件类型。在我的机器上,C#是我使用的默认配置文件;因此,当我启动新的项目活动时,Visual C#项目是我选择的最重要的内容。如果我想构建 Visual Basic、Visual C++、SQL Server,当然还有 JavaScript,我必须在“新建项目”对话框的“其他语言”部分中查找。
Visual Studio 2012 中的 C#配置文件包含许多 Visual Studio 允许您创建的项目类型。使用 C#,您可以创建从 Windows 服务到 Windows Phone 8 应用的任何东西。如今,像 Xamarin 这样的公司已经通过插件将 C# / Visual Studio 2012 平台提升到了一个新的水平,这些插件允许 C#开发人员甚至可以构建 iOS 和 Android 应用。图 3-3 显示了同一个对话框:这次 JavaScript 部分被展开,显示了通过 JavaScript 公开的 Windows Store 项目。
图 3-3。
New project templates for Windows 8 JavaScript apps
在“名称”文本栏中输入对您有意义的名称,以及项目文件的储存位置。然后单击 OK 按钮生成项目。图 3-4 显示了项目创建后的解决方案资源管理器窗口。
图 3-4。
Project items of a newly created WinJS project
正如所料,该项目包含 JavaScript、HTML 和 CSS 文件——包含默认图像只是为了显示磁贴图标,而不是应用的内容。清单 3-1 显示了默认 JavaScript 文件default.js
的内容。
Listing 3-1. Default JavaScript File of the New Project
// For an introduction to the Blank template, see the following documentation:
//
http://go.microsoft.com/fwlink/?LinkId=232509
(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();
})();
清单 3-2 显示了应用的相关 HTML 内容。
Listing 3-2. Default HTML File of the New Project
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>FirstApp</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>
<!-- FirstApp references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
</head>
<body>
<p>Content goes here</p>
</body>
</html>
现在您已经展示了一个项目示例,让我们继续讨论控件。在讨论使用数据绑定的数据驱动控件之前,先深入研究标准控件。
合并 HTML 控件
正如在第二章中提到的,使用 Windows JavaScript 库的一个主要好处是它本质上是纯 HTML。这一区别于在基于 C#或 C++的 Windows 8 应用中发现的纯粹 WinRT 的因素并不比在控件的情况下更明显。因为 WinJS 应用是使用 HTML、CSS 和 JavaScript 的 web 标准构建的,所以它们支持将 HTML 控件集用于应用布局、样式和交互性编程。
Note
可以在微软 MSDN 网站 http://msdn.microsoft.com/library/windows/apps/hh767345.aspx
上找到 Windows Store JavaScript 应用支持的 HTML 控件的完整列表。
了解控件
除了 HTML 控件之外,您还可以访问一些作为类投射到 WinJS 中的 WinRT 控件。这些类型的控件的一个例子是您在上一章中使用的AppBar
控件。注意,并不是所有的 WinRT 控件都被投射到 JavaScript 中。例如,命名空间Windows.UI.Xaml.Controls
中的Border
类,可以通过工具箱在 C++和 C#中获得,在 XAML 中,也可以在代码中作为可以实例化并附加到文档树的类获得。正如你在图 3-5 中看到的,尽管 WinJS 知道名称空间,但是它没有一个用于Border
的类。
图 3-5。
Lack of XAML controls in WinJS
最后,控件也可以是 JavaScript 类的形式。作为开发人员,您可以编写这些类来降低用户界面构造的复杂性,并增加布局的灵活性。WinJS 附带了许多这种控件,最著名的是ListView
控件。图 3-6 显示了默认网格应用模板上的内置ListView
控件。在图 3-3 所示的新建项目对话框中,选择 grid app 而不是 Blank App,可以创建一个 Grid App。
图 3-6。
A grid app running
应用控件
正如你可能想象的那样,尽管这些技术彼此之间有很大的不同,但在使用 JavaScript 构建 Windows 8 应用时,你可以互换使用它们。在清单 3-3 中,您向示例项目添加了一个应用栏和一个 HTML 按钮,然后使用 JavaScript 事件处理程序打开一个消息框,计算按钮被点击的次数。从修改用户界面开始。清单 3-3 显示了新版本的default.html
,增加了粗体字。
Listing 3-3. Example App with an App Bar Added
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>FirstApp</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>
<!-- FirstApp references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
</head>
<body>
<p>Using Controls from different paradigms</p>
<input id="btn_button_count" type="button" value="Click to count" />
<div id="appbar" data-win-control="WinJS.UI.AppBar">
<button id="btn_appbar_count" data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{id:'cmd', label:'Also count', icon:'placeholder'}"
type="button"></button>
</div>
</body>
</html>
您还可以更改表单的标题,使其更符合您的意图。如果您现在运行该应用,它应该看起来类似于图 3-7 。
图 3-7。
Using a simple HTML button in WinJS
右键单击屏幕(或从按钮上滑动以激活应用栏)会显示您添加到应用视图中的应用栏(参见图 3-8 )。
图 3-8。
The app bar in the example app
现在你已经修改了布局,你可以回到default.js
并进行必要的修改来启用计数行为。清单 3-4 处理纯 HTML 和 WinRT 应用栏按钮的click
事件。
Listing 3-4. Event Handling for Both WinJS and Pure HTML Controls
// For an introduction to the Blank template, see the following documentation:
//
http://go.microsoft.com/fwlink/?LinkId=232509
(function () {
"use strict";
WinJS.Binding.optimizeBindingReferences = true;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var _count = 0;
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());
}
_count = 0;
btn_button_count.onclick = function ()
{
_count++;
var mbox = new Windows.UI.Popups.MessageDialog("Button clicked; you clicked
something " + _count + " time(s)");
mbox.showAsync();
};
btn_appbar_count.onclick = function () {
_count++;
var mbox = new Windows.UI.Popups.MessageDialog("AppBar clicked; you clicked
something " + _count + " time(s)");
mbox.showAsync();
};
};
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();
})();
请注意,第三个控件MessageDialog
也在示例中使用。当用户点击 HTML 按钮或应用栏按钮时,_count
变量增加 1。允许这两种技术之间的这种透明性所必需的任何编组都是对您隐藏的。就开发人员而言,当用户点击应用栏按钮时,可以将其视为 HTML 按钮,使用标准的onclick
事件处理程序来处理点击的效果。稍后,当您查看应用栏按钮的其他一些事件时,您会看到这种集成是多么强大和无缝——这些事件对于标准的 HTML 按钮是不可用的。图 3-9 显示了点击应用栏按钮时应用的用户界面。
图 3-9。
Message dialog when the app bar button is clicked
当点击 HTML 按钮时,_count
变量增加并照常显示,但消息框显示其发起者(见图 3-10 )。消息框文本已从“点击了 AppBar,您点击了。 . .点击了“收件人”按钮,您点击了。 . . ."
图 3-10。
Message dialog when the HTML button is clicked
正如你从例子中看到的,整合标准的 HTML 控件相对简单——它的工作方式与 web 开发相同,所以如果你已经熟悉 HTML,你会很容易习惯这一点,甚至可以将基于 web 的应用移植到 Windows 8。对于单页应用(spa)来说尤其如此。SPAs 是托管在 web 上的 HTML 页面,基本上将所有功能整合到一个页面上。该页面调用它需要的任何后端服务,下载任何额外需要的 HTML 或 JavaScript,并在需要的地方本地存储内容。Gmail 就是一个很好的例子, Outlook.com
的邮件客户端也是。如果您使用过这些基于 web 的应用,您可能会注意到它们从不“空白”——这是浏览器因为服务器请求而清空屏幕的典型标志。诸如此类的网站广泛使用 HTML 和 JavaScript,是可以轻松移植到 Windows 8 的应用类型的重要示例(也是 Windows 8 JavaScript 开发能力的重要示例,因为这种应用的开发人员只需要一个版本的应用代码。)
合并 WinJS 控件
HTML 控件非常棒,从 UI 的角度来看,你可以将它们与标准的 HTML 特性结合使用来做 WinJS 控件能做的任何事情。但是如果你想快速启动并运行,WinJS 提供了许多控件。WinJS 控件使用与 Windows 应用商店应用相同的 HTML JavaScript 和 CSS 编写,因此它们可以与页面上的其他元素无缝集成。您可以在清单 3-3 中看到这一点。让我们给页面添加更多的控件:在清单 3-5 中,您添加了一个DatePicker
。
Listing 3-5. Using a DatePicker
Control in HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>FirstApp</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>
<!-- FirstApp references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
</head>
<body>
<p>Using Controls from different paradigms</p>
<input id="btn_button_count" type="button" value="Click to count" />
<div id="control_datepicker" data-win-control="WinJS.UI.DatePicker"></div>
<div id="appbar" data-win-control="WinJS.UI.AppBar">
<button id="btn_appbar_count" data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{id:'cmd', label:'Also count', icon:'placeholder'}"
type="button"></button>
</div>
</body>
</html>
新控件以粗体突出显示。图 3-11 显示添加了DatePicker
控件的 app。
图 3-11。
DatePicker control in use
从这个例子中,您应该注意到两件事。首先,data-win-control
属性中的控件名(自定义属性在第二章的中讨论过)与AppBar
控件共享相同的名称空间。这个WinJS.UI
命名空间是你可以找到所有 WinJS 控件的地方。第二,控件需要一个根元素来附加它们自己。在这种情况下,您使用一个div
,但是可以使用任何元素。尝试将清单 3-4 中使用的div
改为按钮,并再次运行该示例:您应该注意到整个控件周围有一个白条,其功能类似于按钮(意味着它是可点击的)。图 3-12 展示了这个诡异的 UI。
图 3-12。
DatePicker applied to a HTML button control
如果您将宿主控件切换到 input 元素,您会得到一个更奇怪的结果:显示一个输入文本框!这个故事的寓意是,要小心将控件属性应用于什么元素,因为它们仍然会试图呈现自己。使用div
或span
是理想的,因为当控件应用于它们时,它们似乎放弃了它们的标准行为。例如,基于 HTML 布局规则,一个div
应该在它自己的行上;但是正如你在图 3-11 中看到的,它的行为就像一个内嵌元素。
这就引出了清单 3-4 中讨论的“无缝集成”的一个优点。如果你看看页面的 UI,btn_appbar_count
的宿主不是一个div
而是一个 HTML 按钮。因此,你可以对两个按钮使用相同的onclick
事件;它们都是简单的 HTML 按钮。这就引出了这样一个问题:如果目的是呈现一个控件而不是基础元素,那么如何访问与该元素相关联的特定于控件的功能。例如,假设事件和属性可能与DatePicker
控件相关联是正确的,它告诉您当前在选择器中选择的日期以及选择何时改变。控件也可以有一些方法,可以调用这些方法以某种编程方式实现自动化。例如,AppBar
控件可能有你可以调用来显示和隐藏它的方法。为此,所有 WinJS 控件修饰的 HTML 元素在运行时都有一个winControl
属性。winControl
属性提供了从承载控件的 HTML 元素到实际 WinJS 控件的连接,并可用于访问与控件关联的任何事件、属性或方法。
整合 winControl
让我们用一些代码来修改正在进行的示例,以展示如何使用winControl
。您可以使用该属性来处理当显示或隐藏应用栏时触发的事件,但是首先您需要更新页面的用户界面。在清单 3-6 中,您添加了一些基本的样式来使页面内容居中,还添加了一个新的控件来显示应用栏的当前状态。
Listing 3-6. Handling App Bar States
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>FirstApp</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>
<!-- FirstApp references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
<style>
div {
padding: 5px;
}
.centered {
margin-top: 50px;
margin-left: auto;
margin-right: auto;
width: 900px;
box-shadow: 3px 3px 3px #000;
}
</style>
</head>
<body>
<div class="centered">
<p>Using Controls from different paradigms</p>
<input id="btn_button_count" type="button" value="Click to count" />
<div>
AppBar State: <span id="txt_appbarstate" >AppBar Hidden</span>
</div>
<div>
<div id="control_datepicker" data-win-control="WinJS.UI.DatePicker"></div>
</div>
</div>
<div id="appbar" data-win-control="WinJS.UI.AppBar">
<button id="btn_appbar_count" data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{id:'cmd', label:'Also count', icon:'placeholder'}"
type="button"></button>
</div>
</body>
</html>
这里没有什么特别的:您添加了一个新的div
,其中有一个span
,显示应用的应用栏的当前状态。您还添加了一些将页面内容居中并应用阴影的基本样式。图 3-13 显示了 UI 现在的布局。
图 3-13。
New layout of the page with styling
有趣的事情发生在后端,如清单 3-7 所示。
Listing 3-7. Referencing a WinJS Control from an HTML Element
// For an introduction to the Blank template, see the following documentation:
//
http://go.microsoft.com/fwlink/?LinkId=232509
(function ()
{
"use strict";
WinJS.Binding.optimizeBindingReferences = true;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var _count = 0;
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());
}
_count = 0;
btn_button_count.onclick = function ()
{
_count++;
var mbox = new Windows.UI.Popups.MessageDialog("Button clicked; you clicked
something " + _count + " time(s)");
mbox.showAsync();
};
btn_appbar_count.onclick = function ()
{
_count++;
var mbox = new Windows.UI.Popups.MessageDialog("AppBar clicked; you clicked
something " + _count + " time(s)");
mbox.showAsync();
};
appbar.winControl.addEventListener("aftershow", function ()
{
txt_appbarstate.innerText = "AppBar Showing";
});
appbar.winControl.addEventListener("afterhide", function ()
{
txt_appbarstate.innerText = "AppBar Hidden";
});
};
app.oncheckpoint = function (args)
{
};
app.start();
})();
注意,当连接事件处理程序时,这里使用了addEventListener
约定。这不是强制性的;但是如果您选择使用它,请注意事件名称的on
部分总是被排除在外。清单 3-8 显示了如果使用另一种方法,这两个事件处理程序会是什么样子。从例子中可以看出,应用栏显示或隐藏时的事件名称是onaftershow
和onafterhide
,即使使用addEventListener
时添加了aftershow
和afterhide
。这是惯例,所以一定要记住它或者坚持一种方法。对于其他的例子,你可以使用清单 3-8 中的方法。
Listing 3-8. Using the Event Handler Convention for Handling WinJS Control Events
appbar.winControl.onaftershow = function ()
{
txt_appbarstate.innerText = "AppBar Showing";
};
appbar.winControl.onafterhide = function ()
{
txt_appbarstate.innerText = "AppBar Hidden";
};
图 3-14 显示了打开应用栏时 FirstApp 的用户界面。
图 3-14。
UI for FirstApp when the app bar is open
winControl
运行时属性对于 WinJS 控件来说是一个不可避免的祸害,这是因为您迄今为止创建的控件的实例化方式。到目前为止,您已经通过用win-data-control
属性修饰目标 HTML 元素向页面添加了控件(并在需要时可选地添加了win-data-options
属性)。但是 WinJS 控件的伟大之处在于它们首先是类,因此可以在代码中实例化。以这种方式创建 WinJS 控件可确保您拥有实际控件实例的句柄。要看到这一点,您可以做一些小的代码更改。首先,在default.html
中,修改应用栏 HTML 以匹配清单 3-9。请注意,div
已被重命名为appbar_host
。
Listing 3-9. Changes to the App Bar Layout
<div id="``appbar_host
<button id="btn_appbar_count" data-win-control="WinJS.UI.AppBarCommand" data-win-
options="{id:'cmd', label:'Also count', icon:'placeholder'}" type="button"></button>
</div>
因为您已经从appbar_host div
中移除了data-win-control
声明,所以现在您所拥有的只是一个简单的 HTML div
,可以说没有“特殊功能”。这是测试winControl
是与附加到主机 HTML 元素的底层控件相关联的运行时属性的好时机。如果您通过 Visual Studio 界面运行代码(启用调试),您应该会得到如图 3-15 所示的错误。
图 3-15。
Error using winControl
on standard HTML elements
如果遇到这个对话框,单击 Break 并将鼠标放在winControl
属性上。正如所料,winControl
是未定义的——这意味着它不作为appbar_host
对象的属性而存在。
现在,让我们通过实例化一个新的AppBar
类实例来改变后面的代码。用清单 3-10 中的代码替换当前的onactivated
事件处理程序。
Listing 3-10. Instantiating an AppBar
Control in JavaScript
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
。
}
var appbar_instance = new WinJS.UI.AppBar(appbar_host);
appbar_instance.onafterhide = function ()
{
txt_appbarstate.innerText = "AppBar Hidden";
}
args.setPromise(WinJS.UI.processAll());
}
_count = 0;
btn_button_count.onclick = function ()
{
_count++;
var mbox = new Windows.UI.Popups.MessageDialog("Button clicked; you clicked
something " + _count + " time(s)");
mbox.showAsync();
};
btn_appbar_count.onclick = function ()
{
_count++;
var mbox = new Windows.UI.Popups.MessageDialog("AppBar clicked; you clicked
something " + _count + " time(s)");
mbox.showAsync();
};
appbar_host.winControl.onaftershow = function ()
{
txt_appbarstate.innerText = "AppBar Showing";
};
};
不是声明性地添加AppBar
控件,而是实例化它,传入将承载它的目标元素。所有 WinJS 控件都需要 HTML 元素作为构造器的第一个参数。这相当于向目标元素添加了data-win-control
属性(在本例中是appbar_host
)。如果控件具有您选择定义的选项,则可以使用构造函数的第二个参数(可选参数)。如果这样做,选项将被表示为一个 JSON 对象。例如,如果你要构造一个AppBarCommand
类,构造器的第二个参数将如清单 3-11 所示。
Listing 3-11. Example of Configuring Options from JavaScript
var appbarbutton_instance = new WinJS.UI.AppBarCommand(btn_appbar_count, { id: 'cmd', label:
'Also count', icon: 'placeholder' });
Note
WinJS 控件的关键是process
函数,特别是WinJS.UI.process
,不管它是通过 HTML 声明定义的还是在 JavaScript 中实例化的。当您使用声明性方法实例化 WinJS 控件时,这没有多大关系;但是重要的是要理解,必须在控件上调用process
来呈现它,并且控件在调用process
时呈现。是process
函数添加了winControl
运行时属性。在清单 3-10 中,JavaScript 进行了调用args.setPromise(WinJS.UI.processAll())
,它有效地遍历了 DOM,对任何有关联控件的元素调用进程。可以想象,在 JavaScript 实例化控件的情况下,创建和关联它们的调用必须发生在调用processAll
之前;所以一定要养成这样做的习惯。
潜入 WinJS 控件
WinJS 提供了几个控件,可以用来增强 Windows 8 JavaScript 应用。本节将详细介绍其中的几个,并举例说明如何在一般意义上使用它们。我建议查看 MSDN 的WinJS.UI
主题,了解更多关于具体控制的细节。不过,在你开始使用控件之前,让我们花点时间回顾一下在第一章中开始的页面和导航主题。这样做有两个原因:首先,页面是控件,即使它们没有像其他控件一样被实例化或使用(它们也是通过WinJS.UI
名称空间访问的);其次,页面是组织为每个控件创建的示例的好方法。
页
WinJS 导航框架既涉及页面的创建,也涉及一些应用的启动,以便让您能够启动并运行。在开始之前,让我们创建一个名为Demo
的新文件夹和另一个名为AppBarDemo
的文件夹。
要创建一个文件夹,从解决方案资源管理器中右键单击项目并从上下文菜单中选择添加,然后选择新建文件夹(参见图 3-16 )。
图 3-16。
Adding a new folder in Visual Studio 2012
将页面控件添加到项目中
在新创建的AppBarDemo
文件夹中,右键单击并选择 Add,这次从上下文菜单中选择 New Item。在弹出的添加新项目对话框中选择页面控件,如图 3-17 ,命名为AppBarDemo
,点击【添加】。
图 3-17。
Selecting the Page Control template
最终的解决方案资源管理器视图应该如图 3-18 所示。
图 3-18。
FirstApp project structure
这里要注意的是,使用页面控件模板创建页面不仅会添加页面的 HTML,还会添加 JavaScript 和 CSS。在AppBarDemo.html
中添加的默认 HTML 如清单 3-12 所示。
Listing 3-12. AppBarDemo UI
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>AppBarDemo</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="AppBarDemo.css" rel="stylesheet" />
<script src="AppBarDemo.js"></script>
</head>
<body>
<div class="AppBarDemo fragment">
<header aria-label="Header content" role="banner">
<button class="win-backbutton" aria-label="Back" disabled type="button"></button>
<h1 class="titlearea win-type-ellipsis">
<span class="pagetitle">Welcome to AppBarDemo</span>
</h1>
</header>
<section aria-label="Main content" role="main">
<p>Content goes here.</p>
</section>
</div>
</body>
</html>
相关的默认 JavaScript 如清单 3-13 所示。
Listing 3-13. AppBarDemo JavaScript
// For an introduction to the Page Control template, see the following documentation:
//
http://go.microsoft.com/fwlink/?LinkId=232511
(function () {
"use strict";
WinJS.UI.Pages.define
("/Demos/AppBarDemo/AppBarDemo.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) {
// TODO: Initialize the page here
。
}
,
unload: function () {
// TODO: Respond to navigations away from this page
。
}
,
updateLayout: function (element, viewState, lastViewState) {
/// <param name="element" domElement="true" />
// TODO: Respond to changes in viewState
。
}
});
})();
您可以看到,页面是通过调用WinJS.UI.Pages.define
(以粗体突出显示)并传入页面用户界面的路径(以/Demos/AppBarDemo/AppBarDemo.html
的形式)和一个表示页面的类来定义的(在本例中,您使用了一个包含ready
、unload
和updateLayout
函数的匿名对象)。这个类至少应该包含ready
方法,因为当页面完全初始化时会调用这个方法。
修改演示以说明页面
您需要重组原始的default.html
以使用突出显示的方法。default.html
文件现在应该如清单 3-14 所示。
Listing 3-14. Restructured default.html
Page
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>FirstApp</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>
<!-- FirstApp references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
</head>
<body>
<div id="control_pagehost" style="height:80%"></div>
<div>
<button id="btn_appbardemo">AppBar demo</button>
</div>
</body>
</html>
default.js
文件如清单 3-15 所示。
Listing 3-15. Restructured default.js
// For an introduction to the Blank template, see the following documentation:
//
http://go.microsoft.com/fwlink/?LinkId=232509
(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)
{
};
app.start();
})();
现在,您用新的布局和支持它的 JavaScript 代码来修改刚刚创建的AppBarDemo
页面。最初的 HTML 和 JavaScript 是为了实现基本功能而放在那里的样板代码。新代码增加了交互性和独特的用户界面布局。AppBarDemo.html
如清单 3-16 所示。
Listing 3-16. Restructured AppBarDemo.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>AppBarDemo</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="AppBarDemo.css" rel="stylesheet" />
<script src="AppBarDemo.js"></script>
<style>
div {
padding: 5px;
}
.centered {
margin-top: 50px;
margin-left: auto;
margin-right: auto;
width: 400px;
box-shadow: 3px 3px 3px #000;
}
</style>
</head>
<body>
<div class="centered">
<p>Using Controls from different paradigms</p>
<input id="btn_button_count" type="button" value="Click to count" />
<div>
AppBar State: <span id="txt_appbarstate">AppBar Hidden</span>
</div>
<div>
<div id="control_datepicker" data-win-control="WinJS.UI.DatePicker"></div>
</div>
</div>
<div id="appbar_host">
<button id="btn_appbar_count" type="button"></button>
</div>
</body>
</html>
最后,AppBarDemo.js
如清单 3-17 所示。
Listing 3-17. Restructured AppBarDemo.js
// For an introduction to the Page Control template, see the following documentation:
//
http://go.microsoft.com/fwlink/?LinkId=232511
(function () {
"use strict";
var _count = 0;
WinJS.UI.Pages.define("/Demos/AppBarDemo/AppBarDemo.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) {
_count = 0;
var appbarbutton_instance = new WinJS.UI.AppBarCommand(btn_appbar_count
,ⅵ
{ id: 'cmd', label: 'Also count', icon: 'placeholder', });
var appbar_instance = new WinJS.UI.AppBar(appbar_host);
btn_button_count.onclick = function ()
{
_count++;
var mbox = new Windows.UI.Popups.MessageDialog("Button clicked; you clicked
something " + _count + " time(s)");
mbox.showAsync();
};
appbar_instance.onbeforeshow = function ()
{
txt_appbarstate.innerText = "AppBar Showing";
}
appbar_instance.onbeforehide = function ()
{
txt_appbarstate.innerText = "AppBar Hidden";
}
appbarbutton_instance.onclick = function ()
{
_count++;
var mbox = new Windows.UI.Popups.MessageDialog("AppBar clicked; you clicked
something " + _count + " time(s)");
mbox.showAsync();
}
}
,
});
})();
将 AppBarDemo 连接到 default.html
在这一点上,如果您要运行这个示例,您会看到一个空白屏幕,这并不奇怪。这是因为页面尚未添加到default.html
。与典型的控件不同,PageControl
不需要遵循你在前面章节中看到的AppBar
的实例化模式。您可以通过多种方式查看刚刚创建的页面。然而,无论您使用哪种方法,您总是需要一个标准的 HTML 控件来承载它。这就是为什么在清单 3-14 中,您添加了一个新的div
,control_pagehost
。
在主屏幕上显示页面的最简单方法是调用
WinJS.UI.Pages.render("/demos/appbardemo/appbardemo.html",control_pagehost);
您希望仅在点击btn_appbardemo
按钮时这样做,否则显示默认介绍页面。让我们修改 UI 来解决这个问题。清单 3-18 显示了应用的新用户界面。您向应用首次启动时显示的content_pagehost div
添加一些内容,并将显示的内容居中。
Listing 3-18. Modified User Interface Layout for the FirstApp Example
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>FirstApp</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>
<!-- FirstApp references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
</head>
<body>
style="height:80%; width:500px; margin-left:auto;``style="height:80%; width:500px; margin-left:auto;
margin-right:auto"
>
Welcome to the WinJS controls demo, click a button to open a page that runs the demo
</div>
<div>
<button id="btn_introduction">Introduction</button>
<button id="btn_appbardemo">AppBar demo</button>
</div>
</body>
</html>
您还向页面添加了一个新按钮,该按钮总是将您带回到应用的默认状态。清单 3-19 显示了这个页面的 JavaScript 代码。我还擅自清理了 Visual Studio 自动生成的所有无关内容。
Listing 3-19. Modified JavaScript for FirstApp
(function ()
{
"use strict";
WinJS.Binding.optimizeBindingReferences = true;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args)
{
btn_appbardemo.onclick = function ()
{
WinJS.UI.Pages.render("/demos/appbardemo/appbardemo.html", control_pagehost);
}
btn_introduction.onclick = function ()
{
control_pagehost.innerText = "Welcome to the WinJS controls demo, click a
button to open a page that runs the demo";
}
};
app.start();
})();
当您运行这个应用时,默认状态应该如图 3-19 所示。该图像已被裁剪以减少页面空间。
图 3-19。
Changing the host for the DatePicker
control from a div
to a button
使用空函数
在您使用的实现中有一个小错误,它与 HTML 元素中内容的放置有关,这些元素被指定为 WinJS 控件的宿主。如果您还记得的话,前面有一个警告告诉您不要使用呈现自己用户界面的 HTML 元素,因为 WinJS 呈现过程不会干扰这类元素的正常呈现。当您将DatePicker
控件的宿主从div
更改为按钮时,您就看到了这样的例子。当您运行应用时,按钮用户界面包围了DatePicker
,用户仍然可以作为按钮与元素进行交互!当你点击 AppBar 演示按钮时,例子中的错误就暴露出来了(见图 3-20 所示)。
图 3-20。
Bug revealed
您应该立即发现问题。页面按预期呈现,但没有篡改已经在div
中的内容!为了解决这个问题,WinJS 提供了一个实用函数WinJS.Utilities.empty
,可以用来清除元素的内容。(是的,这也可以使用标准的 JavaScript/DOM 策略来完成。事实上,你已经在使用这样的策略了。设置control_pagehost.innerText
会清除div
中的所有内容,并替换为您指定的文本)。
empty
函数将您希望清除子内容的目标元素作为参数。清单 3-20 显示了事件处理程序的修改代码。
Listing 3-20. Modified Code for Event Handler
btn_appbardemo.onclick = function ()
{
WinJS.Utilities.empty(control_pagehost);
WinJS.UI.Pages.render("/demos/appbardemo/appbardemo.html", control_pagehost);
}
现在,当你点击 AppBar 演示按钮时,你的屏幕应该如图 3-21 所示。
图 3-21。
Demo after clicking the AppBar Demo button
从页面内导航
在根用户界面中设计导航菜单的场景中,使用这种方法非常好。因为在default.html
中直接有按钮,当被点击时会调用事件处理程序,事件处理程序使用render
将PageControl
渲染到目标div
上,所以在导航方面你不用太担心。但是如果没有根导航菜单呢?在如此高的层次上定义菜单并不总是理想的,在很多情况下,一个页面可能希望直接导航到另一个页面。
正是为了这样的场景,才创建了WinJS.Navigation
API。简而言之,WinJS 导航框架提供了一个全局函数和事件处理程序,允许您的根页面(承载您的控制主机的页面—在本例中为default.html
)在它所承载的PageControl
JavaScript 中请求导航时进行侦听。然后,您可以调用render
,传入所请求的页面。使用示例代码,不用每次添加新按钮时都调用empty
和render
,你可以创建一个调用empty
的函数,然后一般地调用render
(见清单 3-21)。
Listing 3-21. Global Navigation Handling
(function ()
{
"use strict";
WinJS.Binding.optimizeBindingReferences = true;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args)
{
btn_appbardemo.onclick = function ()
{
WinJS.Navigation.navigate("/demos/appbardemo/appbardemo.html");
}
btn_introduction.onclick = function ()
{
control_pagehost.innerText = "Welcome to the WinJS controls demo, click a
button to open a page that runs the demo";
}
};
WinJS.Navigation.addEventListener("navigated", function (args)
{
WinJS.Utilities.empty(control_pagehost);
WinJS.UI.Pages.render(args.detail.location, control_pagehost);
});
app.start();
})();
从应用的任何页面,调用navigate
将触发高亮显示的事件处理函数。这个函数只是将location
属性(相当于传递给navigate
函数的值)传递给render
。
本章的其余部分假设向default.html
添加适当的按钮,以及导航到与您创建的示例相关联的演示页面所需的适当的 JavaScript 事件处理程序。我不包括default.html
或default.js
的变更列表。另请注意,不会有与default.html
相关的数字。
打电话给我
AppBar
控件代表用于显示命令的应用工具栏。在本章的前面,您看到了如何监听应用栏何时打开或关闭。您也可以在应用栏打开或关闭之前收听事件。我发现之前版本的事件出现得更快;因此,从可用性的角度来看,如果您计划根据应用栏可见性的变化来更新用户界面,那么使用它们可能会更好。如果需要以编程方式显示或隐藏应用栏,可以分别使用show()
或hide()
方法。应用栏还提供了显示或隐藏特定命令的灵活性。
让我们修改您到目前为止创建的 AppBar 来突出显示其中的一些特性。清单 3-22 将应用栏演示变成了一个简单的三个问题的调查,其中应用栏命令代表问题。
Listing 3-22. Code for the App Bar Demo Page
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>AppBarDemo</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="AppBarDemo.css" rel="stylesheet" />
<script src="AppBarDemo.js"></script>
<style>
div {
padding: 5px;
}
.panel {
width: 400px;
}
.centered {
margin-top: 50px;
margin-left: auto;
margin-right: auto;
}
.shadow {
box-shadow: 3px 3px 3px #000;
}
</style>p
</head>
<body>
<div class="centered panel">
<p>AppBar Survey</p>
<div>
Question: <span id="txt_appbarquestion">AppBar Hidden</span>
</div>
<div>
<button id="btn_startsurvey">Start Survey</button>
</div>
</div>
<div id="div_overlay" style="position:absolute; left:0px; top:0px; width:100%;
height:100%; opacity:.65; background-color:blue;visibility:collapse">
<div class="centered shadow panel" style="margin-top:200px; background-color:white;
width:400px; height:250px; border-radius:5px;" >
<span class="centered" style="color:black; font-size:xx-large">Pick a question
</span>
</div>
</div>
<div id="appbar_host">
<button id="btn_appbar_question1" data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{ id: 'cmd1', label: 'Question 1', icon: 'placeholder', }">
</button>
<button id="btn_appbar_question2" data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{ id: 'cmd2', label: 'Question 2', icon: 'placeholder', }">
</button>
<button id="btn_appbar_question3" data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{ id: 'cmd3', label: 'Question 3', icon: 'placeholder', }">
</button>
</div>
</body>
</html>
这个页面的 JavaScript 代码如清单 3-23 所示。
Listing 3-23. An AppBar Example
(function ()
{
"use strict";
WinJS.UI.Pages.define("/Demos/AppBarDemo/AppBarDemo.html", {
ready: function (element, options)
{
var appbarbutton_question1 =
document.getElementById("btn_appbar_question1").winControl;
var appbarbutton_question2 =
document.getElementById("btn_appbar_question2").winControl;
var appbarbutton_question3 =
document.getElementById("btn_appbar_question3").winControl;
var div_overlay = document.getElementById("div_overlay");
var appbar_instance = new WinJS.UI.AppBar(appbar_host);
var command_array = new Array(appbarbutton_question1);
appbar_instance.showOnlyCommands(command_array);
appbar_instance.onbeforeshow = function ()
{
div_overlay.style.visibility = "visible";
}
appbar_instance.onbeforehide = function ()
{
div_overlay.style.visibility = "collapse";
}
appbarbutton_question1.onclick = function ()
{
txt_appbarquestion.innerText = "What is your name?";
appbar_instance.hide();
command_array = new Array(appbarbutton_question1, appbarbutton_question2);
appbar_instance.showOnlyCommands(command_array);
}
appbarbutton_question2.onclick = function ()
{
txt_appbarquestion.innerText = "What is your age?";
appbar_instance.hide();
command_array = new Array(appbarbutton_question1, appbarbutton_question2
,ⅵ
appbarbutton_question3);
appbar_instance.showOnlyCommands(command_array);
}
appbarbutton_question3.onclick = function ()
{
txt_appbarquestion.innerText = "Do you know what lies beyond the shadow
of the statue?";
appbar_instance.hide();
}
btn_startsurvey.onclick = function ()
{
btn_startsurvey.style.visibility = "collapse";
appbar_instance.show();
}
}
,
});
})();
示例调查中的每个问题都是在用户点击与之相关的适当的AppBarCommand
时触发的。当应用的应用栏打开时,一个透明的div
覆盖了整个屏幕,并指示该做什么。从应用栏中选择一个问题会强制关闭该栏,并显示所选的问题。
日期选择器
DatePicker
控件只允许用户选择一个日期。它与您以前可能接触过的任何其他标准日期选择器控件没有什么不同。在清单 3-5 中,您看到了DatePicker
在页面上的样子,但是您从来没有从编程的角度考虑过加入它的一些特性。基本上,与大多数日期选择控件一样,WinJS.UI.DatePicker
提供了一个用于访问当前选定日期的current
属性和一个在current
属性被更改时通知您的onchange
事件。清单 3-24 展示了一个简单的日期选择器演示程序的用户界面,展示了该控件的一些特性。
Listing 3-24. User Interface for a Simple Date Picker
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>DatePickerDemo</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="DatePickerDemo.css" rel="stylesheet" />
<script src="DatePickerDemo.js"></script>
<style>
div {
padding: 5px;
}
.panel {
width: 400px;
}
.centered {
margin-top: 50px;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<div class="centered panel div">
<div>
Select Your Date of Birth:<div id="datepicker_host"></div>
</div>
<div>
You are <span id="txt_dob">unknown</span> years old
</div>
</div>
</body>
</html>
清单 3-25 提供了演示页面的 JavaScript 代码。
Listing 3-25. JavaScript for the Date Picker Demo Page
(function () {
"use strict";
WinJS.UI.Pages.define("/Demos/DatePickerDemo/DatePickerDemo.html", {
ready: function (element, options)
{
var date_picker = new WinJS.UI.DatePicker(datepicker_host);
date_picker.maxYear = 1995;
var today = new Date();
var dob = null;
var current_date = Date.parse(Date.now().toString());
var age = 0;
date_picker.onchange = function ()
{
dob = date_picker.current;
age = today.getFullYear() - dob.getFullYear();
txt_dob.innerText = " " + age + " ";
}
}
,
});
})();
布局相对简单,如图 3-22 所示。根据用户的选择,您计算用户的年龄。使用DatePicker
的maxYear
属性,你可以将应用的使用限制在 18 岁或以上。
图 3-22。
Using the DatePicker
control
图 3-23 显示了用户做出选择后的 UI。
图 3-23。
UI following a selection
弹出控件
Flyout
控件可以用来显示一个临时的轻量级用户界面,当用户点击超出其边界的屏幕区域时,这个界面就会消失。它非常适合以非交互的方式显示信息,或者整合功能,否则会因为过多的控件而使屏幕变得混乱。之前,您以问卷的形式创建了一个演示。清单 3-26 展示了如何使用一个Flyout
控件来实现一个单独的问题(以及相关的答案)。
Listing 3-26. Implementing an Individual Question Using a Flyout
Control
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>FlyoutDemo</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="FlyoutDemo.css" rel="stylesheet" />
<script src="FlyoutDemo.js"></script>
<style>
div {
padding: 5px;
}
.panel {
width: 400px;
}
.centered {
margin-top: 50px;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<div class="centered panel div">
<div>
How often do you perform a strenuous activity?<button id="btn_answer">
Select an answer...</button>
</div>
<div id="control_flyouthost" style="width:150px;">
<div>
<input type="radio" value="Once a day" name="answer" >Once a day
</div>
<div>
<input type="radio" value="Once a week" name="answer"/>Once a week
</div>
<div>
<input type="radio" value="Once a month" name="answer"/>Once a month
</div>
<div>
<input type="radio" value="Once a year" name="answer"/>Once a year
</div>
</div>
</div>
</body>
</html>
清单 3-27 提供了 HTML 的 JavaScript 代码。应用代码和Flyout
控件本身一样简单明了。当按钮被点击时,调用Flyout
实例上的show
,传入 HTML 元素作为它的锚点,以及相对于锚点的位置。Flyout
只不过是 HTML 的容器(就像用 JavaScript 显示和隐藏的div
)。
Listing 3-27. Code Behind for the Flyout Demo Page
(function () {
"use strict";
WinJS.UI.Pages.define("/Demos/FlyoutDemo/FlyoutDemo.html", {
ready: function (element, options) {
var flyout = new WinJS.UI.Flyout(control_flyouthost);
btn_answer.onclick = function ()
{
flyout.show(btn_answer, "bottom");
}
}
,
});
})();
图 3-24 显示了这个视图运行时的样子。
图 3-24。
Example of a Flyout
control
HtmlControl
还记得大约 20 页前我讨论页面和PageControl
如何工作的时候吗?当你创建你的第一个页面时,我解释了有几种方法可以在屏幕上显示一个页面。一种方法是使用render
函数,这在那一节已经展示过了。它从您指定的页面获取 HTML、CSS 和 JavaScript,并智能地将其注入宿主控件范围内的宿主页面。这种工作方式很有趣,但在大多数情况下,它需要一些额外的跑腿工作来处理empty
功能和所有的WinJs.Navigation
喧嚣。
对这一点来说,这是一个非常漂亮的简写。有了它,你可以很容易地创建一个导航应用,而不需要像 JavaScript 那样做太多其他事情——这是render
方法明显需要的。清单 3-28 通过将清单 3-26 中的弹出演示加载到视图中,创建了一个简单的HtmlControl
演示。
Listing 3-28. Example of HtmlControl
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>HtmlControlDemp</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="HtmlControlDemo.css" rel="stylesheet" />
<script src="HtmlControlDemo.js"></script>
</head>
<body>
<div class="HtmlControl fragment">
<div data-win-control="WinJS.UI.HtmlControl" data-win-options=
"{uri:'/demos/flyoutdemo/flyoutdemo.html'}"></div>
</div>
</body>
</html>
结果应该与图 3-24 完全相同,除了一个用于导航到演示的额外按钮(见图 3-25 )。
图 3-25。
Adding an extra navigation button
菜单
Menu
控件与Flyout
相对相似,以至于您为其构建的示例应用本质上与您之前创建的 survey 相同,但带有一个Menu
控件。您可能会想,如果它们如此相似,为什么还需要两个独立的控件。答案是它们之间有一个主要的区别:尽管Flyout
控件支持任何添加到其中的有效元素,但是Menu
控件只允许添加MenuCommand
控件。这样,Menu
控件有点像你在本章前面看到的AppBar
控件。
两个控件都通过一个command
属性添加命令,该属性只接受特定类型的控件(也只接受特定类型的 HTML 元素——两个控件都要求它们的命令对象驻留在按钮元素中)。清单 3-29 显示了菜单演示的用户界面。
Listing 3-29. UI for the Menu Demo
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>MenuDemo</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="MenuDemo.css" rel="stylesheet" />
<script src="MenuDemo.js"></script>
<style>
div {
padding: 5px;
}
.panel {
width: 400px;
}
.centered {
margin-top: 50px;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<div class="centered panel div">
<div>
How often do you perform a strenuous activity?<button id="btn_answer">
Select an answer...</button>
</div>
<div id="control_flyouthost" style="width:150px;">
<button id="control_daily" data-win-control="WinJS.UI.MenuCommand"
data-win-options="{label:'daily'}"></button>
<button id="control_weekly" data-win-control="WinJS.UI.MenuCommand"
data-win-options="{label:'weekly'}"></button>
<button id="control_monthly" data-win-control="WinJS.UI.MenuCommand"
data-win-options="{label:'monthly'}"></button>
<button id="control_yearly" data-win-control="WinJS.UI.MenuCommand"
data-win-options="{label:'yearly'}"></button>
</div>
</div>
</body>
</html>
清单 3-30 显示了这个 HTML 的 JavaScript 代码。
Listing 3-30. JavaScript for the HTML Example
(function () {
"use strict";
WinJS.UI.Pages.define("/Demos/MenuDemo/MenuDemo.html", {
ready: function (element, options) {
var menu = new WinJS.UI.Menu(control_flyouthost);
btn_answer.onclick = function ()
{
menu.show(btn_answer, "bottom");
}
}
,
});
})();
图 3-26 显示了应用运行时页面的外观。
图 3-26。
The running application
除了使用不同的控件(飞出式演示使用单选按钮,而这个演示使用装饰为WinJS.UI.MenuCommand
对象的强制按钮元素)之外,这个例子的功能本质上是相同的。选择使用哪一种最终取决于开发人员和他们想要设计的风格。显然,弹出控件给了你更多的灵活性和力量,但是对于小的情况(为了让用户和一个共同的主题保持一致),标准的Menu
控件可能不会太差。因为您使用的是命令而不是自由格式的 HTML,并且因为您没有以任何方式操纵命令,所以在本例中您以声明方式添加了控件。
评级控件、设置弹出按钮、时间选择器和切换开关
WinJS 包含许多其他相当简单的控件,本节没有详细介绍。大多数控件的功能应该是不言自明的,所以与其深入研究每个控件,不如让我们创建一个示例来说明这些控件。首先,您需要构建另一个页面来承载其余的控件。在这种情况下,您创建了一个页面,该页面在启动时利用了弹出的设置。清单 3-31 定义了用户界面。
Listing 3-31. UI for a Page Utilizing a Settings Flyout
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>OtherControls</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="OtherControls.css" rel="stylesheet" />
<script src="OtherControls.js"></script>
<style>
div {
padding: 5px;
color:black;
}
.panel {
width: 400px;
}
.centered {
margin-top: 50px;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<div class="OtherControls fragment">
<div id="control_settingsflyouthost" style="background-color:gray">
<div>Thanks for checking out our demos</div>
<div>
Rate this application: <div id="control_ratehost"></div>
</div>
<div>
Enter your date of birth: <div id="control_timepickerhost"></div>
</div>
<div>
Contact me in the future: <div id="control_toggleswitchhost"></div>
</div>
<button id="btn_save" >Save</button>
</div>
</div>
</body>
</html>
清单 3-32 显示了这个 HTML 的 JavaScript 代码。
Listing 3-32. JavaScript Code for the Example HTML
(function ()
{
"use strict";
WinJS.UI.Pages.define("/Demos/OtherControls/OtherControls.html", {
ready: function (element, options)
{
var settings_flyout = new WinJS.UI.SettingsFlyout(control_settingsflyouthost);
settings_flyout.show();
var ratings = new WinJS.UI.Rating(control_ratehost);
var timepicker = new WinJS.UI.TimePicker(control_timepickerhost);
var toggle_switch = new WinJS.UI.ToggleSwitch(control_toggleswitchhost);
btn_save.onclick = function ()
{
var result = "You rated this app a " + ratings.userRating
+ "\nYou want us to contact you?" + toggle_switch.checked
+ "\nYou rated us on " + timepicker.current.toString();
var mbox = new Windows.UI.Popups.MessageDialog(result);
mbox.showAsync();
}
}
,
});
})();
与本章中的其他例子不同,清单 3-32 以编程方式创建用于该部分的控件。正如您在 JavaScript 中看到的,您只是从目标控件中读取一些公共属性;当用户点击 Save 按钮时,您在一个MessageBox
中向他们呈现他们选择的值。当选择该页面时,产生的屏幕应如图 3-27 所示。
图 3-27。
Screen after the user selects the MessageBox
最后,图 3-28 显示了点击保存按钮时会发生什么。在这种情况下,应用被用户☺.评为 5 级
图 3-28。
Screen after the user clicks the Save button
高级控制
上一节介绍了在使用 WinJS 控件堆栈时可以使用的一些基本控件。这些控件以相对相似的方式工作,只需要声明(或在代码中实例化)控件,就可以绘制其用户界面。
本节介绍由 WinJS 框架提供的更复杂的控件。这些控件包含数据,以便创建它们的用户界面。可以说,仅仅宣布这样的控制不会“达成协议”。您还需要创建一个表示控件显示的数据的数据对象列表,定义一个表示列表中每一项(单个数据对象)的视图的模板,并将两者绑定在一起,以便生成的视图显示给定模板的列表。这样做很好地介绍了 Windows 8 应用可用的绑定功能。本章的其余部分将带您了解翻转视图、列表视图和语义缩放,为您使用常见的高级控件做好准备。
使用高级控件
让我们先简单解释一下 Windows 8 应用开发的模板化方法。这种方法并不是起源于 Windows 8 编程(或 Windows 编程,就此而言),它分两步工作。首先,开发人员定义一个模板,它本质上是一组离散的用户界面元素,以某种对应用有意义的模式进行布局。如果这个概念听起来很熟悉,那是因为控件在这方面类似于模板。控件的不同之处在于,它们可以被实例化,并且可以独立存在而没有数据。模板不能,因为它们代表数据—模板是数据元素的用户界面视图。以清单 3-33 中定义的person
对象为例。
Listing 3-33. Code for a person
Object
var person = new {
FirstName: "Azuka"
,
LastName: "Moemeka"
,
Age: 6.5
,
ImageID: 1234
,
};
如果您要直观地表示这一点,它可能会以水平方式显示在名和姓的列表中(见图 3-29 )。它也可能以细节视图的形式出现,带有真实人物的图像。
图 3-29。
The person
object
这些视图中的每一个都从概念上表示相同的数据。一个使用person
对象的名字和姓氏属性;另一个可能使用该人的姓氏、年龄和指向该人照片的图像。要让这些视图工作,您必须将对象实例的属性与表示它们的可视化控件连接起来。
图 3-29 使用两个span
元素:一个表示名,一个表示姓。在运行时,您将适当的span
的innerText
属性设置为您想要与之关联的属性。清单 3-34 显示了图 3-29 的 HTML 用户界面。
Listing 3-34. HTML UI for the person
Object Screenshot
<span id="span_fname"> </span><span style="margin-left:5px" /><span id="span_lname"> </span>
清单 3-35 显示了如何将对象的适当属性应用到每个span
,这一活动通俗地称为连接。
Listing 3-35. Binding to Span Elements
ready: function (element, options) {
span_fname.innerText = person.FirstName;
span_lname.innerText = person.LastName;
}
,
你可以更进一步。假设指定的跨度已经被设计成只需要名称(可能已经对它们应用了一种样式,以“名称”的方式呈现它们所呈现的任何内容)。您可以使用第二章中讨论的作者定义的属性功能来构建一个更通用的应用版本。您可以创建一个通用函数来查找适当的属性并自动为您设置值,而不是像清单 3-5 那样以编程方式设置值。清单 3-36 显示了这个新实现的完整 JavaScript。
Listing 3-36. Homegrown Data Binding
(function () {
"use strict";
var person = {
FirstName: "Azuka"
,
LastName: "Moemeka"
,
Age: 6.5
,
ImageID: 1234
,
};
WinJS.UI.Pages.define("/Demos/FlipViewDemo/FlipViewDemo.html", {
ready: function (element, options) {
ProcessBindings(person);
}
,
});
function ProcessBindings(data_source)
{
var list = document.getElementsByTagName("span");
for (var i = 0; i < list.length; i++)
{
var node = list.item(i);
if (node != null)
{
if (node.attributes != null)
{
var isboundcontrol = node.hasAttribute("data-binding");
if (isboundcontrol)
{
var property_to_bind = node.getAttribute("data-binding");
node.innerText = data_source[property_to_bind];
}
}
}
}
}
})();
清单 3-36 从调用一个函数ProcessBindings
开始,传递一个参数作为数据源对象(稍后会有更多关于数据源的内容)。在ProcessBindings
中,获取文档中的所有span
元素,并遍历列表,搜索指定了作者定义的data-binding
属性的范围。当您遇到这样的控件时,您使用data-binding
属性的值作为索引来检索具有相同名称的data_source
属性的属性值。清单 3-37 显示了这个页面的用户界面现在的样子。
Listing 3-37. Homegrown Data Binding User Interface
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>FlipViewDemo</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="FlipViewDemo.css" rel="stylesheet" />
<script src="FlipViewDemo.js"></script>
</head>
<body>
<div class="FlipViewDemo fragment">
<div>
Beautiful children!
</div>
<div>
<span data-binding="FirstName"></span><span style="margin-left:5px" />
<span data-binding="LastName"></span>
</div>
</div>
</body>
</html>
运行该示例会产生与图 3-29 所示相同的结果。该示例阐释了绑定基础结构的一些主要概念、数据控件的主要组件及其用法。模板的功能本质上类似于清单 3-37 中强调的用户界面:它们提供了一个用户界面,数据被注入其中。数据源表示要注入模板的数据源。
绑定是将数据的给定属性与用户界面中的元素相关联的活动。它包括绑定定义部分(在本例中,指定包含FirstName
值的data-binding
属性——这表明FirstName
将被放置在该 span 中)和实际的绑定动作(将值放置在该 span 中)。
该示例展示了绑定的基本机制,但是您应该很容易看出它有多么有限。首先,它只适用于跨度;一个健壮的数据绑定解决方案将允许使用多种类型的控件。第二,它在约束目标方面是有限的。在这个例子中,您总是绑定到span
元素上的innerText
;但是如果这是一个图像呢?在这种情况下,您需要使用src
属性。
给出了数据绑定的基本描述以及它如何应用于数据控件,现在让我们看看 WinJS 提供的一些控件。从这些数据控件中最简单的开始:FlipView
。使用这个作为第一个例子的好处是,由于FlipView
相对简单的实现和编程方法,很容易掌握和理解为 Windows 8 编程数据驱动 WinJS 控件背后的核心概念。
FlipView
FlipView
控件可用于一次显示一个对象列表。Windows Marketplace 应用的应用详细信息页面上显示了一个示例。当您需要显示列表项的详细视图,同时仍为最终用户提供在列表中前后移动的能力时,此控件非常有用。
让我们扩展清单 3-27 中的例子,将它转换成使用一个FlipView
控件。首先需要将person
对象转换成一个people
列表,然后创建一个模板来表示这个列表中的每个人,最后声明FlipView
控件并连接它,使它显示这个列表。
清单 3-38 显示了你使用的新数据源。请注意,您向设计中添加了两个额外的活动。创建人员数组后,必须将他们添加到WinJS.Binding.List
对象中。关于这个列表,重要的是要知道,因为它涉及到所有类型的数据控件,它对于绑定是必要的。
Listing 3-38. Setting Up a List for the FlipView
var people = [
{
FirstName: "Alex"
,
LastName: "Moemeka"
,
Age: 9
,
ImageID: 5678
,
}
,
{
FirstName: "Azuka"
,
LastName: "Moemeka"
,
Age: 6.5
,
ImageID: 1234
,
}
];
var item_source = new WinJS.Binding.List(people);
Note
FlipView
和ListView
不能处理除了提供由WinJS.UI.IListDataSource
公开的方法和属性的对象之外的任何东西。在尝试绑定时,两个控件都调用预期属于绑定类型的方法。使用这个对象的好处是它公开了与标准 JavaScript 数组相同的方法和属性,所以学会如何使用它应该不会太难。我推荐一直用;但 WinJS 的价值主张之一是,它可以很容易地转换为标准 JS,并在基于 web 的应用版本中使用。使用任何不能转换成标准 JavaScript 的 WinJS 类型都不能做到这一点。
您需要对这个列表做些别的事情,以便为绑定做好准备。到目前为止,我还没有谈到这一点,但是您需要使列表在标记中可寻址。默认情况下,以声明方式定义的控件无法访问您在 JavaScript 文件中创建的对象。为了使它们对 UI 层可见,您需要使用WinJS.Namespace.define
函数将它们声明为名称空间。添加清单 3-39 中的声明。
Listing 3-39. Defining the people
List
WinJS.Namespace.define("people_list", {
bindingList: item_source
,
array: people
,
});
就像使用WinJS.Binding.List
一样,这种符号是你必须习惯的。为了使绑定工作,您公开的对象必须有一个表示被绑定到的列表的属性(在本例中是,bindingList)
)和一个在其上定义的数组属性。bindingList
属性应该被设置为绑定列表的一个实例。array
属性应该引用你的数组。
既然您已经准备好了数据源,那么您可以转到用户界面并定义用于存储数据的模板。该模板使用与自主开发的数据绑定示例相同的用户界面布局,经过修改后支持官方的 Windows 8 数据绑定格式(参见清单 3-40)。
Listing 3-40. Creating a Template
<div id="basic_template" data-win-control="WinJS.Binding.Template">
<span data-win-bind="innerText:FirstName"></span><span style="margin-left: 5px" /><span
data-win-bind="innerText:LastName"></span>
</div>
基于你对控件和自制绑定的理解,清单 3-40 应该是不言自明的。您已经创建了一个表示一个person
对象的数据模板的模板控件。在模板控件中,您定义了实际的绑定,如前所述,这些绑定是您试图显示的对象的用户界面表示。和本地的例子一样,每个人由两个跨度表示:一个是他们的名,一个是他们的姓(用一个span
在它们之间创建一个 5 像素的空间)。请注意,在正式的 WinJS 绑定方法中,绑定字符串说明了被绑定到的控件的类型。data-win-bind
,在功能上等同于自行开发的示例中的数据绑定,既接受属性名以绑定到给定的控件,又接受属性名以绑定值。在这种情况下,因为两者都是跨度,data-win-bind
的值就是innerText:<data source property name>
。一般来说,格局是<element's attribute name>:<data source property name>
;因此,如果其中一个模板元素是图像,那么data-win-bind
属性将是src:<data source property name>
。
最后,您定义控件。请注意,因为模板由控件引用和使用,所以它必须在使用的任何控件之前定义。清单 3-41 显示了完整的用户界面 HTML,包括模板和控件。
Listing 3-41. Full UI Code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>FlipViewDemo</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="FlipViewDemo.css" rel="stylesheet" />
<script src="FlipViewDemo.js"></script>
</head>
<body>
<div class="FlipViewDemo fragment">
<div>
FlipView Demo
</div>
<div aria-label="Main content" role="main">
<div id="basic_template" data-win-control="WinJS.Binding.Template">
<span data-win-bind="``innerText:FirstName``"></span><span style="margin-left: 5px"
ⅳ
/><span data-win-bind="``innerText:LastName
</div>
<div style="background-color:slategray" data-win-control="WinJS.UI.FlipView"
="{itemTemplate:basic_template, itemDataSource:``="{itemTemplate:basic_template, itemDataSource:
people_list.bindingList.dataSource}
">
</div>
</div>
</div>
</body>
</html>
特别要注意FlipView
控件的data-win-options
属性:它和ListView
使用相同的机制。在其中,您分配了模板,FlipView
使用该模板来呈现数据源中的每一项以及数据源本身。顾名思义,itemTemplate
应该引用呈现每个项目的模板(应该是表示每个项目用户界面的 HTML 容器的元素 ID),而itemDataSource
应该引用数据源——使用清单 3-39 中定义的公共作用域名称。如果您还记得,您创建的那个对象包含一个名为bindingList
的属性。在清单 3-41 中,你引用了这个对象的一个dataSource
属性。这是一个您没有定义但在运行时附加到对象的属性;你在这种情况下使用它,它充满了数据。
呈现时,这个FlipView
显示与前面相同的用户界面布局(名字,姓氏),只有一个例外:在支持触摸的设备上,您可以在控件上从右向左滑动以显示其他列表值。(如果您没有支持触摸的设备,但有鼠标,则FlipView
控件会显示向前和向后箭头,您可以使用这些箭头来浏览控件列出的元素。)图 3-30 显示了无触摸机器上的用户界面。
图 3-30。
FlipView
control example
列表视图
既然您已经看到了如何构建一个FlipView
控件,并且对数据驱动控件开发的主要组件有了大致的了解,那么您应该能够推断出ListView
是如何设置的。从编程的角度来看,ListView
控件在功能上是相似的(唯一的区别是用户界面和交互模式)。这当然主要是由于每个控件满足的使用场景。虽然FlipView
的目标是一次呈现一个列表项的模板,但是ListView
以列表形式显示它们。展示这一点的最简单方法是将清单 3-41 中的控件类型从WinJS.UI.FlipView
改为WinJS.UI.ListView
。现在运行示例以列表形式显示相同的数据,如图 3-31 所示。
图 3-31。
ListView
control example
Visual Studio 2012 包括用于创建新项目的项目模板,其中所有ListView
控件的绑定和布局都已经连接好。图 3-32 显示了添加新项目窗口,您可以在其中选择网格应用模板。该模板以两种格式提供了开始构建数据驱动的主从式应用所需的一切。在 WinJS 中,网格应用模板以网格格式创建分组视图。Split 应用将主数据全部放在一个页面上。
图 3-32。
Project template selection for Grid App
如果您创建了这个项目并立即运行它,您应该有一个类似于图 3-33 的用户界面。
图 3-33。
The default Grid App template in action
语义缩放
本节讨论的最后一个控件是SemanticZoom
。SemanticZoom
控件以两种视图显示列表:放大视图和缩小视图。您可以使用这种功能来创建一个用户界面,类似于 Windows 8 开始屏幕在所有应用视图上的功能(从开始屏幕,滑动打开魅力栏,然后单击搜索魅力)。如果你有一个支持触摸的设备,在视图中捏住屏幕(对于没有触摸的机器,按 Control + scroll ),你应该会看到类似图 3-34 的视图。
图 3-34。
Zoomed-out Start screen
处于缩小状态允许用户快速导航到他们感兴趣的区域。在这种情况下,用户可以进入以字母 X 开头的应用,而不必滚动每个应用;但是任何您认为与用户相关的高级分组都将以相同的方式运行。如果 Windows 8 开发人员使用这些排序机制中的一种,该列表中的应用可能会缩小以显示应用发布者的姓名、安装日期、权限集或任何其他有意义的分组。目前,他们选择按应用名称的第一个字母分组。请注意,桌面应用不仅仅是按应用名称分组的(它们参与首字母分组的方式与 Windows 8 应用不同)。这将映射到旧的“开始”菜单中可见的根应用文件夹名称。图 3-35 显示了所有应用屏幕的放大视图。
图 3-35。
Zoomed-in view of all apps
创建这个相对简单。您只需在 HTML 中定义一个SemanticZoom
控件并指定两个视图,使用ListView
控件(或其他数据控件)来表示它必须提供的两种状态。SemanticZoom
控件的第一个 HTML 子控件是放大视图,第二个 HTML 子控件是缩小视图。
缩小视图使用分组功能。分组允许您将绑定到控件的数据分类到通过您提供的条件标识的组中。在图 3-34 中,所有的 Windows 8 应用都按照应用名称的第一个字母进行分组(因此有了字母列表)。因为您在people
列表中只有两个元素,所以让我们修改它,以便您可以很容易地看到分组功能。清单 3-42 显示了一个新列表(family_members
),它扩展了您之前创建的people
列表。
Listing 3-42. A List of Family Members
var family_members = [
{
FirstName: "Alex"
,
LastName: "Moemeka"
,
Age: 9
,
ImageID: 5678
,
}
,
{
FirstName: "Azuka"
,
LastName: "Moemeka"
,
Age: 6.5
,
ImageID: 1234
,
}
,
{
FirstName: "Elizabeth"
,
LastName: "Moemeka"
,
Age: 9
,
ImageID: 910
,
}
,
{
FirstName: "Edward"
,
LastName: "Moemeka"
,
Age: 9
,
ImageID: 1112
,
}
,
{
FirstName: "Tiki"
,
LastName: "The Cat"
,
Age: 1
,
ImageID: 1314
,
}
];
变化不大;您刚刚向现有列表中添加了更多元素。您使用每个家庭成员名字的第一个字母对列表进行分组(类似于开始屏幕方法)。如果您查看数组元素,您应该注意到根据您指定的标准有三个分组(A、E 和 T)。在前面的例子中,您创建了一个List
对象并将您的控件绑定到该对象;在这种情况下,您需要再添加几个步骤来启用该数据集的分组。幸运的是,您一直在使用的 WinJS list
对象提供了启用分组所需的一切。使用createGrouped
功能,您可以返回家庭成员列表的分组版本。createGrouped
将三个函数作为参数:第一个接受列表中的一个元素并标识它所属的组,第二个检索用于分组的组,最后一个对元素进行排序(在列表和分组中)。请参见清单 3-43。
Listing 3-43. Creating a Grouped List that Is Ready for Data Binding
var family_members = [
{
FirstName: "Alex"
,
LastName: "Moemeka"
,
Age: 9
,
ImageID: 5678
,
}
,
{
FirstName: "Azuka"
,
LastName: "Moemeka"
,
Age: 6.5
,
ImageID: 1234
,
}
,
{
FirstName: "Elizabeth"
,
LastName: "Moemeka"
,
Age: 9
,
ImageID: 910
,
}
,
{
FirstName: "Edward"
,
LastName: "Moemeka"
,
Age: 9
,
ImageID: 1112
,
}
,
{
FirstName: "Tiki"
,
LastName: "The Cat"
,
Age: 1
,
ImageID: 1314
,
}
,
];
var item_source = new WinJS.Binding.List(family_members);
var grouped_source = item_source.createGrouped(function (item)
{
// return the string that will be used to evaluate grouping
return item.FirstName.charAt(0).toString();
}, function (item)
{
return {
Title: item.FirstName.charAt(0).toString()
};
}, function (left, right)
{
return left.toUpperCase().charAt(0) < right.toUpperCase().charAt(0);
});
WinJS.Namespace.define("familymember_list", {
bindingList:
grouped_source
,
array: family_members
,
});
在清单 3-43 中,与您之前看到的FlipView
和ListView
例子相比,有两点发生了变化。首先,不是仅仅创建一个列表,而是使用createGrouped
函数从列表中创建一个组。其次,分组项用于定义公共名称空间,而不是列表本身。createGrouped
的三个参数相对简单。第一个函数返回列表中每一项的第一个字符,因此对于 Edward,它将返回 E,对于 Tiki,它将返回 t。第二个函数在每个组中调用一次,返回组对象以及与之相关联的可绑定名称。这对绑定到模板很重要。在这种情况下,您使用名称Title
来引用每个组的名称。final 函数用于对列表和分组列表中的每个元素进行排序。它总是传递两个元素,并期待两者之间的比较。这里使用布尔比较,但是正数值和负数值也可以。清单 3-44 提供了示例的用户界面。
Listing 3-44. Semantic Zoom Example
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>SZDemo</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="SZDemo.css" rel="stylesheet" />
<script src="SZDemo.js"></script>
</head>
<body>
<div class="SZDemo fragment">
<div>
Semantic Zoom
</div>
<div id="basic_template" data-win-control="WinJS.Binding.Template">
<span data-win-bind="innerText:FirstName"></span><span style="margin-left: 5px"
/><span data-win-bind="innerText:LastName"></span>
</div>
<div id="group_header_template" data-win-control="WinJS.Binding.Template">
<span data-win-bind="innerText:Title"></span>
</div>
<div data-win-control="``WinJS.UI.SemanticZoom``" style="background-color: slategray;
ⅳ
height: 300px"
>
<div id="in" data-win-control="WinJS.UI.ListView" style="background-color:
slategray; height:300px"
data-win-options="{itemTemplate:basic_template, itemDataSource:
familymember_list.bindingList.dataSource}">
</div>
<div id="out" data-win-control="WinJS.UI.ListView" style="background-color:
slategray; height:300px"
data-win-options="{itemTemplate:group_header_template, itemDataSource:
familymember_list.bindingList.groups.dataSource
}">
</div>
</div>
</div>
</body>
</html>
请注意一个不同的缩小视图模板,它绑定到清单 3-43 中 group 对象上定义的Title
属性。
如前所述,SemanticZoom
控件包含两个集合控件:第一个表示放大视图(在本例中,一个ListView
绑定到family_members
列表),第二个表示缩小视图(绑定到family_members
列表的groups
属性)。第二个控件连接到另一个模板group_header_template
,因为它包含一个组对象列表(带有Title
属性)而不是一个person
对象列表。图 3-36 和 3-37 显示了该示例的外观。
图 3-37。
SemanticZoom
example in the zoomed-out view
图 3-36。
SemanticZoom
example in the default zoomed-in view
您可以使用右下角的减号缩小SemanticZoom
控件(对于鼠标),使用 Control +鼠标滚轮向后滚动(也用于鼠标交互),使用 Control +减号,或者,如果您有支持触摸的设备,通过捏捏SemanticZoom
表面。
创建自己的控件
到目前为止,您已经看到了如何整合内置的 HTML 控件,如input
、button
、image
和span
元素。然后,您研究了如何使用不属于 HTML 控件集的控件。这些控件非常棒,因为它们封装了与 Windows 8 开发相关的附加功能;其中包括AppBar
、Flyout
和Ratings
控件。您继续研究了模板、绑定和高级的以数据为中心的控件,如FlipView
、ListView
和令人兴奋的新SemanticZoom
控件。
这些控件代表 Windows 编程 API 的开发人员认为重要的众所周知的问题的解决方案;但是,如果您想要为自己的库封装用户界面功能,该怎么办呢?这样的控件可以在多个项目中重用,为您开发的应用提供一致的外观。WinJS 提供了创建您自己的自定义控件的能力,如果没有内置控件满足您的需求。
正如你在本章前面所看到的,控件只不过是 JavaScript 类,它们使用 HTML 树中的指针(一个主机元素)来呈现它们的内容。您在第一章的中使用了这种机制,使用data-win
自定义属性创建了一个自制的控制模式。在本节中,您将创建一个新的控件PersonControl
,它的功能基本上类似于您在上一节中创建的用于呈现person
对象的模板。要做到这一点,你需要做两件事。首先,您需要创建一个表示控件的类——接受宿主元素和您熟悉的选项作为构造函数参数。您以前没有遇到过这种情况,但是在 WinJS 中创建一个类非常简单。您只需要调用WinJS.Class.define
,传入一个表示该类构造函数的函数。define
还带有另外两个参数:一个需要一个表示该类所有公共实例成员的对象,另一个需要一个表示该类所有公共静态成员的对象。这个例子没有深入研究这两个参数的用法,但是欢迎您以这种方式探索类的用法。你可以在 http://msdn.microsoft.com/en-us/library/windows/apps/br229813.aspx
找到更多关于他们的信息。
您还需要通过为该类定义一个命名空间来使该类成为公共的;您在上一节中使用了相同的方法,使您的数据列表对声明它们绑定到的控件的 HTML 标记可见。清单 3-45 显示了自定义控件示例的 JavaScript 代码。定义了类和命名空间之后,就不需要再做什么了(如果计划用 HTML 声明控件的话)。正如您在“深入研究 WinJS 控件”一节中看到的,用 JavaScript 实例化控件需要更多的代码,但本质上与 HTML 替代方法的工作方式相同。
Listing 3-45. Creating a Custom Control
(function ()
{
"use strict";
var person_contructor = WinJS.Class.define(function (element, options)
{
element.winControl = this;
this.element = element;
var first_name = options.firstname;
var last_name = options.lastname;
var firstname_span = document.createElement("span");
firstname_span.innerText = first_name;
element.appendChild(firstname_span);
var space_span = document.createElement("span");
space_span.innerText = " ";
element.appendChild(space_span);
var lastname_span = document.createElement("span");
lastname_span.innerText = last_name;
element.appendChild(lastname_span);
});
WinJS.Namespace.define("MyControls", {
PersonControl: person_contructor
,
});
WinJS.UI.Pages.define("/Demos/CustomDemo/CustomControlDemo.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)
{
// TODO: Initialize the page here
。
}
,
});
})();
清单非常简单。您使用WinJS.Class.define
创建一个类,传递宿主元素和声明时指定的选项。在构造函数中,创建三个<SPAN>
元素:一个用于名,一个用于名和姓之间的空格,一个用于姓。您从声明时传入的options
对象中检索这些值。将这些值设置为适当跨度的innerText
属性,然后以适当的顺序将跨度附加到主体元素。
接下来定义该类的名称空间,以便可以从 HTML 标记中引用它。在这种情况下,该类可寻址为MyControls.PersonControl
。清单 3-46 显示了这个例子的 HTML 标记。
Listing 3-46. Example Custom Control Markup
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>CustomControlDemo</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="CustomControlDemo.css" rel="stylesheet" />
<script src="CustomControlDemo.js"></script>
</head>
<body>
<div>
Semantic Zoom
</div>
<div id="control_person" data-win-control="MyControls.PersonControl" data-win-options=
"{firstname:'chinua',lastname:'achebe'}"></div>
</body>
</html>
就像你在本章中使用的其他控件一样,你可以通过附加到一个容器的自定义属性声明PersonControl
来实例化它,该容器代表控件的宿主。然后使用data-win-options
将实例化选项传递给控件,这些选项用于控制第一次呈现时控件的最终布局。图 3-38 显示了导航到页面时的用户界面。
图 3-38。
Custom control
摘要
您刚刚完成了内容丰富的一章,对合并布局和控件进行了全面而深入的研究。以下是对关键点的回顾:
- Windows 的 JavaScript 库本质上是纯 HTML。基于 C#或 C++的 Windows 8 应用中的纯 Windows 运行时之间的区别因素并不比控件的情况更明显。因为 WinJS 应用是使用 HTML、CSS 和 JavaScript 的 web 标准构建的,所以它们支持将 HTML 控件集用于应用布局、样式和交互性编程。
- 控件可以是 JavaScript 类的形式。这些可以是您自己编写的类,以降低用户界面构造的复杂性并增加布局的灵活性。
- WinJS 提供了许多快速启动和运行的控件。WinJS 控件使用与 Windows 应用商店应用相同的 HTML JavaScript 和 CSS 编写,因此它们可以与页面上的其他元素无缝集成。
- 为此,所有 WinJS 控件修饰的 HTML 元素在运行时都有一个附加的属性,称为
winControl
。winControl
属性提供了从承载控件的 HTML 元素到实际 WinJS 控件的连接,并可用于访问与控件关联的任何事件、属性或方法。 - 使用页面控件模板创建页面不仅会添加页面的 HTML,还会添加 JavaScript 和 CSS。
- 一个
AppBar
控件代表一个应用工具栏,用于显示命令。 - 当
PageControl
从指定页面获取 HTML、CSS 和 JavaScript 并智能地将其注入宿主控件范围内的宿主页面时,HtmlControl
可用于创建页面。 - 虽然
Flyout
和Menu
控件是相似的,但是Flyout
控件支持添加任何有效的元素;Menu
控件只允许添加MenuCommand
控件。 - 除了 HTML 控件和不属于 HTML 控件集的控件提供的功能,您还可以使用模板、绑定和高级数据中心控件,如用于 Windows 8 应用开发的
FlipView
、ListView
和SemanticZoom
。 - 自定义控件使您能够创建可重用的用户界面。这允许您在重复的实例中使用具有相同布局和行为的控件。
四、穿越媒体迷宫
Abstract
媒体体验长期以来一直是微软平台的一个弱点,但这并不是因为这些功能不是系统固有的。Windows 提供了广泛的内置播放和管理功能。该平台的不足之处在于向最终用户公开媒体内容创建和管理工具。如果您了解 DirectX 或任何底层 API,那么您就可以继续这样做了。但是传统上,这种技术的应用范围并没有远离那些有资金雇佣具有这种知识的资源的组织。
媒体体验长期以来一直是微软平台的一个弱点,但这并不是因为这些功能不是系统固有的。Windows 提供了广泛的内置播放和管理功能。该平台的不足之处在于向最终用户公开媒体内容创建和管理工具。如果您了解 DirectX 或任何底层 API,那么您就可以继续这样做了。但是传统上,这种技术的应用范围并没有远离那些有资金雇佣具有这种知识的资源的组织。
微软通过 Windows Store 应用发布了 Windows 8,改变了这一局面。Windows Store 应用的编程界面旨在确保提供现代、快速和流畅的应用。作为一个全方位的内容创建和消费平台,Windows 8 凭借其广泛的媒体支持功能脱颖而出,这些功能通过 Windows Runtime (Win RT)和 Windows Runtime for JavaScript 向开发人员公开。
媒体播放
如果您曾经从事过 Windows 窗体开发,您就会知道媒体播放曾经是多么可怕。在 Windows Presentation Foundation/Silverlight、Flash 和 HTML 5 之前,Windows 平台上的媒体播放涉及使用一种称为 ActiveX 的技术将 Windows Media Player 播放用户界面嵌入到播放目标中。无论是在网页中还是在桌面应用中,Windows media playback 都是一种完全断开的体验,甚至需要最终用户下载并安装附加组件才能访问媒体。
在 Windows 8 JavaScript 应用中,微软选择保持该语言完全符合 HTML 5。为此,媒体播放遵循使用熟悉的video
标签的模式。从上一章对页面和导航的讨论中,你可能已经注意到,当你需要从应用中引用 HTML 时,你使用相对路径来实现。这也符合标准的 HTML 模式和实践。可以想象,回放媒体至少需要指定要回放的媒体的位置。在 Windows 应用中,用于回放的媒体可以来自两个地方(一般来说):它可以是机器本地的,也可以来自某个远程源 web 资源。本地媒体以与 HTML 内容(以及应用中需要引用的任何其他类型的内容)相同的方式放置在项目结构中。在图 4-1 中,我已经将我计划播放的视频内容放在了我的项目结构中。本练习使用了 www.bigbuckbunny.org
中的大巴克兔子视频。你可以直接从 www.bigbuckbunny.org/index.php/download/
下载一份录音。
图 4-1。
Project structure with video added
这当然是一个比前一章中使用的更加复杂的项目结构。尽管它有更多的文件夹,但模式保持不变。即使主 HTML 文档不再是主要内容区域,您也可以使用应用级导航来帮助您在页面之间移动。(当然,从概念上讲,它仍然是主要文档;其他“页面”仅仅是被注入其中。)本节并不详细介绍每个示例,而是为将托管其余应用的应用提供基准。让我们从应用的主框架开始。
设置项目
在第三章中,当你深入研究导航机制时,你设计了主页面default.html
以两种视图显示其内容。在最上面的部分是承载所有页面的框架;在它的下面是可以用来从一个例子到另一个例子导航的按钮。您在这个项目中重用这个公式来创建一个布局和导航结构,允许您在一个项目中构建所有的示例。当然,您可以自由地以对您有意义的方式组织您的示例项目。不管它们是在同一个项目中还是分开的,它们都应该运行相同的程序。(请注意,您需要在为不同的示例创建不同的项目的场景中包含所有适当的引用。)清单 4-1 显示了示例浏览器项目的 HTML 布局。
Listing 4-1. HTML Layout for Example Browser
<!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 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>
在您看到这段代码的用户界面之前,您需要将清单 4-2 中的代码添加到应用启动部分。这确保了示例页面的内容宿主在应用启动时加载一些内容。在这种情况下,它加载了一个页面,这个页面将所有的例子都作为页面上的按钮列出。
Listing 4-2. Application Startup Handler for the Example Browser
if (args.detail.kind === activation.ActivationKind.launch)
{
args.setPromise(WinJS.UI.processAll().then(function ()
{
WinJS.Navigation.navigate("/samplelist.html");
}));
}
基本上是一个按钮列表,代表你的应用可以显示的每个例子。从本章开始,这些按钮中只有一个是适用的,但是您仍然要将它们都添加到这里。每当你用一个新的例子开始一个新的章节时,应用这里强调的模式取决于你。你知道基于主题要激活的按钮。清单 4-3 显示了示例列表页面的用户界面。
Listing 4-3. UI for the Example Listing Page
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>SampleList</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="SampleList.css" rel="stylesheet" />
<script src="SampleList.js"></script>
<script src="sampleData.js" type="text/javascript"></script>
</head>
<body>
<section aria-label="Main content" role="main" style="margin-top: 5px; margin-left: 100px;">
<div class="sampleListViewItemTemplate" data-win-control="WinJS.Binding.Template">
<div>
<div data-win-bind="textContent:name"></div>
</div>
</div>
<div style="width: auto; height: auto;">
<div style="height: 0px;"></div>
<div style="width: auto; height: 39.04px; top: 0px; margin-top: 0px;">
<input id="btn_navsamples" type="button" value="Navigation Samples"
style="width: 353.55px; height: 35.89px;" />
</div>
<div style="width: auto; height: 38.32px; ">
<input id="btn_appbarsamples" type="button" value="AppBar samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_backgroundtasks" type="button" value="Background Tasks">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_jquery" type="button" value="jQuery Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_fileio" type="button" value="FileIO Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_lockscreen" type="button" value="LockScreen Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_video" type="button" value="Video Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_search" type="button" value="Search Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_share" type="button" value="Share Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_settings" type="button" value="Settings Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_printing" type="button" value="Printing Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_dialogs" type="button" value="Dialog Samples">
</div>
<div style="width: auto; height: 41.64px;">
<input id="btn_notifications" type="button" value="Notification Samples">
</div>
</div>
</section>
</body>
</html>
图 4-2 显示了应用运行时的样子。
图 4-2。
App UI for the example browser
清单 4-4 显示了这个页面的 JavaScript 代码,用于点击按钮启动视频示例。像这样的事件处理程序应该放在正在执行的页面的ready
函数中。在这种情况下,那将是samplelist.js
。
Listing 4-4. Event Handler Code for When the Video Button Is Clicked
btn_video.onclick = function ()
{
WinJS.Navigation.navigate("/samples/videosample/testvideo.html"
,ⅵ
"Testing Video Tasks");
};
注意到这个navigate
用法的有趣之处了吗?在第三章的中,当你使用navigate
时,你只传递了一个参数:你要导航到的 HTML 文件的 URL。现在你要传递一个额外的参数:字符串"Testing Video Tasks"
。第二个参数用于将状态信息从导航页面传递到被导航的页面。这是在两个页面之间传递上下文信息的好方法,而不必创建全局状态。清单 4-5 展示了如何使用WinJS.Navigation
的navigated
事件将这个值传递给页面呈现函数。如果你还记得第三章的话,全局导航需要在根主机页面的 JavaScript 代码中。这是托管所有其他页面的主 HTML 页面的 JavaScript(类似于框架在基于 web 的 HTML 页面中的工作方式)。对于这个项目,这是default.js
文件。在主应用函数中,您可以将其插入到主声明之后和调用app.start()
之前的任何地方。注意,在默认项目配置中,default.js
位于项目的js
文件夹中(见图 4-1 )。
Listing 4-5. JavaScript for the Main HTML Page
WinJS.Navigation.addEventListener("navigated", function (args)
{
//find the frame
var frame = document.getElementById("frame");
//clear the frame
WinJS.Utilities.empty(frame);
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));
});
您从这段代码中调用了两件事。首先,使用state
的值在根页面上设置一个标题——这个想法是用户总是知道他们在哪里。其次,根据导航栈是否允许后退来设置后退按钮的visibility
状态(最初是不可见的)。为了支持向后导航,将清单 4-6 中的代码添加到应用的activated
事件处理程序中(应用启动代码位于default.js
)。
Listing 4-6. Code to Handle Backward Navigation
var btn_back = document.getElementById("btn_back");
btn_back.onclick = function ()
{
WinJS.Navigation.back();
};
媒体播放
当您点击视频样本按钮时,您应该会看到如图 4-3 所示的屏幕。
图 4-3。
Video sample user interface
你能猜出生成这个页面的 HTML 是什么吗?当然,对于经验丰富的 web 开发人员来说,这样的页面是微不足道的(这样做是为了将重点主要放在 Windows 8 开发上,而不是 HTML 布局实践)。清单 4-7 显示了它是如何组成的。注意,到目前为止,还没有使用 WinJS,所以这个页面理论上可以毫不费力地移植到 web 上。
Listing 4-7. HTML for the Video Sample
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TestVideo</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="TestVideo.css" rel="stylesheet" />
<script src="TestVideo.js"></script>
<style>
div
{
margin-left: 5px;
}
span
{
font-size: 22pt;
font-family: Calibri;
}
span.notification
{
background-color: red;
}
input[type=file]
{
width: 100%;
}
.centered
{
margin-left: auto;
margin-right: auto;
width: 800px;
box-shadow: 0px 0px 5px #000;
}
</style>
<script type="text/javascript">
</script>
</head>
<body>
<div class="TestVideo fragment">
<div class="centered">
<div>
<span class="heading">Video Test Actions</span>
</div>
<div>
<video id="player_video" style="width: 500px; height: 400px"
src="/samples/videosample/big_buck_bunny_720p_surround.avi" autoplay></video>
</div>
<div>
<div style="display: table-cell">
<div>
<button id="btn_playvideo">Play video</button>
</div>
<div>
<button id="btn_capture">Camera Capture</button>
</div>
<div>
<button id="btn_advancedcapture">Advanced Media Capture</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
现在不要担心按钮btn_capture
和btn_advancecapture
;当你进入 Windows 8 应用中的媒体捕获机制时,会详细讨论它们。在本例中,最终用户无法控制回放开始的时间。由于autoplay
属性,回放在这里立即开始。您可以将回放控制委托给用户,如清单 4-8 所示(当然也去掉了autoplay
反馈)。
Listing 4-8. Video Playback
<video id="player_video" style="width:500px; height:400px"
src="/samples/videosample/big_buck_bunny_720p_surround.avi"``controls
当然,您也可以使用 JavaScript 通过应用控制回放。清单 4-9 使用 JavaScript 来播放视频(您也可以使用 JavaScript 的pause()
函数来停止或暂停视频)。
Listing 4-9. JavaScript Video Playback (Playing Video)
btn_playvideo.onclick = function ()
{
player_video.play();
};
除了autoplay
和controls
之外,您还可以对video
标签应用其他各种属性。这些包括但当然不限于以下内容:
muted
:告诉视频控制静音poster
:允许您指定一个 URL,该 URL 指向视频不播放时显示的图像loop
:告诉视频控制在视频结束后重启视频
清单 4-10 显示了poster
属性的用法。可以想象,在用户决定播放视频之前,它对描述视频内容非常有用。你可能在 YouTube、AOL 和 MSN 等热门网站上看到过这种技术,诱使你播放视频。
Listing 4-10. Adding a Poster to the Video Tag
<video id="player_video" style="width: 500px; height: 400px"
src="/samples/videosample/big_buck_bunny_720p_surround.avi"
poster="/samples/videosample/Big_buck_bunny_poster_big.jpg"
controls></video>
在这种情况下,您使用项目文件夹中的文件作为图像(该图像文件也可以来自任何 web 资源)。我选择使用大巴克兔子电影的海报,可以在下面的维基共享网址找到: https://commons.wikimedia.org/wiki/File:Big_buck_bunny_poster_big.jpg
。你当然欢迎使用任何对你有意义的图像。
运行此示例时,可以看到图像直接显示在视频控件中。只要视频处于停止状态(像这样的媒体可以播放、暂停或停止),它就会一直保持在那里。图 4-4 显示了播放开始前视频控件的外观。
图 4-4。
Poster associated with the video
tag
使用video
标签,您还可以直接从后端 web 服务器播放媒体。清单 4-11 显示了这一点。
Listing 4-11. Server-side Video Playback
<video id="player_video" style="width:500px; height:400px"
src="
http://sample.com/samples/videosample/big_buck_bunny_720p_surround.avi
"
controls></video>
为了让这个示例工作,您必须将 Internet 客户端功能添加到您的应用清单中,如第一章中所述。互联网客户端授予 Windows 8 应用访问互联网的权限。没有它,你就不能通过任何 API 连接到任何基于云的资源。
Windows 8 中的video
控件是HTMLMediaElement
的子类。它可以用来播放视频和音频。如果你确定除了音频(也许是一个游戏介绍序列或一个音乐流应用)之外没有其他需要,那么你可以使用audio
标签。像video
标签一样,audio
标签可以用来回放视频和音频内容,但需要注意的是audio
标签只能回放音频(不呈现视频)。尝试用清单 4-12 中的代码替换清单 4-11 中的 HTML。
Listing 4-12. Playing Only Audio
<audio id="player_video" src="/samples/videosample/big_buck_bunny_720p_surround.avi"
controls></audio>
现在,当你点击播放视频按钮,只有与大巴克兔子视频相关的声音播放。您可能还会注意到,现在只有视频控件的底部(带有播放/暂停按钮的部分)是可见的。当我说audio
标签不播放任何视频时,我是认真的。
当然,如清单 4-13 所示,audio
标签也可以用于从远程服务器传输音频,方式与video
标签相同。
Listing 4-13. Streaming Only Audio from a Remote Server
<audio id="player_video" src="
http://sample.com/samples/videosample/
ⅳ
big_buck_bunny_720p_surround.avi" controls></audio>
到目前为止的例子使用了一个视频,大巴克兔子,它使用 AVI 文件格式。Windows 8 支持音频和视频播放的多种编码类型和文件格式。来自 MSDN 的表 4-1 显示了所有支持的媒体格式。
表 4-1。
Supported Media Playback Formats
| 媒体文件容器或文件格式 | 文件扩展名 | 媒体流格式(编解码器) | | --- | --- | --- | | 录像 | 声音的 | | --- | --- | | MPEG-4 | `.3g2` | H.263 H.264(基线、主、高)MPEG-4 第二部分 SP 和 ASP | AAC(拉加、HE) | | `.3gp2` | | `.3gp` | | `.3gpp` | | `.m4a` | 不适用的 | AAC (LC、HE) MP3 AC3 (DD、DD+) | | `.m4v` | H.263 H.264(基线、主、高)MPEG-4 第二部分 SP 和 ASP | | `.mp4v` | | `.mp4` | | `.mov` | | MPEG-2 | `.m2ts`(如 AVCHD) | H.264 | MPEG-2 (L1,L2,立体声仅限)MPEG-1 (L1,L2) AAC (LC,HE) AC3 (DD,DD+) | | 格式 | `.asf` | VC-1 WMV9 战斗机 | WMA 标准 WMA 语音 WMA 无损 WMA 专业 AC3 (DD,DD+) | | `.wm` | | `.wmv` | | `.wma` | 不适用的 | | 阿德特 | `.aac` | 不适用的 | AAC(拉加、HE) | | `.adt` | | `.adts` | | MP3 文件 | `.mp3` | 不适用的 | MP3 文件 | | 声音资源文件 | `.wav` | 不适用的 | PCM MP3 MS ADPCM IMA ADPCM MS CCITT g . 711 MS GSM 6.10 AC3(日、月+) | | 影片格式 | `.avi` | MPEG-4 第二部分 SP 和 ASP 运动-JPG H.263 未压缩 | PCM MP3 MS ADPCM IMA ADPCM MS CCITT g . 711 MS GSM 6.10 AC3(日、月+) | | AC-3 | `.ac3` `.ec3` | 不适用的 | AC3 (DD、DD+) |您可以在 http://msdn.microsoft.com/en-us/library/windows/apps/hh986969.aspx
直接访问该表。
视频/音频效果
您可以为视频播放器中播放的视频或音频添加效果。要添加任何效果,可以使用video
类的msInsertVideoEffect
方法。清单 4-14 在btn_playvideo
事件处理程序中增加了一行。
Listing 4-14. Adding a Video Stabilization Effect to a Video Being Played Back
btn_playvideo.onclick = function ()
{
player_video.msInsertVideoEffect
("Windows.Media.VideoEffects.videoStabilization", false);
player_video.play();
};
清单 4-14 应用了框架内置的视频稳定效果。这种效果可以在指定的命名空间中找到。注意,videoStabilization
不是一个类,而是一个字符串,它表示映射到这个效果的唯一标识符(ClassID
)。在这样的动画视频中,稳定自然是无效的。但是,如果您有摄像机或照相机拍摄的视频,并且可以在开发 PC 上访问,欢迎您在 PC 上尝试。
背景音频
前一节开始深入研究audio
元素的复杂性。您看到了如何使用audio
标签来播放位于用户机器上或来自远程服务器的音频数据。然而,当这些音频文件播放时,它们被默认设计为仅在前台播放。在 Windows 8 的世界里,这意味着一旦你离开应用,声音就会停止。现在,您可以通过向您一直使用的示例代码中添加一个新的音频控件来尝试一下。(您先前将video
标签修改为audio
标签,以查看两者之间的区别;您不需要为音频创建单独的专用标签。)清单 4-15 显示了到目前为止你一直在使用的修改过的用户界面的摘录,现在有了一个专用的音频控件。该控件以声明方式配置为在加载页面时自动播放。视频控件的尺寸也减小了。
Listing 4-15. UI with a Dedicated Audio Control
<div>
style="width: 300px; height: 200px"``style="width: 300px; height: 200px"
src="/samples/videosample/big_buck_bunny_720p_surround.avi"
poster="/samples/videosample/Big_buck_bunny_poster_big.jpg" controls></video>
</div>
<div>
<audio id="player_audio" src="/samples/videosample/big_buck_bunny_720p_surround.avi"
controls autoplay></audio>
</div>
这段代码应该替换前面清单中的部分,它只包含了元素video
的div
。或者,您可以添加第二个音频div
并修改视频div
以适应列表。在这两种情况下,您都应该有两个媒体控制,一个在另一个上面,所有内建回放控制都在这两个媒体控制上启用。图 4-5 显示了该用户界面的外观。请注意,当您运行它时,音频会立即开始播放(基于您分配给它的autoplay
属性)。另请注意,当您切换到另一个应用或 Windows 8 开始屏幕时,音频会迅速淡出。
图 4-5。
Test video UI with both video and audio controls
导航回应用,音频再次开始。如果您在导航离开时密切关注媒体的播放时间,请注意,它的值从您导航离开的点开始增加(并且还注意到媒体是从稍后的位置播放的)。这是因为音频一直在播放,而你的示例应用被转移到背景。但是,因为以这种方式配置的音频在托管它的应用对用户不再可见时听不到,所以当您切换到不同的应用时什么也听不到。
启用后台回放需要三个步骤。首先,您需要声明性地告诉 Windows 音频播放器是为背景音频播放而设计的。然后,您需要通知 Windows 您打算让应用在后台运行(为了播放音频)。最后,您需要将应用挂接到背景音频播放基础结构,以便用户可以使用 Windows media 播放控件来控制背景音频。我们开始吧。
在这个audio
标签中,您设置了几个属性中的一个,如果在video
元素上设置,这些属性将是无用的:msAudioCategory
属性。系统使用该属性来帮助识别和管理音频的性能和集成。当它的值被设置为BackgroundCapableMedia
时,它将通过audio
元素播放的音频标记为可供背景音频播放器使用。清单 4-16 显示了修改后的audio
标签。
Listing 4-16. audio
Tag Configured for Background Audio Playback
<audio id="player_audio" src="/samples/videosample/big_buck_bunny_720p_surround.avi"
msAudioCategory="BackgroundCapableMedia"
controls autoplay></audio>
现在您已经完成了必要的用户界面修改,下一步是转到项目的package.appxmanifest
配置文件的便捷的声明选项卡。您正在向应用添加一个新的后台任务声明,它支持音频任务。务必在起始页文本框中指定项目起始页,如图 4-6 所示。
图 4-6。
Background audio declaration
你快完成了。现在,您必须修改后面的代码,以启用后台音频子系统的挂钩。这是必需的,以便用户可以在使用另一个应用时识别和控制音频。清单 4-17 添加了管理它的代码。注意一些audio
元素事件的使用,比如onplay
和onstop
。这些都是强大的事件,用于判断视频控件何时播放、暂停、出错以及处于许多其他状态,所以如果您计划构建富媒体应用,请了解它们。
Listing 4-17. Enabling Background Audio
(function ()
{
"use strict";
var media_control = null;
WinJS.UI.Pages.define("/samples/VideoSample/TestVideo.html", {
ready: function (element, options)
{
media_control = Windows.Media.MediaControl;
media_control.onplaypressed = function ()
{
player_audio.play();
}
media_control.onpausepressed = function ()
{
player_audio.pause();
}
media_control.onstoppressed = function ()
{
player_audio.pause();
}
media_control.onplaypausetogglepressed = function ()
{
if (media_control.isPlaying)
{
player_audio.pause();
} else {
player_audio.play();
}
}
player_audio.onplaying = function ()
{
media_control.isPlaying = true;
}
player_audio.onpause = function ()
{
media_control.isPlaying = false;
}
player_audio.onended = function ()
{
media_control.isPlaying = false;
}
Windows.Media.MediaControl.isPlaying = false;
Windows.Media.MediaControl.artistName = "The Peach Open Movie Project";
Windows.Media.MediaControl.trackName = "Big Buck Bunny";
btn_playvideo.onclick = function ()
{
player_audio.play();
};
}
,
});
})();
为了让后台回放工作,您的代码必须至少为onplaypressed
、onpausepressed
、onstoppressed
和onplaypausetoggle
提供一个事件处理程序。如果这些事件中的任何一个没有被处理,后台回放将不会工作。图 4-7 显示了大巴克兔子音频在后台运行时的背景音频控制器。
图 4-7。
Audio being controlled through the background audio controller
向其他设备传输流媒体
您可以将您在应用中回放的媒体连接到任何支持您想要流化的媒体类型的数字生活网络联盟(DLNA)兼容设备。DLNA 是一个非营利性的贸易组织,它定义和管理支持设备间数字内容共享的互操作性标准。例如,支持 DLNA 的电视可以通过家庭网络无线接收来自移动设备的输入。Play To 是一种允许用户使用这些 DLNA 标准将媒体内容从他们的设备流式传输到他们选择的屏幕上的技术。
当用户在显示或播放媒体的应用中选择设备魅力时,一个潜在的播放设备列表就会呈现在他们面前。选择一个播放目标会向已配置为将内容流式传输到播放基础架构的应用发送请求,通知应用呈现它想要流式传输的流。当应用附加流时,编程接口就完成了。清单 4-18 修改了前一个例子的用户界面,引入了一个播放音频和视频的专用按钮。
Listing 4-18. New UI for the Example App
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TestVideo</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="TestVideo.css" rel="stylesheet" />
<script src="TestVideo.js"></script>
<style>
div {
margin-left: 5px;
}
span {
font-size: 22pt;
font-family: Calibri;
}
span.notification {
background-color: red;
}
input[type=file] {
width: 100%;
}
.centered {
margin-left: auto;
margin-right: auto;
width: 800px;
box-shadow: 0px 0px 5px #000;
}
</style>
<script type="text/javascript">
</script>
</head>
<body>
<div class="TestVideo fragment">
<div class="centered">
<div>
<span class="heading">Video Test Actions</span>
</div>
<div>
<video id="player_video" style="width: 300px; height: 200px"
src="/samples/videosample/big_buck_bunny_720p_surround.avi"
poster="/samples/videosample/Big_buck_bunny_poster_big.jpg" controls>
</video>
</div>
<div>
<audio id="player_audio"
src="/samples/videosample/big_buck_bunny_720p_surround.avi"
msaudiocategory="BackgroundCapableMedia" controls>
</audio>
</div>
<div>
<div style="display: table-cell">
<div>
<button id="btn_playvideo">Play video</button>
<span style="width: 10px" />
<button id="btn_playaudio">Play audio</button>
</div>
<div>
<button id="btn_capture">Camera Capture</button>
</div>
<div>
<button id="btn_advancedcapture">Advanced Media Capture</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
清单 4-19 显示了 JavaScript 代码,包括合并 Play To 功能的代码。
Listing 4-19. Implementing Play To in the Example App
(function ()
{
"use strict";
var media_control = null;
var manager = null;
WinJS.UI.Pages.define("/samples/VideoSample/TestVideo.html", {
ready: function (element, options)
{
media_control = Windows.Media.MediaControl;
media_control.onplaypressed = function ()
{
player_audio.play();
}
media_control.onpausepressed = function ()
{
player_audio.pause();
}
media_control.onstoppressed = function ()
{
player_audio.pause();
}
media_control.onplaypausetogglepressed = function ()
{
if (media_control.isPlaying)
{
player_audio.pause();
} else
{
player_audio.play();
}
}
player_audio.onplaying = function ()
{
media_control.isPlaying = true;
}
player_audio.onpause = function ()
{
media_control.isPlaying = false;
}
player_audio.onended = function ()
{
media_control.isPlaying = false;
}
Windows.Media.MediaControl.isPlaying = false;
Windows.Media.MediaControl.artistName = "The Peach Open Movie Project";
Windows.Media.MediaControl.trackName = "Big Buck Bunny";
btn_playvideo.onclick = function ()
{
player_video.play();
};
btn_playaudio.onclick = function ()
{
player_audio.play();
}
manager = Windows.Media.PlayTo.PlayToManager.getForCurrentView();
manager.onsourcerequested = function (e)
{
e.sourceRequest.setSource(player_video.msPlayToSource);
}
}
,
});
})();
使游戏成为可能的关键因素是PlayToManager
类。为了讨论这个问题,我必须暂时深入一下魅力条和魅力条编程的主题(在第五章中有更详细的介绍)。一般来说,当用户在你的应用处于活动状态时通过点击一个 charms bar 按钮来选择一个动作时(还有其他高级场景,其中你的应用是不活动的,如第五章中所讨论的),Windows 会询问你的应用,看它是否支持用户请求的功能。正如您在介绍性章节中看到的,这可以是搜索、共享、设置或设备。通过多功能设备请求播放功能。当用户在你的应用运行时点击设备图标时,会向你的应用发出一个查询,看看它是否有一个播放视频,它愿意使用 Play To 通过网络发送。Windows 通过你的应用处理前面提到的PlayToManager
类的onsourcerequested
事件来确定你至少对 Play To 感兴趣。要发布你的内容,只需调用清单 4-19 所示的setSource
函数,通过调用它的msPlayToSource
属性,传递一个你想要的源媒体的句柄。
如果你决定在应用中使用(或不使用)Play To,这里有一些临别指南。微软希望,如果你的应用功能中有回放媒体,你就可以将 Play 暴露给 contract(这是一个将 Play 与你听到的其他 charms 结合使用的花哨词)。目前,这是一个请求,而不是命令。此外,由于 Play To 是 Windows 的一项功能,用户可以通过 charms 栏随时请求播放,因此建议在应用的整个生命周期内将媒体播放器保留在范围内。在您创建的示例中,用户在到达视频示例页面之前不会意识到播放功能;当他们离开该页面时,他们就失去了播放《大兔兔》电影的能力,因为该页面已从内存中删除。然而,如果您在用于播放目的的default.html
页面上有一个可用的根video
标记,那么用户可以自由地浏览示例应用,而不会出现任何问题。
媒体捕获
Windows 8 捕获框架为应用开发人员提供了在应用中捕获照片、音频记录和视频记录形式的媒体的能力。对于任何拥有摄像头的 Windows 8 设备,您都可以使用摄像头捕捉编程接口来录制视频或拍照。最简单的方法是使用内置的CameraCaptureUI
对话框。
在你进入这个对话框做什么之前,我应该提一下,与 Windows 8 的大多数事情一样,你的应用需要用户许可才能访问运行它的设备的板载摄像头。这意味着您需要向您的package.appxmanifest
文件添加一些功能。要使用摄像头、麦克风和录像机,必须启用网络摄像头功能。我还建议申请权限,将图片存储在图片库中,并附带网络摄像头功能。这就是所谓的图片库功能。(您需要这些功能以及音乐库功能来运行本节中的示例,所以一定要启用它们。)
CameraCaptureUI
打开代表相机取景器的全屏模式对话框。当这个对话框被激活时,用户的屏幕看起来如图 4-8 所示。
图 4-8。
Camera capture dialog
您可以指定当通过启动相机捕捉对话框的功能启动相机捕捉对话框时,用户可以使用哪些选项:
- 仅对于图片,选择
Windows.Media.Capture.CameraCaptureUIMode.photo
。 - 对于视频,选择
Windows.Media.Capture.CameraCaptureUIMode.video
。 - 如果你想让用户选择使用对话框来捕捉图片或视频,还有一个选项:
Windows.Media.Capture.CameraCaptureUIMode.photoOrVideo
。
CameraCaptureUI
还公开了一个名为PhotoSettings
的属性,它属于CameraCaptureUIVideoCaptureSettings
类型(相同的名称空间)。此属性可用于在对话框上执行附加配置。例如,您可以使用此设置来切换是否希望用户启用裁剪。我们来看一个用CameraCaptureUI
类拍照的例子。
首先,回想一下前面的例子中有额外的btn_capture
和btn_advancecapture
按钮,您忽略了它们。在清单 4-20 中,你最后为btn_capture
添加了一个事件处理程序来展示捕捉媒体是多么容易。
Listing 4-20. Using the Camera Capture Interface to Capture an Image and Place It in the Pictures Library
btn_capture.onclick = function ()
{
var capture = Windows.Media.Capture.CameraCaptureUI();
capture.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photoOrVideo)
.then(function (file)
{
if (file != null)
file.copyAsync(Windows.Storage.KnownFolders.picturesLibrary);
});
};
对于简单明了的场景来说,这是很棒的,但是它有两个缺点。首先,全屏模式对话框覆盖了整个应用用户界面,基于您正在构建的应用的类型,您可能不希望这样。其次,它只允许视频和图像捕捉。如果你想更深入地控制你的应用捕获媒体的能力——例如,如果你需要捕获音频——你可以使用MediaCapture
类来实现。您可以使用清单 4-21 中的代码在 JavaScript 中创建和初始化一个MediaCapture
对象。
Listing 4-21. Capturing Media Using the MediaCapture
Object
// Create and initialize the MediaCapture object
。
function initMediaCapture() {
var capture = null;
capture = new Windows.Media.Capture.MediaCapture();
capture.initializeAsync().then (function (result) {
}, errorHandler);
}
如果您需要稍微调整一下媒体捕获机制,WinRT 通过MediaCaptureInitializationSettings
对象提供了这样的机制。配置此设置可让您的应用指定您希望捕获如何发生的详细信息。您可以使用它进行音频采集而不是视频采集,并设定采集的格式。因为某些用例在媒体捕获时是常见的,API 还提供了编码配置文件,可用于快速设置记录格式和结构。清单 4-22 为btn_advancedcapture
按钮添加了一个事件处理程序,显示了如何使用概要文件来设置音频记录。
Listing 4-22. Capturing an Audio Recording
btn_advancedcapture.onclick = function ()
{
var capture = new Windows.Media.Capture.MediaCapture();
var profile = Windows.Media.MediaProperties.MediaEncodingProfile.createMp3
(Windows.Media.MediaProperties.AudioEncodingQuality.High);
Windows.Storage.KnownFolders.musicLibrary.createFileAsync("recordings.mp3"
,ⅵ
Windows.Storage.CreationCollisionOption.generateUniqueName).then(function (file)
{
capture.initializeAsync().then(function ()
{
capture.startRecordToStorageFileAsync(profile, file);
});
});
};
清单 4-23 使用同样的模式创建一个使用 WMV 格式捕获视频的概要文件。
Listing 4-23. Capturing Video
btn_advancedcapture.onclick = function ()
{
var capture = new Windows.Media.Capture.MediaCapture();
var profile = Windows.Media.MediaProperties.MediaEncodingProfile.createWmv
(Windows.Media.MediaProperties.VideoEncodingPropertiesHigh);
Windows.Storage.KnownFolders.musicLibrary.createFileAsync("video.wmv"
,ⅵ
Windows.Storage.CreationCollisionOption.generateUniqueName).then(function (file)
{
capture.initializeAsync().then(function ()
{
capture.startRecordToStorageFileAsync(profile, file);
});
});
};
如果您将清单 4-23 中的代码嵌入到一个应用中,您可能会注意到缺少了一些东西。当应用捕捉视频时,你没有取景器可以看到摄像机当前指向的方向。当您使用MediaCapture
类进行视频捕获时,没有用于显示取景器内容的内置用户界面(本质上是预览正在录制的内容)。JavaScript 应用的 WinRT 不同于传统的。这是因为它们没有用于呈现媒体捕获预览的内置控件(称为CaptureElement
)。相反,WinJS 重用了video
标签来完成这个任务。清单 4-24 中的例子显示了如何在应用中启用视频捕获预览。
Listing 4-24. Enabling Video Preview
var capture = new Windows.Media.Capture.MediaCapture();
..
。
var myVideo = document.getElementById("player_video");
myVideo.src = URL.createObjectURL(capture);
myVideo.play();
在前面的例子中,一旦捕获被初始化,页面上的一个video
标记的源就被设置为那个MediaCapture
对象的对象 URL,它是通过调用URL.createObjectURL
获得的。将这段代码添加到清单 4-23 中,可以在video
标签的框架中预览捕获的内容(之前大巴克兔子在这里展示)。
摘要
在本章中,您了解了在应用中使用媒体的许多方式。本章的研究结果包括:
- 将本地来源和远程网站的媒体播放集成到您的应用中
- 媒体捕获和在您的应用中实现它的许多方法,以及使用
CameraCaptureUI
可以做的许多事情 - 更强大的
MediaCapture
类,它提供了捕获音频和创建用于捕获视频的定制取景器的功能 - 在保持应用用户界面的重要性的场景中使用
MediaCapture
API(当然,在高级场景中,当您需要捕获管道的较低层次的改进时)
五、充分利用魅力和契约
Abstract
契约和魅力是 Windows 8 中引入的两个新概念,它们不仅彻底改变了应用之间的通信活动,还为开发人员引入了新的使用场景。使用 charms,用户可以在整个设备以及应用中进行搜索,向其他应用或设备发送内容,并以标准化的方式访问设置。使用契约可以进一步增强应用之间的互操作性。这种方法的美妙之处在于,安装在 Windows 8 系统上的每个应用都通过对正式交互(如打开文件或从联系人存储中选择联系人)进行良好定义的扩展来增强系统功能。作为 Windows 8 应用开发人员,您使用契约作为一种机制来处理用户通过 charms 进行的交互;您还可以使用契约来促进应用之间的这种隐式交互。这一章将在后面详细讨论契约。我们开始吧。
契约和魅力是 Windows 8 中引入的两个新概念,它们不仅彻底改变了应用之间的通信活动,还为开发人员引入了新的使用场景。使用 charms,用户可以在整个设备以及应用中进行搜索,将内容发送到其他应用或设备,并以标准化的方式访问设置。使用契约可以进一步增强应用之间的互操作性。这种方法的美妙之处在于,安装在 Windows 8 系统上的每个应用都通过对正式交互(如打开文件或从联系人存储中选择联系人)进行良好定义的扩展来增强系统功能。作为 Windows 8 应用开发人员,您使用契约作为一种机制来处理用户通过 charms 进行的交互;您还可以使用契约来促进应用之间的这种隐式交互。这一章将在后面详细讨论契约。我们开始吧。
吸引力
第一章介绍了 charms 的概念,介绍了终端用户可用的核心活动。如果你记得,在任何时候从右边滑动都会产生 charms 菜单,你可以用它来执行五个基本活动(在 Windows 8 上,尽管默认情况下不在 Windows Server 2012 上):搜索、共享、开始、设备和设置。图 5-1 显示了当魅力菜单可见时的样子。
图 5-1。
Windows charms
搜索魅力
本节将向您介绍 Windows 8 中的搜索魅力功能,详细介绍作为应用开发人员,您如何将系统范围内的搜索整合到您的应用中,甚至提供自动完成提示。如果你过去使用过 Windows 7,那么你应该熟悉 Search charm 的工作方式,因为它取代了 Windows 7 开始菜单中的搜索框。使用搜索功能,您可以在电脑上找到应用、设置和文件。
搜索魅力用法
Search charm 用于跨所有应用、文件和设置执行系统范围的搜索。此外,Windows 8 提供了一个可扩展性框架,应用可以使用该框架显式插入搜索基础架构。当你在开始屏幕上开始输入时,你可以随时看到搜索的魅力。默认情况下,Windows 8 会按名称搜索所有应用,并将应用列表过滤为与您键入的文本匹配的应用。您还应该注意到,在搜索文本框处于焦点时,会出现“搜索魅力”弹出窗口(参见图 5-2 )。
图 5-2。
Search charm at work
搜索体验
如前所述,搜索体验为应用提供了许多可扩展点。首先,让我们看看如何直接从应用启动搜索体验。从 JavaScript 执行此操作的调用非常简单:
Windows.ApplicationModel.Search.SearchPane.getForCurrentView().show();
然而,正如你在新的 Windows 8 模型中多次看到的那样,进行这种呼叫需要用户在某种程度上的参与。就搜索而言,这种参与是通过应用清单实现的,它在 Windows 应用商店中表现为给定应用所需的一组功能。
图 5-3 显示了从最终用户的角度看应用的权限。在这里,用户可以决定是否要下载和安装该应用(考虑到在这种情况下,Skype 需要使用用户的网络摄像头)。
图 5-3。
Application permissions listed for the user in the Windows Store
这是一个愚蠢的例子;当然,鉴于 Skype 的受欢迎程度以及大多数用户已经对它的功能有所了解,一般用户会下载并安装它。事实上,它是目前(在撰写本文时)社交类的顶级应用。
清单 5-1 显示了一个名为 TestSearch 的应用的简单搜索用户界面,带有一个名为TestSearch.html
的默认页面。我们首先定义了一个简单的用户界面,它只有一个按钮,用于启动搜索窗格。虽然页面上还有其他用户界面元素,但是这里的讨论主要集中在按钮上(btn_startsearch
)。
Listing 5-1. Search Example UI (TestSearch.html
)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test Search</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="TestSearch.css" rel="stylesheet" />
<script src="TestSearch.js"></script>
</head>
<body>
<div class="TestSearch fragment">
<section aria-label="Main content" role="main">
<p id="txt_searchtext">Search Text Goes Here</p>
</section>
<section aria-label="Main content" role="main">
<p>Use the search charm to initiate a search.</p>
</section>
<section aria-label="Main content" role="main">
<p><input type="button" id="btn_startsearch" value="Open Search Pane" /></p>
</section>
</div>
</body>
</html>
我们现在为btn_search
的clicked
事件添加一个事件处理程序(见清单 5-2)。
Listing 5-2. Search Example JavaScript Code (TestSearch.js
)
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SearchSample/TestSearch.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_startsearch.onclick = function ()
{
Windows.ApplicationModel.Search.SearchPane.getForCurrentView().show();
};
}
,
});
})();
如果我们编译并运行这个例子,在我们真正点击这个按钮之前,一切都应该运行良好。如果我们在没有调试的情况下运行,应用就会崩溃,单击按钮后我们会返回到 Windows 8 开始屏幕。如果我们使用 Visual Studio 2012 进行调试,窗口将切换到 IDE,并显示以下错误:
Unhandled exception at line 13, column 17 in ms-appx://8a2843c4-6e36-40f7-8966 85789a855fa8
/samples/searchsample/TestSearch.js
0x80070005 - JavaScript runtime error: Access is denied
。
WinRT information: The search extension must be specified in the manifest in order to use the Search Pane APIs
。
从错误消息中可以看出,应用默认情况下不能调用搜索基础设施。为了允许访问,应用必须首先明确声明它计划集成搜索。
无需以任何方式修改代码,我们可以通过向应用的包清单中添加正确的声明来让应用正常运行。首先,我们需要在标有package.appxmanifest
的项目中定位文件。图 5-4 显示了一个 Windows 8 应用项目的典型应用清单的位置。(习惯这个文件;这本书的很多例子都用到了它。)
图 5-4。
Application manifest in a Visual Studio 2012 project structure
搜索声明
如果您还记得,第二章在谈到已知文件夹和配置文件类型关联时讨论了一些包清单特性。图 5-5 显示了添加搜索声明后的“声明”选项卡(要添加声明,点击可用声明的下拉菜单,选择您想要的声明,然后点击“添加”按钮)。
图 5-5。
Declarations tab with Search declaration added
添加权限
既然您已经看到了最终用户是如何使用权限的,那么让我们看看如何向应用添加适当的权限。将可执行文件、入口点和起始页字段留空,重新构建应用并再次运行它。请注意,单击搜索按钮不再导致错误;相反,它会激活 Windows 8 搜索体验(见图 5-6 )。
图 5-6。
Search experience launched from an application
下表突出显示了SearchPane
类的所有成员:表 5-1 中的事件、表 5-2 中的属性以及表 5-3 中的方法。
表 5-3。
SearchPane
Methods
表 5-2。
SearchPane
Properties
表 5-1。
SearchPane
Events
我们之前创建的搜索应用很简单,但它可以作为一个基础来整合更多的功能。示例中输入的文本目前没有推回到应用,所以让我们改变一下。清单 5-3 修改了这个例子,增加了处理搜索窗格中的用户输入。现在,当我们单击按钮打开搜索窗格时,搜索文本框显示文本“输入搜索”。此外,当我们在搜索文本框中键入文本时,文本会出现在应用中。
Listing 5-3. Search Notification
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SearchSample/TestSearch.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_startsearch.onclick = function ()
{
var search =
Windows.ApplicationModel.Search.SearchPane.getForCurrentView();
search.placeholderText = "enter search";
search.onquerychanged = function (e)
{
txt_searchtext.innerText = e.queryText;
};
search.show();
};
}
,
});
})();
清单修改了我们开始的搜索示例,为queryChanged
事件添加了一个事件监听器。事件的结果打印在 HTML 界面的搜索内容区域中。图 5-7 显示了克隆的,也就是复制的应用。
图 5-7。
Cloning search input
搜索框架还允许应用注入自动建议值。清单 5-4 使用这个特性根据用户在全局搜索框中输入的第一个字符创建建议。请注意,在现实场景中,搜索建议理想情况下是从数据库或 web 服务等外部源异步加载的。
Listing 5-4. Search Auto-Suggestion Example
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SearchSample/TestSearch.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_startsearch.onclick = function ()
{
var search =
Windows.ApplicationModel.Search.SearchPane.getForCurrentView();
search.placeholderText = "enter search";
search.onquerychanged = function (args)
{
txt_searchtext.innerText = args.queryText;
};
search.onsuggestionsrequested = function (args)
{
if (search.queryText.charAt(0) == "a")
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Avengers");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Aliens");
args.request.searchSuggestionCollection
.appendQuerySuggestion("A Man Apart");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Anaconda");
} else if (search.queryText.charAt(0) == "b")
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Braveheart");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Birds");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Baby's Day Out");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Bridesmaids");
} else
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Catwoman");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Dances With Wolves");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Empire Strikes Back");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Ferris Bueller's Day Off");
}
};
search.show();
};
}
,
});
})();
图 5-8 显示了最终的用户体验。如您所见,根据在搜索文本框中输入的文本,显示了一个可能选项的列表。在该示例中,键入字母 d,这将产生包含 d 的建议列表。如前所述,在理想情况下,这些建议将从应用外部的源中检索,如数据库或 web 服务。请记住,在使用这种技术时,向用户提供的建议可能会有延迟。
图 5-8。
Auto-suggesting search
到目前为止,所有的例子都集中在应用运行时搜索应用。如果您检查这些示例,您会看到应用直到单击 search 按钮才订阅 Search。理想情况下,允许搜索的应用在全局级别订阅它,并根据搜索标准显示适当的界面。我们可以通过将搜索初始化代码移出btn_startsearch
click 处理程序来轻松解决这个问题,但是另一个问题呢?
在应用中搜索
如果你研究一下 Windows 8 的搜索体验,你会发现除了搜索应用、文件和设置内容之外,用户还可以直接在应用中搜索。清单 5-3 和 5-4 中的例子简单地提到了这一点;然而,前面的例子都是从应用运行开始的,并且在应用保持该状态时执行搜索。也可以在目前没有运行的应用中进行搜索。在这些场景中,您选择一个应用进行搜索;应用启动时,搜索信息已经存在,并且在许多情况下,搜索已经执行。用我们正在开发的示例应用来尝试一下。它会启动,但不会像您预期的那样响应搜索字符串。这是因为应用需要对其启动代码进行一些修改来支持这种情况。
通常,用户点击代表 Windows 8 应用的磁贴就可以启动该应用。然而,在搜索的情况下,应用是通过单击搜索窗格上代表它的图标来启动的。当这种情况发生时,理想的体验是直接进入特定于应用的搜索屏幕;但是正如你在例子中看到的,除非应用知道它是作为搜索的一部分被启动的(相对于正常激活),否则应用会继续显示其标准的激活用户体验。为了帮助解决这一问题,在启动过程中,Windows 8 应用会被传递识别激活源的参数。因此,当应用通过搜索启动时,它有一个搜索激活类型,而不是标准的启动激活。通过在应用的onactivated
事件处理程序中测试这一点,当应用使用 search 启动时,您可以用所有正确的信息引导应用。清单 5-5 说明了这一点。
Listing 5-5. Handling Search Activation (default.js
)
"use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args)
{
if (args.detail.kind === activation.ActivationKind.launch)
{
args.setPromise(WinJS.UI.processAll().then(function ()
{
WinJS.Navigation.navigate("samplelist.html");
}));
} else if (args.detail.kind === activation.ActivationKind.search)
{
WinJS.Navigation.navigate
ⅱ
("/samples/searchsample/testsearch.html", args.detail.detail[0].queryText);
}
};
在本例中,我们修改了示例应用的onactivated
事件处理程序,以测试搜索激活。虽然应用通常会启动页面samplelist.html
,但是在激活类型为search
的场景中,我们会启动testsearch.html
。如果我们发现应用已经通过搜索激活,我们将用户直接导航到搜索示例,并将queryText
值(当前在全局搜索框中的文本)传播到该页面。清单 5-6 显示了如何修改搜索页面来支持这一变化。
Listing 5-6. Handling Search Activation
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SearchSample/TestSearch.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)
{
var state = WinJS.Navigation.state;
var search =
Windows.ApplicationModel.Search.SearchPane.getForCurrentView();
if (state != null)
{
txt_searchtext.innerText = state;
}
search.onquerychanged = function (args)
{
txt_searchtext.innerText = args.queryText;
//perform search against back end and present the results
};
search.onsuggestionsrequested = function (args)
{
if (search.queryText.charAt(0) == "a")
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Avengers");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Aliens");
args.request.searchSuggestionCollection
.appendQuerySuggestion("A Man Apart");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Anaconda");
} else if (search.queryText.charAt(0) == "b")
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Braveheart");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Birds");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Baby's Day Out");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Bridesmaids");
} else
{
args.request.searchSuggestionCollection
.appendQuerySuggestion("Catwoman");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Dances With Wolves");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Empire Strikes Back");
args.request.searchSuggestionCollection
.appendQuerySuggestion("Ferris Bueller's Day Off");
}
};
btn_startsearch.onclick = function ()
{
search.placeholderText = "enter search";
search.show();
};
}
,
});
})();
分享魅力
Share charm 是应用之间最突出的共享方式。它专为这种功能而设计,因此是 Windows 8 的一流应用间通信机制。当应用订阅共享体验时,它可以发送和接收各种形式的内容,从纯文本和 HTML 内容到复杂的结构。
当涉及到权限时,共享遵循类似于搜索的模式——在这种情况下,需要Share Target
声明。与搜索一样,当需要声明时,它会以权限的形式出现,用户可以在决定是否下载和安装你的应用时进行探索。如果他们因为兴奋而错过了这个,他们只需要使用设置菜单来访问给定应用运行所需的相同权限列表。图 5-9 显示了非常受欢迎的 ESPN 应用,设置面板的权限屏幕暴露在外。
图 5-9。
Permissions available through Settings menu
共享魅力支持两种类型的契约:本质上,一种用于提供内容,另一种用于接收内容。因为等式的给予方只能由最终用户通过 Share charm 激活,所以不需要明确定义任何东西来启动它。
共享为源
就 Windows 8 应用而言,它总是可以“分享”它认为有意义分享的内容。让我们看一个简单的例子。我们将与 Windows Mail 应用共享示例应用中的一些文本(但实际上是与任何支持文本共享的应用)。清单 5-7 显示了启动这个的代码。
Listing 5-7. Basic Sharing
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TestSharing</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="TestSharing.css" rel="stylesheet" />
<script src="TestSharing.js"></script>
</head>
<body>
<div class="TestSharing fragment">
<section aria-label="Main content" role="main">
<p><input type="text" id="txt_content" /></p>
</section>
<section aria-label="Main content" role="main">
<p><input type="button" id="btn_openshare" value="Open Share" /></p>
</section>
</div>
</body>
</html>
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SharingSample/TestSharing.html", {
ready: function (element, options)
{
var transfer = Windows.ApplicationModel
。*本文件迟交
DataTransfer.DataTransferManager;
var share = transfer.getForCurrentView();
share.ondatarequested = function ( args)
{
args.request.data.properties.title = "Sample Sharing Text";
args.request.data.properties
.description = "Description of the text being sent";
args.request.data.setText(txt_content.value);
};
btn_openshare.onclick = function ()
{
transfer.showShareUI();
};
}
,
});
})();
当我们运行这个应用并点击 Open Share 按钮时,就会调用ondatarequested
中的代码。它用来自txt_content
文本框的文本填充共享上下文。基于指定的内容(在这种情况下,文本),可以处理接收该内容类型的应用列表出现在共享窗格中(参见图 5-10 )。
图 5-10。
Sharing pane with compatible applications for the shared content type
当单击其中一个应用时,该应用将在共享视图中启动,用户可以选择进行最后的编辑并完成共享过程。图 5-11 显示了点击 Windows Mail 应用时会发生什么。
图 5-11。
Sharing with Windows Mail
在清单 5-7 中你可以看到,共享遵循一个事件订阅模型来连接。这是因为,如上所述,共享仅通过 Windows 8 共享体验进行。每当用户暴露共享窗格(无论是通过 Share charm 还是通过showShareUI()
)时,都会调用事件处理程序ondatarequested
。它让应用有机会将任何信息加载到共享上下文中。
股份关系的给予通过三个主要的类来完成:DataTransferManager
、DataRequest
和DataPackage
。DataTransferManager
用于连接到共享上下文,监听共享按钮的点击。DataRequest
封装 Windows 8 为响应用户开始共享体验而发起的数据请求——通过args.request
属性公开。它通过ondatarequested
事件的事件处理程序传递给应用。DataPackage
代表在共享应用之间传输的数据的概念包;因此它控制数据的实际读写。它是通过args.request.data
属性暴露的。表 5-4 列出了DataPackage
类的一些方法。
表 5-4。
DataPackage
Methods
DataPackage
还通过其properties
属性公开了一个DataPackagePropertySet
类型的属性,您可以使用它将元数据应用到正在共享的内容。它包含title
和description
属性,但也是一个属性包,可用于以名称/值格式存储附加属性。
清单 5-8 使用数据包类提供的标准格式之一在应用之间发送数据:text
。正如您从表 5-4 中看到的,除此之外,您还可以跨应用发送图像、RTF 内容、HTML 内容,甚至原始数据(使用setData
)。清单 5-7 显示了如何在两个应用之间的DataPackage
中添加一个图像。
Listing 5-8. Sharing Images
(function ()
{
"use strict";
WinJS.UI.Pages.define("/samples/SharingSample/TestSharing.html", {
ready: function (element, options)
{
var transfer = Windows.ApplicationModel.DataTransfer
。*本文件迟交
DataTransferManager;
var share = transfer.getForCurrentView();
share.ondatarequested = function (args)
{
args.request.data.properties.title = "Sample Sharing Text";
args.request.data.properties
。*本文件迟交
description = "Description of the text being sent";
//set text
args.request.data.setText(txt_content.value);
//set image
var file = Windows.Storage.ApplicationData.current
.localFolder.getFileAsync("image_file.bmp");
var stream = Windows.Storage.Streams
.RandomAccessStreamReference.createFromFile(file);
args.request.data.setBitmap(stream);
};
btn_openshare.onclick = function ()
{
transfer.showShareUI();
};
}
,
});
})();
作为目标的共享
每个应用都有一组它支持的内容类型(一些是公共的,一些是私有的)。例如,Windows Mail 应用可以接受 HTML 来呈现从“giving”应用生成的消息。为了正确呈现内容,消息必须遵循特定的格式(在本例中由 Microsoft 控制)。这个故事的寓意是,与直接从应用作者那里获得的文档定义的特定应用需求相比,内容结构更不重要。
通过共享契约接收内容要比发送内容复杂得多。首先,与向其他应用发送内容不同,接收内容需要将应用注册为共享内容类型的目标。这是有意义的,因为当用户希望在应用之间传输内容时,您不会希望共享的每个应用都出现在共享目标列表中。相反,Windows 8 会过滤用户点击共享按钮时出现的应用列表,只包括那些可以使用共享格式内容的应用。不难理解为什么想要注册为共享目标的应用需要声明它们可以处理的内容类型。
毫不奇怪,这样做的模式类似于搜索的注册和处理方式。首先,您必须将共享目标功能添加到应用清单中。图 5-12 显示了该能力的节点。
图 5-12。
Share Target configuration
在图片中,我添加了一些应用支持的数据格式。通过为格式类型指定格式名字对象,可以添加数据格式。在这种情况下,我是说应用可以处理任何类型的文本。(对于更具体的格式,我建议使用带有格式名的setData
方法,该格式名可用于标识这种类型文档的布局和结构,而不仅仅是通过 MIME 类型。)让我们将简单的共享示例扩展为支持接收文本格式的内容。
如果您还记得搜索示例,当应用被激活时,它的激活类型由 Windows 注册并作为启动参数传递,这样应用就可以为给定的场景选择显示哪个页面。您还可以使用文件类型关联来启用共享。这样做允许应用使用非标准格式通过StorageItems
属性共享数据。如果使用的文件格式与指定的数据格式相匹配,它将被视为匹配;否则,共享源必须使用args.Request.Data.SetStorageItems
方法将非标准格式(作为存储文件)添加到共享上下文中。图 5-13 显示了包含数据格式和文件类型关联的共享目标配置页面的放大视图。
图 5-13。
Share Target configuration with file-type association
清单 5-5 使用了从 Windows 8 传递给它的 activation kind 参数,当应用从 Search charm 启动时,它直接指向 TestSearch 页面。清单 5-9 修改了清单 5-5,增加了一个新的else-if
块来过滤掉那些ActivationKind
是shareTarget
的应用初创公司。
Listing 5-9. shareTarget
Activation Kind at Work
"use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args)
{
if (args.detail.kind === activation.ActivationKind.launch)
{
args.setPromise(WinJS.UI.processAll().then(function ()
{
WinJS.Navigation.navigate("samplelist.html");
}));
} else if (args.detail.kind == activation.ActivationKind.search)
{
WinJS.Navigation.navigate
("/samples/searchsample/testsearch.html", args.detail.detail[0].queryText);
}
else if (args.detail.kind === activation.ActivationKind.shareTarget)
{
var search_target_activated_args = args.detail.detail[0];
if (search_target_activated_args.shareOperation.data
.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.text))
{
search_target_activated_args.shareOperation
.data.getTextAsync().then(function (text)
{ WinJS.Navigation.navigate("/samples/sharingsample/testsharing.html", text)
});
}
}
};
如您所见,这个过程与搜索所用的方法非常相似。我们首先从args
对象中检索特定于共享的对象实例。(在 JavaScript 中,这是一个动态对象,所以直到运行时才知道实际公开的类型。这种行为可能很难处理。)接下来,我们确保分享的内容是应用支持的格式;如果是这样,我们使用适当的方法来检索它(在本例中是getTextAsync
)。当然,因为这是一个异步调用,我们知道必须使用then
延续来处理调用的结果。最后,我们导航到testsharing.html
,传递共享的内容。清单 5-10 显示了修改后的testsharing.html
代码。基本上,我们所做的就是检查state
属性是否包含值;如果是,我们将该值放入页面上的文本框中。
Listing 5-10. Presenting Shared Content
"use strict";
WinJS.UI.Pages.define("/samples/SharingSample/TestSharing.html", {
ready: function (element, options)
{
var transfer = Windows.ApplicationModel.DataTransfer.DataTransferManager;
var share = transfer.getForCurrentView();
var state = WinJS.Navigation.state;
if (state != null)
{
txt_content.value = state;
}
share.ondatarequested = function (args)
{
args.request.data.properties.title = "Sample Sharing Text";
args.request.data.properties
.description = "Description of the text being sent";
//set text
args.request.data.setText(txt_content.value);
};
btn_openshare.onclick = function ()
{
transfer.showShareUI();
};
}
,
为了验证这一点,我去 google.com
搜索了这本书。当我复制谷歌搜索文本框中的内容,然后点击共享时,该应用显示为可能的共享目标之一。点击后显示如图 5-14 所示的视图。
图 5-14。
Sharing between IE and the example application
需要注意的一点是,共享目标的宽度不同于普通应用。共享视图的布局必须考虑到这一点,否则用户体验会受到影响。将用户导航到正常尺寸的标准页面(像素或边距定位)可能会使用户无法共享。
开始魅力
Start charm 并不是一个扩展点,而是为了完整性而在这里列出的。它的功能类似于键盘上的硬件 Windows 键(并非所有键盘都有 Windows 键)。单击此按钮可以在新的开始屏幕和当前运行的应用之间切换,或者根据上下文在 Windows 桌面之间切换。
设备魅力
任何内置到使用外部设备的应用中的功能都通过 Devices charm 滑出菜单显示出来。如果应用订阅了适当的 Windows 8 机制(稍后将讨论),单击 Devices charm 会显示功能和内容可以导出到的目标设备。
视频播放和数字生活网络联盟(DLNA)设备就是一个很好的例子。在应用中回放视频时,您可以选择将视频流式传输到任何兼容 DLNA 的屏幕(例如,兼容 Xbox 或 DLNA 的电视)。另一个很好的例子——也是第六章的基础——是打印。当在应用屏幕中启用打印时,用户可以在设备魅力的滑出式属性页中看到潜在的打印设备。后一种情况的有趣之处在于,它在某种程度上是间接起作用的。与 Share charm 不同,“设备”菜单显示一组设备,这些设备专门映射到当前应用视图显示的行为类型。在打印示例的情况下,打印功能的使用(通过PrintManager
类)向 Windows 展示了要在列表中显示的一组适当的设备:打印机。与共享一样,该列表会筛选出有必要列出的设备。图 5-15 显示了我点击设备图标时的 Internet Explorer 应用(不是桌面版)。
图 5-15。
Devices pane when IE is the running app
设置魅力
设置魅力圆了魅力菜单扩展性的故事。毫不奇怪,它提供了一个扩展点,您可以将应用设置和配置用户界面附加到该扩展点。这些挂钩在设置菜单中显示为链接;单击时,它们会触发可以在应用中处理的事件。设置链接最常见的用途是启动应用中的配置页面,否则这些页面将无法导航。清单 5-11 是一个挂钩 Windows 8 设置魅力的基本例子。
Listing 5-11. Connecting to the Settings Charm
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/SettingsSample/TestSettings.html", {
ready: function (element, options) {
var settings = Windows.UI.ApplicationSettings
.SettingsPane.getForCurrentView();
settings.oncommandsrequested = function (args)
{
var privacy_command = new Windows.UI.ApplicationSettings
.SettingsCommand("privacy_statement", "Privacy Policy", function (command)
{
var msg = new Windows.UI.Popups
.MessageDialog("This app does nothing so there is no policy.");
msg.showAsync();
});
var help_command = new Windows.UI.ApplicationSettings
.SettingsCommand("helo", "Help", function (command)
{
var msg = new Windows.UI.Popups
.MessageDialog("This app does nothing so there is need for help.");
msg.showAsync();
});
var about_command = new Windows.UI.ApplicationSettings
.SettingsCommand("about", "About", function (command)
{
var msg = new Windows.UI.Popups
.MessageDialog("This app was developed by the collective we.");
msg.showAsync();
});
var options_command = new Windows.UI.ApplicationSettings
.SettingsCommand("options", "Options", function (command)
{
var msg = new Windows.UI.Popups
.MessageDialog("This app does nothing so there are no options.");
msg.showAsync();
});
args.request.applicationCommands.clear();
args.request.applicationCommands.append(options_command);
args.request.applicationCommands.append(about_command);
args.request.applicationCommands.append(help_command);
args.request.applicationCommands.append(privacy_command);
};
}
,
});
btn_settings.onclick = function ()
{
Windows.UI.ApplicationSettings.SettingsPane.show();
};
})();
添加到applicationCommands
属性中的每个命令实际上都包含一个标签、唯一的名称和一个处理函数,当单击命令的链接时会调用这个函数。图 5-16 显示了这个应用运行时的样子。
图 5-16。
Settings pane with additional settings added
隐性契约
与魅力驱动的契约一样,隐式契约通过允许每次额外安装应用时使用模式的新组合,扩展了 Windows 8 的开箱即用体验。然而,与它们的对手不同,与隐式契约的集成点更加无缝:应用甚至可能颠覆前面描述的以用户为中心的权限模型。例如,一个应用可以通过在其应用清单中指定它处理的文件类型的文件扩展名来声明它处理某种类型的文件。从 Windows 上的任何地方(甚至在另一个应用中)激活该类型的文件会立即启动声明的应用(或者提供一个界面,在该界面中,用户可以选择适当的应用,以便在多个应用声明它们可以处理指定文件类型的情况下使用)。
隐式契约包括扩展内置拣选器的契约:FileSavePicker
、FileOpenPicker
和ContactPicker
。像搜索契约和共享目标契约一样,像这样的挑选者依赖于读取激活类型。表 5-5 列出了应用可用的所有可能的激活类型。
表 5-5。
ActivationKind
Members
清单 5-12 展示了使用激活类型来决定应用应该如何表现自己的策略。
Listing 5-12. More Contracts
(function ()
{
"use strict";
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");
}));
} else if (args.detail.kind == activation.ActivationKind.search)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.shareTarget)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.fileOpenPicker)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.fileSavePicker)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.contactPicker)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.file)
{
//TODO: target the page you want here
}
else if (args.detail.kind === activation.ActivationKind.cameraSettings)
{
//TODO: target the page you want here
}
};
})();
摘要
随着本章的结束,您应该会对 Windows 8 的精神和环境有更深入的了解,以便在 Windows 8 的 JavaScript 开发中为您服务。Charms 使用户能够在整个设备和应用中进行搜索,向其他应用或设备发送内容,并以标准化的方式访问设置。通过使用契约,可以进一步增强应用之间的互操作性。在 Windows 8 平台中访问五个 charms。这些包括开始和本章所涉及的四个:搜索、共享、设备和设置。以下是这些魅力的一些要点:
- Search charm 可用于跨所有应用、文件和设置执行系统范围的搜索。
- Share charm 是应用之间最突出的共享方式。当应用订阅共享体验时,它可以以从简单到复杂的各种形式发送和接收内容。
- 内置于使用外部设备的应用中的功能通过设备魅力显现出来。设备魅力表面的目标设备的功能和内容可以导出。
- Settings charm 提供了一个扩展点,应用设置和配置用户界面可以附加到这个扩展点上。它最常见的用途是在应用中启动原本无法导航的配置页面。
- 隐式契约通过允许使用模式的新组合与每个额外的应用安装相结合,扩展了 Windows 8 的开箱即用体验。
六、解决打印问题
Abstract
现在你已经了解了所有的魅力和它们的用法和功能,你可以进入它们在现实世界中的使用细节。第五章讨论了设备的魅力。这一章深入探讨了在一个特定的场景中使用这种魅力的更多细节:打印。如前一章所述,当在应用屏幕中启用打印时,用户可以在设备魅力的弹出属性页中看到潜在的打印设备。本章还比较了 Windows 8 中的打印和 Windows 传统应用中的打印。并且查看创建了打印任务的示例。我们开始吧。
现在你已经了解了所有的魅力和它们的用法和功能,你可以进入它们在现实世界中的使用细节。第五章讨论了设备的魅力。这一章深入探讨了在一个特定的场景中使用这种魅力的更多细节:打印。如前一章所述,当在应用屏幕中启用打印时,用户可以在设备魅力的弹出属性页中看到潜在的打印设备。本章还比较了 Windows 8 中的打印和 Windows 传统应用中的打印。并且查看创建了打印任务的示例。我们开始吧。
Windows 8 之前的打印
旧的 Windows 应用——现在称之为桌面应用——使用菜单范例来执行应用命令。这意味着一个应用通常有一个内容区域和一个用户可以通过命令控制内容的区域。这个命令区域就是菜单,它开始只是一个顶级菜单,后来演变成上下文菜单、工具栏和专用于发布命令的浮动窗口。
菜单之所以很棒,有很多原因,其中一个重要的原因是,它们可以让你直接看到一个应用可以执行的所有操作的完整列表。尽管它们是上下文感知的,菜单使用启用/禁用机制来训练用户在任何给定的时间什么能做什么不能做。好处是你知道在某个时候,你可以执行一个动作。
相比之下,上下文菜单和功能区等功能遵循不同的方法。随着应用可以执行的动作数量的增加,将它们全部列在顶部菜单中变得非常困难。在与应用的大多数正常交互过程中,大部分操作都被禁用。在这种情况下,使用上下文菜单只会显示适当的命令,即与特定上下文相关的命令。缺点是这种方法没有向用户宣传功能。
历史上,打印是通过菜单和命令的机制呈现给用户的。用户的打印方法很简单:单击文件菜单,如果菜单选项存在,则选择打印。图 6-1 显示了在我发表我最喜欢的博客作者艾德·博特(CNET)的一篇文章之前,Windows 8 机器上的 Internet Explorer 10 的文件菜单。
图 6-1。
The File menu with printing enabled
作为开发桌面的开发人员。NET 应用中,由您决定哪些屏幕包含可打印的内容并需要启用打印功能,哪些屏幕不需要。虽然 IE10 不是一个. NET 应用,但同样的规则也适用。图 6-2 显示了 IE10 的启动屏幕:因为它没有实际内容,所以没有什么可以打印的,因此文件菜单中的打印功能被禁用。
图 6-2。
The File menu with printing disabled
与标准的桌面应用不同,基于 web 的应用(JavaScript 开发人员以前可能开发过的那种应用)作为内容存在于总体应用(浏览器)的上下文中。虽然菜单可以模仿桌面上的功能,但习惯上是以链接和按钮的形式在内容中嵌入命令。在 JavaScript 中,您只能通过window.print()
函数以编程方式调用打印对话框;您不能启动实际的打印操作(即,打印与用户看到的内容不同的文档)。因此,通常的方法是重定向到“打印就绪”页面,用户可以在此启动打印操作。当然,因为命令嵌入在内容的前端和中心,用户可以看到它们,广告功能的问题就解决了。
Windows 8 打印故事
当您过渡到 Windows 8 应用时,从用户的角度来看,打印故事开始失去焦点。第一,菜单没有了,所以没有标准化的控制机制可言(app bars 确实在一定程度上填补了这个角色)。其次,没有用户可识别的打印命令或 charm——打印是通过 Devices charm 进行的,在我看来,这一点也不直观。
鉴于这一事实,您可能认为采用将命令嵌入内容的 web 应用方法是可行的,但您可能错了(至少根据微软发布的样式指南)。相反,这个想法是使用底部的应用栏——专门用于 Windows 8 应用的命令(按照惯例)的区域——并从应用中以编程方式调用打印体验,而不是期望用户单击设备图标来确定他们当前的页面是否可以打印。
创建打印任务
此示例从演示 Windows 8 中的打印开始,而不是向您展示如何以编程方式启动打印过程。清单 6-1 是一个简单的应用,当点击它时,在打印环境中打开设备弹出窗口。
Listing 6-1. A Simple Printing App
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TestPrinting</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="TestPrinting.css" rel="stylesheet" />
<script src="TestPrinting.js"></script>
</head>
<body>
<div class="TestPrinting fragment">
<header aria-label="Header content" role="banner">
</header>
<section aria-label="Main content" role="main">
<p>
<input id="btn_start" type="button" value="Start Printing" />
</p>
</section>
</div>
</body>
</html>
该文件的相关 JavaScript 如清单 6-2 所示。
Listing 6-2. Print Experience JavaScript Handler
btn_start.onclick = function (e)
{
Windows.Graphics.Printing.PrintManager.showPrintUIAsync();
};
该清单要求 Windows 在单击指定按钮时显示打印用户界面。该界面的第一个视图总是显示可供用户选择的打印机列表。为了显示该列表,必须启用打印。
为应用启用打印意味着处理PrintManager
类的printtaskrequested
方法并调用createPrintTask
函数(见清单 6-3)。
Listing 6-3. Creating a Print Task
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TestPrinting</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="TestPrinting.css" rel="stylesheet" />
<script src="TestPrinting.js"></script>
</head>
<body>
<div class="TestPrinting fragment">
<header aria-label="Header content" role="banner">
<button class="win-backbutton" aria-label="Back" disabled type="button"></button>
<h1 class="titlearea win-type-ellipsis">
<span class="pagetitle">Welcome to TestPrinting</span>
</h1>
</header>
<section aria-label="Main content" role="main">
<p><input id="btn_print" type="button" value="Enable Printing" /></p>
</section>
</div>
</body>
</html>
清单 6-4 显示了这背后的 JavaScript 代码。
Listing 6-4. Handling a Print Request from Windows
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/PrintingSample/TestPrinting.html", {
ready: function (element, options) {
btn_print.onclick = function (e)
{
var print_manager = Windows.Graphics.Printing
.PrintManager.getForCurrentView();
print_manager.onprinttaskrequested = function (print_event)
{
print_event.request
.createPrintTask("Sample Print Task", function (args)
{
});
};
};
}
,
});
})();
将创建打印任务的代码放入按钮click
事件对于日常使用来说是不切实际的。在正常情况下,支持打印的代码位于页面级别,不会被按钮调用。这样,点击设备图标会立即显示应用的打印选项。在本例中,它展示了在设备魅力和设备弹出窗口中启用打印的方式。如果您运行示例,然后单击 Devices charm,您的屏幕应该如图 6-3 所示。
图 6-3。
The Devices charm fly-out before printing is enabled by the application
单击“启用打印”按钮将应用挂接到打印。现在,点击设备图标,调用onprinttaskrequested
,显示打印目标列表,如图 6-4 所示。
图 6-4。
Devices charm fly-out after printing is enabled by the application
因为我使用的特定机器(Windows 8 模拟器)没有连接打印机,所以没有打印机出现在列表中。OneNote 和 Microsoft 的 XPS Writer 具有打印机的功能,因此它们显示为默认设置。
注意应用也可以通过调用showPrintUIAsync
通过PrintManager
类初始化打印体验。这个函数除了自动执行用户点击设备的动作之外什么也不做。您可以修改代码样本来尝试这样做:首先向用户界面添加一个新按钮(参见清单 6-5)。
Listing 6-5. Adding a Button to Invoke the Printing Pane
<section aria-label="Main content" role="main">
<p>
<input id="btn_start" type="button" value="Start Printing" />
</p>
</section>
然后处理该按钮的click
事件,如清单 6-6 所示。
Listing 6-6. Displaying the Printing Pane from Code
btn_start.onclick = function (e)
{
Windows.Graphics.Printing.PrintManager.showPrintUIAsync();
};
使用 WinJS 打印
要完成该过程并提供实际的打印文档,您需要创建一个打印文档。对于 XAML 应用,打印文档必须使用不同的PrintDocument
类进行实例化。使用 WinJS 的 HTML 应用不需要这个。WinJS 提供了一个实用程序对象(MSApp
),您可以使用它将代表应用 HTML 的 document 对象转换为打印文档;实用方法是getHtmlPrintDocumentSource
。使用这种方法,如清单 6-7 所示,您可以格式化打印源,用于打印到前面讨论的目标。
Listing 6-7. Setting the Print Source for a Document
btn_print.onclick = function (e)
{
var print_manager = Windows.Graphics.Printing.PrintManager
.getForCurrentView();
print_manager.onprinttaskrequested = function (print_event)
{
print_event.request.createPrintTask("Sample Print Task"
,function (args)
{
args.setSource(MSApp.getHtmlPrintDocumentSource(document));
});
};
};
btn_start.onclick = function (e)
{
Windows.Graphics.Printing.PrintManager.showPrintUIAsync();
};
设置打印源后,单击“启用打印”按钮,然后单击“开始打印”按钮,您将进入相同的“设备”弹出窗口。但是现在有了更多。点击任一打印目标将显示如图 6-5 所示的弹出窗口。您应该看到文档第一页的预览,分页控件允许您根据需要快速导航到其他页面。该弹出菜单还允许您指定要打印的文档的方向,并应用附加选项(通过更多设置链接),如页面布局和纸张质量。
图 6-5。
Print fly-out after the source is set
WinJS 打印实现的伟大之处在于它自动化了分页。在标准 WinRT 打印中,应用开发人员通常必须确定打印文档的“页面”由什么组成。因为 WinJS 使用 HTML,所以大部分功能都是内置的。让我们修改文档来说明(见清单 6-8)。
Listing 6-8. Multipage Document
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TestPrinting</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="TestPrinting.css" rel="stylesheet" />
<script src="TestPrinting.js"></script>
</head>
<body>
<div class="TestPrinting fragment">
<header aria-label="Header content" role="banner">
<button class="win-backbutton" aria-label="Back" disabled type="button"></button>
<h1 class="titlearea win-type-ellipsis">
<span class="pagetitle">Welcome to TestPrinting</span>
</h1>
</header>
<section aria-label="Main content" role="main">
<p><input id="btn_print" type="button" value="Enable Printing" /></p>
</section>
<section aria-label="Main content" role="main">
<p>
<input id="btn_start" type="button" value="Start Printing" />
</p>
</section>
<div aria-label="Main content" role="main" style="width:200px;">
<p>
Four score and seven years ago our fathers brought forth on this continent
a new nation conceived in liberty and dedicated to the
proposition that all men are created equal
。
</p>
<p>
Now we are engaged in a great civil war testing whether that nation
,
or any nation so conceived and so dedicated, can long endure
。
We are met on a great battlefield of that war. We have come to dedicate
a portion of that field as a final resting place for those who
here gave their lives that that nation might live. It is altogether
fitting and proper that we should do this
。
</p>
<p>
But in a larger sense we can not dedicate - we can not consecrate –
we can not hallow - this ground. The brave men living and dead who
struggled here, have consecrated it far above our poor power to add
or detract
。
</p>
</div>
</div>
</body>
</html>
根据设备的不同,如果运行这个示例,应该会看到文档现在是两页(这可能会因用户的页面大小而略有不同);见图 6-6 。
Note
如果这在你的电脑上没有显示为两页,在Main content div
的正文中添加更多的文本,直到它结束。
图 6-6。
Printing with paging
打印的要素
应用可以订阅打印完成后触发的事件。为此,您需要处理PrintTask
对象的oncompleted
事件。表 6-1 提供了可以在PrintTask
课堂上订阅的活动的完整列表。
表 6-1。
PrintTask Events
| 事件 | 描述 | | --- | --- | | `completed` | 打印任务完成时引发 | | `previewing` | 当打印系统初始化打印预览模式时引发 | | `progressing` | 引发以提供进度信息,说明已将多少打印内容提交给打印子系统进行打印 | | `submitting` | 当打印任务开始向打印子系统提交要打印的内容时引发 |清单 6-9 是相同的 JavaScript,订阅了表 6-1 中列出的事件。
Listing 6-9. PrintTask
Events Handled
(function () {
"use strict";
WinJS.UI.Pages.define("/samples/PrintingSample/TestPrinting.html", {
ready: function (element, options) {
btn_print.onclick = function (e)
{
var print_manager = Windows.Graphics.Printing.PrintManager
.getForCurrentView();
print_manager.onprinttaskrequested = function (print_event)
{
var print_task = print_event.request
.createPrintTask("Sample Print Task", function (args)
{
args.setSource(MSApp.getHtmlPrintDocumentSource(document));
});
print_task
。oncompleted
= function (e)
{
//called when the print has completed
};
print_task
。onsubmitting
= function (e)
{
//called when the actual print button is hit
。
//The print activity has been submitted
。
};
print_task
。onprogressing
= function (e)
{
//called to give feedback on the progress of the print job
};
print_task
。onpreviewing
= function (e)
{
//called when the print document is being previewed
};
};
};
btn_start.onclick = function (e)
{
Windows.Graphics.Printing.PrintManager.showPrintUIAsync();
};
}
,
});
})();
PrintTask
还包含许多属性,可用于事后处理源或配置用户的打印体验(见表 6-2 )。
表 6-2。
PrintTask
Properties
使用Options
属性(它公开了PrintTaskOptions
对象),应用可以修改打印体验格式的各个方面。表 6-3 强调了该类的关键属性。如您所见,这些映射到可以编程设置的打印机选项。
表 6-3。
PrintTaskOptions
Properties
由突出显示的属性返回的大多数对象都包含在Windows.Graphics.Printing
名称空间中。当谈到打印时,这是挂钩 Windows 8 APIs 向开发人员公开的内容的起点。表 6-4 显示了这个名称空间包含的类型的完整列表。
表 6-4。
Windows.Graphics.Printing
Namespace Classes
摘要
恭喜你!现在,您对 Windows 应用商店应用的打印功能有了具体的了解。阅读完本章并检查示例后,您已经了解了
- 使用设备魅力进行打印
- 如何使用 WinJS 提供的工具对象将代表应用 HTML 的实际文档对象转换为打印文档
- 通过单击打印目标显示的弹出窗口
- WinJS 打印实现如何自动分页
- 应用如何订阅在打印预览、进行或完成后触发的事件
PrintTask
,包含对事后处理源或配置用户打印体验有用的属性- 使用
Options
属性修改应用打印体验的各种格式
七、提供清晰的通知
Abstract
前一章的讨论以 Windows 8 锁定屏幕的演练结束,包括使用 Windows 应用商店 API 内置的通知机制以某种形式向用户发送消息的示例。这就引出了对两种主要通知类型的讨论:祝酒词和瓷砖。本章研究了这些通知,它们的用途,以及作为一名开发者,你如何设计它们来最有效地为你的应用和用户服务。
前一章的讨论以 Windows 8 锁定屏幕的演练结束,包括使用 Windows 应用商店 API 内置的通知机制以某种形式向用户发送消息的示例。这就引出了对两种主要通知类型的讨论:祝酒词和瓷砖。本章研究了这些通知,它们的用途,以及作为一名开发者,你如何设计它们来最有效地为你的应用和用户服务。
通知过程
让我们从定义现代 Windows 8 应用环境中的通知开始。通知是发送给应用最终用户的消息,提供有关应用使用的有意义的信息。可能是让用户知道他们的密码无效,他们无权访问应用中的某些内容,他们已经空闲了一段时间,他们的会话即将超时,或者应用中有错误。这种类型的通用通知不是本章讨论的内容。相反,您关注的是一类以前在 Windows 开发的 API 环境中没有明确表示的通知:当应用不在焦点上时发生的通知。
以微软 Office 的 Outlook 邮件客户端为例。如果您在 Outlook 中启用了 toast 通知,则每次收到新邮件时,系统托盘区域上方都会出现一个小的无边框窗口。单击此 toast 窗口会立即启动完整的 Outlook 客户端,并允许您查看邮件。
像这样的模式并没有融入传统的 Windows 应用开发中。每个开发人员都必须使用隐藏窗口和系统托盘图标创建该功能的独特实现。最重要的是,因为操作系统不提供任何机制来支持它,所以任何希望使用这种机制的应用都必须通过在关闭时隐藏自己或者拥有单独的可执行文件(守护程序)来表现出不运行的假象,该可执行文件拥有通知过程,并且还负责在用户与 toast 窗口交互时启动主应用。Windows 8 采用了通知的概念,并将其融入操作系统,以便所有 Windows 8 应用使用相同的机制,即所有用户都能立即识别并在所有应用中保持一致的机制。
作为构建面向现代应用平台的应用的 Windows 8 开发人员,您可以使用四种关键机制向您的用户提供通知:toast 通知、磁贴通知、徽章通知和推送通知。Toast、tile 和 badge 通知通常需要应用正在运行(如果不在焦点上),而推送通知代表了一种新的通知类别,其中应用不需要在客户端上运行。
如前所述,Windows 8 试图解决的部分通知问题是一致性问题。如果每个应用都必须创建自己的通知框架,就像遗留应用一样,用户不仅很难确定用户界面元素是否是通知,还很难确定它是什么类型的通知以及它公开了什么交互模式。例如,Internet Explorer 10 使用通知。下载完成后,任务栏图标会闪烁以通知用户。对于受过训练的人来说,这是有道理的。但是新手用户可能不理解其中的含义——特别是因为不是每个应用都是这样运行的。在设计 Windows 8 API 环境时,在通知方面创造更强的一致性是很重要的。为此,Windows API 设计人员选择使用一组预定义的模板,而不仅仅是公开一个用户界面,让应用在其中显示它们的通知。这些模板确保所有通知都遵循标准化的呈现格式,在应用之间保持一致,防止用户体验不一致或不一致。在讨论 toast 和 tile 通知类型的过程中,以下部分将详细介绍这些模板。
Toast 通知
Toast 通知的功能与上一节中讨论的通知类似。传统的 Windows toast 通知是应用遵循的一种交互模式,而现代 Windows 8 应用所采用的 toast 通知机制则融入了操作系统和 API 表面区域。这意味着所有现代应用都可以通过一组通用的 API 来使用它们,并且无论通知者是什么,用户都可以看到一个相似的交互模式。这与 Windows 8 的总体模式是一致的:应用之间的主要区别因素是内容。当然,应用可以在设计上有所不同,但总的来说,在现代 Windows 8 应用中,内容是王道,优先于 chrome。
图 7-1 显示了当用户在 Windows 8 开始屏幕上时,toast 通知的外观。如你所见,它出现在屏幕的右上角。
图 7-1。
Example of a toast notification
无论你在哪里,在操作系统中你在做什么,一个 toast 通知总是出现在右上角,当用户没有与之交互时逐渐淡化。在接收到多个祝酒词的情况下,后续的祝酒词显示在先前呈现的祝酒词下面。图 7-2 显示了多个 toast 通知如何出现在用户的屏幕上。
图 7-2。
Multiple toast notifications on screen (unlike the example in Figure 7-1, these notifications have no icon associated with them)
生成 Toast 通知
祝酒词可以在本地发送,也可以通过推送通知发送。用户可以通过触摸或点击来选择 toast,以便启动相关联的应用。因为这些通知被设计为上下文感知的,所以可以在启动的应用中处理 toast 通知的上下文,以便它呈现通知内容的详细视图。
清单 7-1 显示了一个非常简单的本地 toast 通知的例子。用户单击一个按钮(btn_toast
),在事件处理程序中,您使用对show
方法的调用呈现一个 toast 通知。
Listing 7-1. Local Toast Notification
(function ()
{
"use strict";
WinJS.UI.Pages.define("/samples/NotificationSample/TestNotification.html", {
ready: function (element, options)
{
var count = 0;
btn_toast.onclick = function ()
{
count++;
var toast_xml = Windows.UI.Notifications.ToastNotificationManager
.getTemplateContent(Windows.UI.Notifications
.ToastTemplateType.toastText01);
var text_node = toast_xml.documentElement.getElementsByTagName("text")[0];
text_node.appendChild(toast_xml.createTextNode("A toast notification
message: " + count));
var toast = new Windows.UI.Notifications.ToastNotification(toast_xml);
var toast_notifier = Windows.UI.Notifications.ToastNotificationManager
。*本文件迟交
createToastNotifier();
toast_notifier.show(toast);
};
}
,
});
})();
这个例子相对简单。首先,建立一个变量 count,它表示点击 toast 按钮的次数。每点击一次btn_toast
,就增加 count,然后生成一个新的 toast 通知,将新的 count 合并到它的消息中。从清单中可以看出,生成 toast 非常简单,只需访问一个 XML 文档,操作该文档中的内容,然后将完成的文档作为参数传递给ToastNotificationManager
类的实例。ToastNotificationManager
然后用这个来构造合适的ToastNotifier
。
清单 7-1 中的例子就是创建图 7-2 中的 toast 通知列表的例子。要让它工作,您必须打开您的package.appmanifest
文件并为应用启用 toast 通知。可以在应用 UI 选项卡上找到完成此操作的设置。在“所有图像资产”树中,选择“徽章徽标”,您会看到“通知”部分,其中有一个标记为“Toast Capable”的下拉列表。要为您的应用启用 toast 通知,请将此下拉列表的值设置为Try
。
这个用于启动流程的 XML 文档被称为模板,通过枚举ToastTemplateType
公开。在本章的开始,你已经了解了模板。模板允许您以多种方式呈现 toast 通知。在本例中,您提供了一个纯文本通知。Windows 8 提供了八个模板:四个是纯文本的,四个同时使用图像和文本。
下一节将更详细地介绍各种 toast 通知格式。请注意,对于所有实例,溢出的文本将被修剪,无效的图像将被视为没有指定图像。
ToastText01
这是最基本的敬酒方式。该模板显示一个最多包含三行文本的字符串。清单 7-2 显示了这种模板类型的 XML。粗体的节点表示基于清单 7-1 所示模式的目标区域。
Listing 7-2. ToastText01 Template Definition
<toast>
<visual>
<binding template="ToastText01">
<text id="1">bodyText</text>
</binding>
</visual>
</toast>
ToastText02
该模板在第一行显示一个粗体文本字符串,在第二行和第三行显示一个普通文本字符串。清单 7-3 显示了这种模板类型的 XML。粗体的节点表示基于清单 7-1 所示模式的目标区域。
Listing 7-3. ToastText02 Template Definition
<toast>
<visual>
<binding template="ToastText02">
<text id="1">headlineText</text>
<text id="2">bodyText</text>
</binding>
</visual>
</toast>
ToastText03
这个模板是 ToastText02 设计的变体,在第一行和第二行用粗体文本显示一个字符串,在第三行用常规文本显示一个字符串。清单 7-4 显示了这种模板类型的 XML。注意这个和清单 7-3 的唯一区别是模板名。粗体的节点表示基于清单 7-1 所示模式的目标区域。
Listing 7-4. ToastText03 Template Definition
<toast>
<visual>
<binding template="ToastText03">
<text id="1">headlineText</text>
<text id="2">bodyText</text>
</binding>
</visual>
</toast>
ToastText04
该模板在第一行显示一个粗体文本字符串,在随后的每一行显示一个常规文本字符串(注意,没有换行;每一行都有自己唯一的字符串)。清单 7-5 显示了这种模板类型的 XML。粗体的节点表示基于清单 7-1 所示模式的目标区域。
Listing 7-5. ToastText04 Template Definition
<toast>
<visual>
<binding template="ToastText04">
<text id="1">headlineText</text>
<text id="2">bodyText1</text>
<text id="3">bodyText2</text>
</binding>
</visual>
</toast>
向祝酒词添加图像
Toast 通知还可以包括图像。图 7-3 显示了修改后的 toast 通知,现在编程为支持图像。
图 7-3。
Toast notification with an image
为此,您只需更改正在使用的模板,并将适当的内容插入到底层文档的适当节点中。清单 7-6 显示了用于生成图 7-3 中的 toast 通知的代码。粗体文本是更改的部分。
Listing 7-6. Toast Notification with an Image
(function ()
{
"use strict";
WinJS.UI.Pages.define("/samples/NotificationSample/TestNotification.html", {
ready: function (element, options)
{
var count = 0;
btn_toast.onclick = function ()
{
count++;
var toast_xml = Windows.UI.Notifications.ToastNotificationManager
.getTemplateContent(Windows.UI.Notifications.ToastTemplateType
.toastImageAndText01);
//set text
var text_node = toast_xml.documentElement.getElementsByTagName("text")[0];
text_node.appendChild(toast_xml.createTextNode
("A toast notification message: " + count));
//set image
var image_node = toast_xml.documentElement.getElementsByTagName("image")[0];
image_node.setAttribute("src", "ms-appx:///img/hiredup_150150.png");
var toast = new Windows.UI.Notifications.ToastNotification(toast_xml);
var toast_notifier = Windows.UI.Notifications.ToastNotificationManager
.createToastNotifier();
toast_notifier.show(toast);
};
}
,
});
})();
在指定要使用的图像源时,请注意协议ms-appx
的使用。您可以使用此协议从应用的部署包中检索内容。该 API 还支持http/https
检索基于网络的图像,支持ms-appdata:///local/
从本地存储中提取图像。
清单 7-7 显示了所有为带有图像的 toast 通知公开的文档模板类型。它们反映了为纯文本 toast 通知公开的相同文档(除了添加的 image 元素),因此无需进一步解释。
Listing 7-7. Image-Based Toast Notification Document Types
<toast>
<visual>
<binding template="ToastImageAndText01">
<image id="1" src="image1" alt="image1"/>
<text id="1">bodyText</text>
</binding>
</visual>
</toast>
<toast>
<visual>
<binding template="ToastImageAndText02">
<image id="1" src="image1" alt="image1"/>
<text id="1">headlineText</text>
<text id="2">bodyText</text>
</binding>
</visual>
</toast>
<toast>
<visual>
<binding template="ToastImageAndText03">
<image id="1" src="image1" alt="image1"/>
<text id="1">headlineText</text>
<text id="2">bodyText</text>
</binding>
</visual>
</toast>
<toast>
<visual>
<binding template="ToastImageAndText04">
<image id="1" src="image1" alt="image1"/>
<text id="1">headlineText</text>
<text id="2">bodyText1</text>
<text id="3">bodyText2</text>
</binding>
</visual>
</toast>
为祝酒词增添声音
Toast 通知也可能有与之相关的声音。想象一个场景,一个像 Skype 这样的 VoIP 风格的应用正在接收一个来电。仅仅弹出一个通知可能是不够的(特别是如果用户目前不在计算机上工作!).在这个领域,API 设计者再次选择了统一性而不是终极灵活性,只允许一组指定的声音与 toast 通知一起使用。Windows 8 总共提供了八种声音:四种可以循环播放(像打电话的声音),四种不能。此外,您可以指定 toast 通知为静音。清单 7-8 显示了完整的音频节点。
Listing 7-8. Audio Node
<audio src="ms-winsoundevent:Notification.Mail" loop="false" silent="false"/>
您可以通过添加audio
标签作为顶级 toast 元素的直接子元素,将 toast 通知音频添加到上述任何模板中。(请记住,到目前为止,您看到的所有其他内容都与烤面包的视觉表现相关。元素作为visual
节点的子元素没有多大意义。)在清单 7-9 中,您在现有的例子中添加了一个循环调用声音。
Listing 7-9. Toast Notification with Audio
(function ()
{
"use strict";
WinJS.UI.Pages.define("/samples/NotificationSample/TestNotification.html", {
ready: function (element, options)
{
var count = 0;
btn_toast.onclick = function ()
{
count++;
var toast_xml = Windows.UI.Notifications.ToastNotificationManager
.getTemplateContent(Windows.UI.Notifications.ToastTemplateType
.toastImageAndText01);
toast_xml.documentElement.setAttribute("duration", "long");
//set text
var text_node = toast_xml.documentElement.getElementsByTagName("text")[0];
text_node.appendChild(toast_xml.createTextNode
("A toast notification message: " + count));
//set image
var image_node = toast_xml.documentElement.getElementsByTagName("image")[0];
image_node.setAttribute("src", "ms-appx:///img/hiredup_150150.png");
//add audio to the notification
var audio_node = toast_xml.createElement("audio");
audio_node.setAttribute("src", "ms-winsoundevent:Notification.Looping.Call");
audio_node.setAttribute("loop", "true");
toast_xml.documentElement.appendChild(audio_node);
var toast = new Windows.UI.Notifications.ToastNotification(toast_xml);
var toast_notifier = Windows.UI.Notifications.ToastNotificationManager
.createToastNotifier();
toast_notifier.show(toast);
};
}
,
});
})();
如您所见,要为 toast 通知添加声音,您需要创建 audio 节点,填充适当的属性,并将其添加到模板表示的 XML 文档中,这样就完成了。但是,请注意,示例中的其他内容发生了变化。您的顶层文档现在有了一个duration
属性,它被设置为long
。这是循环通知特别需要的,但可以在所有 toast 通知中使用,以创建持久的 toast。持续的祝酒持续的时间更长。
表 7-1。
Looping and Non-Looping Sounds that Can Be Attached to Toast Notifications
| 非循环 | 环 | | --- | --- | | `Notification.Default` | `Notification.Looping.Alarm` | | `Notification.IM` | `Notification.Looping.Alarm2` | | `Notification.Mail` | `Notification.Looping.Call` | | `Notification.Reminder` | `Notification.Looping.Call2` | | `Notification.SMS` | |Note
当您在audio
标签的src
属性中使用表 7-1 中的值时,一定要用ms-winsoundevent
名称空间来限定它们。于是Notification.Looping.Alarm2
变成了ms-winsoundevent:Notification.Looping.Alarm2
。尽管忽略这一点不会导致错误,但音频通知不会起作用。
安排祝酒
在某些情况下,将 toast 安排在未来的某个时间点发生(或者以重复的方式发生)可能比在命令执行后立即发出通知更合适。在这种情况下,您可以使用一个名为ScheduledToastNotification
的专门类。ScheduledToastNotification
提供了一个构造函数,它不仅接受通知的 XML 模板,还接受应该显示通知的日期、通知的休眠时间(在通知在一段时间内重复的情况下),以及一个指示通知在最终终止前休眠的最大次数的数字。清单 7-10 显示了这种 toast 通知的使用。当用户单击 toast 按钮时,您将通知安排在 2012 年 12 月 20 日,根据玛雅人的说法,这是世界末日的前一天!
Listing 7-10. Toasting the End of the World
(function ()
{
// "use strict";
WinJS.UI.Pages.define("/samples/NotificationSample/TestNotification.html", {
ready: function (element, options)
{
var count = 0;
btn_toast.onclick = function ()
{
count++;
var toast_xml = Windows.UI.Notifications.ToastNotificationManager
.getTemplateContent(Windows.UI.Notifications.ToastTemplateType
.toastImageAndText01);
toast_xml.documentElement.setAttribute("duration", "long");
//set text
var text_node = toast_xml.documentElement.getElementsByTagName("text")[0];
text_node.appendChild(toast_xml.createTextNode
("A toast notification message: " + count));
//set image
var image_node = toast_xml.documentElement.getElementsByTagName("image")[0];
image_node.setAttribute("src", "ms-appx:///img/hiredup_150150.png");
//add audio to the notification
var audio_node = toast_xml.createElement("audio");
audio_node.setAttribute("src", "ms-winsoundevent:Notification.Looping.Call");
audio_node.setAttribute("loop", "true");
toast_xml.documentElement.appendChild(audio_node);
var stoast = new Windows.UI.Notifications.ScheduledToastNotification
(toast_xml, new Date("12/20/2012"),(60 * 1000) * 60,5);
stoast.id = "the_end";
var toast_notifier = Windows.UI.Notifications.ToastNotificationManager
.createToastNotifier();
toast_notifier.addToSchedule(stoast);
};
}
,
});
})();
注意使用了addToSchedule
而不是show
。顾名思义,addToSchedule 将查询 toast 通知请求,并在到达指定日期时显示 toast。
响应 Toast 通知事件
前面,你已经了解到 toast 通知的一个很酷的地方——它与本章中讨论的其他类型的通知不同——是它们是上下文感知的。这意味着您的应用可以以独特的和特定于上下文的方式对用户激活或取消的 toast 通知做出反应。WinJS 在ToastNotification
类上公开事件来处理这种情况。参见清单 7-11。
Listing 7-11. Using the Events of the ToastNotification
Class
(function ()
{
// "use strict";
WinJS.UI.Pages.define("/samples/NotificationSample/TestNotification.html", {
ready: function (element, options)
{
var count = 0;
btn_toast.onclick = function ()
{
count++;
var toast_xml = Windows.UI.Notifications.ToastNotificationManager
.getTemplateContent(Windows.UI.Notifications.ToastTemplateType
.toastImageAndText01
);
toast_xml.documentElement.setAttribute("duration", "long");
//set text
var text_node = toast_xml.documentElement.getElementsByTagName("text")[0];
text_node.appendChild(toast_xml.createTextNode
("A toast notification message: " + count));
//set image
var image_node = toast_xml.documentElement.getElementsByTagName("image")[0];
image_node.setAttribute("src", "ms-appx:///img/hiredup_150150.png");
//add audio to the notification
var audio_node = toast_xml.createElement("audio");
audio_node.setAttribute("src", "ms-winsoundevent:Notification.Looping.Call");
audio_node.setAttribute("loop", "true");
audio_node.setAttribute("silent", "false");
toast_xml.documentElement.appendChild(audio_node);
var toast = new Windows.UI.Notifications.ToastNotification(toast_xml);
//lift the value of count so that it is always the value at the
//time when the button is clicked that is used when the toast
//is activated or deactivated
var indicator = count;
toast.onactivated = function ()
{
txt_display.innerText = "you activated toast for: " + indicator;
};
toast.ondismissed = function ()
{
txt_display.innerText = "you dismissed toast for: " + indicator;
};
var toast_notifier = Windows.UI.Notifications.ToastNotificationManager
.createToastNotifier();
toast_notifier.show(toast);
};
}
,
});
})();
在本例中,您添加了一个新的用户界面元素txt_display
,用于记录用户对 toast 通知的处理。如果用户通过点击或轻敲激活了一个 toast,您将其记录为激活;如果用户取消它,你记录为解雇。(因为这些是长祝酒词通知,所以它们出现的时间更长。)您还可以通过显示用于生成 toast 的值count
来指示哪个 toast 被取消或取消。
图 7-4 显示了实际应用。请注意,该图包含代码示例的用户界面 HTML 中没有的附加按钮。
Note
在清单 7-11 中,您在onclick
事件处理程序中将count
设置为一个局部变量。这样做可以确保调用任何 toast 事件处理程序时使用的count
的值与最初单击按钮时使用的值相同。indicator
的每个唯一值被称为在一个唯一的全局状态中被提升,因此当调用适当版本的onactivated
/ ondismissed
时,每个值都可以被使用。
图 7-4。
Toast notification handling events
磁贴通知
还记得第一章里关于 Windows 8 的概述讨论吗?它谈到了开始屏幕上的矩形形状,将快速启动区域的功能与应用的启动快捷方式结合起来,并在任务栏上显示应用。正如在第一章中提到的,这些矩形形状被称为活瓷砖。关于它们的“活性”的伟大之处在于,它将这些磁贴变成了应用可以向用户呈现通知的另一个表面。磁贴通知在许多方面不同于 toast 通知。
首先,它们实际上是一种应用行为模式,位于动态磁贴的概念之上。你可能会说它们根本不是通知。许多应用,如 Travel,在不通知用户任何事情的情况下使用了实时磁贴功能(Travel 显示世界上不同地方的图片,这与 Mail 应用对磁贴的使用明显不同)。第二,因为这种通知方式使用给定应用的图块,所以可以通过多种方式对其进行限制。首先,如果磁贴不在 Windows 开始屏幕上,就不会有通知(也不会向应用指示通知没有发生)。另一个问题是这种类型的通知局限于 Windows 开始屏幕;根据磁贴在用户开始屏幕上的位置,它可能不可见。最后也是最重要的,应用的动态磁贴主要是为非侵入式浏览信息而设计的。它不应该是一个对话框或消息框;这意味着向用户提供快速的信息,可能会诱使他们点击磁贴并获得更多信息。
在您学习这一部分的过程中,让我们正确地看待这一功能。因为这里使用的术语通知有些误导(特别是与本节中的对应术语并列使用时),所以您可能会尝试将 live tiles 的使用限制为向用户显示消息。这样做会严重低估这项技术的力量。图 7-5 显示了许多流行的 Windows 8 应用的实时磁贴。
图 7-5。
Live tiles for some Modern Windows 8 apps
图块通知可以是纯文本、带图像的文本、所有图像或图像的组合,或者它们可以包含另一类称为徽章通知的通知。与 toast 通知不同,如前所述,toast 通知主要用于提醒用户他们需要相对快速地采取行动或信息,tile 通知侧重于呈现更持久、侵入性更低的通知内容。区分 toast 通知和磁贴通知(就它们应该呈现的内容而言)的一般方法是,实时磁贴呈现用户可能感兴趣的内容和事件,而 toast 通知呈现用户应该感兴趣的内容和事件。
暂且不论应用的实时磁贴是一种多么强大的通知机制,使用实时磁贴基础设施向用户呈现通知还是有好处的。首先,因为动态磁贴是为了吸引用户,所以它可以被用作提醒或更新——抓住用户的注意力。您可以在微软 MSDN 网站 http://msdn.microsoft.com/en-us/library/windows/apps/hh761491.aspx
上找到所有磁贴模板的完整列表,以及最终磁贴布局的图像。这可以作为一个参考,以了解您可以做什么和不可以做什么。
注意 Windows 8.1 增加了两个新的磁贴大小,使用户可以配置应用开始屏幕磁贴的总共 4 种可能的大小。这些图块大小为 50x50 正方形、150x150 像素正方形、310x150 矩形和巨型 310x310 正方形。当微软第一次在 Windows 8 中构思磁贴的概念时,他们很自然地认为前面提到的“小”和“宽”磁贴大小对于用户来说已经足够了,因此用于识别模板的磁贴名称具有“tileSquare”和“tileWide”的通用形式。幸运的是,从 Windows 8.1 开始,微软已经对这些模板进行了重命名,以允许更多的磁贴尺寸。现在,除了通用名称之外,模板还包含瓷砖的实际尺寸。因此,模板名称的“tileSquare”部分现在变成了“tileSquare150x150”,允许方形图块格式有两种变体(另一种是 tileSquare310x310)。以下部分使用 Windows 8 惯例,但请记住,一旦用户升级到 Windows 8.1,如果您的应用没有使用更新的新模板名称重新编译和发布,您的磁贴通知将停止工作。
生成磁贴通知
像 toast 通知一样,磁贴通知可以在本地发送,也可以通过推送通知发送(在下一章讨论)。与 toasts 不同,针对应用磁贴的通知没有单独的交互模式。它们只是简单地呈现在磁贴上,由用户决定是否启动应用。
清单 7-12 显示了一个非常简单的本地磁贴通知的例子。它用一个新按钮btn_tile
扩展了上一节中的通知示例,用户可以单击该按钮来生成基本的磁贴通知。
Listing 7-12. Simple Tile Notification
btn_tile.onclick = function ()
{
count++;
var tile_xml = Windows.UI.Notifications.TileUpdateManager
.getTemplateContent(Windows.UI.Notifications.TileTemplateType
.tileWideText01
);
var text_nodes = tile_xml.documentElement.getElementsByTagName("text");
text_nodes[0].appendChild(tile_xml.createTextNode("Message : " + count));
var tile = new Windows.UI.Notifications.TileNotification(tile_xml);
var tile_updater = Windows.UI.Notifications.TileUpdateManager
.createTileUpdaterForApplication();
tile_updater.update(tile);
};
如果您阅读了整个前一节,那么您应该会注意到这里的一些东西。创建 tile 通知的开发模式类似于创建 toast 通知的方法。首先选择一个合适的模板(它映射到一个 XML 文档)。一旦检索到模板,就可以找到需要放置内容的文档部分。然后将内容放入这些元素/属性中,使用创建的文档生成适当的 tile-notification 对象,并将该对象传递给TileUpdateManager
。更新实际的磁贴是TileUpdateManager
的工作。由此你可以推测,微软又一次选择了统一性和模板驱动的方法来提供一个界面,开发人员可以在这个界面上一次性展示特定于应用的通知。当点击btn_tile
十次时(只要已经为应用指定了一个宽的徽标图像),清单 7-12 中的例子产生如图 7-6 所示的结果。
图 7-6。
Updated live tile
因为平铺显示比您在处理 toast 通知时看到的简单示例要复杂得多,所以平铺显示的模板也是不同的。总共有 45 个模板可供选择!它们从简单的文本,如清单 7-12 所示,到没有文本的图像数组。这些模板还包含现代 Windows 8 应用可用的两种可能的磁贴大小:方形磁贴和宽(矩形)磁贴。你在图 7-5 和 7-6 中看到了矩形瓷砖。方形磁贴的功能类似,但在 Windows 8 开始屏幕上占用的空间更少。
Note
方形图块不能用来呈现现代 Windows 8 应用所能提供的所有模板格式。总之,他们只能使用十个模板:五个纯文本的,一个纯图像的,四个带有图片和文本的。扫视图像块是配置有图像的块,这些图像从块的底部出现,然后向下滑动,就像从块显示区域下面的某个地方窥视一样。另请注意,当您没有在应用清单中包括宽磁贴徽标时,应用只负责它所呈现的磁贴类型(实际上强制您的应用只存在于开始屏幕上的方形磁贴中)。当应用磁贴处于方形磁贴状态时,通知仅限于那些仅针对方形磁贴的通知。如果您包含宽磁贴徽标,您需要同时对正方形和宽磁贴执行磁贴更新,因为您不知道开始屏幕上当前显示的是哪个版本的磁贴。
通常,图块模板采用六种形式:仅文本、仅图像、图像和文本、查看图像和文本、查看图像和查看图像集合。您可能已经在 windows 的“开始”屏幕上看到了“人物”应用的偷窥现象。图块更新从图块的底部开始显示,就像在偷看一样,然后向下滑动。几秒钟内,它会向上滑动并覆盖先前的磁贴内容。
本讨论专门关注宽瓷砖模板。两者的开发和交互模式是相同的,因此将下面几节中概述的相同概念应用到 square 应用块应该没有问题。此外,本章没有讨论所有广泛的模板类型,而是关注一些有趣的模板类型,您可能会发现它们对向用户呈现内容很有帮助。
用标题呈现文本内容
TileWideText09
是向用户呈现简单文本内容的主体。它使用第一个文本元素作为标题,第二个元素作为文本内容换行到其余四行。清单 7-13 显示了这种模板类型的模板定义。
Listing 7-13. TileWideText09
Template Definition
<tile>
<visual>
<binding template="TileWideText09">
<text id="1">Text Header Field 1</text>
<text id="2">Text Field 2</text>
</binding>
</visual>
</tile>
这是一个很好的呈现文本的模板,因为它允许您将内容区域换行到多行。几乎所有其他模板类型都要求您分别在每一行中输入文本,这迫使您监控通知字符串的长度。如果您的内容区域不需要换行符,那么推荐使用这种方法。清单 7-14 修改了清单 7-12 的例子,用TileWideText09
代替TileWideText01
。图 7-7 显示了点击一次btn_tile
按钮时磁贴呈现的效果。
Listing 7-14. Using TileWideText09
to Display Text Content
btn_tile.onclick = function ()
{
count++;
var tile_xml = Windows.UI.Notifications.TileUpdateManager
.getTemplateContent(Windows.UI.Notifications.TileTemplateType
.tileWideText09
);
var text_nodes = tile_xml.documentElement.getElementsByTagName("text");
text_nodes[0].appendChild(tile_xml.createTextNode("Message : " + count));
text_nodes[1].appendChild(tile_xml.createTextNode
(count + " quick brown fox jumped over a lazy dog!"));
var tile = new Windows.UI.Notifications.TileNotification(tile_xml);
var tile_updater = Windows.UI.Notifications.TileUpdateManager
.createTileUpdaterForApplication();
tile_updater.update(tile);
};
图 7-7。
What the tile renders when the btn_tile
button is clicked once
纯文本内容
在需要展示没有标题的纯文本内容的情况下(例如,新闻类应用的标题),TileWideText04
是一个很好的模板。它删除了标题,并为应用提供了一行文本,该文本覆盖了图块的所有五行。清单 7-15 显示了这个模板的模板定义。
Listing 7-15. Template Definition for TileWideText04
<tile>
<visual>
<binding template="TileWideText04">
<text id="1">Text Field 1</text>
</binding>
</visual>
</tile>
汇总模板
最后,如果应用需要以纯文本的列表格式显示摘要信息,TileWideText02
和TileWideText01
是很好的模板。清单 7-12 使用TileWideText01
在屏幕上显示一些简单的文本。它单独列出文本行,上面有一个标题,如图 7-6 所示。TileWideText02
遵循相同的格式,有一个关键区别:文本显示在两列中,每列包含四行文本。清单 7-16 显示了TileWideText02
的模板定义。
Listing 7-16. Template Definition for TileWideText02
<tile>
<visual>
<binding template="TileWideText02">
<text id="1">Text Header Field 1</text>
<text id="2">Text Field 2</text>
<text id="3">Text Field 3</text>
<text id="4">Text Field 4</text>
<text id="5">Text Field 5</text>
<text id="6">Text Field 6</text>
<text id="7">Text Field 7</text>
<text id="8">Text Field 8</text>
<text id="9">Text Field 9</text>
</binding>
</visual>
</tile>
清单 7-17 修改了 tile-notification 的例子,使用了TileWideText02
。
Listing 7-17. Using TileWideText02
to Update an Application Tile
btn_tile.onclick = function ()
{
count++;
var tile_xml = Windows.UI.Notifications.TileUpdateManager
.getTemplateContent(
Windows.UI.Notifications.TileTemplateType.tileWideText02
);
var text_nodes = tile_xml.documentElement.getElementsByTagName("text");
text_nodes[0].appendChild(tile_xml.createTextNode("Message : " + count));
text_nodes[1].appendChild(tile_xml.createTextNode(count.toString()));
text_nodes[3].appendChild(tile_xml.createTextNode((count + 1).toString()));
text_nodes[5].appendChild(tile_xml.createTextNode((count + 2).toString()));
text_nodes[7].appendChild(tile_xml.createTextNode((count + 3).toString()));
text_nodes[2].appendChild(tile_xml.createTextNode("Dogs"));
text_nodes[4].appendChild(tile_xml.createTextNode("Cats"));
text_nodes[6].appendChild(tile_xml.createTextNode("Bunnies"));
text_nodes[8].appendChild(tile_xml.createTextNode("Fish"));
var tile = new Windows.UI.Notifications.TileNotification(tile_xml);
var tile_updater = Windows.UI.Notifications.TileUpdateManager
.createTileUpdaterForApplication();
tile_updater.update(tile);
};
当这个应用运行并且用户点击btn_tile
时,磁贴看起来如图 7-8 所示。
图 7-8。
Using TileWideText02
创建更干净的模板
在我看来,工作做得很好,但是审美脱节。如果通知的外观非常重要,那么你可以使用一个更干净的模板,比如TileWideBlockAndText01
。该模板在左侧呈现为四个展开的文本字符串,在右侧呈现为一个短的粗体文本字符串上的一大块文本。您丢失了列,但是可以使用左边的自由文本来显示数字和文本,如图 7-9 所示。
图 7-9。
Using TileWideBlockAndText01
这个通知是通过应用清单 7-18 中概述的模板定义实现的。
Listing 7-18. TileWideBlockAndText01
Template Definition
<tile>
<visual>
<binding template="TileWideBlockAndText01">
<text id="1">1 dogs</text>
<text id="2">2 cats</text>
<text id="3">3 bunnies</text>
<text id="4">4 fish</text>
<text id="5">10</text>
<text id="6">House Pets</text>
</binding>
</visual>
</tile>
将图像添加到图块通知
与 toast 通知一样,可以将图像应用于磁贴通知,以提高磁贴的整体美观性。在极端情况下,如果您对 Microsoft 提供的 45 种模板类型中的任何一种都不满意,您可以使用其中一种仅包含图像的通知模板,并通过动态图像生成来生成您自己的通知布局。目前,WinJS 没有提供从用户界面元素创建图像的方法,但在未来的版本中,这可能成为可能。此外,没有什么可以阻止您通过 web 服务调用来实现这一点。与图像相关的各种形式的图块通知太多了,在使用上也太相似了,所以在本书中不再深入讨论。
计划磁贴通知
像 toast 通知一样,磁贴通知可以安排在预先确定的时间运行。如前所述,在某些情况下,将图块更新安排在未来的某个时间进行更合适,而不是在命令执行后立即发出通知。在这样的场景中,可以使用ScheduledTileNotification
。它的构造函数不仅接受通知的 XML 模板,还接受应该显示通知的日期。
清单 7-19 显示了安排图块通知是多么容易。当用户点击磁贴通知按钮时,您将通知安排在 2012 年 12 月 20 日,也就是玛雅人所说的世界末日的前一天。
Listing 7-19. Summary of Equipment for the End of the World
btn_tile.onclick = function ()
{
count++;
var tile_xml = Windows.UI.Notifications.TileUpdateManager
.getTemplateContent(Windows.UI.Notifications.TileTemplateType
.tileWideBlockAndText01
);
var text_nodes = tile_xml.documentElement.getElementsByTagName("text");
text_nodes[0].appendChild(tile_xml.createTextNode(count + " food"));
text_nodes[1].appendChild(tile_xml.createTextNode
((count + 1).toString() + " drink"));
text_nodes[2].appendChild(tile_xml.createTextNode
((count + 2).toString() + " maps"));
text_nodes[3].appendChild(tile_xml.createTextNode
((count + 3).toString() + " tools"));
text_nodes[4].appendChild(tile_xml.createTextNode
(count + (count + 1) + (count + 2) + (count + 3)));
text_nodes[5].appendChild(tile_xml.createTextNode("World End List"));
//var tile = new Windows.UI.Notifications.TileNotification(tile_xml);
var stile = new Windows.UI.Notifications.ScheduledTileNotification
(tile_xml, new Date("12/20/2012"));
var tile_updater = Windows.UI.Notifications.TileUpdateManager
.createTileUpdaterForApplication();
tile_updater.addToSchedule(stile);
};
摘要
我希望你已经发现了这个关于 toast 和 tile 通知的详尽解释,并且对你未来的应用开发有所帮助。回顾一下,讨论内容包括生成 toast 和 tile 通知、添加图像和声音、计划和响应通知。通过本章介绍的规则和表单,Windows 8 再次为用户创造了统一性——在这种情况下,是通过通知。作为一名 Windows 8 开发人员,您现在有工具和真实世界的例子来使用这些通知进行磁贴设计和锁屏交互。以下是本章中需要记住的一些要点:
- Toast 和 tile 通知可以在本地发送。
- Toast 通知是上下文感知的。应用可以以特定于上下文的方式对用户激活或取消的 toast 通知做出独特的反应。
- 图块通知本质上是一种高于动态图块概念的模式。如果互动程序不在 Windows 开始屏幕上,则不会有通知。
- 图块通知可以是纯文本、带图像的文本、所有图像或图像的组合。Toast 通知可以是纯文本的,也可以是带图像的文本。
- Toast 和 tile 通知可以作为以各种方式吸引用户注意力的动态工具。
八、保持应用在后台运行
Abstract
微软定义、创造和注册的术语 Windows 与计算功能有关,应用的功能区域可以被划分到屏幕的一个角落,四处移动,并最大化以占据整个屏幕表面区域,对此任何人都不会感到惊讶。简而言之,Windows 允许你进入一个正在运行的应用。随着时间的推移,显示应用的能力演变为能够运行和使用多个应用,包括窗口重叠、快速任务切换和许多我们都知道、喜欢并期望从 Windows、Mac OS 甚至 Linux 和 Unix 产品树等自尊操作环境中获得的更多令人愉快的功能。
微软定义、创造和注册的术语 Windows 与计算功能有关,应用的功能区域可以被划分到屏幕的一个角落,四处移动,并最大化以占据整个屏幕表面区域,对此任何人都不会感到惊讶。简而言之,Windows 允许你进入一个正在运行的应用。随着时间的推移,显示应用的能力演变为能够运行和使用多个应用,包括窗口重叠、快速任务切换和许多我们都知道、喜欢并期望从 Windows、Mac OS 甚至 Linux 和 Unix 产品树等自尊操作环境中获得的更多令人愉快的功能。
因此,对于每个开始使用 Windows 8 的人来说,所有你喜欢的漂亮创新,以及多年来用户反馈、试错和功能调整的自然演变,都在这次 Windows 的重新发明中消失了,这应该是一个完全的惊喜。您如何提供相同的体验—应用切换、后台处理、窗口等等—同时保持新的范式,允许 Windows 在任何设备上运行,从基于 ARM 的设备(您可能在 iPhone 上找到的类型)到成熟的 Windows 体验(例如 Core i7 über 设备)?
这一章着重于 Windows 的一个原则,这个原则在我称之为“大清洗”中幸存了下来:多种形式的后台处理。在以前的 Windows 版本中,应用通过各种机制在后台运行。简单地从一个应用切换到另一个应用实际上是把第一个应用放到了后台(尽管在以前的 Windows 版本中,它继续像在前台一样运行)。这为用户和开发人员提供了极大的灵活性,但是不利的是,给他们的工作流带来了难以置信的复杂性。作为一个用户,如果不打开所有的超级用户工具,很难确切地知道在任何给定的时间你的机器上正在运行什么。作为一名开发人员,几乎不可能预测其他应用会如何反应和对待您的应用,而且您的应用与另一个应用之间的兼容性问题总是作为 bug 与您的应用联系在一起。我记得曾经写过一个小小的不可见的控制台应用,它定期监视某个文字处理软件的可执行文件,并用一个process –kill
命令关闭它。(在你打电话给 FBI 之前,请明白这是 CS 室友之间的玩笑。)
因为 Windows 8 引入了一种新的应用行为方法,在这种方法中,应用用户界面总是占据整个屏幕表面区域,这意味着用户应该与一个应用(或者两个,如果用户已经捕捉到一个辅助应用)进行交互,所以前台应用被认为对用户来说是最重要的。
Note
捕捉功能仅在有足够水平空间支持的屏幕上可用。在 Windows 8 的当前版本中,这样的屏幕必须支持 1366 x 768 分辨率。在 Windows 8 的未来版本中,有传言称将允许更多的快照宽度。
第一章在某种程度上谈到了这一点。因为前台应用接收所有系统资源,所以用户看不到的应用会进入暂停状态,不再执行。以这种方式暂停的应用会一直保持这种状态,直到用户通过应用切换、重新启动或通过契约机制初始化来恢复它。这种方法的好处之一是,系统不会受到在用户看不到的后台运行的应用的负面影响。
Note
在 Windows 8 的当前实现中,这一开创性操作系统的愿景尚未完全实现。在 WinRT 版本的操作系统上,运行在基于 ARM 的处理器上,如 NVIDIA Tegra 3,你不能安装传统的应用,如 Skype for desktop(一直在后台运行)。但是,在 Windows 8 的完整版本中,用户可以在桌面模式下安装应用,这些应用会对系统产生持续的性能影响。因此,当批处理作业以桌面模式在 SQL Server 实例上运行时,“开始”菜单可能会出现滞后和延迟。
在后台运行
Windows 8 应用使用后台任务来提供运行的功能,而不管底层应用当前是否正在运行。注册后,后台任务由称为触发器的外部事件启动,并允许根据称为条件的大量标准启动,所有这些条件都必须为真,后台任务才能运行。即使触发了触发事件也是如此。后台任务的触发事件可能是时间,或者一些系统事件,如软件安装或系统更新的完成。
由于对速度和流畅性的要求,重要的是后台任务不能无节制地运行,在用户不知情的情况下降低系统速度。因此,这些任务是通过资源管理环境执行的,该环境只为后台任务提供有限的时间来运行其任意代码。
鉴于现代 Windows 8 应用以这种方式运行,问题是一个应用不在前台时可以做什么。Windows 8 提供了许多功能,让应用有机会从非运行状态执行、从后台执行或从前台切换时继续执行。尽管执行机制不在前台,但它们针对系统性能和更长的电池寿命进行了优化(这是不允许多个应用在后台运行的另一个原因)。Windows 8 允许这样做的一些情况如下:
- 按定时间隔执行任务
- 若要在应用关闭后继续后台传输数据
- 若要在应用关闭后继续播放音频
- 发生系统事件时执行任务
后台任务
本节的介绍说,后台任务使用一个称为触发器的外部事件。触发器指示任务应该何时运行,并提供一组条件,这些条件必须为真,任务才能运行。当触发触发器时,底层任务基础结构启动该类或调用与该触发器关联的方法。无论应用当前是正在运行、暂停还是完全从内存中删除,都会执行此操作。请注意,后台任务永远不会转移到前台,也不会使用任何与前台相关的功能,例如呈现用户界面。(不过,他们可以启动磁贴和吐司通知。)
清单 8-1 中的例子创建了一个基于时间的后台任务,每 15 分钟唤醒并执行一次。您可能想将此间隔缩短为一分钟,这样您就可以看到工作中的后台任务;但是要注意,这种类型的后台任务不能以少于 15 分钟的增量运行(下一节将进一步讨论这一点)。这应该强调了微软对性能的重视。更好或更坏的是,这个想法是为用户提供一致的体验,不管他们是在低功率的机器上还是在高功率的庞然大物上。不幸的是,这通常意味着将最小公分母暴露给 API 环境。
Listing 8-1. Timer-Triggered Background Task
function GetTask(task_name) {
var iter = Windows.ApplicationModel.Background.BackgroundTaskRegistration.allTasks.first();
var hascur = iter.hasCurrent;
while (hascur) {
var cur = iter.current.value;
if (cur.name === task_name) {
return cur;
}
hascur = iter.moveNext();
}
return null;
}
(function () {
"use strict";
var Background = Windows.ApplicationModel.Background;
WinJS.UI.Pages.define("/samples/BGTaskSample/TestBGTasks.html", {
ready: function (element, options) {
var timer_id = -1;
var task_name = "timer task";
var can_run = false;
var background_task = null;
//request the background task
Background.BackgroundExecutionManager.requestAccessAsync().then(
function (access_status) {
if (access_status != Background.BackgroundAccessStatus.Denied
&& access_status != Background .BackgroundAccessStatus.Unspecified) {
can_run = true;
background_task = GetTask(task_name);
if (background_task != null) {
btn_timer.innerText = "Unregister Background Task";
} else {
btn_timer.innerText = "Register Background Task";
}
}
});
btn_timer.onclick = function () {
if (!can_run) {
var v = new Windows.UI.Popups.MessageDialog("Application
cannot run in the background/");
v.showAsync();
return;
}
//if this has not been created, do so
if (background_task == null) {
btn_timer.innerText = "Unregister Background Task";
var task = new Background.BackgroundTaskBuilder();
var timer = new Background.TimeTrigger(15, false);
task.setTrigger(timer);
task.taskEntryPoint = "BackgroundTaskHost.TestTimerTask";
task.name = task_name;
background_task = task.register();
if (background_task != null) {
background_task.oncompleted = function (evt) {
try {
var complete_date = new Date();
txt_display.textContent = txt_display.textContent +
"_done at " + complete_date.getHours() + " : " +
complete_date.getMinutes();
} catch (e) {
}
};
background_task.onprogress = function (evt) {
try {
txt_display.Text = txt_display.Text + "_"
+ evt.progress.toString();
} catch (e) {
}
};
//countdown to the trigger being fired
var minutes_count = 0;
var seconds_count = 0;
timer_id = setInterval(function () {
try {
seconds_count++;
if (seconds_count == 60) {
seconds_count = 0;
minutes_count++;
}
txt_display.value = minutes_count + " mins, "
+ seconds_count + " secs";
} catch (e) {
}
}, 1000);
}
} else {
//unregister the created task
background_task.unregister(true);
if (background_task != null) {
}
btn_timer.innerText = "Register Background Task";
clearInterval(timer_id);
}
};
}
,
});
})();
清单 8-1 首先通过requestAccessAsync
方法请求访问后台执行引擎。与 Windows 8 的所有功能一样,该操作会提示用户授予应用作为后台任务运行的权限。图 8-1 显示了你刚刚创建的应用的权限对话框。当你在电脑上运行应用时,你应该会看到这个。
图 8-1。
Permissions dialog box for background tasks
如果用户允许访问,您将答案记录为can_run
中的true
值。注意全局方法getTask
,它返回一个给定名称的先前调度的任务。计划任务存储在应用外部的一个结构中,该结构以任务名称为关键字。这允许您引用以前创建的任务,以便取消注册它们。在清单 8-1 中,您首先使用这个功能来搜索任务名称:如果它不存在,您创建它;如果它确实存在,你注销它。
要向后台任务基础设施注册一个定时任务,可以使用BackgroundTaskBuilder
。在这个例子中,您正在注册一个定义在类型BackgroundTaskHost.TestTimerTask
中的任务。在这种情况下,这是使用另一种 Windows 8 编程语言(C#)创建的类型。因此,在注册希望作为后台任务执行的代码时,必须应用一些额外的规则。在 JavaScript 中,您只需创建一个简单的 JavaScript 文件,称为专用工作器,而在 C#中,您必须使用强类型系统将一段代码标识为后台任务。首先创建一个新类,它的类型必须实现IBackgroundTask
,并作为字符串传递给BackgroundTaskBuilder
的TaskEntryPoint
属性。
请注意,您不必为了调试后台任务代码而等待触发条件得到满足。Visual Studio 2012 提供了一个漂亮的调试工具,让您可以在实际的后台任务类中运行代码。
回到 JavaScript 世界,您现在可以创建一个时间触发器,并将其作为参数传递给BackgroundTaskBuilder
的setTrigger
方法。时间触发器是一种使后台任务运行的触发机制。与传统 Win32 应用的方法不同,后台任务不能由它们的主机应用启动;相反,必须满足一组特定的条件,然后触发后台任务的启动。下一节更多地谈到这一点;现在,让我们看一下在满足本例的触发条件时运行的任务。清单 8-2 中显示了后台任务的摘录(用 C#)。
Listing 8-2. Run Example for the Background Task
async public void Run(IBackgroundTaskInstance taskInstance)
{
var def = taskInstance.GetDeferral();
try
{
//create the state for the application
var has_count = await Windows.Storage.ApplicationData.Current.LocalFolder
.FileExistsAsync("count.txt");
if (!has_count)
{
await Windows.Storage.ApplicationData.Current.LocalFolder
.CreateFileAsync("count.txt");
}
//read the count from the local state
var text = await Windows.Storage.ApplicationData.Current.LocalFolder
.ReadTextAsync("count.txt");
int count;
if (!int.TryParse(text, out count))
count = 0;
//display the toast notification
Toast("toast " + count);
//display the badge
var badge_type = count % 6;
Badge((BadgeType)badge_type);
//display the tile notification
Tile("Tile notification " + count);
count++;
await Windows.Storage.ApplicationData.Current
.LocalFolder.WriteAllTextAsync("count.txt",count.ToString());
}
catch (Exception ex)
{
}
def.Complete();
}
我将首先说明,您不需要理解 C#来构建后台任务。这个例子使用一个 C#后台任务来说明 WinRT 提供的无缝语言集成。如果你不知道它是做什么的,或者它看起来过于复杂,不要惊慌。JavaScript 的部分魅力和强大之处在于它可以对您隐藏许多这些复杂性(或者至少通过一个不那么神秘的 API 来提供它们)。这个后台任务实现从一个文本文件中读取一个计数,并触发 toast、tile 和 badge 通知,这些通知显示计数、递增计数并将值保存回文本文件,以便下次触发该任务时,它会显示一个更大的数字。
如前所述,WinRT 的伟大之处在于它均匀地扩展到了所有语言(在大多数情况下),这意味着没有一种语言被视为需要近亲才能进入的第二代表亲。这是一种迂回的说法,即您也可以使用 JavaScript 来构建后台任务,使用称为 workers 的专用 JavaScript 文件。(以下部分将更详细地介绍工作人员如何操作,并可以连接到后台任务引擎。)要使用 JavaScript worker 实现后台任务,您需要向项目中添加一个专门的 worker,并在BackgroundTaskBuilder
的TaskEntryPoint
属性中引用 worker 的文件名(而不是 C#类的完全限定类型名)。所以在清单 8-1 中,你使用了调用task.taskEntryPoint = "/samples/bgtasksample/timerworker.js"
,而不是调用task.taskEntryPoint = "BackgroundTaskHost.TestTimerTask";
,当然假设你已经将timerworker.js
专用工人文件添加到你的项目中。同样,下面几节将进一步讨论这一点。图 8-2 显示了选择了专用工人模板的项目项目选择器。
图 8-2。
Selecting the background worker project item type
情况
后台任务也可以有与之相关联的条件。您可以使用BackgroundTaskBuilder
的addCondition
方法添加这些。应用于后台任务的条件必须全部评估为true
,任务才能执行。例如,如果您希望您在清单 8-1 中创建的后台任务只在互联网连接可用时执行,那么在调用setTrigger
之后添加清单 8-3 中的代码片段就可以激活该条件。
Listing 8-3. Adding Conditions to a Background Task
..
。
task.setTrigger(timer);
task.addCondition(new Background.SystemCondition
(Background.SystemConditionType.internetAvailable));
..
。
表 8-1 列出了可能的条件类型。
表 8-1。
Conditions Types
| 成员 | 描述 | | --- | --- | | `userPresent` | 指定后台任务只能在用户在场时运行。如果触发了具有`userPresent`条件的后台任务,并且用户不在,则该任务不会运行,直到用户出现。 | | `userNotPresent` | 指定后台任务只能在用户不在时运行。如果触发了具有`UserNotPresent`条件的后台任务,并且用户在场,则该任务不会运行,直到用户变为非活动状态。 | | `internetAvailable` | 指定后台任务只能在 Internet 可用时运行。如果触发了具有`internetAvailable`条件的后台任务,并且互联网不可用,则该任务将不会运行,直到互联网再次可用。 | | `internetNotAvailable` | 指定后台任务只能在互联网不可用时运行。如果触发了具有`internetNotAvailable`条件的后台任务,并且互联网可用,则该任务将不会运行,直到互联网不可用。 | | `sessionConnected` | 指定后台任务只能在用户会话连接时运行。如果触发了具有`sessionConnected`条件的后台任务,并且用户会话未登录,则该任务将在用户登录时运行。 | | `sessionDisconnected` | 指定后台任务只能在用户会话断开时运行。如果触发了具有`sessionDisconnected`条件的后台任务,并且用户已登录,则该任务将在用户注销时运行。 |扳机
可以想象,基于计时器的后台任务并不是应用可以使用的唯一类型的后台任务。与条件一样,后台任务可以基于用户会话期间发生的系统事件来启动。到目前为止,您一直在使用TimeTrigger
类来触发一个后台任务;该触发器以给定的时间间隔执行后台任务。表 8-2 提供了可用于启动后台任务的所有可能触发器的列表。许多触发器类型遵循相似的模式,因此本节重点介绍常见的维护和系统触发器。
表 8-2。
Background Task Trigger Types
| 触发器名称 | 描述 | | --- | --- | | 时间触发器 | `Windows.ApplicationModel.Background.TimeTrigger`表示触发后台任务运行的时间事件。 | | 系统触发器 | `Windows.ApplicationModel.Background.SystemTrigger`表示触发后台任务运行的系统事件。系统事件的示例包括网络中断、网络访问以及用户登录或注销。 | | 推送通知触发器 | `Windows.ApplicationModel.Background.PushNotificationTrigger`表示调用应用上的后台工作项的对象,以响应原始通知的接收。推送通知超出了本书的范围;有关为 Windows 8 应用设置推送通知服务的更多信息,请访问 Windows 8 应用开发中心。 | | 维护触发器 | `Windows.ApplicationModel.Background.MaintenanceTrigger`代表维护触发器。与`TimeTrigger`一样,`MaintenanceTrigger`在时间间隔结束时执行(可以配置为只执行一次),但是设备必须连接电源才能执行指定触发有效的任务。 |对于需要代码在时间和交流电源范围之外的系统事件发生时执行的应用来说,SystemTrigger
类是一个包罗万象的备选方案。SystemTrigger
用一个枚举实例化,该枚举标识哪个系统事件触发底层后台任务。清单 8-4 创建了一个系统触发器,当设备连接到互联网时就会被触发。
Listing 8-4. System Trigger
var task = new Background.BackgroundTaskBuilder();
var internet_available = new Background.SystemTrigger(Background.SystemTriggerType.internetAvailable, false);
task.taskEntryPoint = "/samples/bgtasksample/timerworker.js";
task.setTrigger(internet_available);
background_task = task.register();
表 8-3 列出了可用的系统触发类型。
表 8-3。
System Trigger Types
| 触发器类型 | 描述 | | --- | --- | | `smsReceived` | 当已安装的移动宽带设备收到新的 SMS 消息时,将触发后台任务。 | | `userPresent` | 当用户出现时,触发后台任务。注意:应用必须放置在锁定屏幕上,才能使用此触发器类型成功注册后台任务。 | | `userAway` | 当用户不在时,触发后台任务。注意:应用必须放置在锁定屏幕上,才能使用此触发器类型成功注册后台任务。 | | `networkStateChange` | 当网络发生变化时,例如开销或连接性的变化,触发后台任务。 | | `internetAvailable` | 当互联网变得可用时,触发后台任务。 | | `sessionConnected` | 当会话连接时,将触发后台任务。注意:应用必须放置在锁定屏幕上,才能使用此触发器类型成功注册后台任务。 | | `servicingComplete` | 当系统完成应用更新时,会触发后台任务。 | | `lockScreenApplicationAdded` | 将磁贴添加到锁定屏幕时,会触发后台任务。 | | `lockScreenApplicationRemoved` | 当磁贴从锁定屏幕移除时,触发后台任务。 | | `timeZoneChange` | 当设备上的时区发生变化时(例如,当系统根据夏令时调整时钟时),将触发后台任务。 | | `onlineIdConnectedStateChange` | 当连接到该帐户的 Microsoft 帐户发生变化时,将触发后台任务。 |Note
一些系统触发器需要应用在锁定屏幕上:SessionConnected
、UserPresent
、UserAway
和ControlChannelReset
。如果您在应用不在锁定屏幕上的情况下使用这些触发器,BackgroundTaskBuilder
上的register
呼叫将会失败。
宿主进程
后台任务要么在系统提供的主机可执行文件(称为backgroundtaskhost.exe
)中执行,要么在 app 进程中执行。清单 8-1 使用了一个单独库中的后台任务作为后台任务。在这个场景中,使用了backgroundtaskhost.exe
,任务的启动与应用的状态无关。这意味着后台任务可以在相关应用没有启动的情况下启动和运行。在清单 8-4 中,后台任务指向位于/samples/bgtasksample/timerworker.js
的一个 JavaScript 工作器。这是一个在 app 进程中执行的后台任务的例子。因为应用正在托管这种类型的后台任务,所以可能需要启动它才能运行底层任务。如果当这样的任务被触发时,应用已经在运行,它将在应用的上下文中启动。如果应用在任务被触发时被挂起,它的线程将被解冻,然后任务将被启动。最后,如果应用在触发后台任务时处于终止状态,则启动应用,然后执行任务。
报告进度
为了向应用报告进度(当后台任务执行时,应用在前台运行),BackgroundTaskRegistration
提供了progress
事件处理程序。对于 WinJS 应用,WebUIBackgroundTaskInstance
被传递给Run
方法以向前台应用传达进度。该接口有一个可选的Progress
属性,可以由后台任务更新。
BackgroundTaskRegistration
还提供了一个completed
事件,当后台任务完成时,应用可以使用该事件得到通知。任务运行时引发的完成状态或任何异常都将作为事件处理程序的输入参数传递给前台应用中的任何完成处理程序。如果应用在任务完成时暂停,它会在下次恢复时收到完成通知。在应用被终止的情况下,它不会收到完成通知。在这种情况下,由应用开发人员将任何完成状态信息保存在一个商店中,前台应用也可以访问该商店。
JavaScript 后台任务必须在完成工作后调用close
,这样任务才能被关闭。一定要进行这个调用,因为它向后台任务基础设施表明任务已经完成(没有它,JavaScript 主机将保持活动)。
在清单 8-2 中,你看到了一个基于 C#的后台工作器的例子。清单 8-5 用 JavaScript 创建了一个简单的后台任务。注意在方法返回之前使用了close
函数。
Listing 8-5. JavaScript Background Worker
onmessage = function (event) {
var task_instance = Windows.UI.WebUIBackgroundTaskInstance.current;
var count= 0;
for (int i = 0; i < 10; i++)
{
count += 10;
backgroundTask.progress = count;
}
backgroundTask.succeeded = true;
close();
}
声明后台任务
本章首先讨论了传统应用的一些缺点,因为它们与系统资源的使用有关。同样,假设遗留应用可以在没有用户通知或参与(除了启动它们)的情况下在后台运行,那么系统作为一个整体可能会感觉迟钝。并不是说在后台运行应用本身就有什么问题;但是这种活动的影响很少传达给用户。你会惊讶于你的系统中有多少前台应用运行着一个或多个后台任务,甚至在应用没有运行的时候!遵循 Windows 8 的总体主题,像这样的决定不是留给应用开发人员,而是委托给用户。由用户决定哪些应用应该在后台运行。
为了帮助实现这一点,寻求使用后台任务的现代 Windows 8 应用必须明确声明自己是后台任务,并明确指出它们公开的后台任务类型。图 8-3 显示了这个声明在 Visual Studio 2012 IDE 中的位置(package.appmanifest
)。它在同一个声明选项卡中,这个选项卡被反复使用,向用户清楚地概述了应用公开的特性。这很好,因为用户最终负责选择使用一个应用还是另一个。
图 8-3。
Background task declaration
在图中,入口点表示包含后台任务实现的类的完全限定类型名。它应该用在使用 C#或 C++/Cx 开发后台任务的场景中。如果后台任务是在宿主 JavaScript 应用中定义的,那么起始页应该填充专用工作文件的路径。
Note
因为许多后台任务需要锁屏访问才能运行,所以您还需要在应用 UI 选项卡上指定徽章信息,以便声明正确。图 8-4 显示了应用清单的应用 UI 选项卡的视觉资产部分。为了使图 8-1 中的声明有效,应用必须具有有效的徽章标志,并且必须启用某种形式的锁屏通知。如图所示,如果没有正确配置这些元素,应用将无法正确构建。
图 8-4。
Visual Assets section of the Application UI tab in an app’s manifest
资源限制
因为后台任务是指快速进出、消耗很少系统资源的短期工作单元,所以对它们应用 CPU 和网络使用限制也就不足为奇了。具体来说,锁定屏幕上的每个应用每 15 分钟接收 2 秒钟的 CPU 时间,供应用的所有后台任务使用。在 15 分钟结束时,锁定屏幕上的每个应用都会获得另外 2 秒钟的 CPU 时间,供其后台任务使用。(不在锁屏的每个应用每 2 小时接收 1 秒的 CPU 时间。)
在网络方面,数据吞吐量用于限制后台任务。这些指标根据手机、网络接口、可用电池寿命以及 CPU 使用情况而有所不同。一般来说,在设备上可用的平均吞吐量为 10Mbps 的情况下,一个应用可能每 15 分钟被允许大约 4.69MB(每天 450MB)。
有一些警告;例如,如果该应用恰好也是前台应用,那么这些 CPU 和网络限制就不再适用。此外,如果设备没有连接到电源,网络限制就不适用。
最后,在控制信道和推送通知后台任务的情况下,存在约束,但是如同其他后台任务类型一样,约束是针对每个任务而不是针对所有任务应用的。
后台传输
在现代计算时代,通过网络移动文件是一种常见的活动。无论您是将视频、音频或图像上传到社交网站,还是从媒体目录下载电影,都有对该功能的基本需求。如果目标文件非常小,这通常就像连接到内容提供商并下拉(或上推)所需的内容一样简单。但是当内容非常大的时候会怎么样呢?鉴于 Windows 8 被设计为快速流畅,允许用户在应用之间快速无缝地切换,应用如何处理长时间运行的网络数据传输活动正在进行中,而用户将应用移出前台状态的情况?当然,解决方案不是告诉用户,“警告:在下载完成之前不要离开应用!”
微软的人用BackgroundTransfer
功能覆盖了这个用例。使用BackgroundDownloader
,应用可以安排下载(或使用BackgroundUploader
上传)内容,这样即使用户暂停或终止应用,下载/上传仍会在后台继续。清单 8-6 显示了一个使用BackgroundDownloader
类从远程资源中下载一个大文件的简单例子。
Listing 8-6. BackgroundDownloader
at Work
btn_transfer.onclick = function ()
{
var known_folders = Windows.Storage.KnownFolders;
var foundation = Windows.Foundation;
var downloader = new Windows.Networking.BackgroundTransfer
.BackgroundDownloader();
var file = known_folders.videosLibrary.createFileAsync("kinectnui_ch9.wmv")
.then(function (file)
{
var download_operation = downloader.createDownload(
new foundation.Uri
("
http://www.xochl.com/media/videos/KinectNui_ch9.wmv
download_operation.startAsync();
});
};
清单 8-6 显示了一个btn_transfer
按钮的事件处理程序,当点击这个按钮时,从远程资源下载一个大文件。要运行此示例,您必须将视频库功能添加到目标应用的清单中,因为这是远程文件下载到的位置。一旦点击按钮并开始下载,用户可以切换离开目标应用,甚至停止它,下载仍然会完成。
摘要
现在,您已经探索完了在 Windows 8 环境中让应用在后台运行的方法,让我们回顾一下本章涉及的一些要点:
- 您学习了各种类型的触发器。对于需要代码执行的应用,即使系统超出了时间和交流电源的范围,
SystemTrigger
类是一个包罗万象的选择。 - 后台任务是指快速进入和退出,消耗很少系统资源的短期工作单元。CPU 和网络使用限制适用于它们。
- 使用
BackgroundTransfer
功能,BackgroundDownloader
是一个可以安排内容下载(或上传,使用BackgroundUploader
)的类,这样即使应用被用户暂停或终止,下载/上传也会在后台继续。 - 后台任务在系统提供的主机可执行文件或 app 进程中执行。
九、应用货币化:内幕
Abstract
说到底,大多数开发人员并不仅仅是为了创造性的表达。在大多数情况下,你希望从你所做的工作中获得一些经济上的收益——即使它仅够支付构建一个应用的费用。可以肯定的是,构建和维护现代应用会很快变得非常昂贵。首先,由于 Windows 8 应用的沙盒特性,许多功能——特别是那些针对传统桌面构建的应用——在 Windows 8 应用世界中不可用。Windows 8 应用当然是沙盒化的,这是有原因的——为了最大限度地减少行为不端的应用对设备造成的损害——但这些理所当然的限制的副作用是应用通常需要包含云组件。应用的这个驻留在云上的部分就像一个传统的应用一样,可以完全访问云服务器的环境。托管这样一个解决方案可以迅速将你的固定成本应用(用于构建应用的血汗资产与一次性价格相关联)转变为一个循环成本应用。
说到底,大多数开发人员并不仅仅是为了创造性的表达。在大多数情况下,你希望从你所做的工作中获得一些经济上的收益——即使它仅够支付构建一个应用的费用。可以肯定的是,构建和维护现代应用会很快变得非常昂贵。首先,由于 Windows 8 应用的沙盒特性,许多功能——特别是那些针对传统桌面构建的应用——在 Windows 8 应用世界中不可用。Windows 8 应用当然是沙盒化的,这是有原因的——为了最大限度地减少行为不端的应用对设备造成的损害——但这些理所当然的限制的副作用是应用通常需要包含云组件。应用的这个驻留在云上的部分就像一个传统的应用一样,可以完全访问云服务器的环境。托管这样一个解决方案可以迅速将你的固定成本应用(用于构建应用的血汗资产与一次性价格相关联)转变为一个循环成本应用。
市场加剧了这个问题。应用的消费者通常习惯于应用更新的便捷性和频繁性。这意味着对正在进行的开发的潜在期望扩展到市场上的任何应用,无论它是由像脸书这样的十亿美元的组织还是由你创建的。当你将一个应用发布到市场上时,你还必须考虑支持它所需的时间成本。
出于这些以及更多的原因,像 Windows Store、Windows Phone Store 和 Google Play 这样的数字市场都可以让你通过直接销售将应用货币化,市场就像商家一样。使用这种市场模式,潜在的 Windows 8 用户可以从 Windows 商店发现和购买应用。利润的一部分归开发目标应用的开发者,其余部分归微软,以帮助抵消维护 Windows 商店的成本。图 9-1 显示了 Windows Store 的登陆屏幕。
图 9-1。
Windows Store landing page
本章讨论了通过你的应用赚钱的两种常见方式:销售应用和在应用内销售。许多应用也通过应用内广告创收,但这种方法的细微差别超出了本书的范围。
销售您的应用
如果你有机会拥有一部 iPhone,并浏览苹果的应用商店购买流程,你可能会注意到许多非免费/收费应用有两个版本:一个免费的“演示”版本,用于展示应用可以做什么,以及一个附带的全功能版本。演示版要么功能有限,要么完全开放,但支持广告。附带的全功能版本通常不支持广告。用户可以习惯这样的约定,但在开发方面,这可能会导致头痛,因为特性必须跨两个代码库进行管理:一个用于应用的演示版本,另一个用于完整版本。
微软的 Windows Store 通过让你发布付费应用作为试用版来帮助解决这个问题。这允许开发人员维护应用的一个版本,作为试用版和完整版,并指定应用可以从一个版本迁移到另一个版本的条件。从 Windows Store 的角度来看,这避免了像苹果应用商店那样不必要的重复。从最终用户的角度来看,它提供了一种简单易行的方法来试用一个应用,如果你喜欢就购买它。图 9-2 显示了一个 Windows Store 付费应用页面,用户可以使用试用模式。
图 9-2。
Application with trial mode
从开发人员的角度来看,构建用于销售但包含试用组件的应用的工作流程相对简单:
Build the application as a full-featured application without worrying about which pieces of functionality are trial bits and which aren’t. After the application is built and tested and you’re sure everything works as intended, determine which features you want available to trial-version users and which bits you want to make visible only to paid-version users. Use the Microsoft-provided classes to gate the sections of the application that a trial user can’t access.
每个应用都有自己控制试用用户的方法。对于一些应用,比如游戏,你可能想展示应用能做的一切,但是对高级功能(或者应用的整体使用)有时间限制。
试用的想法不仅仅是对应用进行测试;这也是为了推销这款应用的好处。当你为了赚钱而开发应用时,请记住这个核心概念:追加销售,追加销售,追加销售!
接下来,让我们看看示例试用应用。
试用应用示例
考虑到上一节中的三个步骤,清单 9-1 在步骤 1 的末尾给出了一个非常简单的应用。
Listing 9-1. Simple Application with Two Features and a Buy App Button
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Listing1</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="Listing1.css" rel="stylesheet" />
<script src="Listing1.js"></script>
</head>
<body>
<div class="Listing1 fragment">
<section aria-label="Main content" role="main">
<header aria-label="Header content" role="banner">
<button class="win-backbutton" aria-label="Back" disabled
type="button"></button>
<h1 class="titlearea win-type-ellipsis">
<span class="pagetitle">Trial Mode Sample</span>
</h1>
</header>
</section>
<section aria-label="Main content" role="main">
<input type="button" id="btn_buyapp" value="buy app" />
</section>
<section aria-label="Main content" role="main">
<input type="button" id="btn_coolfeature" value="cool feature" />
</section>
<section aria-label="Main content" role="main">
<input type="button" id="btn_coolerfeature" value="even cooler feature" />
</section>
</div>
</body>
</html>
应用试用模式
试用模式应用向用户呈现了两个特性:一个很酷,另一个更酷。想必你想让这款应用的用户能够使用酷炫的功能,但只能看到更酷的功能。此外,当应用处于试用模式时,您希望购买应用按钮可见,但在购买应用后,您希望该按钮消失——否则用户可能会感到困惑,认为他们的购买没有注册或应用有缺陷。
对于 Windows 8 应用,购买是通过 Windows 商店 API 管理的,该 API 通过Windows.ApplicationModel.Store
命名空间提供。这是所有相关信息的入口,这些信息包括用户是否在使用试用版,如果是,还有多长时间可以试用。不幸的是,这些类只针对实际生产的 Windows 应用商店数据,这意味着它们期望您的应用在商店中并处于活动状态。这当然带来了一个问题,因为一个未发布的应用没有可用的信息,因此不可能测试试用模式的工作流。为了帮助解决这一困境,Windows 应用商店 API 包括一个模拟器 API 集,它模仿实时版本的功能,但针对您提供的 XML 文件工作。以这种方式使用 Windows Store commerce 基础架构的 Windows Store 应用需要您构建和管理这个 XML 文件,该文件称为WindowsStoreProxy.xml
,并且始终位于%userprofile%\appdata\local\packages\<package-moniker>\localstate\microsoft\Windows Store\Apidata
文件夹中。由开发人员决定是否放置/修改该文件,以适应您打算在应用中进行测试的场景(在这种情况下,测试以查看应用在试用模式和购买模式下的行为)。清单 9-2 显示了这个文件格式的一个例子。
Listing 9-2. WindowsStoreProxy.xml
<?xml version="1.0" encoding="utf-16" ?>
<CurrentApp>
<ListingInformation>
<App>
<AppId>2B14D306-D8F8-4066-A45B-0FB3464C67F2</AppId>
<LinkUri>
http://apps.microsoft.com/app/2B14D306-D8F8-4066-A45B-0FB3464C67F2</LinkUri
<CurrentMarket>en-us</CurrentMarket>
<AgeRating>6</AgeRating>
<MarketData xml:lang="en-us">
<Name>Trial management full license</Name>
<Description>Sample app for demonstrating trial license management</Description>
<Price>4.99</Price>
<CurrencySymbol>$</CurrencySymbol>
</MarketData>
</App>
</ListingInformation>
<LicenseInformation>
<App>
<IsActive>true</IsActive>
<IsTrial>true</IsTrial>
<ExpirationDate>2011-01-01T00:00:00.00Z</ExpirationDate>
</App>
</LicenseInformation>
</CurrentApp>
许可您的应用
下一节将更详细地介绍WindowsStoreProxy.xml
文件的各种元素。现在,让我们把注意力集中在LicenseInformation
部分,重点放在IsTrial
节点上——在本文档中它被设置为true
。
您主要通过一个类与 Windows 商店进行交互:CurrentApp
。(由于上述原因,在这个例子中,您使用了另一个类——使用了WindowsStoreProxy.xml
的那个类——叫做CurrentAppSimulator
。它模拟了CurrentApp
的 API 表面积。)CurrentApp
公开了一个LicenseInformation
类型的LicenseInformation
属性,您可以用它来读取与应用相关的许可数据。清单 9-2 中的WindowsStoreProxy.xml
文件的粗体部分对应于这个部分。LicenseInformation
提供IsActive
、IsTrial
、ExpirationDate
等属性。在正常情况下(当你的应用已经发布到 Windows Store 并且是公开的),调用Windows.ApplicationModel.Store.CurrentApp.licenseInformation
返回应用的LicenseInformation
实例。在这种情况下,因为您的应用还没有发布,所以您必须使用CurrentAppSimulator
,当属性被调用时,它期望WindowsStoreProxy.xml
文件位于前面讨论的位置;否则会出现错误。
在此示例中,您没有在 Windows 应用商店文件夹中查找该文件的放置位置并手动将其复制到该位置,而是创建了该文件的临时版本,您可以根据需要对其进行编辑。您将它放在应用的安装目录中;然后,每次应用启动时,您动态创建适当的 Windows Store 文件夹,在该文件夹中创建WindowsStoreProxy.xml
文件,并将临时代理文件的内容复制到最终位置。这样,您可以轻松地使用本地副本。
在您的示例项目中,添加storeproxy
文件夹;在其中,添加文件TrialManagement.xml
,这是默认的WindowsStoreProxy.xml
文件的副本。清单 9-3 显示了带有刚才描述的功能的样例应用的 JavaScript。
Listing 9-3. JavaScript for Example App
(function ()
{
"use strict";
var _listing_information = null;
var _license = null;
WinJS.UI.Pages.define("/Listing1/Listing1.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)
{
initializePurchaseState();
}
,
});
function initializePurchaseState()
{
var proxy_folder = null;
var install_location = null;
var temp_proxy_file = null;
Windows.Storage.ApplicationData.current.localFolder
。
createFolderAsync("Microsoft\\Windows Store\\ApiData"
,ⅵ
Windows.Storage.CreationCollisionOption.replaceExisting)
.then(
function (folder)
{
proxy_folder = folder;
return Windows.ApplicationModel.Package.current.installedLocation
.getFolderAsync("storeproxy");
})
.then(function (location)
{
install_location = location;
return install_location.getFileAsync("trialmanagement.xml");
})
.then(function (temp)
{
temp_proxy_file = temp;
return proxy_folder.createFileAsync("WindowsStoreProxy.xml"
,ⅵ
Windows.Storage.CreationCollisionOption.replaceExisting);
})
.then(function (proxy_file)
{
temp_proxy_file.copyAndReplaceAsync(proxy_file);
})
.done(function ()
{
_license = Windows.ApplicationModel.Store↩
.CurrentAppSimulator.licenseInformation;
});
}
})();
清单 9-3 从调用initializePurchaseState
开始。无论您是使用代理文件还是去 Windows 应用商店获取有关应用许可的信息,预先做这些都是有意义的。毕竟,您使用这些信息来确定向用户公开什么功能以及隐藏什么功能。initializePurchaseState
相当直白;您打开或创建代理文件所在的目标文件夹,在那里创建正式的代理文件,然后将您的临时代理文件(您使用和修改的那个)的内容复制到正式的WindowsStoreProxy.xml
文件。注意使用文件 I/O 时使用的承诺。在这种情况下,因为这只是为了开发目的,所有参数和文件名都在您的控制之下,所以您省略了每个then
函数的第二个参数,它是错误条件的处理程序。正常情况下,这个应该包括在内!
在async
链的末端是done
函数,在这里您最终读取了LicenseInformation
。同样,因为您使用的是CurrentAppSimulator
,所以这是从您的WindowsStoreProxy.xml
文件中读取的,而不是实际的 Windows 应用商店。图 9-3 显示了此时应用的外观和感觉。
图 9-3。
Trial mode sample app
现在您已经有了应用许可的详细信息,您可以根据需要开始显示和隐藏功能。在这种情况下,您需要做的就是读取IsTrial
的值,并基于此切换购买应用按钮的可见性和更酷功能按钮的可交互性。清单 9-4 相应地更新了应用。
Listing 9-4. Trial App Updated with IsTrial
Logic
(function ()
{
"use strict";
var _listing_information = null;
var _license = null;
WinJS.UI.Pages.define("/Listing1/Listing1.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)
{
initializePurchaseState();
btn_coolfeature.onclick = function ()
{
var v = new Windows.UI.Popups
.MessageDialog("This feature available to ALL customers");
v.showAsync();
};
btn_coolerfeature.onclick = function ()
{
if (_license.isTrial)
{
var v = new Windows.UI.Popups
.MessageDialog("This feature is only available to PAYING
customers");
v.showAsync();
} else
{
var v = new Windows.UI.Popups
.MessageDialog("PAYING customer feature");
v.showAsync();
}
};
}
,
});
function initializePurchaseState()
{
var proxy_folder = null;
var install_location = null;
var temp_proxy_file = null;
Windows.Storage.ApplicationData.current.localFolder
。
createFolderAsync("Microsoft\\Windows Store\\ApiData"
,ⅵ
Windows.Storage.CreationCollisionOption.replaceExisting)
.then(
function (folder)
{
proxy_folder = folder;
return Windows.ApplicationModel.Package.current.installedLocation
.getFolderAsync("storeproxy");
})
.then(function (location)
{
install_location = location;
return install_location.getFileAsync("trialmanagement.xml");
})
.then(function (temp)
{
temp_proxy_file = temp;
return proxy_folder.createFileAsync("WindowsStoreProxy.xml"
,ⅵ
Windows.Storage.CreationCollisionOption.replaceExisting);
})
.then(function (proxy_file)
{
temp_proxy_file.copyAndReplaceAsync(proxy_file);
})
.done(function ()
{
_license = Windows.ApplicationModel.Store.CurrentAppSimulator
.licenseInformation;
loadAppPurchaseStateUI();
});
}
function loadAppPurchaseStateUI()
{
if (_license.isTrial)
{
btn_buyapp.style.visibility = "visible";
} else
{
btn_buyapp.style.visibility = "hidden";
}
}
})();
粗体显示的变化很简单。当应用启动时,以及当用户点击更酷的特性按钮时,您实际上切换了基于IsTrial
标志的行为。图 9-4 、 9-5 、 9-6 和 9-7 显示了基于用户点击的应用的各种状态。
图 9-7。
App view when Even Cooler Feature is clicked (IsTrial
is true
)
图 9-6。
App view when Even Cooler Feature is clicked (IsTrial
is false
)
图 9-5。
Prompt when Cool Feature is clicked
图 9-4。
App main screen when IsTrial
is false
将代码添加到应用
该流程的最后一个部分是添加执行应用的实际购买所必需的代码。一旦应用被激活,您可以通过再次调用loadAppPurchaseStateUI
来刷新用户界面。清单 9-5 显示了 Buy App 按钮的onclick
事件的函数回调。
Listing 9-5. Simulating the User Purchasing the Application
btn_buyapp.onclick = function ()
{
Windows.ApplicationModel.Store.CurrentAppSimulator
.requestAppPurchaseAsync(true)
.then(function (receipt)
{
_license = Windows.ApplicationModel.Store.CurrentAppSimulator
.licenseInformation;
loadAppPurchaseStateUI();
});
};
应用购买请求
通过调用CurrentApp
或CurrentAppSimulator
的requestAppPurchaseAsync
方法,应用可以代表用户提出购买请求(这必须通过用户驱动的事件来完成,如鼠标点击)。虽然 API 是相同的,但是随后的操作会根据使用的类而有所不同。CurrentApp
调用 Windows 商店购买用户界面,而CurrentAppSimulator
启动一个传统的 Windows 对话框,您可以在其中选择您希望模拟器返回到您的应用的响应类型。您可以使用该功能来模拟快乐之路之外的各种用例。图 9-8 显示了点击购买应用按钮时出现的对话框。
您的代码应该能够处理这些场景,这样您的应用就不会崩溃。为此,您需要整合清单 9-5 中的错误处理函数。回想一下第一章中的,您使用的then
承诺可以接受三个函数:正常的完成处理程序、异步处理过程中发生错误时的错误处理程序,以及提供操作进度洞察的进度处理程序。在图 9-8 的对话框中选择的值作为参数传递给错误事件处理器。您可以使用该值向用户显示适当的消息。
图 9-8。
CurrentAppSimulator
purchasing dialog for testing purchasing scenarios
在应用内销售
您已经了解了如何构建一个支持试用的应用。这种方法的前提是将您的应用发布为付费应用。付费应用代表了通过 Windows Store 应用赚钱的主要方式,但只是触及了总体模式的表面。
提供试用版是让潜在客户下载你的应用并在承诺支付费用前进行试运行的好方法,但这种方法在与免费同行竞争时有一些明显的缺点。毫无疑问,统计数据显示(在所有主要的应用商店中),免费应用的下载量远远超过付费应用,并且与之相关的活动和对话也更多。虽然从技术上来说,试用应用是免费的,但它仍然在 Windows Store 的付费类别中列出,这使得发现你的应用成为一个问题。此外,正如您在前面的例子中看到的,付费/试用模式的灵活性有限。
作为一名开发者,你实际上只有两种产品可以呈现给最终用户:应用的免费版本和完整版本。实现的二进制性质限制了应用中自然存在的商业机会。例如,在这个例子中,假设您创建了一个“最酷的特性”功能,您额外给了它一美元。这将把应用的成本提高到 5.99 美元(根据WindowsStoreProxy.xml
文件,该产品目前的成本为 4.99 美元)。不仅高昂的价格点可能难以消化,而且它还消除了用户只为一个功能付费的选择。通过应用内购买,你可以将购买的压力从整个应用转移到应用内的特定功能上。这一举两得。一方面,你把你的应用转移到世界上流量更高的地方,让它更容易被发现。另一方面,你继续将你的应用货币化,这一次你可以更好地控制你免费提供的应用。
在应用内购买方面,一个突出的应用类别是游戏。用户经常偶然发现免费游戏,由于不存在的价格标签,他们会很快下载。一旦参与(上瘾是一个常见的术语),用户更倾向于为额外的功能付费。
将应用内购买添加到应用
让我们修改示例应用以使用应用内购买。清单 9-6 从稍微修改应用的用户界面开始。
Listing 9-6. Modified User Interface for the Trial App
<section aria-label="Main content" role="main">
<input type="button" id="btn_buyapp" value="buy app" />
</section>
<section aria-label="Main content" role="main">
<input type="button" id="btn_coolfeature" value="cool feature" />
</section>
<section aria-label="Main content" role="main">
<input type="button" id="btn_coolerfeature" value="please wait..." />
</section>
<section aria-label="Main content" role="main">
<input type="button" id="btn_coolestfeature" value="please wait..." />
</section>
到目前为止,您已经添加了一个新按钮btn_coolestfeature
,并且修改了btn_coolerfeature
的内容,以向用户表明他们的内容正在加载。正如你马上看到的,你这样做是为了利用应用内购买的一个好处:你可以在应用外定义你的产品目录。你不再需要硬编码你正在销售的特性的名字;您可以直接从 Windows 应用商店阅读它们。
第十章将详细介绍如何在商店中设置你的应用。为了帮助解决这个问题,应用内购买向WindowsStoreProxy.xml
文件引入了一些新的元素类型。清单 9-7 更新了您的本地副本,包含了表示应用内购买功能所需的所有元素。
Listing 9-7. WindowsStoreProxy.xml
with Product Listing Information Included
<?xml version="1.0" encoding="utf-16" ?>
<CurrentApp>
<ListingInformation>
<App>
<AppId>2B14D306-D8F8-4066-A45B-0FB3464C67F2</AppId>
<LinkUri>
http://apps.microsoft.com/app/2B14D306-D8F8-4066-A45B-0FB3464C67F2</LinkUri
<CurrentMarket>en-us</CurrentMarket>
<AgeRating>6</AgeRating>
<MarketData xml:lang="en-us">
<Name>Trial management full license</Name>
<Description>Sample app for demonstrating trial license management</Description>
<Price>0.00</Price>
<CurrencySymbol>$</CurrencySymbol>
</MarketData>
</App>
<Product ProductId="coolerfeature">
<MarketData xml:lang="en-us">
<Name>Cooler Feature</Name>
<Price>1.99</Price>
<CurrencySymbol>$</CurrencySymbol>
</MarketData>
</Product>
<Product ProductId="coolestfeature">
<MarketData xml:lang="en-us">
<Name>Coolest Feature</Name>
<Price>2.99</Price>
<CurrencySymbol>$</CurrencySymbol>
</MarketData>
</Product>
</ListingInformation>
<LicenseInformation>
<App>
<IsActive>true</IsActive>
<IsTrial>false</IsTrial>
</App>
<Product ProductId="coolerfeature">
<IsActive>false</IsActive>
</Product>
<Product ProductId="coolestfeature">
<IsActive>false</IsActive>
</Product>
</LicenseInformation>
</CurrentApp>
实现应用内购买
实现应用内购买会以多种方式改变应用。在前面的方法中,您让用户单击更酷的特性按钮,然后用一条消息提示他们,要使用该特性,他们必须购买该应用。既然您正在将应用转换为商业风格的应用,那么您应该提前为用户提供信息,以帮助他们做出购买决定。为此,您需要访问产品列表信息,以确定每个特性的名称以及该特性的价格。
清单 9-6 修改了两个目标按钮来显示文本请等待。现在让我们进入下一步,用直接从WindowsStoreProxy.xml
文件中提取的适当内容填充每个按钮。清单 9-8 显示了支持应用内购买的加载过程的修改。
Listing 9-8. Loading Content from the Windows Store
function initializePurchaseState()
{
var proxy_folder = null;
var install_location = null;
var temp_proxy_file = null;
Windows.Storage.ApplicationData.current.localFolder
。
createFolderAsync("Microsoft\\Windows Store\\ApiData"
,ⅵ
Windows.Storage.CreationCollisionOption.replaceExisting)
.then(
function (folder)
{
proxy_folder = folder;
return Windows.ApplicationModel.Package.current.installedLocation
.getFolderAsync("storeproxy");
})
.then(function (location)
{
install_location = location;
return install_location.getFileAsync("trialmanagement.xml");
})
.then(function (temp)
{
temp_proxy_file = temp;
return proxy_folder.createFileAsync("WindowsStoreProxy.xml"
,
Windows.Storage.CreationCollisionOption.replaceExisting);
})
.then(function (proxy_file)
{
temp_proxy_file.copyAndReplaceAsync(proxy_file);
})
。then(
function ()
{
_license = Windows.ApplicationModel.Store.CurrentAppSimulator
.licenseInformation;
返回窗口。applicationmodel.store.currentappsimulator↩
.loadListingInformationAsync();
})
.done(function (listing)
{
_listing_information = listing;
loadAppUI();
});
}
function loadAppUI()
{
if (_listing_information.productListings["coolerfeature"] != null)
{
GenerateButtonUI(btn_coolerfeature, "coolerfeature");
}
if (_listing_information.productListings["coolestfeature"] != null)
{
GenerateButtonUI(btn_coolestfeature, "coolestfeature");
}
}
function GenerateButtonUI(btn_target, feature_name)
{
var feature = _listing_information.productListings[feature_name];
var action_type = "Buy for " + feature.formattedPrice;
var feature_license = _license.productLicenses[feature_name];
if (feature_license.isActive)
{
action_type = "Use now";
}
btn_target.value = feature.name + " - " + action_type;
}
在清单 9-8 中你做的第一件事是通过调用Windows.ApplicationModel.Store.CurrentAppSimulator.loadListingInformationAsync
来扩展async
链。这个调用返回向用户展示特性名称和价格所需的列表信息。此时运行应用会产生如图 9-9 所示的视图。
图 9-9。
User interface elements populated from the Windows Store listing
现在,您需要添加必要的逻辑,以便在单击任一按钮时购买或激活相应的功能。清单 9-9 显示了对每个按钮的onclick
事件的更改,以启用该功能。
Listing 9-9. Enabling In-App Commerce
btn_coolerfeature.onclick = function ()
{
var feature_license = _license.productLicenses["coolerfeature"];
var feature_listing = _listing_information.productListings["coolerfeature"];
if (feature_license.isActive)
{
var v = new Windows.UI.Popups
.MessageDialog("Using " + feature_listing.name + "!");
v.showAsync();
}
else
{
//buy the product
Windows.ApplicationModel
.Store.CurrentAppSimulator
.requestProductPurchaseAsync(feature_license.productId, true)
.then(function (receipt)
{
//refresh after purchase
loadAppUI();
});
}
};
btn_coolestfeature.onclick = function ()
{
var feature_license = _license.productLicenses["coolestfeature"];
var feature_listing = _listing_information.productListings["coolestfeature"];
if (feature_license.isActive)
{
var v = new Windows.UI.Popups
.MessageDialog("Using " + feature_listing.name + "!");
v.showAsync();
}
else
{
//buy the product
Windows.ApplicationModel
.Store.CurrentAppSimulator
.requestProductPurchaseAsync(feature_license.productId, true)
.then(function (receipt)
{
//refresh after purchase
loadAppUI();
});
}
};
逻辑很简单:当用户单击任一按钮时,您检查产品功能是否有有效的许可证。如果是,则调用特征功能。如果没有,则向商店发起购买该特征的请求。与 app 购买一样,CurrentAppSimulator
显示了之前在图 9-8 中显示的对话框。一旦购买了一个特性,就调用loadAppUI
来重新加载用户界面。这一次,您不会看到与该特定功能相关的价格,因为它已经被购买,并且现在处于活动状态。图 9-10 说明了。
图 9-10。
App after both features have been purchased
图 9-11 显示了当点击其中一个按钮时会发生什么——在这个例子中,是最酷的功能按钮。
图 9-11。
Using a purchased feature in an app
应用内购买和试用应用是帮助您将应用货币化的强大功能,因此确保正确的功能至关重要。考虑到从用户的角度来看,对应用稳定性的信心对购买体验有多么重要,你最不希望的事情就是你的应用在购买场景中不能正常运行。在测试这些场景时,结合使用CurrentAppSimulator
和WindowsStoreProxy.xml
文件是至关重要的。来自 MSDN 文档( http://msdn.microsoft.com/en-us/library/windows/apps/hh779766.aspx
)的以下部分详细概述了该文件的各个节点。如果你有兴趣将你的应用货币化,请确保你了解这些节点是如何工作的:
ListingInformation
: Represents data that would normally be found in the app’s listing and includes these elements: App
: Represents data about the app and contains these elements: AppId
: The GUID that identifies the app in the store. This can be any GUID for testing. LinkUri
: The URI of the listing page in the store. This can be any valid URI for testing. AgeRating
: An integer that represents the minimum age rating of the app. This is the same value you would specify in the Dashboard when you submit the app. The values used by the Windows Store are: 3, 7, 12, and 16. CurrentMarket
: The customer’s country/region. For each country/region in which the app is listed, there must be a MarketData
element: MarketData
: Info about the app for this country/region. Requires the xml:lang
attribute, which specifies the country/region for which this info applies: Name
: The name of the app in this country/region. Description
: The description of the app for this country/region. Price
: The price of the app in this country/region. CurrencySymbol
: The currency symbol used in this country/region. Product
: Describes a product or a feature in this app that is enabled when the customer makes an in-app purchase. Requires the ProductId
attribute, which contains the string used by the app to identify the product or feature. Requires the LicenseDuration
attribute, which contains the number of days the license will be valid after the purchase. The expiration date of the new license created by a product purchase is the purchase date plus the license duration: MarketData
: Info about the product or feature for this country/region. Requires the xml:lang
attribute, which specifies the country/region for which this information applies: Name
: The name of the product or feature in this country/region. Price
: The price of the product or feature in this country/region. CurrencySymbol
: The currency symbol used in this country/region. CurrencyCode
: The currency code used in this country/region. LicenseInformation
: Represents data that describes the licenses available for this app and includes the following elements: App
: Describes the app’s license: IsActive
: Describes the current license state of this app. true
indicates the license is valid. Normally this value is true
, whether the app has a trial mode or not. false
indicates an invalid license. Set this value to false
to test how your app behaves when it has an invalid license. IsTrial
: Describes the current trial state of this app. true
indicates the app is being used during the trial period. false
indicates the app isn’t in a trial, either because the app has been purchased or because the trial period has expired. ExpirationDate
: The date the trial period for this app expires. The date must be express as: yyyy-mm-ddThh:mm:ss.ssZ. For example, 05:00 on January 19, 2012 is specified as 2012-01-19T05:00:00.00Z
. This element is required when IsTrial
is true
. Otherwise, it isn’t required. DefaultResponse
: Describes the default error code returned by a given method. The MethodName
attribute allows you to specify one of these methods: RequestAppPurchaseAsync_GetResult
, RequestProductPurchaseAsync_GetResult
, or LoadListingInformationAsync_GetResult
. The HResult
attribute allows you to specify the error code. Product
: Describes the license status of a product or feature in the app: IsActive
: Describes the current license state of this product or feature. true
indicates the product or feature can be used. false
indicates the product or feature can’t be used or has not been purchased. ExpirationDate
: The date the product or feature expires. The date must be express as: yyyy-mm-ddThh:mm:ss.ssZ. For example, 05:00 on January 19, 2012 is specified as 2012-01-19T05:00:00.00Z
. If this element is present, the product or feature has an expiration date. If this element isn’t present, the product or feature doesn’t expire.
摘要
无论采取措施让你的应用赚钱是必要的坏事还是锦上添花,这都是你应该知道的事情,并且可以从现实世界的应用开发中受益。本章提供了要遵循的步骤,以及一些有用的见解,以最有效地将你的应用货币化。本章涵盖的要点包括以下内容:
- Microsoft Windows 应用商店可以将付费应用发布为试用版,允许您创建和维护单个应用的试用(免费)和全功能(付费)版本,并指定应用从一个版本迁移到另一个版本的条件。
- 实现单个应用的试用版和全功能版有助于提升应用的销量。
- 在测试场景中,结合使用
CurrentAppSimulator
和WindowsStoreProxy.xml
文件是很重要的。 - 通过销售你的应用来赚钱和通过在应用内销售来赚钱是不同的。
- 提供免费应用、付费应用或带有免费试用选项的付费应用都有潜在的利弊。
- 实现应用内购买是一种强大的应用货币化方法。
十、在 Windows 应用商店获取你的应用
Abstract
前一章深入探讨了你的应用的货币化。但你还没到那一步。为了让你的应用在 Windows 应用商店中发布,一些管理职责是不容忽视的。注意:你已经完成了与世界分享你的 Windows 8 应用的最后一步。
前一章深入探讨了你的应用的货币化。但你还没到那一步。为了让你的应用在 Windows 应用商店中发布,一些管理职责是不容忽视的。注意:你已经完成了与世界分享你的 Windows 8 应用的最后一步。
作为一名开发人员,在 Windows Store 上下载你的应用可能是旅程中最乏味的部分。但是请放心,遵循前几章提出的指导方针会让你的应用尽可能无缝地被接受发布。本章讨论设置开发者帐户、应用提交、应用跟踪和应用管理。您将了解发布后功能,包括提供销售数据、下载、激活和遥测视图的工具。抓紧了:你就要——你的应用就要——出来了!
创建应用包
让我们直接进入 Windows 应用商店提交流程。您熟悉开发人员中心和开发人员仪表板。这就是神奇继续发生的地方——你进入 Windows 应用商店的入口。
若要创建应用包,请在 Visual Studio 2012 的“商店”子菜单中选择“创建应用包”项。如果您选择为 Windows 应用商店构建应用包的选项,系统会提示您使用 Windows 开发人员帐户登录应用商店,您将被定向到“选择应用名称”屏幕。图 10-1 显示了 Visual Studio 中的提交应用菜单和子菜单选项。
图 10-1。
Visual Studio Submit App drop-down menu and submenu selections
图 10-2 显示了选择应用名称屏幕。
图 10-2。
Visual Studio Select an App Name screen
图 10-2 中列出的 Windows 应用商店应用名称是您之前必须通过 Windows 应用商店开发者中心注册的名称。选择其中一个名称会将您正在处理的项目与该特定应用相关联。截至本文撰写之时,您还不能通过 Visual Studio 2012 界面注册应用名称。你将在本章后面了解更多关于注册应用名称的信息;现在,让我们专注于配置您的应用,以便它可以发布到商店。
如果您选择不创建 Windows 应用商店包,则不必登录,但生成的应用包只能安装在开发人员计算机上或支持侧加载的环境中。(侧装是绕过 Windows 商店,直接在 PC 上安装 Windows 8 应用的过程。)点击 Next,你会看到一个 package settings 屏幕,在这里你可以指定包输出位置、版本号和构建配置(见图 10-3 )。
图 10-3。
Package settings screen
应用包使用符合 Windows 8 应用风格指南的.zip
APPX 打包格式。APPX 包是在您从 Windows 应用商店安装应用时下载到您的计算机上的包,它是您用于提交到 Windows 应用商店的格式。APPX 软件包可以部署为 x86(适用于 32 位操作系统)、x64(适用于 64 位 Windows 8 版本)、ARM(适用于基于 ARM 的平板电脑和 PC,如微软 Surface 和华硕 VivoTab)或 Neutral(适用于所有平台的版本)。
完成应用包创建方式的配置后,单击创建按钮构建包。Visual Studio 将您的项目编译成适当的可执行格式,并将其打包成 APPX(包括证书和包清单)。至此,向导完成。它为您提供了可以找到您创建的包的位置。图 10-4 显示了最终屏幕(显示了我机器上所有学习包的路径)。
图 10-4。
The wizard’s final screen
验证你的应用包
为了让您将应用提交到 Windows 应用商店,它必须满足 Microsoft 确定的特定标准。这确保了 Windows 应用商店中的应用对于最终用户来说是安全的,他们可以在所有目标平台上无缝地安装和工作,没有例外。Microsoft 希望您的应用使用经过批准的标准化 API 外围应用,并遵循与 Windows 应用商店主题相匹配的设计、风格和内容指南。验证内容和样式组件需要人工输入;但作为应用提交过程的一部分,Windows Store 会自动检查应用包,以确定您使用了哪些 API 和编程模式。如果包不满足必要的要求,它不会被接受,并且您不能继续提交过程。
微软提供了一个名为 Windows 应用认证工具包(WACK)的有用的认证工具,它可以在本地运行你的应用,并执行一系列旨在找到常见且容易检测到的应用认证失败的测试。要启动它,只需使用搜索符并输入它的名称。图 10-5 显示了 WACK 接口(双关语)。
图 10-5。
WACK interface
正如你可能想象的那样,WACK 工具可以帮助你识别和解决应用中的任何问题,否则这些问题会阻止应用提交到 Windows 应用商店。表 10-1 列出了 WACK 工具进行的一些测试。
表 10-1。
WACK App Tests
| 试验 | 描述 | | --- | --- | | 遵守系统重启消息 | 测试您的应用在收到系统关闭通知时是否尽快退出。 | | 应用清单策略 | 测试应用清单的内容,以确保其正确无误且格式正确。 | | 应用清单资源 | 测试应用清单中定义的资源,以确保它们存在并且有效。这包括用适当的图像/字符串来呈现应用的各种状态。 | | 干净可逆安装 | 确保应用正确安装和卸载,没有残留文件或注册表项。 | | 兼容性和弹性 | 验证应用没有使用任何 Windows 兼容性修复程序。 | | 崩溃和挂起 | 通过执行启动、运行和关闭过程来检查应用是否崩溃或挂起。 | | 调试配置 | 确认应用包是使用发布配置构建的。 | | 数字签名文件 | 验证有效的数字签名。 | | 直接 3d 特征级别 | 测试 Microsoft Direct3D 应用,以确保它们可以在所有 Windows 8 图形硬件上运行。 | | 文件编码 | 确认所有文件编码为 UTF-8。 | | 安装并写入正确的文件夹 | 确保应用将程序和数据文件写入正确的文件夹。 | | 表演 | 通过计时应用启动和暂停来确认性能速度。 | | 支持 Windows 8 商店 API | 确认使用 Windows 8 风格支持的 API。 | | Windows 安全功能 | 确认没有篡改默认的 Windows 安全保护。 |WACK 测试要么在后台运行,要么在一个小的应用窗口中运行,这取决于您启动该工具的方式。在 WACK 运行整套测试的几分钟内,您会看到您的应用出现和消失几次。无论你做什么,不要试图在测试期间与你的应用交互,否则你的电脑可能会过热并爆炸。不完全是,但是你的应用将会关闭,你将不能与之互动。一旦 WACK 工具完成测试,就会出现结果屏幕。如果测试失败,WACK tool 报告会传达失败的原因。如果 WACK 测试通过,你可以直接去 Windows 商店继续提交你的应用,或者从结果窗口查看链接的详细报告。
提交您的应用
您已经用一个经过验证的应用结束了上一节,您确信可以毫无问题地提交到 App Store。你之所以确定这一点,是因为在尝试提交之前,你使用了 WACK 工具来验证该应用是否符合 Windows 应用商店允许的最低标准。本节介绍了如何使用开发者仪表盘将此经过验证的应用包上传到 Windows 应用商店,以及如何通过提交应用商店认证来完成此过程。
您可以通过导航到 http://msdn.microsoft.com/en-US/windows/apps
来访问 Windows 应用商店开发中心。在那里,你用你的 Windows 应用商店帐户(通常是一个Hotmail/live/outlook.com
电子邮件地址)登录。图 10-6 显示了开发中心登陆页面。
图 10-6。
Dev Center landing page
到目前为止,您所做的一切都不需要您注册任何东西,但您需要一个开发人员帐户来保留应用名称并向 Windows 应用商店提交应用。您可以在以下网站开始注册: http://msdn.microsoft.com/en-us/library/windows/apps/jj193592.aspx
。
注册并登录后,单击仪表板链接(如图 10-6 所示)将带您进入 Windows 8 应用的 Windows 应用商店仪表板。图 10-7 显示了 Windows 应用商店仪表盘。出于隐私原因,登录名(通常出现在屏幕的右上角)和应用列表已被隐藏。
图 10-7。
Windows Store dashboard
向 Windows 应用商店提交应用的主要方式是通过开发中心仪表板中的提交应用链接。单击此链接将进入一个工作流,从为应用命名到提供详细描述。作为提交过程的一部分,你还必须为你的申请提供销售细节——结合你在第九章中学到的一切。这意味着不仅要为应用整体定价,还要为应用中的功能定价(应用内优惠包含在该流程的高级功能步骤中)。
作为提交过程的一部分,您还需要提供内容分级信息。这确保了发布到商店的应用可以针对适当的年龄组。你可以指定你的应用使用的任何加密技术,然后通过一个步骤将你的应用上传到商店。如果你的应用已经通过了 WACK 工具的验证,那么这应该是一种形式;否则,这可能会是一个非常令人沮丧的过程,因为运行该工具会增加上传时间。(请注意,应用认证不会在上传过程中进行,而是作为整体提交的一部分在其他任何事情之前运行。)
最后一步,提供你的应用描述,可能看起来微不足道,但它很可能是这个过程中最吸引人的部分(特别是如果你没有一个图形设计师和定义良好的图标)。在这一步中,您不仅要为您的应用提供文本描述,还要包括您正在运行的应用的各种屏幕截图、不同分辨率的宣传图片、隐私政策 URL、电子邮件地址以及更多详细信息。图 10-8 显示了提交应用屏幕。
图 10-8。
App submission screen
命名您的应用
提交工作流程从命名应用开始。与网络域名一样,应用名称是先到先得的。幸运的是,在应用的世界里,房地产是购买的首选——这与如今的网络域名形成了鲜明的对比。
当你点击应用名称图标或链接时,你会被带到一个简单的屏幕,屏幕上有一个输入框,你可以在其中输入应用的名称(见图 10-9 )。输入您选择的名称,然后点按“保留应用名称”。如果名称不可用,您会看到一条错误消息。
图 10-9。
Reserving an app name
一旦选定的名称被保留,您有一年的期限来提交您的应用。在那之后,这个名字会回到池中。
销售详情
现在,你将进入销售详情页面。图 10-10 显示了页面的顶部,在这里你可以选择是将你的应用列为免费还是付费,或者使用基于应用功能/版本的多样化定价,正如在第九章中深入讨论的。你还需要提供免费试用的详细信息,以及你想在哪里销售应用的信息。
图 10-10。
Upper portion of the Selling Details screen
图 10-11 显示了销售详情屏幕的底部。您可以在这里指定您的应用何时发布。您需要提供应用类别和子类别,还需要指定任何硬件要求和辅助功能。
图 10-11。
Lower part of the Selling Details screen
高级功能
接下来,系统会提示您指定应用提供的任何高级功能。其中包括背景磁贴更新的推送通知、Live Connect 服务以及将通过 Windows 8 应用提供的任何应用内服务。如果您已经将产品添加到您的应用中(产品是上一章讨论的应用内购买功能的一部分),您可以在这里指定这些添加内容;它们允许你提供一些功能,用户可以在使用你的应用时购买。图 10-12 显示了 Windows 应用商店开发者仪表板的高级功能屏幕。
图 10-12。
Advanced Features screen
年龄评级和评级证书
接下来,你需要从表 10-2 中列出的选项中为你的应用选择一个年龄等级。这些评级是游戏所必需的。然而,即使你的应用不是游戏,如果包含成人内容,最好保持谨慎或保守,并提供评级。这样做将有助于确保你不会仅仅因为没有通过认证而失败。如果您不确定如何对您提供的内容进行分级,我建议使用对所提供的内容有意义的最高年龄分级。(还是那句话,保守有益。)
还要注意的是,对于游戏、在线访问和在线互动等活动,有不同的年龄要求,您可能不会立即意识到这一点(并且可能因国家而异)。例如,尽管社交 Web 应用本质上是良性的,但它们通常需要更高的评级,因为没有办法完全控制用户最终查看的内容。如果您正在构建这样的应用,如果成人和未成年人之间存在潜在的交互,那么显示明确禁止儿童的警告是很重要的。拥有更高的评级允许父母和监护人限制访问此类应用。
表 10-2。
Windows Store App Ratings
| 评级 | 描述 | | --- | --- | | 3+适合幼儿 | 这些应用被认为适合幼儿。在非现实的卡通形式中,可能会有很少的喜剧暴力。角色不应与现实生活中的角色相似或相关联。不应有可能令人恐惧的内容,也不应有裸露或涉及性或犯罪活动。具有此年龄分级的应用也不能启用可能访问不适合幼儿的内容或功能的功能。这包括但不限于访问在线服务、收集个人信息或激活麦克风或网络摄像头等硬件。 | | 7+适合 7 岁及以上儿童 | 这种年龄分级的应用与 3+应用具有相同的标准,只是这些应用可能包含可能会吓到年轻观众的内容,并且可能包含部分裸体,只要裸体不涉及性活动。 | | 12 岁以上适合 12 岁及以上儿童 | 如果您不确定要为您的应用选择哪个年龄分级,请选择此分级。具有此年龄分级的应用可能包含更多非性本质的裸露,对非现实人物的轻微图形暴力,或对现实人物或动物角色的非图形暴力。这个年龄等级也可能包括亵渎,但不是性的性质。此外,具有此年龄分级的应用可能包括对在线服务的访问,并启用麦克风和网络摄像头等功能。 | | 16 岁以上适合 16 岁及以上的人 | 具有这种年龄分级的应用可以用最少的鲜血描绘真实的暴力,并且可以描绘性活动。在认证要求规定的限制范围内,它们还可能包含毒品或烟草使用和犯罪活动,以及比 12+应用允许的更多的亵渎内容。 | | 18 岁以上仅适合成人。 | 除了适合 16 岁以上的应用的内容之外,具有此年龄分级的应用可能包含激烈、粗俗或特定的暴力,以及只适合成年观众的血腥或血腥。 |如果你的应用是一个游戏,或者根据判断需要评级,下一步就是提交并保存你的评级。提交的应用内容将根据您在认证过程中给出的评分进行评估。对于归类为游戏的 Windows 8 应用,会显示一个评级板,其中有一个针对该应用列出的每个地理市场的证书。基于这些地理市场,特别是巴西、韩国、南非和台湾,如果您计划在全球范围内提供游戏应用,则必须获得列出的证书。当然,如果获得证书有问题,你可以选择放弃在某些市场的销售。
密码系统
如果您的应用使用任何形式的数据文件加密、使用公钥基础架构、使用安全通信通道或使用平台 DRM 功能,系统会要求您回答一系列问题,并验证您对加密的使用仅限于常见情况,如密码、复制保护、数字签名或 DRM。图 10-13 显示了该页面。
图 10-13。
Cryptography screen
如果应用的加密超出了这些常见情况,您必须从美国商务部获取、输入并保存出口商品分类编号。(有关此过程的信息可在 http://export.gov/logistics/eg_main_018803.asp
找到)。)
上传你的应用
您已经到达了实际准备上传您的包的步骤。首先,您需要在 Visual Studio 中恢复您的包。打开应用包的文件夹,找到保存它的文件系统。您可以使用文件对话框找到该文件,或者您可以将当前版本的 APPX 上传文件拖放到软件包页面上的拖放区(参见图 10-14 )。为了确保所有需要的文件和资源都包含在包中,系统会执行验证,验证完成后会显示一条错误或成功消息。
图 10-14。
Packages screen
描述
现在是有趣的部分。通过在描述页面上输入对您的应用的描述,为您的辛勤工作感到自豪。系统还会提示您输入应用的功能、关键词和屏幕截图,并附有简短描述。潜在用户和审批者将看到相同的信息,因此内容应该准确、简洁、有说服力。图 10-15 显示了描述页面的顶部,在这里您可以提供描述和应用功能。
图 10-15。
Top part of the Description page
所有应用必须提供至少一个,最多八个截图。您可以选择使用 Windows 8 模拟器,它也会将图像存储在您的Pictures
library 文件夹中,方法是在 Visual Studio 中打开您的应用并使用 Capture Screenshots 项。图 10-16 显示了描述页面的这一部分。
图 10-16。
The screenshots section of the Description page
描述页面的下一部分允许您提供搜索关键字以及额外信息,如应用的许可条款、版权信息以及所需或推荐的硬件。如果你在某个时候返回并编辑你的应用提交(例如,如果你有一个新版本),你需要包括一个单独的更新描述。硬件字段是您指定应用是否需要传感器或设备才能运行的地方。图 10-17 显示了描述页面的这一部分。
图 10-17。
Extra description information on the Description page
最后,你可以上传你的应用的宣传图片。其中包括任何用于 App Store 营销目的的图片。您可以输入与您的应用或公司关联的网站(如果有)。您还可以提供所有这些最终但重要的详细信息,包括支持联系地址和 URL 以及您的在线隐私声明的链接。参见图 10-18 。
图 10-18。
The Promotional Images section of the Description page
测试人员注意事项
下一个也是最后一个屏幕,如图 10-19 所示,允许您输入您选择提供给测试人员的任何其他信息。请注意,这是输入登录测试凭据的地方。
图 10-19。
Notes to Testers screen
让您的应用获得认证
现在是最容易的部分——或者最难的部分,取决于你如何看待它——在你等待你的应用的认证和随后的发布时,监控你的仪表板。提交后,您将收到来自 Windows 应用商店团队的确认电子邮件,让您知道这不仅仅是一个梦。这封电子邮件还提供了到您的仪表板的链接,用于跟踪应用在批准过程中的进度。
这个过程需要六个步骤,估计需要六天的等待时间。如果测试失败,您会通过仪表板和电子邮件得到通知,并允许您在解决错误后重新提交:
The submitted app is run through a series of automated tests. The app proceeds to security testing. (Everyone appreciates downloading secure apps to their Windows 8 devices, after all.) The app is put through a technical compliance test, which, if you had no problems running the WACK tool, should be smooth sailing, because Microsoft is running the WACK tool on its own servers. This completes the automated testing of the app. Your humble creation is sent to an actual tester (what a fun job!) for compatibility testing. Your dashboard provides an estimate in terms of days, but because this is the most subjective stage of the approvals, there can be some variance in this estimation. Once it passes this step, your app is ready to be published. The app receives digital signage and proceeds to the Windows Store servers and on to release in the Windows Store! But, ugh, there is still a potential for post-publication failure during what’s called the Manual Content Compliance test. In this environment, your app is tested for bugs, security, and appropriateness. You’ll receive an e-mail if the app fails this final step, directing you to the report detailing the fail reasons and actions to resolve in the Dev Center.
更新你的应用
希望你的应用会做得很好,随着技术的进步,更新应用功能的需求也会增加。请放心,亲爱的读者,你会收到忠实用户的反馈——也许比你预想的还要多!这种用户反馈可以提供关于可用性问题的有价值的见解,什么对你的用户有用,什么对你的用户没用,以及你的应用的当前版本中可能缺少什么。
当更新版本的开发完成后,您会发现商店更新流程是初始提交流程的简化版本。Windows 应用商店要求提交新的应用包,应用更新也要经过同样的六步验证过程。您可以再次运行 WACK 工具进行您自己的本地测试。幸运的是,提交更新的应用版本并获得批准应该更简单,只要遵守风格指南,减少错误风险和其他潜在问题。
以下是需要做和记住的事情的快速清单:
- 单击更新按钮,在应用页面上启动新的更新。
- 在提交新版本 2.0 之前,请记住在创建应用包向导中创建新版本号。
- 拿起您更新的应用包,回到仪表板。
- 在提交认证之前,我建议对变更或新增功能进行简要描述。更新页面看起来类似于新应用的提交页面,只是大部分信息已经提供了。
- 如果您需要更新关于您的应用的任何信息,从其功能或许可条款到屏幕截图或应用内购买优惠,您可以重新访问相关部分并相应地更新信息。否则,您可以直接进入上传软件包步骤,上传最新版本的应用。上传完成后,转到“详细信息”步骤,填写“更新描述”字段,这是必填字段。
摘要
恭喜你!你不仅完成了这本书,还完成了让你的 Windows 8 应用进入 Windows 应用商店的最后一步。通过阅读这一章,你知道这个过程的步骤是简单明了的;然而,您可能也可以确定,这可能是一个紧张的体验,可能会导致大量的调试或错误修复。但是一旦你走到这一步,不要放弃或沮丧。如果有的话,从你的代码中休息一下,用新鲜的眼光重新审视它。现在,我们来简要回顾一下本章的要点:
- 为您的应用保留名称。此名称保留一年,在此期间,您可以使用此名称发布您的应用。之后,该名称被重新分配到池中。
- 提交您的应用和您在 Windows 应用商店提交过程中使用的工具,包括创建应用包向导、开发中心和您的开发者仪表板。
- 一款名为 Windows 应用认证工具包(WACK)的认证工具,可用于测试你的应用。
- 认证过程,包括自动化测试、人工测试和手动内容合规性测试。
- 通过发布新版本来更新您的应用。这是让你的应用保持相关性,让你的用户满意,并保持应用处于技术进步前沿的一个重要方面。