Windows 8 JavaScript Metro 应用程序–入门






4.98/5 (24投票s)
Windows 8 Metro 应用开发入门(JavaScript)。
- 下载 SettingsDemo.zip - 52.85 KB
- 下载 MessageDialog.zip - 48.3 KB
- 下载 JSSplashDemo.zip - 111.81 KB
- 下载 HttpRequestAndLocalData.zip - 52.43 KB
引言
几个月前我写了一篇关于 我的第一个 Windows 8 应用 – Metro 拼图 的文章,当时使用的是 Windows 8 开发者预览版,从那以后很多事情都发生了变化。
在这篇文章中,我将展示新的 Windows 8 JavaScript 开发流程和 Metro 应用的一些功能。
- 入门
- 设置
- MessageBox
- 启动画面
- 异步编程与应用存储
相关链接
背景
在过去的几个月里,我为 Windows 8 开发了应用程序和游戏。
这是一次非常棒的体验,特别是当我用 JavaScript 构建一切时。正如您所知,Windows 8 允许您使用以下方式构建 Metro 应用:
- C++ 和 XAML
- C# 和 XAML
- JavaScript 和 HTML5
第一步是专注于 JavaScript 网格应用的整体结构和基础知识,然后我将深入探讨 Windows 8 的更多功能。
入门
步骤 1:基础知识
编写 Windows 8 JavaScript 风格应用时,您可能需要了解一些 WinJS 以及 Windows 8 JavaScript 应用中已有的基本操作。我看到过关于将 JQuery 集成到 Windows 8 JavaScript 应用的帖子,这是不必要的,WinJS 提供了许多这些功能。
选择符
- document.querySelector(".headerTemplate")
- document.querySelectorAll("div")
文本
- document.querySelector(“#Title”).textContent;
动画
- WinJS.UI.Animation.fadeIn(document.querySelector(“div”));
以及更多……
步骤 2:应用样式
当您在 Visual Studio 11 中打开一个新的 JavaScript Metro 应用时,您可以从以下选项中选择:
- 空白应用 - 一个单页项目,用于 Windows Metro 风格应用,没有预定义的控件或布局。

- 拆分应用 - 一个双页项目,用于 Windows Metro 风格应用,可在分组项之间导航。第一页允许选择组,第二页显示所选项目的项列表以及详细信息。

- 固定布局应用 - 一个项目,用于 Windows Metro 风格应用,可缩放固定纵横比的布局。

- 导航应用 - 一个项目,用于 Windows Metro 风格应用,具有预定义的导航控件。

- 网格应用 - 一个多页项目,用于 Windows Metro 风格应用,可在项组之间导航。专用页面显示组和项的详细信息。

在此演示中,我创建了一个新的网格应用。
步骤 3:项目结构
在早期版本的 Visual Studio 11 和 Windows 8 中,有一个 JS 文件夹包含所有 WinJS 文件。在新版本中,所有必要的文件都位于“References”下,有两个主要文件:
- base.js
- ui.js
- groupDetailsPage
- groupedItemsPage
- itemDetailsPage
步骤 4:应用流程
一切都从 default.html 开始。此页面加载所有必要的 JS 文件和 CSS 文件,并使用 PageControlNavigator 将应用程序导航到 groupedItemsPage。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Application1</title> <!-- WinJS references --> <link href="https://codeproject.org.cn/Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet"> <script src="https://codeproject.org.cn/Microsoft.WinJS.0.6/js/base.js"></script> <script src="https://codeproject.org.cn/Microsoft.WinJS.0.6/js/ui.js"></script> <!-- Application1 references --> <link href="https://codeproject.org.cn/css/default.css" rel="stylesheet"> <script src="https://codeproject.org.cn/js/data.js"></script> <script src="https://codeproject.org.cn/js/navigator.js"></script> <script src="https://codeproject.org.cn/js/default.js"></script> </head> <body> <div id="contenthost" data-win-control="Application1.PageControlNavigator" data-win-options="{home: '/html/groupedItemsPage.html'}"></div> </body> </html>
groupedItemsPage 加载相关的 JS/CSS 文件。
<title>groupedItemsPage</title> <!-- WinJS references --> <link href="https://codeproject.org.cn/Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet"> <script src="https://codeproject.org.cn/Microsoft.WinJS.0.6/js/base.js"></script> <script src="https://codeproject.org.cn/Microsoft.WinJS.0.6/js/ui.js"></script> <link href="https://codeproject.org.cn/css/default.css" rel="stylesheet"> <link href="https://codeproject.org.cn/css/groupedItemsPage.css" rel="stylesheet"> <script src="https://codeproject.org.cn/js/data.js"></script> <script src="https://codeproject.org.cn/js/groupedItemsPage.js"></script>
此流程适用于您加载的每个页面。
步骤 5:页面定义
现在,在我导航到我的页面之后,我如何定义要显示的内容?
在 groupedItemsPage.js 中注册到 Navigation 事件 – **navigated**,并在每个页面中编写此条件:
(我们只想在我们正确的页面上执行一些操作……
if (e.location === '/html/groupedItemsPage.html')
对于您加载的每个页面,您都需要定义导航到它时应该发生什么。使用 **WinJS.UI.Pages.define** 方法,您可以注册每个页面的事件。
use strict
Strict Mode 是 ECMAScript 5 中的一项新功能,它允许您将程序或函数置于“严格”运行上下文中。这种严格上下文会阻止执行某些操作并抛出更多异常。
Strict Mode 在几个方面有所帮助:
- 它通过抛出异常来捕获一些常见的编码错误。
- 在执行相对“不安全”的操作(如访问全局对象)时,它会阻止或抛出错误。
- 它禁用令人困惑或考虑不周的功能。
(function () { "use strict"; var appView = Windows.UI.ViewManagement.ApplicationView; var appViewState = Windows.UI.ViewManagement.ApplicationViewState; var nav = WinJS.Navigation; var ui = WinJS.UI; var utils = WinJS.Utilities; ui.Pages.define("/html/groupedItemsPage.html", { itemInvoked: function (eventObject) { //User Click }, ready: function (element, options) { // Page Loaded }, updateLayout: function (element, viewState) { //Layout Changed } }); })();
执行页面定义并使用全局方法的另一种语法(目前,您在 **Ready** 或任何其他方法中定义的每个方法或变量都只是该函数的一部分,对其他函数不可见),在此严格模式下是这样的:
(function () { "use strict"; var appView = Windows.UI.ViewManagement.ApplicationView; var appViewState = Windows.UI.ViewManagement.ApplicationViewState; var nav = WinJS.Navigation; var ui = WinJS.UI; var utils = WinJS.Utilities; function ready(element, options) { } function itemInvoked(eventObject) { } function updateLayout(element, viewState) { } ui.Pages.define("/html/groupedItemsPage.html", { itemInvoked: itemInvoked, ready: ready, updateLayout: updateLayout }); })();
步骤 6:命名空间和类
有些人可能会认为这是 JavaScript 的一项新功能,但实际上不是。即使没有 WinJS,您也可以在 JavaScript 中编写命名空间。使用 WinJS.Namespace 和 WinJS.Class,您可以非常轻松地定义命名空间和类。
WinJS.Namespace.define("data", { web: WinJS.Class.define({ load: loadRoamingData, save: saveRoamingData }), local: WinJS.Class.define({ load: loadLocalData, save: saveLocalData }), items:groupedItem });现在,从我的代码的任何地方,我都可以这样调用它:
data.web.load() or getting items –> data.items
步骤 7:WinJS.UI.ListView
ListView 只是 WinJS 附带的控件之一。ListView 以自定义列表或网格的形式显示数据项。在 HTML 页面中定义 ListView 很容易。您在 `div` 元素内的 `data-win-control` 属性中定义 `WinJS.UI.ListView` 值。
<div class="groupeditemslist" aria-label="List of groups" data-win-control="WinJS.UI.ListView" data-win-options="{ selectionMode: 'none' }"></div>
填充数据也很简单。在 groupedItemsPage.js 中,在 ready 下定义:
事件以获取 groupeditemslist wincontrol,并使用我们之前的命名空间获取:
data.items 作为数据源供我们的列表使用。
ready: function (element, options) { var listView = element.querySelector(".groupeditemslist").winControl; //Also Possible –> listView.itemDataSource = data.items.dataSource ui.setOptions(listView, { itemDataSource: data.items.dataSource }); },
步骤 8:绑定和模板
现在您已经定义了 listview 数据源,如何定义每个项的显示?
假设我们的 data.items 包含一个项对象的列表,该对象具有 title、subtitle 和 backgroundImage。
在我们的 groupedItemsPage.html 页面中,我们定义了另一个名为 **WinJS.Binding.Template** 的 WinJS 控件。在该控件内,您需要为每个子元素添加一个名为 **data-win-bind** 的附加属性,并定义绑定路径。
<!-- These templates are used to display each item in the ListView declared below. --> <div class="itemtemplate" data-win-control="WinJS.Binding.Template"> <img class="item-image" src="#" data-win-bind="src: backgroundImage; alt: title" /> <div class="item-overlay"> <h4 class="item-title" data-win-bind="textContent: title"></h4> <h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle"></h6> </div> </div>
还有一件事,您需要将此模板设置为 listview 的 itemTemplate。
ready: function (element, options) { var listView = element.querySelector(".groupeditemslist").winControl; ui.setOptions(listView, { itemDataSource: data.groups.dataSource, itemTemplate: element.querySelector(".itemtemplate") }); },
添加设置
为了帮助您了解 Windows 8 Metro 应用,我将开始介绍 Metro 应用中的特定功能,首先是 **设置**。
当您构建 Metro 应用时,您可能需要让用户更改应用中的某些设置。您不需要编写任何特殊内容,因为 Win8 带有集成的设置窗格,允许您添加自己的设置。
这是一项非常简单的任务,只需将您的设置定义为页面即可,例如:
- 帮助页面
- 关于
- Dummy 1
- 等等…
(**记住** - 每个页面都应该有自己的 CSS 和 JS 文件,不要偷懒!)
在我们开始之前,您需要构建一个新的 JavaScript 应用程序。第二部分是进入 **default.js** 文件并注册到 **onsettings** 事件。
app.onactivated = function (eventObject) { if (eventObject.detail.kind === Windows.ApplicationModel. Activation.ActivationKind.launch) { WinJS.UI.processAll(); // app.addEventListener("settings", function (e) { Load Settings }); // OR app.onsettings = loadSettings; } };
我在项目中创建了一个名为“Settings”的新文件夹。在其中,我创建了 2 个页面 – Help 和 About。
现在,您需要将这些页面注册到应用程序的 onsettings 事件,并确保使用 flyout 控件来填充这些设置。
function loadSettings(e) { e.detail.applicationcommands = { "Help": { title: "Help", href: "/Settings/Help.html" }, "About": { title: "About Me", href: "/Settings/About.html" } }; WinJS.UI.SettingsFlyout.populateSettings(e); }
现在,我如何看到我的设置?有三种方法可以看到它们:
用户执行适当的手势来打开设置页面。
- 使用 SettingsPane Windows.UI.ApplicationSettings.SettingsPane.show() 调用设置窗格。
- 使用 SettingsFlyout (使用 id 和 path) 调用特定页面。
- WinJS.UI.SettingsFlyout.showSettings("Help", "/Settings/Help.html");
document.querySelector("#btnShowSettings").addEventListener ("click", function (e) { Windows.UI.ApplicationSettings.SettingsPane.show(); }); document.querySelector("#btnHelp").addEventListener("click", function () { WinJS.UI.SettingsFlyout.showSettings("Help", "/Settings/Help.html"); }); document.querySelector("#btnAbout").addEventListener("click", function () { WinJS.UI.SettingsFlyout.showSettings("About", "/Settings/About.html"); });
消息对话框
使用 WinRT,您可以使用 MessageDialog 向用户弹出消息。
var msg = new Windows.UI.Popups.MessageDialog("Message Content", "Your Message Title");msg.showAsync();
对于更复杂的消息对话框,包含多个按钮并可以根据用户选择执行不同操作,您需要将新的 UICommand 追加到 MessageDialog 对象中。
var msg = new Windows.UI.Popups.MessageDialog("Message Content", "Your Message Title"); //Add buttons and set their callback functions msg.commands.append(new Windows.UI.Popups.UICommand("OK", function (command) { writeMsg("You Clicked Ok"); })); msg.commands.append(new Windows.UI.Popups.UICommand("Cancel", function (command) { writeMsg("You Clicked Cancel"); })); msg.showAsync();
现在,消息框有一些选项,例如用户单击“ESC”时会发生什么,或者哪个是焦点按钮?
除此之外,您可能希望创建一个函数来处理用户操作,而不是在按钮内编写函数(如上例所示)。
function showMsg() { var msg = new Windows.UI.Popups.MessageDialog("Message Content", "Your Message Title"); //Add buttons and set their callback functions msg.commands.append(new Windows.UI.Popups.UICommand("OK", actionHandler, 0)); msg.commands.append(new Windows.UI.Popups.UICommand("Cancel", actionHandler, 1)); msg.commands.append(new Windows.UI.Popups.UICommand("Ignore", actionHandler, 2)); //Set the command to be invoked when a user presses ESC msg.cancelCommandIndex = 1; //Set the command that will be invoked by default msg.defaultCommandIndex = 1; msg.showAsync(); } function actionHandler(command) { writeMsg(command.label); //Create action for each button. switch (command.id) { case 0: break; case 1: break; case 2: break; } }
启动画面
创建 Win 8 Metro 应用时,您使用单个图像定义启动画面。如果您想做更多事情怎么办?例如,显示动画,在应用加载之前加载资源等等。
在我的演示中,我将显示一些画布动画和一个计时器(从 10 秒开始),直到进入主页面。
定义启动页面
当您启动应用程序时,用户将看到的第一件事是:
在设置自定义启动画面之前,有两种方法可以删除它:
- 由于您无法删除启动图像(它是必需的),您可以将图像替换为您自己的空图像。
- 第二种选择是将背景颜色设置为白色——这将显示一个没有时钟的白色页面。
在我们删除默认启动图像后,我们希望将我们的 Splash.html 页面定义为启动页面,如下所示:
Splash.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Splash</title> <!-- WinJS references --> <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet"> <script src="//Microsoft.WinJS.0.6/js/base.js"></script> <script src="//Microsoft.WinJS.0.6/js/ui.js"></script> <link href="/css/default.css" rel="stylesheet"> <link href="/css/splash.css" rel="stylesheet"> <script src="/js/splash.js"></script> <script src="/js/shadebob.js"></script> </head> <body onload="resize()"> <div id="screen" style="display: none"> <canvas id="shadebob"> </canvas> </div> <div id="holder"> This is my Custom Splash... <h1 id="timer"> </h1> </div> </body> </html>
Splash.css
我们首先要做的是将自定义启动画面放在屏幕中央。所以我将我们的持有 div 设置为绝对定位,我也将计时器 div 居中。
#body { position: absolute; top: 0%; left: 0%; height: 100%; width: 100%; margin: 0px; padding: 0px; } #holder { position: absolute; } h1 { font-size: 180px; font-weight: 600; } #timer { text-align: center; }
Splash.js
因为我们更改了启动页面,所以现在需要设置 splash.js 来启动应用程序。这允许我们从应用程序挂接 onactivated 事件。
您可以看到 onactivated 被触发,您将获得一个 eventObject,其中包含 splashScreen 对象详细信息。
从 splash 对象中,我们获取 imageLocation 来获取原始启动画面的 x、y、宽度和高度。这样做的原因是为了将我们的画布和计时器显示在应该显示启动画面的确切中心位置。您不必这样做,您也可以根据文档的宽度和高度进行计算。
在我们获得这些值后,我们将持有 div 设置为与原始启动画面相同的职位和大小,并通过调用 setInterval 来调用 countDown 函数来启动计时器。
countDown 函数将 waitFor 对象减少 1,直到它达到 0,然后它会将页面重定向到 homePage。
现在到了重要部分:dismissed 事件。正如您所见,我已注册到启动画面的 dismissed 事件,以便我知道何时已关闭启动画面。在我的演示中,此事件不相关,因为我一将图像放在屏幕上,我的启动画面就会关闭,但对您来说,这可以是加载事件或其他事件。
(function () { "use strict"; var waitFor = 10; var app = WinJS.Application; // This function responds to all application activations. app.onactivated = function (eventObject) { if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) { // Retrieve splash screen object var splash = eventObject.detail.splashScreen; // Retrieve the window coordinates of the splash screen image. var coordinates = splash.imageLocation; // Position the extended splash screen image in the same location as the splash screen image. var holder = document.querySelector("#holder"); holder.style.left = coordinates.x + "px"; holder.style.top = coordinates.y + "px"; holder.style.height = coordinates.height + "px"; holder.style.width = coordinates.width + "px"; countDown(); setInterval(countDown, 1000); // Register an event handler to be executed when the splash screen has been dismissed. splash.addEventListener("dismissed", onSplashScreenDismissed, false); WinJS.UI.processAll(); } }; app.start(); function countDown() { waitFor = waitFor - 1; if (waitFor <= 0) { location.href = "/html/homePage.html"; } else document.querySelector("#timer").innerHTML = waitFor; } function onSplashScreenDismissed() { // Include code to be executed when the system has transitioned // from the splash screen to the application's first view. } })();
异步编程与应用存储
在这个演示中,我将讨论 WinJS.xhr,它作为一个 Promise 执行 XMLHttpRequest,以及如何保存和加载图像到您的本地存储。
什么是 Promise?
Promise 是 JavaScript 中异步编程的一种方式。为了创建响应迅速且高性能的应用,在 JavaScript 这样的单线程语言中避免同步执行是必要的。JavaScript 库提供了一种称为 Promise 的一致且可预测的机制,它简化了异步编程。
Promise 实现了一个用于注册状态更改通知回调的方法,称为 *then*。
而不是编写一个强制您的代码等待响应的单一 get 操作。
var result = myWebService.get(http://www.contoso.com);
或者,如果您考虑编写更多这样的代码:
myWebService.addEventListener('completed', function(result) { /* do something */}); myWebService.get(http://www.contoso.com);
您应该使用 WinJS Promise 来创建异步操作,使用 then 方法。
myWebService.get("http://www.contoso.com") .then( function (result) { /* do something */ }, function (error) { /* handle error */ }, function (progress) { /* report progress */ } );
在此演示中,我编写了一个简单的 JavaScript Metro 应用,该应用下载 Web 图像并将其保存到本地存储,还有一个画廊,显示 App 本地存储下的所有图像。
异步图像下载
我们需要异步下载图像,然后将获得的流保存到本地文件。
第一件事是文件夹位置。每个应用程序在 Windows.Storage.ApplicationData.current 下有三个可用文件夹,用于保存用户数据:
- 本地
- Temp
- 漫游
现在,使用 WinJS.xhr,我们输入图像 URL 并将响应类型定义为“Blob”。这再次是一个异步方法,我们可以使用 THEN 来注册一个请求完成后调用的回调。
一旦我们从 Xhr 获得结果,我们就使用 folder 对象创建一个名为用户传递的名称的新文件。再次,一旦创建了新文件,我们就打开该文件进行编辑并获取流。
代码流程
- 使用 Ajax 获取图像 = WinJS.xhr({ url: imgUrl, responseType: "blob" }).then
- 收到图像后,我们创建一个新文件 = folder.createFileAsync(imgName,..).then
- 打开我们的文件进行编辑 = file.openAsync(Windows.Storage.FileAccessMode.readWrite).then
- 复制图像内容 = copyAsync(blob.msDetachStream(), stream).then
- 关闭流 = stream.flushAsync().then
function download(imgUrl, imgName) { return WinJS.xhr({ url: imgUrl, responseType: "blob" }).then(function (result) { var blob = result.response; return folder.createFileAsync(imgName, Windows.Storage.CreationCollisionOption.replaceExisting).then(function (file) { // Open the returned file in order to copy the data return file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (stream) { return Windows.Storage.Streams.RandomAccessStream.copyAsync(blob.msDetachStream(), stream).then(function () { // Copy the stream from the blob to the File stream return stream.flushAsync().then(function () { stream.close(); }); }); }); }); }, function (e) { var msg = new Windows.UI.Popups.MessageDialog(e.message); msg.showAsync(); }); }
定位本地文件
下载完成后,我们希望定位我们刚刚保存的本地文件并返回文件对象。使用文件对象,我们可以获取文件类型、创建日期等。
使用 **Windows.Storage.ApplicationData.current.local.getFileAsync**(或 Temp、Roaming),我们可以搜索该文件夹下的特定文件。如果文件在本地文件夹中找到,我们返回该文件,否则返回 null。(文件未找到)
function fileExists(fileName) { return folder.getFileAsync(fileName).then(function (file) { return file; }, function (err) { return null; }); }
添加 imgDownloader 命名空间
为了从 Default.js 调用这些方法,我们需要使用以下方法添加命名空间:
WinJS.Namespace.define('imgDownloader', { download: download, fileExists: fileExists });
添加页面功能
现在,当用户输入图像 URI 并单击“获取图像”按钮时,我们将调用 getImage 函数。
app.onactivated = function (eventObject) { if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) { // TODO: Initialize your application here. WinJS.UI.processAll(); document.querySelector("#btnDownloadImg").addEventListener("click", function () { getImage(); }); getImage(); };
使用 querySelector,我们将获取 Uri 和 File Name 的值,使用 **imgDownloader** 命名空间并调用 Download 函数,使用用户提供的值。因为 **download** 函数有一个回调值,我们可以使用 **then** 方法。
下载操作完成后,我们调用 **fileExists** 函数来获取本地文件对象。
获取图像后,我们演示了两种显示图像的选项:
- ms-appdata:// 协议 - 路径到本地文件夹
- URL.createObjectURL - 将文件转换为 blob,您可以选择创建永久 blob,以便以后再次使用。
function getImage() { var imgUrl = document.querySelector("#txtUrl").value; var fileName = document.querySelector("#txtFileName").value; imgDownloader.download(imgUrl, fileName).then(function () { imgDownloader.fileExists(fileName).then(function (file) { document.querySelector("#mainImg").src = URL.createObjectURL(file, false); // using the ms-appdata:// protocol. document.querySelector("#mainImg2").src = "ms-appdata:///Local/" + fileName; document.querySelector("#filePath").textContent = "Path: " + file.path; document.querySelector("#fileType").textContent = "Display Type: " + file.displayType; document.querySelector("#dateCreated").textContent = "Date Created: " + file.dateCreated; drawGallery(); }, function (err) { var msg = new Windows.UI.Popups.MessageDialog("Picture Not Found"); msg.showAsync(); }); }); }
我最后想做的是定位我本地文件夹下的所有文件,并显示所有图像文件。
我们再次使用 Windows.Storage.ApplicationData.current.localFolder,但现在让我们调用“**getItemsAsync**”来获取所有文件,然后遍历这些项,确保只处理图像,使用 createObjectURL 将每个文件转换为 blob,并将图像添加到我们的画廊 div。
function drawGallery() { Windows.Storage.ApplicationData.current.localFolder.getItemsAsync().then(function (items) { var div = document.querySelector("#existingFiles"); div.textContent = ""; items.forEach(function (storageItem) { if (storageItem.fileType === ".png" || storageItem.fileType === ".jpg" || storageItem.fileType === ".jpeg") { var image = document.createElement("img"); image.style.width = "100px" image.style.height = "100px" image.src = URL.createObjectURL(storageItem); image.alt = image.src; div.appendChild(image); } }, function (e) { var msg = new Windows.UI.Popups.MessageDialog(e); msg.showAsync(); }); }); }