三、整合布局和控制

Abstract

现代 Windows 用户界面编程总是涉及某种形式的控件,即以用户体验的形式封装可预测行为的可重用用户界面元素。Windows 8 并没有改变 Windows 开发的这一原则。在这一章中,您将了解到作为一名 Windows 8 JavaScript 开发人员,控件是如何以按钮、列表框等交互控件的形式向您展示的;和布局控件,您可以使用它们来构建应用用户界面的组织方式。

现代 Windows 用户界面编程总是涉及某种形式的控件,即以用户体验的形式封装可预测行为的可重用用户界面元素。Windows 8 并没有改变 Windows 开发的这一原则。在这一章中,您将了解到作为一名 Windows 8 JavaScript 开发人员,控件是如何以按钮、列表框等交互控件的形式向您展示的;和布局控件,您可以使用它们来构建应用用户界面的组织方式。

如第 2 章中所述,Windows 8 JavaScript 开发不同于 Windows 8 开发者可用的其他用户界面渲染引擎,它基于 HTML5。这意味着 Windows 8 应用的布局和控件首先要使用纯 HTML +CSS3。此外,使用前一章中概述的控件集成模式,应用开发人员可以使用新功能扩展 HTML5 布局引擎,将使用 WinJS 构建的 JavaScript 应用与 Windows 8 原生外观紧密联系起来。特别是,第 2 章讨论了应用栏,并展示了如何将它整合到你的应用用户界面中。

本章介绍了如何在 Windows 8 应用开发中整合布局和控件,您首先要创建一个项目。这个示例项目将作为一个模板,您可以从中构建基本的控件。本章讨论并解释了这些标准控件,然后讨论了数据驱动控件及其数据绑定的使用。

设置项目

第 2 章讨论了 Windows 8 开发的许多基础元素。本章开始时,您将快速浏览一个 Windows Runtime(WinRT)for JavaScript 项目。您创建了众所周知的“Hello World”应用——在本例中是“Hello Windows 8 with JavaScript”尽管您可以使用任何 HTML 兼容的应用(或者记事本,如果您愿意)来完成大部分开发和用户界面布局,但是您需要 Visual Studio 2012 来将您的应用编译到第 2 章中讨论的应用包中。Visual Studio 还为您提供了一些部署工作流自动化工具,对于为 Windows 应用商店构建应用的 Windows 8 开发人员来说,这些工具非常有价值。

首先,启动 Visual Studio 2012。图 3-1 显示了 Visual Studio 2012 IDE 在我的机器上的外观。如果您更改了 Visual Studio 2012 的配色方案或安装了各种其他技术的加载项,则它在您的计算机上可能会有所不同。

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

图 3-1。

Visual Studio 2012

本章没有花时间介绍 IDE 的各个部分。详细讨论可以参考 Adam Freeman (Apress 2012)的《Pro Visual Studio 2012》之类的书。出于本练习的目的,需要注意的重要部分是中心内容区域、左侧的工具箱以及右侧的解决方案资源管理器。稍后,您将创建一个新项目,并查看当项目在范围内时,屏幕上的这些区域是如何“点亮”的。这是因为,像 Visual Studio 2010 中的许多视图一样,它们是上下文感知的:根据所选择的内容(在某些情况下,中心内容区域中的文档当前是活动的),其他关联窗口的视图可能会发生变化。在继续下一步之前,花点时间体验一下 IDE 布局。您可以分离和重新附加几乎任何内容,也可以取消固定默认的侧边部分(解决方案资源管理器、工具箱),以最大化内容区域的空间。您甚至可以将整个内容区域拖到另一个屏幕并最大化它,有效地将整个屏幕用于内容,没有菜单、工具栏或干扰。

您可以通过选择文件➤新➤项目来创建新项目。在我的机器上,结果对话框看起来如图 3-2 所示。

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

图 3-2。

New Project dialog in Visual Studio 2012

Visual Studio 2012 使用使用率配置文件机制来突出用户感兴趣的活动,而不是用户不感兴趣的功能。根据您第一次运行 Visual Studio 2012 时的设置方式,您可能会被锁定为 C#开发人员、Web 开发人员、数据库开发人员或任何其他配置文件类型。在我的机器上,C#是我使用的默认配置文件;因此,当我启动新的项目活动时,Visual C#项目是我选择的最重要的内容。如果我想构建 Visual Basic、Visual C++、SQL Server,当然还有 JavaScript,我必须在“新建项目”对话框的“其他语言”部分中查找。

Visual Studio 2012 中的 C#配置文件包含许多 Visual Studio 允许您创建的项目类型。使用 C#,您可以创建从 Windows 服务到 Windows Phone 8 应用的任何东西。如今,像 Xamarin 这样的公司已经通过插件将 C# / Visual Studio 2012 平台提升到了一个新的水平,这些插件允许 C#开发人员甚至可以构建 iOS 和 Android 应用。图 3-3 显示了同一个对话框:这次 JavaScript 部分被展开,显示了通过 JavaScript 公开的 Windows Store 项目。

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

图 3-3。

New project templates for Windows 8 JavaScript apps

在“名称”文本栏中输入对您有意义的名称,以及项目文件的储存位置。然后单击 OK 按钮生成项目。图 3-4 显示了项目创建后的解决方案资源管理器窗口。

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

图 3-4。

Project items of a newly created WinJS project

正如所料,该项目包含 JavaScript、HTML 和 CSS 文件——包含默认图像只是为了显示磁贴图标,而不是应用的内容。清单 3-1 显示了默认 JavaScript 文件default.js的内容。

Listing 3-1. Default JavaScript File of the New Project

// For an introduction to the Blank template, see the following documentation:

//http://go.microsoft.com/fwlink/?LinkId=232509T3】

(function () {

"use strict";

WinJS.Binding.optimizeBindingReferences = true;

var app = WinJS.Application;

var activation = Windows.ApplicationModel.Activation;

app.onactivated = function (args) {

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

if (args.detail.previousExecutionState !==

activation.ApplicationExecutionState.terminated) {

// TODO: This application has been newly launched. Initialize

// your application here

} else {

// TODO: This application has been reactivated from suspension

// Restore application state here

}

args.setPromise(WinJS.UI.processAll());

}

};

app.oncheckpoint = function (args) {

// TODO: This application is about to be suspended. Save any state

// that needs to persist across suspensions here. You might use the

// WinJS.Application.sessionState object, which is automatically

// saved and restored across suspension. If you need to complete an

// asynchronous operation before your application is suspended, call

// args.setPromise()

};

app.start();

})();

清单 3-2 显示了应用的相关 HTML 内容。

Listing 3-2. Default HTML File of the New Project

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>FirstApp</title>

<!-- WinJS references -->

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

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

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

<!-- FirstApp references -->

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

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

</head>

<body>

<p>Content goes here</p>

</body>

</html>

现在您已经展示了一个项目示例,让我们继续讨论控件。在讨论使用数据绑定的数据驱动控件之前,先深入研究标准控件。

合并 HTML 控件

正如在第 2 章中提到的,使用 Windows JavaScript 库的一个主要好处是它本质上是纯 HTML。这一区别于在基于 C#或 C++的 Windows 8 应用中发现的纯粹 WinRT 的因素并不比在控件的情况下更明显。因为 WinJS 应用是使用 HTML、CSS 和 JavaScript 的 web 标准构建的,所以它们支持将 HTML 控件集用于应用布局、样式和交互性编程。

Note

可以在微软 MSDN 网站 http://msdn.microsoft.com/library/windows/apps/hh767345.aspx 上找到 Windows Store JavaScript 应用支持的 HTML 控件的完整列表。

了解控件

除了 HTML 控件之外,您还可以访问一些作为类投射到 WinJS 中的 WinRT 控件。这些类型的控件的一个例子是您在上一章中使用的AppBar控件。注意,并不是所有的 WinRT 控件都被投射到 JavaScript 中。例如,命名空间Windows.UI.Xaml.Controls中的Border类,可以通过工具箱在 C++和 C#中获得,在 XAML 中,也可以在代码中作为可以实例化并附加到文档树的类获得。正如你在图 3-5 中看到的,尽管 WinJS 知道名称空间,但是它没有一个用于Border的类。

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

图 3-5。

Lack of XAML controls in WinJS

最后,控件也可以是 JavaScript 类的形式。作为开发人员,您可以编写这些类来降低用户界面构造的复杂性,并增加布局的灵活性。WinJS 附带了许多这种控件,最著名的是ListView控件。图 3-6 显示了默认网格应用模板上的内置ListView控件。在图 3-3 所示的新建项目对话框中,选择 grid app 而不是 Blank App,可以创建一个 Grid App。

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

图 3-6。

A grid app running

应用控件

正如你可能想象的那样,尽管这些技术彼此之间有很大的不同,但在使用 JavaScript 构建 Windows 8 应用时,你可以互换使用它们。在清单 3-3 中,您向示例项目添加了一个应用栏和一个 HTML 按钮,然后使用 JavaScript 事件处理程序打开一个消息框,计算按钮被点击的次数。从修改用户界面开始。清单 3-3 显示了新版本的default.html,增加了粗体字。

Listing 3-3. Example App with an App Bar Added

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>FirstApp</title>

<!-- WinJS references -->

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

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

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

<!-- FirstApp references -->

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

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

</head>

<body>

<p>Using Controls from different paradigms</p>

<input id="btn_button_count" type="button" value="Click to count" />

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

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

data-win-options="{id:'cmd', label:'Also count', icon:'placeholder'}"

type="button"></button>

</div>

</body>

</html>

您还可以更改表单的标题,使其更符合您的意图。如果您现在运行该应用,它应该看起来类似于图 3-7

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

图 3-7。

Using a simple HTML button in WinJS

右键单击屏幕(或从按钮上滑动以激活应用栏)会显示您添加到应用视图中的应用栏(参见图 3-8 )。

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

图 3-8。

The app bar in the example app

现在你已经修改了布局,你可以回到default.js并进行必要的修改来启用计数行为。清单 3-4 处理纯 HTML 和 WinRT 应用栏按钮的click事件。

Listing 3-4. Event Handling for Both WinJS and Pure HTML Controls

// For an introduction to the Blank template, see the following documentation:

//http://go.microsoft.com/fwlink/?LinkId=232509T3】

(function () {

"use strict";

WinJS.Binding.optimizeBindingReferences = true;

var app = WinJS.Application;

var activation = Windows.ApplicationModel.Activation;

var _count = 0;

app.onactivated = function (args) {

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

if (args.detail.previousExecutionState !==

activation.ApplicationExecutionState.terminated) {

// TODO: This application has been newly launched. Initialize

// your application here

} else {

// TODO: This application has been reactivated from suspension

// Restore application state here

}

args.setPromise(WinJS.UI.processAll());

}

_count = 0;

btn_button_count.onclick = function ()

{

_count++;

var mbox = new Windows.UI.Popups.MessageDialog("Button clicked; you clicked

something " + _count + " time(s)");

mbox.showAsync();

};

btn_appbar_count.onclick = function () {

_count++;

var mbox = new Windows.UI.Popups.MessageDialog("AppBar clicked; you clicked

something " + _count + " time(s)");

mbox.showAsync();

};

};

app.oncheckpoint = function (args) {

// TODO: This application is about to be suspended. Save any state

// that needs to persist across suspensions here. You might use the

// WinJS.Application.sessionState object, which is automatically

// saved and restored across suspension. If you need to complete an

// asynchronous operation before your application is suspended, call

// args.setPromise()

};

app.start();

})();

请注意,第三个控件MessageDialog也在示例中使用。当用户点击 HTML 按钮或应用栏按钮时,_count变量增加 1。允许这两种技术之间的这种透明性所必需的任何编组都是对您隐藏的。就开发人员而言,当用户点击应用栏按钮时,可以将其视为 HTML 按钮,使用标准的onclick事件处理程序来处理点击的效果。稍后,当您查看应用栏按钮的其他一些事件时,您会看到这种集成是多么强大和无缝——这些事件对于标准的 HTML 按钮是不可用的。图 3-9 显示了点击应用栏按钮时应用的用户界面。

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

图 3-9。

Message dialog when the app bar button is clicked

当点击 HTML 按钮时,_count变量增加并照常显示,但消息框显示其发起者(见图 3-10 )。消息框文本已从“点击了 AppBar,您点击了。 . .点击了“收件人”按钮,您点击了。 . . ."

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

图 3-10。

Message dialog when the HTML button is clicked

正如你从例子中看到的,整合标准的 HTML 控件相对简单——它的工作方式与 web 开发相同,所以如果你已经熟悉 HTML,你会很容易习惯这一点,甚至可以将基于 web 的应用移植到 Windows 8。对于单页应用(spa)来说尤其如此。SPAs 是托管在 web 上的 HTML 页面,基本上将所有功能整合到一个页面上。该页面调用它需要的任何后端服务,下载任何额外需要的 HTML 或 JavaScript,并在需要的地方本地存储内容。Gmail 就是一个很好的例子, Outlook.com 的邮件客户端也是。如果您使用过这些基于 web 的应用,您可能会注意到它们从不“空白”——这是浏览器因为服务器请求而清空屏幕的典型标志。诸如此类的网站广泛使用 HTML 和 JavaScript,是可以轻松移植到 Windows 8 的应用类型的重要示例(也是 Windows 8 JavaScript 开发能力的重要示例,因为这种应用的开发人员只需要一个版本的应用代码。)

合并 WinJS 控件

HTML 控件非常棒,从 UI 的角度来看,你可以将它们与标准的 HTML 特性结合使用来做 WinJS 控件能做的任何事情。但是如果你想快速启动并运行,WinJS 提供了许多控件。WinJS 控件使用与 Windows 应用商店应用相同的 HTML JavaScript 和 CSS 编写,因此它们可以与页面上的其他元素无缝集成。您可以在清单 3-3 中看到这一点。让我们给页面添加更多的控件:在清单 3-5 中,您添加了一个DatePicker

Listing 3-5. Using a DatePicker Control in HTML

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>FirstApp</title>

<!-- WinJS references -->

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

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

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

<!-- FirstApp references -->

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

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

</head>

<body>

<p>Using Controls from different paradigms</p>

<input id="btn_button_count" type="button" value="Click to count" />

<div id="control_datepicker" data-win-control="WinJS.UI.DatePicker"></div>

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

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

data-win-options="{id:'cmd', label:'Also count', icon:'placeholder'}"

type="button"></button>

</div>

</body>

</html>

新控件以粗体突出显示。图 3-11 显示添加了DatePicker控件的 app。

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

图 3-11。

DatePicker control in use

从这个例子中,您应该注意到两件事。首先,data-win-control属性中的控件名(自定义属性在第 2 章的中讨论过)与AppBar控件共享相同的名称空间。这个WinJS.UI命名空间是你可以找到所有 WinJS 控件的地方。第二,控件需要一个根元素来附加它们自己。在这种情况下,您使用一个div,但是可以使用任何元素。尝试将清单 3-4 中使用的div改为按钮,并再次运行该示例:您应该注意到整个控件周围有一个白条,其功能类似于按钮(意味着它是可点击的)。图 3-12 展示了这个诡异的 UI。

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

图 3-12。

DatePicker applied to a HTML button control

如果您将宿主控件切换到 input 元素,您会得到一个更奇怪的结果:显示一个输入文本框!这个故事的寓意是,要小心将控件属性应用于什么元素,因为它们仍然会试图呈现自己。使用divspan是理想的,因为当控件应用于它们时,它们似乎放弃了它们的标准行为。例如,基于 HTML 布局规则,一个div应该在它自己的行上;但是正如你在图 3-11 中看到的,它的行为就像一个内嵌元素。

这就引出了清单 3-4 中讨论的“无缝集成”的一个优点。如果你看看页面的 UI,btn_appbar_count的宿主不是一个div而是一个 HTML 按钮。因此,你可以对两个按钮使用相同的onclick事件;它们都是简单的 HTML 按钮。这就引出了这样一个问题:如果目的是呈现一个控件而不是基础元素,那么如何访问与该元素相关联的特定于控件的功能。例如,假设事件和属性可能与DatePicker控件相关联是正确的,它告诉您当前在选择器中选择的日期以及选择何时改变。控件也可以有一些方法,可以调用这些方法以某种编程方式实现自动化。例如,AppBar控件可能有你可以调用来显示和隐藏它的方法。为此,所有 WinJS 控件修饰的 HTML 元素在运行时都有一个winControl属性。winControl属性提供了从承载控件的 HTML 元素到实际 WinJS 控件的连接,并可用于访问与控件关联的任何事件、属性或方法。

整合 winControl

让我们用一些代码来修改正在进行的示例,以展示如何使用winControl。您可以使用该属性来处理当显示或隐藏应用栏时触发的事件,但是首先您需要更新页面的用户界面。在清单 3-6 中,您添加了一些基本的样式来使页面内容居中,还添加了一个新的控件来显示应用栏的当前状态。

Listing 3-6. Handling App Bar States

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>FirstApp</title>

<!-- WinJS references -->

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

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

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

<!-- FirstApp references -->

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

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

<style>

div {

padding: 5px;

}

.centered {

margin-top: 50px;

margin-left: auto;

margin-right: auto;

width: 900px;

box-shadow: 3px 3px 3px #000;

}

</style>

</head>

<body>

<div class="centered">

<p>Using Controls from different paradigms</p>

<input id="btn_button_count" type="button" value="Click to count" />

<div>

AppBar State: <span id="txt_appbarstate" >AppBar Hidden</span>

</div>

<div>

<div id="control_datepicker" data-win-control="WinJS.UI.DatePicker"></div>

</div>

</div>

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

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

data-win-options="{id:'cmd', label:'Also count', icon:'placeholder'}"

type="button"></button>

</div>

</body>

</html>

这里没有什么特别的:您添加了一个新的div,其中有一个span,显示应用的应用栏的当前状态。您还添加了一些将页面内容居中并应用阴影的基本样式。图 3-13 显示了 UI 现在的布局。

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

图 3-13。

New layout of the page with styling

有趣的事情发生在后端,如清单 3-7 所示。

Listing 3-7. Referencing a WinJS Control from an HTML Element

// For an introduction to the Blank template, see the following documentation:

//http://go.microsoft.com/fwlink/?LinkId=232509T3】

(function ()

{

"use strict";

WinJS.Binding.optimizeBindingReferences = true;

var app = WinJS.Application;

var activation = Windows.ApplicationModel.Activation;

var _count = 0;

app.onactivated = function (args)

{

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

{

if (args.detail.previousExecutionState !==

activation.ApplicationExecutionState.terminated)

{

// TODO: This application has been newly launched. Initialize

// your application here

} else

{

// TODO: This application has been reactivated from suspension

// Restore application state here

}

args.setPromise(WinJS.UI.processAll());

}

_count = 0;

btn_button_count.onclick = function ()

{

_count++;

var mbox = new Windows.UI.Popups.MessageDialog("Button clicked; you clicked

something " + _count + " time(s)");

mbox.showAsync();

};

btn_appbar_count.onclick = function ()

{

_count++;

var mbox = new Windows.UI.Popups.MessageDialog("AppBar clicked; you clicked

something " + _count + " time(s)");

mbox.showAsync();

};

appbar.winControl.addEventListener("aftershow", function ()

{

txt_appbarstate.innerText = "AppBar Showing";

});

appbar.winControl.addEventListener("afterhide", function ()

{

txt_appbarstate.innerText = "AppBar Hidden";

});

};

app.oncheckpoint = function (args)

{

};

app.start();

})();

注意,当连接事件处理程序时,这里使用了addEventListener约定。这不是强制性的;但是如果您选择使用它,请注意事件名称的on部分总是被排除在外。清单 3-8 显示了如果使用另一种方法,这两个事件处理程序会是什么样子。从例子中可以看出,应用栏显示或隐藏时的事件名称是onaftershowonafterhide,即使使用addEventListener时添加了aftershowafterhide。这是惯例,所以一定要记住它或者坚持一种方法。对于其他的例子,你可以使用清单 3-8 中的方法。

Listing 3-8. Using the Event Handler Convention for Handling WinJS Control Events

appbar.winControl.onaftershow = function ()

{

txt_appbarstate.innerText = "AppBar Showing";

};

appbar.winControl.onafterhide = function ()

{

txt_appbarstate.innerText = "AppBar Hidden";

};

3-14 显示了打开应用栏时 FirstApp 的用户界面。

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

图 3-14。

UI for FirstApp when the app bar is open

winControl运行时属性对于 WinJS 控件来说是一个不可避免的祸害,这是因为您迄今为止创建的控件的实例化方式。到目前为止,您已经通过用win-data-control属性修饰目标 HTML 元素向页面添加了控件(并在需要时可选地添加了win-data-options属性)。但是 WinJS 控件的伟大之处在于它们首先是类,因此可以在代码中实例化。以这种方式创建 WinJS 控件可确保您拥有实际控件实例的句柄。要看到这一点,您可以做一些小的代码更改。首先,在default.html中,修改应用栏 HTML 以匹配清单 3-9。请注意,div已被重命名为appbar_host

Listing 3-9. Changes to the App Bar Layout

<div id="``appbar_hostT2】

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

options="{id:'cmd', label:'Also count', icon:'placeholder'}" type="button"></button>

</div>

因为您已经从appbar_host div中移除了data-win-control声明,所以现在您所拥有的只是一个简单的 HTML div,可以说没有“特殊功能”。这是测试winControl是与附加到主机 HTML 元素的底层控件相关联的运行时属性的好时机。如果您通过 Visual Studio 界面运行代码(启用调试),您应该会得到如图 3-15 所示的错误。

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

图 3-15。

Error using winControl on standard HTML elements

如果遇到这个对话框,单击 Break 并将鼠标放在winControl属性上。正如所料,winControl是未定义的——这意味着它不作为appbar_host对象的属性而存在。

现在,让我们通过实例化一个新的AppBar类实例来改变后面的代码。用清单 3-10 中的代码替换当前的onactivated事件处理程序。

Listing 3-10. Instantiating an AppBar Control in JavaScript

app.onactivated = function (args)

{

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

{

if (args.detail.previousExecutionState !==

activation.ApplicationExecutionState.terminated)

{

// TODO: This application has been newly launched. Initialize

// your application here

} else

{

// TODO: This application has been reactivated from suspension

// Restore application state here

}

var appbar_instance = new WinJS.UI.AppBar(appbar_host);

appbar_instance.onafterhide = function ()

{

txt_appbarstate.innerText = "AppBar Hidden";

}

args.setPromise(WinJS.UI.processAll());

}

_count = 0;

btn_button_count.onclick = function ()

{

_count++;

var mbox = new Windows.UI.Popups.MessageDialog("Button clicked; you clicked

something " + _count + " time(s)");

mbox.showAsync();

};

btn_appbar_count.onclick = function ()

{

_count++;

var mbox = new Windows.UI.Popups.MessageDialog("AppBar clicked; you clicked

something " + _count + " time(s)");

mbox.showAsync();

};

appbar_host.winControl.onaftershow = function ()

{

txt_appbarstate.innerText = "AppBar Showing";

};

};

不是声明性地添加AppBar控件,而是实例化它,传入将承载它的目标元素。所有 WinJS 控件都需要 HTML 元素作为构造器的第一个参数。这相当于向目标元素添加了data-win-control属性(在本例中是appbar_host)。如果控件具有您选择定义的选项,则可以使用构造函数的第二个参数(可选参数)。如果这样做,选项将被表示为一个 JSON 对象。例如,如果你要构造一个AppBarCommand类,构造器的第二个参数将如清单 3-11 所示。

Listing 3-11. Example of Configuring Options from JavaScript

var appbarbutton_instance = new WinJS.UI.AppBarCommand(btn_appbar_count, { id: 'cmd', label:

'Also count', icon: 'placeholder' });

Note

WinJS 控件的关键是process函数,特别是WinJS.UI.process,不管它是通过 HTML 声明定义的还是在 JavaScript 中实例化的。当您使用声明性方法实例化 WinJS 控件时,这没有多大关系;但是重要的是要理解,必须在控件上调用process来呈现它,并且控件在调用process时呈现。是process函数添加了winControl运行时属性。在清单 3-10 中,JavaScript 进行了调用args.setPromise(WinJS.UI.processAll()),它有效地遍历了 DOM,对任何有关联控件的元素调用进程。可以想象,在 JavaScript 实例化控件的情况下,创建和关联它们的调用必须发生在调用processAll之前;所以一定要养成这样做的习惯。

潜入 WinJS 控件

WinJS 提供了几个控件,可以用来增强 Windows 8 JavaScript 应用。本节将详细介绍其中的几个,并举例说明如何在一般意义上使用它们。我建议查看 MSDN 的WinJS.UI主题,了解更多关于具体控制的细节。不过,在你开始使用控件之前,让我们花点时间回顾一下在第 1 章中开始的页面和导航主题。这样做有两个原因:首先,页面是控件,即使它们没有像其他控件一样被实例化或使用(它们也是通过WinJS.UI名称空间访问的);其次,页面是组织为每个控件创建的示例的好方法。

WinJS 导航框架既涉及页面的创建,也涉及一些应用的启动,以便让您能够启动并运行。在开始之前,让我们创建一个名为Demo的新文件夹和另一个名为AppBarDemo的文件夹。

要创建一个文件夹,从解决方案资源管理器中右键单击项目并从上下文菜单中选择添加,然后选择新建文件夹(参见图 3-16 )。

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

图 3-16。

Adding a new folder in Visual Studio 2012

将页面控件添加到项目中

在新创建的AppBarDemo文件夹中,右键单击并选择 Add,这次从上下文菜单中选择 New Item。在弹出的添加新项目对话框中选择页面控件,如图 3-17 ,命名为AppBarDemo,点击【添加】。

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

图 3-17。

Selecting the Page Control template

最终的解决方案资源管理器视图应该如图 3-18 所示。

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

图 3-18。

FirstApp project structure

这里要注意的是,使用页面控件模板创建页面不仅会添加页面的 HTML,还会添加 JavaScript 和 CSS。在AppBarDemo.html中添加的默认 HTML 如清单 3-12 所示。

Listing 3-12. AppBarDemo UI

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>AppBarDemo</title>

<!-- WinJS references -->

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

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

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

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

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

</head>

<body>

<div class="AppBarDemo fragment">

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

<button class="win-backbutton" aria-label="Back" disabled type="button"></button>

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

<span class="pagetitle">Welcome to AppBarDemo</span>

</h1>

</header>

<section aria-label="Main content" role="main">

<p>Content goes here.</p>

</section>

</div>

</body>

</html>

相关的默认 JavaScript 如清单 3-13 所示。

Listing 3-13. AppBarDemo JavaScript

// For an introduction to the Page Control template, see the following documentation:

//http://go.microsoft.com/fwlink/?LinkId=232511T3】

(function () {

"use strict";

WinJS.UI.Pages.define ("/Demos/AppBarDemo/AppBarDemo.html", {

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

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

ready: function (element, options) {

// TODO: Initialize the page here

}

unload: function () {

// TODO: Respond to navigations away from this page

}

updateLayout: function (element, viewState, lastViewState) {

/// <param name="element" domElement="true" />

// TODO: Respond to changes in viewState

}

});

})();

您可以看到,页面是通过调用WinJS.UI.Pages.define(以粗体突出显示)并传入页面用户界面的路径(以/Demos/AppBarDemo/AppBarDemo.html的形式)和一个表示页面的类来定义的(在本例中,您使用了一个包含readyunloadupdateLayout函数的匿名对象)。这个类至少应该包含ready方法,因为当页面完全初始化时会调用这个方法。

修改演示以说明页面

您需要重组原始的default.html以使用突出显示的方法。default.html文件现在应该如清单 3-14 所示。

Listing 3-14. Restructured default.html Page

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>FirstApp</title>

<!-- WinJS references -->

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

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

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

<!-- FirstApp references -->

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

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

</head>

<body>

<div id="control_pagehost" style="height:80%"></div>

<div>

<button id="btn_appbardemo">AppBar demo</button>

</div>

</body>

</html>

default.js文件如清单 3-15 所示。

Listing 3-15. Restructured default.js

// For an introduction to the Blank template, see the following documentation:

//http://go.microsoft.com/fwlink/?LinkId=232509T3】

(function ()

{

"use strict";

WinJS.Binding.optimizeBindingReferences = true;

var app = WinJS.Application;

var activation = Windows.ApplicationModel.Activation;

app.onactivated = function (args)

{

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

{

if (args.detail.previousExecutionState !==

activation.ApplicationExecutionState.terminated)

{

// TODO: This application has been newly launched. Initialize

// your application here

} else

{

// TODO: This application has been reactivated from suspension

// Restore application state here

}

}

args.setPromise(WinJS.UI.processAll());

};

app.oncheckpoint = function (args)

{

};

app.start();

})();

现在,您用新的布局和支持它的 JavaScript 代码来修改刚刚创建的AppBarDemo页面。最初的 HTML 和 JavaScript 是为了实现基本功能而放在那里的样板代码。新代码增加了交互性和独特的用户界面布局。AppBarDemo.html如清单 3-16 所示。

Listing 3-16. Restructured AppBarDemo.html

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>AppBarDemo</title>

<!-- WinJS references -->

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

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

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

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

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

<style>

div {

padding: 5px;

}

.centered {

margin-top: 50px;

margin-left: auto;

margin-right: auto;

width: 400px;

box-shadow: 3px 3px 3px #000;

}

</style>

</head>

<body>

<div class="centered">

<p>Using Controls from different paradigms</p>

<input id="btn_button_count" type="button" value="Click to count" />

<div>

AppBar State: <span id="txt_appbarstate">AppBar Hidden</span>

</div>

<div>

<div id="control_datepicker" data-win-control="WinJS.UI.DatePicker"></div>

</div>

</div>

<div id="appbar_host">

<button id="btn_appbar_count" type="button"></button>

</div>

</body>

</html>

最后,AppBarDemo.js如清单 3-17 所示。

Listing 3-17. Restructured AppBarDemo.js

// For an introduction to the Page Control template, see the following documentation:

//http://go.microsoft.com/fwlink/?LinkId=232511T3】

(function () {

"use strict";

var _count = 0;

WinJS.UI.Pages.define("/Demos/AppBarDemo/AppBarDemo.html", {

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

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

ready: function (element, options) {

_count = 0;

var appbarbutton_instance = new WinJS.UI.AppBarCommand(btn_appbar_count,ⅵ

{ id: 'cmd', label: 'Also count', icon: 'placeholder', });

var appbar_instance = new WinJS.UI.AppBar(appbar_host);

btn_button_count.onclick = function ()

{

_count++;

var mbox = new Windows.UI.Popups.MessageDialog("Button clicked; you clicked

something " + _count + " time(s)");

mbox.showAsync();

};

appbar_instance.onbeforeshow = function ()

{

txt_appbarstate.innerText = "AppBar Showing";

}

appbar_instance.onbeforehide = function ()

{

txt_appbarstate.innerText = "AppBar Hidden";

}

appbarbutton_instance.onclick = function ()

{

_count++;

var mbox = new Windows.UI.Popups.MessageDialog("AppBar clicked; you clicked

something " + _count + " time(s)");

mbox.showAsync();

}

}

});

})();

将 AppBarDemo 连接到 default.html

在这一点上,如果您要运行这个示例,您会看到一个空白屏幕,这并不奇怪。这是因为页面尚未添加到default.html。与典型的控件不同,PageControl不需要遵循你在前面章节中看到的AppBar的实例化模式。您可以通过多种方式查看刚刚创建的页面。然而,无论您使用哪种方法,您总是需要一个标准的 HTML 控件来承载它。这就是为什么在清单 3-14 中,您添加了一个新的divcontrol_pagehost

在主屏幕上显示页面的最简单方法是调用

WinJS.UI.Pages.render("/demos/appbardemo/appbardemo.html",control_pagehost);

您希望仅在点击btn_appbardemo按钮时这样做,否则显示默认介绍页面。让我们修改 UI 来解决这个问题。清单 3-18 显示了应用的新用户界面。您向应用首次启动时显示的content_pagehost div添加一些内容,并将显示的内容居中。

Listing 3-18. Modified User Interface Layout for the FirstApp Example

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>FirstApp</title>

<!-- WinJS references -->

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

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

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

<!-- FirstApp references -->

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

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

</head>

<body>

style="height:80%; width:500px; margin-left:auto;``style="height:80%; width:500px; margin-left:auto;

margin-right:auto" >

Welcome to the WinJS controls demo, click a button to open a page that runs the demo

</div>

<div>

<button id="btn_introduction">Introduction</button>

<button id="btn_appbardemo">AppBar demo</button>

</div>

</body>

</html>

您还向页面添加了一个新按钮,该按钮总是将您带回到应用的默认状态。清单 3-19 显示了这个页面的 JavaScript 代码。我还擅自清理了 Visual Studio 自动生成的所有无关内容。

Listing 3-19. Modified JavaScript for FirstApp

(function ()

{

"use strict";

WinJS.Binding.optimizeBindingReferences = true;

var app = WinJS.Application;

var activation = Windows.ApplicationModel.Activation;

app.onactivated = function (args)

{

btn_appbardemo.onclick = function ()

{

WinJS.UI.Pages.render("/demos/appbardemo/appbardemo.html", control_pagehost);

}

btn_introduction.onclick = function ()

{

control_pagehost.innerText = "Welcome to the WinJS controls demo, click a

button to open a page that runs the demo";

}

};

app.start();

})();

当您运行这个应用时,默认状态应该如图 3-19 所示。该图像已被裁剪以减少页面空间。

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

图 3-19。

Changing the host for the DatePicker control from a div to a button

使用空函数

在您使用的实现中有一个小错误,它与 HTML 元素中内容的放置有关,这些元素被指定为 WinJS 控件的宿主。如果您还记得的话,前面有一个警告告诉您不要使用呈现自己用户界面的 HTML 元素,因为 WinJS 呈现过程不会干扰这类元素的正常呈现。当您将DatePicker控件的宿主从div更改为按钮时,您就看到了这样的例子。当您运行应用时,按钮用户界面包围了DatePicker,用户仍然可以作为按钮与元素进行交互!当你点击 AppBar 演示按钮时,例子中的错误就暴露出来了(见图 3-20 所示)。

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

图 3-20。

Bug revealed

您应该立即发现问题。页面按预期呈现,但没有篡改已经在div中的内容!为了解决这个问题,WinJS 提供了一个实用函数WinJS.Utilities.empty,可以用来清除元素的内容。(是的,这也可以使用标准的 JavaScript/DOM 策略来完成。事实上,你已经在使用这样的策略了。设置control_pagehost.innerText会清除div中的所有内容,并替换为您指定的文本)。

empty函数将您希望清除子内容的目标元素作为参数。清单 3-20 显示了事件处理程序的修改代码。

Listing 3-20. Modified Code for Event Handler

btn_appbardemo.onclick = function ()

{

WinJS.Utilities.empty(control_pagehost);

WinJS.UI.Pages.render("/demos/appbardemo/appbardemo.html", control_pagehost);

}

现在,当你点击 AppBar 演示按钮时,你的屏幕应该如图 3-21 所示。

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

图 3-21。

Demo after clicking the AppBar Demo button

从页面内导航

在根用户界面中设计导航菜单的场景中,使用这种方法非常好。因为在default.html中直接有按钮,当被点击时会调用事件处理程序,事件处理程序使用renderPageControl渲染到目标div上,所以在导航方面你不用太担心。但是如果没有根导航菜单呢?在如此高的层次上定义菜单并不总是理想的,在很多情况下,一个页面可能希望直接导航到另一个页面。

正是为了这样的场景,才创建了WinJS.NavigationAPI。简而言之,WinJS 导航框架提供了一个全局函数和事件处理程序,允许您的根页面(承载您的控制主机的页面—在本例中为default.html)在它所承载的PageControl JavaScript 中请求导航时进行侦听。然后,您可以调用render,传入所请求的页面。使用示例代码,不用每次添加新按钮时都调用emptyrender,你可以创建一个调用empty的函数,然后一般地调用render(见清单 3-21)。

Listing 3-21. Global Navigation Handling

(function ()

{

"use strict";

WinJS.Binding.optimizeBindingReferences = true;

var app = WinJS.Application;

var activation = Windows.ApplicationModel.Activation;

app.onactivated = function (args)

{

btn_appbardemo.onclick = function ()

{

WinJS.Navigation.navigate("/demos/appbardemo/appbardemo.html");

}

btn_introduction.onclick = function ()

{

control_pagehost.innerText = "Welcome to the WinJS controls demo, click a

button to open a page that runs the demo";

}

};

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

{

WinJS.Utilities.empty(control_pagehost);

WinJS.UI.Pages.render(args.detail.location, control_pagehost);

});

app.start();

})();

从应用的任何页面,调用navigate将触发高亮显示的事件处理函数。这个函数只是将location属性(相当于传递给navigate函数的值)传递给render

本章的其余部分假设向default.html添加适当的按钮,以及导航到与您创建的示例相关联的演示页面所需的适当的 JavaScript 事件处理程序。我不包括default.htmldefault.js的变更列表。另请注意,不会有与default.html相关的数字。

打电话给我

AppBar控件代表用于显示命令的应用工具栏。在本章的前面,您看到了如何监听应用栏何时打开或关闭。您也可以在应用栏打开或关闭之前收听事件。我发现之前版本的事件出现得更快;因此,从可用性的角度来看,如果您计划根据应用栏可见性的变化来更新用户界面,那么使用它们可能会更好。如果需要以编程方式显示或隐藏应用栏,可以分别使用show()hide()方法。应用栏还提供了显示或隐藏特定命令的灵活性。

让我们修改您到目前为止创建的 AppBar 来突出显示其中的一些特性。清单 3-22 将应用栏演示变成了一个简单的三个问题的调查,其中应用栏命令代表问题。

Listing 3-22. Code for the App Bar Demo Page

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>AppBarDemo</title>

<!-- WinJS references -->

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

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

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

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

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

<style>

div {

padding: 5px;

}

.panel {

width: 400px;

}

.centered {

margin-top: 50px;

margin-left: auto;

margin-right: auto;

}

.shadow {

box-shadow: 3px 3px 3px #000;

}

</style>p

</head>

<body>

<div class="centered panel">

<p>AppBar Survey</p>

<div>

Question: <span id="txt_appbarquestion">AppBar Hidden</span>

</div>

<div>

<button id="btn_startsurvey">Start Survey</button>

</div>

</div>

<div id="div_overlay" style="position:absolute; left:0px; top:0px; width:100%;

height:100%; opacity:.65; background-color:blue;visibility:collapse">

<div class="centered shadow panel" style="margin-top:200px; background-color:white;

width:400px; height:250px; border-radius:5px;" >

<span class="centered" style="color:black; font-size:xx-large">Pick a question

</span>

</div>

</div>

<div id="appbar_host">

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

data-win-options="{ id: 'cmd1', label: 'Question 1', icon: 'placeholder', }">

</button>

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

data-win-options="{ id: 'cmd2', label: 'Question 2', icon: 'placeholder', }">

</button>

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

data-win-options="{ id: 'cmd3', label: 'Question 3', icon: 'placeholder', }">

</button>

</div>

</body>

</html>

这个页面的 JavaScript 代码如清单 3-23 所示。

Listing 3-23. An AppBar Example

(function ()

{

"use strict";

WinJS.UI.Pages.define("/Demos/AppBarDemo/AppBarDemo.html", {

ready: function (element, options)

{

var appbarbutton_question1 =

document.getElementById("btn_appbar_question1").winControl;

var appbarbutton_question2 =

document.getElementById("btn_appbar_question2").winControl;

var appbarbutton_question3 =

document.getElementById("btn_appbar_question3").winControl;

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

var appbar_instance = new WinJS.UI.AppBar(appbar_host);

var command_array = new Array(appbarbutton_question1);

appbar_instance.showOnlyCommands(command_array);

appbar_instance.onbeforeshow = function ()

{

div_overlay.style.visibility = "visible";

}

appbar_instance.onbeforehide = function ()

{

div_overlay.style.visibility = "collapse";

}

appbarbutton_question1.onclick = function ()

{

txt_appbarquestion.innerText = "What is your name?";

appbar_instance.hide();

command_array = new Array(appbarbutton_question1, appbarbutton_question2);

appbar_instance.showOnlyCommands(command_array);

}

appbarbutton_question2.onclick = function ()

{

txt_appbarquestion.innerText = "What is your age?";

appbar_instance.hide();

command_array = new Array(appbarbutton_question1, appbarbutton_question2,ⅵ

appbarbutton_question3);

appbar_instance.showOnlyCommands(command_array);

}

appbarbutton_question3.onclick = function ()

{

txt_appbarquestion.innerText = "Do you know what lies beyond the shadow

of the statue?";

appbar_instance.hide();

}

btn_startsurvey.onclick = function ()

{

btn_startsurvey.style.visibility = "collapse";

appbar_instance.show();

}

}

});

})();

示例调查中的每个问题都是在用户点击与之相关的适当的AppBarCommand时触发的。当应用的应用栏打开时,一个透明的div覆盖了整个屏幕,并指示该做什么。从应用栏中选择一个问题会强制关闭该栏,并显示所选的问题。

日期选择器

DatePicker控件只允许用户选择一个日期。它与您以前可能接触过的任何其他标准日期选择器控件没有什么不同。在清单 3-5 中,您看到了DatePicker在页面上的样子,但是您从来没有从编程的角度考虑过加入它的一些特性。基本上,与大多数日期选择控件一样,WinJS.UI.DatePicker提供了一个用于访问当前选定日期的current属性和一个在current属性被更改时通知您的onchange事件。清单 3-24 展示了一个简单的日期选择器演示程序的用户界面,展示了该控件的一些特性。

Listing 3-24. User Interface for a Simple Date Picker

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>DatePickerDemo</title>

<!-- WinJS references -->

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

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

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

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

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

<style>

div {

padding: 5px;

}

.panel {

width: 400px;

}

.centered {

margin-top: 50px;

margin-left: auto;

margin-right: auto;

}

</style>

</head>

<body>

<div class="centered panel div">

<div>

Select Your Date of Birth:<div id="datepicker_host"></div>

</div>

<div>

You are <span id="txt_dob">unknown</span> years old

</div>

</div>

</body>

</html>

清单 3-25 提供了演示页面的 JavaScript 代码。

Listing 3-25. JavaScript for the Date Picker Demo Page

(function () {

"use strict";

WinJS.UI.Pages.define("/Demos/DatePickerDemo/DatePickerDemo.html", {

ready: function (element, options)

{

var date_picker = new WinJS.UI.DatePicker(datepicker_host);

date_picker.maxYear = 1995;

var today = new Date();

var dob = null;

var current_date = Date.parse(Date.now().toString());

var age = 0;

date_picker.onchange = function ()

{

dob = date_picker.current;

age = today.getFullYear() - dob.getFullYear();

txt_dob.innerText = " " + age + " ";

}

}

});

})();

布局相对简单,如图 3-22 所示。根据用户的选择,您计算用户的年龄。使用DatePickermaxYear属性,你可以将应用的使用限制在 18 岁或以上。

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

图 3-22。

Using the DatePicker control

3-23 显示了用户做出选择后的 UI。

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

图 3-23。

UI following a selection

弹出控件

Flyout控件可以用来显示一个临时的轻量级用户界面,当用户点击超出其边界的屏幕区域时,这个界面就会消失。它非常适合以非交互的方式显示信息,或者整合功能,否则会因为过多的控件而使屏幕变得混乱。之前,您以问卷的形式创建了一个演示。清单 3-26 展示了如何使用一个Flyout控件来实现一个单独的问题(以及相关的答案)。

Listing 3-26. Implementing an Individual Question Using a Flyout Control

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>FlyoutDemo</title>

<!-- WinJS references -->

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

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

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

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

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

<style>

div {

padding: 5px;

}

.panel {

width: 400px;

}

.centered {

margin-top: 50px;

margin-left: auto;

margin-right: auto;

}

</style>

</head>

<body>

<div class="centered panel div">

<div>

How often do you perform a strenuous activity?<button id="btn_answer">

Select an answer...</button>

</div>

<div id="control_flyouthost" style="width:150px;">

<div>

<input type="radio" value="Once a day" name="answer" >Once a day

</div>

<div>

<input type="radio" value="Once a week" name="answer"/>Once a week

</div>

<div>

<input type="radio" value="Once a month" name="answer"/>Once a month

</div>

<div>

<input type="radio" value="Once a year" name="answer"/>Once a year

</div>

</div>

</div>

</body>

</html>

清单 3-27 提供了 HTML 的 JavaScript 代码。应用代码和Flyout控件本身一样简单明了。当按钮被点击时,调用Flyout实例上的show,传入 HTML 元素作为它的锚点,以及相对于锚点的位置。Flyout只不过是 HTML 的容器(就像用 JavaScript 显示和隐藏的div)。

Listing 3-27. Code Behind for the Flyout Demo Page

(function () {

"use strict";

WinJS.UI.Pages.define("/Demos/FlyoutDemo/FlyoutDemo.html", {

ready: function (element, options) {

var flyout = new WinJS.UI.Flyout(control_flyouthost);

btn_answer.onclick = function ()

{

flyout.show(btn_answer, "bottom");

}

}

});

})();

3-24 显示了这个视图运行时的样子。

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

图 3-24。

Example of a Flyout control

HtmlControl

还记得大约 20 页前我讨论页面和PageControl如何工作的时候吗?当你创建你的第一个页面时,我解释了有几种方法可以在屏幕上显示一个页面。一种方法是使用render函数,这在那一节已经展示过了。它从您指定的页面获取 HTML、CSS 和 JavaScript,并智能地将其注入宿主控件范围内的宿主页面。这种工作方式很有趣,但在大多数情况下,它需要一些额外的跑腿工作来处理empty功能和所有的WinJs.Navigation喧嚣。

对这一点来说,这是一个非常漂亮的简写。有了它,你可以很容易地创建一个导航应用,而不需要像 JavaScript 那样做太多其他事情——这是render方法明显需要的。清单 3-28 通过将清单 3-26 中的弹出演示加载到视图中,创建了一个简单的HtmlControl演示。

Listing 3-28. Example of HtmlControl

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>HtmlControlDemp</title>

<!-- WinJS references -->

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

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

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

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

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

</head>

<body>

<div class="HtmlControl fragment">

<div data-win-control="WinJS.UI.HtmlControl" data-win-options=

"{uri:'/demos/flyoutdemo/flyoutdemo.html'}"></div>

</div>

</body>

</html>

结果应该与图 3-24 完全相同,除了一个用于导航到演示的额外按钮(见图 3-25 )。

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

图 3-25。

Adding an extra navigation button

菜单

Menu控件与Flyout相对相似,以至于您为其构建的示例应用本质上与您之前创建的 survey 相同,但带有一个Menu控件。您可能会想,如果它们如此相似,为什么还需要两个独立的控件。答案是它们之间有一个主要的区别:尽管Flyout控件支持任何添加到其中的有效元素,但是Menu控件只允许添加MenuCommand控件。这样,Menu控件有点像你在本章前面看到的AppBar控件。

两个控件都通过一个command属性添加命令,该属性只接受特定类型的控件(也只接受特定类型的 HTML 元素——两个控件都要求它们的命令对象驻留在按钮元素中)。清单 3-29 显示了菜单演示的用户界面。

Listing 3-29. UI for the Menu Demo

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>MenuDemo</title>

<!-- WinJS references -->

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

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

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

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

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

<style>

div {

padding: 5px;

}

.panel {

width: 400px;

}

.centered {

margin-top: 50px;

margin-left: auto;

margin-right: auto;

}

</style>

</head>

<body>

<div class="centered panel div">

<div>

How often do you perform a strenuous activity?<button id="btn_answer">

Select an answer...</button>

</div>

<div id="control_flyouthost" style="width:150px;">

<button id="control_daily" data-win-control="WinJS.UI.MenuCommand"

data-win-options="{label:'daily'}"></button>

<button id="control_weekly" data-win-control="WinJS.UI.MenuCommand"

data-win-options="{label:'weekly'}"></button>

<button id="control_monthly" data-win-control="WinJS.UI.MenuCommand"

data-win-options="{label:'monthly'}"></button>

<button id="control_yearly" data-win-control="WinJS.UI.MenuCommand"

data-win-options="{label:'yearly'}"></button>

</div>

</div>

</body>

</html>

清单 3-30 显示了这个 HTML 的 JavaScript 代码。

Listing 3-30. JavaScript for the HTML Example

(function () {

"use strict";

WinJS.UI.Pages.define("/Demos/MenuDemo/MenuDemo.html", {

ready: function (element, options) {

var menu = new WinJS.UI.Menu(control_flyouthost);

btn_answer.onclick = function ()

{

menu.show(btn_answer, "bottom");

}

}

});

})();

3-26 显示了应用运行时页面的外观。

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

图 3-26。

The running application

除了使用不同的控件(飞出式演示使用单选按钮,而这个演示使用装饰为WinJS.UI.MenuCommand对象的强制按钮元素)之外,这个例子的功能本质上是相同的。选择使用哪一种最终取决于开发人员和他们想要设计的风格。显然,弹出控件给了你更多的灵活性和力量,但是对于小的情况(为了让用户和一个共同的主题保持一致),标准的Menu控件可能不会太差。因为您使用的是命令而不是自由格式的 HTML,并且因为您没有以任何方式操纵命令,所以在本例中您以声明方式添加了控件。

评级控件、设置弹出按钮、时间选择器和切换开关

WinJS 包含许多其他相当简单的控件,本节没有详细介绍。大多数控件的功能应该是不言自明的,所以与其深入研究每个控件,不如让我们创建一个示例来说明这些控件。首先,您需要构建另一个页面来承载其余的控件。在这种情况下,您创建了一个页面,该页面在启动时利用了弹出的设置。清单 3-31 定义了用户界面。

Listing 3-31. UI for a Page Utilizing a Settings Flyout

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>OtherControls</title>

<!-- WinJS references -->

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

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

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

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

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

<style>

div {

padding: 5px;

color:black;

}

.panel {

width: 400px;

}

.centered {

margin-top: 50px;

margin-left: auto;

margin-right: auto;

}

</style>

</head>

<body>

<div class="OtherControls fragment">

<div id="control_settingsflyouthost" style="background-color:gray">

<div>Thanks for checking out our demos</div>

<div>

Rate this application: <div id="control_ratehost"></div>

</div>

<div>

Enter your date of birth: <div id="control_timepickerhost"></div>

</div>

<div>

Contact me in the future: <div id="control_toggleswitchhost"></div>

</div>

<button id="btn_save" >Save</button>

</div>

</div>

</body>

</html>

清单 3-32 显示了这个 HTML 的 JavaScript 代码。

Listing 3-32. JavaScript Code for the Example HTML

(function ()

{

"use strict";

WinJS.UI.Pages.define("/Demos/OtherControls/OtherControls.html", {

ready: function (element, options)

{

var settings_flyout = new WinJS.UI.SettingsFlyout(control_settingsflyouthost);

settings_flyout.show();

var ratings = new WinJS.UI.Rating(control_ratehost);

var timepicker = new WinJS.UI.TimePicker(control_timepickerhost);

var toggle_switch = new WinJS.UI.ToggleSwitch(control_toggleswitchhost);

btn_save.onclick = function ()

{

var result = "You rated this app a " + ratings.userRating

+ "\nYou want us to contact you?" + toggle_switch.checked

+ "\nYou rated us on " + timepicker.current.toString();

var mbox = new Windows.UI.Popups.MessageDialog(result);

mbox.showAsync();

}

}

});

})();

与本章中的其他例子不同,清单 3-32 以编程方式创建用于该部分的控件。正如您在 JavaScript 中看到的,您只是从目标控件中读取一些公共属性;当用户点击 Save 按钮时,您在一个MessageBox中向他们呈现他们选择的值。当选择该页面时,产生的屏幕应如图 3-27 所示。

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

图 3-27。

Screen after the user selects the MessageBox

最后,图 3-28 显示了点击保存按钮时会发生什么。在这种情况下,应用被用户☺.评为 5 级

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

图 3-28。

Screen after the user clicks the Save button

高级控制

上一节介绍了在使用 WinJS 控件堆栈时可以使用的一些基本控件。这些控件以相对相似的方式工作,只需要声明(或在代码中实例化)控件,就可以绘制其用户界面。

本节介绍由 WinJS 框架提供的更复杂的控件。这些控件包含数据,以便创建它们的用户界面。可以说,仅仅宣布这样的控制不会“达成协议”。您还需要创建一个表示控件显示的数据的数据对象列表,定义一个表示列表中每一项(单个数据对象)的视图的模板,并将两者绑定在一起,以便生成的视图显示给定模板的列表。这样做很好地介绍了 Windows 8 应用可用的绑定功能。本章的其余部分将带您了解翻转视图、列表视图和语义缩放,为您使用常见的高级控件做好准备。

使用高级控件

让我们先简单解释一下 Windows 8 应用开发的模板化方法。这种方法并不是起源于 Windows 8 编程(或 Windows 编程,就此而言),它分两步工作。首先,开发人员定义一个模板,它本质上是一组离散的用户界面元素,以某种对应用有意义的模式进行布局。如果这个概念听起来很熟悉,那是因为控件在这方面类似于模板。控件的不同之处在于,它们可以被实例化,并且可以独立存在而没有数据。模板不能,因为它们代表数据—模板是数据元素的用户界面视图。以清单 3-33 中定义的person对象为例。

Listing 3-33. Code for a person Object

var person = new {

FirstName: "Azuka"

LastName: "Moemeka"

Age: 6.5

ImageID: 1234

};

如果您要直观地表示这一点,它可能会以水平方式显示在名和姓的列表中(见图 3-29 )。它也可能以细节视图的形式出现,带有真实人物的图像。

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

图 3-29。

The person object

这些视图中的每一个都从概念上表示相同的数据。一个使用person对象的名字和姓氏属性;另一个可能使用该人的姓氏、年龄和指向该人照片的图像。要让这些视图工作,您必须将对象实例的属性与表示它们的可视化控件连接起来。

3-29 使用两个span元素:一个表示名,一个表示姓。在运行时,您将适当的spaninnerText属性设置为您想要与之关联的属性。清单 3-34 显示了图 3-29 的 HTML 用户界面。

Listing 3-34. HTML UI for the person Object Screenshot

<span id="span_fname"> </span><span style="margin-left:5px" /><span id="span_lname"> </span>

清单 3-35 显示了如何将对象的适当属性应用到每个span,这一活动通俗地称为连接。

Listing 3-35. Binding to Span Elements

ready: function (element, options) {

span_fname.innerText = person.FirstName;

span_lname.innerText = person.LastName;

}

你可以更进一步。假设指定的跨度已经被设计成只需要名称(可能已经对它们应用了一种样式,以“名称”的方式呈现它们所呈现的任何内容)。您可以使用第 2 章中讨论的作者定义的属性功能来构建一个更通用的应用版本。您可以创建一个通用函数来查找适当的属性并自动为您设置值,而不是像清单 3-5 那样以编程方式设置值。清单 3-36 显示了这个新实现的完整 JavaScript。

Listing 3-36. Homegrown Data Binding

(function () {

"use strict";

var person = {

FirstName: "Azuka"

LastName: "Moemeka"

Age: 6.5

ImageID: 1234

};

WinJS.UI.Pages.define("/Demos/FlipViewDemo/FlipViewDemo.html", {

ready: function (element, options) {

ProcessBindings(person);

}

});

function ProcessBindings(data_source)

{

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

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

{

var node = list.item(i);

if (node != null)

{

if (node.attributes != null)

{

var isboundcontrol = node.hasAttribute("data-binding");

if (isboundcontrol)

{

var property_to_bind = node.getAttribute("data-binding");

node.innerText = data_source[property_to_bind];

}

}

}

}

}

})();

清单 3-36 从调用一个函数ProcessBindings开始,传递一个参数作为数据源对象(稍后会有更多关于数据源的内容)。在ProcessBindings中,获取文档中的所有span元素,并遍历列表,搜索指定了作者定义的data-binding属性的范围。当您遇到这样的控件时,您使用data-binding属性的值作为索引来检索具有相同名称的data_source属性的属性值。清单 3-37 显示了这个页面的用户界面现在的样子。

Listing 3-37. Homegrown Data Binding User Interface

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>FlipViewDemo</title>

<!-- WinJS references -->

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

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

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

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

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

</head>

<body>

<div class="FlipViewDemo fragment">

<div>

Beautiful children!

</div>

<div>

<span data-binding="FirstName"></span><span style="margin-left:5px" />

<span data-binding="LastName"></span>

</div>

</div>

</body>

</html>

运行该示例会产生与图 3-29 所示相同的结果。该示例阐释了绑定基础结构的一些主要概念、数据控件的主要组件及其用法。模板的功能本质上类似于清单 3-37 中强调的用户界面:它们提供了一个用户界面,数据被注入其中。数据源表示要注入模板的数据源。

绑定是将数据的给定属性与用户界面中的元素相关联的活动。它包括绑定定义部分(在本例中,指定包含FirstName值的data-binding属性——这表明FirstName将被放置在该 span 中)和实际的绑定动作(将值放置在该 span 中)。

该示例展示了绑定的基本机制,但是您应该很容易看出它有多么有限。首先,它只适用于跨度;一个健壮的数据绑定解决方案将允许使用多种类型的控件。第二,它在约束目标方面是有限的。在这个例子中,您总是绑定到span元素上的innerText;但是如果这是一个图像呢?在这种情况下,您需要使用src属性。

给出了数据绑定的基本描述以及它如何应用于数据控件,现在让我们看看 WinJS 提供的一些控件。从这些数据控件中最简单的开始:FlipView。使用这个作为第一个例子的好处是,由于FlipView相对简单的实现和编程方法,很容易掌握和理解为 Windows 8 编程数据驱动 WinJS 控件背后的核心概念。

FlipView

FlipView控件可用于一次显示一个对象列表。Windows Marketplace 应用的应用详细信息页面上显示了一个示例。当您需要显示列表项的详细视图,同时仍为最终用户提供在列表中前后移动的能力时,此控件非常有用。

让我们扩展清单 3-27 中的例子,将它转换成使用一个FlipView控件。首先需要将person对象转换成一个people列表,然后创建一个模板来表示这个列表中的每个人,最后声明FlipView控件并连接它,使它显示这个列表。

清单 3-38 显示了你使用的新数据源。请注意,您向设计中添加了两个额外的活动。创建人员数组后,必须将他们添加到WinJS.Binding.List对象中。关于这个列表,重要的是要知道,因为它涉及到所有类型的数据控件,它对于绑定是必要的。

Listing 3-38. Setting Up a List for the FlipView

var people = [

{

FirstName: "Alex"

LastName: "Moemeka"

Age: 9

ImageID: 5678

}

{

FirstName: "Azuka"

LastName: "Moemeka"

Age: 6.5

ImageID: 1234

}

];

var item_source = new WinJS.Binding.List(people);

Note

FlipViewListView不能处理除了提供由WinJS.UI.IListDataSource公开的方法和属性的对象之外的任何东西。在尝试绑定时,两个控件都调用预期属于绑定类型的方法。使用这个对象的好处是它公开了与标准 JavaScript 数组相同的方法和属性,所以学会如何使用它应该不会太难。我推荐一直用;但 WinJS 的价值主张之一是,它可以很容易地转换为标准 JS,并在基于 web 的应用版本中使用。使用任何不能转换成标准 JavaScript 的 WinJS 类型都不能做到这一点。

您需要对这个列表做些别的事情,以便为绑定做好准备。到目前为止,我还没有谈到这一点,但是您需要使列表在标记中可寻址。默认情况下,以声明方式定义的控件无法访问您在 JavaScript 文件中创建的对象。为了使它们对 UI 层可见,您需要使用WinJS.Namespace.define函数将它们声明为名称空间。添加清单 3-39 中的声明。

Listing 3-39. Defining the people List

WinJS.Namespace.define("people_list", {

bindingList: item_source

array: people

});

就像使用WinJS.Binding.List一样,这种符号是你必须习惯的。为了使绑定工作,您公开的对象必须有一个表示被绑定到的列表的属性(在本例中是,bindingList))和一个在其上定义的数组属性。bindingList属性应该被设置为绑定列表的一个实例。array属性应该引用你的数组。

既然您已经准备好了数据源,那么您可以转到用户界面并定义用于存储数据的模板。该模板使用与自主开发的数据绑定示例相同的用户界面布局,经过修改后支持官方的 Windows 8 数据绑定格式(参见清单 3-40)。

Listing 3-40. Creating a Template

<div id="basic_template" data-win-control="WinJS.Binding.Template">

<span data-win-bind="innerText:FirstName"></span><span style="margin-left: 5px" /><span

data-win-bind="innerText:LastName"></span>

</div>

基于你对控件和自制绑定的理解,清单 3-40 应该是不言自明的。您已经创建了一个表示一个person对象的数据模板的模板控件。在模板控件中,您定义了实际的绑定,如前所述,这些绑定是您试图显示的对象的用户界面表示。和本地的例子一样,每个人由两个跨度表示:一个是他们的名,一个是他们的姓(用一个span在它们之间创建一个 5 像素的空间)。请注意,在正式的 WinJS 绑定方法中,绑定字符串说明了被绑定到的控件的类型。data-win-bind,在功能上等同于自行开发的示例中的数据绑定,既接受属性名以绑定到给定的控件,又接受属性名以绑定值。在这种情况下,因为两者都是跨度,data-win-bind的值就是innerText:<data source property name>。一般来说,格局是<element's attribute name>:<data source property name>;因此,如果其中一个模板元素是图像,那么data-win-bind属性将是src:<data source property name>

最后,您定义控件。请注意,因为模板由控件引用和使用,所以它必须在使用的任何控件之前定义。清单 3-41 显示了完整的用户界面 HTML,包括模板和控件。

Listing 3-41. Full UI Code

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>FlipViewDemo</title>

<!-- WinJS references -->

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

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

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

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

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

</head>

<body>

<div class="FlipViewDemo fragment">

<div>

FlipView Demo

</div>

<div aria-label="Main content" role="main">

<div id="basic_template" data-win-control="WinJS.Binding.Template">

<span data-win-bind="``innerText:FirstName``"></span><span style="margin-left: 5px"

/><span data-win-bind="``innerText:LastNameT2】

</div>

<div style="background-color:slategray" data-win-control="WinJS.UI.FlipView"

="{itemTemplate:basic_template, itemDataSource:``="{itemTemplate:basic_template, itemDataSource:

people_list.bindingList.dataSource} ">

</div>

</div>

</div>

</body>

</html>

特别要注意FlipView控件的data-win-options属性:它和ListView使用相同的机制。在其中,您分配了模板,FlipView使用该模板来呈现数据源中的每一项以及数据源本身。顾名思义,itemTemplate应该引用呈现每个项目的模板(应该是表示每个项目用户界面的 HTML 容器的元素 ID),而itemDataSource应该引用数据源——使用清单 3-39 中定义的公共作用域名称。如果您还记得,您创建的那个对象包含一个名为bindingList的属性。在清单 3-41 中,你引用了这个对象的一个dataSource属性。这是一个您没有定义但在运行时附加到对象的属性;你在这种情况下使用它,它充满了数据。

呈现时,这个FlipView显示与前面相同的用户界面布局(名字,姓氏),只有一个例外:在支持触摸的设备上,您可以在控件上从右向左滑动以显示其他列表值。(如果您没有支持触摸的设备,但有鼠标,则FlipView控件会显示向前和向后箭头,您可以使用这些箭头来浏览控件列出的元素。)图 3-30 显示了无触摸机器上的用户界面。

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

图 3-30。

FlipView control example

列表视图

既然您已经看到了如何构建一个FlipView控件,并且对数据驱动控件开发的主要组件有了大致的了解,那么您应该能够推断出ListView是如何设置的。从编程的角度来看,ListView控件在功能上是相似的(唯一的区别是用户界面和交互模式)。这当然主要是由于每个控件满足的使用场景。虽然FlipView的目标是一次呈现一个列表项的模板,但是ListView以列表形式显示它们。展示这一点的最简单方法是将清单 3-41 中的控件类型从WinJS.UI.FlipView改为WinJS.UI.ListView。现在运行示例以列表形式显示相同的数据,如图 3-31 所示。

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

图 3-31。

ListView control example

Visual Studio 2012 包括用于创建新项目的项目模板,其中所有ListView控件的绑定和布局都已经连接好。图 3-32 显示了添加新项目窗口,您可以在其中选择网格应用模板。该模板以两种格式提供了开始构建数据驱动的主从式应用所需的一切。在 WinJS 中,网格应用模板以网格格式创建分组视图。Split 应用将主数据全部放在一个页面上。

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

图 3-32。

Project template selection for Grid App

如果您创建了这个项目并立即运行它,您应该有一个类似于图 3-33 的用户界面。

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

图 3-33。

The default Grid App template in action

语义缩放

本节讨论的最后一个控件是SemanticZoomSemanticZoom控件以两种视图显示列表:放大视图和缩小视图。您可以使用这种功能来创建一个用户界面,类似于 Windows 8 开始屏幕在所有应用视图上的功能(从开始屏幕,滑动打开魅力栏,然后单击搜索魅力)。如果你有一个支持触摸的设备,在视图中捏住屏幕(对于没有触摸的机器,按 Control + scroll ),你应该会看到类似图 3-34 的视图。

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

图 3-34。

Zoomed-out Start screen

处于缩小状态允许用户快速导航到他们感兴趣的区域。在这种情况下,用户可以进入以字母 X 开头的应用,而不必滚动每个应用;但是任何您认为与用户相关的高级分组都将以相同的方式运行。如果 Windows 8 开发人员使用这些排序机制中的一种,该列表中的应用可能会缩小以显示应用发布者的姓名、安装日期、权限集或任何其他有意义的分组。目前,他们选择按应用名称的第一个字母分组。请注意,桌面应用不仅仅是按应用名称分组的(它们参与首字母分组的方式与 Windows 8 应用不同)。这将映射到旧的“开始”菜单中可见的根应用文件夹名称。图 3-35 显示了所有应用屏幕的放大视图。

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

图 3-35。

Zoomed-in view of all apps

创建这个相对简单。您只需在 HTML 中定义一个SemanticZoom控件并指定两个视图,使用ListView控件(或其他数据控件)来表示它必须提供的两种状态。SemanticZoom控件的第一个 HTML 子控件是放大视图,第二个 HTML 子控件是缩小视图。

缩小视图使用分组功能。分组允许您将绑定到控件的数据分类到通过您提供的条件标识的组中。在图 3-34 中,所有的 Windows 8 应用都按照应用名称的第一个字母进行分组(因此有了字母列表)。因为您在people列表中只有两个元素,所以让我们修改它,以便您可以很容易地看到分组功能。清单 3-42 显示了一个新列表(family_members),它扩展了您之前创建的people列表。

Listing 3-42. A List of Family Members

var family_members = [

{

FirstName: "Alex"

LastName: "Moemeka"

Age: 9

ImageID: 5678

}

{

FirstName: "Azuka"

LastName: "Moemeka"

Age: 6.5

ImageID: 1234

}

{

FirstName: "Elizabeth"

LastName: "Moemeka"

Age: 9

ImageID: 910

}

{

FirstName: "Edward"

LastName: "Moemeka"

Age: 9

ImageID: 1112

}

{

FirstName: "Tiki"

LastName: "The Cat"

Age: 1

ImageID: 1314

}

];

变化不大;您刚刚向现有列表中添加了更多元素。您使用每个家庭成员名字的第一个字母对列表进行分组(类似于开始屏幕方法)。如果您查看数组元素,您应该注意到根据您指定的标准有三个分组(A、E 和 T)。在前面的例子中,您创建了一个List对象并将您的控件绑定到该对象;在这种情况下,您需要再添加几个步骤来启用该数据集的分组。幸运的是,您一直在使用的 WinJS list对象提供了启用分组所需的一切。使用createGrouped功能,您可以返回家庭成员列表的分组版本。createGrouped将三个函数作为参数:第一个接受列表中的一个元素并标识它所属的组,第二个检索用于分组的组,最后一个对元素进行排序(在列表和分组中)。请参见清单 3-43。

Listing 3-43. Creating a Grouped List that Is Ready for Data Binding

var family_members = [

{

FirstName: "Alex"

LastName: "Moemeka"

Age: 9

ImageID: 5678

}

{

FirstName: "Azuka"

LastName: "Moemeka"

Age: 6.5

ImageID: 1234

}

{

FirstName: "Elizabeth"

LastName: "Moemeka"

Age: 9

ImageID: 910

}

{

FirstName: "Edward"

LastName: "Moemeka"

Age: 9

ImageID: 1112

}

{

FirstName: "Tiki"

LastName: "The Cat"

Age: 1

ImageID: 1314

}

];

var item_source = new WinJS.Binding.List(family_members);

var grouped_source = item_source.createGrouped(function (item)

{

// return the string that will be used to evaluate grouping

return item.FirstName.charAt(0).toString();

}, function (item)

{

return {

Title: item.FirstName.charAt(0).toString()

};

}, function (left, right)

{

return left.toUpperCase().charAt(0) < right.toUpperCase().charAt(0);

});

WinJS.Namespace.define("familymember_list", {

bindingList: grouped_source

array: family_members

});

在清单 3-43 中,与您之前看到的FlipViewListView例子相比,有两点发生了变化。首先,不是仅仅创建一个列表,而是使用createGrouped函数从列表中创建一个组。其次,分组项用于定义公共名称空间,而不是列表本身。createGrouped的三个参数相对简单。第一个函数返回列表中每一项的第一个字符,因此对于 Edward,它将返回 E,对于 Tiki,它将返回 t。第二个函数在每个组中调用一次,返回组对象以及与之相关联的可绑定名称。这对绑定到模板很重要。在这种情况下,您使用名称Title来引用每个组的名称。final 函数用于对列表和分组列表中的每个元素进行排序。它总是传递两个元素,并期待两者之间的比较。这里使用布尔比较,但是正数值和负数值也可以。清单 3-44 提供了示例的用户界面。

Listing 3-44. Semantic Zoom Example

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>SZDemo</title>

<!-- WinJS references -->

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

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

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

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

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

</head>

<body>

<div class="SZDemo fragment">

<div>

Semantic Zoom

</div>

<div id="basic_template" data-win-control="WinJS.Binding.Template">

<span data-win-bind="innerText:FirstName"></span><span style="margin-left: 5px"

/><span data-win-bind="innerText:LastName"></span>

</div>

<div id="group_header_template" data-win-control="WinJS.Binding.Template">

<span data-win-bind="innerText:Title"></span>

</div>

<div data-win-control="``WinJS.UI.SemanticZoom``" style="background-color: slategray;

height: 300px"

>

<div id="in" data-win-control="WinJS.UI.ListView" style="background-color:

slategray; height:300px"

data-win-options="{itemTemplate:basic_template, itemDataSource:

familymember_list.bindingList.dataSource}">

</div>

<div id="out" data-win-control="WinJS.UI.ListView" style="background-color:

slategray; height:300px"

data-win-options="{itemTemplate:group_header_template, itemDataSource:

familymember_list.bindingList.groups.dataSource }">

</div>

</div>

</div>

</body>

</html>

请注意一个不同的缩小视图模板,它绑定到清单 3-43 中 group 对象上定义的Title属性。

如前所述,SemanticZoom控件包含两个集合控件:第一个表示放大视图(在本例中,一个ListView绑定到family_members列表),第二个表示缩小视图(绑定到family_members列表的groups属性)。第二个控件连接到另一个模板group_header_template,因为它包含一个组对象列表(带有Title属性)而不是一个person对象列表。图 3-363-37 显示了该示例的外观。

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

图 3-37。

SemanticZoom example in the zoomed-out view

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

图 3-36。

SemanticZoom example in the default zoomed-in view

您可以使用右下角的减号缩小SemanticZoom控件(对于鼠标),使用 Control +鼠标滚轮向后滚动(也用于鼠标交互),使用 Control +减号,或者,如果您有支持触摸的设备,通过捏捏SemanticZoom表面。

创建自己的控件

到目前为止,您已经看到了如何整合内置的 HTML 控件,如inputbuttonimagespan元素。然后,您研究了如何使用不属于 HTML 控件集的控件。这些控件非常棒,因为它们封装了与 Windows 8 开发相关的附加功能;其中包括AppBarFlyoutRatings控件。您继续研究了模板、绑定和高级的以数据为中心的控件,如FlipViewListView和令人兴奋的新SemanticZoom控件。

这些控件代表 Windows 编程 API 的开发人员认为重要的众所周知的问题的解决方案;但是,如果您想要为自己的库封装用户界面功能,该怎么办呢?这样的控件可以在多个项目中重用,为您开发的应用提供一致的外观。WinJS 提供了创建您自己的自定义控件的能力,如果没有内置控件满足您的需求。

正如你在本章前面所看到的,控件只不过是 JavaScript 类,它们使用 HTML 树中的指针(一个主机元素)来呈现它们的内容。您在第 1 章的中使用了这种机制,使用data-win自定义属性创建了一个自制的控制模式。在本节中,您将创建一个新的控件PersonControl,它的功能基本上类似于您在上一节中创建的用于呈现person对象的模板。要做到这一点,你需要做两件事。首先,您需要创建一个表示控件的类——接受宿主元素和您熟悉的选项作为构造函数参数。您以前没有遇到过这种情况,但是在 WinJS 中创建一个类非常简单。您只需要调用WinJS.Class.define,传入一个表示该类构造函数的函数。define还带有另外两个参数:一个需要一个表示该类所有公共实例成员的对象,另一个需要一个表示该类所有公共静态成员的对象。这个例子没有深入研究这两个参数的用法,但是欢迎您以这种方式探索类的用法。你可以在 http://msdn.microsoft.com/en-us/library/windows/apps/br229813.aspx 找到更多关于他们的信息。

您还需要通过为该类定义一个命名空间来使该类成为公共的;您在上一节中使用了相同的方法,使您的数据列表对声明它们绑定到的控件的 HTML 标记可见。清单 3-45 显示了自定义控件示例的 JavaScript 代码。定义了类和命名空间之后,就不需要再做什么了(如果计划用 HTML 声明控件的话)。正如您在“深入研究 WinJS 控件”一节中看到的,用 JavaScript 实例化控件需要更多的代码,但本质上与 HTML 替代方法的工作方式相同。

Listing 3-45. Creating a Custom Control

(function ()

{

"use strict";

var person_contructor = WinJS.Class.define(function (element, options)

{

element.winControl = this;

this.element = element;

var first_name = options.firstname;

var last_name = options.lastname;

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

firstname_span.innerText = first_name;

element.appendChild(firstname_span);

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

space_span.innerText = " ";

element.appendChild(space_span);

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

lastname_span.innerText = last_name;

element.appendChild(lastname_span);

});

WinJS.Namespace.define("MyControls", {

PersonControl: person_contructor

});

WinJS.UI.Pages.define("/Demos/CustomDemo/CustomControlDemo.html", {

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

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

ready: function (element, options)

{

// TODO: Initialize the page here

}

});

})();

清单非常简单。您使用WinJS.Class.define创建一个类,传递宿主元素和声明时指定的选项。在构造函数中,创建三个<SPAN>元素:一个用于名,一个用于名和姓之间的空格,一个用于姓。您从声明时传入的options对象中检索这些值。将这些值设置为适当跨度的innerText属性,然后以适当的顺序将跨度附加到主体元素。

接下来定义该类的名称空间,以便可以从 HTML 标记中引用它。在这种情况下,该类可寻址为MyControls.PersonControl。清单 3-46 显示了这个例子的 HTML 标记。

Listing 3-46. Example Custom Control Markup

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>CustomControlDemo</title>

<!-- WinJS references -->

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

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

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

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

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

</head>

<body>

<div>

Semantic Zoom

</div>

<div id="control_person" data-win-control="MyControls.PersonControl" data-win-options=

"{firstname:'chinua',lastname:'achebe'}"></div>

</body>

</html>

就像你在本章中使用的其他控件一样,你可以通过附加到一个容器的自定义属性声明PersonControl来实例化它,该容器代表控件的宿主。然后使用data-win-options将实例化选项传递给控件,这些选项用于控制第一次呈现时控件的最终布局。图 3-38 显示了导航到页面时的用户界面。

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

图 3-38。

Custom control

摘要

您刚刚完成了内容丰富的一章,对合并布局和控件进行了全面而深入的研究。以下是对关键点的回顾:

  • Windows 的 JavaScript 库本质上是纯 HTML。基于 C#或 C++的 Windows 8 应用中的纯 Windows 运行时之间的区别因素并不比控件的情况更明显。因为 WinJS 应用是使用 HTML、CSS 和 JavaScript 的 web 标准构建的,所以它们支持将 HTML 控件集用于应用布局、样式和交互性编程。
  • 控件可以是 JavaScript 类的形式。这些可以是您自己编写的类,以降低用户界面构造的复杂性并增加布局的灵活性。
  • WinJS 提供了许多快速启动和运行的控件。WinJS 控件使用与 Windows 应用商店应用相同的 HTML JavaScript 和 CSS 编写,因此它们可以与页面上的其他元素无缝集成。
  • 为此,所有 WinJS 控件修饰的 HTML 元素在运行时都有一个附加的属性,称为winControlwinControl属性提供了从承载控件的 HTML 元素到实际 WinJS 控件的连接,并可用于访问与控件关联的任何事件、属性或方法。
  • 使用页面控件模板创建页面不仅会添加页面的 HTML,还会添加 JavaScript 和 CSS。
  • 一个AppBar控件代表一个应用工具栏,用于显示命令。
  • PageControl从指定页面获取 HTML、CSS 和 JavaScript 并智能地将其注入宿主控件范围内的宿主页面时,HtmlControl可用于创建页面。
  • 虽然FlyoutMenu控件是相似的,但是Flyout控件支持添加任何有效的元素;Menu控件只允许添加MenuCommand控件。
  • 除了 HTML 控件和不属于 HTML 控件集的控件提供的功能,您还可以使用模板、绑定和高级数据中心控件,如用于 Windows 8 应用开发的FlipViewListViewSemanticZoom
  • 自定义控件使您能够创建可重用的用户界面。这允许您在重复的实例中使用具有相同布局和行为的控件。