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

103 阅读1小时+

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

原文:Real World Windows 8 App Development with JavaScript

协议:CC BY-NC-SA 4.0

六、解决打印问题

Abstract

现在你已经了解了所有的魅力和它们的用法和功能,你可以进入它们在现实世界中的使用细节。第五章讨论了设备的魅力。这一章深入探讨了在一个特定的场景中使用这种魅力的更多细节:打印。如前一章所述,当在应用屏幕中启用打印时,用户可以在设备魅力的弹出属性页中看到潜在的打印设备。本章还比较了 Windows 8 中的打印和 Windows 传统应用中的打印。并且查看创建了打印任务的示例。我们开始吧。

现在你已经了解了所有的魅力和它们的用法和功能,你可以进入它们在现实世界中的使用细节。第五章讨论了设备的魅力。这一章深入探讨了在一个特定的场景中使用这种魅力的更多细节:打印。如前一章所述,当在应用屏幕中启用打印时,用户可以在设备魅力的弹出属性页中看到潜在的打印设备。本章还比较了 Windows 8 中的打印和 Windows 传统应用中的打印。并且查看创建了打印任务的示例。我们开始吧。

Windows 8 之前的打印

旧的 Windows 应用——现在称之为桌面应用——使用菜单范例来执行应用命令。这意味着一个应用通常有一个内容区域和一个用户可以通过命令控制内容的区域。这个命令区域就是菜单,它开始只是一个顶级菜单,后来演变成上下文菜单、工具栏和专用于发布命令的浮动窗口。

菜单之所以很棒,有很多原因,其中一个重要的原因是,它们可以让你直接看到一个应用可以执行的所有操作的完整列表。尽管它们是上下文感知的,菜单使用启用/禁用机制来训练用户在任何给定的时间什么能做什么不能做。好处是你知道在某个时候,你可以执行一个动作。

相比之下,上下文菜单和功能区等功能遵循不同的方法。随着应用可以执行的动作数量的增加,将它们全部列在顶部菜单中变得非常困难。在与应用的大多数正常交互过程中,大部分操作都被禁用。在这种情况下,使用上下文菜单只会显示适当的命令,即与特定上下文相关的命令。缺点是这种方法没有向用户宣传功能。

历史上,打印是通过菜单和命令的机制呈现给用户的。用户的打印方法很简单:单击文件菜单,如果菜单选项存在,则选择打印。图 6-1 显示了在我发表我最喜欢的博客作者艾德·博特(CNET)的一篇文章之前,Windows 8 机器上的 Internet Explorer 10 的文件菜单。

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

图 6-1。

The File menu with printing enabled

作为开发桌面的开发人员。NET 应用中,由您决定哪些屏幕包含可打印的内容并需要启用打印功能,哪些屏幕不需要。虽然 IE10 不是一个. NET 应用,但同样的规则也适用。图 6-2 显示了 IE10 的启动屏幕:因为它没有实际内容,所以没有什么可以打印的,因此文件菜单中的打印功能被禁用。

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

图 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 所示。

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

图 6-3。

The Devices charm fly-out before printing is enabled by the application

单击“启用打印”按钮将应用挂接到打印。现在,点击设备图标,调用onprinttaskrequested,显示打印目标列表,如图 6-4 所示。

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

图 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 所示的弹出窗口。您应该看到文档第一页的预览,分页控件允许您根据需要快速导航到其他页面。该弹出菜单还允许您指定要打印的文档的方向,并应用附加选项(通过更多设置链接),如页面布局和纸张质量。

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

图 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的正文中添加更多的文本,直到它结束。

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

图 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_taskoncompleted = function (e)

{

//called when the print has completed

};

print_taskonsubmitting = function (e)

{

//called when the actual print button is hit

//The print activity has been submitted

};

print_taskonprogressing = function (e)

{

//called to give feedback on the progress of the print job

};

print_taskonpreviewing = 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`,该任务定义如何格式化内容以进行打印。 | | `Properties` | 只读 | 检索一组与`PrintTask`相关联的属性。 | | `Source` | 只读 | 返回一个指向应用提供的对象的指针,该对象表示要打印的内容。这个对象必须支持`IPrintDocumentSource`接口。 |

使用Options属性(它公开了PrintTaskOptions对象),应用可以修改打印体验格式的各个方面。表 6-3 强调了该类的关键属性。如您所见,这些映射到可以编程设置的打印机选项。

表 6-3。

PrintTaskOptions Properties

| 财产 | 访问类型 | 描述 | | --- | --- | --- | | `Binding` | 读/写 | 获取或设置打印任务的绑定选项 | | `Collation` | 读/写 | 获取或设置打印任务的排序规则选项 | | `ColorMode` | 读/写 | 获取或设置打印任务的颜色模式选项 | | `DisplayedOptions` | 只读 | 获取为打印体验显示的选项列表 | | `Duplex` | 读/写 | 获取或设置打印任务的双面打印选项 | | `HolePunch` | 读/写 | 获取或设置打印任务的打孔选项 | | `MaxCopies` | 只读 | 获取打印任务支持的最大份数 | | `MediaSize` | 读/写 | 获取或设置打印任务的媒体大小选项 | | `MediaType` | 读/写 | 获取或设置打印任务的媒体类型选项 | | `MinCopies` | 只读 | 获取打印任务允许的最小份数 | | `NumberOfCopies` | 读/写 | 获取或设置打印任务的份数的值 | | `Orientation` | 读/写 | 获取或设置打印任务的方向选项 | | `PrintQuality` | 读/写 | 获取或设置打印任务的打印质量选项 | | `Staple` | 读/写 | 获取或设置打印任务的装订选项 |

由突出显示的属性返回的大多数对象都包含在Windows.Graphics.Printing名称空间中。当谈到打印时,这是挂钩 Windows 8 APIs 向开发人员公开的内容的起点。表 6-4 显示了这个名称空间包含的类型的完整列表。

表 6-4。

Windows.Graphics.Printing Namespace Classes

| 班级 | 描述 | | --- | --- | | `PrintManager` | 通知 Windows 应用希望参与打印。`PrintManager`类也用于以编程方式启动打印。 | | `PrintTask` | 表示打印操作,包括要打印的内容,并提供对描述如何打印内容的信息的访问。 | | `PrintTaskCompletedEventArgs` | 报告打印任务的完成情况。 | | `PrintTaskOptions` | 表示用于管理定义内容打印方式的选项的方法和属性的集合。 | | `PrintTaskProgressingEventArgs` | `PrintTask.Progressing`事件的事件参数。该事件在`PrintTask`的提交阶段(不要与`submitting`事件混淆)引发。在提交文档期间,内容被发送到打印子系统。这项工作的进展在这里被跟踪。 | | `PrintTaskRequest` | 包含系统创建打印任务的请求。在清单 6-9 中,您调用了作为处理`onprinttaskrequested`的一部分返回的`print_event`对象的`request`属性。确切的代码是`print_event.request.createPrintTask("Sample Print Task", function (args)`。这里的`request`属性返回这个类的一个实例。 | | `PrintTaskRequestedEventArgs` | 与`PrintTaskRequest`相关的事件参数。 | | `PrintTaskSourceRequestedArgs` | 与`PrintTaskSourceRequestedHandler`委托相关联的参数。提供将要打印的内容交给`PrintTask`的方法。 |

摘要

恭喜你!现在,您对 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 通知的外观。如你所见,它出现在屏幕的右上角。

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

图 7-1。

Example of a toast notification

无论你在哪里,在操作系统中你在做什么,一个 toast 通知总是出现在右上角,当用户没有与之交互时逐渐淡化。在接收到多个祝酒词的情况下,后续的祝酒词显示在先前呈现的祝酒词下面。图 7-2 显示了多个 toast 通知如何出现在用户的屏幕上。

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

图 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 通知,现在编程为支持图像。

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

图 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时,每个值都可以被使用。

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

图 7-4。

Toast notification handling events

磁贴通知

还记得第一章里关于 Windows 8 的概述讨论吗?它谈到了开始屏幕上的矩形形状,将快速启动区域的功能与应用的启动快捷方式结合起来,并在任务栏上显示应用。正如在第一章中提到的,这些矩形形状被称为活瓷砖。关于它们的“活性”的伟大之处在于,它将这些磁贴变成了应用可以向用户呈现通知的另一个表面。磁贴通知在许多方面不同于 toast 通知。

首先,它们实际上是一种应用行为模式,位于动态磁贴的概念之上。你可能会说它们根本不是通知。许多应用,如 Travel,在不通知用户任何事情的情况下使用了实时磁贴功能(Travel 显示世界上不同地方的图片,这与 Mail 应用对磁贴的使用明显不同)。第二,因为这种通知方式使用给定应用的图块,所以可以通过多种方式对其进行限制。首先,如果磁贴不在 Windows 开始屏幕上,就不会有通知(也不会向应用指示通知没有发生)。另一个问题是这种类型的通知局限于 Windows 开始屏幕;根据磁贴在用户开始屏幕上的位置,它可能不可见。最后也是最重要的,应用的动态磁贴主要是为非侵入式浏览信息而设计的。它不应该是一个对话框或消息框;这意味着向用户提供快速的信息,可能会诱使他们点击磁贴并获得更多信息。

在您学习这一部分的过程中,让我们正确地看待这一功能。因为这里使用的术语通知有些误导(特别是与本节中的对应术语并列使用时),所以您可能会尝试将 live tiles 的使用限制为向用户显示消息。这样做会严重低估这项技术的力量。图 7-5 显示了许多流行的 Windows 8 应用的实时磁贴。

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

图 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 所示的结果。

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

图 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);

};

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

图 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>

汇总模板

最后,如果应用需要以纯文本的列表格式显示摘要信息,TileWideText02TileWideText01是很好的模板。清单 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 所示。

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

图 7-8。

Using TileWideText02

创建更干净的模板

在我看来,工作做得很好,但是审美脱节。如果通知的外观非常重要,那么你可以使用一个更干净的模板,比如TileWideBlockAndText01。该模板在左侧呈现为四个展开的文本字符串,在右侧呈现为一个短的粗体文本字符串上的一大块文本。您丢失了列,但是可以使用左边的自由文本来显示数字和文本,如图 7-9 所示。

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

图 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 显示了你刚刚创建的应用的权限对话框。当你在电脑上运行应用时,你应该会看到这个。

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

图 8-1。

Permissions dialog box for background tasks

如果用户允许访问,您将答案记录为can_run中的true值。注意全局方法getTask,它返回一个给定名称的先前调度的任务。计划任务存储在应用外部的一个结构中,该结构以任务名称为关键字。这允许您引用以前创建的任务,以便取消注册它们。在清单 8-1 中,您首先使用这个功能来搜索任务名称:如果它不存在,您创建它;如果它确实存在,你注销它。

要向后台任务基础设施注册一个定时任务,可以使用BackgroundTaskBuilder。在这个例子中,您正在注册一个定义在类型BackgroundTaskHost.TestTimerTask中的任务。在这种情况下,这是使用另一种 Windows 8 编程语言(C#)创建的类型。因此,在注册希望作为后台任务执行的代码时,必须应用一些额外的规则。在 JavaScript 中,您只需创建一个简单的 JavaScript 文件,称为专用工作器,而在 C#中,您必须使用强类型系统将一段代码标识为后台任务。首先创建一个新类,它的类型必须实现IBackgroundTask,并作为字符串传递给BackgroundTaskBuilderTaskEntryPoint属性。

请注意,您不必为了调试后台任务代码而等待触发条件得到满足。Visual Studio 2012 提供了一个漂亮的调试工具,让您可以在实际的后台任务类中运行代码。

回到 JavaScript 世界,您现在可以创建一个时间触发器,并将其作为参数传递给BackgroundTaskBuildersetTrigger方法。时间触发器是一种使后台任务运行的触发机制。与传统 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,并在BackgroundTaskBuilderTaskEntryPoint属性中引用 worker 的文件名(而不是 C#类的完全限定类型名)。所以在清单 8-1 中,你使用了调用task.taskEntryPoint = "/samples/bgtasksample/timerworker.js",而不是调用task.taskEntryPoint = "BackgroundTaskHost.TestTimerTask";,当然假设你已经将timerworker.js专用工人文件添加到你的项目中。同样,下面几节将进一步讨论这一点。图 8-2 显示了选择了专用工人模板的项目项目选择器。

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

图 8-2。

Selecting the background worker project item type

情况

后台任务也可以有与之相关联的条件。您可以使用BackgroundTaskBuilderaddCondition方法添加这些。应用于后台任务的条件必须全部评估为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

一些系统触发器需要应用在锁定屏幕上:SessionConnectedUserPresentUserAwayControlChannelReset。如果您在应用不在锁定屏幕上的情况下使用这些触发器,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)。它在同一个声明选项卡中,这个选项卡被反复使用,向用户清楚地概述了应用公开的特性。这很好,因为用户最终负责选择使用一个应用还是另一个。

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

图 8-3。

Background task declaration

在图中,入口点表示包含后台任务实现的类的完全限定类型名。它应该用在使用 C#或 C++/Cx 开发后台任务的场景中。如果后台任务是在宿主 JavaScript 应用中定义的,那么起始页应该填充专用工作文件的路径。

Note

因为许多后台任务需要锁屏访问才能运行,所以您还需要在应用 UI 选项卡上指定徽章信息,以便声明正确。图 8-4 显示了应用清单的应用 UI 选项卡的视觉资产部分。为了使图 8-1 中的声明有效,应用必须具有有效的徽章标志,并且必须启用某种形式的锁屏通知。如图所示,如果没有正确配置这些元素,应用将无法正确构建。

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

图 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 进程中执行。