二十六、自动播放、协议激活和打印契约
在这一章中,我将向你展示如何实现另外三个契约来更紧密地将你的应用集成到 Windows 中:自动播放契约、协议激活契约和打印契约。表 26-1 提供了本章的总结。
重温示例应用
对于这一章,我将继续使用我在第二十四章中创建并在第二十五章中扩展的PhotoAlbum
应用。作为一个提醒,这个应用的基本功能让用户选择要在一个简单的相册中显示的图像文件(这只是一系列显示在WinJS.UI.ListView
控件中的缩略图)。
我在此基础上实现了文件激活、保存选取器和打开选取器,以及共享合约,展示了应用集成到操作系统的不同方式——这是我将在本章中通过添加对更多合约的支持来继续的主题。提醒一下,图 26-1 展示了PhotoAlbum
显示图像时的样子。
***图 26-1。*相册示例 app
注意再次声明,我不打算重新列出示例的代码和标记,因为你可以在第二十四章中找到它们,或者从
Apress.com
下载该项目作为源代码包的一部分。
实施自动播放契约
自动播放契约允许您的应用在新存储连接到 Windows 8 设备时自动做出响应。AutoPlay 近年来已经失宠,因为它一直被用作让个人电脑感染恶意软件的手段,但针对 Windows 8 进行了改进,并有可能被更广泛地使用,特别是在用户喜欢简单而不是安全的平板电脑设备上(一般来说,我发现如果有机会,用户会更喜欢任何东西而不是安全)。
像所有的契约一样,实现自动播放是可选的,但却是值得的,尤其是当你的应用以任何方式处理媒体文件的时候。正如你将看到的,自动播放契约建立在我在第二十四章中展示的文件激活契约的基础上,一旦你实现了文件激活,只需要一点额外的工作。
更新清单
自动播放契约需要进行两项清单更改。首先,我需要声明我的应用想要访问可移动存储。为此,从 Visual Studio 的Solution Explorer
窗口中打开package.appxmanifest
文件,点击Capabilities
选项卡,在功能列表中勾选Removable Storage
项,如图图 26-2 所示。
***图 26-1。*在应用清单中启用移动存储功能
此外,我还必须告诉 Windows 我的应用希望如何集成到自动播放功能中,这需要移动到清单的Declarations
选项卡。
从Available Declarations
列表中选择AutoPlay Content
,点击Add
按钮。填写属性部分以匹配图 26-3 ,点击Add New
按钮创建新的Launch action
部分。
***图 26-3。*宣布支持自动播放契约
很难阅读图像中的文本,所以我在表 26-2 中列出了清单表单字段的必需值。
Content event
值是您希望应用得到通知的 Windows 事件的名称。对于我的例子,我对ShowPicturesOnArrival
、MixedContentOnArrival
和StorageOnArrival
事件感兴趣。这些是而不是 JavaScript 事件,声明中的条目作为 Windows 内部和你的应用之间的映射——我将很快向你展示这些事件是如何呈现给你的应用的。确保如我所展示的那样将事件名称大写——如果您采用 JavaScript 全小写约定,您的契约实现将会失败。表 26-3 列出了最常见的 Windows 自动播放事件。
要弄清楚每种事件被触发的环境,需要对 Windows 进行一些研究。我发现在广泛的事件中注册兴趣更容易,这就是为什么如果用户连接包含图像的存储设备,我会为我可以预期的每个事件创建声明。
verb
值是一个特定于应用的字符串,您将使用它来标识 Windows 事件。我的示例应用只能将文件添加到相册中,这就是为什么我的动词是addpictures
、addmixed
和addstorage
,以及为什么Action Display Name
,它是给用户的应用将通过自动播放执行的动作的描述,在每种情况下都被设置为Add to Photo Album
。一个更复杂的应用可能会为每个事件提供多个操作,例如,播放、复制、打印或压缩存储设备上的文件。
响应激活事件
自动播放契约的激活事件的detail.kind
属性被设置为ActivationKind.file
。这与用于文件关联契约的值相同。通过读取detail.verb
属性的值来区分事件相关的契约,对于文件关联,该属性将被设置为open
,对于自动播放,该属性将被设置为您分配给清单声明的verb
字段的值。对于我的例子,这意味着我可以预期文件关联的verb
属性是open
,自动播放契约的属性是addpictures
、addmixed
或addstorage
,这取决于哪个 Windows 事件被触发。
为了响应自动播放的verb
值,我对清单 26-1 中的PhotoAlbum
default.js
文件进行了修改。
清单 26-1 。更新 default.js 文件以支持自动播放
`(function () {
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var appstate = activation.ApplicationExecutionState;
var storage = Windows.Storage;
var query = storage.ApplicationData.current.localFolder.createFolderQuery();
query.addEventListener(“contentschanged”, function () {
App.loadFilesFromCache();
});
query.getFoldersAsync();
app.onactivated = function (args) {
if (args.detail.previousExecutionState != appstate.suspended) {
args.setPromise(WinJS.UI.processAll().then(function () {
if (ViewModel.fileList.length == 0) {
App.loadFilesFromCache();
}
switch (args.detail.kind) {
case activation.ActivationKind.fileOpenPicker:
var pickerUI = args.detail.fileOpenPickerUI;
WinJS.Navigation.navigate(“/pages/openPickerView.html”,
pickerUI);
break;
case activation.ActivationKind.fileSavePicker:
var pickerUI = args.detail.fileSavePickerUI;
WinJS.Navigation.navigate(“/pages/savePickerView.html”,
pickerUI);
break;
** case activation.ActivationKind.file:**
** switch (args.detail.verb) {**
** case ‘addpictures’😗*
** case ‘addmixed’😗*
** case ‘addstorage’😗*
** WinJS.Navigation.navigate(“/pages/autoplayView.html”,
args.detail.files);**
** break;**
** case ‘open’😗*
** args.detail.files.forEach(function (file) {**
** App.processFile(file);**
** });**
** WinJS.Navigation.navigate(“/pages/albumView.html”);**
** break;**
** }**
** break;**
default:
WinJS.Navigation.navigate(“/pages/albumView.html”);
break;
}
}));
}
};
app.start();
})();`
当处理一个file
激活事件时,我现在查看verb
事件属性的值。如果值是open
,我知道我正在处理文件关联契约,我从detail.files
属性为数组中包含的每个文件调用App.processFile
函数,就像我在上一章所做的一样。
当我得到一个其他的verb
值时,我知道我正在处理自动播放契约,并且我通过导航到一个新的内容页面来响应,这个页面是我添加到项目pages
文件夹中的,名为autoPlayView.html
。当调用WinJS.Navigation.navigate
方法时,我传递detail.files
属性的值,这样我就可以在内容文件中使用它(你可以在第七章中了解更多关于这种技术的内容)。您可以在清单 26-2 中看到autoPlayView.html
文件的内容。
清单 26-2 。autoPlayView.html 文件的内容
`
WinJS.UI.Pages.define(“/pages/autoPlayView.html”, {
ready: function (element, folders) {
var list = new WinJS.Binding.List();
apListView.winControl.itemDataSource = list.dataSource;
var addFile = function (file) {
list.push({
img: URL.createObjectURL(file),
title: file.displayName,
file: file
});
};
folders.forEach(function (folder) {
folder.getFilesAsync(storage.Search.CommonFileQuery.orderByName)
.then(function (files) {
files.forEach(addFile);
});
});
addButton.addEventListener(“click”, function (e) {
apListView.winControl.selection.getItems().then(function (items) {
items.forEach(function (item) {
localFolder.createFileAsync(item.data.file.name,
storage.CreationCollisionOption.replaceExisting)
.then(function (newfile) {
item.data.file.copyAndReplaceAsync(newfile)
.then(function () {
App.processFile(newfile);
});
});
});
});
WinJS.Navigation.navigate(“/pages/albumView.html”);
});
}
});
该文件呈现的布局基于一个ListView
控件,在该控件中,我显示我在存储设备上找到的图像,允许用户选择应该导入到相册中的图像。还有一个button
,用户点击它表示他们已经选择了想要的文件。
注意我可以使用一个标准的打开文件选择器,让用户选择他们想要从存储设备导入的文件,但是没有办法禁用选择器导航控件。这意味着用户可以离开存储设备,从任何地方挑选文件。这并不总是所有应用的问题,但我想在这个例子中限制用户的选择,这就是我使用自定义布局的原因。
这个文件中的代码是我在前面章节中使用的技术的复述,并做了一些调整以适应自动播放契约。来自激活事件的detail.files
属性的值作为folders
参数传递给ready
函数——我更改了名称,因为当自动播放契约的事件被触发时,detail.files
属性返回的数组实际上包含了StorageFolder
对象。通常只有一个文件夹,它是可移动存储设备的根目录。我处理数组中的所有项目,只是为了预防可移动设备的不同行为(微软对于是否会有多个文件夹被发送到应用非常含糊。)
为了安全起见,我查询了数组中的每个文件夹,并构建了我在一个WinJS.Binding.List
对象中找到的文件的详细信息,我将它用作ListView
控件的数据源。此时,我不想将存储设备的内容与应用中的其余图像混合在一起,这就是为什么我在本地将List
定义到这个文件中,而不是使用视图模型中的那个。
提示 Windows 将过滤查询返回的文件,因此只有那些匹配自动播放清单声明的文件才会包含在结果中。
当用户点击Add Selected Images
按钮时,我获取所选的ListView
项,并将文件复制到本地 app data 文件夹中。通过复制文件,我可以将图像作为相册的一部分显示,即使自动播放存储设备已断开连接或被移除。
一旦文件操作开始,我就导航到/pages/albumView.html
文件,这样用户就可以看到他们添加的效果。
测试契约执行情况
现在,您已经看到了我是如何实现该契约的,是时候看看它是如何运行的了。首先,启动应用,以便在 Windows 设备上安装最新版本。应用不需要运行来处理自动播放事件,因此如果您愿意,您可以在此时停止或终止应用。
接下来就是准备 Windows 8 的机器了。这不是用户会采取的步骤,但我想展示一个特殊的效果。打开自动播放控制面板,确保勾选了Use AutoPlay for all media and devices
选项,并将Removable drive
选项设置为Ask me every time
,如图 26-4 中所示。
***图 26-4。*配置自动播放测试契约执行
接下来,插入一些包含 JPG 或 PNG 文件的可移动存储设备,如 u 盘或相机存储卡。我在测试中使用了 u 盘,但几乎任何可移动存储设备都可以。你会看到一个弹出的提示信息,就像图 26-5 中的所示。
***图 26-5。*自动播放祝酒词
单击 toast,告诉 Windows 您要在存储设备上执行什么操作。窗口将显示一组选项,如图 26-6 所示。根据您安装的应用,您可能会看到不同的选项。我突出显示了您应该选择的操作,这显示了示例应用和我在前面的清单中指定的消息。
***图 26-6。*选择可移动硬盘的自动播放动作
点击Add to Photo Album
项,Windows 将启动示例应用。激活事件将加载autoPlayView.html
文件作为布局,查询存储设备的文件内容,找到的图像文件在ListView
控件中呈现给用户,如图图 26-7 所示。
***图 26-7。*通过呈现可移动存储设备上的图像来响应自动播放事件
选择图像并点击Add Selected Images
按钮会将图像复制到本地应用数据文件夹,并将布局切换到pages/albumView.html
文件。
测试提示
当您测试 AutoPlay 契约的实现时,插拔存储设备可能会变成一个乏味的过程。你可以做一些事情来简化这个过程。第一个是在自动播放控制面板中为您正在使用的设备类型更改设置,如图图 26-8 所示。您可以看到示例应用包含在应用列表中,当连接新的存储设备时,可以选择该列表作为默认应用。
***图 26-8。*将示例应用设置为可移动驱动器的默认自动播放动作
您可以通过插入硬件一次,然后在每次想要测试时使用文件资源管理器触发自动播放操作,来避免完全处理硬件。在文件浏览器窗口中右键单击设备,在弹出菜单中选择Open AutoPlay
,如图 26-9 所示。
***图 26-3。*无需移除和连接存储设备即可触发自动播放操作
这些技术使测试自动播放契约实现成为一种更加愉快的体验,尤其是当您处理大量不同的事件和动词时。
实现协议激活契约
协议激活契约让你的应用处理标准的 URL 协议,比如mailto
,它用于启动创建和发送电子邮件的过程。该合约还可用于处理自定义协议,这些协议可用于执行应用之间的基本通信,或在您的网站和 Windows 应用之间移交任务(您在网页中嵌入了具有特定协议的链接,当用户单击该链接时,该链接会激活应用)。
在本节中,我将演示如何处理自定义协议,并使用它将数据从一个应用传递到另一个应用。
创建助手应用
首先,我需要创建助手应用,它将向用户呈现使用我的自定义协议的链接。我创建了一个名为ProtocolHelper
的新应用,这个应用非常简单,所有的东西 HTML、CSS 和 JavaScript——都包含在default.html
文件中,如清单 26-3 所示。
清单 26-3 。来自 ProtocolHelper 应用的 default.html 文件
`
这个应用执行对Pictures
库的深度查询,并显示它找到的第一张图像的缩略图。该布局还包含一个a
元素,其href
属性包含一个带有自定义协议的 URL,如下所示:
<a id="linkElem" **href=”photoalbum:C:\Users\adam\Pictures\flowers\astor.jpg”**>Protocol Link</a>
协议设置为photoalbum
,网址的其余部分包含助手应用找到并显示的文件路径。在图 26-10 中可以看到助手应用运行时的样子。
注意为了确保助手应用可以找到文件,您需要检查清单中的
Pictures Library
功能。
***图 26-3。*protocol helper app 的布局
点击Protocol Link
会让 Windows 寻找一个可以处理自定义协议的应用。当然,目前还没有这样的应用,所以你会看到一个类似于图 26-11 所示的消息。在下一节中,我将向您展示如何在PhotoAlbum
应用中添加协议激活支持。
***图 26-11。*没有应用处理 URL 协议时显示的消息
增加协议激活支持
现在我有了一个包含与photoalbum
协议的链接的应用,我可以返回到PhotoAlbum
应用并添加对处理该协议的支持,这意味着当你单击助手应用中的链接时,PhotoAlbum
将被激活并被赋予计算出该做什么的任务。
添加清单声明
第一步是更新清单。从 Visual Studio Solution Explorer
窗口打开package.appxmanifest
文件,并导航到Declarations
选项卡。从Available Declarations
列表中选择Protocol
,点击Add
。将显示文本字段,供您输入想要支持的协议的详细信息。在Name
字段中输入photoalbum
并键入Control+S
保存对清单的更改。(在本例中,您可以忽略Logo
和Display name
字段,它们用于区分支持相同协议的应用,这种情况下不会发生。)你可以在图 26-12 中看到完整的清单部分。
***图 26-12。*宣布支持协议激活契约
响应激活事件
协议激活的激活事件的detail.kind
属性设置为ActivationKind.protocol
。你可以在清单 26-4 中的PhotoAlbum
default.js
文件中看到我是如何回应这个事件的。
清单 26-4 。响应协议激活事件
`(function () {
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var appstate = activation.ApplicationExecutionState;
var storage = Windows.Storage;
var query = storage.ApplicationData.current.localFolder.createFolderQuery();
query.addEventListener(“contentschanged”, function () {
App.loadFilesFromCache();
});
query.getFoldersAsync();
app.onactivated = function (args) {
if (args.detail.previousExecutionState != appstate.suspended) {
args.setPromise(WinJS.UI.processAll().then(function () {
if (ViewModel.fileList.length == 0) {
App.loadFilesFromCache();
}
switch (args.detail.kind) {
** case activation.ActivationKind.protocol:**
** if (args.detail.uri.schemeName == “photoalbum”) {**
** var path = args.detail.uri.path;**
** storage.StorageFile.getFileFromPathAsync(path)**
** .then(function (file) {**
** App.processFile(file);**
** });**
** }**
** break;**
// …statements removed for brevity…
default:
WinJS.Navigation.navigate(“/pages/albumView.html”);
break;
}
}));
}
};
app.start();
})();`
激活事件的detail.uri
属性返回一个Windows.Foundation.Uri
对象。我使用schemeName
属性来确定我的应用是为哪个协议激活的,并使用path
属性来获取 URL 的路径部分,在本例中是ProtocolHelper
应用在Pictures
库中找到的文件的路径。除了schemeName
和path
之外,Windows.Fountation.Uri
对象还有许多有用的属性,我已经在表 26-4 中列出了它们,以及如果使用的 URL 是[
www.apress.com/books/index.html](http://www.apress.com/books/index.html)
时它们将返回的值(一个虚构但有用的 URL 来演示)。
综上所述,您可以看到我通过调用StorageFile.getFileFromPathAsync
方法,使用 URL 的path
组件获得一个StorageFile
对象,从而对photoalbum
协议的激活做出响应。然后我将StorageFile
传递给App.processFile
方法,该方法将文件添加到照片库中。
声明能力
还有一个步骤:在PhotoAlbum
清单中声明应用需要访问Pictures
库。
这不是协议激活的一个要求,但它是让我的应用按照我想要的方式工作所必需的。协议激活的优点是实现起来快速简单,并且已经定义了很多有用的协议,比如mailto
。
缺点是应用之间没有信任转移或清单声明,这意味着协议激活最适合简单数据,或者当您确信两个应用对设备具有相同的访问权限时。对于我的例子,这意味着PhotoAlbum
应用需要能够访问作为协议激活过程的一部分发送的文件,这意味着需要访问Pictures
库(因为,正如您所记得的,激活事件的原因ProtocolHelper
应用在Pictures
库位置找到并显示第一个图像)。
在 Visual Studio 中打开package.appxmanfest
文件,导航到Capabilities
选项卡,并检查Pictures Library Access
功能。对于一个更复杂的方法,你需要使用共享契约,我在第二十五章中描述过。
测试契约实现
要测试对协议激活契约的支持,请启动PhotoAlbum
应用,以便在您的设备上安装最新的更改。与许多其他契约一样,应用不一定要运行才能使契约生效,但是在 Visual Studio 中启动应用的过程会强制 Windows 处理清单并注册应用对契约的支持。如果您愿意,可以在此时终止PhotoAlbum
应用,测试仍将继续。
接下来,启动ProtocolHelper
应用并点击Protocol Link
锚元素。Windows 将向PhotoAlbum
应用发送协议激活事件,这将具有将文件路径从一个应用传递到另一个应用的效果,从而将图像添加到相册中。
执行印刷契约
从应用中打印内容的能力非常重要,尽管与几年前相比,用户打印的次数减少了,在屏幕上查看和阅读的内容增加了。假设你的用户不需要打印并跳过这个契约是很诱人的,但是在做这个决定的时候,你是在强迫用户以一种特殊的方式消费你的内容,并且忽略了他们的偏好。
我发现使用打印机是一件痛苦的事情——尽管在 Windows 应用中相对容易处理——但每当我想跳过对打印的支持时,我都会提醒自己我的图书销售数字。有超过 50%的机会,你正在阅读这本书的印刷本。你可能更喜欢印刷本的原因有很多——也许你喜欢在通勤时在火车上阅读,你喜欢打开书,这样你就可以在屏幕上编写代码时跟随示例,或者你喜欢买一本并与你的团队分享。不管是什么原因,Apress 不会因为让你购买电子书而忽视你的偏好,同样,你也不应该因为忽略打印功能而忽视用户的偏好。在这一节中,我将通过添加对打印来自PhotoAlbum
示例应用的图像的支持来解释 Windows 对打印的支持是如何工作的。
从 Windows 应用打印是基于将应用的当前布局发送到打印机,这意味着很容易获得基本的打印工作,但需要更多的努力才能产生有用的东西。我将首先向您展示基本的打印机制,然后向您展示如何创建仅用于打印的内容。
注正如你所料,你需要一台打印机来跟踪本章的这一节。您不需要实际打印任何东西,但是您需要有一个 Windows 识别为打印机的设备,以便它出现在 Devices Charm 中。
实现基本打印
印刷契约不需要清单声明。相反,您注册一个函数来处理由可以在Windows.Graphics.Printing
名称空间中找到的PrintManager
对象发出的printtaskrequested
事件。此事件表示用户已发起打印请求,您可以相应地准备您的应用。
为了演示契约是如何操作的,我在albumView.html
文件中添加了打印支持,这是PhotoAlbum
应用用来显示它所编目的图像的内容。您可以在清单 26-5 中看到我所做的更改,我将在接下来的章节中介绍我所使用的对象。
注意我在本章这一部分介绍的新对象都在
Windows.Graphics.Printing
名称空间中,除非另有说明。在代码示例中,我将这个名称空间别名化为print
。
清单 26-5 。给 albumView.html 文件添加基本的打印支持
`
WinJS.UI.Pages.define(“/pages/albumView.html”, {
ready: function () {
** print.PrintManager.getForCurrentView()**
** .addEventListener(“printtaskrequested”, function (e) {**
** if (ViewModel.fileList.length > 0) {**
** var printTask = e.request.createPrintTask(**
** “PrintAlbum”, function (printEvent) {**
** printEvent.setSource(**
** MSApp.getHtmlPrintDocumentSource(document));**
** });**
** printTask.options.orientation =**
** print.PrintOrientation.landscape;**
** };**
** });**
WinJS.Utilities.query(“button”).listen(“click”, function (e) {
if (this.id == “openButton”) {
App.pickFiles();
} else {
App.clearCache();
}
});
}
});
当printtaskrequested
事件被触发时,处理函数被传递一个对象,该对象的request
属性返回一个PrintTaskRequest
。您的目标是创建并配置一个PrintTask
对象,设置将要打印的内容并配置打印过程。
创建打印任务
通过调用PrintTaskRequest.createPrintTask
方法创建一个PrintTask
对象。参数是任务的标题和一个处理程序,如果用户选择打印任务,这个处理程序将被调用。这似乎是重复的,但是当您将支持打印契约所需的步骤联系起来时,这是有意义的。为了理解我的意思,启动PhotoAlbum
应用,调出魅力栏并激活设备的魅力(如果你愿意,你可以使用Win+K
直接激活魅力)。
当你激活 Devices Charm 时,Windows 会向你发送printtaskrequested
事件,这是你决定你的应用此刻是否能够打印的机会。你可以在清单 26-6 中看到我是如何处理的,这里我重复了来自printtaskrequested
事件处理程序的部分代码。
清单 26-6 。当接收到 printtaskrequested 事件时,决定是否打印
... print.PrintManager.getForCurrentView().addEventListener("printtaskrequested", function (e) { ** if (ViewModel.fileList.length > 0) {** var printTask = e.request.createPrintTask("PrintAlbum", function (printEvent) { printEvent.setSource(MSApp.getHtmlPrintDocumentSource(document)); }); printTask.options.orientation = print.PrintOrientation.landscape; }; }); ...
如果相册中没有图像,那么我就没有东西可以打印。因此,当我得到printtaskrequested
时,我检查在WinJS.Binding.List
中是否有我用来跟踪应用内容的对象,并且只有当有图像时才调用createPrintTask
方法。
当你激活设备魅力时,你看到的将取决于你之前添加到应用中的图像数量。你可以在图 26-13 中看到两种不同的结果。图中左侧的屏幕向用户显示了可用打印机的列表,并且在应用调用createPrintTask
时显示。图右边的屏幕显示了没有调用createPrintTask
时呈现给用户的消息,表示此时没有要打印的内容。
***图 26-13。*接收到 printtaskrequested 事件时调用 createPrintTask 方法的效果
如图所示,我有几台旧的惠普打印机可供选择,当然,您在列表中看到的会有所不同。
配置打印任务
一旦创建了PrintTask
对象,就可以对其进行配置,为用户提供打印任务的合理初始值。您可以通过返回一个PrintTaskOptions
对象的PrintTask.options
属性来实现。您可以为打印任务配置许多选项,但我不打算在此列出。首先,它们中的许多是大多数应用不会关心的小众设置,其次,用户可以在打印过程的下一阶段设置它们(我将很快谈到)。
相反,我在表 26-5 中列出了四个配置选项的示例。其中两个在很多情况下都很有用,另外两个说明了您对打印任务的控制级别。
许多配置选项采用由Windows.Graphics.Printing
名称空间中的对象定义的值。因此,例如,orientation
属性被设置为由PrintOrientation
对象定义的值之一,这可以在表 26-6 中看到。
我不想让你觉得我没有必要跳过这一部分。我想谈谈打印契约的核心内容,即准备您的应用内容,以便您获得良好的打印结果。你可以在打印任务上设置太多的细节,如果我把所有的细节都列出来,我会没有空间,而且这些设置中的大部分从来没有被使用过。相反,你可以看到我是如何应用清单 26-7 中一个真正有用的设置选项的,在这里我设置了打印任务的方向。
清单 26-7 。设置打印任务的方向
... print.PrintManager.getForCurrentView().addEventListener("printtaskrequested", function (e) { if (ViewModel.fileList.length > 0) { var printTask = e.request.createPrintTask("PrintAlbum",
function (printEvent) { printEvent.setSource(MSApp.getHtmlPrintDocumentSource(document)); } ); ** printTask.options.orientation = print.PrintOrientation.landscape;** }; }); ...
注意我在这里走了一条捷径——这是我在真正的应用中不推荐的。如果你查看表 26-6 中的值,你会发现它们对应于我在第六章中向你展示的方向。如您所见,Windows 应用打印通过打印应用的布局来工作,这意味着您通常需要设置打印任务的方向以匹配设备的方向。
指定要打印的内容
当您创建一个PrintTask
时,您指定了一个当用户从 Device Charm 中选择一个设备时将被调用的函数。这个函数被传递了一个定义了setSource
方法的PrintTaskSourceRequestedArgs
对象。
setSource
方法用于指定将要打印的内容,对于 JavaScript Windows 应用,这意味着你必须使用MSApp.getHtmlPrintDocumentSource
方法,如清单 26-8 中突出显示的,我在这里重复了PhotoAlbum
应用的语句。
清单 26-8 。设置打印源
... print.PrintManager.getForCurrentView().addEventListener("printtaskrequested", function (e) { if (ViewModel.fileList.length > 0) { var printTask = e.request.createPrintTask("PrintAlbum", function (printEvent) { ** printEvent.setSource(MSApp.getHtmlPrintDocumentSource(document));** } ); printTask.options.orientation = print.PrintOrientation.landscape; }; }); ...
getHtmlPrintDocumentSource
方法只接受一个 DOM Document
对象,这意味着你只能打印应用的当前内容或者一个iframe
元素的内容。这意味着您必须有创造性地为文档打印一些有用的东西,这是我稍后将返回的主题。
但是,首先让我们用应用中的默认内容完成打印过程。启动应用,确保相册中有一些图像,并激活设备的魅力。单击列表中的一台打印机将触发我传递给createPrintTask
方法的函数,该函数将设置打印任务的源——Windows 获取这些信息并呈现给用户,如我在图 26-14 中所示。
***图 26-14。*完成打印任务
Windows 向用户显示了内容的预览,以及更改打印任务设置的机会(More settings
链接允许用户查看和更改我前面提到的更神秘的配置选项)。如果你继续打印这个文档,你会得到如图图 26-15 所示的结果。(我使用保存图像的打印机驱动程序捕捉到了这一点,这使我可以向您显示结果,而不必打印到纸上,然后再次扫描页面。)
***图 26-15。*打印输出
我添加了此图所示的边框,因为 Windows 所做的更改之一是更改打印任务中的背景颜色,以便打印图像时不会消耗掉用户所有的墨水/碳粉。
这是我所做的唯一更改,否则,打印输出将与创建打印任务时应用的布局相匹配。这是好的,因为这意味着在应用中支持基本的打印很简单,这也是坏的,因为这意味着布局中的所有东西——包括按钮和滚动条——都被发送到打印机。在接下来的部分中,我将向您展示控制发送到打印机的内容的不同技术。
操作应用布局进行打印
改善打印效果的第一种方法是临时操作专门用于打印作业的应用布局。你可以使用 CSS,当然也可以使用 JavaScript,我会在本章的这一节向你展示这两种方法。
使用 CSS 操作应用布局
CSS 的一个鲜为人知且不常使用的功能是能够创建仅适用于特定类型媒体的样式。这意味着我可以轻松地向我的albumView.html
文档添加一个style
元素,在打印时改变内容的布局。你可以在清单 26-9 中看到一个简单的例子,在这里我创建了一个样式,隐藏了Open
和Clear
按钮,并为打印执行了一些其他小的布局调整。
清单 26-9 。打印时使用 CSS 样式化元素
`…
** **这项技术的关键是将media
属性添加到style
元素中,并将值设置为print
。这可确保仅在打印布局时应用样式,允许您调整应用布局以改善打印结果。你可以在图 26-16 中看到的变化。(我又一次给这个图加了边框。)
***图 26-16。*使用 CSS 改变打印布局的样式
正如您在图中看到的,我隐藏了按钮,并更改了为每个图像显示的标签的格式。我在这个例子中定义的样式应用了相对较小的变化,但是效果可以像你喜欢的那样广泛。
使用 JavaScript 操作应用布局
通过使用 JavaScript 来改变应用的布局,您可以做出更深刻的改变,尽管这比使用 CSS 需要更多的努力。特别是,您需要考虑一些 UI 控件的方式,包括我在示例应用中使用的ListView
,依赖于使用事件向 UI 控件传递更改的数据源。作为示范,我修改了PhotoAlbum
应用,这样每行最多打印两幅图像,避免了你在图 26-16 中看到的问题,每行的第三幅图像只是部分可见。首先,我在js/app.js
文件中创建了一个名为adaptLayout
的新函数,它切换作为ListView
UI 控件数据源的WinJS.Binding.List
对象中的元素数量。你可以在清单 26-10 中看到这个函数。
清单 26-10 。app.js 文件中的 adaptLayout 函数
`(function () {
var storage = Windows.Storage;
var access = storage.AccessCache;
var cache = access.StorageApplicationPermissions.futureAccessList;
var pickers = storage.Pickers;
var dataCache = [];
WinJS.Namespace.define(“App”, {
** adaptLayout: function (prepareForPrint) {**
** var flist = ViewModel.fileList;
if (prepareForPrint == true) {**
** dataCache = flist.splice(4, flist.length -4);**
** } else {**
** dataCache.forEach(function (item) {**
** flist.push(item);**
** });**
** dataCache.length = 0;**
** }**
** },**
// …other functions removed for brevity…
});
})();`
如果使用true
作为参数调用该函数,则List
中的项目数量将减少到 4 个。当使用参数false
再次调用该函数时,这些项将被删除,从而将布局恢复到之前的状态。
我还更新了albumView.html
文件中的script
元素来使用这个函数进行打印,如清单 26-11 所示。
清单 26-11 。使用 JavaScript 改变打印布局
`…
…`
当我的PrintTask
被用户激活时,我调用App.adaptLayout
函数。我遇到的问题是,adaptLayout
函数对List
对象进行了更改,该对象使用事件将这些更改与ListView
UI 控件进行了通信。那些事件将在我的函数被执行后被执行,这意味着我需要延迟用setSource
方法将我的应用布局传递到窗口,直到那些事件被处理后,这就是我使用setImmediate
方法的原因(它推迟了工作的执行,我在第九章中对此进行了详细描述):
... var printTask = e.request.createPrintTask("PrintAlbum", function (printEvent) { var deferral = printEvent.getDeferral(); App.adaptLayout(true); ** setImmediate(function() {** ** printEvent.setSource(MSApp.getHtmlPrintDocumentSource(document));** deferral.complete(); ** })** }) ...
因为我推迟了对setSource
方法的调用,所以在我的函数执行完成之前,我不能给 Windows 打印的内容。幸运的是,PrintTaskSourceRequestedArgs
对象定义了一个getDeferral
方法,该方法返回一个对象,当我异步设置内容时,我可以调用该对象的complete
方法。(你可以在第十九章中了解更多关于延期的内容,在那里我解释了它们在生命周期事件中的用法。)结果是我修改了List
的内容,然后推迟了对setSource
方法的调用,直到这些更改反映在ListView
控件中。
与 CSS 技术不同,使用 JavaScript 会以用户可以看到的方式影响应用的布局,这意味着当打印任务完成或被用户取消时,将布局恢复到原始状态非常重要。PrintTask
对象定义了一些有用的事件,我已经在表 26-7 中描述过,这些事件可以用来跟踪打印进度。
为了恢复布局,我对completed
事件感兴趣,我通过调用App.adaptLayout
函数来响应该事件。我已经提供了这个函数作为addEventListener
方法的一个参数,这意味着它将被传递给 event 对象,该对象具有恢复布局的效果(因为除了 Boolean true 之外的任何值都被作为将图像恢复到List
的请求):
... printTask.addEventListener("**completed**", App.adaptLayout); ...
您可以在图 26-17 中的打印结果上看到这些变化的结果。
***图 26-17。*使用 JavaScript 改变打印应用的布局
创建特定打印内容
您可以通过调整应用的现有布局来改善打印效果,但要完全控制打印过程,您需要创建仅用于打印的内容。为了演示这种方法,我在albumView.html
文件的script
元素中添加了一个新特性,当用户在激活设备的魅力之前选择了ListView
控件中的一个项目时,该特性会显示特定于打印的内容。你可以在清单 26-12 中看到支持这个特性的变化。
清单 26-12 。添加对特定打印内容的支持
`…
…`
如果在ListView
中选择了一个项目,那么我调用导航 API 来显示我添加到pages
文件夹中的名为printView.html
的新页面。我将一个对象传递给navigate
方法,该方法包含对PrintTaskSourceRequestedArgs
对象的引用、由getDeferral
方法返回的对象以及用户选择的来自ListView
数据源的项目。这些细节将可用于我在printView.html
文件的script
元素中定义的ready
函数,你可以在清单 26-13 中看到。
注意我为
PrintTask.completed
事件注册了一个处理程序,当打印任务完成或取消时,它将应用导航回 albumView.html 页面。这样做的一个副作用是,我不得不改变对由PrintManager
对象发出的printtaskrequested
事件的兴趣注册方式。如果您试图使用addEventListener
方法为printtaskrequested
事件添加第二个侦听器,则会抛出异常,并且由于当应用导航回albumView.html
文件时会执行ready
函数中的代码,因此我需要通过向onprinttaskrequested
属性分配一个新函数来替换现有的侦听器——这确保了最多只有一个侦听器,并且不会遇到异常。
清单 26-13 。printView.html 文件的内容
`
imgElem.src = URL.createObjectURL(details.item.data.file);
imgTitle.innerText = details.item.data.file.displayName;
details.event.setSource(MSApp.getHtmlPrintDocumentSource(document));
details.deferral.complete();
}
});
这个文件包含一个非常简单的布局,显示选中的图像及其名称。script
元素中的代码使用从ListView
数据源中选择的项目来配置布局中的元素,然后调用setSource
方法来为 Windows 提供要打印的内容。最后,调用延迟对象的complete
方法,向 Windows 指示异步任务已经完成,可以向用户显示内容预览。你可以在图 26-18 中看到该文件产生的打印结果。
***图 26-18。*使用专用内容进行打印
从图中可以看出,只需稍加努力,您就可以创建专门用于打印的内容,并且不会受到试图调整主要用于在屏幕上显示的布局的限制。
总结
在这一章中,我已经向您展示了如何实现另外三个契约:自动播放、协议激活和打印。这三者在提供一流的应用体验方面都有自己的位置,你应该考虑实现它们,以便将你的应用更紧密地集成到更广泛的 Windows 体验中。在下一章中,我将向您展示如何控制在 Windows 开始屏幕上为您的应用创建的磁贴。
二十七、使用应用切片
在第四章中,我向你展示了如何设置一个应用在 Windows 开始屏幕上使用的图片。这是磁贴的基本操作,但应用可以更进一步,创建实时磁贴,即使在应用不运行时,也可以通过开始屏幕显示有用的信息。在本节中,我将向您展示如何创建动态切片,以及通过应用更新动态切片的不同方式。
使用动态磁贴有两个基本原因:因为您希望用户更频繁地运行您的应用,或者因为您希望用户不那么频繁地运行您的应用。如果你有一个旨在吸引用户注意力的应用,那么你希望将用户的目光吸引到你的应用磁贴上,并提醒他们你在那里。这对于游戏来说是真实的,例如,你想吸引用户并提醒他们你的游戏比他们通过开始菜单想要做的更有趣或更令人兴奋。在这些情况下,你有责任创建吸引人但不分散注意力的磁贴——这符合你的利益,因为 Windows 8 允许用户禁用令人讨厌的应用的动态磁贴。Windows Store 中的一些早期应用有动态磁贴设计,它们的风格非常激进,以至于我发现自己在移动磁贴,这样我就看不到它们了。
如果你有一个旨在提高用户生产力或工作生活的应用,那么你希望使用磁贴为用户提供关键信息的及时摘要,以便他们可以轻松获得关键事实*,而无需*启动你的应用,等待初始化,导航到正确的部分,等等。简而言之,你在不启动你的应用的情况下,通过给用户提供他们需要的信息来帮助他们。这需要仔细考虑用户关心什么,并为用户提供改变显示在磁贴上的信息种类的方法。
对于这类 app 来说,你的责任就是创建一个内容及时、明显、准确的磁贴,也就是说当 app 状态发生变化的时候更新磁贴。
我对实时应用磁贴的看法和我对 UI 动画的看法是一样的:如果少用的话,它们是个好东西,但是很快就会变得烦人和分散注意力(你应该总是提供一种方法来禁用它们)。不要误判你的应用对用户的重要性,不要把他们的开始屏幕变成维加斯老虎机。表 1 提供了本章的总结。
为本章创建示例
我为这一章创建了一个名为LiveTiles
的新 Visual Studio 项目。起点是非常基本的,因为我只需要布局包含按钮,将执行不同种类的瓷砖更新,从一个基本的实时瓷砖开始。清单 1 显示了应用的default.html
文件,其中包含一个按钮。我将在本章中添加新的元素,并演示不同的技术。
清单 1。来自 LiveTiles 项目的 default.html 文件
`
这个示例应用的 CSS 同样简单,您可以在清单 2 的中看到/css/default.css
文件的内容。这个 CSS 中没有新的技术,只是将 HTML 中的按钮放在屏幕的中央。
清单 2。/css/default.css 文件的内容
body { display: -ms-flexbox;-ms-flex-direction: row;-ms-flex-align: stretch; -ms-flex-pack: center;} #container { display: -ms-flexbox; -ms-flex-direction: column; -ms-flex-align: stretch; -ms-flex-pack: center;} #container button {font-size: 30pt; width: 400px; margin: 10px;}
你可以在图 1 中看到 app 的初始布局。正如承诺的那样,它非常简单,我将在本章的后面添加额外的按钮来演示其他特性。
***图 1。*示例 app 的初始布局
定义 JavaScript
如清单 3 所示,我从一个简单的default.js
文件开始。这个 app 全是磁贴,我没有任何后台工作或者 app 状态要担心。我甚至不必像往常一样调用WinJS.Binding.processAll
方法,因为我没有视图模型或任何要处理的数据绑定。
清单 3。一个简单的 Default.js 文件
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var $ = WinJS.Utilities.query;
WinJS.strictProcessing();
var textMessages = [“Today: Pick up groceries”,
“Tomorrow: Oil change”,
“Wed: Book vacation”,
“Thu: Renew insurance”];
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
$(“#container > button”).listen(“click”, function (e) {
** switch (this.id) {**
** case “basicTile”😗*
** // TODO - code for tile goes here**
** break;**
** }**
** });**
}));
}
};
app.start();
})();`
我在文档中定位button
元素并监听click
事件,使用被点击按钮的id
值计算出在每种情况下我需要做什么。目前布局中只有一个按钮,但我会添加更多。
设置平铺图像
因为这是关于 tiles 的一章,所以我在 Visual Studio 项目的images
文件夹中添加了文件,这样我就可以创建一个基本的静态 tile。这些文件被称为tile30.png
、tile150.png
和tile310.png
,你可以在图 2 的清单的Application UI
部分看到我是如何应用这些图像的。
***图二。*为应用磁贴设置图像
提示当您使用磁贴并更改应用使用的设置时,您可能会发现磁贴图标不会显示在开始屏幕上。我发现右键点击应用,点击应用栏上的
Uninstall
,重新启动应用,往往就能解决问题。有时应用磁贴根本不显示——在这种情况下,键入应用名称的前几个字母来执行搜索,然后按 escape 键返回主开始屏幕;瓷砖通常会出现。如果所有这些都失败了,从模拟器和开发机器上卸载应用,重新启动,并在不启动模拟器的情况下从 Visual Studio 启动应用。
测试示例应用
目前没有太多的功能,但是如果您从 Visual Studio 启动应用,然后切换到开始屏幕,您将能够看到示例应用的静态磁贴。您可以使用Larger
和Smaller
AppBar 命令在正常和宽按钮配置之间切换,您可以在图 3 中看到。
***图三。*示例 app 的方形宽静态瓷砖
如图所示,我添加到项目中并在清单中应用的图像显示了一个警铃;我为示例应用选择了这张图片,因为它将像一个提醒程序一样创建磁贴更新。我不打算创建提醒逻辑,但我需要一个更新和提醒是理想的主题。
创建动态磁贴
创建动态切片的基本原则包括三个步骤:
- 选择一个 XML 模板。
- 用您的数据填充模板。
- 将填充的 XML 传递给 Windows 以更新图块。
完成所有这些工作的 API 相当笨拙,但是这种笨拙可以相当简单地用助手函数来包装,一旦您启动并运行,这个过程就变得相对容易了。首先,我将创建基本类型的 live tile,它只包含文本信息,然后再创建更复杂的替代方案。清单 4 展示了当点击应用布局中的button
时对default.js
文件的修改,它创建了一个动态磁贴。
清单 4。创建实时互动程序
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
** var wnote = Windows.UI.Notifications;**
WinJS.strictProcessing();
var textMessages = [“Today: Pick up groceries”,
“Tomorrow: Oil change”,
“Wed: Book vacation”,
“Thu: Renew insurance”];
** function getTemplateContent(template) {**
** return wnote.TileUpdateManager.getTemplateContent(template);**
** }**
** function populateTemplateText(xml, values) {**
** var textNodes = xml.getElementsByTagName(“text”);**
** var count = Math.min(textNodes.length, values.length);**
** for (var i = 0; i < count; i++) {**
** textNodes[i].innerText = values[i];**
** }**
** return xml;**
** }**
** function updateTile(xml) {**
** var notification = new wnote.TileNotification(xml);**
** var updater = wnote.TileUpdateManager.createTileUpdaterForApplication();**
** updater.update(notification);**
** }**
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “basicTile”:
** var template = wnote.TileTemplateType.tileSquareText03;**
** var xml = getTemplateContent(template);**
** updateTile(populateTemplateText(xml, textMessages));**
break;
}
});
}));
}
};
app.start();
})();`
我将工作分成了三个助手函数,可以在后面的例子中使用,而不必直接使用Windows.UI.Notifications
名称空间,在那里可以找到与 tile 相关的功能。我将在接下来的部分中分解这个过程。
获取模板内容
可用于实时图块的模板集合由Windows.UI.Notifications.TileTemplateType
枚举定义。有 45 种不同类型的模板可用,它们提供不同的大小(用于正方形和宽瓷砖)和不同数量的文本,有或没有图像。如果你看一下TileTemplateType
(在[
msdn.microsoft.com/en-us/library/windows/apps/windows.ui.notifications.tiletemplatetype.aspx](http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.notifications.tiletemplatetype.aspx)
),的 API 文档,你会看到每个不同模板的例子。我将从TileTemplateType.tileSquareText03
模板开始,这是一个显示四行文本的纯文本模板。您可以在清单 5 中看到这个模板的 XML。
清单 5。tileSquareText03 模板的 XML
<tile> <visual> <binding template="TileSquareText03"> <text id="1"></text> <text id="2"></text> <text id="3"></text> <text id="4"></text> </binding> </visual> </tile>
目标是设置每个text
元素的内容——我将使用我在/js/default.js
文件的textMessages
数组中定义的四个字符串,它们代表我的假提醒应用即将发出的提醒。
获取 XML 模板的内容需要将一个值从TileTemplateType
枚举传递给由Windows.UI.Notifications.TileUpdateManager
定义的getTemplateContent
方法。在这个区域中有一些相当长的名称空间和对象名,所以我为Windows.UI.Notifications
名称空间定义了一个别名,并将获取 XML 内容的调用放入getTemplateContent
助手函数中。
getTemplateContent
方法返回一个Windows.Data.Xml.Dom.XmlDocument
对象,为 XML 内容提供 DOM 操作。操作 XML 与处理 HTML 内容非常相似,尽管您将在下一节中看到,在处理动态切片时,您不必进行太多的操作。
提示除了
Windows.Data.Xml.Dom
名称空间,你会发现应用也可以使用Windows.Data.Html
和Windows.Data.Json
名称空间。它们提供了用于处理 HTML 和 JSON 内容的对象,如果您想要在当前 DOM 之外处理 HTML,或者想要超越 Internet Explorer 10 中可用的基本 JSON 支持,它们会非常有用。
推广 xml 文本元素的内容
获得 XML 模板后,我现在需要填充text
元素的内容。我已经定义了populateTemplateText
函数,它接受一个模板和一个字符串值数组,并使用innerText
属性设置文本元素的内容。
尽管模板中的text
元素有id
属性,但是XmlDocument.getElementById
方法不能正常工作,所以下一个最好的选择是使用getElementsByTagName
方法定位所有的text
元素。这给了我一组按照它们在 XML 文档中出现的顺序排列的text
元素,我依靠这种顺序使用innerText
属性设置text
元素的内容,就像我处理 HTML 元素一样。结果是一个填充的 XML 模板,如清单 6 所示。
清单 6。填充的 XML 模板
<tile> <visual> <binding template="TileSquareText03"> <text id="1">**Today: Pick up groceries**</text> <text id="2">**Tomorrow: Oil change**</text> <text id="3">**Wed: Book vacation**</text> <text id="4">**Thu: Renew insurance**</text> </binding> </visual> </tile>
更新磁贴
最后一步是通过将填充的 XML 传递给系统来更新图块,这是通过一系列笨拙的 API 调用来完成的,如清单 7 中的所示,它重复了几页前的updateTile
助手函数。
清单 7。用填充的 XML 更新图块
... function updateTile(xml) { var notification = new wnote.TileNotification(xml); var updater = wnote.TileUpdateManager.createTileUpdaterForApplication(); updater.update(notification); } ...
首先,我需要创建一个TileNotification
对象,将填充的 XML 作为构造函数参数传入。对于一个简单的磁贴,你只需要创建对象,但是我将在本章的后面向你展示如何为不同的场景配置它。接下来,我通过调用TileUpdateManager.createTileUpdaterForApplication
方法创建一个TileUpdater
对象。TileUpdater
对象提供了一系列更新图块的不同方法。目前,我只是使用了最基本的方法,即调用update
方法,传入上一步创建的TileNotification
对象。稍后我将向您展示更复杂的安排。结果是当你点击布局中的button
时,静态磁贴变成活动的,显示我的假约会数据,如图图 4 所示。
警告Visual Studio 模拟器不支持实时图块。要测试 live tiles,你必须使用真正的 Windows 8 设备。
***图 4。*一个基本活瓦
您可以看到,小图标用于实时磁贴更新,以帮助用户识别与磁贴相关的应用。你还可以看到,我所做的更新并不是特别有用——在一个正方形的磁贴中没有太多的空间,当依赖文本时,很难向用户提供有意义的信息。
提示你不能指望用户看到你的磁贴更新。首先,您的应用磁贴可能不在最初显示的开始屏幕上,用户可能不会滚动它以使它变得可见。其次,用户可以使用开始屏幕应用栏禁用实时磁贴。这意味着您应该使用实时磁贴来显示应用本身也提供的信息,而不是将磁贴视为核心应用功能的一部分。
创建更有用的实时互动程序
我的基础磁贴更新还有一个问题,就是不影响宽磁贴。为了解决缺乏实用性的问题并支持所有的图块格式,我将采用不同的方法,如清单 8 所示,它强调了我对default.js
文件所做的添加,以一起更新窄和宽图块格式。
清单 8。创建更有用的磁贴更新
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var wnote = Windows.UI.Notifications;
WinJS.strictProcessing();
var textMessages = [“Today: Pick up groceries”,
“Tomorrow: Oil change”,“Wed: Book vacation”,
“Thu: Renew insurance”, “Sat: BBQ”];
function getTemplateContent(template) {
return wnote.TileUpdateManager.getTemplateContent(template);
}
function populateTemplateText(xml, values) {
var textNodes = xml.getElementsByTagName(“text”);
var count = Math.min(textNodes.length, values.length);
for (var i = 0; i < count; i++) {
textNodes[i].innerText = values[i];
}
return xml;
}
function updateTile(xml) {
var notification = new wnote.TileNotification(xml);
var updater = wnote.TileUpdateManager.createTileUpdaterForApplication();
updater.update(notification);
}
** function combineXML(firstXml, secondXML) {**
** var wideBindingElement = secondXML.getElementsByTagName(“binding”)[0];**
** var importedNode = firstXml.importNode(wideBindingElement, true);**
** var squareVisualElement = firstXml.getElementsByTagName(“visual”)[0];**
** squareVisualElement.appendChild(importedNode);**
** return firstXml;**
** }**
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “basicTile”:
var squareTemplate =
** wnote.TileTemplateType.tileSquareBlock;**
** var squareXML = populateTemplateText(**
** getTemplateContent (squareTemplate),**
** [textMessages.length, “Reminders”]);**
** var wideTemplate =**
** wnote.TileTemplateType.tileWideBlockAndText01;**
** var wideData = textMessages.slice(0, 4)**
** wideData.push(textMessages.length, “Reminders”);**
** var wideXml = populateTemplateText(**
** getTemplateContent(wideTemplate),wideData);**
** updateTile(combineXML(squareXML, wideXml));**
break;
}
});
}));
}
};
app.start();
})();`
要执行影响方形和宽瓷砖的瓷砖更新,您需要选择并填充两个不同的模板,并组合它们的内容。为了使 square 更新更具可读性,我使用了一个不同的模板tileSquareBlock
,它有两个文本元素——一个大的显示在一个小的上面(我通过查看用于TileTemplateType
枚举的 API 文档选择了该模板,其中包含每个模板将如何出现的图片)。图 5 显示了微软为该模板提供的描述和图片。
***图 5。*tileSquareBlock 模板的描述
我不会显示单个提醒的详细信息,因为正如上一个演示所示,在一个狭窄的磁贴上没有空间,所以我调用带有摘要信息的populateTemplateText
方法,如下所示:
... var squareXML = populateTemplateText(getTemplateContent (squareTemplate), **[textMessages.length, "Reminders"]**); ....
模板中text
元素的顺序与它们在模板中出现的顺序相匹配,这使得为模板设置内容非常简单。对于宽磁贴,我选择了tileWideBlockAndText01
模板,你可以在图 6 中看到微软是如何描述的。
***图六。*tilewideblockandtext 01 模板的描述
您可以在清单 9 中看到这个模板的 XML。同样,文本元素的顺序与它们的显示顺序一致,因此前四个text
元素对应于平铺左侧的文本,后两个对应于右侧的文本。
清单 9。宽图块模板的 XML
<tile> <visual>
<binding template="TileWideBlockAndText01"> <text id="1"></text> <text id="2"></text> <text id="3"></text> <text id="4"></text> <text id="5"></text> <text id="6"></text> </binding> </visual> </tile>
我通过从数据数组中复制前四项并将两个新项推入数组来填充模板。在一个真实的项目中,你需要考虑数据项比text
元素少,但是为了简单起见,我跳过了这个细节。下一步是将两个模板组合在一起,形成单个更新的基础。这需要对 XML 进行一些操作——我在 wide 模板的 XML 中找到了binding
元素,并将其插入到 square 模板的 XML 中,产生了如清单 10 所示的 XML 组合片段。
清单 10。组合 XML 片段以更新不同的图块大小
<tile> <visual> <binding template="TileSquareBlock"> <text id="1">5</text> <text id="2">Reminders</text> </binding> <binding template="TileWideBlockAndText01"> <text id="1">Today: Pick up groceries</text> <text id="2">Tomorrow: Oil change</text> <text id="3">Wed: Book vacation</text> <text id="4">Thu: Renew insurance</text> <text id="5">5</text> <text id="6">Reminders</text> </binding> </visual> </tile>
将 XML 传递给 Windows 也是以同样的方式完成的,并且更新两种大小的图块。你可以在图 7 的中看到结果。要查看实时磁贴,您需要重启示例应用,点击应用布局中的基本磁贴按钮,然后切换到Start
屏幕。您可以通过选择图块并从开始屏幕应用栏中选择Larger
或Smaller
按钮来切换图块尺寸。
***图 7。*示例应用的方形和宽瓷砖更新
为你的应用选择正确的模板至关重要。你需要找到一种向用户传达信息的方式,这种方式是有帮助的,并且(可选地)会鼓励他们打开和使用你的应用。
使用图像模板
并非所有的应用都受益于在磁贴中只显示文本。为此,有显示图像或图像和文本混合的模板。还有 peek 模板,在两种显示之间交替,通常是所有图像,然后是文本或文本和图像的混合。清单 11 显示了tileSquarePeekImageAndText02
模板的 XML,它包含了text
和image
元素的混合。
清单 11。混合文本和图像模板的 XML
<tile> <visual> <binding template="TileSquarePeekImageAndText02"> ** <image id="1" src=""/>** ** <text id="1"></text>** </binding> </visual> </tile>
你可以看到我是如何构建一个小的帮助函数库来处理瓷砖的,我需要添加对处理image
元素的支持。您可以在清单 12 中看到我对default.js
文件所做的更改。
清单 12。添加对填充图像元素的支持
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var wnote = Windows.UI.Notifications;
WinJS.strictProcessing();
var textMessages = [“Today: Pick up groceries”, “Tomorrow: Oil change”,
“Wed: Book vacation”, “Thu: Renew insurance”, “Sat: BBQ”];
** var images = img/lily.png",img/astor.png",img/carnation.png",**
** img/daffodil.png",img/snowdrop.png"];**
function getTemplateContent(template) {
return wnote.TileUpdateManager.getTemplateContent(template);
}
** function populateTemplate(xml, textValues, imgValues) {**
** if (textValues) {**
** var textNodes = xml.getElementsByTagName(“text”);**
** var count = Math.min(textNodes.length, textValues.length);**
** for (var i = 0; i < count; i++) {**
** textNodes[i].innerText = textValues[i];
}**
** }**
** if (imgValues) {**
** var imgNodes = xml.getElementsByTagName(“image”);**
** var count = Math.min(imgNodes.length, imgValues.length);**
** for (var i = 0; i < count; i++) {**
** imgNodes[i].attributes.getNamedItem(“src”).innerText = imgValues[i]**
** }**
** }**
** return xml;**
** }**
function updateTile(xml) {
var notification = new wnote.TileNotification(xml);
var updater = wnote.TileUpdateManager.createTileUpdaterForApplication();
updater.update(notification);
}
function combineXML(firstXml, secondXML) {
var wideBindingElement = secondXML.getElementsByTagName(“binding”)[0];
var importedNode = firstXml.importNode(wideBindingElement, true);
var squareVisualElement = firstXml.getElementsByTagName(“visual”)[0];
squareVisualElement.appendChild(importedNode);
return firstXml;
}
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “basicTile”:
var squareTemplate =
** wnote.TileTemplateType.tileSquarePeekImageAndText02;**
** var squareXML =**
** populateTemplate(getTemplateContent(squareTemplate),**
** [textMessages.length, “Reminders”], images);**
** var wideTemplate =**
** wnote.TileTemplateType.tileWidePeekImageCollection02;**
** var wideData = textMessages.slice(0, 4)**
** wideData.unshift(textMessages.length + " Reminders");**
** var wideXml =**
** populateTemplate(getTemplateContent(wideTemplate),**
** wideData, images);**
** updateTile(combineXML(squareXML, wideXml));**
break;
}
});
}));
}
};
app.start();
})();`
我已经用populateTemplate
替换了populateTemplateText
函数,它既处理text
又处理image
元素。为了演示图像的使用,我在 Visual Studio 项目的images
文件夹中添加了一些文件。我使用了前几章的花卉图片,你可以在图 8 的解决方案浏览器中看到我添加的内容。您可以在apress.com
从本书附带的源代码下载中获得这些图像,或者使用您自己的图像(在这种情况下,您需要更改/js/default.js
文件中的文件名)。
***图 8。*向 Visual Studio 项目添加图像文件
提示注意,我通过
attributes
属性设置了image
元素中src
属性的值,而不是使用由XmlElement
对象定义的setAttribute
方法。这是因为 Windows 8 的 DOM 支持不一致,调用getElementsByNameTag
有时会返回一个XmlElement
对象的集合,有时反而会返回一个IXmlNode
对象的集合。IXmlNode
对象没有定义setAttribute
方法,所以我必须找到src
属性并使用innerText
属性设置其内容。
更新后,平铺最初显示图像,然后切换到带有动画的文本显示。几秒钟后,这个过程重复进行,如此继续下去。我觉得这种 live tile 有点烦人,但它可能正是你的应用所需要的。您可以在图 9 中看到图块的不同状态。请注意,我用于宽尺寸的模板有五个图像。我不需要调整图像的大小来显示它们;这是作为磁贴更新的一部分自动完成的。
***图九。*使用包含图像的图块模板
清除瓷砖
有时您需要清除图块的内容,通常是因为显示的信息现在已经过时,并且没有新的或值得注意的内容来代替它。为了演示如何清除文件,我向default.html
页面添加了一个新的按钮元素,如清单 13 所示。
清单 13。向布局添加一个按钮来清除磁贴
`…
清除磁贴很简单,如清单 14 所示,它详细说明了我对default.js
文件所做的更改。
清单 14。添加清除磁贴的支持
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var wnote = Windows.UI.Notifications;
WinJS.strictProcessing();
var textMessages = [“Today: Pick up groceries”, “Tomorrow: Oil change”,
“Wed: Book vacation”, “Thu: Renew insurance”, “Sat: BBQ”];
var images = img/lily.png",img/astor.png",img/carnation.png",
img/daffodil.png",img/snowdrop.png"];
// …helper functions removed for brevity…
** function clearTile() {**
** wnote.TileUpdateManager.createTileUpdaterForApplication().clear();**
** }**
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “basicTile”:
// …statements removed for brevity…
break;
** case “clearTile”😗*
** clearTile();**
** break;**
}
});
}));
}
};
app.start();
})();`
要清除图块,可以通过调用TileUpdateManager.createTileUpdaterForApplication
方法创建一个TileUpdater
对象,并对其调用clear
方法。清除磁贴会将其返回到静态,显示清单中定义的应用图像。
使用徽章
徽章是显示在图块上的一个小指示器,可以作为我在前面几节中展示的完整实时图块更新的替代或补充。添加工卡所需的步骤类似于定期更新所需的步骤,因为您选择一个 XML 模板,填充内容并将其作为更新传递给 Windows。
徽章出现在应用磁贴的右下角,可以是 1 到 99 之间的数字,也可以是 Windows 定义的少数图标之一(称为徽章 字形)。这是一种非常有限的表达信息的方式,但在某些情况下却很有用。我发现显示数字的能力很有用,但是还没有找到一个真正的项目,因为选择是如此有限(并且你不能定义你自己的)。
我在示例项目的default.html
文件中添加了两个新的button
元素,这样我就可以演示徽章的使用。新增内容如清单 15 所示。
清单 15。向 default.html 文件添加一个新的按钮元素
`…
您可以看到我对default.js
文件所做的添加,以支持清单 16 中的徽章。我列出了这个文件的完整代码,因为我想强调徽章的技术和瓷砖的技术是多么相似。
清单 16。向 default.js 文件添加对徽章的支持
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var wnote = Windows.UI.Notifications;
WinJS.strictProcessing();
var textMessages = [“Today: Pick up groceries”, “Tomorrow: Oil change”,
“Wed: Book vacation”, “Thu: Renew insurance”, “Sat: BBQ”];
var images = img/lily.png",img/astor.png",img/carnation.png",
img/daffodil.png",img/snowdrop.png"];
function getTemplateContent(template) {
return wnote.TileUpdateManager.getTemplateContent(template);
}
** function getBadgeTemplateContent(template) {**
** return wnote.BadgeUpdateManager.getTemplateContent(template);**
** }**
function populateTemplate(xml, textValues, imgValues) {
if (textValues) {
var textNodes = xml.getElementsByTagName(“text”);
var count = Math.min(textNodes.length, textValues.length);
for (var i = 0; i < count; i++) {
textNodes[i].innerText = textValues[i];
}
}
if (imgValues) {
var imgNodes = xml.getElementsByTagName(“image”);
var count = Math.min(imgNodes.length, imgValues.length);
for (var i = 0; i < count; i++) {
imgNodes[i].attributes.getNamedItem(“src”).innerText = imgValues[i]
}
}
return xml;
}
** function populateBadgeTemplate(xml, value) {**
** var badgeNode = xml.getElementsByTagName(“badge”)[0];**
** badgeNode.attributes.getNamedItem(“value”).innerText = value;**
** return xml;**
** }**
function updateTile(xml) {
var notification = new wnote.TileNotification(xml);
var updater = wnote.TileUpdateManager.createTileUpdaterForApplication();
updater.update(notification);
}
** function updateBadge(xml) {**
** var notification = new wnote.BadgeNotification(xml);**
** var updater = wnote.BadgeUpdateManager.createBadgeUpdaterForApplication();**
** updater.update(notification);**
** }**
function combineXML(firstXml, secondXML) {
var wideBindingElement = secondXML.getElementsByTagName(“binding”)[0];
var importedNode = firstXml.importNode(wideBindingElement, true);
var squareVisualElement = firstXml.getElementsByTagName(“visual”)[0];
squareVisualElement.appendChild(importedNode);
return firstXml;
}
function clearTile() {
wnote.TileUpdateManager.createTileUpdaterForApplication().clear();
** wnote.BadgeUpdateManager.createBadgeUpdaterForApplication().clear();**
}
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “basicTile”:
var squareTemplate =
wnote.TileTemplateType.tileSquarePeekImageAndText02;
var squareXML =
populateTemplate(getTemplateContent(squareTemplate),
[textMessages.length, “Reminders”], images);
var wideTemplate =
wnote.TileTemplateType.tileWidePeekImageCollection02;
var wideData = textMessages.slice(0, 4)
wideData.unshift(textMessages.length + " Reminders");
var wideXml =
populateTemplate(getTemplateContent(wideTemplate),
wideData, images);
updateTile(combineXML(squareXML, wideXml));
break;
case “clearTile”:
clearTile();
break;
** case “numericBadge”😗*
** var template = getBadgeTemplateContent(**
** wnote.BadgeTemplateType.badgeNumber);**
** var badgeXml = populateBadgeTemplate(template,**
** textMessages.length);**
** updateBadge(badgeXml);**
** break;**
** case “glyphBadge”😗*
** var template = getBadgeTemplateContent(**
** wnote.BadgeTemplateType.badgeGlyph);**
** var badgeXml = populateBadgeTemplate(template, “alert”);**
** updateBadge(badgeXml);**
** break;**
}
});
}));
}
};
app.start();
})();`
徽章的所有对象都在Windows.UI.Notifications
名称空间中,旁边是用于图块的对象。通过将一个值从BadgeTemplateType
枚举传递给BadgeUpdateManager.getTemplateContent
方法,可以获得想要使用的模板。我已经在表 2 中展示了两种不同的模板类型。
两个模板的内容是相同的,尽管使用正确的模板很重要,以防它们在未来的版本中发生变化。您可以在清单 17 的中看到由getTemplateContent
方法返回的 XML。
清单 17。徽章模板的 XML 内容
<badge value=""/>
这是一个非常简单的模板。如果你想显示一个数字徽章,那么你设置value
为你想显示的数字,在 1 到 99 之间。如果您设置的值超出了此范围,该值将显示为99+
(数字 99 后跟一个加号)。
如果您想要显示一个字形徽章,那么您可以将value
属性设置为您想要的字形的名称。在这个例子中,我使用了alert
值。没有枚举这些值的 JavaScript 对象,但是您可以在[
msdn.microsoft.com/en-us/library/windows/apps/hh761458.aspx](http://msdn.microsoft.com/en-us/library/windows/apps/hh761458.aspx)
看到它们的列表。共有 11 种字形,它们涵盖了应用可能想要传达给用户的一些常见信息。
一旦您填充了模板,您就创建了一个通知并将其传递给一个徽章更新程序,如示例中的updateBadge
函数所示。使用徽章时,您不必担心不同的图块大小,一次徽章更新会影响方形和宽图块。在图 10 的中,您可以看到应用于示例应用磁贴的数字和字形徽章。我已经展示了应用于静态和动态瓷砖的徽章。
***图 10。*将徽章应用于静态和动态应用磁贴
最后,清除徽章更新需要调用徽章更新程序上的clear
方法。您可以在示例中的clearTile
函数中看到一个演示,它现在删除了动态磁贴更新和徽章,将磁贴返回到其初始静态。
高级磁贴功能
我在前面几节中展示的磁贴技术将满足大多数应用的需求。对于更特殊的情况,您可以使用一些高级功能来更好地控制您的应用切片。在接下来的小节中,我将向您展示如何使用这些特性。为了演示这些特性,我创建了一个名为AdvancedTiles
的新 Visual Studio 项目。default.html
中的初始布局如清单 18 中的所示,由三个简单的button
元素组成。
清单 18。default.html 文件的初始内容
`
button
元素是为本章这一部分的第一个例子设置的。您可以在清单 19 的文件中看到这个项目的/css/default.css
的内容。
清单 19。default.css 文件的内容
body { display: -ms-flexbox; -ms-flex-direction: row; -ms-flex-align: stretch; -ms-flex-pack: center;} #container {display: -ms-flexbox; -ms-flex-direction: column; -ms-flex-align: stretch; -ms-flex-pack: center;} #container button {font-size: 30pt; width: 500px; margin: 10px;}
你可以在图 11 中看到这个 app 的初始布局。我将在本章的后面添加额外的按钮。
***图 11。*示例 app 的布局
我已经将本章前面的 tile helper 函数放到一个名为/js/tiles.js
的文件中,并通过Tiles
名称空间使它们可用。您可以在清单 20 中看到 tiles.js 文件的内容。
清单 20。/js/tiles.js 文件的内容
`(function () {
var wnote = Windows.UI.Notifications;
WinJS.Namespace.define(“Tiles”, {
getTemplateContent: function (template) {
return wnote.TileUpdateManager.getTemplateContent(template);
},
getBadgeTemplateContent: function (template) {
return wnote.BadgeUpdateManager.getTemplateContent(template);
},
populateTemplate: function (xml, textValues, imgValues) {
if (textValues) {
var textNodes = xml.getElementsByTagName(“text”);
var count = Math.min(textNodes.length, textValues.length);
for (var i = 0; i < count; i++) {
textNodes[i].innerText = textValues[i];
}
}
if (imgValues) {
var imgNodes = xml.getElementsByTagName(“image”);
var count = Math.min(imgNodes.length, imgValues.length);
for (var i = 0; i < count; i++) {
imgNodes[i].attributes.getNamedItem(“src”).innerText = imgValues[i]
}
}
return xml;
},
populateBadgeTemplate: function (xml, value) {
var badgeNode = xml.getElementsByTagName(“badge”)[0];
badgeNode.attributes.getNamedItem(“value”).innerText = value;
return xml;
},
updateTile: function (xml) {
var notification = new wnote.TileNotification(xml);
var updater = wnote.TileUpdateManager.createTileUpdaterForApplication();
updater.update(notification);
},
updateBadge: function (xml) {
var notification = new wnote.BadgeNotification(xml);
var updater = wnote.BadgeUpdateManager.createBadgeUpdaterForApplication();
updater.update(notification);
},
combineXML: function (firstXml, secondXML) {
var wideBindingElement = secondXML.getElementsByTagName(“binding”)[0];
var importedNode = firstXml.importNode(wideBindingElement, true);
var squareVisualElement = firstXml.getElementsByTagName(“visual”)[0];
squareVisualElement.appendChild(importedNode);
return firstXml;
},
clearTile: function () {
wnote.TileUpdateManager.createTileUpdaterForApplication().clear();
wnote.BadgeUpdateManager.createBadgeUpdaterForApplication().clear();
}
});
})();`
当我想在前面的例子基础上构建并列出不同之处时,我将调用这些函数。您可以在清单 21 中看到/js/default.js
文件的初始内容。
清单 21。default.js 文件的初始内容为
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var wnote = Windows.UI.Notifications;
WinJS.strictProcessing();
var dataObjects = [
{ name: “Projects”, quant: 6, key: “projects” },
{ name: “Clients”, quant: 2, key: “clients” },
{ name: “Milestones”, quant: 4, key: “milestones” }
];
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “clear”:
Tiles.clearTile();
break;
}
});
}));
}
};
app.start();
})();`
Clear Tile
按钮已经连接好,并调用了Tile.clearTile
方法来将磁贴重置为静态,并移除任何徽章。在接下来的部分中,我将添加其他按钮的代码。
最后,我在项目中添加了我在之前的示例应用中使用的相同的图块图像,并更新了应用清单,如图 12 所示。
***图 12。*设置清单图像文件
使用通知队列
我要描述的第一个高级特性是通知队列,它允许您使用磁贴轮流显示多达五个更新。当你的应用需要向用户显示一系列相关的消息或图像时,这可能会很有用——尽管,由于单个消息会显示大约 5 秒钟,你不能指望用户在访问开始屏幕时看到队列中的所有消息。这意味着你应该仔细选择你要展示的内容,这样每条信息都是独立的、有意义的,并且是有帮助的或有吸引力的。在示例应用中,我定义了一些可能总结用户项目承诺的数据:
... var dataObjects = [ { name: "Projects", quant: 6, key: "projects" }, { name: "Clients", quant: 2, key: "clients" }, { name: "Milestones", quant: 4, key: "milestones" }]; ...
每个数据对象都有三个属性:名称、数量和键。前两个是将向用户显示的数据项,key
属性将让我区分队列中的消息——当您想要刷新通知时,这变得很重要。您可以在清单 22 中看到我对default.js
文件所做的更改,以使用通知队列。
清单 22。使用通知队列
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var wnote = Windows.UI.Notifications;
WinJS.strictProcessing();
var dataObjects = [
{ name: “Projects”, quant: 6, key: “projects” },
{ name: “Clients”, quant: 2, key: “clients” },
{ name: “Milestones”, quant: 4, key: “milestones” }];
** function updateTileQueue(xml, tag) {
var notification = new wnote.TileNotification(xml);**
** notification.tag = tag;**
** var updater = wnote.TileUpdateManager.createTileUpdaterForApplication();**
** updater.update(notification);**
** }**
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
** wnote.TileUpdateManager.createTileUpdaterForApplication()**
** .enableNotificationQueue(true);**
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “clear”:
Tiles.clearTile();
break;
case “multiple”:
** dataObjects.forEach(function (item) {**
** var xml = Tiles.getTemplateContent(**
** wnote.TileTemplateType.tileSquareBlock);**
** Tiles.populateTemplate(xml, [item.quant, item.name]);**
** updateTileQueue(xml, item.key);**
** });**
break;
}
});
}));
}
};
app.start();
})();`
您必须显式地启用通知队列,这是通过创建一个TileUpdater
对象并调用enableNotificationQueue
方法,传递true
作为方法参数来完成的。你只需要这样做一次,这就是为什么我在应用的初始化阶段执行这项任务。
当点击Multiple Notifications
按钮时,我调用forEach
方法来枚举数据对象。对于每个对象,我获取并填充模板 XML,就像我在本章前面所做的那样。(我将只为方块大小创建通知,但是组合模板的过程与本章的第一部分相同)。
提示通知队列最多可容纳五个通知;如果您添加的项目超过五个,最新添加的项目将会推出旧的项目。
当我来更新磁贴时,差异就出现了。我使用填充的 XML 创建了TileNotification
对象,然后给tag
属性赋值。所有这些都发生在updateTileQueue
函数中,它允许系统区分它必须显示的不同通知。您将很快了解到,您可以通过重用标记值来替换单个通知。结果是,我将三个通知放入队列,磁贴将在它们之间旋转,大约每五秒钟从一个通知切换到另一个通知。您可以在图 13 中看到显示的三个通知。
***图十三。*在一个磁贴中显示多个通知
注意同样,您需要在本地机器上运行这个示例应用,因为 Visual Studio 模拟器不支持动态磁贴或通知。
我真的无法通过显示单个通知来捕捉这种工作方式,我建议您运行示例应用并查看开始屏幕,看看通知队列中的项目是如何显示的。
更新通知
您可以通过发出重用标记名的更新来更新通知。例如,如果我接受了一个新客户,那么我希望更新客户通知,以正确反映额外的业务。清单 23 展示了如何响应被点击的Update Notification
按钮。
清单 23。通过重用标签更新通知
... switch (this.id) { case "clear": Tiles.clearTile(); break; case "multiple": dataObjects.forEach(function (item) { var xml = Tiles.getTemplateContent( wnote.TileTemplateType.tileSquareBlock); Tiles.populateTemplate(xml, [item.quant, item.name]); updateTileQueue(xml, item.key); }); break; ** case "update":** ** var dob = dataObjects[1];** ** dob.quant++;** ** var xml = Tiles.getTemplateContent(**
** wnote.TileTemplateType.tileSquareBlock);** ** Tiles.populateTemplate(xml, [dob.quant, dob.name]);** ** updateTileQueue(xml, dob.key);** ** break;** } ...
我增加数值属性,并通过updateTileQueue
函数发布一个更新。务必注意使用正确的tag
值。如果重用标签,更新将替换现有项目,保持队列的顺序。如果您使用的标记值不在队列中,Windows 会将其视为新的通知,并将其附加到队列的末尾,这意味着您可能会得到两个显示冲突数据的通知。
调度通知
您可以安排通知出现在磁贴上的时间,以及从队列中删除通知的时间。未来安排通知的能力对于确保在应用可能被暂停或被终止时向用户呈现不是立即有用的信息是有用的。然而,当这种情况发生时,你不能指望用户查看你的应用磁贴,所以对于重要信息,你应该使用更直接的方法,比如 toast 通知,我在第二十八章中描述了这一点。
计划从队列中删除通知的时间点的能力更有用,如果用户有一段时间没有运行您的应用,您可以避免向用户显示过时的数据。我在default.html
文件中添加了一个新的button
来发送预定的通知,如清单 24 所示。
清单 24。添加一个按钮元素来支持预定通知
`…
清单 25 显示了我对default.js
文件所做的更改,以响应这个button
并安排一个通知。
清单 25。安排图块通知
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var wnote = Windows.UI.Notifications;
WinJS.strictProcessing();
var dataObjects = [
{ name: “Projects”, quant: 6, key: “projects” },
{ name: “Clients”, quant: 2, key: “clients” },
{ name: “Milestones”, quant: 4, key: “milestones” }];
function updateTileQueue(xml, tag) {
var notification = new wnote.TileNotification(xml);
notification.tag = tag;
var updater = wnote.TileUpdateManager.createTileUpdaterForApplication();
updater.update(notification);
}
** function scheduleTileQueue(xml, tag, start, end) {**
** var notification = new wnote.ScheduledTileNotification(xml, start);**
** notification.tag = tag;**
** notification.expirationTime = end;**
** var updater = wnote.TileUpdateManager.createTileUpdaterForApplication();**
** updater.addToSchedule(notification);**
** }**
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
wnote.TileUpdateManager.createTileUpdaterForApplication()
.enableNotificationQueue(true);
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “clear”:
Tiles.clearTile();
break;
case “multiple”:
// …statements removed for brevity…
break;
case “update”:
// …statements removed for brevity…
break;
** case “schedule”😗*
** var xml = Tiles.getTemplateContent(**
** wnote.TileTemplateType.tileSquareBlock);**
** Tiles.populateTemplate(xml, [10, “Days Left”]);**
** var start = new Date(new Date().getTime() + (20 * 1000));**
** var end = new Date(start.getTime() + (30 * 1000));**
** scheduleTileQueue(xml, “daysleft”, start, end);**
** break;**
}
});
}));
}
};
app.start();
})();`
创建通知与其他技术完全相同:您像往常一样获取并填充 XML 内容。当您将更新应用到 tile 时,差异就出现了,这是我通过清单中所示的scheduleTileQueue
函数完成的。
这项技术的关键是ScheduledTileNotification
对象,通过将填充的 XML 和 tile 应该开始显示更新的时间传递给构造函数来创建这个对象。时间被表示为一个Date
对象,在这个例子中,我已经指定更新应该在未来 20 秒后开始显示(对于一个真正的应用来说,这是一个非常短的时间,但是对于一个例子来说是理想的,因为你在点击按钮后不久就可以看到变化)。ScheduledTileNotification
对象定义了一组用于配置通知的属性,如表 3 所示。
您可以从清单中看到,我已经将到期时间设置为首次显示通知后的 30 秒。ScheduledTileNotification
对象的时间值是绝对的,这意味着您的Date
对象应该用日、月和年来定义(而不是仅仅用一段时间来表示)。如果您启动示例应用,单击Schedule Notification
按钮,并切换到开始屏幕,您可以看到这些更改的效果。
您看到的确切效果将取决于通知开始显示时磁贴的状态。如果队列中已经有通知,则计划的通知将作为定期轮换的一部分显示。到达到期时间后,计划通知将从队列中删除,仅显示原始通知。
如果图块是静态的(即队列中没有通知),情况会略有不同。通知被显示为磁贴的唯一内容,并且当到期时间到达时,磁贴重置为其静态。您可以在图 14 中看到该示例对通知的影响。
***图十四。*向磁贴添加预定通知
确定通知是否启用
在本章中,我将向您展示的最后一项技术是确定应用磁贴是否会显示实时更新。应用磁贴的设置由Windows.UI.Notifications.NotificationSetting
对象中的值表示,我已经在表 4 中列出了这些值。
我已经在default.html
文件中添加了一个最终的button
来确定应用的磁贴设置,如清单 26 所示。
清单 26。添加一个按钮来检查实时磁贴更新的状态
`…
清单 27 显示了我在default.js
文件中添加的内容,以确定状态。为了简单起见,我将结果写入 Visual Studio JavaScript Console
窗口,这意味着您需要使用调试器启动应用来查看消息。
清单 27。检查实时更新是否对用户可见
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var wnote = Windows.UI.Notifications;
WinJS.strictProcessing();
var dataObjects = [
{ name: “Projects”, quant: 6, key: “projects” },
{ name: “Clients”, quant: 2, key: “clients” },
{ name: “Milestones”, quant: 4, key: “milestones” }];
// …functions removed for brevity…
** function getValueFromEnum(val) {**
** for (var prop in wnote.NotificationSetting) {**
** if (wnote.NotificationSetting[prop] == val) {**
** return prop;**
** }**
** }**
** }**
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
wnote.TileUpdateManager.createTileUpdaterForApplication()
.enableNotificationQueue(true);
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “clear”:
Tiles.clearTile();
break;
case “multiple”:
// …statements removed for brevity…
break;
case “update”:
// …statements removed for brevity…
break;
case “schedule”:
// …statements removed for brevity…
break;
** case “check”😗*
** var setting =**
** wnote.TileUpdateManager.**
** createTileUpdaterForApplication().setting;**
** console.log("Live tile updates are " +**
** getValueFromEnum(setting));**
break;
}
});
}));
}
};
app.start();
})();`
您可以通过创建一个TileUpdater
对象(通过调用TileUpdateManager.createTileUpdaterForApplication
方法)并读取setting
属性的值来查看这些值中的哪一个适用。您可以通过选择应用磁贴并单击应用栏中的按钮来更改设置。两个选项如图图 15 所示。他们将在enabled
和disabledForApplication
设置之间切换。
***图 15。*启用和禁用单个应用的磁贴通知
如果没有为切片启用通知,则生成通知不会导致任何问题-通知会被直接丢弃。这意味着,如果您想提醒用户图块通知可用,只需检查设置值。对设置值的重要性保持敏感——如果用户只禁用了你的应用的通知(disabledForApplication
值),那么你的通知可能很烦人或者没有为用户提供价值。如果用户已经禁用了所有应用的动态磁贴(值为disabledForUser
),那么他们不太可能为你的应用破例。如果你遇到了disabledByGroupPolicy
值,那就继续——提醒用户通知可用是没有意义的,因为他们不太可能覆盖设置。这在大型企业部署中很常见,在这些部署中,以安全和易于管理的名义禁用了许多系统功能。
总结
如果使用得当,动态磁贴可以为应用的核心功能增添强大的功能,让用户不必启动应用,或者吸引用户来启动应用。在本章中,我向您展示了如何使用 live app 磁贴,从选择和填充单个模板的基本技巧开始,然后继续演示如何组合多个模板以及如何使用徽章。
在很大程度上,这些基本技术将是您所需要的,但是我也向您展示了一些用于要求更高的应用的高级特性。其中包括使用通知队列显示消息的循环序列,更新队列中各个通知的内容,以及控制通知的计划时间。我已经向你展示了如何判断是否显示实时更新,从而结束了这一章。将这项技术放在最后可能有点奇怪,但在大多数情况下并不需要,因为如果动态磁贴被禁用,通知就会被丢弃。磁贴并不是 Windows 应用唯一可用的通知机制——在下一章,当我向您展示如何使用 toast 以及介绍系统启动器功能时,您将会了解到这一点。
二十八、使用Toast
和系统启动器
在这一章中,我将向你展示如何使用 toast 通知,这是一种吸引用户注意力的更直接、更具侵入性的机制。Toast 通知是对我在上一章向您展示的更微妙的磁贴通知和徽章的补充,用于更重要或紧急的信息。像任何一种通知一样,祝酒词应该谨慎使用——太少的通知会让你的用户得不到他们想要的提示和提醒,太多的会让用户疲劳和烦恼。当有疑问时,允许用户使用我在第二十七章中描述的设置特性和技术来配置发出 toast 通知的环境。
我在本章中还描述了系统启动器功能,它允许你启动用户选择的 app 来处理你的 app 无法处理的文件和 URL。这是一个简单但有用的特性,值得了解如何使用它。表 1 提供了本章的总结。
使用 Toast 通知
Toast 通知是一种更直接、更吸引用户注意力的通知方式。屏幕上弹出一个祝酒词,通常伴随着声音效果,并具有打断用户注意力和工作流程的效果。因此,应该谨慎使用 toast 通知,只在重要且需要立即采取措施的情况下才显示它们。在接下来的小节中,我将带您完成创建和显示 toast 通知的过程,并向您展示当用户与它们交互时如何响应。
创建示例应用
我为本章创建的示例应用名为Toast
,我遵循了与上一章中的示例相同的基本格式——一个显示按钮的单页应用,当按钮被按下时,将触发通知。您可以在清单 1 中看到我对default.html
文件所做的初始添加,它包括本章中 toast 通知示例的按钮元素。
清单 1。Toast 示例应用的初始 default.html
`
您可以在清单 2 的中看到css/default.css
文件,它显示了我用来创建应用布局的样式。
清单 2。来自 Toast 项目的 css/default.css 文件
body, #container { display: -ms-flexbox; -ms-flex-direction: row; -ms-flex-align: stretch; -ms-flex-pack: center;} #container {-ms-flex-direction: column;} #container button {font-size: 30pt;width: 400px; margin: 10px;}
标记和 CSS 结合在一起为应用创建了一个简单的布局,你可以在图 1 中看到。布局非常简单,因为本例中的所有工作都将用于生成和管理 toast 通知。
***图 1。*吐司示例 app 的布局
对于这一章,我不需要担心保存应用状态或处理暂停或终止,所以我从一个简单的default.js
文件开始,如清单 3 所示。
清单 3。Toast 示例应用的初始 default.js 文件
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
** var wnote = Windows.UI.Notifications;**
WinJS.strictProcessing();
** var toastMessages = [“7pm Leave Office”, “8pm: Meet Jacqui at Lucca’s Bar”,**
** “9pm: Dinner at Joe’s”];**
** var toastImage =img/reminder.png";**
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
** case “toast”😗*
** // code will go here**
** break;**
}
});
}));
}
};
app.start();
})();`
这与我在 live tiles 章节中使用的起点非常相似。代码中的switch
语句被设置为当Show Toast
按钮被按下时做出响应(它的id
属性值为toast
),我将在本章后面为剩余的按钮添加其他的case
语句。
注意提供 toast 通知功能的对象是
Windows.UI.Notifications
名称空间的一部分,为了简洁起见,我在示例中将其别名为wnote
。除非我另有说明,本章中我提到的所有对象都是这个名称空间的一部分。
我已经为这个例子定义了不同的提醒数据,并向名为img/reminder.png
的项目添加了一个图像,如图 2 中的所示。
***图二。*添加到项目中的 reminder.png 图像,用于 toast 通知
当我在本章后面显示 toast 通知时,您将再次看到该图像。除了reminder.png
文件,我还在images
文件夹中添加了我在上一节中使用的相同的闹钟图标集,并将这些文件应用到应用清单的应用 UI 部分,如图图 3 所示。
提示你可以从
apress.com
开始获得这本书的免费源代码下载中的所有图片文件。
***图三。*在清单中应用图标图片
使用 toast 通知的一个重要区别是,您必须明确声明您将在应用清单中使用它们。为此,在 manifest Application UI
选项卡上查找Notifications
部分。将Toast
设置为Yes
,如图图 4 所示。
***图 4。*启用吐司通知
警告如果您忘记在清单中声明您将使用 toast 通知,Windows 将自动丢弃您的 toast 通知。
创建基本的祝酒通知
创建 toast 通知的过程与创建 live tile 通知的过程非常相似:选择一个 XML 模板,填充内容,然后将其传递给系统,以便向用户显示。您可以在清单 4 的中看到我添加到default.js
文件中的代码,它创建了一个基本的 toast 通知。
清单 4。创建基本的祝酒通知
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var wnote = Windows.UI.Notifications;
WinJS.strictProcessing();
var toastMessages = [“7pm Leave Office”, “8pm: Meet Jacqui at Lucca’s Bar”,
“9pm: Dinner at Joe’s”];
var toastImage =img/reminder.png";
** function getTemplateContent(templateName) {**
** var template = wnote.ToastTemplateType[templateName];**
** return wnote.ToastNotificationManager.getTemplateContent(template);**
** }**
** function populateTemplate(xml, textValues, imgValues) {**
** if (textValues) {**
** var textNodes = xml.getElementsByTagName(“text”);
var count = Math.min(textNodes.length, textValues.length);**
** for (var i = 0; i < count; i++) {**
** textNodes[i].innerText = textValues[i];**
** }**
** }**
** if (imgValues) {**
** var imgNodes = xml.getElementsByTagName(“image”);**
** var count = Math.min(imgNodes.length, imgValues.length);**
** for (var i = 0; i < count; i++) {**
** imgNodes[i].attributes.getNamedItem(“src”).innerText = imgValues[i]**
** }**
** }**
** return xml;**
** }**
** function showToast(xml) {**
** var notification = wnote.ToastNotification(xml);**
** var notifier = wnote.ToastNotificationManager.createToastNotifier();**
** notifier.show(notification);**
** return notification;**
** }**
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “toast”:
** var xml = getTemplateContent(“toastImageAndText04”);**
** populateTemplate(xml, toastMessages, [toastImage]);**
** showToast(xml);**
break;
}
});
}));
}
};
app.start();
})();`
如果你运行应用并点击Show Toast
按钮,你会看到屏幕右上角弹出了吐司,你可以在图 5 中看到示例制作的吐司是什么样子。
注意Visual Studio 模拟器不支持 toast 通知,就像它不支持 live tiles 一样。为了测试这个例子和本章中的所有其他例子,你必须使用一个真实的 Windows 8 设备,比如你的开发机器。
***图五。*敬酒通知
我选择了一个包含图像和一些文本行的 toast 通知模板,但系统添加了 bell 徽标,我再次在清单中为我的应用配置使用了该徽标,就像我在第二十七章中所做的一样。默认情况下,当显示提示信息时,您会听到一声蜂鸣声,几秒钟后提示信息会再次消失。
一次最多可以显示三个通知,它们可以来自多个应用或来自同一个应用。第一个 toast 通知与屏幕右侧对齐,与屏幕顶部有一个小间隙,后续通知堆叠在第一个通知的下方。如果要显示三个以上的通知,则使用一个队列,当旧的通知逐渐消失时,显示新的通知。在接下来的几节中,我将解释示例中的代码是如何工作的,以及它如何导致图中所示的 toast。
获取并填充 Toast 模板
创建 toast 通知的第一步是选择模板。八个受支持的模板由Windows.UI.Notifications.ToastTemplateType
对象枚举,包含纯文本和文本-图像混合选项。你可以在[
msdn.microsoft.com/en-us/library/windows/apps/windows.ui.notifications.toasttemplatetype.aspx](http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.notifications.toasttemplatetype.aspx)
的文档中看到ToastTemplateType
对象的模板样本。通过将来自ToastTemplateType
对象的值作为参数传递给Windows.UI.Notifications.ToastNotficationManager.getTemplateContent
方法,可以获得 toast 模板的 XML 内容。在示例中,我添加了一个助手函数来完成这项工作,我也称之为getTemplateContent
。作为我在上一章中使用的方法的变体,这个函数将包含模板名称的字符串值作为它的参数,并返回适当的 XML,如清单 5 中的所示。
清单 5。来自示例应用的 getTemplateContent 函数
... function getTemplateContent(templateName) { var template = wnote.ToastTemplateType[templateName]; return wnote.ToastNotificationManager.getTemplateContent(template); } ...
在这个例子中,我选择了toastImageAndText04
模板,它包含一个图像和三行非换行文本。在该模板中,第一行以粗体显示,如果文本行太长而无法显示,Windows 将缩短它们并应用省略号(...
)。您可以在清单 6 中看到这个模板的 XML。
清单 6。toastImageAndText04 模板的 XML
<toast> <visual> <binding template="ToastImageAndText04">
<image id="1" src=""/> <text id="1"></text> <text id="2"></text> <text id="3"></text> </binding> </visual> </toast>
填充 XML 模板的过程与 tile 的过程相同——事实上,我从上一章复制了这个例子的populateTemplate
函数。toast 模板只有一种大小,这意味着您不必担心 XML 片段的组合。在这个例子中,我使用了三个简单的文本字符串和我前面提到的图像来填充模板,您可以在清单 7 中看到结果。
清单 7。示例应用的填充 Toast 模板
<toast> <visual> <binding template="ToastImageAndText04"> <image id="1" src="img/reminder.png**"/> <text id="1">**7pm Leave Office**</text> <text id="2">**8pm: Meet Jacqui at Lucca's Bar**</text> <text id="3">**9pm: Dinner at Joe's**</text> </binding> </visual> </toast>
显示 Toast 通知
一旦填充了 XML 模板,就可以向用户显示 toast 通知。在示例中,我创建了showToast
函数来处理这个问题。
第一步是创建一个ToastNotification
对象,将填充的 XML 作为构造函数参数传入。然后创建一个负责实际工作的ToastNotifier
对象——通过调用ToastNotificationManager.createToastNotifier
方法得到这个对象。最后一步是在ToastNotifier
对象上调用show
方法,传入ToastNotification
对象向用户显示吐司。我已经重复了清单 8 中的showToast
函数。
清单 8。来自示例应用的 showToast 函数
... function showToast(xml) { var notification = wnote.ToastNotification(xml); var notifier = wnote.ToastNotificationManager.createToastNotifier(); notifier.show(notification); return notification; } ...
配置 Toast 通知
您可以通过向模板 XML 添加属性来更改 toast 通知的行为。在接下来的小节中,我将解释这些选项,并演示应用它们所需的代码。
配置持续时间
有两个设置可用于控制向用户显示 toast 通知的时间段:short
和long
。默认是short
,这意味着祝酒词显示 7 秒钟。long
持续时间显示 25 秒的通知。
您可以通过向模板 XML 中的 toast 元素添加一个duration
属性并将其设置为long
或short
(这是仅有的两个受支持的值),来指定将使用哪个设置。为了支持设置持续时间,我向示例中的default.js
文件添加了一个setToastDuration
函数,如清单 9 所示。
清单 9。将 setToastDuration 函数添加到 default.js 文件
`…
function getTemplateContent(templateName) {
var template = wnote.ToastTemplateType[templateName];
return wnote.ToastNotificationManager.getTemplateContent(template);
}
function setToastDuration(xml, duration) {
** var attribute = xml.createAttribute(“duration”);**
** attribute.innerText = duration;**
** xml.getElementsByTagName(“toast”)[0].attributes.setNamedItem(attribute);**
}
// …other functions omitted for brevity…
…`
然后我从onactivated
函数中的button
元素的click
事件处理函数中调用这个函数。您可以看到我是如何将这一更改应用到清单 10 中的default.js
文件的。
清单 10。调用 setToastDuration 函数
... app.onactivated = function (args) { if (args.detail.kind === activation.ActivationKind.launch) { args.setPromise(WinJS.UI.processAll().then(function () { WinJS.Utilities.query("#container > button").listen("click", function (e) { switch (this.id) { case "toast": var xml = getTemplateContent("toastImageAndText04"); populateTemplate(xml, toastMessages, [toastImage]); ** setToastDuration(xml, "long");** showToast(xml); break; }
}); })); } }; ...
在清单 11 的中,您可以看到向模板 XML 添加了duration
属性。
清单 11。向 XML 中的 Toast 元素添加 Duration 属性
<toast **duration="long"**> <visual> <binding template="ToastImageAndText04"> <image id="1" srcimg/reminder.png"/> <text id="1">7pm Leave Office</text> <text id="2">8pm: Meet Jacqui at Lucca's Bar</text> <text id="3">9pm: Dinner at Joe's</text> </binding> </visual> </toast>
只有当通知的内容特别重要并且错过通知的影响很严重时,才应该使用long
设置。例如,微软引用未接电话和即将到来的约会提醒作为适合long
选项的例子。我对这种方法的问题是,它让我决定什么对用户来说是重要的,所以我倾向于让这成为我的应用的可配置选项,使用我在第二十章中描述的设置功能。
配置音频警报
可以通过 XML 属性配置的另一个行为是当显示 toast 通知时播放的音频警报。您可以通过添加一个audio
属性来实现,这个属性有一个src
属性来指定播放的音频,还有一个loop
属性来指定如何播放。这两个属性的选项都非常有限,大概是为了加强某种一致性,并确保用户将小范围的音频选项与 toast 通知相关联。有九个不同的值可以用于src
属性,如表 2 所示。正如你将看到的,微软一直非常禁止何时使用它们。
第一次显示通知时,Default
、IM
、Mail
、Reminder
、SMS
音很短,播放一次。Alarm
、Alarm2
、Call
和Call2
声音更长,并且只要通知可见就重复。
使用表中的值来指定 toast 通知的声音效果时需要小心。如果您想要使用Alarm
、Alarm2
、Call
或Call2
声音,那么您需要确保 audio 元素上的loop
属性被设置为true
,并且toast
元素上的duration
属性被设置为long
。如果没有正确设置这两个属性,那么用户将会听到默认的声音(相当于指定了Notification.Default
值)。
但是,如果您想在duration
属性设置为long
时使用Default
、IM
、Mail
、Reminder
或SMS
声音,那么您必须确保loop
设置为false
。如果loop
是true
,那么用户将会听到Alarm
的声音,与您指定的值无关。
提示如果你不想让任何声音伴随你的祝酒通知,使用一个
silent
属性设置为true
的audio
元素。
对于我的示例应用,我想使用Reminder
声音,这意味着我想生成如清单 12 所示的 XML,确保loop
属性的值为false
。
清单 12。向 Toast 通知 XML 添加音频元素
<toast duration="long"> <visual> <binding template="ToastImageAndText04"> <image id="1" srcimg/reminder.png"/> <text id="1">7pm Leave Office</text> <text id="2">8pm: Meet Jacqui at Lucca's Bar</text> <text id="3">9pm: Dinner at Joe's</text> </binding> </visual> ** <audio src="ms-winsoundevent:Notification.Reminder" loop="false"/>** </toast>
为了将audio
元素添加到示例中的 XML,我定义了setToastAudio
函数,您可以在清单 13 中看到。如果silent
参数为true
,该函数将禁用 toast 通知的声音,否则将使用提供的值设置src
和loop
属性。
清单 13。示例应用的 setToastAudio 函数
`…
function getTemplateContent(templateName) {
var template = wnote.ToastTemplateType[templateName];
return wnote.ToastNotificationManager.getTemplateContent(template);
}
function setToastDuration(xml, duration) {
var attribute = xml.createAttribute(“duration”);
attribute.innerText = duration;
xml.getElementsByTagName(“toast”)[0].attributes.setNamedItem(attribute);
}
function setToastAudio(xml, silent, sound, loop) {
** var audioElem = xml.createElement(“audio”);**
** if (silent) {**
** audioElem.setAttribute(“silent”, “true”);**
** } else {**
** audioElem.setAttribute(“src”, sound);**
** audioElem.setAttribute(“loop”, loop);**
** }**
** xml.getElementsByTagName(“toast”)[0].appendChild(audioElem);**
}
// …other functions omitted for brevity…
…`
您可以看到我是如何从清单 14 中的default.js
按钮click
处理函数调用这个函数的,它创建了我在清单 12 中展示的 XML。
清单 14。调用 setToastAudio 函数
... app.onactivated = function (args) { if (args.detail.kind === activation.ActivationKind.launch) { args.setPromise(WinJS.UI.processAll().then(function () { WinJS.Utilities.query("#container > button").listen("click", function (e) { switch (this.id) { case "toast": var xml = getTemplateContent("toastImageAndText04"); populateTemplate(xml, toastMessages, [toastImage]); setToastDuration(xml, "long"); ** setToastAudio(xml, false,** ** "ms-winsoundevent:Notification.Reminder", false);** showToast(xml); break; } }); })); } }; ...
这些变化的效果是,我的祝酒词伴随着与提醒相关的标准声音。
处理吐司激活和解除
我还没有把属性添加到 XML 中。还有一个属性可以使用,我将在这一节解释它是什么以及它是如何工作的。在此之前,我需要解释一下 toast 通知的生命周期。
一旦将填充的 XML 传递给ToastNotifier.show
方法,通知就会呈现给用户,有三种可能的结果。第一个结果是用户通过点击或触摸通知来激活。第二种结果是用户取消通知,或者通过向屏幕的右边缘滑动通知,或者单击当鼠标在通知窗口上时出现的X
图标,我已经在图 6 中显示了这一点(此图中的红色突出显示是我添加的,没有向用户显示)。
***图六。*出现在祝酒通知上的解雇图标
第三个结果是用户忽略了通知。在由duration
属性指定的时间段之后,系统将通过使弹出窗口慢慢消失来代表用户消除通知。您可以通过监听ToastNotification
对象发出的一组事件来响应这些结果。我在表 3 中总结了三个事件。
在接下来的小节中,我将向您展示如何处理activated
和dismissed
事件。当 Windows 不能向用户显示 toast 通知时,触发failed
事件。据我所知,只发现了两个导致failed
事件的原因:试图从一个应用发出超过 4096 个通知,以及试图为过去的日期安排一个 toast 通知。我将在本章的后面向你展示如何安排 toast 通知,但是我很少遇到 toast 通知的问题,所以我不打算演示如何处理failed
事件。
处理激活
当用户点击或触摸正在显示的通知时,激活的事件被触发。传递给处理函数的对象的target
属性返回用户激活的ToastNotification
对象,如果您在生成多个通知的应用中使用单个函数,这将非常有用。ToastNotification.content
属性返回用于创建通知的 XML,您需要将这个值与您的应用数据关联起来,以确定哪个通知被激活了。为此,您可以将launch
属性应用于 XML 中的toast
元素——该属性可以设置为任何字符串值,以便于识别通知。我在示例中添加了两个函数来设置和读取launch
属性,如清单 15 所示。
清单 15。用于设置和读取 Toast 元素的 Launch 属性的函数
... function getTemplateContent(templateName) { var template = wnote.ToastTemplateType[templateName];
` return wnote.ToastNotificationManager.getTemplateContent(template);
}
function setToastLaunchValue(xml, val) {
** var attribute = xml.createAttribute(“launch”);**
** attribute.innerText = val;**
** xml.getElementsByTagName(“toast”)[0].attributes.setNamedItem(attribute);**
}
function getToastLaunchValue(xml) {
** var attribute =**
** xml.getElementsByTagName(“toast”)[0].attributes.getNamedItem(“launch”);**
** return attribute ? attribute.value : null;**
}
function setToastDuration(xml, duration) {
var attribute = xml.createAttribute(“duration”);
attribute.innerText = duration;
xml.getElementsByTagName(“toast”)[0].attributes.setNamedItem(attribute);
}
…`
这些函数使用标准的 XML DOM 操作来设置或读取属性值。如果我在显示通知之前在 XML 中设置值,那么我可以更容易地识别哪个通知被激活了,如清单 16 中的所示。
清单 16。使用启动属性识别通知
`…
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
** var notificationId = 0;**
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “toast”:
var xml = getTemplateContent(“toastImageAndText04”);
populateTemplate(xml, toastMessages, [toastImage]);
setToastDuration(xml, “long”);
setToastAudio(xml, false,
“ms-winsoundevent:Notification.Reminder”, false);
** setToastLaunchValue(xml, “notification” + notificationId++);**
var notification = showToast(xml);
notification.addEventListener(“activated”, function (e) {
** var id = getToastLaunchValue(e.target.content)**
** console.log(“Toast notification " + id**
** + " was activated”);**
});
break;
}
});
}));
}
};
…`
在这个清单中,我使用setToastLaunchValue
函数为每个通知分配一个launch
值,并使用getToastLaunchValue
函数读取该值。如果您启动应用,单击Show Toast
按钮,然后单击 toast 通知弹出窗口,您将在 Visual Studio JavaScript Console
窗口上看到类似于此的消息,表明通知已激活:
Toast notification notification0 was activated
处理 Toast 激活导致的应用激活
当您的应用运行或暂停时,您只能使用ToastNotification
activated
事件来响应 toast 激活。如果您的应用在通知显示后被用户终止或关闭,那么 Windows 将激活您的应用,并使用传递给onactivated
处理函数的对象的detail.arguments
属性,将您分配给通知 XML 的launch
属性的值传递给您的应用。在清单 17 中,我向您展示了如何回应这些信息。
清单 17。通过 onactivated 功能接收 Toast 激活详情
`…
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
** if (typeof args.detail.arguments == “string”**
** && args.detail.arguments.indexOf(“notification”) == 0) {**
** // respond to notification being activated**
** }**
var notificationId = 0;
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “toast”:
var xml = getTemplateContent(“toastImageAndText04”);
populateTemplate(xml, toastMessages, [toastImage]);
setToastDuration(xml, “long”);
setToastAudio(xml, false,
“ms-winsoundevent:Notification.Reminder”, false);
setToastLaunchValue(xml, “notification” + notificationId++);
var notification = showToast(xml);
notification.addEventListener(“activated”, function (e) {
var id = getToastLaunchValue(e.target.content)
console.log(“Toast notification " + id
+ " was activated”);
});
break;
}
});
}));
}
};
…`
处理解雇
当用户显式关闭弹出窗口,或者当用户完全忽略它并且超时时,ToastNotification
对象触发dismissed
事件。您可以通过读取传递给处理函数的事件的reason
属性的值来找出发生了什么。该属性将返回由ToastDismissalReason
对象枚举的值之一,我在表 4 中总结了这些值。
清单 18 显示了对default.js
文件的添加,以处理dismissed
事件。
清单 18。处理通知驳回事件
... switch (this.id) { case "toast": var xml = getTemplateContent("toastImageAndText04"); populateTemplate(xml, toastMessages, [toastImage]); setToastDuration(xml, "long"); setToastAudio(xml, false, "ms-winsoundevent:Notification.Reminder", false); setToastLaunchValue(xml, "notification" + notificationId++); var notification = showToast(xml); notification.addEventListener("activated", function (e) { var id = getToastLaunchValue(e.target.content); console.log("Toast notification " + id + " was activated"); }); ** notification.addEventListener("dismissed", function (e) {** ** var id = getToastLaunchValue(e.target.content);** ** if (e.reason == wnote.ToastDismissalReason.userCanceled) {** ** console.log("The user dismissed toast notification " + id);** ** } else if (e.reason == wnote.ToastDismissalReason.timedOut) {** ** console.log("Toast notification " + id + " timed out");** ** }** ** });** break;
} ...
提示如果你的应用正在运行,你只能知道通知何时被取消。系统不会启动您的应用来告诉您用户没有回复或取消了通知。
安排祝酒通知
您可以计划在未来某个时间向用户显示 toast 通知,可能是在您的应用不再运行或暂停时。这对于特定时间的通知很有用,比如日历提醒,你想确保用户收到通知,但你不能确定他们会在关键时刻使用你的应用。
您通过创建一个ScheduledToastNotification
对象来调度 toast 通知,如清单 19 中的所示,它显示了我添加到default.js
文件中的scheduleToast
函数来帮助调度通知。
清单 19。添加到示例应用的 default.js 文件中的 scheduleToast 函数
`…
function getToastLaunchValue(xml) {
var attribute =
xml.getElementsByTagName(“toast”)[0].attributes.getNamedItem(“launch”);
return attribute ? attribute.value : null;
}
function scheduleToast(xml, time, interval, count) {
** var notification = wnote.ScheduledToastNotification(xml, time, interval, count);**
** var notifier = wnote.ToastNotificationManager.createToastNotifier();**
** notifier.addToSchedule(notification);**
}
function setToastDuration(xml, duration) {
var attribute = xml.createAttribute(“duration”);
attribute.innerText = duration;
xml.getElementsByTagName(“toast”)[0].attributes.setNamedItem(attribute);
}
…`
传递给函数来创建一个ScheduledToastNotification
对象的四个参数是填充的 XML 模板、一个指定通知将显示的未来时间的Date
对象、暂停持续时间以及通知将暂停的次数。
当用户在由duration
属性指定的时间内没有明确激活或关闭时,一个预定的 toast 通知就会暂停(我在本章前面已经描述过了)。Windows 将在暂停间隔(以毫秒为单位)结束后显示通知,这给了用户另一个响应的机会。这个过程会重复最后一个参数指定的次数。您可以看到我如何安排一个通知来响应在清单 20 中按下的Schedule Toast
按钮。
清单 20。调度敬酒通知
`…
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
if (typeof args.detail.arguments == “string”
&& args.detail.arguments.indexOf(“notification”) == 0) {
// respond to notification being activated
}
var notificationId = 0;
WinJS.Utilities.query(“#container > button”).listen(“click”,
function (e) {
switch (this.id) {
case “toast”:
// …statements removed for brevity…
break;
** case “schedule”😗*
** var xml = getTemplateContent(“toastImageAndText04”);**
** populateTemplate(xml, toastMessages, [toastImage]);**
** var scheduleDate = new Date(new Date().getTime()**
** + (10 * 1000));**
** scheduleToast(xml, scheduleDate, 60000, 2);**
** break;**
}
});
}));
}
};
…`
在这个例子中,我将通知安排在未来 10 秒钟(这样您在试验这个例子时就不必等待太久)。我告诉 Windows 暂停通知 60 秒,并允许通知暂停两次(这意味着用户将总共看到通知三次——一次在预定时间到达时,两次在通知暂停后)。
警告 Windows 对暂停间隔和允许的暂停次数进行限制。间隔必须至少为 1 分钟,并且不能超过 1 小时。snoozes 的数量必须介于 1 和 5 之间(含 1 和 5)。
确定是否启用 Toast 通知
您可以通过创建一个ToastNotifier
对象(通过ToastNotificationManager
)并读取setting
属性来决定是否显示 toast 通知。在清单 21 中,您可以看到我添加到default.js
文件中的最后一条case
语句,以响应被按下的Check Status
按钮。
清单 21。检查 Toast 通知的状态
... switch (this.id) { case "toast": // *...statements omitted for brevity...* break; case "schedule": // *...statements omitted for brevity...* break; ** case "check":** ** var notifier = wnote.ToastNotificationManager.createToastNotifier();** ** var value = getToastSettingValueFromEnum(notifier.setting);** ** console.log("Toast setting: " + value);** ** break;** } ...
setting
属性将返回由NotificationSetting
对象枚举的一个值,为了将该值呈现为可读的形式,我在示例中添加了getToastSettingValueFromEnum
函数,如清单 22 所示。
清单 22。getToastSettingValueFromEnum 函数
`…
function setToastDuration(xml, duration) {
var attribute = xml.createAttribute(“duration”);
attribute.innerText = duration;
xml.getElementsByTagName(“toast”)[0].attributes.setNamedItem(attribute);
}
function getToastSettingValueFromEnum(val) {
** for (var prop in wnote.NotificationSetting) {**
** if (wnote.NotificationSetting[prop] == val) {**
** return prop;**
** }**
** }**
}
// …other functions omitted for brevity…
…`
您可以在表 5 中看到NotificationSetting
对象定义的值。这些值与我在第二十七章中描述的值相同,当时我向你展示了如何决定是否向用户显示实时磁贴通知。
使用应用启动器
有些时候,你想通知用户一些你的应用不能直接处理的重要数据。例如,我可能有一个监控新文件的Pictures
库的应用,但是它只能显示有限范围的格式。我想在有新文件时通知用户,即使我不能直接在应用中显示它们。
这是一个人为的例子,因为 Internet Explorer 将很乐意显示大多数类型的图像文件-但它让我构建一个示例应用来演示应用启动器,您可以使用它来调用其他应用来为您处理您的数据。虽然图像格式可能不是问题,但是我在本节中描述的技术可以在您处理无法直接处理的数据时应用。
虽然这个示例应用很简单,但是考虑到我将要演示的特性的简单性,它是相当长的。我不会为此道歉:它允许我演示不同的功能如何一起使用——在这种情况下,在Windows.Storage
和Windows.Storage.Search
名称空间中的对象(在第二十一章到 23 章中描述)、文件激活契约(在第二十四章中描述)、toast 通知(本章)以及新添加的应用启动器。我在这本书里给你看的例子越多,将来你有问题要解决的时候,找到你需要的东西的机会就越大。
创建示例应用
本节的示例应用名为FileWatcher
,它出现了我之前描述的问题——它监控Pictures
库,并在添加新文件时通知用户。我写的应用,以便它可以只显示 JPG 文件。您可以在清单 23 的中看到这个项目的default.html
文件的内容。
清单 23。default.html 文件的内容
`
这个应用的布局基于一个img
元素和一个button
。当按钮被单击时,我将开始监视Pictures
库,并在用户激活我的 toast 通知时使用img
元素显示 JPG 图像。您可以在清单 24 中看到我用来设计布局样式的 CSS,它显示了css/default.css
文件。
清单 24。default.css 文件的内容
`body { display: -ms-flexbox; -ms-flex-direction: column;
-ms-flex-align: center; -ms-flex-pack: center;}
#imgContainer { border: medium solid white;
width: 512px; height: 382px;
margin: 20px; text-align: center;}
img {height: 382px; max-width: 512px;}
button {font-size: 24pt; margin: 10px;}`
添加图像
我已经向 images 目录添加了一个名为logo.png
的文件,您可以看到我在default.html
文件中的img
元素的src
属性中使用了这个文件。该图像在透明背景上显示一个白色图标,您可以在图 7 中看到该图像,它显示了应用的布局。
***图 7。*file watcher app 的初始布局
我还用包含相同图标的文件替换了清单中使用的图像文件,但这些文件的大小符合要求。您可以从免费的源代码下载中获得所有这些图像文件,可以从apress.com
获得。
定义 JavaScript 代码
这个应用将生成 toast 通知,所以我从本章的第一部分中提取了我需要的函数,并将它们放在一个名为js/toast.js
的文件中。我没有对函数做任何修改,只是将它们放在一个名为Toast
的名称空间中。您可以在清单 25 的中看到toast.js
文件的内容。
清单 25。toast.js 文件
`(function () {
“use strict”;
var wnote = Windows.UI.Notifications;
WinJS.Namespace.define(“Toast”, {
getTemplateContent: function(templateName) {
var template = wnote.ToastTemplateType[templateName];
return wnote.ToastNotificationManager.getTemplateContent(template);
},
populateTemplate: function (xml, textValues, imgValues) {
if (textValues) {
var textNodes = xml.getElementsByTagName(“text”);
var count = Math.min(textNodes.length, textValues.length);
for (var i = 0; i < count; i++) {
textNodes[i].innerText = textValues[i];
}
}
if (imgValues) {
var imgNodes = xml.getElementsByTagName(“image”);
var count = Math.min(imgNodes.length, imgValues.length);
for (var i = 0; i < count; i++) {
imgNodes[i].attributes.getNamedItem(“src”).innerText = imgValues[i]
}
}
return xml;
},
showToast: function(xml) {
var notification = wnote.ToastNotification(xml);
var notifier = wnote.ToastNotificationManager.createToastNotifier();
notifier.show(notification);
return notification;
}
});
})();`
我只需要几个功能,因为这个应用只会创建基本的吐司通知。这个应用的主要代码包含在default.js
文件中,如清单 26 所示,它使用文件查询来监控Pictures
库,并在添加新文件时显示 toast 通知。当用户激活通知时,布局中的img
元素将显示新文件——也就是说,只要它是 JPG 文件。出于这个例子的目的,我假设我的应用不能以同样的方式处理其他文件格式。在清单中,您将看到一个注释,它充当处理其他文件类型的代码的占位符。
清单 26。js/default.js 文件的初始内容
`(function () {
“use strict”;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var storage = Windows.Storage;
var fileList = [];
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
args.setPromise(WinJS.UI.processAll().then(function () {
startButton.addEventListener(“click”, function (e) {
startButton.disabled = true;
var query = storage.KnownFolders.picturesLibrary.createFileQuery(
storage.Search.CommonFileQuery.orderByName);
query.addEventListener(“contentschanged”, function (e) {
query.getFilesAsync().then(function (files) {
files.forEach(function (file) {
if (fileList.indexOf(file.path) == -1) {
fileList.push(file.path);
displayToastForFile(file);
}
});
});
});
setTimeout(function () {
query.getFilesAsync().then(function (files) {
files.forEach(function (file) {
fileList.push(file.path);
});
});
}, 1000);
});
}));
}
};
function displayToastForFile(file) {
var messages = [“Found new file”, file.displayName];
var xml = Toast.getTemplateContent(“toastImageAndText04”);
Toast.populateTemplate(xml, messages, [“ms-appx:img/logo.png”]);
var notification = Toast.showToast(xml);
notification.addEventListener(“activated”, function (e) {
if (file.fileType == “.jpg”) {
imgElem.src = URL.createObjectURL(file);
} else {
** // … code to process other file types will go here…**
}
});
}
app.start();
})();`
当一个新文件被添加到Pictures
库中时,我接收到的contentschanged
事件并没有向我提供新添加文件的详细信息,所以我保存了一个文件路径数组,并使用它来确定库中哪些文件是新的。如果您的Pictures
库中有大量文件,您可能需要监视不同的位置,因为数组会变得非常大。
更新清单
此应用需要进行两项清单更改才能工作。打开package.appxmanifest
文件并导航至Capabilities
选项卡。勾选Pictures Library
选项,如图图 8 所示。这是允许应用监控新文件的Pictures
库所必需的。
***图 8。*启用对图片库的访问
第二个变化是声明应用将生成 toast 通知。导航到清单的Application UI
部分,将Toast capable
选项设置为Yes
,如图图 9 所示。(您也可以为应用设置徽标,但这不是必需的。我在第二十四章的中重复使用了我为PhotoAlbum
应用创建的标志。)
***图九。*为示例应用启用 Toast 通知
测试示例应用
启动示例应用并单击Start
按钮,开始监控Pictures
库中的新文件。如果你拷贝一个新文件到Pictures
库,你会看到一个 toast 通知,如图图 10 所示。(新文件可能需要几秒钟才能报告给应用。)
注意你需要在本地开发机器等真实的 Windows 8 设备上运行这个应用,因为通知不会显示在 Visual Studio 模拟器中。
***图 10。*敬酒通知报告一份新发现的文件
如果您通过点击或触摸通知来激活它,那么应用将使用标记中的img
元素来显示图像,但前提是它是 JPG 文件。如果不是,那么什么都不会发生。我将在下一节中解决这个问题。
使用应用启动器
启动文件的默认应用是通过Windows.System.Launcher
对象完成的,它定义了表 6 所示的方法。
提示从这些方法中可以看出,这个对象也可以用于启动 URL 的默认应用。在这一章中,我将着重于文件激活,但是 URL 激活的过程遵循相同的模式。
使用Windows.System.LauncherOptions
对象指定启动器的选项。这让你可以对启动过程进行一些细粒度的控制,包括指示 Windows 如果没有安装可以处理该文件类型的应用应该发生什么。您可以在表 7 中看到由LauncherOptions
对象定义的属性。
您可以在清单 27 的中看到我如何在我的示例应用中使用Launcher
和LauncherOptions
对象,它显示了我对default.js
文件中的displayToastForFile
函数所做的更改。我使用带有LauncherOptions
对象的launchFileAsyc
方法,配置为向用户显示可以处理图像文件的应用列表。
清单 27。添加启动默认应用的支持
... function displayToastForFile(file) { var messages = ["Found new file", file.displayName]; var xml = Toast.getTemplateContent("toastImageAndText04"); Toast.populateTemplate(xml, messages, ["ms-appx:img/logo.png"]); var notification = Toast.showToast(xml); notification.addEventListener("activated", function (e) { if (file.fileType == ".jpg") { imgElem.src = URL.createObjectURL(file); } else { ** var options = new Windows.System.LauncherOptions();** ** options.displayApplicationPicker = true;** ** Windows.System.Launcher.launchFileAsync(file, options);** } }); } app.start(); ...
如果你启动这个应用,点击Start
按钮,将任何不是 JPG 文件的文件复制到Pictures
库,你会看到和我在上一节给你看的一样的 toast 通知。不同之处在于,当您激活 toast 时,您将看到一个已实现文件激活契约的 Windows 应用商店应用列表(以及已实现等效桌面功能的桌面应用)。选择其中一个应用,您添加到库中的文件将会打开。
此功能无需您知道对于给定的文件类型有哪些应用可用,或者用户选择了哪个应用作为默认应用。要了解这是如何工作的,复制一个完全不同类型的文件——比如一个 Word DOCX
文件——你会看到 Windows 会处理细节。更好的是,微软简化了在没有安装应用的情况下查找合适应用的流程。要了解这是如何工作的,取一个现有文件,将文件扩展名改为.xxxxx
(或者系统没有合适应用的任何扩展名)。当您在示例应用中激活 toast 通知时,您会看到一个有用的警告,并邀请您找到您需要的软件,如图图 11 所示。
***图 11。*帮助用户定位处理文件类型的 app
这是一个简单的功能,但它允许你构建应用,这些应用可以操作它们不直接支持的格式的文件。该特性还利用了文件和协议激活契约,允许您构建补充应用,使能够处理特定的文件格式。
总结
在这一章中,我向你展示了如何使用 toast 通知来吸引用户的注意。谨慎而周到地使用,这是一个重要的特性,可以提高你的应用对用户的价值。过度使用,你会创建一个令人讨厌的、侵扰性的应用,它会打断用户对他们不重视的通知的注意力。我还向您展示了如何使用 app launcher,它允许您启动文件或 URL 的默认应用,而不知道哪个应用被配置为默认应用,甚至不知道是否安装了合适的应用。在下一章中,我将向您展示如何使用 Windows 对传感器的支持,将真实世界的数据引入您的应用。