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

Начала Metro-программирования: страницы (ч.2)

Окончание статьи, посвящённой страницам Metro и использованию их для организации интерфейса Metr-приложений.


7. Обмен данными между страницами и основным интерфейсом приложения. Именованные пространства имён

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

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

Что же делать? Использовать именованные пространства имён. Благо платформа Metro максимально облегчает работу с ними.

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

Именованное пространство имён в Metro представляет собой экземпляр объекта Object. Свойства этого экземпляра объекта хранят данные, которые должны быть доступны для основного интерфейса и всех страниц приложения. А его методы - суть функции, к которым мы хотим иметь доступ "извне". Всё довольно просто.

Получить доступ к элементам именованного пространства имён очень просто. Для этого мы используем синтаксис следующего формата: <имя пространства имён>.<имя свойства или метода>. Собственно, это и так понятно - ведь мы обращаемся к свойству или методу экземпляра объекта.

Для создания именованного пространства имён применяется метод define объекта WinJS.Namespace. (Этот объект содержит методы, предназначенные для создания пространств имён. Единственный его экземпляр создаётся платформой Metro и доступен через переменную WinJS.Namespace.)

WinJS.Namespace.define(
  <имя создаваемого пространства имён>,
  <состав пространства имён>
)

<состав пространства имён>
)[/code]

Этот метод принимает два параметра. Первым параметром передается имя создаваемого пространства имён в виде строки.

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

Но куда же поместить код, создающий именованное пространство имён? Лучше всего для этого подходит самый конец кода логики - место перед вызовом метода start, запускающем приложение:

[code] . . .

<Вот здесь>

app.start();
})();[/code]

Например:

[code]WinJS.Namespace.define("SomeNamespace", {
someProperty: 0,
someMethod: function (<параметры метода>) {
<тело метода>
}
});[/code]

Здесь мы создали именованное пространство имён SomeNamespace, в состав которого включили свойство someProperty с изначальным значением 0 и метод someMethod. После этого мы можем обратиться к ним откуда угодно - из того же фрагмента логики, где и было создано это пространство имён, или "извне".

[code]var s = SomeNamespace.someVar;
SomeNamespace.someFunc( . . . );[/code]

Мы можем создать в логике приложения сколько угодно именованных пространств имён - как в основном интерфейсе, так и в отдельных страницах. Платформа Metro нас в этом никак не ограничивает.


8. Вывод страниц

Теперь выясним, как страницы выводятся на экран.

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

[code]<div id="divBase"></div>[/code]

Для собственно загрузки страницы и вывода её на экран мы воспользуемся методом render объекта WinJS.UI.Pages.

[code]WinJS.UI.Pages.render(
<ссылка на HTML-файл страницы>,
<блок вывода>[,
<дополнительные данные для выводимой страницы>]
)[/code]

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

Третьим, необязательным, параметром могут быть переданы дополнительные данные, которые следует отправить выводимой странице. Эти данные должны представлять собой экземпляр объекта Object, свойства которого и хранят эти самые данные. Этот экземпляр объекта будет передан вторым параметром методу ready объекта-страницы (см. ранее).

Метод render возвращает в качестве результата обязательство. Для этого обязательства мы можем указать в вызове метода then функцию, которая будет выполнена после успешной инициализации и вывода страницы. Если мы собираемся сразу после вывода страницы вызывать какие-либо методы, объявленные в еёименованном пространстве имён, мы можем сделать это в теле данной функции.

[code]var divBase = document.getElementById("divBase");
WinJS.UI.Pages.render("/html/list/html", divBase).then(function() {
SomeNamespace.someMethod();
});[/code]

Здесь мы выводим на экран страницу, хранящуюся в файле list.html, что находится в папке html. Как только страница будет успешно выведена, мы вызываем метод someMethod, объявленный в принадлежащем этой странице именованном пространстве имён SomeNamespace.

Если же мы не планируем этого делать, то должны будем всё же вызвать метод then, но уже без параметров.

[code]var divBase = document.getElementById("divBase");
WinJS.UI.Pages.render("/html/list/html", divBase, {
content: "Текст"
}).then();[/code]

Здесь мы заодно передаём в выводимую страницу строку "Текст" в качестве параметра с именем content.


9. Удаление выведенной страницы с экрана

Если в блоке вывода уже была выведена какая-либо страница, то перед тем как вывести там другую страницу, мы должны удалить уже выведенную. Если мы этого не сделаем, возможны проблемы.

Внимание!
К сожалению, на данном этапе платформа Metro требует перед выводом очередной страницы очищать блок вывода от уже выведенной. По крайней мере, автор, используя Windows 8 Consumer Preview, с этим столкнулся.

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

Мето empty принимает в качестве единственного параметра элемент интерфейса, содержимое которого - все дочерние элементы - требуется удалить. Результата он не возвращает.

[code]WinJS.Utilities.empty(divBase);[/code]

Очищаем блок вывода от выведенной в нём страницы.


10. Пример: приложение для чтения каналов новостей, использующее страницы

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

Создадим в Visual Studio новый проект с именем FeedReader2.


10.1. Основной интерфейс

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

[code]<div id="divHeader">
<h1 id="hHeader"></h1>
</div>

<div id="divBase"></div>

<div id="divURL" data-win-control="WinJS.UI.Flyout">
<div>
<label for="txtURL">Интернет-адрес</label>
<br />
<input type="url" id="txtURL" />
</div>
<div>
<input type="button" id="btnURL" value="Подписаться" />
</div>
</div>

<div id="divAppBar" data-win-control="WinJS.UI.AppBar"
data-win-options="{placement: 'top'}">
<button data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{id: 'btnRefresh', label: 'Обновить', icon: 'refresh', section: 'selection'}"></button>
<button data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{id: 'btnSubscribe', label: 'Подписаться', icon: 'add', section: 'global', type: 'flyout', flyout: 'divURL'}"></button>
</div>[/code]

Здесь мы создали два блока, которые потом разместим на экране с применением сеточной разметки. Блок divHeader нам уже знаком по предыдущему приложению; он будет располагаться в верхней части экрана и служить для вывода заголовка канала новостей, на который была выполнена подписка. Блок divBase займёт остальную часть экрана и станет блоком вывода.

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

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

[code]body {
display: -ms-grid;
-ms-grid-columns: 1fr;
-ms-grid-rows: auto 1fr;
}
#divHeader { -ms-grid-column-align: center; }
#divBase { -ms-grid-row: 2; }[/code]

Они создают сеточную разметку.

Переключимся на файл default.js, что хранит код логики, и первым делом создадим в нём объявления всех необходимых переменных:

[code]var hHeader, ctrURL, txtURL, btnSubscribe, btnRefresh, ctrAppBar,
oFeedURL = null, arrFeed;[/code]

Теперь самое время создать именованное пространство имён и объявить в нём свойства для хранения значений, которые понадобятся нам как здесь, в основном интерфейсе, так и в страницах. Это свойства dsrFeed, selectedItem и divBase, хранящие, соответственно, источник данных, номер выбранного в списке пункта и блок вывода. Назовём создаваемое именованное пространство имён Main.

[code]WinJS.Namespace.define("Main", {
dsrFeed: null,
selectedItem: -1,
divBase: null
});[/code]

Значение -1, изначально присвоенное свойству selectedItem, означает, что в данный момент в списке не выбран ни один пункт, и используется для обозначения этого в большинстве случаев, в том числе и в базовой логике Metro.

Теперь напишем код инициализации.

[code]document.addEventListener("DOMContentLoaded", function() {
WinJS.UI.processAll().then(function() {
hHeader = document.getElementById("hHeader");
Main.divBase = document.getElementById("divBase");
ctrURL = document.getElementById("divURL").winControl;
txtURL = document.getElementById("txtURL");
ctrAppBar = document.getElementById("divAppBar").winControl;
btnSubscribe = ctrAppBar.getCommandById("btnSubscribe");
btnRefresh = ctrAppBar.getCommandById("btnRefresh");
var btnURL = document.getElementById("btnURL");
btnRefresh.addEventListener("click", btnRefreshClick);
btnURL.addEventListener("click", btnURLClick);
ctrAppBar.hideCommands([btnRefresh]);
});
});[/code]

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

[code]function btnURLClick() {
oFeedURL = new Windows.Foundation.Uri(txtURL.value);
ctrURL.hide();
ctrAppBar.showCommands([btnRefresh]);
fillList();
}[/code]

Объявим функцию, которая будет выполнена по нажатию на кнопке Обновить:

[code]function btnRefreshClick() {
fillList();
}[/code]

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

А вот объявление функции fillList, которая выполнит получение списка новостей и формирование на его основе источника данных для списка, будет несколько отличаться.

[code]function fillList() {
var oI, sAuthors, sContent;
arrFeed = [];
var oFeedClient = new Windows.Web.Syndication.SyndicationClient();
oFeedClient.retrieveFeedAsync(oFeedURL).then(function(feed) {
hHeader.textContent = feed.title.text;
for (var i = 0; i < feed.items.size; i++) {
oI = feed.items;
sAuthors = "";
for (var j = 0; j < oI.authors.size; j++) {
if (sAuthors != "") {
sAuthors += ", ";
}
sAuthors += oI.authors[j].name;
}
if (oI.content) {
sContent = oI.content.text;
} else {
sContent = oI.summary.text;
}
arrFeed.push({
title: oI.title.text,
authors: sAuthors,
content: sContent,
url: oI.links[0].uri.absoluteUri,
date: oI.publishedDate.getDate() + "." +
(oI.publishedDate.getMonth() + 1) + "." +
oI.publishedDate.getFullYear()
});
}
Main.dsrFeed = new WinJS.Binding.List(arrFeed);
Main.selectedItem = -1;
WinJS.Utilities.empty(Main.divBase);
WinJS.UI.Pages.render("/html/list.html", Main.divBase).then();
});
}[/code]

Код, формирующий источник данных для списка, остался неизменным. Дальше начинаются различия...

Во-первых, мы сбросили номер выбранного в списке пункта, присвоив свойству selectedItem пространства имён Main значение -1. Это обычная практика после формирования списков - делать их пункты изначально невыбранными.

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


10.2. Страница со списком новостей

Добавим в проект новую страницу и дадим ей имя list. Эта страница будет содержать список новостей.

"Разбросаем" хранящие её код HTML-, CSS- и JS-файлы по соответствующим папкам и соответственно исправим ссылки на эти файлы, присутствующие в их коде. Как это сделать, было описано в начале этой статьи.

Откроем файл list.html, хранящий описание интерфейса страницы, удалим всё содержимое тега <body> и вставим в него следующий код:

[code]<div id="divListTemplate" data-win-control="WinJS.Binding.Template">
<h2 data-win-bind="textContent: title"></h2>
<p data-win-bind="textContent: authors" class="item-authors"></p>
<div data-win-bind="textContent: content" class="item-content"></div>
<p data-win-bind="textContent: date" class="item-date"></p>
</div>

<div id="divList" data-win-control="WinJS.UI.ListView"
data-win-options="{layout: {type: WinJS.UI.ListLayout}, itemTemplate: divListTemplate, selectionMode: 'single'}"></div>[/code]

Здесь мы формируем список Metro, где и будет выводиться перечень новостей, взятых из загруженного канала. Также мы создаём шаблон для этого списка.

Откроем файл list.css, где хранится описание оформления, и создадим в нём такие стили:

[code]#divList { height: 100%; }
#divList .win-item { padding: 10px; }
.item-authors {
font-style: italic;
text-align: right;
}
.item-content {
padding-top: 10px;
padding-bottom: 10px;
}
.item-date {
text-align: right;
padding-right: 20px;
}[/code]

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

Откроем файл list.js, в котором пишется логика страницы. В коде, что описывает экземпляр объекта Object, что передаётся вторым параметром методу define, найдём метод ready, который станет принадлежностью объекта-страницы (см. ранее). Напишем код тела для этого метода:

[code]. . .
ready: function (element, options) {
var ctrList = document.getElementById("divList").winControl;
ctrList.addEventListener("iteminvoked", ctrListItemInvoked);
ctrList.itemDataSource = Main.dsrFeed.dataSource;
if (Main.selectedItem > -1) {
ctrList.selection.set([Main.selectedItem]);
}
},
. . .[/code]

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

И объявим функцию-обработчик для события iteminvoked.

[code]function ctrListItemInvoked(evt) {
evt.detail.itemPromise.then(function(selected) {
Main.selectedItem = selected.index;
WinJS.Utilities.empty(Main.divBase);
WinJS.UI.Pages.render("/html/content.html", Main.divBase).then();
});
}[/code]

Она получит номер выбранного в списке пункта, сохранит его в свойстве selectedItem пространства имён Main и загрузит страницу content.html, которая и выведет полное содержимое новости.


10.3. Страница с содержимым выбранной новости

Добавим в проект ещё одну новую страницу, которой дадим имя content. В ней будет выводиться содержимое выбранной пользователем новости.

Не забудем переместить HTML-, CSS- и JS-файлы по соответствующим папкам и исправить ссылки на эти файлы, что присутствуют в их коде.

Откроем файл content.html, где описывается интерфейс страницы, и введём в тег <body> такой код:

[code]<div id="divCont">
<div id="divButton">
<button id="btnBack" class="win-backbutton" aria-label="Back"></button>
</div>
<div id="divContent">
<iframe id="frmContent"></iframe>
</div>
</div>[/code]

Здесь мы сформировали блок divCont, а в нём - ещё два блока, которые позже разместим на экране также с применением сеточной разметки. Левый блок divButton включит кнопку возврата на страницу со списком новостей, а правый блок - фрейм, где и будет выводиться содержbмое новости.

Откроем файл content.css, где хранится код, описывающий оформление, и создадим там следующие стили:

[code]divCont {
display: -ms-grid;
-ms-grid-columns: auto 1fr;
-ms-grid-rows: 1fr;
width: 100%;
height: 100%;
}
#divButton { -ms-grid-row-align: start; }
#divContent { -ms-grid-column: 2; }
#frmContent {
width: 100%;
height: 100%;
}[/code]

Они создают сеточную разметку и устанавливают для фрейма такие размеры, чтобы он занял всё пространство родителя.

Наконец откроем файл content.js с кодом логики страницы. В коде, описывающем экземпляр объекта Object, который передаётся вторым параметром методу define, найдём метод ready и напишем код его тела:

[code]. . .
ready: function (element, options) {
var frmContent = document.getElementById("frmContent");
var btnBack = document.getElementById("btnBack");
btnBack.addEventListener("click", btnBackClick);
frmContent.src = Main.dsrFeed.getAt(Main.selectedItem).url;
},
. . .[/code]

Этот код привяжет к кнопке возврата обработчик события click и выведет во фрейме Web-страницу с содержимым выбранной новости.

Осталось объявить функцию - обработчик события click кнопки возврата.

[code]function btnBackClick() {
WinJS.Utilities.empty(Main.divBase);
WinJS.UI.Pages.render("/html/list.html", Main.divBase).then();
}[/code]

Она просто выведет на экран страницу со списком новостей.

Сохраним все исправленные файлы и запустим приложение на выполнение. Подпишемся на какой-либо канал новостей, выберем в появившемся на экране списке (рис. 2) любую новость и посмотрим на её содержимое (рис. 3). Вдоволь налюбовавшись на загруженную во фрейме Web-страницу, вернёмся к списку новостей, попытаемся обновить его и подписаться на другой канал новостей.



Рис. 2. Список новостей в приложении FeedReader2





Рис. 3. Содержимое выбранной новости в приложении FeedReader2




11. Заключение

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

Помимо описанных в этой статье, платформа Metro предоставляет и другие средства для управления страницами. В число этих средств входит поддержка гиперссылок, "истории" загруженных страниц (она работает так же, как и аналогичный инструмент Web-обозревателей) и перемещения по ней "назад" и "вперёд". Описание всех этих средств можно найти в разделе MSDN, посвящённом Metro-программированию.


Дополнительные материалы



dronov_va, TheVista.Ru Team
Август 2012

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





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