Заканчиваем рассказ о возможностях платформы Metro по перечислению файлов и папок, получению сведений о них и созданию списков.
7.2.6. Группировка пунктов списка
Вдоволь налюбовавшись списком Metro, самостоятельно фильтрующим и сортирующим данные, давайте дадим ему задачу потруднее. А именно, заставим его группировать пункты по какому-либо признаку, в нашем случае — по дате публикации новостей.
В процессе группировки пункты списка разбиваются на отдельные группы по значению указанного нами признака — критерия группировки. Таким критерием может быть значение какого-либо свойства элемента в массиве данных, фрагмент этого значения или результат вычисления, выполненного на основе сразу нескольких значений. Например, если мы выберем в качестве критерия группировки дату публикации новости, то пункты списка будут разбиты на группы, каждая из которых будет соответствовать определённому значению этой даты.
Группы будут выведены отсортированными. Критерий их сортировки также задаем мы.
При выводе на экран каждой группе пунктов будет предшествовать заголовок. В заголовке обычно указывается значение критерия, общее для пунктов, что входят в группу (например, в нашем случае - значения даты публикации).
Внимание! Группировка в списке Metro станет полноценно работать только в случае вывода пунктов в виде таблицы. Если пункты выводятся в виде списка, заголовки групп отображаться не будут.
Чтобы реализовать группировку, нам сначала придётся объявить три функции.
Первая функция будет определять значение критерия группировки для каждого элемента массива данных. Она примет в качестве единственного параметра сам этот элемент и вернёт соответствующее ему значение критерия группировки обязательно в виде строки.
function getGroupKeyValue(item) {
return item.date.getTime().toString();
}
Используем в качестве критерия группировки числовое представление даты публикации новости, преобразованное в строку.
Вторая функция будет подготавливать данные, которые понадобятся для формирования групп и будут впоследствии выводиться в их заголовках. В качестве единственного параметра она также примет элемент массива данных, а в качестве результата вернёт экземпляр объекта Object со свойствами, которые будут хранить все необходимые для формирования заголовков групп значения.
Экземпляр объекта Object, который вернёт эта функция, будет включать свойство date — дату публикации новости в виде строки. Её-то мы и выведем в заголовке группы.
Третья функция будет представлять уже знакомую нам функцию сравнения (см. ранее). Она получит в качестве параметров два значения критерия группировки.
function sortGroups(group1, group2) {
return parseInt(group1) - parseInt(group2);
}
Объявив все эти три функции, мы вызовем метод createGrouped источника данных:
,
<третья функция>
)[/code]
В качестве параметров он примет три объявленные нами функции. Результатом, который вернёт этот метод, станет новый источник данных, уже сгруппированных.
[code]var dsrGrouped = dsrNews.createGrouped(getGroupKeyValue,
getGroupData, sortGroups);[/code]
Полученный источник данных мы привяжем к списку известным нам способом:
[code]ctrList.itemDataSource = dsrGrouped.dataSource;[/code]
Помимо этого, нам потребуется получить ещё один источник данных — содержащий все группы. Он хранится в свойстве groups источника сгруппированных данных. Извлечённый из него адаптер мы присвоим свойству groupDataSource списка Metro.
[code]ctrList.groupDataSource = dsrGrouped.groups.dataSource;[/code]
Останется только создать шаблон, на основе которого будут формироваться заголовки групп, и привязать его к списку. Такой шаблон создаётся по тем же правилам, что и шаблон для обычных пунктов списка. За одним исключением: данные для вывода он будет брать из экземпляра объекта Object, возвращаемого второй по счёту из объявленных нами функций.
[code]<div id="divGroupTemplate" data-win-control="WinJS.Binding.Template">
<p data-win-bind="textContent: date"></p>
</div>[/code]
Чтобы привязать шаблон заголовка группы к списку, присвоим его свойству groupHeaderTemplate списка:
[code]<div id="divList" data-win-control="WinJS.UI.ListView"
data-win-options="{itemTemplate: divListTemplate, groupHeaderTemplate: divGroupTemplate}"></div>[/code]
Как и в случае фильтрации и сортировки, реализованная нами группировка не будет затрагивать исходный массив данных.
7.3. Получение выбранных пунктов
Что ж, мы заполнили список пунктами и, возможно, реализовали их фильтрацию, сортировку или группировку. Теперь нам нужно как-то выяснить, какие пункты списка выбраны пользователем.
Для этого служит свойство selection списка. Оно возвращает экземпляр особого объекта-коллекции, представляющего набор всех выбранных в списке пунктов.
[code]var oSel = ctrList.selection;[/code]
Этот объект поддерживает метод count, не принимающий параметров и возвращающий в качестве результата количество выбранных пунктов в виде целого числа.
[code]if (oSel.count() > 0) {
//Получаем выбранные пункты
}[/code]
Получить выбранные пункты мы можем, вызвав метод getItems этого же объекта. Он не принимает параметров и возвращает в качестве результата обязательство, для которого мы зададим в вызове метода then функцию, что выполнится после завершения процесса получения выбранных пунктов. В качестве единственного параметра она получит массив, каждый элемент которого будет экземпляром объекта Object со свойствами index и data; первое свойство будет хранить номер выбранного пункта, а второе — соответствующий этому пункту элемент массива данных.
Внимание! Следует иметь в виду, что таким образом мы получим не индексы элементов в массиве данных, соответствующих выбранным пунктам, а номера самих выбранных пунктов. А если мы реализовали в списке фильтрацию, сортировку или группировку, эти значения не будут идентичными!
[code]var arrSelectedNews = [];
oSel.getItems().then(function(selectedItems) {
for (var i = 0; i < selectedItems.length; i++) {
arrSelectedNews.push(selectedItems.data);
}
});[/code]
Получаем все пункты, выбранные пользователем, и помещаем их в массив arrSelectedNews.
Объект-коллекция, представляющий набор выбранных пунктов списка, поддерживает несколько полезных методов, о которых мы сейчас поговорим.
Метод set позволяет сделать выбранными пункты списка с указанными номерами. В качестве единственного параметра он принимает массив номеров выбираемых пунктов и не возвращает результата.
[code]oSel.set([1, 3, 10]);[/code]
Делаем выбранными второй, четвёртый и одиннадцатый пункты списка. (Не забываем, что нумерация пунктов начинается с нуля.)
Метод selectAll делает все пункты списка выбранными, а метод clear, наоборот, снимает выделение со всех пунктов. Оба метода не принимают параметров и не возвращают результата.
[code]oSel.selectAll();[/code]
Событие selectionchanged списка возникает, когда состав выбранных в данный момент пунктов изменяется, то есть когда пользователь выбирает другой пункт или отменяет выбор у выбранного ранее. Мы можем использовать это событие, чтобы отслеживать выбор пунктов в списке.
Впрочем, если список позволяет выбрать только один пункт, наша задача серьёзно упрощается. При выборе любого пункта в списке возникает событие iteminvoked, обработчик которого мы можем использовать для отслеживания выбора пункта и его получения.
Выяснить в этом случае, какой пункт был выбран, можно из экземпляра объекта CustomEvent, который хранит сведения о возникшем событии и передаётся в его функцию-обработчик единственным параметром. Данный объект, в числе прочего, поддерживает свойство detail. Оно хранит набор дополнительных данных о событии в виде экземпляра объекта Object, свойства которого и содержат эти самые данные.
В случае события iteminvoked данный экземпляр объекта Object содержит свойство itemPromise. Оно хранит обязательство, для которого мы в вызове метода then укажем функцию, что выполнится после получения выбранного пункта. Эта функция примет в качестве параметра экземпляр объекта Object, свойство index которого будет хранить номер выбранного пункта, а свойство data — соответствующий ему элемент массива данных.
[code]ctrList.addEventListener("iteminvoked", function(evt) {
evt.detail.itemPromise.then(function(selected) {
var selectedIndex = selected.index;
var selectedItem = selected.data;
. . .
});
});[/code]
Ещё нам может пригодиться метод getAt источника данных. Он принимает в качестве единственного параметра номер пункта списка в виде числа и возвращает в качестве результата соответствующий этому пункту элемент массива данных.
[code]var oItem = dsrList.getAt(0);[/code]
Получаем элемент массива данных, соответствующий первому пункту списка.
Источник данных также поддерживает свойство length, знакомое нам по массивам. Оно возвращает количество пунктов в списке.
[code]var oItem = dsrList.getAt(dsrList.length - 1);[/code]
Получаем элемент массива данных, соответствующий последнему пункту списка.
7.4. Прочие возможности списков Metro
Осталось рассмотреть остальные возможности списка Metro, которые могут нам пригодиться.
Метод ensureVisible списка выполняет прокрутку списка таким образом, чтобы нужный нам пункт стал видимым. В качестве единственного параметра этот метод принимает номер пункта в виде целого числа и не возвращает результата.
[code]ctrList.ensureVisible(dsrList.length - 1);[/code]
Прокручиваем список в самый конец.
Теперь рассмотрим возможности по оформлению списков. Реализуются они созданием стилевых классов со строго определёнными именами.
Стилевой класс .win-listview задаёт оформление для самого списка. Он автоматически привязывается к его элементу-основе.
[code].win-listview {
width: 100%;
height: 100%;
}[/code]
Растягиваем все списки Metro, что входят в интерфейс нашего приложения, на всё пространство их родителей.
Стилевой класс .win-item задаёт оформление для пунктов списка.
[code]#divList .win-item {
width: calc(25% - 10px);
padding-left: 10px;
}[/code]
Задаём для пунктов нашего списка отступ слева, равный 10 пикселам, и ширину, равную 25% от ширины списка минус заданное ранее значение отступа слева.
Стилевой класс .win-groupheader задаёт оформление для заголовков групп.
[code]#divList .win-groupheader { height: 50px; }[/code]
Задаём для заголовка группы высоту в 50 пикселов.
8. Панель вывода Metro Панель вывода (в терминологии Metro — viewbox) устанавливает размеры своего единственного потомка таким образом, чтобы этот потомок занимал всё пространство внутри данного элемента. При этом панель вывода сама следит за тем, чтобы соотношение сторон потомка не исказилось. Настоящая находка для разработчиков, собирающихся создавать приложения для просмотра графики!
[code]<div id="divViewer" data-win-control="WinJS.UI.ViewBox">
<img id="imgViewer" />
</div>
}[/code]
Создаём панель вывода и помещаем в него элемент графического изображения.
9. Собственно приложение просмотрщика графики
Теперь мы можем наконец доделать приложение просмотрщика графики. Запустим Visual Studio и откроем в нём проект ImageViewer, если уже успели его закрыть. И не забудем дать приложению права на доступ к библиотеке "Изображения".
Переключимся на файл default.html и исправим присутствующий в теге <body> код, чтобы он выглядел следующим образом:
<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: 'btnSelect', label: 'Выбрать папку', icon: 'folder', section: 'global'}"></button>
</div>[/code]
Мы просто превратили блоки divViewer и divList в панель вывода и список соответственно, добавив в создающие их теги атрибуты data-win-control и data-win-options.
Теперь переключимся на файл default.css и добавим в присутствующий в нём код такой фрагмент:
[code]#divList .win-item {
width: 190px;
height: 130px;
}[/code]
Этот стиль установит для пунктов списка divList размеры 190*130 пикселов. Такие же размеры будут иметь миниатюры, которые мы станем выводить в этом списке.
Осталось самое сложное - написать код логики. Переключимся на файл default.js, где он хранится.
Сначала введём код, объявляющий необходимые переменные. Вот он:
[code]var divFolderPath, divFileName, imgViewer, ctrList, oCurrentFolder,
arrFiles, dsrFiles;[/code]
Переменные divFolderPath и divFileName будут хранить соответствующие блоки, а переменные imgViewer и ctrList - панель вывода и список. Переменная oCurrentFolder сохранит папку, содержимое которой выводится в списке в данный момент, переменная arrFiles - массив с файлами, что хранятся в этой папке, а переменная dsrFiles - источник данных, созданный на основе этого массива.
Напишем код инициализации.
[code]document.addEventListener("DOMContentLoaded", function() {
WinJS.UI.processAll().then(function() {
var ctrAppBar = document.getElementById("divAppBar").winControl;
divFolderPath = document.getElementById("divFolderPath");
divFileName = document.getElementById("divFileName");
imgViewer = document.getElementById("imgViewer");
ctrList = document.getElementById("divList").winControl;
var btnSelect = ctrAppBar.getCommandById("btnSelect");
ctrList.addEventListener("iteminvoked", ctrListItemInvoked);
btnSelect.addEventListener("click", btnSelectClick);
ctrList.itemTemplate = listTemplate;
oCurrentFolder = Windows.Storage.KnownFolders.picturesLibrary;
fillList();
});
});[/code]
Здесь мы выполняем инициализацию элементов управления Metro, получаем доступ ко всех нужным нам элементам интерфейса и привязываем к ним обработчики событий. Также мы привязываем к списку функцию-шаблон listTemplate, помещаем в переменную oCurrentFolder библиотеку Изображения, содержимое которой должно выводиться в списке изначально, и вызываем функцию fillList, которая заполнит список.
Объявим функцию, которая будет вызвана при щелчке на кнопке Выбрать папку панели инструментов.
[code]function btnSelectClick() {
var oFP = new Windows.Storage.Pickers.FolderPicker();
oFP.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
oFP.fileTypeFilter.replaceAll([".gif", ".jpg", ".png"]);
oFP.viewMode = Windows.Storage.Pickers.PickerViewMode.thumbnail;
oFP.pickSingleFolderAsync().then(function(folder) {
if (folder) {
oCurrentFolder = folder;
fillList();
}
});
}[/code]
Она выведет экран выбора папки и, в случае, если пользователь укажет в нём какую-либо папку, поместит её в переменную oCurrentFolder и, опять же, вызовет функцию fillList...
...которую сейчас самое время объявить.
[code]function fillList() {
divFolderPath.textContent = oCurrentFolder.path;
arrFiles = [];
oCurrentFolder.getFilesAsync().then(function(files) {
if (files.size > 0) {
for (var i = 0; i < files.size; i++) {
arrFiles.push(files);
}
}
dsrFiles = new WinJS.Binding.List(arrFiles);
ctrList.itemDataSource = dsrFiles.dataSource;
});
}[/code]
Мы выводим в блоке divFolderPath путь к текущей папке, помещаем все имеющиеся в ней файлы в массив arrFiles, формируем на основе этого массива источник данных и привязываем его к списку.
На очереди - функция-шаблон listTemplate.
[code]function listTemplate(itemPromise) {
return itemPromise.then(function(item) {
var oItem = item.data;
var oDiv = document.createElement("div");
var oImg = document.createElement("img");
oItem.getThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.picturesView).then(function(thumbnail) {
oImg.src = URL.createObjectURL(thumbnail);
});
oDiv.appendChild(oImg);
return oDiv;
});
}[/code]
Она создаёт блок, который станет пунктом списка, и помещает внутри него элемент графического изображения, в котором выводит миниатюру соответствующего файла.
Не забудем также объявить функцию - обработчик события iteminvoked списка.
[code]function ctrListItemInvoked(evt) {
evt.detail.itemPromise.then(function(selected) {
var oFile = selected.data;
imgViewer.src = URL.createObjectURL(oFile);
divFileName.textContent = oFile.name;
});
}[/code]
Она получит выбранный в списке файл, выведет его содержимое в элементе графического изображения imgViewer, а его имя — в блоке divFileName.
Сохраним все файлы и запустим приложение. Дождёмся, когда в списке появятся все файлы, хранящиеся в основной папке библиотеки Изображения, выберем какой-либо файл и посмотрим на его содержимое (рис. 3). После этого попробуем выбрать другую папку и понаблюдаем, что из этого выйдет. Если мы всё сделали правильно, приложение должно работать нормально.
Рис. 3. Интерфейс приложения для просмотра графических файлов
10. Заключение
В этой статье мы научились работать со списками и панелями вывода Metro, пользоваться экраном выбора папки и средствами Metro для получения списка файлов и сведений о файле, в том числе его миниатюры. Также мы разобрались с механизмами безопасности платформы Metro и узнали, как задавать права приложения. И в качестве практики написали простое приложение для просмотра графики.
Сайт является источником уникальной информации о семействе операционных систем Windows и других продуктах Microsoft. Перепечатка материалов возможна только с разрешения редакции.
Работает на WMS 2.34 (Страница создана за 0.035 секунд (Общее время SQL: 0.016 секунд - SQL запросов: 53 - Среднее время SQL: 0.0003 секунд))