Передать на печать

Начала Metro-программирования: создание компонентов WinRT (ч.1)

Продолжаем изучать возможности Windows Runtime, или, сокращённо, WinRT, - среды исполнения нового класса Windows-приложений, распространяемых через магазин Windows Store. На этот раз мы рассмотрим создание новых компонентов, которые затем можно будет использовать в приложениях наряду со стандартными.

Внимание!
Перед чтением следует ознакомиться с предыдущими статьями из цикла "Начала Metro-программирования", опубликованными на сайте ранее. Описанные в них приёмы программирования будут активно здесь использоваться.


1. Элементы интерфейса, применяемые в приложениях Windows Store. Компоненты
Все элементы интерфейса, применяемые в приложениях Windows Store, делятся на две группы:

  • Элементы интерфейса HTML. Они создаются с помощью тегов HTML и не требуют для своего функционирования ни особых стилей CSS, ни специально написанного кода JavaScript. К таким компонентам можно отнести кнопки, поля ввода, флажки, переключатели, списки, абзацы текста, заголовки, таблицы, блоки и пр.
  • Элементы интерфейса WinRT. Они создаются на основе блоков (тегов <div>), которые называются элементами-основами, и для успешного функционирования требуют особых сценариев JavaScript и, возможно, стилей CSS. К таким элементам управления относятся элементы для указания даты, времени, рейтинга, панели инструментов, всплывающие элементы, контекстные меню и пр.


Элементы интерфейса WinRT можно рассматривать как надстройку над языком HTML, расширяющую его возможности в плане построения интерфейса приложений. Например, только средствами HTML создать элемент для ввода даты невозможно, но средствами WinRT это делается элементарно.

Элементы интерфейса WinRT являются компонентами. Компонент - это особым образом сформированный фрагмент программного кода, создающий какой-либо элемент интерфейса или добавляющий приложению некую часть функциональности, независимый от остального кода логики приложения и при этом отчуждаемый, то есть могущий применяться в различных приложениях. Компонент WinRT включает в свой состав код логики, написанный на JavaScript, а также, возможно, таблицу стилей CSS, описывающую оформление для этого компонента и его отдельных частей, и прочие файлы (изображения, применяемые в компоненте, какие-то данные и др.).

Библиотека WinRT содержит довольно большой набор компонентов, которого может хватить для разработки относительно простых приложений. Однако если мы собираемся создавать что-то более сложное или реализовывать какую-то часть функциональности в нескольких приложениях, нам потребуется узнать, как создавать свои собственные компоненты. А создать их не так уж и сложно, благо WinRT предоставляет для этого весьма удобные инструменты.


2. Создание файла сценариев
Мы только что узнали, что компонент WinRT - это, в первую очередь, код логики, написанный на JavaScript. Следовательно, основная, а зачастую и единственная часть компонента, - это файл сценариев. Который мы сейчас создадим.

Сначала нам следует создать новое приложение WinRT, написанное на JavaScript. К сожалению, таково ограничение Visual Studio, и обойти его у нас не получится... Зато, когда мы начнём тестирвоать наш компонент, нам не потребуется создавать тестовое приложение - оно уже создано.

Добавим в только что созданное приложение новый файл сценариев. Поместим его в папку js, где хранится весь код логики данного приложения, и назовём component.js.

Сначала найдём в панели Обозреватель решений "ветвь" js, представляющую одноимённую папку. (Подробнее об этой панели и её содержимом рассказывалось в первой статье цикла.) Далее щёлкнем на этой "ветви" правой кнопкой мыши и выберем в подменю Добавить появившегося на экране контекстного меню пункт Создать элемент. Поклонники клавиатуры могут также нажать комбинацию клавиш <Ctrl>+<Shift>+<A>.

После любого из этих действий на экране появится диалоговое окно Добавление нового элемента. В левой его части находится иерархический список, в котором выбирается категория добавляемого элемента. Выберем там категорию Установленные -> JavaScript. В списке посередине выводятся разновидности добавляемых элементов, относящихся к выбранной категории. Здесь мы выберем пункт Файл JavaScript. И, наконец, введём в поле ввода Имя имя создаваемого файла - component.js - и нажмём кнопку Добавить.

Вскоре после этого новый файл сценариев будет создан и помещён в состав проекта. А на экране появится окно документа, в котором мы увидим содержимое этого файла. Точнее, ничего не увидим, поскольку этот файл изначально создаётся пустым.


3. Изоляция кода компонента и "строгий" режим выполнения
Так что мы сразу же сможем приступать к написанию кода компонента? В принципе, да. Но...

Код любого компонента WinRT представляет собой объявления объекта (или объектов) и, возможно, переменных и функций. Если мы просто напишем эти объявления, то все переменные, функции и объекты появятся в так называемом глобальном пространстве имён JavaScript. Или, говоря другими словами, станут доступными отовсюду.

В принципе, ничего плохого в этом нет - до поры до времени. До того самого времени, когда где-либо в написанном нами коде или в коде самой WinRT не встретится переменная, функция или объект с тем же самым именем, и в результате не случится конфликт имён. В таком случае возникнет ошибка - ведь JavaScript требует обязательной уникальности объявляемых структур данных, - и выполнение приложения прервётся.

Как этого избежать?

Проще всего это сделать, поместив все объявления в другое пространство имён, в котором они гарантированно не будут ни с кем конфликтовать. Обычно для этого используют так называемое анонимное пространство имён, созданное именно для кода этого компонента. Такое анонимное пространство имён выглядит как анонимная (не имеющая имени) функция JavaScript, в теле которой помещается код, составляющий логику компонента, и которая сразу же после объявления запускается на выполнение.

Объявление анонимной функции, формирующей анонимное пространство имён, выглядит так:

(function () {
  "use strict";

  <код логики компонента>

})();


})();[/code]
Кстати, такой подход называется изоляцией кода.

Но, если все объявленные в анонимном пространстве имён переменные ,функции и объекты недоступны "извне", то как мы будем работать с нащим компонентом. Через другое пространство имён - именованное, которое мы создадим потом.

Но что делает выражение "use strict";? Оно задаёт так называемый "строгий" режим выполнения, при котором на выполняющийся код накладываются дополнительные ограничения; в частности, в этом режиме нельзя использовать переменные без их явного объявления и выполнять запись значений в свойства, доступные только для чтения. Активизация "строгого" режима позволит нам избежать значительного числа ошибок.


4. Создание простейшего компонента
Теперь мы можем приступать к написанию логики нашего компонента.

Давайте создадим раскрывающуюся панель, или спойлер, - один из самых популярных в настоящее время элементов интерфейса, встречающийся на многих Web-страницах. Такая панель изначально отображается на экране в виде заголовка, при щелчке на котором появляется другая панель, включающая собственно основное содержимое.

Наша раскрывающаяся панель будет включать в свой состав следующие элементы интерфейса:

  • внешний блок (тег <div>), который станет элементом-основой для компонента;
  • блок заголовка, который будет содержать текст заголовка раскрывающейся панели и индикатор состояния панели (свёрнута она в данный момент или развёрнута) и на котором пользователь будет щёлкать мышью;
  • блок содержимого, который включит собственно содержимое раскрывающейся панели.


И блок заголовка, и блок содержимого будут находиться внутри внешнего блока. Блок заголовка должен быть первым, а блок содержимого - вторым.

В блоке заголовка перед содержащимся в нём текстом будет автоматически вставлен индикатор состояния раскрывающейся панели - развёрнута она или свёрнута в данный момент. Индикатором свёрнутого состояния панели будут символы "[+]", а индикатором развёрнутого состояния - символы "[-]".

Назовём наш компонент просто - Spoiler.

Итак, файл component.js у нас уже открыт. Ставим курсор в конце строки "use strict";, создаём пустую строку и пишем... А что пишем?


4.1. Объявление объекта
Каждый компонент WinRT должен представлять собой объект JavaScript. Во-первых, таково требование WinRT, а во-вторых, с экземплярами объектов проще управляться.

Сначала нам следует объявить объект, который реализует функциональность нашего компонента. Ранее для этого требовалось использовать довольно мудрёные и не всегда очевидные приёмы программирования (знатоки популярных библиотек jQuery и jQuery UI не дадут автору соврать), но сейчас, с появлением WinRT, это выполняется вызовом одного-единственного метода. Это метод объекта WinJS.Class с именем define. (Объект WinJS.Class предоставляет набор методов для создания объектов и является составной частью WinRT.)

[code]WinJS.Class.define(
<функция-конструктор>[,
<элементы экземпляра>[,
<элементы объекта>]]
)[/code]
Первым параметром этому методу передаётся функция, которая станет конструктором создаваемого объекта и которая будет выполнена при создании каждого его экземпляра. Функция-конструктор должна присвоить всем полям создаваемого экземпляра их значения по умолчанию и выполнить всевозможные предустановки.

Первый параметр метода define является обязательным. В самом деле, какой же объект без конструктора!..

Остальные два параметра метода define мы рассмотрим чуть позже.

А вернёт этот метод полностью сформированный объект JavaScript, который мы присвоим любой переменной.


4.2. Минимальные требования к компоненту WinRT
Теперь поговорим о том, какие минимальные требования предъявляет WinRT к компонентам. Невыполнение этих требований может привести к тому, что созданный нами компонент будет работать неправильно или же не будет работать вообще.

Прежде всего, конструкторы всех объектов, реализующих функциональность компонентов, должны принимать два следующих параметра:

  • Элемент-основу в виде экземпляра объекта, представляющего соответствующий тег <div>. Этот параметр является обязательным и, как правило, называется element.
  • Набор параметров создаваемого компонента. Он должен представлять собой экземпляр объекта Object, содержащий набор свойств; имя свойства должно совпадать со именем свойства, принадлежащего самому компоненту, а значение этого свойства станет значением одноимённого свойства компонента. Этот параметр является необязательным и обычно носит имя options.


Замечание
Объекты, не являющиеся компонентами WinRT, могут принимать произвольный набор параметров. Обычно ими передают изначальные значения свойств создаваемого экземпляра.

Далее, объект компонента должен обязательно поддерживать поле element. Оно должно элемент-основу компонента.

И, наконец, элеемнт-основа должен содержать поле winControl, которое должно хранить элемент управления, реализуемый компонентом. Зачем оно нужно, мы давно знаем, ибо часто им пользуемся в коде логики приложений.


4.3. Собственно создание компонента Spoiler
Что ж, полученных знаний нам вполне хватит, чтобы написать первый в нашей программистской практике компонент WinRT. Он не будет принимать набор дополнительных параметров и изначально станет выводиться свёрнутым.

[code](function () {
"use strict";

var spoilerClass = WinJS.Class.define(
function (element) {
this.element = element;
this.element.winControl = this;

this._isOpened = false;

var cDivs = WinJS.Utilities.query("div", this.element);
this.header = cDivs.get(0);
this.content = cDivs.get(1);

this.content.style.display = "none";

var headerText = this.header.innerHTML;
WinJS.Utilities.setInnerHTML(this.header, "[<span>+</span>]&nbsp;" + headerText);

this.indicator = WinJS.Utilities.query("span", this.header).get(0);

var that = this;

this.header.addEventListener("click",
function() {
if (that._isOpened) {
that.content.style.display = "none";
WinJS.Utilities.setInnerHTML(that.indicator, "+");
} else {
that.content.style.display = "block";
WinJS.Utilities.setInnerHTML(that.indicator, "-");
}
that._isOpened = !that._isOpened;
}
);
}
);
})();[/code]
Давайте построчно рассмотрим код написанного нами конструктора.

Сначала мы создаём поле element и присваиваем ему элемент-основу, полученную первым параметром. Далее в этом самом элементе-основе мы создадим поле winControl и присвоим ему сам компонент.

Нам понадобится где-то хранить состояние раскрывающейся панели - свёрнута она или развёрнута. Для этого мы определим поле _isOpened, которому присвоим значение false (поскольку панель наша изначально должна выводиться свёрнутой).

Отметим, что имя данного поля начинается символом подчёркивания. Это значит, что поле является частным, то есть используется только внутри кода компонента и не должно быть доступно "извне". К сожалению, язык JavaScript не позволяет создавать настоящие частные элементы объекта, поэтому нам остаётся лишь предварять их имена символом подчёркивания и не указывать их в документации.

Теперь нам нужно получить доступ к блоку заголовка и блоку содержимого. Для этого мы используем метод query объекта WinJS.Utilities. (Данный объект предоставляет ряд вспомогательных методов, свойств и вложенных объектов и является частью WinRT.)

[code]WinJS.Utilities.query(
<селектор>[,
<корневой элемент>]
)[/code]
Первым параметром этот метод принимает строку с селектором, записанным в соответствием с правилами CSS; отметим, что это обязательный параметр. Все элементы интерфейса, соответствующие этому селектору, будут найдены и возвращены в качестве результата.

Вторым, необязательным, параметром указывается элемент интерфейса, в котором будут искаться элементы, соответствующие заданному первым параметром селектору. Если второй параметр не указан, будет выполняться поиск по всему интерфейсу приложения.

Мы будем искать с помощью данного метода все блоки (теги <div>), вложенные во внешний блок. Таких блоков должно быть два.

Метод query возвращает список найденных элементов интерфейса в виде экземпляра объекта-коллекции WinJS.Utilities.QueryCollection. Каждый элемент этой коллекции будет соответствовать одному из найденных элементов.

Объект WinJS.Utilities.QueryCollection поддерживает метод get. В качестве единственного параметра он принимает индекс элемента коллекции и возвращает соответствующий ему элемент в качестве результата.

Мы извлекаем из полученной коллекции найденных блоков первые два. Первый блок будет блоком заголовка; мы присвоим его полю header компонента. Второй же блок будет блоком содержимого, и мы присвоим его полю content.

Поскольку наша панель изначально выводится свёрнутой, нам следует как-то скрыть блок содержимого. Для этого мы обратимся к свойству style, которое поддерживается всеми объектами, представляющими элементы интерфейса. Это свойство хранит экземпляр объекта CSSRule, представляющий результирующий стилей, что действует на данный элемент. Каждый из доступных в CSS атрибутов стилей представляется одноимённым свойством объекта CSSRule; так, атрибут стиля display, управляющий отображением элемента, представляется свойством display.

Чтобы скрыть блок содержимого, мы присвоим свойству display его экземпляра объекта CSSRule строку "none". Это значение полностью убирает элемент интерфейса с экрана.

Далее нам следует вставить в начале содержимого блока заголовка символы "[+]", которые станут индикатором свёрнутого состояния панели. Для этого мы сначала получим это содержимое, обратившись к свойству innerHTML. Это свойство поддерживается всеми объектами, представляющими элементы интерфейса, и хранит HTML-код содержимого элемента в виде строки.

После этого мы воспользуемся методом setInnerHTML уже знакомого нам объекта WinJS.Utilities, чтобы поместить в блок заголовка полученный ранее код его содержимого с добавленным индикатором состояния.

[code]WinJS.Utilities.setInnerHTML(
<элемент>,
<HTML-код содержимого>
)[/code]
Первым параметром этот метод получает элемент интерфейса, содержимое которого мы хотим задать, а вторым - HTML-код самого этого содержимого в виде строки. Результата он не возвращает.

Обратим внимание, что сам символ-индикатор мы заключили во встроенный контейнер (тег <span>). Это позволит нам впоследствии обратиться к нему, чтобы поменять индикатор при изменении состояния панели.

Теперь получим этот символ-индикатор. Для этого снова воспользуемся методом query объекта WinJS.Class и искать будем все встроенные контейнеры, присутствующие в блоке заголовка. Первый из найденных контейнеров и будет индикатором; присвоим его полю indicator компонента.

Напоследок нам останется лишь привязать к событию click блока заголовка обработчик, который будет при щелчках мышью изменять состояние панели - раскрывать или скрывать её. Но здесь нас подстерегает проблема. Дело в том, что в теле функции-обработчика мы не сможем получить доступ к компоненту, обратившись к переменной this, -во время выполнения обработчика события этой переменной может вообще не существовать. В качестве решения мы объявим переменную that, которой и присвоим компонент, и уже через эту переменную получим доступ к компоненту и его элементам.

Сам обработчик события click очень прост. Вы можете разобрать его код самостоятельно.

Теперь можно тестировать вновь созданный компонент? Нет, ещё рано...


4.4. Создание пространства имён для компонента
Весь код компонента мы поместили внутрь анонимной функции, и он будет доступен лишь в создаваемом ей анонимном пространстве имён. Следовательно, получить доступ "извне" к этому коду мы не сможем; более того, это не сможет сделать и сама WinRT.

Но ведь нам как-то нужно работать с компонентом! Стало быть, каким-то образом мы должны предоставить доступ к тем объявлениям, что должны быть доступны "извне". Как это сделать?

Через другое пространство имён, которое мы сейчас создадим и которое будет иметь вполне определённое имя. Такое пространство имён называется именованным.

Создание именованного пространства имён выполняет метод define объекта WinJS.Namespace. (Этот объект предоставляет два метода для создания новых пространств имён и является частью WinRT.)

[code]WinJS.Namespace.define(
<имя пространства имён>,
<элементы пространства имён>
)[/code]
Первым параметром метод define принимает имя создаваемого пространства имён в виде строки. Вторым параметром указывается список элементов этого пространства имён в виде экземпляра объекта Object с набором свойств; имена этих свойств укажут имена элементов пространства имён, под которыми они будут доступны "извне", а значениями станут соответствующие им имена объектов, переменных и функций, что объявлены в анонимном пространстве имён, - то есть их реализация.

Метод define возвращает в качестве результата созданное пространство имён. Обычно этот результат игнорируют.

Наилучшее место для размещения вызова данного метода - самый конец объявления анонимной функции, перед её закрывающей фигурной скобкой. Это гарантия того, что перед созданием пространства имён все входящие в него элементы уже будут объявлены.

Давайте создадим пространство имён для нашего компонента. Дадим ему имя TheVista.UI.

Создающий это пространство имён код на приведённом далее листинге выделен соответствующими комментариями.

[code](function () {
"use strict";

var spoilerClass = WinJS.Class.define(
function (element) {
. . .
}
);

//Вставленный код - начало

WinJS.Namespace.define("TheVista.UI",
{
Spoiler: spoilerClass
}
);

//Вставленный код - конец

})();[/code]
Теперь мы можем обратиться к компоненту по его полному имени - TheVista.UI.Spoiler.


4.5. Тестирование компонента
Вот теперь можно проверить наш компонент в работе.

Откроем файл default.html, в котором пишется код интерфейса приложения. Отыщем в нём следующие теги:

[code]<link href="//Microsoft.WinJS.1.0.RC/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.1.0.RC/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0.RC/js/ui.js"></script>
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>[/code]
Внимание!
Ссылки на файлы CSS и JS могут иметь другой вид в зависимости от используемой редакции Windows 8.

Эти теги подключают к файлу интерфейса необходимые таблицы стилей и файлы сценариев. Файл таблицы стилей ui-dark.css и файлы сценариев base.js и ui.js задают базовые оформление и логику и являются частью WinRT. Файл таблицы стилей default.css и файл сценариев default.js хранят оформление и логику данного конкретного приложения, созданные разработчиком.

Нам следует подключить к интерфейсу файл сценариев component.js, где хранится логика компонента. Выполняющий это тег лучше всего вставить в код после тегов, подключающих базовое оформление и логику, и перед тегами, что выполняют подключение оформления и логики, созданной разработчиком. Вот так (вставленный тег выделен комментариями):

[code]<link href="//Microsoft.WinJS.1.0.RC/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.1.0.RC/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0.RC/js/ui.js"></script>

<!-- Вставленный код - начало -->

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

<!-- Вставленный код - конец -->

<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>[/code]

Теперь удалим всё содержимое тега <body>, что вставил туда Visual Studio, и заменим его следующим кодом:

[code]<div data-win-control="TheVista.UI.Spoiler">
<div>Заголовок</div>
<div>
<h1>Содержимое</h1>
<p>Раскрывающаяся панель может включать любое содержимое: абзацы, заголовки,
изображения, таблицы, блоки, аудио, видео и элементы управления.</p>
</div>
</div>[/code]
В тег <div>, создающий внешний блок, мы поместили атрибут тега data-win-control и в качестве его значения указали полное имя созданного нами компонента - TheVista.UI.Spoiler. Встретив этот атрибут тега, WinRT прочитает имя компонента и сформирует его на основе данного блока.

Далее откроем файл default.js, где пришется логика приложения, и создадим код инициализации:

[code]document.addEventListener("DOMContentLoaded", function() {
WinJS.UI.processAll();
});[/code]
Он лишь будет инициализировать элементы интерфейса WinRT.

Сохраним исправленные файлы и запустим приложение. Щёлкнем на заголовке раскрывающейся панели, проверим, раскрылась ли она, и щёлкнем снова, чтобы скрыть её. Посмотрим, присутствует ли в заголовке панели символ-индикатор, и изменяется ли он при разворачивании и сворачивании панели. Если это так, значит, мы всё сделали правильно.


Рис. 1. Первая версия раскрывающейся панели в свёрнутом виде


Рис. 2. Первая версия раскрывающейся панели в развёрнутом виде



Дополнительная литература


Продолжение следует...

dronov_va, TheVista.Ru Team
Декабрь 2012

  Передать на печать





Все права принадлежат © MSInsider.ru и TheVista.ru, 2013
Сайт является источником уникальной информации о семействе операционных систем Windows и других продуктах Microsoft. Перепечатка материалов возможна только с разрешения редакции.
Работает на WMS 1.1 (Страница создана за 0.048 секунд)