Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript
Брокшмидт Крэйг

Содержание


Лекция 0. Введение

Добро пожаловать, друзья, в мир Windows 8! От имени тысяч дизайнеров, менеджеров, разработчиков, тестировщиков и технических писателей, которые дали этому продукту жизнь, я рад приветствовать вас в мире Преображенной Windows.

И это не какая-то маркетинговая уловка, которая нужна для того, чтобы придать чему-либо, в сущности, не изменившемуся, новый вид, наподобие лозунга: "Новая улучшенная упаковка" на товарах для дома. Нет, Microsoft Windows на самом деле была рождена заново, за более чем четверть века появилось кое-что по-настоящему новое.

Я подозреваю, а, на самом деле - ожидаю, что вы уже имеете некоторый опыт работы с преображенным пользовательским интерфейсом Windows 8. Возможно, вы читаете этот курс, так как вы уже знаете о возможностях Windows 8, которые позволяют ей работать на настольных компьютерах, ноутбуках и планшетных ПК. Эти возможности, дополненные глобальной доступностью Магазина Windows, могут дать вам невероятные бизнес-возможности, занимаетесь ли вы бизнесом, как я люблю говорить, ради славы, богатства, удовольствия или из чистого человеколюбия.

Конечно, мы рассмотрим множество граней нового опыта взаимодействия пользователя и системы на протяжении курса. Однако наша основная задача заключается в рассмотрении совершенно нового опыта взаимодействия разработчика и системы.

Я не говорю об этом в пренебрежительном тоне. Когда я впервые проводил презентацию Microsoft о создании приложений для Магазина Windows, мне захотелось показать слайд, показывающий мир, каким он был в 1985 году. Это было время Рональда Рейгана и Маргарет Тэтчер, время Холодной войны. Это было время видеомагнитофонов и обнаружения вируса СПИДа. Тогда вышел в прокат "Назад, в будущее", Майкл Джексон попал на вершину чартов с "Триллером", и Стива Джобса вышибли из Apple. И именно тогда разработчики впервые испробовали вкус оригинального API Windows и модель программирования настольных приложений.

Долговечность той модели программирования впечатляет. Она оставалась на своём месте более двадцати пяти лет, и выросла до уровня, который позволил ей стать сердцем самой большой деловой экосистемы на планете. Да и сам API, известный сегодня как Win32, так же вырос до размеров самого большого API в мире. То, что сначала выглядело как примерно 300 вызываемых методов, выросло на три порядка, в итоге, хорошо, если отдельно взятый человек способен разобраться хотя бы с частью всего этого. Я, конечно, сам оставил бесполезные попытки всё это охватить.

Поэтому, когда я столкнулся с моим старым другом Кайлом Маршем, осенью 2009-го, сразу после того, как была выпущена Windows 7, и узнал от него, что Microsoft планирует влить свежую кровь в разработку приложений для Windows 8, основанных на её собственных стандартах, я навострил уши. Время шло, и я узнал, что Microsoft представляет абсолютно новый API, который называется Windows Runtime (или WinRT). Это не означает замену Win32, если вы об этом подумали; классические приложения для настольных ПК всё еще поддерживаются. Нет, это модель программирования, построенная с нуля, предназначенная для совершенно нового поколения приложений, ориентированных на сенсорное управление, создающих эффект погружения пользователя в виртуальную среду. Эти приложения вполне могут конкурировать с приложениями, которые существуют на различных мобильных платформах. При разработке этой модели программирования больше учитывалась точка зрения разработчика, нежели системные особенности, в итоге, для реализации ключевых возможностей нужно лишь несколько строк кода - вместо сотен или тысяч строк. Кроме того, WinRT позволяет напрямую, без посредников, разрабатывать собственные приложения с использованием множества языков программирования. Это означает, что новые возможности операционной системы доступны разработчикам без необходимости ожидания обновления некоего промежуточного фреймворка. Кроме того, это означает, что разработчики, имеющие опыт программирования на каком-либо языке, будут чувствовать себя как дома при создании приложений для Windows 8.

Меня порадовали эти новости, так как в последний раз Microsoft делала что-то по-настоящему великое в сфере модели программирования для Windows, было в начале 1990-х, когда была представлена технология, названная Component Object Model (COM), которая и способствовала взрывному росту популярности Win32 API. По иронии судьбы, именно я представлял тогда COM сообществу разработчиков, делая это посредством двух редакций книги "Inside OLE" (Microsoft Press, 1993 и 1995) и в ходе почти бесконечных поездок с выступлениями на конференциях и с визитами в компании-партнёры. История, однако, имеет свойство повторяться, и вот, я снова здесь!

В декабре 2010 я был участником небольшой группы, созданной для того, чтобы написать самые первые приложения для Магазина Windows с использованием доступных частей WinRT API. Мы выбрали, в качестве текстового редактора, Notepad, мы компилировали и запускали приложения, используя командную строку и невразумительные скрипты Powershell, что требовало от нас вручную вводить ужасные хэш-строки. У нас не было документации, за исключением часто неполных функциональных спецификаций. У нас не было отладчика, за исключением методов проб и ошибок и использования window.alert и document.writeln. На самом деле, мы, в основном, работали с HTML, CSS и JavaScript, используя браузер и средства отладки F12, добавляя код, специфичный для WinRT лишь на завершающем этапе разработки, так как браузер не может работать с этим API. Вы можете представить себе, как мы ликовали, когда хоть что-то заработало!

К счастью, совсем скоро стали доступны инструменты разработки вроде Visual Studio Express и Blend для Visual Studio. Весной 2011, когда я проводил много тренингов по разработке Windows 8-приложений для сотрудников Microsoft, процесс разработки стал куда более приятным и быстрым. На самом деле, если в 2010-м нам нужно было несколько недель на то, чтобы программа вывела на экран "Hello World", то уже осенью 2011-го мы работали с компаниями-партнерами, которые за то же время создавали готовые приложения для Магазина Windows.

Как мы увидели, что полностью оправдало наши ожидания, можно создать отличное приложение за считанные недели. Я надеюсь, что данный курс, вместе с дополнительными материалами на http://dev.windows.com поможет вам сделать то же самое, и преобразить ваши собственные проекты.

Для кого этот учебный курс

Это учебный курс о создании приложений для Магазина Windows с использованием технологий HTML5, CSS3 и JavaScript. Наша главная цель заключается в применении этих веб-технологий внутри платформы Windows 8, с учетом специфики такого применения, а не в изучении тонкостей самих этих веб-технологий. В большинстве случаев, я предполагаю, что вы обладаете некоторыми познаниями в области этих стандартов. Мы подробно остановимся на некоторых наиболее важных вещах, таких, как CSS-сетка, которая играет ведущую роль при создании макетов страниц приложений, но, в то же время, я надеюсь, что вы способны будете найти дополнительную информацию о других технологиях.

Кроме того, я предполагаю, что ваш интерес к Windows 8 имеет в своей основе, как минимум, два основных мотива.

Во-первых, вы, возможно, хотите начать разработку настолько быстро, насколько это возможно, возможно, для того, чтобы обрести точку опоры в Магазине Windows как можно скорее. Следуя этой идее, я поместил в начальные главы самые важные сведения о разработке, которые, вместе с разделами "Быстрый старт", немедленно предоставят вам необходимый опыт обращения с инструментами разработки, с API и с некоторыми ключевыми возможностями платформы.

Во-вторых, вы, возможно, хотите разработать приложение наилучшим образом. Такое приложение, которое отлично работает и использует преимущества всех возможностей платформы. Ориентируясь на это, я постарался включить в курс материал, который поможет вам, по меньшей мере, узнать о возможностях оптимизации приложений. (Обратите внимание на то, что особенности Магазина Windows обсуждаются в Главе 6 курса "Программная логика приложений для Windows 8 с использованием HTML, CSS и JavaScript и их взаимодействие с системой").

Множество идей приходит от взаимодействия с реальными разработчиками, которые создают реально действующие программы. Как часть команды Windows Ecosystem, я и мои товарищи, находимся на переднем крае процесса, в ходе которого эти первые приложения попадают в Магазин Windows. Это включает в себя написание кода для таких приложений, процесс поиска ошибок, вместе с сопровождением дизайна, кода, обзоров производительности совместно с командой Windows-инженеров. И одна из моих целей, которые я пытаюсь достичь с помощью этого учебного курса - это предоставить возможность глубокого понимания происходящего многим другим разработчикам, и, в том числе - вам!

Что вам понадобится (Как насчет "Примеров"?)

Для того чтобы работать с этим учебным курсом, вам понадобится Windows 8, установленная на вашем рабочем компьютере вместе с Windows SDK и инструментами разработки. Все инструменты, вместе с большим списком других ресурсов, можно найти здесь: "Загрузки для разработчиков приложений Магазина Windows" (http://msdn.microsoft.com/ru-RU/windows/apps/br229516). В частности, вам понадобится Microsoft Visual Studio Express 2012 для Windows 8. Нам понадобятся и другие инструменты, мы, в ходе работы, рассмотрим их при возникновении необходимости. (Обратите внимание на то, что при создании скриншотов к этому курсу, я переключил Visual Studio из "тёмной" темы, которая установлена по умолчанию, на "светлую" тему, так как она лучше смотрится на белых страницах).

Кроме того, убедитесь, что вы загрузили набор примеров Windows 8 app samples (http://code.msdn.microsoft.com/windowsapps/Windows-8-Modern-Style-App-Samples), и, в частности, загрузили примеры по JavaScript SDK. Мы будем использовать много - если не большую часть этих примеров в последующих главах, вытягивая оттуда участки кода для того, чтобы показать, как много различных задач мы можем решать с помощью рассматриваемых технологий.

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

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

Эти дополнительные материалы, кроме того, включают несколько моих собственных разработок, на которые я всегда ссылаюсь как на "упражнения" для того, чтобы было понятно, что они не имеют отношения к официальному контенту из SDK. (Кроме того, я выделил измененные примеры для того, чтобы было понятно, что они - часть этого учебного курса). Я написал это всё для того, чтобы заполнить пробелы, которые имеются в SDK-примерах, или для того, чтобы предоставить более простую демонстрацию возможностей, которые раскрыты в соответствующих образцах более сложным способом. Кроме того, вы найдёте здесь множество редакций приложения, которое я назвал "Here My Am!". Работу над этим приложением мы начнем уже в Главе 2 и будем улучшать его на протяжении всего цикла курсов, в который помимо данного курса входят еще два: "Пользовательский интерфейс приложений для Windows 8 с использованием HTML, CSS и JavaScript" и "Программная логика приложений для Windows 8 с использованием HTML, CSS и JavaScript и их взаимодействие с системой". Кроме прочего, это включает локализацию приложения для множества различных языков на финальной стадии работы.

Кроме того, вы обнаружите, что и в каталоге Windows 8 samples gallery (http://code.msdn.microsoft.com/windowsapps/) и на странице Visual Studio sample gallery (http://code.msdn.microsoft.com/vstudio) есть множество дополнительных примеров, которые добавлены другими разработчиками, и, возможно - вами. (На странице с примерами для Visual Studio, кстати, убедитесь в том, что включили отбор по приложениям для Магазина Windows (Windows Store), так как там присутствуют примеры, касающиеся всех платформ, поддерживаемых Microsoft). И, конечно, многие разработчики самостоятельно выкладывают в общий доступ свои проекты.

В этом курсе я иногда ссылаюсь на посты в "Блоге для разработчиков приложений для Windows 8" (http://blogs.msdn.com/b/windowsappdev_ru/). Это - ценный ресурс, на который стоит подписаться. Кроме того, я рекомендую подписаться на обновления блога "Магазин Windows: блог для разработчиков" (http://blogs.msdn.com/b/windowsstore_ru/) - для того, чтобы быть в курсе сообщений о том, как повысить эффективность вашего бизнеса в Магазине Windows. И, если вам интересны подробности о Windows 8, показаные с точки зрения команды разработчиков этой ОС, загляните в блог "Создание Windows 8" (http://blogs.msdn.com/b/b8_ru/).

Замечания о форматировании

В этом курсе идентификаторы, появляющиеся в коде, такие, как имена переменных, имена свойств, функции API, выделены цветом и набраны моноширинным шрифтом. Например, так: Windows.Storage.ApplicationData.current. Время от времени такие, полные имена, включающие в себя полное пространство имен, могут стать весьма длинными, в итоге, иногда нужно будет выполнять переносы таких имен на новую строку. Например, в таком имени, как Windows.Security.Cryptography.CryptographicBuffer.convertString-ToBinary. Обобщая сказанное, я пытался добавлять знаки переноса либо после точки, либо между отдельными словами, но не внутри слов. В любом случае - эти дефисы не являются частью идентификаторов, за исключением CSS, где использование дефисов разрешено (как в -ms-high-contrast-adjust) и в атрибутах HTML, наподобие aria-label или data-win-options.

Иногда вы будете видеть идентификаторы, выделенные другим цветом, как, например, datarequested. Такое выделение особо указывает на события, которые порождают объекты Windows Runtime, для которых имеются специальные условия на добавление и удаление обработчиков событий в JavaScript. Мы поговорим об этом в Главе 3. Я напоминаю иногда об этом, но цель данного цветового выделения - напомнить вам об этом, не прерывая поток повествования.

Благодарности

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

Многие заслуживают особого упоминания за их длительную поддержку этого проекта. Во-первых, это Крис Селлс, с которым вместе я писал ранние версии этого курса, и который теперь возглавляет отдел разработки в Telerik. Достойны упоминания Махеш Пракрия, Ян ЛеГроув, Ананта Канчерла, Кейт Бойд и их сослуживцы, с которыми я близко сотрудничал, Кейт Роув, Деннис Фланаган, и Ульф Шу, с которыми я счастлив был работать.

Спасибо Дейвону Мастгрейву из Microsoft Press, который провёл много долгих часов, внося правки в мои главы, повторяя это снова и снова. Мои товарищи по команде - Кайл Марш, Тодд Ландстад, Шай Хайнитц, Патрик Денглер, Лора Хейни, Леон Брагински и Джозеф Нгари неоценимы в том, что они делились тем, что узнали от партнёров. Особое спасибо Кеничиро Танаке из Японского отделения Microsoft, который всегда был первым, кто передавал мне просмотренные главы, и за замечательные исследования различных аспектов платформы, о которых я спрашивал. Низкий поклон тебе, мой друг! Поклон и другим в нашей международной команде экосистемы Windows, которые помогли с локализацией "Here My Am!" для Главы 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой": Джил Пенне, Сэм Чанг, Селия Пипо Гарсия, Юрген Швертл, Матен Ван де Доспорт, и Ли-Юн Я (и Шай Хинитц на Иврите).

Следующие люди внесли определенный вклад в учебный курс, просматривая главы, отвечая на мои вопросы, обсуждая со мной подробные детали и так далее. Спасибо всем вам за ваше время и поддержку:

Shakil AhmedScott DickensKishore KotteriDaniel OliverSam Spencer
Chris AndersonTyler DonahueVictoria KruseJason OlsonBen Srour
Erik AndersonBrendan ElliottNathan KuchtaElliot H OmiyaAdam Stritzel
Axel AndrejsMatt EsquivelElmar LangholzLarry OstermanShijun Sun
Tarek AynaDavid FieldsBonny LauRohit PagariyaSou Suzuki
Art BakerErik FortuneTravis LeitheadAnkur PatelSimon Tao
Adam BarrusJim GalasynChantal LeonardHarry PiersonHenry Tappen
Megan BatesGavin GearCameron Lerum*Steve ProteauChris Tavares
Tyler BeamDerek GephardBrian LeVeeHari PulapakaDavid Tepper
Ben BetzMarcelo Garcia GonzalezJianfeng LinArun RabinarSara Thomas
Johnny BregarSunil GottumukkalaTian LuoMatt RakowRyan Thompson
John BrezakScott GrahamSean LyndersayRamu RamanathanBill Ticehurst
John BronskillBen GroverDavid MachajRavi RaoStephen Toub
Jed BrownPaul GusmorinoMike MastrangeloBrent RectorTonu Vanatalu
Vincent CelieRylan HawkinsJordan MatthiesenRuben RiosJeremy Viegas
Raymond ChenJohn HazenIan McBurnieDale RogersonNick Waggoner
Rian ChungJerome HolmanJesse McGathaNick RotondoDavid Washington
Arik CohenScott HoogerwerfMatt MerryDavid RoussetSarah Waskom
Justin CoopermanStephen HufnagelMarkus MielkeGeorge RoussosMarc Wautier
Michael CriderSean HumePavel MinaevJake SabulskyJosh Williams
Priya DandawateMathias JourdainJohn MorrowPerumaal ShanmugamLucian Wischik
Darren DavisDamian KedzierskiFeras MoussaEdgar Ruiz SilvaKevin Michael Woley
Jack DavisSuhail KhalidJohn MullalyKaranbir SinghCharing Wong
Ryan DemopoulosDaniel KitchenerJan Nelson*Peter SmithMichael Ziller

Яну и Кэмерон - особое спасибо за то, что приехали из Редмонда, штат Вашингтон, чтобы навестить меня в Портленде, штат Орегон (где я живу), и разделили со мной тайский ланч, за которым мы говорили о локализации и многоязычных приложениях.

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

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

Лекция 1. Жизненный путь приложений для Магазина Windows: Характеристики платформы Windows 8

Эта лекция посвящена описанию жизненного цикла приложений для Магазина Windows, содержит описание инструментария разработчика и знакомит читателя с основными положениями разработки приложений с использованием технологий HTML, CSS и JavaScript

Бумага или пластик? Да или нет? Быть или не быть? Основанный на стандартах или на собственных правилах? Вот они - вопросы нашего времени…

Может быть, большинство из этих вопросов и не подходят для университетских курсов философии, но последний из них всё важнее и важнее для разработчиков программного обеспечения. Приложения, основанные на стандартах - это замечательно, так как они могут выполняться на множестве платформ; ваши знания и опыт работы с HTML5 и CSS3 вполне поддаются использованию в новых средах. К несчастью, так как обычно требуется много времени на разработку стандартов, они всегда отстают от возможностей платформ, на которых используются. В конце концов, платформы-конкуренты всегда будут соревноваться друг с другом. Например, в то время как HTML5 сейчас имеет стандарт для геолокационных/GPS сенсоров и начата работа над черновыми вариантами проектов, касающихся других сенсоров для ввода информации (акселерометр, компас, бесконтактное взаимодействие устройств и так далее), на платформах, основанных на собственных стандартах, всё это доступно. И, в то время, как стандарты HTML используются и широко поддерживаются, отдельные платформы, конечно, добавляют к ним новые возможности.

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

В итоге: это непростой выбор.

К счастью, Windows 8 предоставляет разработчикам то, что лично я считаю просто замечательным решением для создания приложений. В самом начале, команда разработки Windows задалась решением проблемы о предоставлении прямого доступа к собственным возможностям, другими словами - к системному API - посредством любых языков программирования, в том числе - JavaScript. Это то, что известно как Windows Runtime API, или просто WinRT для краткости.

WinRT API реализуются согласно определенной низкоуровневой структуре и затем "проецируются" в различные языки - а именно, C++, C#, Visual Basic и JavaScript - таким образом, что они выглядят естественно для разработчиков, которые хорошо знакомы с теми или иными языками. Это включает создание объектов, управление ими и их конфигурирование; то, как обрабатываются события, ошибки, исключения; как работают асинхронные операции (для того, чтобы сделать взаимодействие пользователя и приложения быстрым и естественным); и даже приведение к принятому виду имен методов, свойств и событий.

Команда разработки Windows, кроме того, сделала возможным написание собственных приложений, которые используют множество технологий представления информации, в том числе - DirectX, XAML, и, в случае с приложениями, написанными на JavaScript - HTML5 и CSS3.

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

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

Так же возможно использование нескольких языков программирования при написании приложений, используя преимущества динамической природы JavaScript для создания логики приложения, усиленной языками наподобие C# и C++ для интенсивных вычислительных задач (смотрите врезку "Приложения на смешанных языках" далее в этой лекции).

Всюду в этом курсе мы будем обращать внимание на то, как использовать то, что вы знаете о веб-приложениях, основанных на стандартах, для создания отличных Windows 8-приложений. В следующей лекции мы сконцентрируемся на основах создания работающих приложений и на инструментах разработки. Затем мы рассмотрим фундаментальные концепции. Такие, как полная анатомия приложения, элементы управления, коллекции, макеты, командный интерфейс, управление состояниями, ввод данных. Далее следуют лекции, посвященные работе с медиаданными, анимациями, контрактами, позволяющими приложениям взаимодействовать. Мы поговорим о сетевом программировании, об устройствах, компонентах WinRT (посредством которых вы можете использовать другие языки программирования и API, к которым они могут получать доступ). Мы рассмотрим работу с Магазином Windows (тема, включающая вопросы локализации и поддержки приложениями специальных возможностей). Здесь достаточно много всего для изучения.

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

Замечание о терминологии. То, что мы называем приложения для Магазина Windows (Windows Store apps), или просто приложения для Магазина (Store apps), это те приложения, которые можно загрузить из Магазина Windows, и для которых применимы характеристики платформы, о которых идет речь в этой лекции (и в курсе). Они совершенно чётко отличаются от традиционных настольных приложений (desktop applications), которые можно получить через обычные каналы распространения и установить, используя их собственные инсталляторы. Если это особо не отмечено, то "приложение" (app) в этом курсе означает именно приложение для Магазина Windows.

Уход из дома: Обустройство в Магазине Windows

У приложений для Магазина Windows есть лишь одна дверь во внешний мир: пользователь всегда загружает, устанавливает и обновляет приложения посредством Магазина Windows. Разработчики и корпоративные пользователи могут загружать приложения иным способом, но для огромного большинства людей, в которых вы видите пользователей своих приложений, есть лишь один способ получить эти приложения - посредством Магазина Windows.

Это, очевидно, означает, что приложение - как кульминация вашей деятельности по разработке - в первую очередь должно оказаться в Магазине Windows. Это происходит, когда вы, полные гордости и радости, упаковываете свое приложение и отправляете его в Магазин Windows, используя команду Магазин > Отправить пакеты приложения (Store > Upload App Package) в Visual Studio1). Пакет - это файл appx (.appx) (Рис. 1.1), который содержит программный код вашего приложения, ресурсы, библиотеки и файл-манифест. Манифест описывает приложения (название, логотип и так далее), возможности (capabilities), к которым вашему приложению нужен доступ (такие, как определенные области файловой системы или аппаратные устройства наподобие камеры), и всё остальное, что нужно для того, чтобы приложение заработало (ассоциации с файлами, объявления фоновых задач и так далее). Поверьте мне, мы станем близкими друзьями с манифестом!

Appx-пакет - это обычный zip-файл, который содержит файлы и активы приложения, файл-манифест приложения и нечто вроде "оглавления", которое называется картой сопоставления блоков (blockmap). При отправке приложения, изначальная цифровая подпись формируется средствами Visual Studio; в Магазине Windows приложение будет переподписано после сертификации. Карта сопоставления блоков, в одной из своих частей, описывает, как файл приложения разбивается на блоки размером 64 Кб. В дополнение к обеспечению определенных функций безопасности (наподобие определения фактов подделки или неправомерного изменения пакета) и оптимизации производительности приложения, блочная карта используется для точного определения частей приложения, которые подверглись обновлению при выходе новой версии приложения, таким образом, Магазин Windows позволяет загружать, при обновлении приложения лишь измененные блоки вместо загрузки всего приложения. Это значительно уменьшает затраты времени и других ресурсов пользователя, когда он загружает и устанавливает обновления приложений


Рис. 1.1.  Appx-пакет - это обычный zip-файл, который содержит файлы и активы приложения, файл-манифест приложения и нечто вроде "оглавления", которое называется картой сопоставления блоков (blockmap). При отправке приложения, изначальная цифровая подпись формируется средствами Visual Studio; в Магазине Windows приложение будет переподписано после сертификации. Карта сопоставления блоков, в одной из своих частей, описывает, как файл приложения разбивается на блоки размером 64 Кб. В дополнение к обеспечению определенных функций безопасности (наподобие определения фактов подделки или неправомерного изменения пакета) и оптимизации производительности приложения, блочная карта используется для точного определения частей приложения, которые подверглись обновлению при выходе новой версии приложения, таким образом, Магазин Windows позволяет загружать, при обновлении приложения лишь измененные блоки вместо загрузки всего приложения. Это значительно уменьшает затраты времени и других ресурсов пользователя, когда он загружает и устанавливает обновления приложений

В процессе отправки приложения вы пройдёте через этап задания имени приложения (это вы можете сделать заранее, использовав команду Магазин > Зарезервировать имя приложения (Store > Reserve App Name) в Visual Studio), вы выберете параметры продаж (в том числе - цену, параметры продаж внутри приложения, пробный период), введете описание приложения и добавите необходимые изображения, и, кроме того, сможете добавить примечания для тех, кто будет тестировать приложение вручную. После этого ваше приложение проходит через ряд этапов, в частности, через автоматическую проверку (поиск вредоносного ПО, сертификацию GeoTrust), и через серию тестов, которые проводят люди, которые прочтут ваши примечания (поэтому будьте вежливы и доброжелательны). Вы можете отследить прохождение приложения по этапам сертификации, используя сведения о состоянии приложения в Информационной панели Магазина Windows2) (dashboard) (https://appdev.microsoft.com/StorePortals).

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

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

С точки зрения разработчика, на самом деле, эти взаимоотношения распространяются на полный жизненный цикл приложения - от планирования и программирования, до распространения, поддержки и обслуживания. Именно поэтому я начал историю жизни приложения с Магазина Windows, так как вам, на самом деле, нужно разобраться в особенностях полного жизненного цикла приложения, с самого начала - с планирования и проектирования приложения. Если, например, вы хотите получать прибыль с платных приложений или с приложений, внутри которых осуществляются продажи, возможно, так же предлагая ограниченные по времени или функциональности пробные версии, вы захотите и спроектировать своё приложение соответствующим образом. Если вы хотите создать бесплатное приложение, которое ориентировано на показ рекламных объявлений, или если вы захотите использовать бизнес-решения сторонних разработчиков для продаж внутри приложения (обходя, таким образом, отделение части доходов Магазину Windows), такие варианты выбора так же повлияют на проектирование приложения с самого начала. И даже если вы просто хотите выпустить промо-приложение или поделиться с миром чем-то приятным, понимание особенностей взаимоотношений между вашим приложением и Магазином Windows всё так же важно. Под влиянием всех этих причин, вы можете забежать вперед, прочитать раздел "Ваше приложение, ваш бизнес" лекции 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", прежде чем вы всерьез начнете писать код своего приложения. Кроме того, взгляните на статью "Подготовка приложения для отправки в Магазин" (http://msdn.microsoft.com/ru-ru/library/windows/apps/hh694079.aspx) на портале Центра Разработчиков Windows.

В любом случае, если ваше приложение споткнётся на пути сертификации, вы получите отчет, содержащий все подробности, такие, как нарушение "Сертификационных требований к приложениям для Windows 8" (http://msdn.microsoft.com/ru-ru/library/windows/apps/hh694083.aspx), которые являются частью "Соглашения для Магазина Windows" (http://msdn.microsoft.com/ru-ru/library/windows/apps/hh694082.aspx). В противном случае - примите мои поздравления - ваше приложение готово для загрузки пользователями!

Врезка: Store API и имитатор (simulator)

Класс Windows.ApplicationModel.Store.CurrentApp в WinRT предоставляет приложениям возможность получать информацию о своих параметрах (в том числе - о покупках внутри приложения) из Магазина, проверять статус лицензии, предлагать пользователям совершить покупки (такие, как апгрейд пробной версии до полной платной, или покупки внутри приложения).

Безусловно, это приводит к закономерному вопросу: как протестировать эти возможности до того, как приложение размещено в Магазине? Ответ на этот вопрос заключается в том, что в ходе разработки данное API используется с помощью класса Windows.ApplicationModel.Store.CurrentAppSimulator вместо вышеописанного. Он полностью идентичен классу CurrentApp, за исключением того, что он работает с локальными XML-данными вместо того, чтобы работать с облачными данными, хранящимися в Магазине. Это позволяет вам имитировать различные ситуации, в которых могло бы оказаться ваше приложение, таким образом, вы можете соответствующим образом испытать все ветви вашего кода. Непосредственно перед упаковкой приложения и отправки его в Магазин, вам нужно лишь поменять CurrentAppSimulator на CurrentApp, и всё. (Если вы забудете, имитатор просто откажется работать на машине, не являющейся компьютером разработчика, например - на компьютере одного из тестировщиков приложений для Магазина Windows)

Обнаружение, получение и установка

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

Каждое приложение в Магазине имеет страницу описания приложения (product description page), на которой люди могут прочесть описание приложения, увидеть скриншоты и обзоры, и возможности вашего приложения, описанные в его манифесте, как показано на Рис. 1.2. Последнее означает, что вы должны разумно подходить к объявлению возможностей приложения. Приложение для проигрывания музыки, например, очевидно заявит о своем намерении получать доступ к музыкальной библиотеке пользователя, но без особой причины не будет нуждаться в объявлении возможности доступа к библиотеке картинок. Точно так же, коммуникационное приложение обычно запрашивает доступ к камере и микрофону, а приложение для чтения новостей, как правило - нет. С другой стороны, программа для чтения электронных книг может запросить доступ к микрофону если у неё есть функция прикрепления аудиозаметок к закладкам, заданным пользователем.

Типичная страница приложения в Магазине Windows, на которой содержимое манифеста определяет, что будет перечислено в разделе описания разрешений, которые требуются приложению. В данном случае, например, в манифесте приложения PuzzleTouch объявлены возможности доступа к библиотеке изображений (Pictures Library), вебкамере (Webcam) и к Интернету в качестве клиента (Internet (Client)).


Рис. 1.2.  Типичная страница приложения в Магазине Windows, на которой содержимое манифеста определяет, что будет перечислено в разделе описания разрешений, которые требуются приложению. В данном случае, например, в манифесте приложения PuzzleTouch объявлены возможности доступа к библиотеке изображений (Pictures Library), вебкамере (Webcam) и к Интернету в качестве клиента (Internet (Client)).

Размещенный в Магазине Windows appx-пакет приложения (Onboarder appx package)

Возможности, объявленные в манифесте, определяют то, что будет перечислено среди разрешений, которые требуются приложению (Capabilities in the manifest determine what shows under app permissions)

Смысл здесь в том, что те возможности, о которых вы заявляете, играют роль для пользователя, и если есть какие-то сомнения, вы должны чётко отразить связь возможностей с функциями вашего приложения в его описании. (Обратите внимание на то, как в описании Puzzle Touch это сделано для камеры). В противном случае, пользователя может весьма озадачить вопрос о том, что ваша программа для чтения новостей собирается делать с микрофоном, в итоге он может выбрать другое приложение, которое покажется ему менее навязчивым 1), вмешивающимся в его личные дела.

Кроме того, пользователь видит цену вашего приложения, и то, предоставляете ли вы пробный период. В любом случае, если он решит установить приложение (будет ли это бесплатное приложение, платное, или пробная версия приложения), ваше приложение окажется развёрнутым на реальном устройстве пользователя. Appx-пакет будет загружен на устройство и автоматически установлен, вместе с любыми ресурсами, от которых он зависит, таких, как Библиотека Windows для JavaScript (Windows Library for JavaScript) (смотрите врезку "Что такое Библиотека Windows для JavaScript"). Как показано на Рис. 1.3, менеджер развертывания приложений Windows создаёт папку для приложения, извлекает в неё содержимое пакета приложения. Он создаёт папки данных приложения (appdata) (здесь будут храниться локальные, перемещаемые и временные данные, к которым приложение может свободно получить доступ, как и к файлам с параметрами в формате ключ-значение, и к некоторым другим папкам, которыми управляет система). Менеджер выполняет необходимые манипуляции с реестром для установки плитки приложения на Начальный экран (Start screen), создания ассоциаций с файлами, установки библиотек, он выполняет и другие действия, описанные в манифесте приложения. В ходе этого процесса участие пользователя не требуется - за исключением чтения и принятия лицензионного соглашения!

Процесс установки приложения для Магазина Windows: точная последовательность шагов неважна


увеличить изображение

Рис. 1.3.  Процесс установки приложения для Магазина Windows: точная последовательность шагов неважна

Загрузка и установка пакета приложения (Download and install app package)

Создание структур папок данных приложения (Create appdata structures)

Загрузка и установка материалов, от которых зависит приложение (объявленных в манифесте) (Download and install dependencies (identified in the manifest)

Создание записей в реестре на основании данных манифеста (Create registry entries based on manifest)

На самом деле, условия лицензирования приложений встроены в Магазин; загрузка приложения означает принятие этих условий. (Однако приложения вполне могут показывать собственные лицензионные требования при старте, так же как и предлагать совершить первый вход в систему, если они подразумевают подобную возможность). Но вот, что интересно: помните ли вы реальную цель всех этих длинных, скучных, набранных прописными буквами лицензионных соглашений, при виде которых мы обычно лишь делаем вид, что читаем их? Почти все из них обычно сообщают, что вы можете установить программное обеспечение лишь на одном компьютере. В случае с приложениями для Магазина Windows, это изменилось: вместо лицензирования, основанного на привязке к компьютеру, они лицензируются для пользователя, давая ему право установить приложение на нескольких, до пяти, устройствах.

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

В любом случае, конечный результат всего этого заключается в том, чтобы приложение и все его подсистемы, необходимые для работы, были бы полностью готовы к работе на устройстве после того, как пользователь коснётся плитки на Начальном экране или запустит приложение посредством таких функций системы, как Поиск или Общий доступ. И, так как система знает всё, что происходило при установке приложения, можно выполнить полностью обратный процесс для 100%-чистой деинсталляции - полностью уничтожить папки с данными приложения, например, вычистить абсолютно всё, что ранее было записано в реестр. Это поддерживает систему абсолютно чистой в любой момент, даже тогда, когда пользователь устанавливает и деинсталлирует сотни или тысячи приложений. Нам нравится описывать это, приводя пример сравнения между гостями, которые приходят к вам домой, и гостями в отеле. В вашем доме гости могут есть вашу пищу, переставлять мебель, разбить пару ваз, скормить объедки домашним животным, бросать всякий хлам за шкафы, другими словами - оставлять после себя некоторое количество необратимых изменений (и вы знаете классические настольные приложения, которые так и делают, я уверен!). В отеле же гости могут пользоваться весьма небольшой частью общей инфраструктуры, и даже если они завалят мусором свою комнату, отель сможет очистить её и привести всё к такому состоянию, будто никого там и не было.

Врезка: Что такое Библиотека Windows для JavaScript

HTML, CSS и JavaScript-код в приложениях для Магазина Windows обрабатывается, компилируются и отрисовывается во время исполнения программ. (Смотрите раздел "Играя в собственной комнате: Контейнер приложения"). В результате, множество системных возможностей для приложений, написанных на JavaScript, таких, как элементы управления, управление ресурсами, стили по умолчанию, поддерживаются посредством Библиотеки Windows для JavaScript, или WinJS, вместо поддержки их посредством Windows Runtime API. При таком подходе, JavaScript-разработчики видят естественную интеграцию этих возможностей в окружение, которое они уже понимают, вместо того, чтобы испытывать необходимость в использовании незнакомых конструкций.

WinJS, например, предоставляет HTML-реализацию множества элементов управления, так что они выглядят как части DOM, их внешний вид может быть настроен с помощью CSS, как и в случае с "родными" для HTML элементами управления. Такой подход гораздо естественнее для разработчиков, чем создание экземпляра некоего WinRT-класса, связывание его с HTML-элементом и настройка его внешнего вида из кода, или с использованием какой-то фирменной системы разметки. Аналогично, WinJS предоставляет библиотеку анимации, которая построена на CSS, воплощающей привычный для пользователя Windows 8 опыт взаимодействия с системой, в итоге, приложению нет необходимости "ломать голову" над тем, чтобы воспроизвести этот опыт собственными средствами.

Вообще говоря, WinJS - это набор инструментов, который содержит множество независимых возможностей, которыми можно пользоваться как совместно, так и по отдельности. Так, WinJS предоставляет шаблоны для типичных структур программного кода, упрощает объявление пространств имен и объектных классов, помогает в обработке асинхронных операций (которые повсюду встречаются в WinRT) посредством promise-объектов, и предоставляют структурные модели для приложений, привязки данных, навигации по страницам приложения. В то же время, не делается попытка создать "обертку" для WinRT, за исключением тех случаев, где WinJS может принести реальную пользу. В конце концов, механизм, посредством которого WinJS проецируется на JavaScript уже приводит WinRT-структуры к тем, которые хорошо знакомы JavaScript-разработчикам.

В целом, WinJS важен для всех приложений для Магазина Windows, написанных на JavaScript, которые совместно пользуются этим набором инструментов. WinJS автоматически загружается и, при необходимости, обновляется, когда устанавливаются приложения, зависящие от него. Мы увидим множество его возможностей в этом курсе, хотя некоторые наш путь не пересекают. В любом случае, вы можете всегда узнать о том, что можно сделать с помощью WinJS в соответствующем разделе Справочника по Windows API для приложений Магазина Windows (http://msdn.microsoft.com/ru-ru/library/windows/apps/xaml/br211377.aspx).

Врезка: Библиотеки сторонних разработчиков

WinJS - это пример специальной совместно используемой библиотеки, которая автоматически загружается из Магазина Windows для приложений, которые в ней нуждаются. Microsoft поддерживает несколько подобных в Магазине Windows, таким образом, пакет библиотеки должен быть загружен лишь один раз и затем приложения могут использовать его совместно. Библиотеки сторонних разработчиков, к которым возможен общий доступ приложений в настоящее время не поддерживаются.

Однако приложения могут свободно использовать библиотеки сторонних разработчиков, встраивая их в собственные пакеты, при условии, конечно, что библиотеки используют лишь API, доступные приложениям для Магазина Windows.

Например, приложения, написанные на JavaScript могут использовать jQuery, Modernizer, Dojo, prototype.js, Box2D и другие, с учетом того, что некоторая функциональность, в особенности

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

Играя в собственной комнате: Контейнер приложения

Так же, как ваши планы могут меняться каждое утро, когда вы просыпаетесь после ночного отдыха, приложения для Магазина Windows могут просыпаться - то есть - активироваться, по самым разным причинам. Конечно, пользователь может коснуться плитки приложения на Начальном экране или щёлкнуть по ней мышью. Приложение, кроме того, может быть запущено в ответ на команды чудо-кнопок, такие, как Поиск или Общий доступ, посредством связей с файлами или протоколами, и в ходе действия большого количества других механизмов. Мы рассмотрим эти варианты в ходе обучения. Но, в любом случае, кое-что еще можно сказать об этом процессе для приложений, написанных на JavaScript.

В скрытой папке, где хранится пакет приложения, расположены такие же файлы, которые вы можете видеть в веб-проектах: .html-файлы, .css, .js-файлы и так далее. Здесь нет исполняемых файлов наподобие .exe-файлов приложений, написанных на C#, Visual Basic или C++. В итоге, что-то должно взять эти исходные файлы и сделать из них запускаемое приложение. Когда ваше приложение активируется, фактически, его исполняет специальный хост-процесс приложения, который называется wwahost.exe1), как показано на Рис. 1.4.

Хост-процесс приложения - это программа (wwahost.exe), которая загружает, визуализирует и исполняет HTML, CSS и JavaScript, делая это практически так же, как браузер, в котором исполняются веб-приложения.


Рис. 1.4.  Хост-процесс приложения - это программа (wwahost.exe), которая загружает, визуализирует и исполняет HTML, CSS и JavaScript, делая это практически так же, как браузер, в котором исполняются веб-приложения.

Папка приложения (App folder)

Хост-процесс приложения (App Host Process)

Движок JavaScript (JavaScript Engine)

Подсистема визуализации HTML/CSS (HTML/CSS Rendering Engine)

Контейнер приложения (App Container)

В памяти (In memory)

Дисплей (Display)

Хост-процесс - это что-то, напоминающее Internet Explorer 10 без графических элементов браузер. Их сходство определяет то, что ваши приложения исполняются на тех же HTML/CSS/JavaScript-движках, что и в Internet Explorer. Различия заключаются в том, что некоторые механизмы в двух этих средах работают по-разному. Например:

Для того чтобы узнать подробности обо всём этом, посмотрите материалы "Список изменений API для модели DOM и HTML" (http://msdn.microsoft.com/library/windows/apps/hh700404.aspx) и "Возможности и различия HTML, CSS и JavaScript" (http://msdn.microsoft.com/library/windows/apps/hh465380.aspx) в Центре разработчиков Windows (http://msdn.microsoft.com/ru-RU/windows/apps/). Вы должны подружиться с Центром разработчиков так же, как подружитесь с файлом-манифестом приложения.

Сейчас все приложения для Магазина Windows, подразумевающие наличие хост-процесса или нет, выполняются внутри окружения, которое называется контейнером приложений (app container). Это уровень изоляции, если угодно, который блокирует локальное взаимодействие между процессами и, кроме того, блокирует доступ к системным ресурсам или служит брокером (broker), посредником. Ключевые характеристики контейнера приложения описаны ниже и проиллюстрированы на Рис. 1.5.

Приложения для Магазина Windows не могут программно запускать другие приложения по имени файла, или используя путь в файловой системе, но могут сделать это посредством файла или URI-схемы для связи. Так как всё это, в конечном счете, находится под управлением пользователя, нет гарантии, что подобное действие приведет к запуску определенного приложения. Однако мы приводим разработчиков к мысли об использовании схем URI, специфичных для приложений, которые могут эффективно идентифицировать ваше конкретное приложение в качестве целевого. Говоря техническим языком, другое приложение может прийти и зарегистрировать ту же самую схему URI (таким образом, предоставляя пользователю выбор), но это маловероятно со схемой URI, с которой тесно связаны идентификационные данные приложения.

Приложения для Магазина Windows изолированы одно от другого для защиты от различных форм атак. Это так же означает, что некоторые вполне безобидные приложения (такие, как инструмент для создания копий экрана и вырезания их фрагментов) не могут быть созданы в виде приложений для Магазина Windows. Они должны быть настольными приложениями.

Прямое межпроцессное взаимодействие заблокировано между приложениями для Магазина Windows (за исключением некоторых случаев отладки), между приложениями для Магазина Windows и настольными приложениями и между приложениями для Магазина Windows и локальными сервисами. Приложения, по-прежнему, могут взаимодействовать через облако (вбе-сервисы, сокеты и так далее), и большинство общих задач, которые могут требовать совместную работу приложений - такие, как Поиск и Общий доступ к данным - обрабатываются посредством контрактов (contract), при реализации которых приложения не нуждаются в знании подробностей друг о друге.

Изоляция процессов у приложений для Магазина Windows


увеличить изображение

Рис. 1.5.  Изоляция процессов у приложений для Магазина Windows

Данные приложения, локальные, временные, перемещаемые (AppData Local, Temp, Roaming)

Все остальные области (All other areas)

Прямой доступ (Direct Access)

Доступ через брокера (Brokered Access)

Файл-манифест (Manifest)

Хост-процесс приложения (App Host Process)

Контейнер приложения (App Container)

Системные API (System APIs)

Взаимодействие через облако (Communication via Cloud)

Контракты (Contracts)

Процесс приложения (App process)

Врезка: Приложения на смешанных языках

Приложения для Магазина Windows, написанные на JavaScript могут получать доступ к API WinRT только напрямую; приложения или библиотеки, написанные на C#, VisualBasic и C++, кроме того, имеют доступ к подмножествам Win32 и .NET API, как описано в "Win32 и COM для приложений Магазина Windows" (http://msdn.microsoft.com/library/windows/apps/br205757.aspx). Нечестно? Не вполне, так как вы можете писать WinRT-компоненты (WinRT components) на этих языках, чтобы создать функциональность, включающую в себя доступ к иным API в окружении JavaScript (посредством того же механизма проецирования, который использует сам WinRT). Так как эти компоненты компилируются в двоичные динамически связываемые библиотеки (DLL), они так же обычно выполняются быстрее, нежели аналогичный код, написанный на JavaScript, и, кроме того, предоставляют некоторый уровень защиты интеллектуальной собственности (например, с использованием алгоритмов скрытия).

Подобные приложения на смешанных языках (mixed language apps) используют HTML/CSS для своего презентационного уровня и некоторую логику, размещая наиболее чувствительный к производительности или критически важный код в скомпилированных DLL. Динамическая природа JavaScript, фактически, делает его отличным языком для соединения вместе множества компонентов. Подробнее об этом мы поговорим в лекции 5 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой".

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

Разные взгляды на жизнь: Состояния просмотра и масштабирование в зависимости от разрешения

Итак, пользователь коснулся плитки приложения, хост-процесс приложения загружен в память, всё готово, приложение запущено. Что видит пользователь? Первое, что тут же становится видимым - это экран-заставка (splash screen) приложения, который описан в файле-манифесте изображением и цветом фона. Экран-заставка, поддерживаемый системой, гарантирует, что хотя бы что-нибудь будет отображено в момент активации приложения, даже если приложение остановится на первой же строке своего кода или вообще кода не имеет. На самом деле, у приложения есть 15 секунд, чтобы собраться и отобразить своё главное окно, либо Windows автоматически отправляет приложение в отставку (то есть - останавливает его), если пользователь переключается на другое приложение. Это избавляет от приложений, которые виснут при старте и торчат в системе как зомби, что, в итоге, часто заставляет пользователя убивать их, используя самый удобный программный инструмент - Диспетчер задач (Task Manager). (Да, я, конечно же, не удержался от сарказма - даже с учетом того, что Диспетчер задач Windows 8 гораздо дружественнее к пользователям, чем более старые его версии). Конечно, некоторым приложениям нужно больше времени для загрузки, в таком случае вы создаете расширенный экран-заставку (extended splash screen). Это означает задание исходного вида главного окна вашего приложения таким же, как выглядит экран-заставка, после чего вы можете разместить на нём индикатор загрузки или полезное сообщение вроде: "Пойди друг, перекуси, потому что тебе еще сидеть и сидеть здесь!". Так даже лучше, кстати, почему бы не развлечь пользователей, чтобы они приятно провели время с вашим приложением даже в ходе подобного процесса?

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

Цель этих жестов на краю экрана (edge gestures) - то есть, выполнения жестов прокрутки (скольжения) от края к центру экрана - держать и системные элементы управления, и средства управления приложением (вроде меню и других) в стороне, пока они не нужны. Это - один из аспектов принципа дизайна, который мы называем "прежде всего содержимое, и лишь затем внешнее оформление" ("content before chrome"). Такой подход помогает пользователю полностью погрузиться в работу с приложением. Для большей определенности, жесты прокрутки у левого и правого краёв экрана зарезервированы для системных целей, а у верхнего и нижнего краёв - для целей приложения. Жест прокрутки от верхнего или нижнего края, как вы уже, вероятно, видели, вызывает панель приложения (app bar) в нижней части экрана, где приложение размещает большую часть команд. Так же возможно наличие панели навигации (navigation bar) в верхней части экрана.

Работая в полноэкранном режиме, устройство пользователя может иметь как портретную, так и альбомную ориентацию и приложения могут обрабатывать различные события, чтобы поддерживать подобные изменения. Кроме того, приложение может иметь заранее заданную предпочитаемую ориентацию при запуске (startup orientation) в манифесте и может блокировать (lock) ориентацию, если это нужно. Например, для проигрывателя видеофайлов, в большинстве случаев, имеет смысл заблокировать ориентацию в альбомном режиме, так что поворот устройства не изменит вывод информации. Мы рассмотрим подробности о макетах в лекции 6, "Макет". Кроме того, приложение не всегда исполняется в полноэкранном режиме. В альбомном режиме, на самом деле, существуют три режима представления, к работе в которых должна быть готова каждая страница вашего приложения. Это - полноэкранный режим (full-screen), режим прикрепленного просмотра (snapped) и режим заполняющего просмотра (filled), смотрите Рис. 1.6. Два последних режима просмотра позволяют пользователю разделить экран на две части, одна из которых имеет 320 пикселей в ширину и расположена вдоль левой или правой стороны экрана. Это - область прикрепленного просмотра. Вторая занимает оставшуюся часть экрана и представляет собой область заполняющего просмотра. В ответ на действия пользователя, ваше приложение может быть расположено в любой из этих областей и должно отлично туда вписаться, соответствующим образом подстроить свой макет. В основном, исполнение в режиме заполняющего просмотра - это почти то же самое, что и работа в полноэкранном режиме. Исключение - немного отличающийся размер экрана и соотношение его сторон. Множество приложений просто подстраивают собственные макеты под эти параметры. В некоторых случаях, как в случае с приложениями для просмотра фильмов, они просто добавляют пустые области сверху и снизу или по бокам для того, чтобы не нарушить соотношение сторон отображаемого содержимого. Оба подхода достаточно хороши.

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


увеличить изображение

Рис. 1.6.  Четыре режима просмотра приложений для Магазина Windows. Все страницы в приложении должны быть подготовлены для правильного их отображения в каждом из этих четырех режимах. Обычно процесс такой подготовки включает в себя управление видимостью элементов и их расположением, что очень часто может быть полностью реализовано с помощью медиа-запросов CSS.

Полноэкранный портретный режим (Full screen Portrait)

Полноэкранный альбомный режим (Full screen Landscape)

Режим прикрепленного просмотра (Snapped)

Режим заполняющего приложения (Filled)

С другой стороны, в состоянии прикрепленного просмотра, приложение часто меняет отображение содержимого или уровень его детализации. Горизонтально ориентированные списки обычно преобразуют к вертикальной ориентации, с меньшим количеством подробностей. Но не будьте беспечны: вам нужно ответственно подойти к проектированию прикрепленного режима для каждой страницы вашего приложения и сделать это хорошо. В конце концов, пользователи хотят видеть полезные и красивые программы, и чем лучше ваше приложение выглядит в прикрепленном режиме - тем выше вероятность того, что пользователь оставит его на экране даже тогда, когда работает в другом приложении.

Еще одна важная деталь для прикрепленного режима просмотра - и для всех режимов, включая портретный - это то, что они не должны изменять контекст пользователя. Система лишь сообщает приложению нечто вроде: "Пожалуйста, стойте в этом дверном проёме, - или, - пожалуйста, станьте боком". И приложение никогда не должно менять то, чем оно занято (вроде перехода от игрового экрана к списку рекордов), когда оно оказывается в прикрепленном режиме. Оно просто должно соответствующим образом показать собственное содержимое в таком положении. В частности, если приложение действительно не может эффективно работать в прикрепленном режиме, оно должно показать пользователю сообщение об этом и дать пользователю возможность вернуться обратно в полноэкранный режим (имеется соответствующее API для таких действий).

Если отвлечься от режимов просмотра, то ожидается, что приложение должно правильно отображаться на экранах различных размеров. Им будут пользоваться на множестве различных экранов, от имеющих разрешение 1024х768 (минимальное аппаратное требование для Windows 8, в котором приложение так же оказывается, работая в режиме заполняющего просмотра на экране с разрешением 1366x768) до экранов с более высокими разрешениями, например, чего-то вроде 2560x1440. Общая идея здесь заключается в том, что приложение с фиксированным контентом (например, игра) обычно изменяет собственный размер в зависимости от разрешения, в то время как приложение с переменным контентом (вроде программы для чтения новостей) обычно отображает больше информации. Для того, чтобы узнать подробности об этом, обратитесь к материалам "Руководство по масштабированию для различных экранов" (http://msdn.microsoft.com/library/windows/apps/hh780612.aspx) и "Разработка интерфейсов пользователя для приложений" (http://msdn.microsoft.com/library/windows/apps/hh779072.aspx).

Кроме того, возможна ситуация, когда приложение отображается на устройстве с высоким разрешением, но маленькими физическими размерами экрана, то есть, обладающем высокой плотностью пикселей (pixel density), наподобие 10-дюймового экрана с разрешением в 2560х1440. К счастью, Windows выполняет автоматическое масштабирование, то есть, приложение воспринимает такой экран как дисплей с разрешением 1366х768 посредством CSS, JavaScript и API WinRT. Другими словами, вам практически не о чем беспокоиться. Единственное, о чём стоит позаботиться - это растровые графические элементы, которые должны соответствовать подобному масштабированию, как мы увидим в лекции 6.

В итоге хочется отметить, что когда приложение активируется под воздействием контракта наподобие Поиск или Общий доступ, его изначальный вид может не соответствовать полноэкранному, он соответствует специальной странице, подготовленной для контракта, которая перекрывает текущее приложение. Подробности об этом мы рассмотрим в лекции 1 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой".

Врезка: Одностраничная навигация против многостраничной

Когда вы создаете веб-приложение с использованием HTML, CSS и JavaScript, вы обычно получаете в итоге множество разных HTML-страниц и организуете навигацию между ними, используя теги <a href> или задавая document.location.

Всё это замечательно и работает в приложениях для Магазина Windows, однако, имеет некоторые недостатки. Один из них заключается в том, что навигация между страницами подразумевает перезагрузку программного кода, обработку нового HTML-документа, разбор и применение CSS. Помимо очевидного влияния на производительность, это усложняет совместное использование переменных и других данных между страницами, так как вам нужно либо сохранять данные в постоянное хранилище, либо преобразовывать данные в строку и передавать их в URI.

Более того, переключение между страницами сопровождается хорошо видимыми последствиями на экране: пользователь видит пустой экран, пока новая страница загружается. Это усложняет применение плавных, анимированных переходов между страницами - таких, как можно видеть в Windows 8. И такое поведение системы - это полная противоположность идее о "быстроте и динамичности", и гарантированный способ вселить ужас в сердца дизайнеров.

Для того чтобы избежать этих проблем, приложения, написанные на JavaScript, обычно структурированы как единая HTML-страница (обычно, контейнер div), на которой различные участки HTML-содержимого, называемые элементами управления страниц (page control) в WinJS, загружаются в DOM во время исполнения программы, то есть, это похоже на то, как работает AJAX. У такого подхода есть преимущество, заключающееся в сохранении контекста скрипта и в возможности использовать анимированные переходы с использованием библиотеки анимации CSS и/или WinJS. Подробности об этом мы рассмотрим в лекции 3, "Анатомия приложения и навигация по страницам".

И снова возможности: добираемся до данных и устройств

Во время исполнения программы, даже внутри контейнера приложения, у вашего приложения достаточно места для того, чтобы играть и доставлять удовольствие пользователю. Оно может использовать множество различных элементов управления, как мы увидим в лекцииях 4 и 5, настраивать их как угодно - от полной сдержанности до ослепительной яркости, и располагая их на рабочей поверхности в соответствии с вашей дизайнерской фантазией (лекция 6). Она может работать с такими средствами управления, как панель приложения (лекция 1 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"), может обрабатывать изменение состояний и данных пользователя (лекция 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"), принимать и обрабатывать события указателя (pointer events), которые унифицируют методы обработки касаний, ввода данных с помощью мыши или стилуса (лекция 3 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript") - благодаря тому, что эти способы ввода унифицированы, вы можете разрабатывать приложение в расчете на сенсорный интерфейс и получить всё остальное в качестве бесплатного бонуса; так же унифицирован ввод с экранной и физической клавиатур). Кроме того, приложения могут работать с сенсорами (лекция 3 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"), разнообразными мультимедийными элементами (лекция 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"), анимацией (лекция 5 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"), контрактами (лекция 1 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"), плитками и уведомлениями (лекция 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"), могут обмениваться данными по сети (лекция 3 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой") и работать с различными устройствами и принтерами (лекция 4 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"). Возможна оптимизация производительности приложений и расширение их возможностей посредством WinRT-компонентов (лекция 5 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"), приложения могут адаптироваться к различным рынкам, реализуя специальные возможности, в приложениях могут применяться различные варианты монетизации, такие, как реклама, пробные версии, покупки в приложениях (лекция 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой").

Многие из этих возможностей и API, связанные с ними, не затрагивают конфиденциальных данных пользователя, в итоге, приложение имеет свободный доступ к ним. Это - элементы управления, различные варианты ввода данных с использованием сенсорного экрана, мыши, стилуса, ввод данных с клавиатуры, работа с сенсорами (такими, как акселерометр, измеритель угла наклона, датчик освещенности). Папки с данными приложения (локальная, перемещаемая, временная), которые создаются при установке приложения, так же доступны ему без каких-либо дополнительных усилий. Другие функциональные возможности, однако, находятся под более жёстким контролем. Как человек, который работает удалённо из дома, например, я не хочу, чтобы моя вебкамера включалась до тех пор, пока я явно не разрешу это, иначе я могу попасть на видеоконференцию в неподобающем виде. Подобные устройства и другие защищенные функции системы, контролируются на уровне брокера, который запрещает доступ, если (а) соответствующая возможность не объявлена в файле-манифесте, или (b) пользователь не дал разрешение к устройству или функции во время исполнения программы. Такие возможности перечислены в следующей таблице:

Таблица 1.1.
ВозможностьОписаниеЗапрос согласия пользователя во время выполнения программы
Интернет (Клиент) (Internet (Client))Исходящий доступ к Интернету и к общественным сетям (включает в себя выполнение запросов к серверам и получение в ответ некоторой информации)1)Нет
Интернет (клиент и сервер) Internet (Client & Server) (надмножество для Интернет (Клиент); только одну из этих возможностей нужно объявить)Исходящий и входящий доступ к Интернету и общественным сетям (входящие подключения к критически важным портам всегда заблокированы)Нет
Частные сети (клиент и сервер) (Private Networks (Client & Server))Исходящий и входящий доступ к интрасетям дома или на работе (входящие подключения к критически важным портам всегда заблокированы)Нет
Библиотека документов (Documents Library)Доступ для чтения и записи к области файловой системы, где хранятся документы пользователя, имеющие определенные типы файлов, объявленные с помощью сопоставления типов файлов. Требуется учетная запись организации в Магазине Windows.Нет
Библиотека музыки (Music Library) Библиотека изображений (Pictures Library) Библиотека видео (Video Library)Доступ для чтения и записи к областям файловой системы, хранящим музыку, изображения, видеофайлы пользователя (все файлы).Нет
Съемные носители (Removable Storage)Доступ для чтения и записи к файлам на съемных носителях для объявленных типов файлов.Нет
Микрофон (Microphone)Доступ к аудиоканалу микрофона (в том числе - к микрофону на камере)Да
Веб-камера (Webcam)Доступ к каналам передачи аудио-, видео-данных, изображений с вебкамерыДа
Расположение (Location)Доступ к определению местоположения пользователя с использованием GPSДа
Бесконтактное взаимодействие (Proximity)Возможность соединяться с другими устройствами по технологии Near Field Communication (NFC, коммуникация ближнего поля)Нет
Корпоративная аутентификация (Enterprise Authentication)Доступ к ресурсам интрасети предприятия, которым требуются доменные учетные данные. Для большинства приложений эта возможность не требуется. Требуется учетная запись организации в Магазине Windows.Нет
Общие сертификаты пользователей (Shared User Sertificates)Доступ к программным и аппаратным (смарт-карты) сертификатам. Требуется учетная запись организации в Магазине Windows.Да, при этом пользователь должен выбрать сертификат, вставить смарт-карту и так далее.

Когда требуется решение пользователя, при вызове соответствующего API для доступа к ресурсу, пользователю будет задан вопрос (Рис. 1.7.). Если пользователь ответит на вопрос утвердительно, работа продолжится, если отрицательно - вызов API возвратит ошибку. Приложение должно быть соответствующим образом подготовлено для обработки подобных ошибок API, должно соответствующим образом себя вести.

Типичное диалоговое окно, используемое для того, чтобы узнать решение пользователя, которое автоматически запускается при первой попытке приложения использовать возможность, доступную через брокера. Для каждого приложения это происходит лишь однажды, однако, пользователь может поменять решение, воспользовавшись командой Разрешения (Permissions) чудо-кнопки Параметры.


увеличить изображение

Рис. 1.7.  Типичное диалоговое окно, используемое для того, чтобы узнать решение пользователя, которое автоматически запускается при первой попытке приложения использовать возможность, доступную через брокера. Для каждого приложения это происходит лишь однажды, однако, пользователь может поменять решение, воспользовавшись командой Разрешения (Permissions) чудо-кнопки Параметры.

Может ли приложение Карты использовать сведения о вашем местонахождении? (Can Maps use you locations?)

Разрешить (Allow)

Запретить (Block)

В самом начале работы над приложением, вы должны помнить о манифесте и о возможностях - если вы забудете о них, вы столкнетесь с ошибками API, даже написав отличный код (или скопировав его из рабочего примера). В ранний период создания первых приложений для Магазина Windows в Microsoft, мы постоянно забывали объявить возможность Интернет (клиент), в итоге, не работала даже загрузка удалённого изображения в элемент img или выполнение обычного запроса к веб-сервису. Теперь система оповещения о подобных ошибках работает гораздо лучше, но если вы столкнетесь с какой-нибудь таинственной проблемой в коде, в работоспособности которого вы уверены, особенно, в короткие ночные часы, проверьте манифест!

Мы в этом курсе, помимо раздела манифеста, посвященного возможностям, встретимся и с другими его разделами. Например, возможности Библиотека документов и Съемные носители понадобятся вам для того, чтобы задать специфические типы файлов для вашего приложения (в противном случае доступ, как правило, запрещен). Так же манифест содержит раздел URI содержимого (Content URI): особые правила, которые задают, какие URI являются известными и доверенными для вашего приложения и могут, таким образом, применяться в его целях. Манифест, кроме того, является тем местом, где вы объявляете предустановленную ориентацию, фоновые задачи (background tasks) (наподобие проигрывания аудио или поддержки связи в реальном времени), особенности реализации контрактов (таких, как указание страницы в вашем приложения, которая должна быть открыта, если приложение запущено посредством контракта), настраиваемые протоколы и внешний вид плиток и уведомлений. Вы и ваше приложение станете добрыми друзьями с манифестом.

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

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

Использование пользовательского интерфейса средства выбора файлов для доступа к различным частям файловой системы из приложения для Магазина Windows, в частности, к таким, как корневой каталог диска (но не к защищенным системным папкам). Это можно сделать, прикоснувшись к стрелке, направленной вниз около элемента "Файлы" ("Files").


увеличить изображение

Рис. 1.8.  Использование пользовательского интерфейса средства выбора файлов для доступа к различным частям файловой системы из приложения для Магазина Windows, в частности, к таким, как корневой каталог диска (но не к защищенным системным папкам). Это можно сделать, прикоснувшись к стрелке, направленной вниз около элемента "Файлы" ("Files").

Устроим перерыв и немного отдохнём: Управление жизненным циклом процессов

Мы уже многое рассмотрели в первой лекции, наше приложение многое умеет, но мы еще даже не начали писать код. На самом деле, приложение может быть действительно занятым, когда оно реализует определенные стороны контрактов. Если приложение объявило себя целевым для целей поиска, общего доступа, работы с контактами или для целей средства выбора файлов в своём манифесте (среди других параметров), Windows активирует приложение в ответ на соответствующее действие пользователя. Например, если пользователь вызывает средства чудо-кнопки Общий доступ и выбирает ваше приложение в качестве целевого, Windows активирует приложение с указанием данной цели. В ответ, приложение показывает специальный интерфейс для приёма данных, или вид (view), но не всё приложение, и когда выполнение задачи завершается, Windows завершит работу приложения (или отправит его в фоновый режим, если оно уже выполняется) без необходимости в дополнительных действиях пользователя.

Автоматическое завершение работы приложения или отправка его в фоновый режим - это примеры автоматического управления жизненным циклом (lifecycle management) приложений для Магазина Windows, который позволяет сберегать электроэнергию и оптимизировать время жизни устройства от батарей. Реальность обычных многозадачных операционных систем заключается в том, что пользователь обычно имеет множество выполняющихся приложений, каждое из которых потребляет энергию. Это имеет значение для настольных приложений, так как многие из них могут быть, как минимум, частично видимыми. Но в случае с приложениями для Магазина Windows, система смело берет на себя подобную работу и использует полноэкранную сущность таких приложений для их же пользы.

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

Итак, когда приложение уходит в фоновый режим, Windows автоматически приостанавливает (suspend) его после примерно 5 секунд (если судить по настенным часам). Приложение оповещается об этом событии, чтобы оно могло сохранить любые необходимые состояния (подробнее об этом - в следующем разделе). В этот момент приложение всё еще находится в памяти, со всеми своими структурами данных, но ему просто не выделяется процессорное время (Рис. 1.9). Это очень полезно для времени жизни устройства от батарей, так как большинство настольных приложений при бездействии напоминают автомобили, заправляемые бензином, на холостом ходу, потребляя некоторое количество ресурсов центрального процессора, если им это нужно, например, для того, чтобы перерисовать часть окна. Так как графический интерфейс приложений для Магазина Windows, находящихся в фоновом режиме, полностью скрыт, им не нужно выполнять даже такой вот небольшой работы, и они могут быть эффективно заморожены. В этом смысле, это больше похоже на современный электромобиль, который можно включать и выключать так часто, как это необходимо для того, чтобы снизить количество потребляемой им энергии.

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

Последовательность состояний жизненного цикла приложения для Магазина Windows


увеличить изображение

Рис. 1.9.  Последовательность состояний жизненного цикла приложения для Магазина Windows

Пользователь запускает приложение (User Launches App)

Запущенное приложение (Running App)

Приостановка (Suspending)

Возобновление (Resuming)

У приложения есть 5 секунд на обработку приостановки (App gets 5s to handle suspend)

Приложению сообщают, когда оно возобновлено (Apps are notified when they have been resumed)

Приостановленное приложение (Suspended App)

Нехватка ресурсов (Low Resources)

Остановленное приложение (Terminated App)

Приложение не оповещается перед завершением работы (App is not notified before termination)

Экран-заставка (Splash screen)

Код исполняется (Code gets to run)

Код не исполняется (No code runs)

Приложение не исполняется (App not running)

Может исполняться ограниченный набор фоновых задач (Limited Background tasks can run)

Здесь есть пара исключений. Во-первых, Windows предоставляет API для фоновой передачи данных (background transfer) - смотрите лекцию 3 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой" - для того, чтобы убрать из кода приложения реализацию задач по загрузке или отправке данных, что означает, что приложения не обязаны выполняться для выполнения подобного обмена данными. Приложения так же могут попросить систему периодически обновлять живые плитки (live tiles) на Начальном экране с помощью данных, полученных от сервиса, или они могут использовать push-уведомления (push notifications) (посредством Сервиса Push-уведомлений Windows -Windows Push Notification Service, WNS), в итоге, им даже не нужно запускаться для этих целей - смотрите лекцию 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой". Во-вторых, определенные виды приложений делают что-то полезное, когда их графический интерфейс не виден. Это - аудиоплееры, программы для общения, или такие программы, которым нужно выполнить некоторые действия, когда происходит какое-то системное событие (изменилось состояние сети, пользователь вошёл в систему и так далее). В случае с аудио приложение задаёт фоновое воспроизведение аудио в манифесте (а где же еще!) и устанавливает соответствующие свойства подходящего элемента управления. Это позволяет приложению продолжать выполняться в фоновом режиме. В случае с системными событиями, как мы увидим в лекции 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", приложение объявляет фоновые задачи в своём манифесте, привязанные к определенным функциям в их коде. В этом случае Windows вызовет приложение из приостановленного состояния, когда произойдёт соответствующее событие. Это показано в нижней части Рис. 1.9.

С течением времени, естественно, у пользователя может скопиться довольно много программ, загруженных в память, и большинство из них будет приостановлено, потребляя очень мало энергии. Очевидно, придёт время, когда приложение переднего плана (foreground app), особенно - только что запущенное - потребует больше памяти, чем доступно в настоящий момент. В этом случае Windows автоматически прекратит работу (terminate) одного или большего количества приложений, убрав их из памяти (снова посмотрите на Рис. 1.9).

Но вот беда: до тех пор, пока пользователь явно не закроет приложение - используя комбинацию клавиш Alt+F4 или жест скольжения сверху вниз, так как политика Магазина Windows не позволяет приложениям иметь собственные команды или жесты для завершения работы - он вправе полагать, что приложение всё еще выполняется. Если пользователь снова активирует приложения (например, с его плитки), он ожидает вернуться в то же самое место приложения, где был ранее. Например, игра должна быть на том же самом месте, где и раньше (хотя, автоматически поставлена на паузу), программа для чтения должна отображать ту же самую страницу, и видео должно быть приостановлено там же, где оно проигрывалось ранее. В противном случае, представьте себе, какие оценки и отзывы будут у вашего приложения в Магазине Windows!

В итоге вы можете сказать: "Хорошо, мне лишь нужно сохранить состояние приложения, когда завершается его работа, так?". На самом деле - не так. Ваше приложение не оповещается о том, что его работа завершается. Почему? Так как в это время оно уже приостановлено, его код не выполняется. В дополнение к этому, если нужно завершить работу приложения, это происходит в условиях нехватки памяти, и последнее, что вы захотите - это чтобы ваше приложение было активировано и попыталось сохранить собственное состояние, для чего ему понадобится дополнительная память! Обязательно, как было отмечено выше, чтобы приложения сохраняло своё состояние при приостановке, и, в идеале, даже в других ситуациях в процессе нормального выполнения. Итак, посмотрим, как всё это работает.

Вспоминая себя: Состояние приложения и перемещаемые данные

Оглядываясь назад, мы можем говорить о том, что ключевые различия между традиционными настольными приложениями и приложениями для Магазина Windows заключаются в том, что последние, по существу, сохраняют своё текущее состояние. Таким образом, будучи однажды запущенными, они помнят своё состояние между запусками (если только не были в явном виде закрыты пользователем или если не предоставляют средства для явного сбрасывания состояния к исходному). Некоторые настольные приложения работают похожим образом, но многие страдают от чего-то вроде личностного кризиса при каждом запуске. Как Златопуст Локонс из "Гарри Поттер и тайная комната", который часто начинал спрашивать себя: "Кто я?"1), не имея понятия о том, где он был или что делал до этого.

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

Есть и другое изменение зависимости от предыстории. Помните, ранее в этой лекции мы говорили о том, что пользователь может установить то же самое приложение Магазина Windows на несколько, до пяти, устройств? Это означает, что приложения, в зависимости от их устройства, конечно, так же могут сохранять своё состояние между этими устройствами. То есть, если пользователь поставил на паузу игру или воспроизведение видео на одном устройстве или сделал заметку к книге или журналу на одном устройстве, пользователь совершенно естественным образом захочет получить возможность перейти на другое устройство и увидеть всё в точно таком же состоянии.

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

Автоматическое перемещение перемещаемых данных приложения (содержимого папки и параметров) между устройствами


Рис. 1.10.  Автоматическое перемещение перемещаемых данных приложения (содержимого папки и параметров) между устройствами

Устройство №1 (Device #1)

Устройство №2 (Device #2)

Хост-процесс приложения (App Host Process)

Контейнер приложения (App Container)

Приложения используют перемещаемые данные так, как будто это локальные данные приложения (Apps use roaming data as if it were just local appdata)

Перемещаемая папка данных и настроек приложения (AppData Roaming folder and settings)

До 100 Кб перемещаемых данных автоматически синхронизируются между устройствами, на которых установлены одинаковые приложения и на которые пользователь вошёл под одной и той же учетной записью (Roaming data (up to 100K) automatically synced between devices with same app and same user account)

Важнее всего здесь - понять, как и где приложение сохраняет свои состояния. (Мы уже знаем, когда это происходит). Если вы вспомните, есть одно место в файловой системе, к которому приложение имеет неограниченный доступ: это папка с данными приложения. Внутри этой папки Windows автоматически, при установке приложения, создаёт подпапки, которые называются LocalState (Локальное состояние), RoamingState (Перемещаемое состояние) и TempState (Временное состояние) (я обычно упоминаю их без присоединения слова "State" - "Состояние"). Приложение может программным образом работать с любой из этих папок в любое время, может создавать в них любые файлы и подпапки, соответствующие его нуждам. Существуют и API для управления индивидуальными Локальными (Local) и Перемещаемыми (Roaming) параметрами (это - пары вида "ключ - значение"), вместе с группами параметров, которые называют взаимосвязанными (composites), которые всегда записываются, считываются и перемещаются как единое целое. (Это полезно при реализации возможностей настройки параметров приложения с помощью чудо-кнопки Параметры, как показано в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript").

Сейчас, хотя приложение может записывать столько, сколько ему нужно, в область данных приложения (вплоть до заполнения полной ёмкости файловой системы), Windows автоматически перемещает данные из папки, хранящей перемещаемые данные только в том случае, если они соответствуют разрешенному ограничению (примерно 100 Кб, но для этого есть специальное API). Если вы превысите лимит, данные никуда не денутся, но не будут перемещаться. Кроме того, знайте, что облачное хранилище данных имеет различные лимиты на длину имён файлов и путей к файлам, так же как и на сложность структуры папок. Поэтому поддерживайте простую структуру и маленькие размеры перемещаемого состояния. Если приложению нужно перемещать большие объемы данных, используйте дополнительные веб-сервисы наподобие SkyDrive (смотрите пост в блоге: "Реализация возможностей по работе с облаком в приложениях Windows 8 с помощью SkyDrive" (http://blogs.msdn.com/b/b8_ru/archive/2011/10/03/windows-8-skydrive.aspx).

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

Совет. Пароли следует всегда хранить в Хранилище учетных данных (Credential Locker) (подробнее - в лекции 3 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"). Если пользователь разрешит перемещение паролей (Параметры ПК > Синхронизация параметров > Пароли (PC Settings > Sync Your Settings > Passwords)), содержимое хранилища будет автоматически перемещаться.

Когда сохраненное состояние перемещается, знайте, что при разрешении коллизий применяется простая политика "последний записавший побеждает". Так, если вы запустите одно и то же приложение на двух устройствах в одно и то же время, не ожидайте хитроумного объединения состояний или обмена ими. После множества тестов и анализов, инженеры Microsoft, в итоге, решили, что подобная простота лучше всего.

Здесь же я хочу сказать о том, что если пользователь устанавливает приложение, перемещающее некоторые параметры, деинсталлирует приложение, затем, через некоторое время, снова его устанавливает, пользователь обнаружит, что параметры приложения, заданные ранее, сохранены. Это имеет значение, так как было бы слишком жестоким шагом удалять перемещаемые состояния в облаке в момент, когда пользователь случайно удалит приложение на всех своих устройствах. На подобное положение нельзя полагаться, однако, Windows, вероятнее всего, будет некоторое время хранить перемещаемые состояния приложения.

Врезка: Локальные данные против временных данных

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

Для приложений Магазина Windows, написанных на HTML и JavaScript вы так же можете использовать существующий механизм кэширования, наподобие локального хранилища HTML5, IndexedDB, кэша приложения и так далее. Всё это хранится внутри папки приложения LocalState.

Врезка: Возможности лицензирования по пользователям и перемещение данных

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

Пример подобного сценария показан на Рис. 1.11, где приложение может иметь различные индивидуальные особенности или разный дух в зависимости от окружения пользователя и того, как различные устройства могут быть использованы в каждой ситуации. Может показаться весьма прозаичным размышлять о приложении для планирования рациона, управления рецептами и списками покупок, но это постоянно происходит во множестве домов по всему миру. Кроме того, это то, что понравится моей жене, если я напишу больше программного кода, нежели текста!

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

Варианты использования приложения для планирования рациона


увеличить изображение

Рис. 1.11.  Варианты использования приложения для планирования рациона

Чтение журналов, пометка интересных рецептов (Read magazines, mark recipes of interest)

Планирование меню с отмеченными рецептами, составление списка покупок (Plan menus with marked recipes, make shopping lists)

Просмотр списка покупок, поиск магазинов с лучшими ценами (И, да, здесь у Microsoft есть история с телефонами…) (See shopping lists, locate stores with best prices (And yes, Microsoft will eventually have a story with the phones here…))

Подготовка дневного рациона в соответствии с планом меню (а это - планшет, который можно мыть в посудомоечной машине!) (Prepare the day's meals according to menu plan (this is a dishwasher-safe tablet too!))

Возвращение домой: Обновления и новые возможности

Если вы - один из тех разработчиков, которые могут написать превосходное приложение с первого раза, мне хотелось бы узнать - почему вы читаете этот курс! В реальной жизни, не важно, как серьезно мы тестируем приложение перед тем, как оно выйдет во внешний мир, наши усилия бледнеют в сравнении с теми ужасами, которым пользователи подвергают наши приложения. Говоря кратко: ожидайте проблем. Приложение может аварийно завершиться в обстоятельствах, которые мы никогда не предсказывали, или могут возникнуть проблемы с удобством его использования, так как люди находят креативные способы использования приложений, которые находятся за пределами исходного предназначения программ.

К счастью, информационная панель Магазина Windows - перейдите на http://msdn.microsoft.com/ru-RU/windows/apps/ и щёлкните по вкладке Информационная панель (Dashboard) в верхней части, - упрощает получение обратной связи от пользователей, которую обычно получить нелегко. Магазин поддерживает оценки и отзывы для каждого приложения, которые могут стать источником ценной информации о том, насколько хорошо ваше приложение выполняет свои функции в реальном мире и источником идей для следующих выпусков приложения. Кроме того, вы можете прямо сейчас осознать, что вы можете прочесть хвалебный отзыв (если вы сделали хорошую работу), можете столкнуться с критикой или даже с хорошей порцией гадостей (даже если вы сделали своё дело хорошо!). Не воспринимайте это как персональное оскорбление - оценивайте каждое критическое замечание как возможность улучшить приложение и будьте благодарны людям за то, что они нашли время и оставили отзыв. Однажды мудрец, услышав о смерти своего самого шумного критика сказал: "Я только что потерял лучшего друга!".

Магазин Windows, кроме того, предоставляет вам аналитику по аварийным завершениям программы, так что вы можете точно определить проблемные зоны в вашем приложении, которые не были затронуты вашими тестами. Это просто бесценно - возможно, вы уже апплодируете от восхищения! Так как если ранее вам нужны были подобные данные, вы вынуждены были самостоятельно реализовывать весь механизм для их получения. Больше этого делать не нужно. Это один из ценных сервисов, который вы получаете в обмен на стоимость регистрации в Магазине Windows. (Безусловно, вы вполне можете реализовать подобный механизм самостоятельно).

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

Обновления отправляют в Магазин Windows так же, как и первую версию приложения. Вы создаете и отправляете пакет приложения (с тем же именем пакета, что и ранее, но с новым номером версии), и затем обновляете описание, графические элементы, цену и другую информацию. После этого ваш обновлённый пакет проходит сертификацию, его, как и ранее, подписывают, и когда всё это завершится, ваше обновленное приложение будет доступно в Магазине. Те потребители, у которых уже есть ваше приложение, будут оповещены о том, что имеется обновление. Они самостоятельно решат, нужно или нет его устанавливать (И помните, что благодаря блочной карте, о которой мы говорили ранее, загружаться при обновлении будут лишь те части приложения, которые изменились. Это означает, что исправление небольших недочётов не приведет к тому, что пользователям придётся загружать каждый раз весь пакет приложения, который может оказаться довольно большим. Это сближает рассматриваемую модель обновления приложений с тем, как это реализовано в веб-приложениях).

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

Это вызывает интересный вопрос: что происходит с перемещаемыми данными, когда у пользователя установлено несколько версий одного и того же приложения на разных устройствах. Ответ состоит из двух частей. Во-первых, перемещаемые данные имеют собственные номера версий, независимые от приложения, и, во-вторых, Windows совершенно прозрачно поддерживает работу с несколькими версиями перемещаемых состояний приложения в течение времени, пока эти приложения установлены на устройства пользователя, которые ссылаются на данные версии состояний. Когда приложение будет обновлено на всех устройствах и конвертирует своё состояние к новому представлению, Windows удалит старые версии.

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

Последнее, что можно сказать о Магазине, это то, что в дополнение к аналитике по вашему собственному приложению, которая, кроме того, включает данные наподобие объемов продаж, естественно - Магазин так же предоставляет вам аналитику в целом по рынку. Это помогает вам находить новые возможности - возможно, это будет идея, которую вы рассматривали ранее как одну из функций приложения, а теперь выделите её в отдельное приложение в другой категории. Здесь вы можете видеть, что продаётся хорошо (а что - не очень), видеть, что некоторые категории приложений недостаточно заполнены или имеют менее, чем средние, отзывы. Подробности вы можете найти в Информационной панели (http://msdn.microsoft.com/ru-RU/windows/apps/).

И, конечно, кое-что о дизайне

В первой лекции мы поговорили о сущности мира, в котором живут и работают приложения для Магазина Windows. Так же в курсе мы сконцентрируемся на подробностях, касающихся того, как создать подобные приложения с использованием технологий HTML, CSS и JavaScript. Но о чём мы говорить не будем, и чего мы коснёмся лишь в минимальном объеме, это то, как вы будете решать, что именно делает ваше приложение. То есть, о цели существования приложения в мире, а так же, о том, как будет выглядеть ваше приложение для достижений своей цели.

Это - вопрос хорошего дизайна и проектирования приложений для Магазина Windows - вся та работа, которая проводится над приложением задолго до начала написания кода.

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

Что касается дизайна, я предлагаю разработчикам внимательно прочитать материал "Разработка интерфейсов пользователя для приложений" (http://msdn.microsoft.com/library/windows/apps/hh779072.aspx) для лучшего понимания принципов дизайна. Однако, будьте честны, вы действительно хотите размышлять над тем, что означает "быстрый и динамичный" (и проектировать не только статичные сеточные макеты, но и динамические части дизайна, наподобие анимации?). Вы хотите тратить своё время на графический дизайн и создание художественных элементов (что очень важно для замечательного приложения)? Вы хотите беспокоиться о выравнивании каждого пикселя макета приложения во всех четырех режимах просмотра? Если нет, найдите кого-нибудь, кто хочет всем этим заниматься, так как комбинация его дизайнерского чутья и вашего искусства программирования приведет к гораздо лучшим результатам, чем если вы будете работать в одиночку. Как говорит один мой сослуживец, браки "фриков" и "гиков" часто приводят к самым креативным, привлекательным и вдохновляющим результатам.

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

На этом позвольте перейти к нашей части истории приложения: к написанию программного кода!

Лекция 2. Быстрый старт

В этой лекции дано описание стандартных шаблонов приложений для Магазина Windows, рассмотрена методика работы в Visual Studio и Blend для Visual Studio, приведен пошаговый пример разработки простого приложения

Файлы к данной лекции Вы можете скачать  здесь.

Это - учебный курс о разработке приложений. Поэтому, цитируя Пола Бетани, изображающего Джеффри Чосера в "Истории рыцаря": "…но золото в позолоте не нуждается. Отбросим болтовню", давайте что-нибудь создадим.

Настоящий быстрый старт: шаблон Пустое приложение

Конечно, мы должны начать с того, чтобы отдать дань уважения классическому приложению "Hello World". Мы можем сделать это, не написав ни единой строчки кода. Нам лишь нужно создать новое приложение из шаблона в Visual Studio.

  1. Запустите Visual Studio Express. Если вы делаете это впервые, вам предложат получить лицензию разработчика. Сделайте это, так как иначе вы не сможете продолжать!
  2. Щёлкните Создать проект… (New Project…) в окне Visual Studio или используйте команду меню Файл > Создать проект (File > Create Project)
  3. В появившемся диалоговом окне (Рис. 2.1) убедитесь, что выбрали в разделе Шаблоны (Templates), слева, JavaScript и выберите шаблон Пустое приложение (Blank Application) в центральной части окна. Задайте имя (HelloWorld подойдёт), папку и нажмите OK.

    Диалоговое окно Создать проект, использующее светлую тему пользовательского интерфейса. (Смотрите команду меню Сервис > Параметры (Tools > Options) и измените тему в разделе Среда>Общие (Environment>General)). Я использую здесь светлую тему, так как она лучше смотрится на белом фоне страниц


    увеличить изображение

    Рис. 2.1.  Диалоговое окно Создать проект, использующее светлую тему пользовательского интерфейса. (Смотрите команду меню Сервис > Параметры (Tools > Options) и измените тему в разделе Среда>Общие (Environment>General)). Я использую здесь светлую тему, так как она лучше смотрится на белом фоне страниц

  4. После того, как Visual Studio создаст проект, нажмите кнопку Начать отладку (Start Debugging) (или нажмите F5 или выберите команду меню Отладка > Начать отладку (Debug > Start Debugging)). Подразумевая то, что всё у вас установлено верно, вы должны увидеть что-то вроде Рис. 2.2 на своём экране.

    Единственная интересная часть окна приложения HelloWorld. Сообщение - это, как минимум, лучшее приглашение к написанию большего количества кода, чем нужно для вывода стандартного приветствия первого приложения!


    Рис. 2.2.  Единственная интересная часть окна приложения HelloWorld. Сообщение - это, как минимум, лучшее приглашение к написанию большего количества кода, чем нужно для вывода стандартного приветствия первого приложения!

По умолчанию, Visual Studio запускает отладчик в режиме локального компьютера (local machine), в котором приложение запускается в полноэкранном режиме в вашей операционной системе. В результате мы сталкиваемся с неудобством, когда отладчик скрывается, если только вы не используете систему с несколькими мониторами, когда вы можете открыть Visual Studio на одном мониторе и своё приложение для Магазина Windows - на другом. Посмотрите материал "Выполнение приложений для Магазина Windows на локальном компьютере" (http://msdn.microsoft.com/library/windows/apps/hh441483.aspx) для того, чтобы больше об этом узнать.

Visual Studio предлагает два других режима отладки, которые можно выбрать из выпадающего списка на панели инструментов (Рис. 2.3), или в меню Отладка > Свойства [Имя приложения] ( Debug > [Appname] Properties), Рис. 2.4.

Параметры отладки в панели инструментов Visual Studio


Рис. 2.3.  Параметры отладки в панели инструментов Visual Studio

Параметры отладки в окне свойств приложения в Visual Studio


Рис. 2.4.  Параметры отладки в окне свойств приложения в Visual Studio

Параметр Удалённый компьютер (Remote Mashine) позволяет вам запускать приложение на другом устройстве, что абсолютно типично при работе с устройствами, которые не способны выполнять настольные приложения, как, например, ARM-устройства (и если вы видите лишь эту опцию в примере проекта, активная платформа решения, возможно, установлена в ARM). Настройка здесь достаточно проста: смотрите материал "Выполнение приложений для Магазина Windows на удаленном компьютере" (http://msdn.microsoft.com/library/windows/apps/hh441469.aspx), и я рекомендую, чтобы вы хорошо с ним ознакомились. Кроме того, если проект не загружен в Visual Studio, меню Отладка предлагает команду Присоединиться к процессу (Attach to process), которая позволяет отлаживать уже запущенные приложения. Смотрите материал "Запуск сеанса отладки (JavaScript)" (http://msdn.microsoft.com/library/windows/apps/hh771032.aspx).

Имитатор (simulator) так же весьма интересен, а на мой взгляд - самый интересный инструмент, и место, где, по-моему, вы будете проводить очень много времени. Он дублирует ваше окружение внутри новой сессии входа в систему и позволяет вам управлять ориентацией устройства, устанавливать различные разрешения и коэффициенты масштабирования, имитировать события касаний экрана и работать с данными, которые поступили от API определения местоположения. На Рис. 2.5 вы можете видеть приложение Hello World в имитаторе, с отмеченными дополнительными элементами управления. Подробнее об имитаторе вы узнаете далее, хотя вы так же можете захотеть прочесть материал "Выполнение приложений для Магазина Windows на имитаторе" (http://msdn.microsoft.com/library/windows/apps/hh441475.aspx).

Приложение HelloWorld работает в имитаторе, справа добавлены подписи к кнопкам. На самом деле, шаблон "Пустое приложение" оправдывает своё название!


увеличить изображение

Рис. 2.5.  Приложение HelloWorld работает в имитаторе, справа добавлены подписи к кнопкам. На самом деле, шаблон "Пустое приложение" оправдывает своё название!

Поверх остальных окон (Always on top)

Режим мыши(Mouse pointer)

Базовый сенсорный режим (Touch emulator)

Сенсорный режим с жестом сжатия/масштабирования (Emulate touch zooming)

Сенсорный режим с вращением (Emulate touch rotation)

Повернуть по часовой стрелке на 90 градусов (Rotate device 90 clockwise)

Повернуть против часовой стрелки на 90 градусов (Rotate device 90 counter-clockwise)

Изменить разрешение (Set resolution/scaling)

Задать положение (Set GPS Coordinates)

Копирование снимка экрана (Take a screenshot)

Параметры снимка экрана (Screenshot settings)

Справка (Help)

Врезка: Как Visual Studio запускает приложение?

Visual Studio разворачивает приложение так же, как это происходит при получении приложения из Магазина Windows. Приложение появляется на Начальном экране, где вы можете деинсталлировать его. Деинсталляция очищает папки с данными приложения, сведения о его состояниях, что весьма полезно при отладке.

Здесь нет ничего необычного: развёртывание можно произвести и с использованием инструментов командной строки. Для того, чтобы увидеть подробности, воспользуйтесь командой Магазин > Создать пакеты приложения (Store > Create App Package) в Visual Studio, выберите Нет в параметре, отвечающем за отправку в Магазин Windows, и вы увидите диалоговое окно, позволяющее вам сохранить пакет приложения там, где вы захотите. В указанной папке вы найдете пакет appx, сертификат безопасности и пакетный файл, который называется Add-AppxDevPackage. Этот файл содержит скрипты PowerShell, которые разворачивают приложение и ресурсы, от которых оно зависит.

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

Структура проекта Пустое приложение

Приложение, созданное по шаблону Пустое приложение (Blank App), не имеет большого количества визуальных элементов, оно предоставляет определенную структуру проекта. Вот, что вы можете найти после создания проекта по шаблону, в Обозревателе проектов Visual Studio (Рис. 2.6).

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

Папка images содержит четыре образца изображения, и если только вы не хотите выглядеть недалёким разработчиком, вы всегда будете менять эти изображения на собственные перед отправкой приложений в Магазин Windows (и, кроме того, создавать масштабированные версии, как мы увидим в лекции 3, "Анатомия приложения и навигация по страницам".

Папка js содержит файл default.js.

Папка Ссылки (References) указывает на CSS и JS-файлы библиотеки WinJS. Мы можете открыть любой из них для того, чтобы посмотреть, как устроена WinJS. (Обратите внимание на то, что если вы нуждаетесь в поиске по этим файлам, вы должны открыть конкретный файл и выполнять поиск в нём. Поиск по всему проекту или решению в данном случае не поддерживается).

Проект, построенный по шаблону Пустое приложение, полностью раскрыт в Обозревателе решений


Рис. 2.6.  Проект, построенный по шаблону Пустое приложение, полностью раскрыт в Обозревателе решений

Как можно ожидать, в проекте этого типа не так уж много кода, специфичного для приложения. Например, тело HTML-документа имеет лишь один абзац, который вы можете заменить на "Hello World", если вы чувствуете, что не можете без этого обойтись. Что гораздо важней - так это ссылки на компоненты WinJS: основной файл со стилями (ui-dark.css или ui-light.css), файлы base.js и ui.js:

<!DOCTYPE html>	
<html>	
<head>	
<meta charset="utf-8">	
<title>Hello World</title>

<!-- Ссылки WinJS -->	
<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>	

<!-- Ссылки HelloWorld -->	
<link href="/css/default.css" rel="stylesheet">
<script src="/js/default.js"></script>	
</head>	
<body>	
<p>Content goes here</p>	
</body>	
</html>

Как правило, такие ссылки присутствуют всегда (возможно, с ui-light.css) в каждом из HTML-файлов вашего проекта. Двойные // в путях WinJS указывают на библиотеки с разделяемым доступом, а не на файлы в пакете вашего приложения, в то время, как одиночные / указывают на корневой каталог вашего раздела. Помимо этого, всё остальное - это стандартный HTML5, поэтому вы можете свободно добавлять дополнительные HTML-теги и наблюдать за производимым ими эффектом.

Что касается JavaScript, то default.js просто содержит базовый код активации WinJS, находящийся в событии WinJS.Application.onactivated вместе с заглушкой для события, которое называется WinJS.Application.oncheckpoint:

(function () { "use strict";

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: Это приложение было вновь запущено. Инициализируйте
// приложение здесь.
} else {
// TODO: Это приложение вновь активировано после приостановки.
// Восстановите состояние приложения здесь.
}
args.setPromise(WinJS.UI.processAll());
}
};

app.oncheckpoint = function (args) {
};

app.start();
})();

Мы вернемся к checkpoint в лекции 3. Сейчас, вспомните из лекции 1, что приложение может быть активировано различными способами. Это отражено в свойстве args.detail.kind, значения которого принадлежат перечислению Windows.ApplicationModel.Activation.ActivationKind.

Когда приложение запущено прямо со своей плитки на Начальное экране (или запущено в отладчике, как у нас), вид активации имеет значение launch. Как мы увидим далее, другие значения говорят нам о том, что приложение активировано по запросу сервиса, наподобие контракта поиска или общего доступа к данным, путём ассоциации с типом файла, с помощью средства выбора файлов, посредством протокола и в других случаях. В случае с видом активации launch, другая порция данных из перечисления the Windows.ApplicationMode.- Activation.ApplicationExecutionState говорит нам о состоянии исполнения приложения до этого события. Больше об этом будет в лекции 3, а комментарии в стандартном коде выше должны пока удовлетворить ваше любопытство.

А что такое args.setPromise(WinJS.UI.processAll()? Как мы увидим множество раз, WinJS.UI.processAll создаёт экземпляры элементов управления, которые объявлены в HTML, это любой элемент (обычно - div или span), который содержит атрибут data-win-control, который содержит имя функции конструктора. Конечно, шаблон Пустое приложение не содержит таких элементов управления, но так как практически каждое приложение, основанное на этом шаблоне, содержит их, имеет смысл включать его по умолчанию1). Что касается args.setPromise, этот механизм касается отложенного выполнения, и мы отложим его рассмотрение до лекции 3.

Короткий вызов app.start(); расположенный в нижней части кода, так же очень важен. Он позволяет нам быть уверенными в том, что различные события, которые поставлены в очередь при запуске приложения, будут обработаны. Мы рассмотрим это подробнее в лекции 3.

Наконец, вы можете спросить: "Зачем нужна эта сложная конструкция (function () { … })(); ?" Это - лишь удобный способ, используемый в JavaScript (называемый шаблоном модуля (module pattern)), предохранять глобальное пространство имён от загрязнения, в целях заботы о производительности. Подобная синтаксическая конструкция объявляет анонимную функцию, которая сразу же исполняется, что создаёт собственную область видимости функции для всего, что находится внутри неё. Таким образом, переменные наподобие app, вместе со всеми именами функций, доступны внутри модуля, но не появляются в глобальном пространстве имен2).

Вы можете объявлять переменные в глобальном пространстве имен, конечно, и содержать их в порядке, WinJS предоставляет средства для задания ваших собственных пространств имен и классов (смотрите WinJS.Namespace.define и WinJS.Class.define), опять же, помогая минимизировать вмешательство в глобальное пространство имен.

Познакомьтесь с Visual Studio. Если вы новичок в Visual Studio, то поначалу вы можете неуверенно чувствовать себя в этой инструментальной среде, так как она имеет множество функций, даже в Экспресс-версии. Для того, чтобы вы могли быстро ознакомиться с этой средой, я подготовил 10-минутный ролик, Video 2-1, который расположен в дополнительных данных к этой лекции для того, чтобы показать вам базовый рабочий процесс и другие основные вещи.

Врезка: Написание кода в режиме отладки

Из-за динамической природы JavaScript впечатляет то, что команда разработки Visual Studio нашла способ заставить IntelliSence весьма прилично работать в редакторе кода Visual Studio. (Если вы незнакомы с IntelliSence, это сервис для повышения производительности труда, который обеспечивает автозавершение кода и всплывающие подсказки по API в процессе работы. Больше вы можете узнать в материале "IntelliSence для JavaScript" (http://msdn.microsoft.com/library/bb385682.aspx)). При этом можно использовать полезный трюк, который позволяет IntelliSence работать даже лучше - это писать код, когда Visual Studio находится в режиме отладки. То есть, установить точку останова в подходящем месте вашего кода и запустить приложение в отладчике. Когда вы достигнете этой точки останова, вы можете начать писать и править код, и, так как контекст скрипта полностью загружен, IntelliSence будет работать с уже созданными экземплярами переменных, а не только с тем, что этот механизм может получить из исходного кода. Кроме того, вы можете использовать окно интерпретации (Immediate) для того, чтобы непосредственно в нём исполнять код и видеть результаты. (Однако, вам понадобится перезапустить приложение для того, чтобы получить возможность исполнять новый код).

Быстрый старт №1: "Here My Am!" и введение в Blend для Visual Studio

Когда моему сыну было три, он никогда - несмотря на то, что был рождён от двух инженеров-родителей и имел двух инженеров среди бабушек и дедушек - не заглядывал в углы и не появлялся в комнате со словами "Hello world!" (Привет, мир!). Нет, его особенной фразой была "Here my am!" (Вот и я!). Используя этот вариант представления себя вселенной, наше следующее приложение будет захватывать изображение с камеры, определять ваше местоположение на карте и передавать эти данные с помощью чудо-кнопки Общий доступ в Windows 8. Звучит сложно? К счастью, API WinRT делают это весьма простой задачей.

Врезка: Сколько времени понадобится, чтобы написать это приложение?

Написание этого приложения заняло у меня примерно три часа. "Конечно", - подумаете вы, - "вы уже написали кучу приложений, поэтому это для вас так просто!". Хорошо, и да, и нет. Во-первых, я, кроме того, писал данную часть главы в то же самое время и старался произвести код, подходящий для повторного использования. Но гораздо важнее то, что написание программы заняло у меня мало времени, так как я знал, как использовать мои инструменты, в особенности - Blend, и я знал, где я могу найти код, который уже делает большую часть из того, чего я хочу. Я имею в виду примеры из Windows SDK, которые вы можете загрузить с ресурса http://code.msdn.microsoft.com/windowsapps/.

Как можно будет судить по множеству отличных примеров в этом учебном курсе, я надеюсь, что вы загрузите полный их набор. Перейдите по ссылке выше, найдите ссылку "Windows 8 app samples". Эта ссылка приведет вас на страницу, где вы можете получить .zip-файл, который содержит все примеры на JavaScript. Распаковав его однажды, заведите себе привычку просматривать эту папку на предмет любых API или функций, которые вам интересны. Например, код, который я использую ниже для реализации захвата данных с камеры и реализации обмена данными, взят из пары примеров. (Да, если вы откроете пример, который кажется поддерживающим только отладку на удаленном компьютере, целевая платформа, возможно, установлена в значение ARM - измените её на Any CPU для локальной отладки).

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

Проектирование каркаса приложения

Прежде чем мы начнём работу над кодом, сначала посмотрим на дизайнерский каркас этого приложения. О, дизайн? Да. Возможно, впервые в истории Windows, создана настоящая философия дизайна для приложений. В прошлом, с традиционными настольными приложениями, это было похоже на картину, где "что-то происходит". Конечно, были какие-то руководства по пользовательским интерфейсам, но разработчики обычно делали всё, что считали нужным, реализовывали любой опыт взаимодействия пользователя и приложения, который казался им правильным. Например, это могло выглядеть как наборы флагов в четыре уровня глубиной, спрятанные в серии модальных диалоговых окон. Естественно, подобные вещи имели смысл для определенных разработчиков; имели ли они значение для кого-нибудь еще - весьма спорный вопрос.

Если вы когда-либо претендовали или собирались претендовать на то, чтобы быть дизайнером, сейчас - самое время передать эстафету тому, кто по-настоящему в этом разбирается, или отложить занятия программированием на год или два, и потратить это время на саморазвитие. Проще говоря, дизайн имеет значение в приложениях для Магазина Windows, и он определяет разницу между преуспевающими приложениями и приложениями, которые просто существуют в Магазине и не пользуются популярностью. И, когда у вас есть дизайн, его легче реализовать, так как вам не придётся принимать дополнительных решений о дизайне при написании кода. (Если вы всё еще хотите влезть в шкуру дизайнера и работать с приложениями вроде Adobe Illustrator, не забудьте посетить ресурс "Разработка интерфейсов пользователя для приложений" (http://msdn.microsoft.com/library/windows/apps/hh779072) для того, чтобы воспринять философию и подробности о дизайне приложений Магазина Windows, а так же - специальные ресурсы для дизайнеров).

Когда у меня появилась идея этого приложения, я нарисовал простой каркас приложения, позволил нескольким дизайнерам посмеяться надо мной за моей спиной (и предложить улучшения) и остановился на макетах для полноэкранного режима, портретного, прикрепленного и заполняющего режимов просмотра, как показано на Рис. 2.7 и Рис. 2.8.

Замечание. Традиционные табличные каркасы хороши для того, чтобы показать статичный вид приложения, но в "быстром и динамичном" окружении Windows 8 очень важен динамический аспект приложения - анимации и движение. Отличный дизайн приложения включает в себя не только соглашения о том, где будет расположено содержимое, но и о том, как оно будет реагировать на действия пользователя. Лекция 5 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript" обсуждает различные виды встроенных анимаций, которые можно использовать для этих целей.

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


увеличить изображение

Рис. 2.7.  Полноэкранный альбомный макет и макет для заполняющего режима просмотра. Эти режимы просмотра обычно используют одинаковые макеты (с теми же полями), в которых некоторые части пропорционально уменьшены по ширине

Макет для прикрепленного просмотра (слева, только для альбомного режима) и полноэкранный портретный макет (справа). Они не масштабируются


Рис. 2.8.  Макет для прикрепленного просмотра (слева, только для альбомного режима) и полноэкранный портретный макет (справа). Они не масштабируются

Врезка: Проектирование для всех четырех режимов просмотра

Так же, как я размышлял о четырех режимах просмотра для "Here my am!", я рекомендую вам сделать то же самое по одной простой причине: ваше приложение окажется в каждом из этих режимов просмотра независимо от того, приспособили вы его для этого или нет. Режимами просмотра управляют не приложения, а пользователи, поэтому, если вы не разработаете дизайн для каждого из возможных состояний, ваше приложение, вполне возможно, будет выглядеть отвратительно в этих состояниях. Вы можете, как показано в лекции 6, заблокировать ориентацию экрана в портретном или альбомном режиме, если захотите, но это оправданно в целях расширения опыта взаимодействия пользователя и приложения и никак не может служить оправданием лени разработчика. В итоге, если только у вас нет особенных причин не делать этого, каждая страница вашего приложения должна быть приспособлена к каждому из четырех режимов просмотра.

Возможно, это выглядит как ненужная нагрузка, но состояния отображения не влияют на функции приложения: это просто разные способы просмотра одних и тех же данных. Помните о том, что изменение состояния просмотра никогда не изменяет режим работы приложения. Обработка смены состояния просмотра, поэтому - это, в основном, принятие решений о том, какие элементы должны быть видимыми и как эти элементы должны быть расположены на странице. Это не должно быть чем-то более сложным, чем то, о чём сказано выше, и для приложений, написанных на HTML и JavaScript, всё может быть сделано, по большей части, если не полностью, с помощью медиа-запросов CSS.

Один из важных аспектов дизайна приложений для Магазина Windows заключается в понимании единообразного стиля макета: размера шрифтов заголовков, их расположения, определенные размеры границ, особенности сеточной раскладки и так далее (как отмечено на вышеприведенных рисунках). Эти рекомендации способствуют высокому уровню единообразия стиля приложений, в итоге, пользователь может интуитивно, основываясь на мышечной памяти, использовать основные элементы пользовательского интерфейса. Некоторые из этих рекомендаций можно найти в документе "Создание макета приложения" (http://msdn.microsoft.com/library/windows/apps/hh872191.aspx), их можно увидеть реализованными в шаблонах приложений вместе с другими аспектами дизайна. Это - одна из причин, по которой Microsoft обычно рекомендует начинать создание нового приложения с шаблона, начиная с него разработку. То, что я показал в вышеприведенных каркасах, отражает макет, предоставленный одним из более сложных шаблонов. В то же время, узнаваемый стиль приложений - это стартовая точка, но не жёсткое требование - приложения могут отступать от них, когда у этого есть смысл. При отсутствии же весомых причин для отступления, подтвержденных чётким дизайн-проектом, лучше от данных рекомендаций не отходить.

Хватит говорить! Давайте просто примем к сведению, что у нас есть отличный дизайн для дальнейшей работы, наши дизайнеры потягивают капучино, удовлетворённые результатами своей работы. Теперь наша задача заключается в том, чтобы наполнить этот дизайн жизнью.

Создаем разметку

Для целей создания разметки, макета, настройки внешнего вида приложения, вы можете добавить в свой арсенал мощный инструмент - Blend для Visual Studio. Как вы можете знать, Blend был доступен (по более высокой цене) для дизайнеров и разработчиков, работающих с XAML (фреймворк для визуализации, которым пользуются приложения, написанные на C#, Visual Basic и C++). Теперь Blend бесплатен и поддерживает HTML, CSS и JavaScript. Я выделил последнее, так как он не только может загружать разметку и стили: он загружает и исполняет ваш код, прямо на монтажной панели (Artboard, рабочая поверхность, на которой располагаются элементы), так как этот код часто влияет на DOM, стиль элементов и так далее. И здесь присутствует Интерактивный режим (Interactive mode)… но я забегаю вперед!

Blend и Visual Studio похожи на две стороны одной медали: они совместно используют проекты с одинаковыми форматами файлов и имеют команды для лёгкого переключения между ними, в зависимости от того, на чём вы хотите сосредоточиться - на дизайне или на разработке. Для того чтобы показать это, начнём создание приложения "Here My Am!" в Blend. Так же, как мы поступали ранее в Visual Studio, запустите Blend, выполните команду Создать проект… (New Project), выберите тип проекта - Пустое приложение (Blank App). В итоге, будет создан проект с точно такой же структурой, как ранее (Замечание: В файле Video 2-2 показаны эти шаги).

Последуем практике написания разметки страницы в HTML - без стилей, без кода, и не обращая внимание на несколько классов, которые понадобятся нам для настроек стилей - поместим нижеприведенный код в элемент body страницы default.html, (заменяя строку <p>Content goes here</p>):

<div id="mainContent">
<header aria-label="Header content" role="banner">
<h1 class="titlearea win-type-ellipsis">
<span class="pagetitle">Here My Am!</span>
</h1>
</header>
<section aria-label="Main content" role="main">
<div id="photoSection" aria-label="Photo section">
<h2 class="group-title" role="heading">Photo</h2>
<img id="photo" src="images/taphere.png"
alt="Tap to capture image from camera" role="img" />
</div>
<div id="locationSection" aria-label="Location section">
<h2 class="group-title" role="heading">Location</h2>
<iframe id="map" src="ms-appx-web:///html/map.html" aria-label="Map"></iframe>
</div>
</section>
</div>

Здесь мы видим пять элементов: главный заголовок, два подзаголовка, место для фотографии (по умолчанию - с изображением, содержащим инструкцию "коснитесь здесь" (tap here)) и элемент iframe, который содержит страницу, на которой будет создан экземпляр веб-элемента управления для карт Bing1).

Вы можете видеть, что некоторым элементам назначены классы стилей. Они начинаются с win и находятся в таблице стилей2) WinJS. Вы можете просмотреть их в Blend, используя закладку Правила стилей (Style Rules), показанную на Рис. 2.9. Другие стили, такие, как titlearea, pagetitle и group-title подразумевают, что вы определили их в вашем собственном листе стилей, тем самым переопределив стили WinJS для конкретных элементов.

В Blend, закладка Правила стилей (Style Rules) позволяет вам просмотреть таблицу стилей WinJS и увидеть, что именно содержит каждый стиль. Обратите внимание на строку поиска под закладкой. Если вам нужно отыскать какой-то определенный стиль, можно не тратить время, просматривая их список. Достаточно начать вводить его имя в строке поиска и позволить компьютеру делать его работу!


Рис. 2.9.  В Blend, закладка Правила стилей (Style Rules) позволяет вам просмотреть таблицу стилей WinJS и увидеть, что именно содержит каждый стиль. Обратите внимание на строку поиска под закладкой. Если вам нужно отыскать какой-то определенный стиль, можно не тратить время, просматривая их список. Достаточно начать вводить его имя в строке поиска и позволить компьютеру делать его работу!

Страница, которую мы загрузим в элемент iframe, map.html, это часть нашего пакета приложения, которую мы скоро добавим, но обратите внимание на то, как мы ссылаемся на неё. Протокол ms-appx-web:/// показывает, что iframe и его содержимое будут исполняться в веб-контексте (представленном в лекции 1), тем самым, позволяя нам загружать удаленный скрипт для элемента управления карт Bing. Тройной слэш, одна из его частей, а точнее - третий слэш - это краткая запись для "пакета текущего приложения" (значение, которое вы можете получить из document.location.host), таким образом, нам не нужно создавать абсолютные URI для содержимого, которое находится в пакете.

Для того, чтобы показать, что страницу следует загрузить в локальном контексте, протокол выглядит как ms-appx://. Важно помнить о том, что скрипты не могут разделяться между этими контекстами (включая переменные и функции), относительные пути оказываются в том же самом контексте, и взаимодействие между ними осуществляется посредством функции HTML5 postMessage, как мы увидим ниже. Это предотвращает возможность управления вашим приложением различными веб-сайтами и доступ к API WinRT.

Кроме того, я включил в состав элементов различные атрибуты aria-* (так же, как это сделано в шаблоне), которые нужны для поддержки приложением специальных возможностей. Мы рассмотрим специальные возможности в подробностях в лекции 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", но важно, чтобы с самого начала помнили о них: большинство пользователей Windows так или иначе применяют специальные возможности. И хотя некоторые из аспектов специальных возможностей несложно добавить позднее, добавление атрибута aria-* в разметку лучше выполнить раньше.

В лекции 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой" мы так же увидим, как выделить строки (в том числе - метки ARIA) из нашей разметки, JavaScript и даже из манифеста и разместить их в ресурсном файле. Возможно, вы захотите сделать это ранее, поэтому посмотрите раздел "Подготовка к локализации" в этой лекции для того, чтобы узнать подробности. Однако обратите внимание на то, что в Blend разрешение ресурсов нормально не работает, поэтому вам может захотеться сначала выполнить основные настройки внешнего вида приложений, а уже потом прилагать усилия к созданию ресурсных файлов.

Стилизация в Blend

В данный момент, учитывая, что вы обратили должное внимание на чтение сносок, Blend отображает реальный вид приложения, который, очевидно, нуждается в стилизации, как и любой исходный каркас приложения. Посмотрите на Рис. 2.10.

Приложение в Blend без стилизации, отображается в виде, который похож на тот, который виден в имитаторе. Если изображение taphere.png не отображается после его добавления, используйте команду меню Вид > Обновить (View > Refresh)


увеличить изображение

Рис. 2.10.  Приложение в Blend без стилизации, отображается в виде, который похож на тот, который виден в имитаторе. Если изображение taphere.png не отображается после его добавления, используйте команду меню Вид > Обновить (View > Refresh)

Закладки около верхнего левого угла окна программы дают вам доступ к файлам вашего проекта, к Активам (Assets), таким, как элементы управления, которые вы можете добавить в ваш пользовательский интерфейс, и средство просмотра для всех правил стилей (Style Rules), определенных в текущем окружении. В левой нижней части присутствует область Динамическая DOM (Live DOM), которая позволяет вам просматривать элементы в иерархии, и закладка Устройство (Device), которая позволяет вам задавать ориентацию, разрешение экрана и состояние просмотра. Щёлкая на элементе в области Динамическая DOM (Live DOM), вы выделяет его в монтажной панели, так же, как щелчок по элементу в монтажной панели, выделяет его в области Динамическая DOM (Live DOM).

В правой части вы видите то, с чем близко подружитесь: области Атрибуты HTML (HTML Attributes) и Свойства CSS (CSS Properties). Последняя отображает в верхней своей части все источники стилей, которые были применены к выделенному элементу и точное расположение этих стилей (вечная головная боль при работе с CSS). То, что выбрано в данной области. помните, определяет, куда будут записаны изменения, внесенные в панель свойств ниже, поэтому очень внимательно следите за тем, что именно вы выделяете.

Теперь, чтобы привести нашу исходную страницу к виду, напоминающему каркас приложения, нам нужно поработать с элементами и создать необходимые селекторы и стили. Для начала, я рекомендую создать сетку из одной ячейки (1х1) в теле (body) страницы, так как это улучшит отображение контента на монтажной панели Blend. Поэтому, добавьте display: -grid; -ms-grid-rows: 1fr; -ms-grid-columns: 1fr; в default.css для данного элемента.

CSS-сетки делают макет приложения очень простым: мы просто используем пару вложенных сеток для того, чтобы расположить в них основные разделы и подразделы окна приложения, следуя обычным шаблонам настройки стилей, которые отлично работают в Blend.

Так, для элемента div mainContent мы создаем правило из Id и настраиваем его, задавая display: -ms-grid;-ms-grid-columns: 1fr; и -ms-grid-rows: 128px 1fr 60px;. (Рис. 2.11) Эти действия позволят нам создать базовые вертикальные области каркаса приложения. Обычно, вам не захочется устанавливать правые или левые поля прямо в данной сетке, так как нижняя часть часто имеет горизонтально прокручиваемое содержимое, которое выступает за левый и правый края. В нашем случае, мы можем использовать одну сетку, но вместо этого мы добавим эти поля во вложенной сетке внутри элементов заголовка и раздела.

Установка свойств сетки для элемента div mainContent. Обратите внимание на то, как флаг Просматривать только заданные свойства (View Set Properties Only) (в верхней правой части) упрощает просмотр того, какие стили устанавливаются для текущего правила. Кроме того, обратите внимание, как на монтажной панели (Artboard) отмечены столбы и строки сетки, в том числе - с помощь слайдеров (обведены) для управления строками и столбцами непосредственно на монтажной панели.


увеличить изображение

Рис. 2.11.  Установка свойств сетки для элемента div mainContent. Обратите внимание на то, как флаг Просматривать только заданные свойства (View Set Properties Only) (в верхней правой части) упрощает просмотр того, какие стили устанавливаются для текущего правила. Кроме того, обратите внимание, как на монтажной панели (Artboard) отмечены столбы и строки сетки, в том числе - с помощь слайдеров (обведены) для управления строками и столбцами непосредственно на монтажной панели.

Показ работы над стилями, настройки различных уровней разметки и подходящих стилей для медиазапросов, соответствующих различным состояниям просмотра, лучше всего выглядит на видео. В частности, в файле Video 2-2 (оно находится среди дополнительных данных к курсу) вы можете увидеть этот процесс, начиная с создания проекта, настройки стилей для различных состояний просмотра, до переход в Visual Studio (для этого нужно щёлкнуть правой кнопкой проект в Blend и выбрать команду Изменить в Visual Studio (Edit In Visual Studio)) и запуска приложения в имитаторе для того, чтобы убедиться в том, что всё настроено верно. Кроме того, в видео вы можете оценить объем времени, который нужен для работы со стилями, если вам хорошо знакомы инструменты разработки.

В результате всех этих действий вид программа в имитаторе соответствует ранее разработанному каркасу, смотрите рисунки 2.12., 2.13., 2.14. - и все настройки стилей полностью включены в соответствующие медиа-запросы файла default.css. Гораздо важнее то, что Blend отображает нам результаты работы в реальном времени, тем самым сохраняя огромное количество времени в сравнении с тем, если бы нам нужно было, после правок, внесенных в CSS, каждый раз запускать приложение - уверен, вы знакомы с этим неприятным процессом! (И экономия времени даже больше с Интерактивным режимом; обратитесь к видеофайлу Video 4-1, который находится среди дополнительных материалов к лекции 4, "Элементы управления, их стилизация и привязка данных").

Полноэкранный альбомный режим просмотра


увеличить изображение

Рис. 2.12.  Полноэкранный альбомный режим просмотра

Заполняющий режим просмотра (только для альбомного режима)


Рис. 2.13.  Заполняющий режим просмотра (только для альбомного режима)

Режим прикрепленного просмотра (только для альбомного режима) и полноэкранный портретный режим; они показаны в пропорциональном масштабе


Рис. 2.14.  Режим прикрепленного просмотра (только для альбомного режима) и полноэкранный портретный режим; они показаны в пропорциональном масштабе

Добавление кода

Завершим реализацию приложения в Visual Studio. Щёлкните правой кнопкой по имени проекта на панели Проекты (Projects) в Blender и выберите команду Изменить в Visual Studio (Edit In Visual Studio), если вы этого еще не сделали. Обратите внимание на то, что если ваш проект уже загружен в Visual Studio, когда вы переходите в эту среду, вы (по умолчанию) увидите предложение перезагрузить измененные файлы. Ответьте утвердительно1). В данный момент у нас есть макет приложения и стили для всех необходимых режимов просмотра, и мы можем не беспокоиться о коде, за исключением внесения в него небольших улучшений, о которых мы скоро поговорим.

Что это означает, так то, что, по большей части, мы можем просто написать код нашего приложения для разметки, но не для разметки со стилизацией, что, конечно, является наилучшим подходом при работе с HTML/CSS. Вот возможности, которые мы сейчас реализуем:

На Рис. 2.15 показано, на что будет похоже приложение, когда мы завершим работу.

Готовое приложение "Here my am!" (хотя здесь я уменьшил масштаб карты, чтобы вы не могли точно назвать место, где я живу!)


увеличить изображение

Рис. 2.15.  Готовое приложение "Here my am!" (хотя здесь я уменьшил масштаб карты, чтобы вы не могли точно назвать место, где я живу!)

Создание карты с информацией о текущем местоположении

Для отображения карты мы используем веб-элемент управления карт Bing, экземпляр которого создан на странице map.html, которая загружена в элемент iframe главной страницы. Страница загружает скрипт элемента управления карт Bing из удалённого источника и исполняется в веб-контексте. Обратите внимание на то, что мы, кроме того, можем воспользоваться SDK для карт Bing (http://msdn.microsoft.com/library/hh846481.aspx), который предоставит нам скрипт, подходящий для загрузки в локальный контекст. Сейчас я хочу использовать подход с удаленным скриптом, так как это даёт нам возможность работать с веб-содержимым, и веб-контекст, в его основных проявлениях, это то, что вы, я уверен, захотите понять для использования в собственных приложениях. Мы переключимся на локальные элементы управления в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript".

Таким образом, разместим файл map.html в папке html. Для этого щёлкните правой кнопкой мыши по проекту, выберите команду Добавить > Создать папку (Add > New Folder), введите для неё имя html. Затем щёлкните правой кнопкой по этой папке, выберите команду Добавить > Создать элемент (Add > New Item) и выберите HTML-страницу (HTML Page). Когда новая страница отобразится на экране, замените её содержимое на следующее1):

<!DOCTYPE html>
<html>
<head>
<title>Map</title>
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>

<script type="text/javascript">
//Глобальные переменные
var map = null;

document.addEventListener("DOMContentLoaded", init);
window.addEventListener("message", processMessage);

//Функция для преобразования строки к синтаксису { functionName: ..., args: [...] }
//в вызове именованной функции с такими аргументами. Основана на стандартном
//диспетчере, который позволяет коду в iframe быть вызванным посредством
//postMessage. 
function processMessage(msg) {
//Проверяет данные и источник (в данном случае - страницу, работающую в локальном
//контексте)
if (!msg.data || msg.origin !== "ms-appx://" + document.location.host) {
return;
}

var call = JSON.parse(msg.data);

if (!call.functionName) {
throw "Message does not contain a valid function name.";
}

var target = this[call.functionName];

if (typeof target != 'function') {	
throw "The function name does not resolve to an actual function";
}	
	
return target.apply(this, call.args);	
}	


function notifyParent(event, args) {	
//Добавляет имя события к аргументам обекта и преобразует в строку в
//качестве сообщения.
args["event"] = event;	
window.parent.postMessage(JSON.stringify(args),	
"ms-appx://" + document.location.host);	
}	



//Создает карту (хотя пространство имен не может быть определено без возможности связи)
function init() {	
if (typeof Microsoft == "undefined") {	
return;	
}	

map = new Microsoft.Maps.Map(document.getElementById("mapDiv"), {
//Замените эти учетные данные на свои, полученные на 
//http://msdn.microsoft.com/en-us/library/ff428642.aspx 
credentials: "...",
//zoom: 12,
mapTypeId: Microsoft.Maps.MapTypeId.road
});
}

function pinLocation(lat, long) {
if (map === null) {
throw "No map has been created";
}

var location = new Microsoft.Maps.Location(lat, long);
var pushpin = new Microsoft.Maps.Pushpin(location, { draggable: true });

Microsoft.Maps.Events.addHandler(pushpin, "dragend", function (e) {	
var location = e.entity.getLocation();	
notifyParent("locationChanged",	
{ latitude: location.latitude, longitude: location.longitude });
});	

map.entities.push(pushpin);	
map.setView({ center: location, zoom: 12, });
return;	
}	

function setZoom(zoom) {	
if (map === null) {	
throw "No map has been created";
}	

map.setView({ zoom: zoom });
}
</script>
</head>
<body>
<div id="mapDiv"></div>
</body>
</html>

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

В верхней части страницы вы можете видеть ссылку на удалённый скрипт элемента управления карт Bing. Мы можем здесь ссылаться на удалённые скрипты, так как страница загружается в веб-контексте внутри элемента iframe (ms-appx-web:// в default.html). Затем вы можете видеть, как функция init вызывается DOMContentLoader и создаёт элемент управления карты. Далее, у нас есть еще пара методов, pinLocation и setZoom, которые, если понадобятся, могут быть вызваны из основного приложения.

Конечно, так как эта страница загружена в iframe в веб-контексте, мы не можем просто вызвать эти функции напрямую из кода нашего приложения. Вместо этого мы используем функцию HTML5 postMessage, которая вызывает событие message в iframe. Это важный момент: локальный и веб-контексты разделены, поэтому произвольный веб-контент не может управлять приложением или получать доступ к API WinRT. Эти два контекста разделены границей между приложением и веб'ом, которую может пересечь лишь postMessage.

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

Для того, чтобы посмотреть на то, как это работает, взглянем на то, как мы вызываем pinLocation из default.js. Для того, чтобы выполнить этот вызов, нам нужны некоторые координаты, которые мы можем получить из API для определения местоположения (Geolocation) WinRT. Мы делаем это в обработчике события onactivated, поэтому местоположение пользователя задаётся при старте приложения (и сохраняется в переменной lastPosition позже для организации общего доступа):

//Поместите это после строки: var activation = Windows.ApplicationModel.Activation;
var lastPosition = null;


//Поместите это после args.setPromise(WinJS.UI.processAll());
var gl = new Windows.Devices.Geolocation.Geolocator();

gl.getGeopositionAsync().done(function (position) {	
//Сохраним для общего доступа	
lastPosition = { latitude: position.coordinate.latitude,
longitude: position.coordinate.longitude };	

callFrameScript(document.frames["map"], "pinLocation", [position.coordinate.latitude, position.coordinate.longitude]);
});

Здесь callFrameScript - это просто небольшая вспомогательная функция, которая превращает целевой элемент, имя функции и аргументы в соответствующий вызов posMessage:

//Поместите это перед app.start();	
function callFrameScript(frame, targetFunction, args) {	
var message = { functionName: targetFunction, args: args };	
frame.postMessage(JSON.stringify(message), "ms-appx-web://" + document.location.host);
	}

Некоторые замечания по этому коду. Для того чтобы получить координаты, вы можете использовать API WinRT для определения местоположения или API HTML5 для определения местоположения. Они почти одинаковые, с небольшими различиями, описанными в лекции 3 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript", "Ввод данных и сенсоры", во врезке: "Определение местоположения в HTML5". Это API присутствует в WinRT, так как другие поддерживаемые языки (C# и C++) не имеют доступ к API определения местоположения HTML5. Мы, в этом курсе, сосредоточены на API WinRT, поэтому мы просто используем функции в пространстве имен Windows.Devices.Geolocation.

Далее, в качестве второго параметра postMessage, вы можете видеть комбинацию из ms-appx[-web]:// и document.location.host. Это означает: "текущее приложение из локального [или веб] контекста", которое является подходящим источником сообщения. Обратите внимание на то, что мы используем то же самое значение для того, чтобы проверить источник сообщения при его получении: код в map.html проверяет, пришло ли сообщение из локального контекста приложения, тогда как код в default.js проверяет, что сообщение пришло из веб-контекста приложения. всегда будьте уверены в том, что соответствующим образом проверяете источники сообщения; посмотрите материал "Разработка безопасных приложений" (http://msdn.microsoft.com/library/windows/apps/hh849625.aspx).

Наконец, вызов getGeoPositionAsync содержит интересную конструкцию, где мы делаем вызов и используем внутри него цепочку из функции done, аргумент которой - еще одна функция. Это - весьма распространённый подход, который мы можем видеть, работая с API WinRT, для любого API, которому может потребоваться более 50 мс для завершения асинхронной операции. Подобное решение было принято так, что использование API ведет к созданию быстрых и динамичных приложений по умолчанию.

В JavaScript подобные API возвращают то, что называется promise-объектом, он представляет результаты, которые будут получены когда-нибудь в будущем. Каждый такой объект имеет метод done, первый аргумент которого - функция, которая будет вызвана по завершении операции, обработчик завершения (completed handler). В учебном курсе мы еще коснёмся данной темы, в частности, поговорим о функции then, которая похожа на done, но поддерживает дальнейшее объединение функций в цепочку (лекция 3) и о том, как promise-объекты в более общем виде используются в асинхронных операциях (лекция 5 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой").

Аргумент, который передается обработчику завершения содержит результат асинхронного вызова, в нашем случае это объект Windows.Geolocation.Geoposition, содержащий последние данные, прочитанные с сенсора. (Читая документацию по асинхронным функциям, вы можете увидеть, что возвращаемый тип имеет вид, похожий на IAsyncOperation<Geoposition> . Имя внутри <> показывает реальный тип данных результата, в итоге, для того, чтобы узнать подробности, вы можете перейти к материалу по этому типу.) Координаты, прочитанные с сенсора, это то, что мы передаем функции pinLocation внутри iframe, которая, в свою очередь, создаёт на карте маркер в указанных координатах и центрирует карту по данному местоположению2).

Еще одно, последнее, примечание по асинхронным API. Внутри API WinRT все асинхронные функции имеют "Async" в своих именах. Так как подобная практика не принята в среде JavaScript или DOM API, асинхронные функции внутри WinJS не используют данный суффикс. Другими словами, WinRT создана нейтральной по отношению к языкам программирования, а WinJS разработана с учетом языковых соглашений, характерных для JavaScript.

О, подождите, манифест!

Сейчас вы уже могли испытать тот код, который приведен выше, и столкнуться с исключением "Доступ запрещен" при попытке вызова getGeoPositionAsync. Почему это так? Исключение говорит о том, что мы не указали возможность Расположение (Location) в манифесте. Без включения этой возможности, вызов вроде этого, зависящий от возможности, вернет исключение.

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

gl.getGeopositionAsync().done(function (position) {
//...	
}, function(error) {	
console.log("Unable to get location.");	
});

Функция console.log записывает строку в окно Консоли JavaScript (JavaScript Console) в Visual Studio, а это, очевидно, хорошая идея. Сейчас запустите приложение вне отладчика, и вы увидите, что оно запустилось, так как исключение теперь признано "обработанным". В отладчике установите точку останова на строку console.log и точка останова сработает после того, как появится исключение и вы нажмете Далее (Continue). (Пока это всё, что мы делаем с появляющимися ошибками; в лекции 1 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript" мы добавим более подходящее сообщение и команду повтора действия).

Если диалоговое окно исключения вам надоело, вы можете управлять тем, информацию о каких исключениях показывать во всплывающих окнах с помощью диалогового окна Отладка > Исключения (Debug > Exceptions), показанного на Рис. 2.16, в разделе Исключения JavaScript времени выполнения (JavaScript Runtime Exceptions). Если вы снимете флажки в колонке Необработанное пользовательским кодом (User-unhandled), вы не увидите диалоговое окно, когда происходит то или иное исключение.

Исключения JavaScript времени выполнения в диалоговом окне Отладка > Исключения (Debug > Exceptions) в Visual Studio


увеличить изображение

Рис. 2.16.  Исключения JavaScript времени выполнения в диалоговом окне Отладка > Исключения (Debug > Exceptions) в Visual Studio

Вернемся к возможностям: для того, чтобы это приложение правильно себя вело, откройте package.appmanifest вашего проекта, выберите закладку Возможности (Capabilities) и включите Расположение (Location), как показано на Рис. 2.17.

Установка возможности Расположение (Location) в редакторе манифеста Visual Studio. (Обратите внимание на то, что Blend поддерживает правку манифеста лишь в качестве XML-файла)


Рис. 2.17.  Установка возможности Расположение (Location) в редакторе манифеста Visual Studio. (Обратите внимание на то, что Blend поддерживает правку манифеста лишь в качестве XML-файла)

Теперь, даже если мы объявили возможность, геолокация всё еще требует разрешения пользователя, как было указано в лекции 1. При первом запуске приложения с установленной возможность вы увидите всплывающее окно, похожее на то, что изображено на Рис. 2.18. Если пользователь запретит доступ в этом окне, обработчик ошибки снова будет запущен, так как API снова выдаст исключение запрета доступа.

Типичное окно, позволяющее узнать мнение пользователя, отражающее пользовательскую цветовую схему, которое появляется при первой попытке приложения вызвать API, доступ к которому осуществляется через брокера (в данном случае - геолокационное API). Если пользователь запретит доступ, API выдаст ошибку, но позже пользователь может изменить решение в панели Параметры > Разрешения (Settings > Permissions).


увеличить изображение

Рис. 2.18.  Типичное окно, позволяющее узнать мнение пользователя, отражающее пользовательскую цветовую схему, которое появляется при первой попытке приложения вызвать API, доступ к которому осуществляется через брокера (в данном случае - геолокационное API). Если пользователь запретит доступ, API выдаст ошибку, но позже пользователь может изменить решение в панели Параметры > Разрешения (Settings > Permissions).

Врезка: как сбросить информацию о решении пользователя для целей тестирования?

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

Обратите внимание на то, что когда пользователь изменяет настройки разрешений, приложение никак не оповещается об этом. Приложение может обнаружить это изменение, лишь попытавшись использовать API снова. Мы вернемся к этому в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript".

Получение фотографии с камеры

Хоть это и необычно, я надеюсь, что идея добавить описание получений фотографий с камеры в раздел "быстрый старт", заставит вас сомневаться во вменяемости автора. Не потребуется ли для этого огромного количества кода? Да, так было, но в Windows 8 это не так. Все сложные особенности захвата данных с камеры отлично инкапсулированы внутри API Windows.Media.Capture, так что, в итоге, мы можем добавить в приложение эту возможность с помощью нескольких строк кода. Это хороший пример того, как небольшой динамичный код, наподобие JavaScript комбинируется с отлично спроектированными компонентами WinRT - и то и другое в системе, и тем и другим вы можете пользоваться - создавая мощный союз.

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

Возможность, связанная с камерой в редакторе манифеста Visual Studio


Рис. 2.19.  Возможность, связанная с камерой в редакторе манифеста Visual Studio

При первом использовании камеры во время выполнения приложения, вы увидите еще одно диалоговое окно, запрашивающее разрешение пользователя, Рис. 2.20.

Всплывающее окно для получения разрешения пользователя на применение камеры. Вы можете в любой момент изменить эту настройку в панели Параметры > Разрешения (Settings > Permissions)


увеличить изображение

Рис. 2.20.  Всплывающее окно для получения разрешения пользователя на применение камеры. Вы можете в любой момент изменить эту настройку в панели Параметры > Разрешения (Settings > Permissions)

Теперь нам нужно настроить элемент img для того, чтобы он мог реагировать на жест прикосновения. Для этого нам нужно просто добавить прослушиватель события click, который срабатывает для любых способов ввода (сенсорный интерфейс, мышь, перо), как мы увидим в лекции 3 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript".

var image = document.getElementById("photo");
image.addEventListener("click", capturePhoto.bind(image));

Здесь мы указываем capturePhoto в качестве обработчика события и используем функциональность объектного метода bind для того, чтобы объект this внутри capturePhoto был напрямую связан с элементом img. В результате, этот обработчик может быть использован для любого количества элементов, так как он не ссылается на DOM.

//Расположите это под var lastPosition = null;
var lastCapture = null;


//Расположите это после callFrameScript	
function capturePhoto() {	
//В соответствии с вызовом .bind() в addEventListener, "this" будет элементом image,
//но нам нужна копия для асинхронно завершившегося обработчика ниже.	
var that = this;	

var captureUI = new Windows.Media.Capture.CameraCaptureUI();

//Показывает, что мы хотим получить PNG-изображение не больше, чем целевой элемент -	
//пользовательский интерфейс автоматически обрежет изображение под этот размер	
captureUI.photoSettings.format = Windows.Media.Capture.CameraCaptureUIPhotoFormat.png;
captureUI.photoSettings.croppedSizeInPixels =	
{ width: this.clientWidth, height: this.clientHeight };	

captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.done(function (capturedFile) {
//Убедитесь, что проверили, подходит ли вам возвращенный элемент: он может содержать null, если
//пользователь отменил действие.
if (capturedFile) {
lastCapture = capturedFile; //Сохраняем для целей Общего доступа (Share)
that.src = URL.createObjectURL(capturedFile, {oneTimeOnly: true});
}
}, function (error) {
console.log("Unable to invoke capture UI.");
});
}

Нам необходимо создать локальную копию this внутри обработчика click, так как, как только мы оказываемся внутри функции завершения асинхронного запроса (посмотрите функцию внутри captureFileAsync.done) мы оказываемся в новой области видимости и объект this изменяется. Принято такую копию this называть that. Берем это?

Для того чтобы запустить интерфейс камеры, нам лишь нужно создать экземпляр Windows.Media.Capture.CameraCaptureUI с ключевым словом new (обычный способ создания экземпляров динамических объектов WinRT), настроить его на желаемый формат и размер изображения (это одни из многих других возможных параметров, которые обсуждаются в лекции 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript") и вызвать captureFileAsync. Здесь будет произведена проверка манифеста, и, если необходимо, будет запрошено разрешение пользователя.

Это асинхронный вызов, поэтому мы используем .done в конце с обработчиком завершения, который в данном случае принимает объект Windows.Storage.Storagefile. Посредством данного объекта вы можете получить любые исходные данные изображения, которые вам нужны, но для наших целей мы просто хотим отобразить изображение в элементе img. Это очень просто! Мы можем передать объект StorageFile методу URL.createObjectURL и получить URI, который можно напрямую присвоить свойству img.src. Сделанная фотография появится там, где нужно1)!

Обратите внимание на то, что captureFileAsync вызовет обработчик завершения, если пользовательский интерфейс был успешно отображен, но пользователь нажал кнопку Назад и фото не было сделано. Именно поэтому присутствует дополнительная проверка правильности captureFile. Обработчик ошибки в promise-объекте, в свою очередь, сначала перехватит ошибки запуска пользовательского интерфейса, но обратите внимание на то, что при отсутствии разрешения пользователя на работу с камерой, сообщение об этом появится непосредственно в пользовательском интерфейсе камеры (Рис. 2.21), поэтому нет необходимости иметь обработчик ошибки для этих целей в данном API. В большинстве случаев, однако, вам понадобится соответствующий обработчик ошибок для асинхронных вызовов.

Сообщение в пользовательском интерфейсе камеры, когда пользователь не разрешил доступ к ней (слева); вы можете изменить разрешения посредством панели Разрешения чудо-кнопки Параметры (Settings) (справа)


увеличить изображение

Рис. 2.21.  Сообщение в пользовательском интерфейсе камеры, когда пользователь не разрешил доступ к ней (слева); вы можете изменить разрешения посредством панели Разрешения чудо-кнопки Параметры (Settings) (справа)

Делимся удовольствием!

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

Windows 8 вместо этого предложила понятие контракта Общий доступ (Share), который используется для применения возможностей чудо-кнопки Общий доступ (Share) во всех приложениях, реализующих данный контракт. Когда вы, находясь в приложении, нажимаете на чудо-кнопку Общий доступ, Windows запрашивает у приложения исходные (source) данные. Затем система проверяет данные и генерирует список целевых (target) приложений, которые способны воспринимать данные в этом формате (в соответствии с манифестом), и отображает список приложений в панели чудо-кнопки Общий доступ. Когда пользователь выбирает целевой приложение, это приложение активируется и ему передаются исходные данные. Коротко говоря, контракт - это промежуточная абстракция, находящаяся между двумя приложениями, таким образом, приложение-источник и приложение-приемник никогда не нуждаются в информации друг о друге.

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

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

Этот запрос приходит через событие datarequested отправленное объекту Windows.ApplicationModel.DataTransfer.DataTransferManager1). Для начала, нам лишь нужно установить соответствующий прослушиватель события, разместив нижеприведенный код в событии activated в файле default.js после установки прослушивателя для события click элемента img:

var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView();
dataTransferManager.addEventListener("datarequested", provideData);

Идея текущего представления (current view) заключается в том, что это то, что мы видим в появляющихся время от времени областях. Это отражает то, что приложение может быть запущено по разным причинам - таким, как обслуживание контракта - и, таким образом, предоставлять пользователю различные страницы или представления. Эти представления (не связанные с режимами просмотра, такими, как прикрепленный, заполняющий и другие) могут быть активны одновременно. Для обработки этих состояний убедитесь в том, что ваш код реагирует на них, определенные API возвращают объекты, соответствующие текущему представлению приложения, как мы видим здесь.

В данном событии обработчик принимает объект Windows.ApplicationModel.DataTransfer.DataRequest в параметрах события (e.request), которые хранятся в объекте DataPackage (e.request.data). Для того чтобы сделать данные доступными для целей общего доступа, вы заполняете пакет данных (data package) информацией в доступном вам формате. (Мы сохраняем данные в lastPosition и lastCapture.) В нашем случае, мы убеждаемся в том, что имеем фотографию и информацию о местоположении и заполняем свойства текста и изображения (если вы хотите получить карту от сервиса Bing для передачи её в другое приложение, посмотрите материал "Получение статической карты" (http://msdn.microsoft.com/library/ff701724.aspx):

//Поместите это после capturePhoto 
function provideData(e) {
var request = e.request;
var data = request.data;

if (!lastPosition || !lastCapture) {
//Нечего передавать, поэтому выходим
 return;
}

data.properties.title = "Here My Am!";

data.properties.description = "At ("	
+ lastPosition.latitude + ", " + lastPosition.longitude + ")";	
	
//Передавая изображение, добавляем миниатюру	
var streamReference =	
Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(lastCapture);
data.properties.thumbnail = streamReference;	
// Рекомендовано использовать и setBitmap и setStorageItems при передаче отдельного изображения
//так как целевое приложение может поддерживать лишь что-то одно.

//Поместим файл изображения в массив и передадим его setStorageItems data.setStorageItems([lastCapture]);

//Методу setBitmap требуется RandomAccessStream. data.setBitmap(streamReference);
}

Последняя часть этого кода представляет собой стандартную реализацию возможности по передаче изображения из файла (которое находится в lastCapture). Я взял большую часть этого кода из примера "Приложение-источник для общего доступа", (http://code.msdn.microsoft.com/windowsapps/Sharing-Content-Source-App-d9bffd84), подробнее этот пример мы рассмотрим в лекции 1 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой".

С этим последним дополнением, и с подходящим приложением-приемником (таким, как в примере "Приложение-цель для общего доступа", (http://code.msdn.microsoft.com/windowsapps/Sharing-Content-Target-App-e2689782), как показано на Рис. 2.22), сейчас мы получили весьма функциональное приложение, которое состоит из 35 строк HTML-кода, 125 строк CSS и 100 JavaScript-строк!

Передача данных (обезьяна увидела, обезьяна сделала!) в приложение из примера реализации целевого приложения Windows SDK. Целевое приложение частично перекрывает текущее приложение, таким образом, пользователь никогда не потеряет контекст приложения


увеличить изображение

Рис. 2.22.  Передача данных (обезьяна увидела, обезьяна сделала!) в приложение из примера реализации целевого приложения Windows SDK. Целевое приложение частично перекрывает текущее приложение, таким образом, пользователь никогда не потеряет контекст приложения

Исключительное доверие: Принимаем сообщения из iframe

Вот еще один фрагмент кода, который я включил в программу "Here My Am!" для того, чтобы завершить пример базового взаимодействия между приложением и iframe: возможность отправлять сообщения из iframe назад, в приложение. В нашем случае, мы хотим знать, когда изменилось положение маркера местоположения для того, чтобы обновить lastPosition.

Для начала, вот простая стандартная функция, которую я добавил в map.html для того, чтобы включить в неё подходящий вызов postMessage, направленный к приложению, из iframe:

function function notifyParent(event, args) {
Добавляем имя события к объекту, содержащему аргументы, и конвертируем в строку для формирования сообщения
args["event"] = event;	
window.parent.postMessage(JSON.stringify(args), "ms-appx://" + document.location.host);

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

Когда маркер перетащили, карта Bing генерирует событие dragend, к которому мы прикрепим обработчик и обработаем событие в функции setLocator сразу после того, как маркер был создан (так же в map.html):

var pushpin = new Microsoft.Maps.Pushpin(location, { draggable: true });

Microsoft.Maps.Events.addHandler(pushpin, "dragend", function (e) {	
var location = e.entity.getLocation();	
notifyParent("locationChanged",	
{ latitude: location.latitude, longitude: location.longitude });
});

Вернемся назад, в default.js (то есть - в приложение) и добавим прослушиватель для входящих messages (сообщений) внутрь app.onactivated:

window.addEventListener("message", processFrameEvent);

Обработчик processFrameEvent ищет в сообщении информацию о событии и поступает соответствующим образом:

function processFrameEvent (message) {	
//Проверяем данные и источник (в нашем случае это - страница из веб-контекста)	
if (!message.data || message.origin !== "ms-appx-web://" + document.location.host) {
return;	
}	

if (!message.data) {
return;
}

var eventObj = JSON.parse(message.data);

switch (eventObj.event) {	
case "locationChanged":	
lastPosition = { latitude: eventObj.latitude, longitude: eventObj.longitude };
break;	

default:
break;
}
};

Ясно, что здесь больше кода, чем нам нужно для того, чтобы обработать одно сообщение или событие из iframe, но я хочу предоставить вам универсальное решение, которое вы сможете использовать в своих приложениях и в других случаях.

Другие шаблоны

В этой лекции мы работали только с шаблоном Пустое приложение, рассматривали основы написания приложений для Магазина Windows без каких-либо отвлекающих факторов. В лекции 3 мы глубже рассмотрим анатомию приложений, воспользовавшись некоторыми другими шаблонами, мы не будем рассматривать их все. Однако, мы завершим эту главу кратким обзором этих весьма удобных инструментов.

Приложение с фиксированным макетом (Fixed Layout Template)

"Проект приложения для Магазина Windows, которое масштабируется с использованием макета с фиксированными пропорциями" (Описание из Blend/Visual Studio).

Недавно мы видели приложение, которое подстраивается под изменения отображаемой области, изменяя свой макет. В "Here My Am!" мы использовали CSS-сетку с автоматически настраиваемыми областями (значения 1fr в строках и столбцах). Такой подход хорошо работает для приложений с содержимым, которое хорошо поддаётся масштабированию и для приложений, которые могут показывать дополнительный контент, такой, как заголовки новостей или результаты поиска, когда у них больше места.

Как мы увидим в лекции 6, другие виды приложений не обладают такой же гибкостью. Например, игры, где соотношение сторон игровой области должно оставаться постоянным. (Было бы несправедливо, если бы игроки, устройства которых имеют большие экраны, увидели бы большее пространство игрового мира!). Так, когда размер отображаемого пространства приложения изменяется - либо при изменении состояния просмотра, либо разрешения экрана - такое приложение скорее масштабирует своё окно, увеличив его или уменьшив, нежели будет настраивать свой макет.

Приложение с фиксированным макетом предоставляет базовую структуру для подобных приложений, так же, как шаблон Пустое приложение предоставляет базу для создания гибких приложений. Ключевую важность здесь имеет элемент управления WinJS.UI.ViewBox, который автоматически заботится о масштабировании содержимого с сохранением соотношения сторон:

<body>	
<div data-win-control="WinJS.UI.ViewBox">
<div class="fixedlayout">	
<p>Content goes here</p>	
</div>	
</div>	
</body>

В файле default.css вы можете видеть элемент body, которому задан стиль "резинового" блока CSS, центрированного на экране, и элемент fixedLayout, установленный на размер 1024х768 (минимальный размер окон полноэкранных альбомных приложений и приложений в режиме заполняющего просмотра). Размещая содержимое внутри дочернего элемента div, который принадлежит элементу ViewBox, вы можете быть уверены в том, что работаете с этими фиксированными размерностями. ViewBox автоматически настроит масштаб того, что находится внутри, и, при необходимости, добавит пустые пространства сверху и снизу или справа и слева для сохранения пропорций.

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

Приложение навигации (Navigation Template)

"Проект приложения для Магазина Windows, которое содержит предопределенные элементы управления для навигации" (Описание из Blend/Visual Studio).

Шаблон Приложение навигации построен на базе шаблона Пустое приложение путём добавления к нему системы навигации по страницам. Как было сказано в лекции 1, приложения для Магазина Windows, написанные на JavaScript, лучше реализовать в виде единственной HTML-страницы, выступающей в роли контейнера, в который динамически загружаются другие страницы. Это позволяет организовать плавные переходы (так же, как и анимации) между этими страницами и сохранить скриптовый контекст.

Этот шаблон, и другие оставшиеся, использует элемент управления Page Navigator, который облегчает загрузку (и отправку) страниц таким способом. Вам нужно лишь создать сравнительно простую структуру для описания каждой страницы и её поведения. Мы рассмотрим это в лекции 3.

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

Приложение таблицы (Grid Template)

"Трехстраничный проект приложения для Магазина Windows, которое осуществляет переходы между группами элементов, расположенных в сетке. Сведения о группах и элементах отображаются на выделенных страницах" (Описание из Blend/Visual Studio).

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

В дополнение к навигации, шаблон Приложение таблицы показывает пример управления коллекциями данных посредством класса WinJS.Binding.List. Эту тему мы подробно рассмотрим в лекции 5, "Коллекции и элементы управления для коллекций". Кроме того, он предоставляет структуру для создания панели приложения и показывает, как упростить поведение приложения в прикрепленном режиме.

Имя шаблона, кстати, получилось от особого табличного (сеточного) макета, который используется для отображения коллекций данных, а не от CSS-сетки (CSS grid).

Приложение с разделением (Split Template)

"Двухстраничный проект приложения для Магазина Windows, которое осуществляет переходы между группированными элементами. Первая страница позволяет выбрать группу, а вторая отображает список элементов вместе со сведениями о выбранном элементе" (Описание из Blend/Visual Studio).

Этот последний шаблон так же построен на базе шаблона Приложение навигации и работает с коллекцией данных. Его домашняя страница отображает список групп, вместо сгруппированных элементов, как в шаблоне Приложение таблицы. При щелчке по группе осуществляется переход на страницу с подробной информацией о группе, разделенной на две части (отсюда и название). Левая часть страницы содержит вертикально прокручиваемый список элементов; правая часть содержит подробную информацию для текущего выделенного элемента.

Так же, как и шаблон Приложение таблицы, шаблон Приложение с разделением обеспечивает заготовку для создания панели приложения и разумно подходит к отображению данных в портретном и прикрепленном режимах просмотра. Это так, потому что вертикально ориентированные режимы просмотра не подходят достаточно хорошо для разделения экрана (в противоположность описанным выше!). Шаблон демонстрирует, как переключиться на модель навигации по страницам внутри тех же состояний просмотра для достижения тех же целей.

Что мы только что изучили:

Лекция 3. Анатомия приложения и навигация по страницам

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

Файлы к данной лекции Вы можете скачать  здесь.

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

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

Представьте себе, на что будет похож дом, построенный без такого внимания к его деталям. Представьте, что некоторые выключателя просто не работают или управляют не теми светильниками. Представьте себе, что трубы в стенах протекают. Представьте, как шкафы и отделка падают со стен через пару недель после того, как они попали в дом. Даже если дому удалось пройти окончательную проверку, подобные недостатки сделают его почти непригодным для жизни, и неважно, каким красивым он может показаться на первый взгляд. Это походило бы на несколько работ знаменитого архитектора Фрэнка Ллойда Райта: очень интересно с архитектурной точки зрения, но совершенно неудобно для того, чтобы жить там.

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

Эта лекция посвящена тем самым основам: ключевым глубинным конструкциям приложения, на основе которых можно построить нечто, выглядящее красиво и работающее по-настоящему хорошо. Мы сначала доведем до полного понимания окружение хост-процесса приложения и посмотрим на активацию (то, как приложение запускается) и на переходы между стадиями жизненного цикла приложения. Затем мы посмотрим на навигацию по страницам внутри приложения и разберем еще некоторые важные попутные вопросы, такие, как работа с несколькими асинхронными операциями.

Позвольте мне предупредить вас о том, что эта лекция гораздо больше и сложнее, чем многие, следующие за ней, так как она имеет дело с программными эквивалентами создания каркаса дома, построения сантехнических коммуникаций и электропроводки. В случае с нашим домом, я могу с полной уверенностью говорить о том, что установка прекрасных светильников, которые выбрала моя жена, принесло больше удовольствия, чем сам процесс строительства, которым я занимался месяцами ранее. Но сейчас, по-настоящему живя в доме, я чувствую глубокую признательность за всю ту менее привлекательную работу, благодаря которой дом был построен. Это место, где я хочу быть, место, в котором я и моя семья будем счастливы провести большую часть нашей жизни. Вы ведь хотите, чтобы клиенты чувствовали то же самое по отношению к вашим приложениям? Абсолютно точно! Зная о том удовольствии, которое хорошо спроектированные приложения способны принести вашим клиентам, займёмся нашим делом и ощутим удовольствие от исследования деталей!

Локальный и веб-контексты внутри хост-процесса приложения

Как было описано в лекции 1, приложения, написанные на HTML, CSS и JavaScript не являются исполняемыми, как их скомпилированные аналоги, написанные на C#, Visual Basic или C++. В пакетах приложений нет EXE-файлов, там есть лишь .html, .css и .js-файлы (и ресурсы тоже, конечно), которые не содержат ничего, кроме обычного текста. В итоге, что-то должно превратить эти тексты, определяющие приложение, во что-то, что запускается и работает в памяти. Это "кое-что" - хост-процесс приложения (app host), wwahost.exe, который создаёт то, что мы называем управляемой средой (hosted environment) приложений для Магазина Windows.

Рассмотрим то, что мы уже узнали в лекции 1 и лекции 2 о характеристиках управляемой среды.

В этой лекции мы больше будем заниматься не самими этими характеристиками, а тем, как они воздействуют на структуру приложения. (Для того, чтобы посмотреть характеристики, обратитесь к примеру "Интеграция содержимого и элементов управления с веб-сервисов", (http://code.msdn.microsoft.com/windowsapps/Mashup-Sample-10689f5b).)

В первую очередь, и прежде всего, отметим, что домашняя страница приложения, та, которую вы указываете в манифесте, в поле Начальная страница (Start page), на закладке Интерфейс приложения (Application UI)1), всегда выполняется в локальном контексте. И любая страница, к которой она может обратиться напрямую (посредством <href> или команды document.location) так же должна принадлежать локальному контексту.

Далее, страница локального контекста может содержать элемент iframe, имеющий локальный или веб-контекст, предоставляя ему атрибут src, ссылающийся на контент в пакете приложения (и, кстати, программный доступ только для чтения к содержимому вашего пакета, который можно получить посредством Windows.ApplicationMode.Package.Current.InstalledLocation). Ссылки на любые другие местоположения (http[s]:// или другие протоколы) всегда помещаются в iframe, который исполняется в веб-контексте.

<!-- iframe в локальном контексте, с источником из пакета приложения  -->	
<!-- подобное разрешено лишь из локального контекста -->
<iframe src="/frame-local.html"></iframe>	
<iframe src="ms-appx:///frame-local.html"></iframe>	

<!-- iframe в веб-контексте с источником в пакете приложения -->
<iframe src="ms-appx-web:///frame-web.html"></iframe>

<!-- iframe с внешним источником автоматически присваивается веб-контекст -->
<iframe src="http://www.bing.com"></iframe>

Кроме того, если вы используете тег <a href="..." target="..."> с target, указывающем на iframe, схема в href определяет контекст.

Страница в веб-контексте, в свою очередь, может содержать только iframe, который так же имеет веб-контекст. Например, выше можно использовать последние два элемента iframe, в то время как два первых элемента - нет. Кроме того, вы можете использовать ms-appx-web:/// в веб-контексте для того, чтобы ссылаться на другое содержимое в пакете приложения, например, на изображения.

Хотя это не вполне справедливо внутри приложений для Магазина Windows, по причинам, которые мы рассмотрим ниже в этой лекции, похожие правила применимы к навигации между страницами с использованием <href> или document.location. Так как всё то, о чём мы здесь говорили, похоже сейчас на кашу из разных сведений, точное поведение для этих вариаций и iframe приведено в следующей таблице:

Таблица 3.1.
ЦельРезультат на странице в локальном контекстеРезультат на странице в веб-контексте
<iframe src="ms-appx:///"> iframe в локальном контекстеНе разрешено
<iframe src="ms-appx-web:///"> iframe в веб-контексте iframe в веб-контексте
<iframe src="http[s]:// "> или другая схема iframe в веб-контексте iframe в веб-контексте
<a href="[uri]" target="myFrame"> <iframe name="myFrame"> iframe в локальном или веб-контексте, в зависимости от [uri] iframe в веб-контексте; [uri] не может начинаться с ms-appx.
<a href="ms-appx:///">Ссылка на страницу в локальном контекстеНе разрешено, если только не задано явно (смотрите ниже)
<a href="ms-appx-web:///">Не разрешеноСсылка на страницу в веб-контексте
<a href="[uri]"> с любым другим протоколом, включая http[s]Открывает браузер по умолчанию с [uri]Открывает браузер по умолчанию с [uri]

Когда iframe работает в веб-контексте, обратите внимание на то, что страница может иметь ms-appx-web-ссылки на ресурсы пакета приложения, даже если страница загружена с удалённого источника (http[s]). Подобная страница, конечно, не будет работать в браузере.

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

Аналогично, навигация со страницы веб-контекста к страницам локального контекста по умолчанию не разрешена, но вы можете включить это, вызвав сверхсекретную функцию MSApp.addPublicLocalApplicationUri (http://msdn.microsoft.com/library/windows/apps/hh465759.aspx) из кода на локальной странице (на самом деле, функция хорошо документирована) для каждого конкретного URI, который вам нужен:

//Это должно быть вызвано из локального контекста
MSApp.addPublicLocalApplicationUri("ms-appx:///frame-local.html");

Упражнение для этой лекции, "Direct Navigation", содержит демонстрацию этой возможности (как в Сценарии 6, в примере "Интеграция содержимого и элементов управления с веб-сервисов", (http://code.msdn.microsoft.com/windowsapps/Mashup-Sample-10689f5b). Будьте осторожны, когда URI содержит параметры запроса. Например, вы можете не захотеть позволять вебсайту переходить на что-то вроде: ms-appx:///delete.html?file=superimportant.doc , выполняя операцию по удалению очень важных данных!

Здесь возникает еще один вопрос, о возможности предоставить странице в веб-контексте доступ к специфическим функциям, наподобие геолокации, записи данных в буфер обмена, доступ к кэшу приложения, к IndexedDB - к тому, чем обычно пользуются веб-страницы. По умолчанию веб-контекст в приложениях для Магазина Windows не имеют доступа к подобным возможностям уровня операционной системы. Например, создайте новое приложение по шаблону Пустое приложение в Visual Studio с этой единственной HTML-строкой в теле страницы default.html:

<iframe src="http://maps.bing.com" style="width:1366px; height: 768px"></iframe>

Затем, включите возможность Расположение (Location) в манифесте (я об этом забыл, когда проводил данный эксперимент!) и запустите приложение. Вы, как и ожидалось, увидите страницу Bing2). Однако, попытка использовать возможности по определению местоположения на данной странице - щелчок по значку локатора в левой части, у элемента "World", например - приведет к возникновению ошибки, вроде той, которая показана на Рис. 3.1.

Использование возможности, доступ к которой контролируется брокером, наподобие геолокации, внутри веб-контекста, приводит к появлению ошибки


увеличить изображение

Рис. 3.1.  Использование возможности, доступ к которой контролируется брокером, наподобие геолокации, внутри веб-контекста, приводит к появлению ошибки

Подобные возможности заблокированы, так как веб-контент, загруженный в iframe, может легко осуществить переход на любые другие страницы. Со страницы карт Bing, изображенной выше, пользователь может перейти на домашнюю страницу поисковой системы Bing, выполнить поиск, и затем перейти на любое количество непроверенных, потенциально опасных, страниц. В любом случае, эти страницы могут запрашивать доступ к критически важным ресурсам, и если это приведет к показу обычного вопроса для пользователя, который выводит приложение, пользователи могут быть обмануты при предоставлении подобного доступа.

К счастью, если вы хорошо попросите, Windows позволит вам включить эти возможности для веб-страниц, о которых знает приложение. Всё, что для этого нужно - письменные показания, под присягой, подписанные вами и шестнадцатью свидетелями… Ладно, я шучу! Вам просто нужно добавить то, что называется правила URI содержимого приложения (application content URI rules) в ваш манифест. Каждое правило заявляет о том, что контент, доступный по некоторому URI, известен приложению, и оно доверяет ему. Таким образом, этот контент может действовать от имени приложения. Вы так же можете исключать URI, что обычно используется для исключения определенных страниц, которые могли бы быть включены в другое правило.

Подобные правила создаются на закладке URI содержимого (Content URI) редактора манифеста в Visual Studio, как показано на Рис. 3.2. Каждое правило должно содержать точный URI, по которому может быть сделан запрос, такой, как http://www.bing.com/maps/ . Как только мы добавим это правило (как в полном упражнении для этой лекции, "ContentURI"), картам Bing разрешено будет использовать геолокацию. Когда они попытаются это сделать, отобразится диалоговое окно (Рис. 3.3), как тогда, когда приложение само пытается выполнить подобное действие. (Примечание. При выполнении в отладчике, пример "ContentURI" может показать исключение Отсутствие прав доступа (Permission Denied) при запуске. Если это произошло, нажмите Продолжить (Continue) в Visual Studio, так как это не влияет на запуск приложения вне отладчика).

Добавление URI содержимого в манифест приложения; содержимое текстовых полей сохраняется при сохранении манифеста. Кнопка Добавить новый URI (Add New URI) создаёт набор элементов управления, с помощью которых можно добавлять дополнительные правила


Рис. 3.2.  Добавление URI содержимого в манифест приложения; содержимое текстовых полей сохраняется при сохранении манифеста. Кнопка Добавить новый URI (Add New URI) создаёт набор элементов управления, с помощью которых можно добавлять дополнительные правила

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


увеличить изображение

Рис. 3.3.  Когда правило URI содержимого должным образом настроено, веб-контент в iframe действует так же, как приложение, что говорит о том, почему правила URI содержимого необходимы для защиты пользователя от страниц, неизвестных приложению, которые, в противном случае могли бы обмануть пользователя, получая доступ к критически важным ресурсам

Врезка: Несколько советов и предостережений по iframe

Так как мы говорим здесь об iframe, вот несколько дополнительных советов, которые вы можете счесть полезными при работе с этим элементом. Во-первых, чтобы предотвратить выделение, настройте стиль элемента с помощью -ms-user-select: none (http://msdn.microsoft.com/library/windows/apps/hh779846.aspx) или установите его свойство style.msUserSelect в значение none с помощью JavaScript. Во-вторых, некоторые веб-страницы содержат код, который препятствует их загрузке в iframe, в таком случае, страница будет открыта в стандартном браузере, а не в iframe. Если эта страница важна для вашего приложения, вам нужно поработать с владельцем страницы для создания альтернативной страницы, которая предназначена специально для вас. В-третьих, так как плагины в приложениях для Магазина Windows не поддерживаются, они так же не будут загружены для страницы, которая загружена в iframe. В итоге, загружать в приложение веб-содержимое, которое не принадлежит приложению - дело рисковое.

Далее, поддержка iframe не предназначена для того, чтобы позволить вам строить приложение исключительно из удалённых веб-страниц. Раздел 2.4. "Сертификационных требований к приложениям для Windows 8" (http://msdn.microsoft.com/library/windows/apps/hh694083.aspx), фактически, прямо запрещает приложения, которые являются веб-сайтами - основная функциональность приложения должна содержаться внутри приложения, то есть, подразумевается, что она не должна реализовываться средствами веб-сайта, загруженного в элемент iframe. Несколько ключевых причин для этого - то, что веб-сайты обычно не очень хорошо настроены для сенсорного взаимодействия (что нарушает требование пункта 3.5.) и часто не работают нормально в прикрепленном режиме (нарушение требования 3.6.). В итоге, чрезмерное использование веб-контента означает, что приложение не пройдёт сертификацию в Магазине Windows.

Обращение к содержимому из данных приложения: ms-appdata

Как мы уже видели, схема ms-appx[-web]:/// позволяет приложению ссылаться в элементах iframe на страницы, которые существуют внутри пакета приложения или в веб. Встаёт вопрос: может ли приложение сослаться на содержимое в локальной файловой системе, которое существует за пределами пакета, такое, как динамически создаваемый файл в папке данных приложения? Быть может, приложение использует протокол file:// для адресации содержимого или доступа к нему?

Как бы не хотелось мне сказать вам, что это просто работает, ответ несколько неоднозначен. Во-первых, протокол file:// полностью заблокирован при проектировании системы по различным причинам, связанным с безопасностью, даже для папок с данными приложения, к которым вы обладаете полным доступом. (Другие обычные протоколы так же не поддерживаются в URI iframe src.) К счастью, имеется заменитель, ms-appdata:///, который удовлетворяет часть потребностей. Внутри локального контекста приложения, ms-appdata:/// это - короткое имя для папки с данными приложения, где существуют папки локальных, перемещаемых, временных данных. Итак, если вы создали изображение, названное image65.png в папке локальных данных приложения, вы можете сослаться на него, используя команду ms-appdata:///local/image65.png, и похожим образом - для roaming и temp, где URI может быть включено в CSS, как, например, background.

К несчастью, есть оговорка, как это обычно бывает с контейнером приложения. Она заключается в том, что ms-appdata может быть использована только для ресурсов, а именно, это может быть атрибут src для элементов img (изображение), video (видео) и audio (аудио). Данный подход нельзя использовать для загрузки HTML-страниц, таблиц стилей CSS или JavaScript, не подходит он и для целей навигации (iframe, гиперссылки и так далее). Это так, потому что не представляется возможным создания суб-изолированной среды для подобных страниц, а без этого страница, загруженная с помощью ms-appdata://, получит полный доступ к приложению.

Можете ли вы выполнять динамическую генерацию страниц? Да: вам нужно загрузить содержимое файла и обработать его вручную, вставив в DOM посредством свойства innerHTML или чего-то подобного. Вы можете получить доступ к папкам с данными приложения, воспользовавшись API Windows.Storage.ApplicationData. Для загрузки и рендеринга полной HTML-страницы, нужно, чтобы вы обработали все внешние ссылки, разобрались бы со скриптами, но сделать это можно - если вы действительно этого хотите.

Похожий вопрос касается возможности динамической генерации и исполнения скриптов. Ответ снова подразумевает ограничения. Да, вы можете взять строку JavaScript и передать её в функцию eval или exeScript. Однако, учтите, что требования сертификации Магазина Windows прямо запрещают выполнять это со скриптами, полученными из удалённых ресурсов в локальном контексте (раздел 3.9. требований). Другое предостережение заключается в том, что для подобного кода применяется автоматическая фильтрация, которая предотвращает внедрение скриптов (и другого опасного кода) в DOM через свойства вроде innerHTML и outerHTML, и методы, такие, как document.write и DOMParser.parseFromString. Но есть ситуации, когда вы, как разработчик точно знаете, что делаете, наслаждаясь жонглированием бензопилами и горящими кинжалами, и, таким образом, хотите обойти подобные ограничения, особенно - используя библиотеки сторонних разработчиков (смотрите врезку ниже). Понимая это, Microsoft предоставляет механизм для того, чтобы сознательно всё это обойти: MSApp.execUnsafeLocalFunction. Подробности об этом вы можете найти в материале "Разработка безопасных приложений", (http://msdn.microsoft.com/library/windows/apps/hh849625.aspx), который, помимо этой темы, содержит информацию еще по некоторым вещам, которые я сюда не включал. Один из подразделов этого документа - касается различных вариаций атрибута sandbox для iframe. Так же, для того, чтобы лучше разобраться в этом, можете посмотреть пример "JavaScript iframe sandbox attribute sample" (http://code.msdn.microsoft.com/windowsapps/JavaScript-iframe-sandbox-0f077ece)

Как ни странно, WinJS, на самом деле, упрощает жонглирование опасными предметами! WinJS.Utilities.setInnerHTMLUnsafe, setOuterHTMLUnsafe, и insertAdjacentHTMLUnsafe- это "обёртки" для вызова методов DOM, которые могут отфильтровать опасное содержимое.

Рассказав всё это (вы ведь любите углубляться в детали?), давайте рассмотрим пример использования ms-appdata. Скорее всего, этот механизм вы будете широко использовать в своих приложениях.

Врезка: Библиотеки сторонних разработчиков в управляемой среде

В целом, приложения для Магазина Windows могут использовать библиотеки наподобие jQuery, Prototype, Dojo и так далее, как сказано в лекции 1. Тем не менее, существуют некоторые ограничения и оговорки.

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

Во-вторых, изменения в DOM API и ограничения пакета приложения могут повлиять на библиотеку. Например, библиотечные функции, использующие window.alert, работать не будут. Библиотека, кроме того, не может загружать другую библиотеку из удалённого источника в локальный контекст. Важно отметить, что всё в библиотеке, что подразумевает более высокий уровень доверия, чем обеспечивает контейнер приложения (например, открытый доступ к файловой системе) будет сопряжено с проблемами.

Самая распространённая проблема возникает, когда библиотека пытается добавить элементы или скрипты в DOM (как с помощью innerHTML), это - широко распространённый подход для веб-приложений, который, как правило, не разрешен внутри контейнера приложения. Например, попытка создать виджет для выбора даты jQuery ($("myCalendar").datepicker()) выдаст такого рода ошибку. Вы можете обойти эту проблему на уровне приложения, заключив вышеприведенный код в MSApp.execUnsafeLocalFunction, но это не решит проблем с внедрением кода, которое происходит из более глубокого уровня библиотеки. В случае с примером с jQuery, который здесь показан, элемент управления может быть создан, но щелчок по нему выдаст другую ошибку.

В итоге, вы можете использовать библиотеки сторонних разработчиков, понимая, что обычно они написаны, исходя из предположений, не всегда применимых к контейнеру приложения. Со временем, конечно, появятся версии библиотек, полностью совместимые с Windows 8.

"Here My Am!" с ms-appdata

Отлично! Пережив семь страниц эзотерики, давайте поиграем с настоящим кодом и вернемся к приложению "Here My Am!", которое мы написали в лекции 2. Эта программа использует удобный метод URL.createObjectURL для показа изображения, полученного с камеры в элементе img:

captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.done(function (capturedFile) {	
if (capturedFile) {	
that.src = URL.createObjectURL(capturedFile);	
}	
});	

Всё это замечательно: мы просто берем адрес, полагая, что изображение хранится где-то в то время когда мы сформировали URI. На самом деле, фотографии (и видео), захваченные с помощью соответствующего API камеры, просто хранятся во временном файле. Если вы установите точку останова в отладчике и посмотрите на capturedFile, вы увидите, что там находится уродливый путь к файлу, наподобие C:\Users\kraigb\AppData\Local\Packages\ ProgrammingWin8-JS-CH3- HereMyAm3a_5xchamk3agtd6\TempState\picture001.png. Ничего себе! Не выглядит дружественно, да и типичный пользователь вряд ли захочет видеть подобное.

В случае с приложением, подобным данному, скопируем данный временный файл в более подходящее место, для того, чтобы разрешить пользователю, например, выбирать одну из ранее сделанных фотографий (так, как мы сделаем в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Мы создадим копию файла в папке локальных данных приложения и используем ms-appdata для установки img src на данное расположение файла. Начнём с вызова captureUI.captureFileAsync, как и ранее.

//Для использования в promise-вызовах, 
// объединенных в цепочку 
var capturedFile = null;

captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)	
.then(function (capturedFileTemp) {	
//Убедитесь в том, что проверили правильность полученных данных; 
//это может быть null, если пользователь отменит операцию.
if (!capturedFileTemp) { throw ("no file captured"); }

Обратите внимание на то, что вместо вызова done для получения promise-результатов, мы используем then. Это происходит потому, что нам нужно построить цепочку из асинхронных операций и then позволяет информации об ошибках распространяться по цепочке, как мы увидим в следующем разделе. В любом случае, как только мы получим результат в captureFileTemp (файл расположен в подобной, странно выглядящей, папке), затем мы откроем или создадим папку "HereMyAm" внутри папки с локальными данными приложения. Это может быть сделано с помощью Windows.Storage.ApplicationData.current.localFolder, что позволяет нам получить объект Windows.Storage.StorageFolder, который даёт нам метод createFolderAsync:

//Для демонстрации использования ms-appdata usage, скопируем StorageFile в папку HereMyAm
//распложенную в папке appdata/local, и используем ms-appdata для того, чтобы 
//обратиться к ней. 
var local = Windows.Storage.ApplicationData.current.localFolder; capturedFile = capturedFileTemp;
return local.createFolderAsync("HereMyAm", Windows.Storage.CreationCollisionOption.openIfExists);
})
.then(function (myFolder) {
//Снова, проверяем правильность результата операции
if (!myFolder) { throw ("could not create local appdata folder"); }

Предполагая, что папка создана успешно, myFolder будет содержать другой объект StorageFolder. Затем мы используем его в качестве целевого параметра для файлового метода copyAsync, который, в качестве второго параметра, принимает имя файла. Для этого имени мы просто используем исходное имя, добавив к нему дату и время (заменив двоеточия на дефисы, чтобы имя файла было корректным):

//Присоединяем время создания файла (следует избегать коллизий, 
//но нужно заменить двоеточия)	
var newName = capturedFile.displayName + " - "	
+ capturedFile.dateCreated.toString().replace(/:/g, "-") + capturedFile.fileType;
return capturedFile.copyAsync(myFolder, newName);	
})	
.done(function (newFile) {	
if (!newFile) { throw ("could not copy file"); }

Так как это - последняя асинхронная операция в цепочке, мы используем метод promise-объекта done по причинам, о которых скоро поговорим. В любом случае, если копирование удалось, переменная newFile содержит объект StorageFile для копии, и мы можем сослаться на данный файл, используя URI ms-appdata:

lastCapture = newFile; 
//Сохраним для целей общего доступа	
that.src = "ms-appdata:///local/HereMyAm/" + newFile.name;
},	
function (error) {	
console.log(error.message);	
});	

Полный код вы можете найти в упражнении HereMyAm3a.

Конечно, мы можем использовать URL.createObjectURL с newFile, как ранее (убедившись в установке параметра { oneTimeOnly=true } для того, чтобы избежать утечек памяти). Несмотря на то, что это расходится с целями данного упражнения, работает это отлично (и нагрузка на память, в общем, та же самая, при использовании этих двух методов). На самом деле, нам нужно использовать этот подход, если мы скопируем изображения в библиотеку изображений пользователя. Для того, чтобы это сделать, просто замените Windows.Storage.ApplicationData.current.localFolder на Windows.Storage.KnownFolders.picturesLibrary и объявите возможность Библиотека изображений (Pictures Library) в манифесте. Оба API возвращают StorageFolder, поэтому оставшийся код выглядит так же, за исключением того, что мы используем URL.createObjectURL, так как ни ms-appdata://, ни file:// мы не можем использовать для доступа к библиотеке изображений. Упражнение HereMyAm3a содержит данный код в комментариях.

Последовательные асинхронные операции: объединение отложенных результатов в цепочку

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

Хотя, на первый взгляд это может выглядеть странным, подобный подход, на самом деле, является наиболее распространённым шаблоном для работы с последовательными асинхронными операциями, так как он работает лучше, чем более очевидный подход с использованием вложенных конструкций. Вложенность подразумевает вызов следующего асинхронного API внутри обработчика завершения предыдущего, каждый из них завершается оператором done. Вот как код из предыдущего примера может быть переписан с использованием такого подхода (посторонний код удалён для упрощения примера):

captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.done(function (capturedFileTemp) {
//...
local.createFolderAsync("HereMyAm", ...)
.done(function (myFolder) {
//...
capturedFile.copyAsync(myFolder, newName)
.done(function (newFile) {
})
})
});

Единственное преимущество такого подхода заключается в том, что каждый обработчик завершения будет иметь доступ ко всем переменным, объявленным ранее. А вот недостатков у него довольно много. С одной стороны, здесь, между асинхронными вызовами, обычно достаточно много промежуточного кода, что делает структуру выглядящей неопрятно. Важнее то, что обработка ошибок значительно усложняется. Когда promise-вызовы вложены друг в друга, обработку ошибок нужно проводить на каждом из уровней. Если ошибка возникнет на одном из внутренних уровней, обработчик на внешнем уровне не сможет её обработать. Таким образом, каждый promise-вызов нуждается в собственном обработчике ошибок, что превращает базовую структуру подобного кода в нечто, напоминающее спагетти:

captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.done(function (capturedFileTemp) {
//...
local.createFolderAsync("HereMyAm", ...)
.done(function (myFolder) {
//...
capturedFile.copyAsync(myFolder, newName)
.done(function (newFile) {
},
function (error) {
})
},
function (error) {
});
},
function (error) {
});

Не знаю, как вы, а я теряюсь во всех этих } и ) (хотя очень старался вспомнить занятия по LISP в колледже), здесь непросто увидеть, какая функция обработки ошибок применяется к конкретному асинхронному вызову. Объединение promise-вызовов в цепочку решает все эти проблемы, в обмен на небольшую плату в виде необходимости объявления нескольких дополнительных переменных за пределами цепочки. При объединение в цепочку, вы выполняете команду return для следующего promise-объекта в каждом из обработчиков завершения, вместо того, чтобы дополнять его вызовом done. Это позволяет вам выравнивать все асинхронные вызовы лишь однажды, и даёт эффект распространения ошибок по цепочке. Когда в promise-вызове произошла ошибка, вы видите, что то, что возвращается, является promise-объектом, и если вы вызовете метод then (но не done - смотрите следующий раздел), снова будет возвращён другой promise-объект, содержащий ошибку. В результате, любые ошибки быстро доходят по цепочке до первого доступного обработчика ошибок, что позволяет вам иметь лишь один обработчик ошибок в самом конце:

captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.then(function (capturedFileTemp) {
//...
return local.createFolderAsync("HereMyAm", ...);
})
.then(function (myFolder) {
//...
return capturedFile.copyAsync(myFolder, newName);
})
.done(function (newFile) {
},
function (error) {
})

Для моих глаз (и моего ума) эта структура кода кажется более чистой - и такой код легче отлаживать и поддерживать. Если хотите, вы даже можете завершить цепочку вызовом done(null, errorHandler), заменив предыдущий done на then:

captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
//...	
.then(function (newFile) {	
})	
.done(null, function (error) {	
})	
})	

И, наконец, немного об отладке promise-вызовов, объединенных в цепочку (или вложенных, если уж на то пошло). Каждый шаг включает асинхронную операцию, поэтому вы не можете просто применить пошаговое исполнение, как это делается с синхронным кодом (в противном случае, вы окажетесь глубоко в WinJS). Вместо этого, установите точку останова на первой строке внутри каждого обработчика завершения и на первой строке функции обработки ошибок, которая находится в конце. Когда каждая из точек останова сработает, вы можете пошагово исполнить обработчик завершения. Когда вы дойдёте до следующего асинхронного вызова, нажмите на кнопку Далее (Continue) в Visual Studio, в итоге сможет выполниться асинхронная операция, после которой сработает точка останова в следующем обработчике завершения (или точка останова в обработчике ошибок).

Обработка ошибок внутри promise-вызовов: then против done

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

Это приводит нас к различию между then и done. Во-первых, then возвращает еще один promise-объект, тем самым позволяя объединять команды в цепочки, в то время как done возвращает undefined, поэтому он должен быть в конце цепочки. Во-вторых, если исключение возникает внутри асинхронной операции с методом then и на данном уровне нет обработчика ошибок, информация об ошибке сохраняется в promise-объекте, который возвращает then. В противоположность этому, если done видит исключение и не имеет обработчика ошибок, он направляет исключение в цикл событий (event loop) приложения. Оно обходит любые локальные (синхронные) блоки try/catch, хотя вы можете перехватить их с помощью обработчиков WinJS.Application.onerror и window.onerror. (Последний получит ошибку, если первый её не обработал). Если вы этого не сделаете, приложение будет остановлено и информация об ошибке будет отправлена в Магазин Windows и появится на информационной панели. По этой причине мы рекомендуем, чтобы вы реализовывали обработчик WinJS.Application.onerror.

На практике, это означает, что если вы завершите цепочку promise-вызовов then, а не done, все исключения в цепочке будут потеряны и вы никогда не узнаете о проблеме. Это может привести приложение к неопределенному состоянию и привести к большим проблемам позднее. В итоге, если только вы не собираетесь передавать последний promise-объект в цепочке в другой участок кода, в котором будет вызвана команда done (вы можете так поступить, если пишете библиотеку, которая возвращает promise-объект), всегда используйте done в конце цепочки, даже для единичной асинхронной операции1).

С promise-объектами вы можете делать и многое другое, кстати, вроде комбинирования их, отмены и так далее. Мы вернемся к этому в конце данной лекции.

Тестовый вывод, отчёты об ошибках и средство просмотра событий

Когда идёт разговор об исключениях и обработке ошибок, разработчиков обычно огорчает то, что команды window.prompt и window.alert не доступны приложениям для Магазина Windows в качестве средств быстрой отладки. К счастью, у вас есть два других хороших инструмента для этих целей. Первый - это Windows.UI.Popups.MessageDialog, который обычно используется для того, чтобы выводить предупреждения пользователям. Второй - это console.log, как показано ранее, отправляющий текст в панель вывода Visual Studio. Эти сообщения, кроме того, можно записать в качестве лог-файла событий Windows, как мы скоро увидим2).

Другая функция DOM API, которой вам может захотеться воспользоваться - это window.close. Вы можете пользоваться ей во время разработки, но в опубликованном приложении Windows воспринимает её как аварийное завершение программы и генерирует в ответ отчёт об ошибке. Этот отчёт появится в информационной панели Магазина Windows для вашего приложения, с сообщением о том, что вам не следует пользоваться данной функцией! В конце концов, приложения для Магазина Windows не должны предоставлять собственных средств для закрытия приложения, как описано в разделе 3.6. сертификационных требований.

Однако, возможна ситуация, когда опубликованное приложение должно завершить работу в ответ на неисправимое происшествие. Хотя вы можете использовать для этого команду window.close, лучше воспользоваться командой MSApp.terminateApp, так как она позволяет вам, кроме того, включить информацию о произошедшей ошибке. Эти подробности будут показаны в информационной панели Магазина Windows, упрощая диагностику проблемы.

В дополнение к информационной панели Магазина Windows, вам следует ознакомиться со средством просмотра событий Windows (Windows Event Viewer)3). Это место, где могут быть записаны отчёты об ошибках, консольные журналы и сведения о необработанных исключениях (которые завершают приложения без предупреждения).

Для того, чтобы активировать эту возможность, для начала вам нужно перейти к разделу Журналы приложений и служб, развернуть ветку Microsoft > Windows > AppHost, щёлкнуть левой кнопкой мыши по элементу Администратор (Admin) для того, чтобы выделить его (это важно), после чего щёлкнуть по элементу Администратор правой кнопкой мыши и выбрать Вид > Отобразить аналитический и отладочный журналы (View > Show Analytic and Debug logs) для включения вывода полной информации, как показано на Рис. 3.4. Это включит отслеживание ошибок и исключений. Затем щёлкните правой кнопкой пункт AppTracing (так же в разделе AppHost) и выберите пункт Включить журнал (Enable Log). Это позволит отслеживать ваши вызовы console.log, так же, как и другую диагностическую информацию, поступающую от хост-процесса приложения.

События хост-процесса приложения, такие, как необработанные исключения и ошибки загрузки, можно найти в средстве просмотра событий


увеличить изображение

Рис. 3.4.  События хост-процесса приложения, такие, как необработанные исключения и ошибки загрузки, можно найти в средстве просмотра событий

Мы уже говорили о диалоговом окне Исключения в Visual Studio в лекции 2. Вернитесь к Рис. 2.16. Для каждого типа JavaScript-исключений это диалоговое окно предоставляет два флага, названные Вызванное (Thrown) и Не обработанное пользовательским кодом (User-unhandled). Установка флага Вызванное приведет к отображению диалогового окна в отладчике (Рис. 3.5), когда будет вызвано исключение, независимо от того, было ли оно обработано и до того, как сработают любые из ваших обработчиков событий. Если у вас есть обработчики событий, вы можете без проблем нажать на кнопку Продолжить (Continue) в диалоговом окне и ошибка будет передана вашим обработчикам ошибок. (В противном случае работа приложения завершится). Если вы, вместо этого нажмёте на кнопку Остановить отладку (Break), вы можете увидеть подробности об исключении в окне Локальные (Locals), как показано на Рис. 3.6.

Диалоговое окно исключения в Visual Studio. Как показывает диалоговое окно, можно безопасно нажать на кнопку Продолжить (Continue), если в вашем приложении есть обработчик ошибок; в противном случае работа приложения завершится. Обратите внимание на флаг в этом окне, который позволяет быстро включить опцию Вызванное (Thrown) для данного типа исключений в диалоговом окне Исключения


Рис. 3.5.  Диалоговое окно исключения в Visual Studio. Как показывает диалоговое окно, можно безопасно нажать на кнопку Продолжить (Continue), если в вашем приложении есть обработчик ошибок; в противном случае работа приложения завершится. Обратите внимание на флаг в этом окне, который позволяет быстро включить опцию Вызванное (Thrown) для данного типа исключений в диалоговом окне Исключения

Информация в окне Visual Studio Локальные (Locals), когда вы останавливаете работу приложения при исключении


увеличить изображение

Рис. 3.6.  Информация в окне Visual Studio Локальные (Locals), когда вы останавливаете работу приложения при исключении

Опция Не обработанное пользовательским кодом (User-unhandled) (включенная для всех типов исключений по умолчанию) отобразит похожее диалоговое окно всякий раз, когда исключение попадёт в цикл событий, показывая то, что оно не было обработано в функции обработки ошибок приложения (в "пользовательском" коде - с точки зрения системы).

Обычно вы включаете параметр Вызванное только для тех исключений, которые вы собираетесь обрабатывать. Включение их всех может привести к сложностям при работе с приложением. Вы можете сделать это в качестве эксперимента и затем оставить данный параметр установленным лишь для тех исключений, которые вы ожидаете перехватить. Оставьте параметр Не обработанное пользовательским кодом для всех остальных исключений. На самом деле, если у вас нет особых причин не делать этого, убедитесь в том, что параметр Не обработанное пользовательским кодом включен у группы Ошибки времени выполнения JavaScript (JavaScript Runtime Exceptions), так как эта установка включает в себя все исключения, даже не перечисленные. При таком подходе вы можете перехватить (и исправить) любое исключение, которое может внезапно завершить работу приложения, а это кое-что из того, с чем вашим пользователям никогда не следует сталкиваться.

Активация приложения

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

Основы брендинга приложений: экран-заставка и другие визуальные элементы

Говоря об активации приложения, мы, на самом деле, должны сделать шаг назад, во время до того момента, когда будет загружен хост-процесс приложения, к тому моменту, когда пользователь нажал на плитку приложения на Начальном экране или когда ваше приложение было запущено каким-то другим способом. Первое, что происходит, еще до того, как загружается или исполняется код приложения, это - показ Windows экрана-заставки приложения, состоящего из изображения и фонового цвета, которые заданы в вашем манифесте.

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

Экран-заставка, вместе с плиткой вашего приложения, это один из весьма важных путей для брендирования вашего приложения, поэтому убедитесь в том, что ваш художник (или художники) уделили этим вопросам максимум внимания. Есть и другие графические элементы и установки в манифесте, которые так же играют роль в брендинге и в общем виде приложения в системе, как показано ниже, в таблице. Особо отметьте то, что шаблоны в Visual Studio и Blend предоставляют некоторые материалы-заполнители по умолчанию, которые совершенно непривлекательны. Поэтому дайте прямо сейчас торжественную клятву, что вы никогда не будете загружать приложения в Магазин, когда в проекте всё еще присутствуют эти материалы! (За дополнительными сведениями обратитесь к документу: "Руководство и контрольный список для экранов-заставов", (http://msdn.microsoft.com/library/windows/apps/hh465338.aspx).

Вы можете видеть, что в таблице перечислено по несколько размеров для различных изображений, заданных в манифесте для поддержки различной плотности пикселей: для масштабирования в 100%, 140%, 180%, и даже несколько для 80% (не пренебрегайте последним, он, как правило, используется для большинства настольных мониторов). И, в то время, как вы можете просто предоставить одно изображение в масштабе 100% для каждого из перечисленных элементов, практически гарантированно, что версия с увеличенным масштабом графических элементов будет выглядеть не очень хорошо. Поэтому, почему бы не сделать ваше приложение выглядящим наилучшим образом? Уделите время на то, чтобы осознанно создать каждый отдельный графический элемент.

Таблица 3.2.
Закладка манифестаРазделЭлементИспользованиеРазмеры изображений
100%140%180%
Упаковкаn/aЗначокИзображение плитки/значка, используемое для приложения на странице описания в Магазине Windows.50x5070x7090x90
Интерфейс приложенияn/aОтображаемое имя пакетаПоявляется при просмотре Начального экрана в режиме "все приложения" , в результатах поиска, при использовании чудо-кнопки Параметры, в Магазине Windows.n/an/an/a
ПлиткаЗначокКвадратное изображение плитки150x150 (+80% масштаб 120x120)210x210270x270
Широкий значокНеобязательное изображение для широкой плитки. Если присутствует, отображается по умолчанию, но пользователь, если захочет, может использовать вместо него квадратное изображение. 310x150 (+80% масштаб 248x120)434x210558x270
Мелкий значокПлитка, которая используется при уменьшенном просмотре Начального экрана, при просмотре в режиме "все приложения", в панелях чудо-кнопок Поиск и Общий доступ, если приложение поддерживает соответствующие контракты в качестве целевого. Кроме того, используется на плитке приложения, если вы выбрали опцию показа логотипа приложения в левом нижнем углу плитки вместо его названия.30x30 (+80% масштаб 24x24)42x4254x54
Показывать имяЗадаёт, следует ли показывать имя приложения на плитке приложения (на всех, не показывать, только на стандартном или на широком значке). Установите этот параметр в значение "Нет значков", если плитка вашего приложения включает имя приложения.n/an/an/a
Краткое имяНеобязательное. Если задано, используется в качестве имени приложения на плитке, заменяя Отображаемое имя пакета, так как это имя может быть слишком длинным для квадратной плитки.n/an/an/a
Текст переднего планаЦвет текста, которым выведено имя приложения на плитке, если применимо (смотрите Показывать имя). Возможны варианты Светлый (Light) и Тёмный (Dark). Соотношение контраста между этим текстом и цветом фона должен равняться 1.5.n/an/an/a
Цвет фонаЦвет, используемый для прозрачных областей любых изображений на плитке, кроме того, задаёт фон по умолчанию для дополнительных плиток, фон для окон уведомлений, цвет кнопок в диалоговых окнах приложения, цвет границ для тех случаев, когда приложение обеспечивает функциональность контракта средства выбора файлов или контактов, цвет заголовков в панели параметров, цвет страницы приложения в Магазине Windows. Кроме того, задаёт фоновый цвет для экрана-заставки, если он не задан отдельно. n/an/an/a
УведомленияЭмблемаОтображается около уведомления индикатора событий для идентификации приложения на экране блокировки (редко, так как это требует объявления дополнительных возможностей)24x2433x3343x43
ЗаставкаЗаставкаКогда приложение запускается, это изображение выводится в центре экрана на фоне, цвет которого задаёт Цвет фона. Если нужно, изображение может использовать прозрачность.620x300868x4201116x540
Цвет фонаЦвет, который заполняет основную область экрана-заставки. Если не задан, вместо него будет использован Цвет фона из Интерфейса приложенияn/an/an/a

Обратите внимание на то, что в таблице 80% масштаб указан для графических элементов, используемых в особых случаях, таких, как режим низкого DPI (обычно, когда DPI меньше 130 и разрешение ниже, чем 2560х1440) и изображения в таком масштабе следует предоставлять вместе с другими. Кроме того, обратите внимание на дополнительные графические элементы, помимо Значка (Packaging Logo) (первый элемент в таблице), которые вам понадобятся перед отправкой приложения в Магазин Windows. Подробности вы найдете в материале "Выбор изображений для вашего приложения" (http://msdn.microsoft.com/library/windows/apps/hh846296.aspx), в разделе "Рекламные изображения".

Когда вы сохраняете эти файлы, добавьте .scale-80, .scale-100, .scale-140, и .scale-180 к именам файлов перед расширениями, как, например, в имени: splashscreen.scale-140.png. Это позволит вам и в манифесте, и в других местах приложения ссылаться на изображение, используя лишь базовое имя, такое, как splashscreen.png, и Windows автоматически загрузит подходящий вариант для текущего масштаба. В противном случае она будет искать изображение без суффикса. И не нужно дополнительного кода! Это показано в упражнении HereMyAm3b, где я добавил всю необходимую для брендинга графику (с некоторым дополнительным текстом на каждом рисунке для того, чтобы показать масштаб). Для того, чтобы протестировать эти различные графические элементы, используя кнопку Изменить разрешение (Set resolution/scaling) в симуляторе - обратитесь к Рис. 2.5 во второй лекции. Вы можете выбирать различные плотности пикселей на экране размером 10,6" (1366 x 768 =100%, 1920 x 1080 =140%, и 2560 x 1440 = 180%). Кроме того, вы увидите, что 80%-й масштаб используется при других установках экрана, в том числе - на 23" и 27". Во всех случаях, установки влияют на то, какое изображение будет использовано на Начальном экране и на экране-заставке, но обратите внимание на то, что вам может понадобиться выйти и перезапустить симулятор для того, чтобы изменение масштаба возымело действие.

Кроме того, вы должны знать о том, что полноцветные изображения фотографического качества не очень хорошо поддаются качественному уменьшению (Значок (Store Logo, Эмблема магазина), Мелкий значок (Small Logo, Мелкая эмблема)). Это одна из причин того, что данные значки обычно имеют простой дизайн в стиле Магазина Windows, который, кроме того, способствует уменьшению их размеров при сжатии. Это - отличное соображение, следуя которому можно сделать размер пакета вашего приложения меньше, когда вы создаёте больше версий изображений для различных контрастов и языков. Больше об этом будет в лекции 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой".

Совет. Вы можете заинтересоваться еще двумя ресурсами по брендингу. Это - материал "Создание фирменной символики в приложениях Магазина Windows" (http://msdn.microsoft.com/library/windows/apps/hh465418.aspx) из документации (посвящен дизайну) и пример "CSS-стилизация: брендирование вашего приложения (CSS styling: branding you app sample)" (http://code.msdn.microsoft.com/windowsapps/App-Branding-sample-9f87b7a2), который показывает CSS-вариации и динамическое изменение активной таблицы стилей.

Последовательность событий при активации

Так как хост-процесс приложения построен на тех же самых механизмах обработки и рендеринга, что и Internet Explorer, общая последовательность событий при активации более или менее похожа на последовательность, характерную для веб-приложений и наблюдаемую в браузере. На самом деле, скорее "более", чем "менее"! Когда вы запускаете приложение с помощью его плитки, вот что происходит с точки зрения Windows:

  1. Windows отображает экран-заставку, используя информацию из манифеста приложения.
  2. Windows запускает хост-процесс приложения, идентифицирует приложение для запуска.
  3. Хост-процесс приложения получает параметры стартовой страницы приложения (смотрите закладку Интерфейс приложения в редакторе манифеста), которые задают HTML-страницу для загрузки.
  4. Хост-процесс приложения загружает стартовую страницу вместе со связанной CSS-таблицей и скрипт (откладывает загрузку скрипта, если так указано в разметке). Важно, чтобы все файлы имели правильную кодировку для улучшения производительности загрузки (смотрите врезку ниже).
  5. Запускается событие document.DOMContentLoaded. Вы можете использовать его для того, чтобы произвести дальнейшую инициализацию, в особенности, связанную с DOM (используется редко).
  6. Запускается событие Windows.UI.WebUI.WebUIApplication.onactivated. Это обычно происходит, когда вы выполните все стартовые задачи, создадите экземпляр WinJS и элементов управления, инициализируете состояние и так далее.
  7. Экран-заставка скрывается, когда обработчик события activated завершится (если приложение не запросило отложенную операцию, как рассказано далее, в разделе "Задержанная активация").
  8. Запускается событие body.onload. Обычно оно не используется в приложениях для Магазина Windows, хотя оно может использоваться кодом из библиотек сторонних разработчиков.

Важной особенностью является то, что приложение снова может быть активировано по множеству различных причин, таких, как контракт или ассоциация, даже тогда, когда оно уже запущено. Как мы увидим в дальнейших лекциях, загружаемая страница (шаг 3), может варьироваться при реализации контракта, и если определенная страница уже загружена, она получит только событие Windows.UI.WebUI.WebUIApplication.onactivated, не получив другие.

Сейчас, однако, давайте сконцентрируемся на работе с этим базовым процессом загрузки и, так как вы обычно выполняете работу по инициализации внутри события activated, рассмотрим его структуру поближе.

Врезка: Кодировка файлов для лучшей производительности при загрузке

Для того, чтобы оптимизировать генерирование байт-кода при обработке HTML, CSS и JavaScript-файлов, Магазин Windows требует, чтобы все .html, .css и .js-файлы были сохранены в кодировке UTF-8. Это установлено по умолчанию для всех файлов, создаваемых в Visual Studio или в Blend. Если вы импортируете активы из других источников, проверьте их кодировку. В диалоге Сохранить как (Save As) в Visual Studio (в Blend сейчас нет этой возможности), выберите Сохранить с кодировкой (Save with Encoding) и установите кодировку в Юникод (UTF-8, с сигнатурой), кодовая страница 65001 (Unicode (UTF-8 with signature) - Codepage 65001). Комплект сертификации приложений для Windows (Windows App Certification Kit) выдаст предупреждение, если обнаружит файлы не в этой кодировке.

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

Ветви кода активации

Как мы видели в лекции 2, новый проект, создаваемый в Visual Studio или в Blend даёт вам следующий код в файле js/default.jd (некоторые комментарии удалены):

(function () { "use strict";

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: Это приложение было вновь запущено. Инициализируйте
// приложение здесь.
} else {
// TODO: Это приложение было вновь активировано после приостановки.
// Восстановите состояние приложения здесь.
}
args.setPromise(WinJS.UI.processAll());
}
};

app.oncheckpoint = function (args) {
};

app.start();
})();

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

Обратите внимание на то, как мы не обрабатываем напрямую любые события, запускаемые Windows, наподобие DOMContentLoaded или Windows.UI.WebUI.WebUIApplication.onactivated. Может быть, мы просто игнорируем эти события? Вовсе нет: один из удобных сервисов, которые WinJS предоставляет посредством WinJS.UI.Application - это упрощённая структура активации и других событий времени жизни приложения. Совершенно необязательно, но очень полезно.

В случае со start, например, происходит пара вещей. Во-первых, объект WinJS.Application прослушивает различные события, которые исходят из различных источников (DOM, WinRT и других) и собирает их в один объект, с которым вы регистрируете ваши собственные обработчики. Во-вторых, когда WinJS.Application принимает события активации, он не просто передает их в обработчики приложения, так как таких обработчиков, на самом деле, может и не быть. Поэтому он устанавливает эти события в очередь до тех пор, пока приложение не сообщит о том, что оно действительно готово, вызывая start. В этот момент WinJS проходит по очереди и запускает эти события. Вот и всё, что нужно сделать.

Как показывает код из шаблона, приложения обычно выполняют большую часть работы по инициализации в событии activated, где существует несколько потенциальных ветвей кода, прохождение по которым зависит от args.details (объект IActivatedEventArgs (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.iactivatedeventargs.aspx)). Если вы посмотрите документацию по WinJS.Application.onactivated (http://msdn.microsoft.com/library/windows/apps/br212679.aspx), вы увидите, что реальное содержимое args.details зависит от конкретного вида активации. Все виды активации, однако, имеют три общих свойства:

Таблица 3.3.
args.details СвойстваТип (в Windows.Application-Model.Activation)Описание
KindActivationKind (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.activationkind.aspx) Причина активации. Возможные значения launch (наиболее типично); search, shareTarget, file, protocol, fileOpenPicker, fileSavePicker, contactPicker, и cachedFileUpdater (для обслуживания контрактов); и device, printTask, Settings, cameraSettings (обычно используется приложениями, работающими с устройствами). Для каждого поддерживаемого типа активации, приложение будет иметь соответствующий путь инициализации.
previousExecutionStateApplicationExecutionState (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.applicationexecutionstate.aspx) Состояние приложения до данной активации. Возможные значения notRunning, running, suspended, terminated, и closedByUser. Обработка случая terminated наиболее распространена, так как в данном случае вам потребуется восстановить ранее сохраненное состояние сеанса работы с приложением (смотрите "Переход между событиями жизненного цикла приложений").
splashScreensplashScreen (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.splashscreen.aspx) Содержит событие ondismissed для тех случаев, когда экран-заставка не отображается. Так же содержит свойство imageLocation (Windows.Foundation.Rect) с координатами отображения экрана-заставки, как отмечено в разделе "Расширенные экраны-заставки"

Дополнительные свойства предоставляют соответствующие данные по активации. Например, свойство launch предоставляет titleId и arguments от дополнительных плиток (смотрите лекцию 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"). Тип активации search (следующий наиболее часто используемый) предоставляет queryText и language, protocol предоставляет uri и так далее. Мы увидим, как использовать многие из этих свойств в соответствующем им контексте, и иногда они применимы не к default.html, а к другим страницам. То, что включено в шаблон (и то, что мы уже используем в приложении вроде "Here My Am!"), в основном, касается обработки обычного процесса запуска приложения с плитки (или внутри отладчика Visual Studio).

События WinJS.Application

WinJS.Application не связан только с активацией - его цель - централизовать события от нескольких различных источников и превратить их в собственные события. Опять же, это позволяет приложению прослушивать события из одного источника (назначая обработчик с помощью addEventListener(<event>) или on<event>, поддерживается и то, и другое). Вот полный перечень этих событий и их источников (если поставлено в очередь, событие вызывается в WinJS.Application.start):

С большинством из этих событий (за исключением error и settings), args, которые вы получите, содержат метод, который называется setPromise. Если вам нужно произвести любую асинхронную операцию (вроде XmlHttpRequest), вы можете получить promise-объект для этой работы и передать его в setPromise вместо того, чтобы самостоятельно вызывать его then или done. WinJS, таким образом, не обработает следующее событие в очереди до тех пор, пока получение данного отложенного результата не будет выполнено. Отметим, чтобы быть честными, что нет разницы между этим подходом и обычным вызовом done у promise-объекта самостоятельно внутри событий loaded, ready и unload. А вот в случае с activated и checkpoint (в особенности в состоянии suspending) разница есть, так как Windows, в противном случае, полагает, что вы сделали всё, что нужно, как только вы возвратились из обработчика; подробнее об этом - в разделе "Задержанная активация". В итоге, если у вы выполняете асинхронные задачи в обработчиках этих событий, лучше всего использовать setPromise. Так как WinJS.UI.processAll - это сама по себе асинхронная операция, шаблон заключает её в оболочку из setPromise, в итоге, экран-заставка не исчезнет до тех пор, пока не буду созданы экземпляры элементов управления WinJS.

Я думаю, что вы сочтёте WinJS.Application удобным инструментом для ваших приложений, и это средство, кроме того, предоставляет еще некоторые возможности, как описано здесь: "Пространство имен WinJS.Application" (http://msdn.microsoft.com/library/windows/apps/br229774.aspx). Например, оно предоставляет свойства local, temp, roaming, и sessionState которые удобны при управлении состоянием приложения, как мы увидим позже в этой лекции и в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript".

Еще нужно сказать о методах queueEvent и stop. Метод queueEvent помещает событие в очередь, оно попадёт на диспетчеризацию, когда существующие элементы выйдут из очереди, любым прослушивателем события, который вы установили с помощью WinJS.Application. События просто идентифицируются по строке, в итоге, вы можете ставить в очередь события с любым именем, которое вам нравится, и вызывать WinJS.Application.addEventListener с тем же самым именем в любом месте приложения. Это можно использовать для централизации пользовательских событий, вызываемых и при старте и в другое время в процессе выполнения приложения без создания отдельной глобальной функции для подобных целей. Кроме того, это мощное средство, с помощью которого раздельно объявленные, независимые компоненты, могут вызывать события, которые агрегирует один обработчик. (В качестве примера использования queueEvent смотрите Сценарий 2 в примере "Модель приложения" (http://code.msdn.microsoft.com/windowsapps/ApplicationModel-Sample-4be6575d)).

Что касается stop, это событие предоставляется для помощи во время модульного тестирования, во время которого вы можете симулировать различные последовательности активации, не перезапуская приложение и так или иначе симулировать нужные условия при перезапуске. Когда вы вызываете stop, WinJS удаляет прослушиватели событий, очищает очередь событий и очищает объект sessionState, но приложение продолжает работать. Затем вы можете вызвать queueEvent для того, чтобы заполнить очередь событий теми событиями, которыми хотите, и затем снова вызвать start для того, чтобы обработать эту очередь. Этот процесс может повторяться столько раз, сколько нужно.

Расширенные экраны-заставки

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

Честно говоря, до тех пор, пока пользователь держит ваше приложение на переднем плане и не переключается, Windows даёт приложению всё необходимое ему время. Но когда пользователь переключается на Начальный экран или на другое приложение, вы ограничены 15-ю секундами. Если ваше приложение не на переднем плане, Windows даёт приложению 15 секунд на то, что приложение выполнит то, что есть в app.start и вызовет событие activated, в этот момент домашняя страница должна быть отрисована. В противном случае, бабах! Windows автоматически завершает ваше приложение.

Первое условие, конечно, заключается в том, чтобы оптимизировать процесс загрузки для того, чтобы он был как можно более быстрым. Но, всё таки, иногда приложениям, на самом деле, нужно больше, чем 15 секунд для того, чтобы запуститься, в особенности при первом запуске после установки, в итоге, ему следует дать пользователю знать о том, что что-то происходит. Например, пакет приложения может содержать изрядное количество сжатых данных, когда оно загружено из Магазина Windows, и которые должны быть распакованы в локальную файловую систему при первой загрузке, в итоге, последующие запуски будут происходить гораздо быстрее. Многие игры поступают так с графикой и другими ресурсами, оптимизируя локальное хранение данных под характеристики устройства. Другие приложения могут заполнять локальную IndexedDB из данных, хранящихся в JSON-файле или загружать и кэшировать данные из онлайнового сервиса.

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

Во всех эти случаях, всегда, когда приложение рискует превысить лимит в 15 секунд, вам понадобится реализовать расширенный экран-заставку (extended splash screen). Это подразумевает скрытие вашей реальной домашней страницы за другим элементов div, который выглядит точно так же, как системный экран-заставка, однако, находится под контролем приложения, и, в итоге, способен отображать индикаторы прогресса или другие элементы интерфейса, пока происходит инициализация приложения.

В целом, Microsoft рекомендует, чтобы расширенный экран-заставка совпадал с системным для того, чтобы избежать мерцания и резкого изменения изображения. (Смотрите материал "Руководство и контрольный список для экранов-заставок" (http://msdn.microsoft.com/library/windows/apps/hh465338.aspx)). В этот момент многие приложения просто добавляют индикатор прогресса и сообщение вроде: "Пожалуйста, возьмите напиток, выполните несколько физических упражнений или насладитесь несколькими минутами медитации, пока мы всё загрузим". Соответствие системному экрану-заставке, однако, не означает, что расширенный экран-заставка должен действовать подобным образом. Многие приложения, стартуют с копией системного экрана-заставки и затем используют анимированную графику на одной из его сторон, чтобы освободить место для других элементов. Другие приложения плавно скрывают существующие изображения и запускают видео.

Создание плавного перехода - это цель объекта args.detail.splashScreen, включенного в событие activated. Этот объект (смотрите: "Windows.ApplicationModel.Activation.SplashScreen" (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.splashscreen.aspx)) содержит свойство imageLocation (типа Windows.Foundation.Rect), которое содержит данные о расположении и размере изображения для экрана-заставки. Так как ваше приложение может быть запущено на устройствах с экранами различных размеров, это подсказывает вам где расположить то же самое изображение на вашей странице, где начать анимацию, и/или где поместить объекты вроде сообщений и индикаторов прогресса, связанных с данным изображением.

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

Для того, чтобы увидеть пример расширенного экрана-заставки, обратитесь к примеру "Экран-заставка" (http://code.msdn.microsoft.com/windowsapps/Splash-screen-sample-89c1dc78). Еще одна деталь, о которой важно упомянуть, заключается в том, что так как экран-заставка - это обычная страница в вашем приложении, она может быть помещена в различные режимы просмотра - в такие, как режим прикрепленного просмотра. В итоге, как и со всеми остальными страницами вашего приложения, убедитесь в том, что расширенный экран-заставка поддерживает эти состояния!

Задержанная активация

Как упоминалось ранее, как только вы вернетесь из события activated, Windows считает, что вы сделали всё, что нужно, при старте приложения. По умолчанию, таким образом, Windows удалит свой экран-заставку и сделает домашнюю страницу приложения видимой. Но что, если вам нужно завершить одну или более асинхронных операций до того, как домашняя страница будет действительно готова, например, завершить WinJS.UI.processAll?

Это, снова, то, для чего существует метод args.setPromise внутри события activated. Если вы установили для асинхронной операции отложенный результат, использовав setPromise, Windows будет ждать до тех пор, пока отложенный результат не будет получен, и только тогда скроет экран-заставку. Шаблоны используют этот метод для того, чтобы экран-заставка отображался до тех пор, пока не будет завершен WinJS.UI.processAll.

Так как setPromise просто ожидает окончание выполнения отдельной отложенной операции, как обработать несколько асинхронных операций? Вы можете сделать это парой способов. Во-первых, если вам нужно управлять последовательностью подобных операций, вы можете связать их в цепочку, так, как мы уже это умеем - просто убедитесь в том, что в конце цепочки promise-объектов находится тот, который служит аргументом для setPromise - не вызывайте его метод done (вместо этого, если нужно, используйте then)! Если последовательность исполнения неважна, но вам нужно, чтобы все операции завершились, вы можете скомбинировать эти отложенные результаты, используя команду WinJS.Promise.join, передавая результат в setPromise. Если вам нужно, чтобы завершилась лишь одна операция, вы, вместо вышеупомянутой, можете использовать WinJS.Promise.any. Команды join и any будут разъяснены в последнем разделе этой лекции.

Другой подход заключается в том, чтобы зарегистрировать более чем один обработчик с WinJS.Application.onactivated; каждый обработчик получит собственные аргументы события и собственную функцию setPromise, и WinJS объединит эти возвращенные promise-результаты с помощью WinJS.Promise.join.

Метод setPromise из WinJS, на самом деле, реализован с использованием более общего механизма отложенных операций из WinRT. Объект args, передаваемый в Windows.UI.WebUI.WebUIApplication.onactivated (событие WinRT), содержит небольшой метод, который называется getDeferral (технически - Windows.UI.WebUI.ActivatedOperation.getDeferral (http://msdn.microsoft.com/library/windows/apps/windows.ui.webui.activateddeferral.aspx)). Эта функция возвращает отложенный объект, который содержит метод complete, и Windows не скроет системный экран-заставку до тех пор, пока вы не вызовете этот метод (хотя, это не меняет тот факт, что пользователи нетерпеливы, и ваше приложение всё еще ограничено 15-секундным лимитом). Код может быть похож на этот:

//В обработчике события activated 
var activatedDeferral = Windows.UI.WebUI.ActivatedOperation.getDeferral();

someOperationAsync().done(function () {
//После завершения инициализации activatedDeferral.complete();
}

Конечно, setPromise, в конечном счете, делает то же самое, и если вы прямо добавите обработчик события WinRT activated, вы сможете реализовать ту же задержку активации своими силами.

Переход между событиями жизненного цикла приложений и состояние сеанса работы

Для приложений и для тех, кто их публикует, идеальным миром был бы мир, в котором пользователи запускают приложения и остаются в них навсегда (и, без сомнения, совершают множество покупок в приложении!). Но в жестокой реальности это невозможно. Независимо от того, как вам хотелось бы, чтобы это было иначе, ваше приложение - не единственное, которое захочется запустить пользователю. В конце концов, зачем были бы нужны функции вроде общего доступа или закрепления контента, если бы несколько приложений не могли исполняться вместе? К лучшему, или к худшему, пользователи будут переключаться между приложениями, менять состояния просмотра, и, возможно, закрывать ваше приложения. Но то, что вы можете сделать - это направить силы в "лучшую" сторону уравнения, убедившись в том, что ваше приложение хорошо ведет себя в любом из этих обстоятельств.

Первое соображение касается фокуса (focus), который применим к элементам управления в вашем приложении и к приложению в целом. Здесь вы можете просто использовать стандартные события HTML blur и focus. Например, в игре в стиле "экшн", или в чём-то подобном, с таймером, постановка на паузу обычно происходит по событию blur, а запуск - по событию focus.

Похожее, но иное условие касается видимости (visibility). Приложение может быть видимым, но не иметь фокуса, как тогда, когда оно работает в прикрепленном режиме. В подобных случаях приложение может продолжать действия, наподобие анимации или обновления ленты новостей, но оно приостановит подобную активность, когда потеряет видимость (то есть, когда оно окажется в фоновом режиме). Для этого используйте событие visibilityChange (http://msdn.microsoft.com/library/windows/apps/hh441213.aspx) из DOM API и затем проверьте свойство visibilityState (http://msdn.microsoft.com/library/windows/apps/hh453385.aspx) объекта window или document, так же, как и свойство document.hidden. (Событие применимо и к видимости отдельных элементов). Изменение видимости, кроме того, отличное время для того, чтобы сохранить пользовательские данные, наподобие документов или состояния прохождения игры.

Приложение может узнать об изменении состояния просмотра (view state change) несколькими способами. Как показано в примере "Here My Am!", приложение обычно использует медиа-запросы (объявленные в CSS или посредством прослушивателей медиа-запросов в коде) для того, чтобы перенастроить макет и видимость элементов, что, на самом деле, единственное, на что должно влиять изменение состояния просмотра. (Опять же, изменение состояния просмотра никогда не изменяет режим работы приложения, только макет и видимость объектов). В любое время приложение может получить сведения о текущем режиме просмотра посредством Windows.UI.ViewManagement.ApplicationView.value. Эта команда возвращает одно из значений типа Windows.UI.ViewManagement.ApplicationViewState, а именно: snapped, filled, fullScreenLandscape и fullScreenPortrait; подробности вы можете найти в лекции 6.

Когда приложение завершает работу (пользователь провёл сверху вниз по экрану или нажал Alt+F4), важно отметить, что сначала приложение попадает во внеэкранный (скрытый) режим, приостанавливается, а потом закрывается, в итоге обычные DOM-события, наподобие unload, не применимы. Пользователь, кроме того, может остановить процесс вашего приложения через Диспетчер задач, однако, при таком стечении обстоятельств, никакие события в коде приложения не генерируются. Помните так же о том, что приложению не следует самому себя закрывать, как говорилось ранее, но оно может воспользоваться командой MSApp.terminateApp для того, чтобы закрыться в ответ на происшествия, последствия которых невозможно исправить.

Приостановка, возобновление, завершение работы приложения

Помимо фокуса, видимости и состояний просмотра есть три других критических момента в жизненном цикле приложения:

Полезно знать, что вы можете симулировать эти состояния в отладчике Visual Studio, используя выпадающее меню панели инструментов, как показано на Рис. 3.7. Эти команды вызывают соответствующие события и установку значения previousExecutionState для следующего запуска приложения. (Отнеситесь с благодарностью к этим элементам управления. Было время, когда у нас их не было, и было очень неудобно отлаживать эти состояния приложения!).

Выпадающее меню в панели инструментов Visual Studio для симуляции приостановки, возобновления и завершения работы приложения


Рис. 3.7.  Выпадающее меню в панели инструментов Visual Studio для симуляции приостановки, возобновления и завершения работы приложения

Мы уже вкратце обсудили эти состояния, но давайте посмотрим, как они связаны с запускаемыми событиями и со значением previousExecutionState, которое используется при следующем старте приложения. Это может выглядеть несколько сложным, поэтому переходы между состояниями показаны на Рис. 3.8, а в таблице ниже описано, как определяется значение previousExecutionState.

Таблица 3.4.
Значение previousExecutionStateСценарии
notRunningПервый запуск после установки из Магазина Windows. Первый запуск после перезагрузки или выхода. Приложение запущено через примерно 10 секунд после того, как было закрыто пользователем (приблизительное время, необходимое для скрытия, приостановки, и полного завершения работы приложения; если пользователь перезапустит приложение быстрее, Windows предварительно немедленно завершит его работу, не завершая операции, выполняемые при приостановке). Приложение было остановлено с помощью Диспетчера задач во время выполнения или самостоятельно закрылось с помощью MSApp.terminateApp.
runningПриложение уже исполняется и вызвано способом, отличающимся от запуска с помощью плитки, такого, как чудо-кнопки Поиск или Общий доступ, дополнительные плитки, всплывающее уведомление и реализация других контрактов. Когда приложение запущено и пользователь щёлкает по плитке, Windows просто переключается на уже работающее приложение без вызова событий активации (хотя, и focus, и visibilitychange вызываются).
suspendedПриложение приостановлено и затем вызвано способом, отличающимся от запуска с помощью плитки (как показано выше, для running). В дополнение к событиям focus/visibility, приложение принимает событие resuming.
terminatedРанее приложение было приостановлено, после чего завершено Windows по причине нехватки ресурсов. Обратите внимание на то, что это не применимо к MSApp.terminateApp,так как приложение должно выполняться для того, чтобы вызвать данную функцию.
closedByUserПриложение было закрыто с помощью не прерванного жеста закрытия (проведение сверху вниз или Alt+F4). "Прерванное" закрытие происходит, когда пользователь возвращается к приложению в течение 10 секунд, в таком случае предыдущее состояние будет установлено в notRunning.

Обработка событий жизненного цикла и значения previousExecutionState


Рис. 3.8.  Обработка событий жизненного цикла и значения previousExecutionState

Активировано, загружено и так далее (activated, load, etc.)

Выполняется (в памяти) (running (in memory))

Приостановка/контрольная точка (suspending/checkpoint)

Приостановлено (в памяти) (suspended (in memory))

Возобновление (resuming)

Нет событий, предыдущее состояние равно значению завершено только если Windows закрыла приложение ((no event) previous state == terminated only if Windows closed the app)

Не исполняется, закрыто пользователем или завершено (notRunning, closedByUser, or terminated)

Основной вопрос для приложения, конечно, не в том, чтобы определить значение previousExecutionState, а в том, что, на самом деле, делать с этим значением при активации. К счастью, эта история немного проще и кое-что мы видим в коде шаблона:

Именно по причине наличия второго из вышеперечисленных требований, приложения предоставляют структуру кода для этого случая вместе с обработчиком checkpoint. Мы рассмотрим в подробностях сохранение и перезагрузку состояний в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Основная идея заключается в том, что приложению следует, когда оно приостанавливается, если не раньше, сохранит любые передаваемые состояния сеанса работы, которые ему нужны для самовосстановления после остановки. Эти состояния включают в себя непереданные данные форм, позицию прокрутки контента, стек навигации по приложению и другие переменные. Это так потому, что хотя Windows может приостановить приложение и выгрузить его из памяти, пользователь всё еще воспринимает приложение работающим. Таким образом, когда пользователи активируют приложение снова для обычного использования (это будет скорее вид активации launch, чем активация посредством контракта), они ожидают, что приложение будет в том же состоянии, в котором было до этого. В течение времени, когда приложение приостанавливается, таким образом, ему нужно сохранить любые состояния, которые могут сделать вышеупомянутое возможным. Приложение затем восстанавливает это состояние, в том случае, если previousExecutionState равно terminated.

Для того, чтобы узнать подробности о том, где это важно при проектировании приложений, обратитесь к материалу "Руководство по приостановке и возобновлению работы приложений" (http://msdn.microsoft.com/library/windows/apps/hh465088.aspx). Ясно то, что если пользователь непосредственно закроет приложение с помощью Alt+F4 или жеста закрытия, будут вызваны события suspending и checkpoint, в итоге приложение сохранит состояния сеанса работы Однако, приложение автоматически завершает работу после приостановки и перезагрузка состояния сеанса работы не будет запрошена при повторном запуске, так как значение previousExecutionState будет notRunning или closedByUser.

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

Вы всегда сохраняете состояние сеанса работы в папку с данными приложения или контейнеры параметров, которые предоставляет API Windows.Storage.ApplicationData (http://msdn.microsoft.com/library/windows/apps/windows.storage.applicationdata.aspx). Подробности об этом будут в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Сейчас мне хотелось бы лишь указать на некоторые вспомогательные механизмы, которые предоставляет для этих целей WinJS.

Во-первых, это событие WinJS.Application.checkpoint, которое предоставляет единое удобное пространство для сохранения и состояния сеанса работы, и любых других постоянных данных, которые могут у вас быть, если вы еще не сохранили их.

Во-вторых, это объект WinJS.Application.sessionState. При нормальном старте приложения он представляет собой пустой объект, в который вы можете добавить любые свойства по своему желанию, в том числе - другие объекты. Обычный подход заключается в том, чтобы использовать sessionState напрямую, как контейнер для переменных. В событии checkpoint WinJS автоматически сериализует содержимое этого объекта (используя JSON.stringify) в файл, расположенный в папке локальных данных вашего приложения (это значит, что переменные в sessionState должны иметь строковое представление). Обратите внимание на то, что WinJS гарантирует то, что его собственный обработчик события checkpoint всегда вызывается после того, как ваше приложение получит это событие, вы можете быть уверены в том, что WinJS сохранит всё, что вы запишете в sessionState в любое время до того, как сработает команда выхода из вашего обработчика checkpoint.

Затем, когда приложение активируется с предыдущим состоянием - terminated, WinJS автоматически восстанавливает объект sessionState, в итоге, всё что вы в нём разместили снова будет доступным. Если вы использовали этот объект для хранения переменных, вам лишь нужно избегать записи в них значений по умолчанию при перезагрузке состояния сеанса работы.

В-третьих, если вы не хотите использовать объект sessionState, или у вас есть сессия, которая с ним не работает, объект WinJS.Application упрощает запись ваших собственных файлов без необходимости использования асинхронного API WinRT. В особенности, он предоставляет (как показано в документации (http://msdn.microsoft.com/library/windows/apps/br229774.aspx)), объекты local, temp и roaming, каждый из которых имеет методы readText, writeText, exists, и remove. Эти объекты работают с соответствующими им папками данных приложения и предоставляют упрощенное API для операций файлового ввода/вывода, как показано в Сценарии 1, примера "Модель приложения" (http://code.msdn.microsoft.com/windowsapps/ApplicationModel-Sample-4be6575d).

Последнее вспомогательное средство связано с механизмом отложенного исполнения, похожим на тот, который применяется при активации. Отложенные операции важны, так как Windows приостановит приложение, как только оно завершит обработку события suspending. Если вам нужно откладывание для асинхронных операций, аргументы события WinJS.Application.oncheckpoint предоставляют метод setPromise, который связан с более глубокими механизмами отложенного выполнения WinRT. Как и ранее, вы передаете promise-объект для асинхронной операции (или комбинации операций) методу setPromise, который, в ответ, вызывает отложенный метод complete, когда ожидаемые результаты будут получены.

На уровне WinRT, аргументы события suspending содержат экземпляр Windows.UI.WebUI.- WebUIApplication.SuspendingOperation (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.suspendingoperation.aspx). Он предоставляет метод getDeferral, который возвращает отложенный объект с методом complete, как при активации.

Ух ты! Звучит заманчиво! Может быть, это хитрый способ обойти ограничение на исполнение приложений для Магазина Windows в фоновом режиме? Будет ли приложение исполняться вечно, если я запрошу отсроченную операцию, никогда не вызывая complete?

Не тут-то было, амиго. Примите мои извинения за то, что подарил вам волшебный миг восторга. С отсроченной операцией или нет, пять секунд - это наибольшее время, на которое вы можете рассчитывать. Тем не менее, вы можете в полной мере использовать преимущества этого времени, возможно, сначала выполнив критически важную асинхронную операцию (вроде сбрасывания кэша), и затем попытавшись произвести менее важные действия (вроде синхронизации с сервером), которые могут значительно улучшить опыт взаимодействия пользователя с приложением. Для подобных целей объект suspendingOperation так же содержит свойство deadline, имеющее тип Date, показывающее время в будущем, когда Windows принудительно приостановит приложение вне зависимости от наличия отсроченных операций. Как только первая операция завершится, вы можете проверить, имеется ли время на то, чтобы начать вторую, и так далее.

Базовая демонстрация использования отсроченных операций при приостановке, кстати, сделана в примере "Активация, возобновление работы, приостановка приложения" (http://code.msdn.microsoft.com/windowsapps/App-activating-and-ec15b168). Кроме того, здесь есть пример активации приложения через специальную URI-схему, это мы рассмотрим в лекции 1 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой". Пример управления состояниями приложения, в дополнение к обновлениям, которые мы внесем в нашу программу "Here My Am!" в следующем разделе, можно найти в Сценарии 3 примера "Модель приложения" ((http://code.msdn.microsoft.com/windowsapps/ApplicationModel-Sample-4be6575d).

Базовое управление состояниями в "Here My Am!"

Для того, чтобы показать основные приемы обработки состояний сеанса приложения, я внес некоторые изменения в "Here My Am!". Их можно найти в упражнении HereMyAm3c. У нас есть два набора данных, о которых мы беспокоимся: переменные lastCapture (объект StorageFile с изображением) и lastPosition (набор координат). Мы хотим быть уверенными в том, что сохранили их при приостановке приложения, в итоге, мы сможем подходящим образом использовать эти значения, когда приложение будет запущено с предыдущим состоянием terminated.

В случае с lastPosition мы можем просто поместить эти данные в объект sessionState (вставив app.sessionState.) в обработчик завершения getGeoPositionAsync:

gl.getGeopositionAsync().done(function (position) {
app.sessionState.lastPosition = {	
latitude: position.coordinate.latitude,	
e
longitude: position.coordinate.longitud	
};	
updatePosition();	
}, function (error) {	
console.log("Unable to get location.");
});	
}

Так как нам нужно установить позицию на карте и отсюда, и из ранее сохраненных координат, я переместил данный участок кода в другую функцию, которая так же позволяет быть уверенным в том, что данные о местоположении есть в sessionState:

function updatePosition() {	
if (!app.sessionState.lastPosition) {
return;	
}	
callFrameScript(document.frames["map"], "pinLocation", [app.sessionState.lastPosition.latitude, app.sessionState.lastPosition.longitude]);
}

Отметим так же, что app.sessionstate инициализируется по умолчанию пустым объектом, {}, поэтому lastPosition будет иметь значение undefined до определения местоположения. Это полезно нам при восстановлении состояния приложения. Вот как могут выглядеть, с учетом этого, условия с previousExecutionState:

if (args.detail.previousExecutionState !==	
activation.ApplicationExecutionState.terminated) {	
//Нормальный старт: инициализируем lastPosition с помощью API
//определения местоположения	
} else {	
//WinJS перезагружает здесь объект sessionState. Поэтому попытаемся отобразить на карте 
//местоположение из сохраненных данных.
updatePosition();	
}

Так как мы храним lastPosition в sessionState, эти данные автоматически сохраняются в WinJS.Application.checkpoint, когда приложение было запущено ранее. Когда мы перезапускаемся из состояния terminated, WinJS автоматически перезагружает sessionState; если ранее мы сохранили здесь данные, они будут загружены и updatePosition просто будет работать.

Вы можете проверить это, запустив приложение с данными изменениями и затем использовав команду Приостановить и завершить работу (Suspend and shutdown) на панели инструментов Visual Studio. Установите точку останова на вызов updatePosition выше и затем перезапустите приложение в отладчике. Вы увидите, что в этот момент sessionState.lastPosition уже инициализировано.

В случае с последним захваченным изображением, нам не нужно сохранять объект типа StorageFile, нам нужно сохранить лишь путь к файлу: мы копируем файл в папку локальных данных приложения (в итоге, файл сохраняется между сеансами работы) и можем просто использовать схему URI ms-appdata:// для того, чтобы сослаться на него. Когда мы захватываем изображение, мы просто сохраняем этот URI в sessionState.imageURL (имя свойства может быть произвольным) в конце цепочки promise-вызовов внутри capturePhoto:

app.sessionState.imageURL = "ms-appdata:///local/HereMyAm/" + newFile.name;
that.src = app.sessionState.imageURL

Это значение так же будет перезагружено в соответствующем месте при перезагрузке, в итоге, мы можем просто инициализировать соответствующим образом img src:

if (app.sessionState.imageURL) {
document.getElementById("photo").src = app.sessionState.imageURL;
}

Этот код инициализирует отображаемое изображение из sessionState, но нам, кроме того, нужно инициализировать lastCapture для того, чтобы то же самое изображение было доступно для контракта Общий доступ. Для этого нам нужно, кроме того, сохранить полный путь к файлу, в итоге, мы можем повторно получить объект типа StorageFile посредством Windows.SttorageFile.getFileFromPathAsync (что не работает с URI ms-appdata://). В итоге, в capturePhoto:

app.sessionState.imagePath = newFile.path;

И при загрузке:

if (app.sessionState.imagePath) {	
Windows.Storage.StorageFile.getFileFromPathAsync(app.sessionState.imagePath)
.done(function (file) {	
lastCapture = file;	
if (app.sessionState.imageURL) {	
document.getElementById("photo").src = app.sessionState.imageURL;
}	
});	

Я поместил код для установки img.src внутрь обработчика завершения, так как нам нужно, чтобы изображение появлялось только в том случае, если мы можем получить доступ к StorageFile снова для целей общего доступа. В противном случае эти две функции приложения не будут синхронизированы.

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

Примечание. Использование ms-appdata:/// и getFileFromPathAsync возможно, так как файл находится в месте, куда мы можем получить программный доступ по умолчанию. Это, кроме того, работает для библиотек, которые мы указали среди возможностей в манифесте. Если, однако, мы получили объект StorageFile после работы со средством выбора файла, мы должны сохранить его в Windows.Storage.AccessCashe для того, чтобы сохранить права доступа между сеансами работы с приложением.

Данные от сервисов и WinJS.xhr

Хотя мы видели примеры использования данных из пакета приложения (с помощью URI или Windows.ApplicationModel.Package.current.installedLocation), так же, как и из папок данных приложения, весьма вероятно, что ваше приложение будет включать в себя данные из веб-сервисов, и, возможно, так же отправлять данные в эти сервисы. Наиболее распространённым для этих целей методом является XmlHttpRequest. Вы можете использовать его в исходной (асинхронной) форме, если хотите, или вы можете предохранить себя от множества сложностей, используя функцию WinJS.xhr, которая удобно оборачивает все эти действия в promise-объекты.

Сделать запрос очень просто, как показано в примере SimpleXhr для этой лекции. Здесь мы используем WinJS.xhr для получения RSS-канала с блога разработчиков Windows 8:

WinJS.xhr({ url: "http://blogs.msdn.com/b/windowsappdev/rss.aspx" })
.done(processPosts, processError, showProgress);

То есть, здесь мы предоставляем WinJS.Xhr URI и получаем promise-объект, который доставляет свой результат в наш обработчик завершения (в данном случае это processPosts) и даже вызывает индикатор прогресса, если он предоставлен ему. В предыдущем случае результат содержит свойство responseXML, которое является объектом DomParser. В случае с последним, объект события содержит текущий XML в его свойстве response, который мы можем просто использовать для показа объема загруженных данных:

function showProgress(e) {	
var bytes = Math.floor(e.response.length / 1024);	
document.getElementById("status").innerText = "Downloaded " + bytes + " KB";
}

В остальном, приложение просто перерабатывает полученный текст в поисках элементов item и отображает поля title, pubDate и link. Она применяет некоторое форматирование (смотрите файл default.css) и использует классы типографического стиля из WinJS, в частности, win-type-x-large (для title), win-type-medium (для pubDate), и win-type-small (для link). В итоге, мы быстро разработали приложение, которое выглядит так, как показано на Рис. 3.9. Вы можете взглянуть на код для того, чтобы увидеть подробности1).

Вывод данных в приложении SimpleXhr


увеличить изображение

Рис. 3.9.  Вывод данных в приложении SimpleXhr

Для полной демонстрации XHR и связанных вопросов обратитесь к примеру "XHR, обработка ошибок навигации и URL-схем" (http://code.msdn.microsoft.com/windowsapps/XHR-handling-navigation-50d03a7a) и к руководству: "Создание гибридного веб-приложения" (http://msdn.microsoft.com/library/windows/apps/hh452745.aspx). Я не вхожу в описание подробностей о XHR в данном учебном курсе, так как это, в основном, вопрос получения и обработки данных, что имеет мало общего с платформой Windows 8. Что нас интересует, так это последствия приостановки и возобновления работы приложения.

В частности, приложение не может предсказать, как долго оно будет оставаться в приостановленном состоянии, прежде чем его работа будет возобновлена или прежде чем его работа будет остановлена и оно будет перезапущено.

В первом случае, данные приостановленного приложения находятся в памяти. Очень нужно решить, поэтому, устарели ли данные с момента приостановки приложения и истекли ли тайм-ауты соединений с серверами. Так же вы можете подумать о том, после какого периода времени пользователь не помнит и не забоится о том, что произошло, когда он в последний раз видел приложение? Если это неделя или больше, может быть разумным перезапустить приложение или восстановить его состояние по умолчанию. Далее, если вы приводите приложение в состояние, точно соответствующее тому, в котором оно было, пользователь испытывает уверенность, что он может оставить приложение работать сколько угодно долго и ничего не потерять. Или вы можете пойти на компромисс и дать пользователю возможность настроить поведение приложения. Конечно, вы будете размышлять над собственным сценарием, однако, если сомневаетесь, перезагрузите состояние приложения, которое было оставлено пользователем на какое-то время.

Для того, чтобы проверить, сколько времени прошло, сохраните отметку времени при приостановке приложения (с помощью new Date().getTime()), получите другую отметку времени при обработке события resuming, найдите разницу и сравните её с заданным вами периодом обновления. Приложение Stock (Акции), например, может иметь очень короткий период. В случае с приложением для чтения блога разработчиков Windows 8, с другой стороны, новые записи появляются не чаще раза в день, поэтому здесь, для того, чтобы поддерживать приложение в актуальном состоянии и получать новые записи, подойдёт более длительный период, измеряемый часами.

Это реализовано в SimpleXhr путём размещения вызовов WinJS.hxr в отдельной функции, названной downloadPosts, которая вызывается при запуске приложения. Затем мы регистрируемся для обработки события resuming в WinRT:

Windows.UI.WebUI.WebUIApplication.onresuming = function () {
app.queueEvent({ type: "resuming" });
}

Помните, я говорил о том, что мы могли бы использовать WinJS.Application.queueEvent для того, чтобы вызывать собственные события объекта приложения? Вот отличный пример. WinJS.Application не включает в себя автоматически события resuming, так как ему нечего добавить к этому процессу. Но приведенный ниже код реализует в точности то же самое, позволяя нам регистрировать прослушиватели событий, наряду с другими событиями наподобие checkpoint:

app.oncheckpoint = function (args) {	
//Сохраняем в sessionState в том случае, если хотим использовать это с кэшированием
app.sessionState.suspendTime = new Date().getTime();	
};	

app.addEventListener("resuming", function (args) {
//Обычное сокращение для того, чтобы получить либо значение переменной, либо значение
//по умолчанию 
var suspendTime = app.sessionState.suspendTime || 0;

//Определяет, сколько времени, в секундах, прошло
var elapsed = ((new Date().getTime()) - suspendTime) / 1000;

//Обновляет ленту, если > 1 часа (или, для тестирования, используйте меньшее значение)
if (elapsed > 3600) {	
downloadPosts();	
}	
});	

Для того, чтобы протестировать этот код, загрузите отладчик Visual Studio и установите точки останова в этих событиях. Затем щёлкните на пункт приостановки (suspend) на панели инструментов (можете вернуться к Рис. 3.7), и вы должны попасть в обработчик события checkpoint. Подождите несколько секунд и щелкните на кнопку возобновления работы приложения (resume), имеющую треугольный зеленый значок, и вы должны оказаться в обработчике события resuming. Затем вы можете пошагово исполнить код и увидеть, что переменная elapsed содержит количество прошедших секунд, и если вы измените её значение (или замените 3600 меньшим значением), вы можете увидеть, как downloadPosts снова вызывается для обновления данных.

А что насчёт запуска после остановки работы приложения? Хорошо, если вы не кэшировали данные ранее, вам, в любом случае, нужно их обновить. Если вы кэшировали какие-то данные, сохранённое состояние сеанса работы приложения (такое, как отметка времени) поможет вам решить, стоит ли использовать кэшированные данные или нужно загрузить их снова.

Полезно будет упомянуть здесь о том, что вы можете использовать механизмы HTML5, наподобие localStorage, IndexedDB и кэша приложения для целей кэширования; подобные данные хранятся в папке локальных данных приложения. И, если говорить о базах данных, вы можете заинтересоваться, что доступно приложениям для Магазина Windows помимо IndexedDB. Одна из возможностей - это SQLite, как описано в материале "Использование SQLite в приложениях для Магазина Windows" (http://timheuer.com/blog/archive/2012/05/20/using-sqlite-in-metro-style-app.aspx) (в блоге Тима Хейера, одного из инженеров Windows 8). Кроме того, вы можете использовать библиотеку OData для JavaScript, которую можно найти по адресу http://www.odata.org/libraries. Это - один из наиболее лёгких способов для взаимодействия с онлайновыми базами данных на SQL Server (или любыми другими, поддерживающими OData), так как он просто использует возможности XmlHttpRequest.

Работа с сетевыми подключениями (вкратце)

Мы поговорим о сетевом взаимодействии в лекции 3 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", но есть один важный аспект, о котором вы, разрабатывая приложение, должны знать заранее. Что делает приложение с изменениями состояния сетевого соединения, такими как разъединение, повторное соединение и с изменениями полосы пропускания и стоимости соединения (такими, как связь в роуминге, в другой зоне предоставления услуг)?

API Windows.Networking.Connectivity обеспечивает вас такого рода информацией. Вот три основных способа реагирования на подобные события:

Проще говоря, тестируйте приложение как в состоянии подключения к сети, так и без него для того, чтобы обнаружить небольшие оплошности в коде. В "Here My Am!", например, первая версия скрипта в html/map.html не заботилась о том, чтобы проверить, действительно ли загружен удалённый скрипт карт Bing. Сейчас она проверяет, является ли допустимым пространство имен Microsoft (для Microsoft.Maps.Map) В SimpleXhr, тоже, я добавил обработчик ошибки для операции получения отложенного результата в WinJS.xhr, в итоге я могу, по меньшей мере, отобразить обычное сообщение. Здесь, конечно, гораздо больше работы, но попытайтесь, по меньшей мере, охватить основы, для того, чтобы избежать исключений, которые приведут к аварийному завершению работы приложения.

Секреты и советы для WinJS.xhr

Не распутывая клубок проблем, которым является XmlHttpRequest, будет полезным посмотреть на пару дополнительных вещей, касающихся WinJS.xhr.

Во-первых, обратите внимание на то, что одиночный аргумент этой функции является объектом, который содержит множество свойств. Свойство url наиболее распространено, конечно, но вы, кроме того, можете установить свойство type (тип) (по умолчанию оно установлено в "GET") и respondeType для транзакций другого рода, информацию об учетных данных пользователя в виде user и password, установить headers (заголовки) (такие, как "If-Modified-Since" с датой для управления кэшированием) и предоставить любые другие дополнительные данные (data), если в этом есть необходимость (такие, как параметры запроса к базе данных для XHR). Кроме того, вы можете поддерживать функцию customRequestInitializer, которая будет вызываться объектом XmlHttpReauest до отправки данных, позволяя вам произвести любые необходимые действия.

Во-вторых, это установка тайм-аута в запросе. Вы можете использовать для этих целей customRequestInitializer, устанавливая свойство XmlHttpRequest.timeout и, возможно, обрабатывая событие ontimeout. С другой стороны, как мы увидим в разделе "Завершение истории promise-объектов" в конце этой лекции, вы можете использовать функцию WinJS.Promise.timeout, которая позволяет вам устанавливать период тайм-аута, после которого promise-вызов (и асинхронная операция, связанная с ним) будет отменен. Отмена производится простым вызовом метода cancel promise-объекта.

Вам может понадобиться включить WinJS.xhr в другой promise-объект, это мы так же рассмотрим в конце данной лекции. Вы можете сделать это для того, чтобы инкапсулировать другие промежуточные результаты с вызовом XHR, когда ваш код просто использует возвращённый отложенных результат обычным образом. В соединении с тайм-аутами, эта так же может быть использовано для реализации механизма множественного повтора.

Далее, если вам нужно скоординировать вместе несколько вызовов XHR, вы можете использовать WinJS.Promise.join, который мы снова увидим позже.

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

У приложений для Магазина Windows, использующих XHR с localhost: URI (локальная заглушка, петлевой адрес локальной сети) заблокированы при проектировании системы. При разработке, однако, это очень полезно для отладки сервисов без их развёртывания. Вы можете включить локальную заглушку в Visual Studio, открыв диалоговое окно свойств проекта (контекстное меню проекта > Свойства), выбрав группу Отладка (Debugging) в левой части окна и установив параметр Разрешить петлевой адрес в локальной сети (Allow Local Network Loopback) в значение Да. Мы рассмотрим соответствующий пример в лекции 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", где это окажется очень полезным для отладки сервисов, которые выдают данные для обновления плиток и другие оповещения.

Наконец, полезно знать, что по соображениям безопасности куки-данные (cookies) автоматически вырезаются из XHR-данных, поступающих в локальный контекст. Один из способов это обойти - выполнять XHR-вызовы из iframe, находящегося в веб-контексте (в котором вы можете использовать WinJS.XHR), затем извлекать куки-данные, которые вам нужны и передавать их в локальный контекст, используя postMessage. С другой стороны, вы можете решить проблему на стороне сервиса, такую, как реализацию API, предоставляющего необходимые вам данные, которые вы извлекали из куки, напрямую.

Другие подробности о данной функции вы можете найти в документации по WinJS.xhr (http://msdn.microsoft.com/library/windows/apps/br229787.aspx), и по ссылкам на дополнительные материалы, которые она содержит.

Элементы управления страниц и навигация

Сейчас мы подошли к той стороне приложений для Магазина Windows, которая весьма сильно отличает их от обычных веб-приложений. В веб-приложениях, навигация между страницами реализуется посредством использования гиперссылок <a href> или установки параметра document.location в JavaScript. Всё это замечательно. Часто здесь либо нет данных для передачи между страницами, либо их немного. И даже когда есть что передавать между страницами, существует хорошо налаженный механизм HTML5для этого, такой, как sessionStorage и localStorage (который хорошо работает и с приложениями для Магазина).

Однако, подобные способы навигации приводят к некоторым проблемам в случае с приложениями для Магазина Windows. Первая, перемещение на совершенно новую страницу подразумевает совершенно новый контекст скрипта - все JavaScript-переменные с предыдущей страницы будут потеряны. Конечно, вы можете передать данные между этими страницами, но управление подобным по всему приложению серьезно ухудшает производительность и может очень скоро превратиться в вашу самую нелюбимое дело в работе программиста. Лучше и проще, другими словами, чтобы клиентское приложение поддерживало в памяти постоянное состояние, не зависящее от перемещений по страницам.

Кроме того, природа подсистемы рендеринга HTML/CSS такова, что при переходах между страницами с помощью гиперссылки появляется пустой экран. Пользователи веб-приложений привыкли ждать какое-то время, пока браузер загрузит новую страницу (я много что успеваю сделать за дополнительные 15 секунд!), но это не соответствует тому, что ожидают пользователи от быстрых и динамичных приложений для Магазина Windows. Более того, подобные переходы не позволяют анимацию различных элементов на экране, что помогает создать ощущение неразрывной связи страниц, если это соответствует дизайну приложения.

В итоге, хотя вы можете использовать прямые ссылки, приложения для Магазина обычно реализуют понятие "страница" динамически заменяя секции в DOM внутри контекста одной страницы наподобие default.html, что родственно тому, как работают приложения, основанные на AJAX. Подобный подход позволяет всегда сохранять контекст скрипта и обеспечить переходы между элементами и группами элементов так, как вам нужно. В некоторых случаях имеет значение простой показ и скрытие страниц, в итоге вы можете быстро переходить вперед и назад. Посмотрим на стратегии и инструменты, с помощью которых можно достичь эти цели.

Инструменты WinJS для страниц и навигации по страницам

Сама по себе Windows и хост-процесс приложения не предоставляют средств для работы со страницами - с точки зрения системы это лишь детали реализации приложения, о которых не стоит беспокоиться. К счастью, инженеры, создавшие WinJS и шаблоны в Visual Studio и Blend как следует об этом подумали! В результате они предоставили изумительные инструменты для управления частями, состоящими из HTML+CSS+JS в контексте единственной страницы-контейнера:

Эти API предоставляют только средства для загрузки и выгрузки отдельных страниц - они берут HTML из других файлов (вместе с соответствующим CSS и JS-кодом) и присоединяют эти данные к элементу в DOM. Вот и всё. Для того, чтобы по-настоящему реализовать структуру навигации по страницам, нам нужно еще два механизма: что-то, что управляло бы стеком навигации и что-то, что перехватывало бы события навигации в механизме загрузки страниц WinJS.UI.Pages.

Первую задачу можно выполнить с помощью WinJS.Navigation, который с помощью примерно 150 строк CS101-кода поддерживает базовый навигационный стек. Это всё, что он делает. Сам по себе стек - это просто список URI, основываясь на котором WinJS.Navigation формирует свойства state, location, history, canGoBack, и canGoForward. Манипуляция стеком производится посредством методов forward, back и navigate, объект WinJS.Navigation вызывает несколько событий - beforenavigation, navigating и navigated - для любых прослушивателей (через addEventListener).2)

Для выполнения второй задачи вы можете создать собственные связи между WinJS.Navigation и WinJS.UI.Pages. На самом деле, на ранних стадиях разработки приложений для Windows 8, вплоть до первых публичных предварительных релизов для разработчиков, программисты писали один и тот же код снова и снова. В ответ на это, команда разработки в Microsoft, ответственная за шаблоны, великодушно решали создать стандартную реализацию этого механизма, что добавило несколько команд для работы с клавиатурой (для перемещения вперед и назад) и некоторые удобные оболочки для использования в шаблонах. Ура!

Этот инструмент называется PageControlNavigator. Так как это - всего лишь часть кода из шаблона, а не часть WinJS, он находится полностью под вашим управлением, в итоге, вы можете делать с ним всё, что хотите3). В любом случае, так как весьма вероятно то, что вы часто будете использовать PageControlNavigator в собственных программах, посмотрим, как это всё работает в контексте шаблона Приложение навигации (Navigation App).

Примечание. Дополнительные материалы, демонстрирующие основы управления страницами, вместе с обработкой состояния сеанса работы приложения, можно найти в следующих SDK-примерах: "Активация и приостановка приложений с использованием WinJS" (http://code.msdn.microsoft.com/windowsapps/App-activation-events-and-d39c53d5) (использование состояние сеанса работы приложения и элемента управления страницей), "Активация, возобновление, приостановка работы приложения" (http://code.msdn.microsoft.com/windowsapps/App-activating-and-ec15b168) (описано ранее, показывает использование задержанной приостановки и запуск приложения после завершения работы), и "Навигация и история навигации" (http://code.msdn.microsoft.com/windowsapps/Navigation-sample-cf242faa).

Шаблон Приложение навигации, структура PageControl и PageControlNavigator

Если вспомнить шаблон Пустое приложение, шаблон Приложение навигации демонстрирует основы использования элементов управления страницами. (Более сложные шаблоны строят систему навигации, идущую дальше). Если вы создаёте новый проект с использованием данного шаблона в Visual Studio или Blend, вот что вы получите:

Для разработки на базе этой структуры, добавьте дополнительные страницы, используя шаблон Элемент управления страницей. Рекомендую сначала создать новую папку для страницы в папке pages, наподобие папки home в стандартной структуре проекта. Затем щёлкните правой кнопкой мыши по этой папке, выберите команду Добавить > Создать элемент (Add > New Item) и выберите Элемент управления страницей (Page Control). Эта команда создаст подходящим образом названные .html, .js и .css-файлы в данной папке.

Давайте посмотрим на тело страницы default.html (опустив стандартный заголовок закомментированный элемент управления AppBar):

<body>	
<div id="contenthost" data-win-control="Application.PageControlNavigator"
data-win-options="{home: '/pages/home/home.html'}"></div>	
</body>

Всё, что здесь есть - это один контейнер div, названный contenthost (название может быть любым), в котором мы объявляем элемент управления Application.PageControlNavigator. В нём мы задаём единственный параметр, определяющий первый элемент управления страницей, который ему следует загрузить (/pages/home/home.html). Экземпляр элемента управления PageControlNavigator будет создан в обработчике события activated при вызове WinJS.UI.processAll.

Внутри home.html имеется базовая, для элемента управления страницы, разметка. Это то, что шаблон Приложение навигации предоставляет в качестве домашней страницы по умолчанию, и этого в значительной степени то, что вы получаете, добавляя новый PageControl из шаблона элемента:

<!DOCTYPE html>	
<html>	
<head>	
<!--... обычный HTML-заголовок и ссылки на WinJS опущены -->
<link href="/css/default.css" rel="stylesheet">	
<link href="/pages/home/home.css" rel="stylesheet">	
<script src="/pages/home/home.js"></script>	
</head>
<body>
<!-Содержимое, которое будет загружено и отображено. -->
<div class="fragment homepage">
<header aria-label="Header content" role="banner">
<button class="win-backbutton" aria-label="Back" disabled></button>
<h1 class="titlearea win-type-ellipsis">
<span class="pagetitle">Welcome to NavApp!</span>
</h1>
</header>
<section aria-label="Main content" role="main">
<p>Content goes here.</p>
</section>
</div>
</body>
</html>

Элемент с CSS-классами fragment и homepage, вместе с header, создают страницу со стандартным визуальным профилем и кнопкой Назад, которую PageControlNavigator автоматически присоединяет к событиям клавиатуры, мыши и сенсорного экрана. (Это ли не внимание!). Всё, что вам нужно сделать - это изменить текст внутри элемента h1 и содержимое внутри section, или просто заменить это всё на ту разметку, которая вам нужна. (Кстати, хотя ссылки на WinJS присутствуют в каждом элементе управления страницы, они, на самом деле, не перезагружаются; они существуют здесь лишь для того, чтобы помочь вам править элементы управления страниц в Blend.)

Определение реального элемента управления страницы находится в файле pages/home/home.js. По умолчанию шаблон предоставляет лишь абсолютный минимум:

(function () { 
"use strict";

WinJS.UI.Pages.define("/pages/home/home.html", {
// Эта функция вызывается каждый раз, когда пользователь переходит на данную страницу. Она
// заполняет элементы страницы данными приложения . 
ready: function (element, options) {
// TODO: Инициализируйте страницу здесь.
}
});
})();

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

В случае со страницей, создаваемой из шаблона Элемент управления страницей, вы получите пару дополнительных методов в её структуре (некоторые комментарии опущены):

(function () { "use strict";

WinJS.UI.Pages.define("/page2.html", {
ready: function (element, options) {
},

updateLayout: function (element, viewState, lastViewState) {
// TODO: Ответ на изменения в состоянии viewState.
},

unload: function () {	
// TODO: Ответ на уход с этой страницы.
}	
});	
})();	

Хорошо будет отметить, что как только вы определили элемент управления страницы таким способом, вы можете создавать его экземпляры из JavaScript с использованием ключевого слова new, сначала получив его конструктор из WinJS.UI.Pages.get(<page_uri>) и затем вызвав этот конструктор с родительским элементом и объектом, содержащим его параметры.

Хотя, базовая структура для метода ready предоставляется шаблоном, WinJS.UI.Pages и PageControlNavigator будут использовать следующие методы, если они доступны:

Таблица 3.5.
Метод PageControl Когда вызывается
initВызывается перед тем, как созданы элементы из элемента управления страницы.
processedВызывается после того, как WinJS.UI.processAll завершен (то есть, были созданы экземпляры элементов управления на странице, что выполняется автоматически), но перед тем, как содержимое страницы добавлено в DOM.
readyВызывается после того, как страница добавлена в DOM.
errorВызывается, если возникла ошибка при загрузке или рендеринге страницы.
unloadВызывается при уходе со страницы.
updateLayoutВызывается в ответ на событие window.onresize, которое сигнализирует о смене между альбомным, заполняющим, прикрепленным, портретным режимами просмотра.

Обратите внимание на то, что WinJS.UI.Pages вызывает первые четыре метода. Методы unload и updateLayout, с другой стороны, используются только PageControlNavigator. Среди всех них метод ready реализуют чаще всего. Это то место, где вы можете провести дальнейшую инициализацию элемента управления (например, заполняете списки), подключаете другие обработчики событий, специфичных для страницы и так далее. Метод unload это так же место, где вы можете удалить прослушиватели событий для объектов WinRT, как описано в разделе "События WinRT и removeEventListener" ниже. Метод updateLayout важен, когда вы хотите адаптировать макет страницы к новым условиям, как, например изменения макета элемента управления ListView (как мы увидим в лекции 5, "Коллекции и элементы управления для вывода коллекций").

Что касается самого PageControlNavigator, код в js/navigator.js показывает, как он определен и как он подключает несколько событий в своём конструкторе:

(function () { "use strict";

// [некоторые части опущены]
var nav = WinJS.Navigation;

WinJS.Namespace.define("Application", {	
PageControlNavigator: WinJS.Class.define(	
// Определение функции конструктора для объекта PageControlNavigator.
function PageControlNavigator (element, options) {	
this.element = element || document.createElement("div");	
this.element.appendChild(this._createPageElement());	

this.home = options.home;
nav.onnavigated = this._navigated.bind(this);
window.onresize = this._resized.bind(this);

document.body.onkeyup = this._keyupHandler.bind(this);	
document.body.onkeypress = this._keypressHandler.bind(this);	
document.body.onmspointerup = this._mspointerupHandler.bind(this);
}, {	
//...	

В первую очередь мы видим определение пространства имен Application в качестве контейнера класса PageControlNavigation. Его конструктор принимает element, который содержит объект (div contenthost в default.html), или он создаёт новый экземпляр, если ничего не задано. Конструктор, кроме того, принимает параметр options, который задаётся в атрибуте этого элемента data-win-options. Элемент управления страницы затем присоединяет своё содержимое к этому корневому элементу, добавляет прослушиватель для события WinJS.Navigation.onnavigated и устанавливает прослушиватели для клавиатуры, мыши и событий изменения размера. Затем он ждёт, пока кто-нибудь вызовет WinJS.Navigation.Navigate, что происходит в обработчике события activated в js/default.js, для того, чтоб перейти либо на домашнюю страницу, либо на последнюю просмотренную страницу, если было перезагружено предыдущее состояние сеанса работы с программой:

if (app.sessionState.history) {
nav.history = app.sessionState.history;
}
args.setPromise(WinJS.UI.processAll().then(function () {
if (nav.location) { nav.history.current.initialPlaceholder = true; return nav.navigate(nav.location, nav.state);
} else {
return nav.navigate(Application.navigator.home);
}
}));

Когда это случается, активируется обработчик PageControlNavigator _navigated, что, в свою очередь, приводит к вызову WinJS.UI.Pages.render для выполнения загрузки, содержимое затем будет присоединено в виде элементов-потомков к элементу управления средства навигации:

_navigated: function (args) {	
var that = this;	
var newElement = that._createPageElement();	
var parentedComplete;	
var parented = new WinJS.Promise(function (c) { parentedComplete = c; });

args.detail.setPromise( WinJS.Promise.timeout().then(function () {
if (that.pageElement.winControl && that.pageElement.winControl.unload) {
that.pageElement.winControl.unload();
}
return WinJS.UI.Pages.render(args.detail.location, newElement, args.detail.state, parented);
}).then(function parentElement(control) { that.element.appendChild(newElement); that.element.removeChild(that.pageElement); that.navigated();
parentedComplete();
})
);
},

Здесь вы можете видеть, как PageControlNavigator вызывает событие unload предыдущей страницы. После этого содержимое новой страницы добавляется в DOM и затем содержимое старой страницы убирается. Вызов that.navigated затем сбрасывает состояние this.element.

Совет. В JavaScript-коде элемента управления страницы вы можете использовать this.element.querySelector вместо document.querySelector если вы лишь хотите просмотреть содержимое элемента управления страницы и не нуждаетесь в обходе всего DOM. Так как this.element - это лишь узел, он не имеет других методов обхода наподобие getElementById.

Вот как, друзья мои, это работает! В дополнение к примеру "Элементы управления HTML-страниц" (http://code.msdn.microsoft.com/windowsapps/Page-Controls-sample-568b10b4), и для показа конкретного примера работы этих механизмов в реальном приложении, код в примере HereMyAm3d конвертирован для использования данной модели его единственной страницей. Для того чтобы выполнить эту конверсию, я начал с нового проекта, использующего шаблон Приложение навигации для того, чтобы получить настроенную структуру страничной навигации. Затем я скопировал или импортировал необходимый код и ресурсы из HereMyAm3c, в основном, в pages/home/home.html, home.js и home.css. И вспомните, как я говорил, что вы можете открыть элемент управления страницы напрямую в Blend (и почему страницы имеют ссылки на WinJS)? В качестве упражнения, откройте этот проект в Blend. Во-первых, вы увидите, что всё отображается в default.html, но вы так же можете открыть саму home.html и редактировать лишь эту страницу.

Вам следует заметить, что WinJS вызывает WinJS.UI.processAll в процессе загрузки элемента управления страницы, таким образом нам не нужно беспокоиться об этих деталях. С другой стороны, перезагрузка состояния приложения при previousExecutionState==terminated нуждается в некотором внимании. Так как это происходит в событии WinJS.Application.onactivated прежде чем любые элементы управления страницами загружены, и до того, как PageControlNavigator хотя бы инициализирован, нам нужно помнить об этом условии, о том, что метод ready домашней страницы может позже создать ее экземпляр в соответствии с данными из app.sessionState. Для этого мы просто записываем еще один флаг, названный initFromState, в app.sessionState (он содержит значение true, если previousExecutionState равняется terminated и false в иных случаях).

Врезка: WinJS.Namespace.define and WinJS.Class.define

WinJS.Namespace.define (http://msdn.microsoft.com/library/windows/apps/br212667.aspx) предоставляет короткое имя для шаблона пространства имен JavaScript. Это помогает уменьшить загрязнение глобального пространства имен, так как каждое пространство имен, определенное приложением, это лишь отдельных объект в глобальном пространстве имен, который может предоставить доступ к любому количеству других объектов, функций и так далее. Это широко используется в WinJS и, так же, рекомендовано для приложений, где вы определяете всё, что вам нужно в модуле - внутри блока (function() { ... })() и затем выборочно экспортируете переменные или функции посредством пространства имен. Коротко говоря, используя пространство имен в любое время вы, фактически, добавляете любые глобальные объекты или функции!

Здесь используется следующий синтаксис: var ns = WinJS.Namespace.define(<name>, <members>), где <name> - это строка (точки допустимы), и <members> - это любой объект, заключенный в {}. Кроме того, команда WinJS.Namespace.defineWithParent(<parent>, <name>, <members>) определяет то же самое в пространстве имен <parent>.

Если вы вызываете WinJS.Namespace.define для того же самого <name> несколько раз, элементы <members> комбинируются. При возникновении коллизии, наиболее поздно добавленный элемент побеждает. Например:

WinJS.Namespace.define("MyNamespace", { x: 10, y: 10 }); WinJS.Namespace.define("MyNamespace", { x: 20, z: 10 });
//MyNamespace == { x: 20, y: 10, z: 10}

WinJS.Class.define (http://msdn.microsoft.com/library/windows/apps/br229813.aspx) это, в свою очередь, сокращение для шаблона объекта, определяющее конструктор, в итоге экземпляр такого объекта может быть создан с использованием new.

Синтаксис: var className = WinJS.Class.define(<constructor>, <instanceMembers>, <staticMembers>) где <constructor> это функция, <instanceMembers> это объект со свойствами и методами класса, и <staticMembers> это объект, к свойствам и методам которого можно получить прямой доступ посредством конструкции <className>.<member> (без использования ключевого слова new).

Варианты: WinJS.Class.derive(<baseClass>, ...) создаёт подкласс (... это тот же самый список аргументов, как и в случае с define) используя прототипное наследование, и WinJS.Class.mix(<constructor>, [<classes>]) описывает класс, который комбинирует инициализированный экземпляр (и статическое представление) класса в один или большее количество других <classes> и инициализирует объект с помощью <constructor>.

Наконец, заметьте, что, так как описание класса просто генерирует объект, WinJS.Class.define обычно используется внутри модуля, а результирующий объект экспортируется в приложение в качестве члена пространства имен. После этого вы можете использовать команду <namespace>.<class> в любом месте приложения.

Врезка: помощь IntelliSense

В приложения для Магазина Windows могут быть включены определенные структуры разметки, внутри комментариев, часто начинающиеся с тройного слэша, ///. Их используют Visual Studio и Blend для того, чтобы обеспечить поддержку IntelliSense в редакторах кода. Вы увидите, например, комментарий вида /// <reference path…/>, который создаёт взаимоотношения между вашим текущим файлом скрипта и другим скриптом для разрешения внешних функций и переменных. Этот механизм разъяснен на странице "IntelliSense для JavaScript" (http://msdn.microsoft.com/library/bb385682.aspx) в документации. Для вашего собственного кода, в особенности, для пространств имен и классов, которые вы используете из других частей приложения, используйте эти структуры комментариев при описании собственных интерфейсов для IntelliSence. Подробности вы можете найти в материале "Расширение IntelliSence для JavaScript" (http://msdn.microsoft.com/library/hh874692.aspx) и просмотреть JavaScript-файлы WinJS, в которых есть множество примеров.

Процесс и стили навигации

Понимая взаимосвязь элемента управления страницы, WinJS.UI.Pages, WinJS.Navigation, и PageControlNavigator можно ясно увидеть, как организовать навигацию между несколькими страницами внутри контекста одной HTML-страницы (например, default.html). При созданном экземпляре PageControlNavigator и заданном посредством WinJS.UI.Pages элементе управления страницы, нужно лишь вызвать WinJS.Navigation.Navigate с соответствующим URI данного элемента управления страницы (его идентификатором). Эта команда загрузит данную страницу и добавит её в DOM внутри того элемента, к которому прикреплен PageControlNavigator, выгрузив любую предыдущую страницу. В результате данная страница будет видима, таким образом произойдёт "перемещение" к странице в ожидаемой пользователем форме. Кроме того, вы можете использовать другие методы WinJS.Navigating для того, чтобы перемещаться вперед и назад по стеку навигации с помощью его свойств canGoBack и canGoForward, которые позволяют вам активировать и деактивировать элементы управления навигацией. Просто помните, что всё время вы находитесь в том же контексте вашей хост-страницы, где вы создали элемент управления PageControlNavigator.

В качестве примера, создайте новый проект, используя шаблон Приложение таблицы (Grid app) и обратите внимание на следующее:

На всякий случай, шаблон Приложение с разделением (Split App) работает похожим образом, когда каждый элемент списка на pages/items привязан, при активизации, к перемещению на pages/split .

В любом случае, шаблон Приложение таблицы, кроме того, служит примером того, что мы называем стилем навигации Хаб-Раздел-Сведения (Hub-Section-Details). Здесь домашняя страница приложения представляет собой центральную страницу, где пользователь может в полной мере изучить приложение. Щелчок по заголовку группы осуществляет навигацию к странице раздела, второму уровню организации, где отображены лишь элементы этой группы. Щелчок по элементу (на центральной странице или на странице разделов) переносит нас на страницу сведений для данного элемента. Вы можете, конечно, реализовать данный стиль навигации любым желаемым способом. Шаблон Приложение таблицы использует элементы управления страниц, WinJS.Navigation и PageControlNavigator. (Контекстное масштабирование (семантический зум, semantic zoom), как мы увидим в лекции 5, так же поддерживается в качестве инструмента навигации для переключения между центральными страницами и страницами разделов.)

Альтернативная модель навигации - это плоский (flat) стиль, который просто имеет один уровень иерархии. Здесь навигация происходит на любую из страниц в любое время посредством панели навигации (navigation bar) (она появляется вместе с панелью приложения, смотрите лекцию 1 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"). При использовании элементов управления страницы и PageControlNavigator, элементы управления навигацией могут просто запускать WinJS.Navigation.Navigate для этой цели. Обратите внимание, что при таком стиле навигации кнопка Назад обычно не используется.

Информацию об этих стилях, вместе со многими другими аспектами пользовательского интерфейса, касающимися навигации, можно обнаружить в материале "Проектирование навигации для приложений Магазина Windows" (http://msdn.microsoft.com/library/windows/apps/hh761500.aspx). Это - важный материал для дизайнеров.

Врезка: Страницы первоначальной идентификации пользователя и лицензионного соглашения (EULA) в приложении

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

Обычно подобные страницы появляются лишь при первом запуске приложения. Если пользователь ввёл подходящие идентификационные данные, их можно сохранить для последующего использования, воспользовавшись API Windows.Security.Credentials.PasswordVault (http://msdn.microsoft.com/library/windows/apps/windows.security.credentials.passwordvault.aspx). Если пользователь принял EULA, сведения об этом должны быть сохранены среди данных приложения и повторно загружены всякий раз, когда приложению нужно это проверить. Эти параметры (идентификационные данные и факт соглашения с лицензией) следует сделать доступными посредством чудо-кнопки Параметры. Правовая информация, кстати, так же как и лицензионное соглашение, всегда следует делать доступными посредством Параметров. Смотрите материал "Руководство и контрольный список для элементов управления входом" (http://msdn.microsoft.com/library/windows/apps/hh965453.aspx).

Оптимизация переключения страниц: показать и скрыть

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

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

Вы можете использовать разделенный (основные данные - подробные данные) вид, конечно, но это подразумевает разделение доступного рабочего пространства экрана. Альтернатива заключается в том, чтобы постоянно, всё время, держать страницу со списком полностью загруженной. Вместо того чтобы переходить к сведениям об элементе тем способом, который мы рассматривали, просто выведите детальную страницу (смотрите WinJS.UI.Pages.render) в другой div, который занимает весь экран и перекрывает список, а затем сделайте этот div видимым. Когда вы закрываете страницу сведений, просто скройте элемент div и установите innerHTML в значение "". Используя этот подход вы получите тот же эффект, что и при навигации между страницами, но всё будет происходить гораздо быстрее. Кроме того, вы можете применить анимации WinJS, такие, как enterContent (http://msdn.microsoft.com/library/windows/apps/Hh701582.aspx) и exitContent (http://msdn.microsoft.com/library/windows/apps/hh701585.aspx) для того, чтобы сделать переходы более динамичными.

Обратите внимание на то, что так как PageControlNavigator предоставляется шаблоном как часть вашего приложения, вы можете модифицировать его как вам будет угодно для того, чтобы реализовать подобную возможность более последовательным способом.

События WinRT и removeEventListener

Обычной практикой в HTML и JavaScript, особенно для веб-сайтов, является то, что мы уже делали в данном учебном курсе. Мы вызывали addEventListener для задания обработчика события или просто присваивали обработчик события свойству on<event> какого-либо объекта. Часто эти обработчики просто объявляют в качестве встроенных анонимных функций:

var myNumber = 1;
element.addEventListener(<event>, function (e) { myNumber++; } );

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

Для того, чтобы убедиться, что подобные переменные доступны данной анонимной функции, когда она позже будет вызвана в качестве обработчика события, JavaScript-движок создаёт замкнутое выражение (closure), структуру данных, описывающую локальные переменные, доступные данной функции. Обычно замкнутое выражение представляет собой небольшой участок памяти, но в зависимости от кода внутри обработчика события, замкнутое выражение может включить в себя всё глобальное пространство имён, что потребует весьма значительного выделения памяти!

Каждое такое замкнутое выражение увеличивает объем памяти или рабочий набор (working set) приложения, поэтому хорошо поддерживать минимальный объем подобных данных. Например, объявление отдельных именованных функций, которые имеют собственную область видимости - уменьшит размер необходимых замкнутых выражений.

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

Обычно это не то, о чём вам нужно думать. Когда объекты, такие, как HTML-элементы, уничтожаются, как в случае, когда элементы страницы выгружаются из DOM, связанные с ними прослушиватели автоматически удаляются и ресурсы, занятые замкнутыми выражениями так же освобождаются. Однако, в приложениях для Магазина Windows, написанных на HTML и JavaScript есть и другие источники событий, для которых приложение может добавить прослушиватели в то время, как данные объекты никогда не уничтожаются. Это могут быть объекты из WinJS, объекты из WinRT, window и document. Данные Прослушиватели должны быть соответствующим образом очищены, в противном случае приложение будет иметь утечки памяти (память, которая выделена, но никогда не освобождается при операции сборки мусора).

Особого внимания требуют события, которые исходят от объектов WinRT. Из-за сущности уровня проекции, который делает WinRT доступным в WinJS, WinRT ограничивается хранением ссылок на обработчики событий JavaScript (известных так же как делегаты (delegates)), пока замкнутые выражения JavaScript хранят ссылки на некоторые объекты WinRT. В результате наличия подобных перекрестных ссылок, эти замкнутые выражения могут никогда не быть очищенными.

Это не проблема, помните, если приложение всегда прослушивает конкретные события. Например, события suspending и resuming - это те события, которые приложение обычно прослушивает в течение всего времени жизни приложения, в итоге, любые связанные с ними выделения памяти будут очищены при завершении работы приложения. То же самое справедливо для большинства прослушивателей, которые вы можете добавить для событий объектов window и document, которые постоянно существуют во время жизни приложения.

Утечки памяти, однако, возникают, когда приложение прослушивает события объектов WinRT лишь временно и пренебрегает непосредственным вызовом removeEventListener, или когда приложение вызывает addEventListener для одного и того же события несколько раз (в таком случае вы получите несколько замкнутых выражений). В случае с элементом управления страницы, как обсуждалось в данной лекции, обычная практика заключается в вызове addEventListener в методе ready страницы для некоторого WinRT-объекта. Когда вы делаете это, убедитесь в том, что есть соответствующий данному вызову вызов removeEventListener в методе страницы unload, который освободит ресурсы, занятые замкнутым выражением. Я сделал это в примере HereMyAm3d с datarequested, для ясности.

В этом учебном курсе события WinRT, на которые вам следует обратить внимание, выделены специальным цветом, как datarequested (за исключением текста, который является гиперссылкой). Это напоминание для проверки того, нужен ли явный вызов removeEventListener. Опять же, если вы всегда прослушиваете событие, удалять прослушиватель не нужно, но если вы добавили его, когда загружали элемент управления страницы, вам практически гарантированно понадобится выполнить этот дополнительный вызов. Особенно обратите внимание на то, что примеры не обязательно уделяют внимание этой особенности, поэтому не повторяйте примеры, не задумываясь об этом. И, наконец, обратите внимание на то, что события от объектов WinJS не нуждаются в подобном внимании, так как библиотека уже обрабатывает удаление прослушивателей событий.

В следующих лекциях я напомню вам о том, что мы только что обсудили на нашей первой значимой встрече с событиями WinRT. В любом случае, будьте внимательны к цветовому выделению текста.

Завершение истории promise-объектов

Ух ты! Мы прошли большой путь в этой лекции, через множество тонких деталей того, как строятся приложения, и как они выполняются (или не выполняются!). Вы могли заметить, что наша постоянная тема касалась promise-объектов - они появлялись почти в каждом разделе. На самом деле, WinJS и WinRT изобилуют асинхронными операциями и выполняются они с помощью получения отложенных результатов, promise-объектов.

Я хочу завершить эту лекцию, однако, завершив историю promise-объектов, так как они обеспечивают гораздо более обширную функциональность, чем мы использовали. Демонстрацию того, что мы рассмотрим здесь, можно найти в примере "WinJS Promise" (http://code.msdn.microsoft.com/windowsapps/Promise-e1571015), а если вам нужна самая полная история асинхронных операций, прочтите материал "Использование асинхронности в среде выполнения Windows для создания быстрых и гибких приложений" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/03/28/windows.aspx) в блоге разработчиков Windows 8.

Давайте сделаем шаг назад и уточним, что, на самом деле означает "promise". Говоря просто, это объект, который возвращает значение, простое или сложное, когда-то в будущем. Способ, благодаря которому вы узнаёте, когда доступно это значение - это вызов методов promise-объекта then или done с обработчиком завершения (completed handler). Этот обработчик будет вызван со значением, предоставленным promise-объектом (с обещанным значением) (с результатом (result)), когда это значение будет готово - это происходит немедленно, если значение уже доступно. Более того, вы можете вызывать then/done множество раз для одного и того же promise-объекта и вы просто получаете тот же самый результат в каждом обработчике завершения. Это не приведет к системному сбою или к чему-то подобному.

Если происходит ошибка, второй параметр у then/done это - обработчик ошибки (error handler), который будет вызван вместо обработчика завершения. В противном случае исключение будет либо поглощено в then, либо передано в цикл событий приложений done, как мы уже говорили об этом.

Третий параметр у then/done - это обработчик прогресса (progress handler), который периодически вызывается асинхронной операцией, поддерживающей его1). Мы уже видели, например, как операции WinJS.xhr периодически вызывают функцию прогресса для отображения изменения "состояния готовности" при загрузке данных, запрошенных с сервера.

Сейчас нет требований к тому, чтобы promise-объект инкапсулировал асинхронные операции или синхронные. Вы можете, фактически, "обернуть" в этот объект любое значение, воспользовавшись статическим методом WinJS.Promise.wrap. Подобный контейнер для уже существующего значения (будущее - это сейчас!) будет ждать своего часа и вызовет обработчик завершения с данным значением, как только вы вызовете then или done. Это позволяет вам использовать любое значение там, где ожидается promise-объект, или возвращать что-то вроде ошибок из функций, которые в противном случае возвращают promise-объекты для асинхронных операций. WinJS.Promise.wraperror существует именно для этой специфической цели.

WinJS.Promise (http://msdn.microsoft.com/library/windows/apps/br211867.aspx), кроме того, поддерживает набор полезных статических методов, вызываемых напрямую из WinJS.Promise вместо того, чтобы пользоваться каким-то конкретным экземпляром promise-объекта.

В дополнение к использованию функций наподобие as и wrap, вы так же можете создать promise-объект из заготовки, используя команду new WinJS.Promise(<init> [, <oncancel>). Здесь <init> - это функция, которая принимает диспетчеры (dispatcher) завершения, ошибки и прогресса, а oncancel - это необязательная функция, которая вызывается в ответ на WinJS.Promise.Cancel. Диспетчеры - это то, что вы вызываете, чтобы вызвать любые обработчики завершения, ошибки или прогресса, заданные методам promise-объекта then или done, в то время как oncancel - это ваша собственная функция, которую promise-объект вызовет, если он подвергнется операции отмены. Создание новых promise-объектов подобным способом обычно используют, когда создают собственные асинхронные функции. Например, мы увидим, как это используется для упаковывания асинхронного рабочего веб-процесса (web worker) в лекции 5 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой". Кроме того, если возможностей WinJS.Promise.as недостаточно, создание promise-объектов подобным образом полезно для упаковывания других операций (не только значений) в структуру promise-объекта, в итоге, такая операция может быть объединена в цепочку или соединена с другими операциями. Например, если у вас есть библиотека, которая обменивается данными с веб-сервисом посредством обычного асинхронного XmlHttpRequest, вы можете упаковать каждое API этой библиотеки в Promise-объект. Вы можете, кроме того, использовать новый promise-объект для того, чтобы упаковать множество асинхронных операций (или других promise-объектов!) из различных источников в единый promise-объект, в то время, как join или any не дадут вам нужного уровня контроля. Другой пример - инкапсуляция специфических функций обработки завершения, ошибок, прогресса операции в promise-объект, как при реализации механизма множественных вызовов поверх отдельных XHR-операций, для перехвата доступа к стандартному интерфейсу индикатора прогресса, или для добавления фонового ведения журнала, или подсистемы аналитики с вызовами сервиса, в итоге вашему коду никогда не понадобится знать об этих механизмах.

Что мы только что изучили:

Лекция 4. Элементы управления, их стилизация и привязка данных

Здесь читателю предложено описание устройства стандартных элементов управления, описана методика управления их внешним видом с использованием стилей, рассмотрены вопросы привязки данных

Файлы к данной лекции Вы можете скачать  здесь.

Элементы управления - это одни из тех вещей, от которых никуда не деться, особенно в нашей культуре, одержимой технологиями, как та, которая окружает многих из нас. Даже не самые высокотехнологичные устройства, вроде велосипедов и разных садоводческих инструментов, имеют элементы управления. Но это не проблема - это реальная необходимость. Элементы управления - это средства, с помощью которых намерения людей переводятся в область механики и электроники и они созданы для того, чтобы приглашать человека к взаимодействию. Когда я это писал, на самом деле, я сидел в самолёте и разглядывал все элементы управления в поле моего зрения. Маленький мальчик, сидевший в следующем ряду, казалось, занят тем же, и большая кнопка "вызвать стюардессу" просто умоляла о том, чтобы её нажали!

Элементы управления, безусловно, необходимы приложениям для Windows 8, и они приглашают пользователей ткнуть в них, зовут прикасаться, нажимать, перетаскивать их. (Они, кроме того, приглашают пользователей, у которых не совсем чистые руки, а так же - пользователей-малышей. Кто-нибудь уже сделал планшетный компьютер, который можно мыть в посудомоечной машине?). Windows 8, конечно, предоставляет богатый набор элементов управления, написанных с использованием HTML, CSS, и JavaScript. Что наиболее заметно в этой связи, так то, что с самых ранних стадий проектирования, Microsoft решила избегать принуждения HTML/JavaScript-разработчиков к использованию элементов управления, которые не соответствовали бы тому, что уже знают эти разработчики, а именно, разработчики могут использовать HTML-элементы управления, такие, как <button>, стилизованные с помощью CSS и привязанные к JavaScript с использованием функций наподобие addEventListener и свойств, таких, как on<event>.

Конечно, вы можете использовать эти внутренние элементы управления HTML в приложениях для Windows 8, так как эти приложения выполняются на базе тех же подсистем рендеринга HTML/CSS, что и в Internet Explorer. Нет проблем. Здесь даже есть специальные классы, псевдо-классы и псевдо-элементы, которые дают вам точные средства настройки стилей, как мы увидим ниже. Но главный вопрос заключался в том, как реализовать элементы управления, специфичные для Windows 8, такие, как тумблеры (toggle switch) и элементы просмотра списков (list view), чтобы позволить вам работать с ними тем же способом - то есть - объявлять их в разметке, настраивать их внешний вид с помощью CSS и привязывать их к JavaScript-сценариям с помощью addEventListener и свойств on<event>.

В результате всего этого вы, разработчики HTML/JavaScript, можете найти всё это в WinJS, без необходимости заглядывать в WinRT. Другими словами, если вы обратили внимание на большой набор API в пространстве имен Windows.UI.Xaml (что составляет около 40% WinRT), знаете что? Вы можете полностью всё это игнорировать! Вместо этого вы будете использовать элементы управления из WinJS, которые поддерживают декларативную разметку, стилизацию с использованием CSS и так далее, что означает, что элементы управления Windows (и элементы управления, созданные разработчиками, следующие той же модели), в конечном счете, отображаются в DOM вместе со всем остальным, что делает их доступными в таком виде, который вы уже знаете и понимаете.

Рассказ об элементах управления в Windows 8, на самом деле, больше, чем одна лекция. Здесь мы рассмотрим, преимущественно, те элементы управления, которые работают с простыми данными (одиночными значениями) и включаются в макет страницы как элементы DOM. Включение в DOM, на самом деле, это причина того, что вы можете стилизовать элементы и управлять ими (в HTML и JavaScript), используя стандартные механизмы, и основная часть этой лекции посвящена показу внешнего вида параметров стилизации, которые вам доступны. В заключительной части данной лекции мы так же рассмотрим соответствующие темы, касающиеся привязки данных: создания взаимодействия между свойствами объектов, содержащих данные, и свойствами элементов управления (в том числе, со стилями). Таким образом, элементы управления отражают то, что происходит с данными.

Рассказ продолжится в лекции 5, "Коллекции и элементы управления для вывода коллекций", где мы посмотрим на элементы управления для работы с коллекциями - те, которые работают с потенциально большими наборами данных - и рассмотрим дополнительные возможности привязки данных, относящиеся к таким элементам управления. Кроме того, мы обратим особое внимание на мультимедиа-элементы (изображения, аудио, видео) в лекции 4 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript", точно названной "Мультимедиа", так как эти элементы управления имеют множество уникальных особенностей. Похожим образом, те элементы, которые используются, преимущественно, для создания макетов (таких, как сеточный (grid) макет или макет на основе гибких окон (flex box)), как описано в лекции 6, "Макет". И мы, кроме того, рассмотрим множество эл ементов пользовательского интерфейса, которые не включаются, как таковые, в макет - такие, как панели приложения (app bars), всплывающие элементы (flyouts), как мы увидим в лекции 1 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript".

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

Врезка: Важные материалы по элементам управления

Прежде чем мы продолжим, вам полезно будет узнать о двух важных материалах в Центре разработчиков Windows, к которым вы, что весьма вероятно, будете периодически обращаться. Первый - это полный список элементов управления (http://msdn.microsoft.com/library/windows/apps/hh465453.aspx), который содержит описания всех элементов управления, которые вам доступны, как мы подытожим позже в данной лекции. Второй - это всесторонний "Указатель рекомендаций по взаимодействию с пользователем" (http://msdn.microsoft.com/library/windows/apps/hh465424#ui_controls), который описывает лучшие подходы к использованию основных элементов управления и сценарии, в которых они не используются. Это очень ценный ресурс и для вас и для вашего дизайнера.

Модель управления для HTML, CSS и JavaScript

Снова повторюсь, что когда Microsoft создавала среду для разработчиков, мы стремились к высокой степени единообразия между встроенными элементами управления HTML, элементами WinJS и пользовательскими элементами управления. Я называю всё это "элементы управления", так как все они приводят к похожему опыту взаимодействия пользователя и системы: всё это различные визуальные элементы, с помощью которых пользователь взаимодействует с приложением. В этом смысле каждый такой элемент имеет три части:

Стандартные элементы управления HTML, конечно, уже имеют выделенную для них разметку, позволяющую объявлять их, наподобие <button>, <input>, и <progress>. Элементы управления WinJS и пользовательские элементы управления, лишены преимуществ существующих стандартов, их объявляют, используя некие корневые элементы, обычно - <div> или <span>, с двумя особыми атрибутами data-*: data-win-control и data-win-options. Значение data-win-control задаёт полное имя для публичной функции-конструктора, которая создаёт элемент управления в качестве дочернего элемента по отношению к корневому. Второе, data-win-options - это JSON-строка, содержащая пары ключ-значение, разделенные запятыми: { <key1>: <value1>, <key1>: <value2>, ... }.

Подсказка. Если вы только что внесли изменение в data-win-options и ваше приложение, при следующем запуске, завершает работу без видимой причины (и не выдавая исключение), проверьте синтаксические ошибки в строке параметров. Например, подобное поведение характерно для случаев, когда забывают закрывающую фигурную скобку }.

Функция-конструктор принимает два параметра: корневой элемент (родитель) и объект с параметрами. Удобная команда WinJS.Class.define позволяет создавать подобные функции, делая весьма удобным процесс определения элементов управления (так поступает и WinJS). Конечно, так как атрибуты data-*, в соответствии со спецификацией HTML5, полностью игнорируются подсистемой рендеринга HTML/CSS, нужны дополнительные усилия для того, чтобы превратить элемент с подобными атрибутами в настоящий элемент управления в DOM. И это, как я уже говорил ранее, цель существования методов WinJS.UI.process и WinJS.UI.processAll. Как мы скоро увидим, эти методы обрабатывают атрибуты с параметрами и передают получившийся объект и корневой элемент конструктору функции, заданной в data-win-control.

Результатом этой простой декларативной разметки, дополненной WinJS.UI.process/processAll является то, что элементы управления WinJS и пользовательские элементы управления являются, как и прочие, обычными DOM-элементами. На них могут ссылаться API для обхода DOM, они могут быть целями для стилизации с использованием полного набора CSS-селекторов (как мы увидим в галерее стилей, ниже). Они могут прослушивать внешние события, как и другие элементы и могут вызывать собственные события, реализуя методы [add/remove]EventListener и свойства on<event>. (WinJS, опять же, предоставляет стандартную реализацию addEventListener, removeEventListener и dispatchEvent для этой цели.)

Давайте посмотрим на элементы управления, которые мы можем использовать для Windows 8-приложений, начав с HTML- и WinJS-элементов. В обоих случаях мы посмотрим на их базовый внешний вид, как создаются их экземпляры, рассмотрим параметры, которые можно к ним применить.

Элементы управления HTML

Надеюсь, элементы управления HTML не нуждаются в долгих предварительных пояснениях. Они описаны в справочных материалах по HTML5, как в http://www.w3schools.com/tags/default.asp , и показаны в стандартном "светлом" стиле, как на Рис. 4.1 и Рис. 4.2. (В следующем разделе вы узнаете подробности о таблицах стилей WinJS). Важно отметить, что большинство внедренных объектов не поддерживается, за исключением специфических элементов управления ActiveX, смотрите материал "Перенос веб-приложения" (http://msdn.microsoft.com/library/windows/apps/hh465143.aspx).

Создание или создание экземпляров объектов HTML-элементов управления работает так, как вы можете ожидать. Вы можете объявить их в разметке, используя атрибуты для настройки параметров, краткое описание которых дано в таблице, следующей за Рис. 4.2. Вы, кроме того, можете создавать их программно, из JavaScript, вызывая new с соответствующим конструктором, настраивая свойства и прослушивателей событий так, как вам хочется, и добавлять новые элементы в DOM туда, где они нужны. Приложения для Windows 8 не предлагают в этом вопросе ничего нового.

Для того, чтобы увидеть пример создания и использования этих элементов управления, обратитесь к примеру "Основные элементы управления HTML" (http://code.msdn.microsoft.com/windowsapps/Common-HTML-controls-and-09a72a24) из Windows SDK, из которого получены изображения, приведенные на Рис. 4.1 и Рис. 4.2.

Стандартные элементы управления HTML5 в стандартном "светлом" стиле (таблица стилей ui-light.css в WinJS)


увеличить изображение

Рис. 4.1.  Стандартные элементы управления HTML5 в стандартном "светлом" стиле (таблица стилей ui-light.css в WinJS)

Стандартные элементы управления HTML5 в стандартном "светлом" стиле (таблица стилей ui-light.css в WinJS)


увеличить изображение

Рис. 4.2.  Стандартные элементы управления HTML5 в стандартном "светлом" стиле (таблица стилей ui-light.css в WinJS)

Таблица 4.1.
Элемент управленияРазметкаОбычные параметрыСодержимое элемента (внутренний текст/HTML)
Button (Кнопка)<button type="button">(обратите внимание, что без задания типа (type), тип по умолчанию - "submit")Текст кнопки
Button (Кнопка)<input type="button"> <input type="submit"> <input type="reset">value (текст кнопки)n/a
Checkbox (Флажок)<input type="checkbox">value, checkedn/a (используйте элемент Label (Метка) вокруг флажка для того, чтобы добавить кликабельный текст)
Drop Down List (Выпадающий список)<select>size="1" (по умолчанию), multiple, selectedIndexМножество элементов <option>
Email (Поле ввода адреса электронной почты)<input type="email">value (начальный текст)n/a
File Upload (Отправка файла)<input type="file">accept (mime-типы), mulitplen/a
Hyperlink (Гиперссылка)<a>href, targetТекст ссылки
ListBox (Список)<select> с размером > 1size (число больше, чем 1), multiple, selectedIndexМножество элементов <option>
Multi-line Text (Многострочное текстовое представление)<textarea>cols, rows, readonly, data-placeholder (так как в placeholder есть ошибка)Исходное текстовое содержимое
Number (Числовое поле ввода)<input type="number">value (Исходный текст)n/a
Password (Поле ввода пароля)<input type="password">value (Исходный текст)n/a
Phone Number (Поле ввода телефонного номера)<input type="tel">value (Исходный текст)n/a
Progress (Индикатор выполнения)<progress>value (начальная позиция), max (самая большая позиция; минимум - 0); отсутствие значений делает его неопределеннымn/a
Radiobutton (Переключатель)<input type="radiobutton">value, checked, defaultCheckedМетка переключателя
Rich Text (Поле ввода форматированного текста)<div>contentEditable="true"HTML-содержимое
Slider (Ползунок)<input type="range">min, max, value (initial position), step (increment)n/a
URL (Поле ввода URL-адреса)<input type="url">value (Исходный текст)n/a

Два механизма, которые добавляют что-то HTML-элементам управления - это таблицы стилей WinJS и дополнительные методы, свойства и события, которые подсистема рендеринга Microsoft добавляет к большинству HTML-элементов. Этому посвящены два следующих раздела.

Таблицы стилей WinJS: ui-light.css, ui-dark.css и стили win-*

WinJS поставляется с двумя параллельно существующими таблицами стилей, которые предоставляют множество стилей по умолчанию и классов стилей для приложений Магазина Windows: ui-light.css и ui-dark.css. Вы всегда будете использовать либо один, либо второй, так как они взаимоисключающи. Первый предназначен для приложений, которые ориентированы на отображение текста, так как темный текст на светлом фоне обычно легче читается (поэтому данная тема часто используется в приложениях чтения новостей, книг, журналов и так далее, в том числе для изображений в публикующихся книгах!). Тёмная тема, с другой стороны, предназначена для приложений, ориентированных на мультимедийные данные, такие, как просмотрщики изображений и видео, где вы хотите обратить внимание пользователя на подобное содержимое.

Обе таблицы стилей задают множество классов стилей win-*, о которых я обычно думаю как о пакетах стилей, которые эффективно добавляют стили и поведение, основанное на CSS (наподобие псевдо-класса :hover), которые превращают стандартные HTML-элементы управления в их вариант, специфичный для Windows 8. Среди них - win-backbutton для кнопок, win-ring, win-medium, и win-large для кольцевых индикаторов выполнения, win-small для элемента управления выставления оценок, win-vertical для вертикального ползунка, и win-textareal для div, поддерживающего правку содержимого. Если вам нужны детали, поищите по их именам на закладке Правила стилей (Style Rules) в Blend.

Расширения для HTML-элементов

Как вы уже, наверное, знаете, есть множество разрабатываемых стандартов для HTML и CSS. До тех пор, пока разработка не завершена, реализация этих стандартов в различных браузерах обычно бывает доступна с помощью префикса поставщика. В дополнение к этому, разработчики браузеров иногда добавляют собственные расширения к DOM API для различных элементов.

В случае с приложениями для Магазина Windows, конечно, вам не нужно беспокоиться о различиях между браузерами, но так как эти приложения исполняются на основе механизмов Internet Explorer, полезно знать об этих расширениях, которые всё еще применимы. Они приведены в таблице ниже, вы можете найти полное описание элементов (http://msdn.microsoft.com/library/windows/apps/hh767345.aspx) в документации, узнать там любые интересующие вас подробности (их слишком много, чтобы излагать их здесь).

Если вы уже работали с HTML5 и CSS3 в Internet Explorer, вы можете задаться вопросом, почему в таблице не показаны различные анимации (msAnimation*), переходы (msTransition*) и свойства трансформации (msPerspective* and msTransformStyle), вместе с msBackfaceVisibility. Это потому, что эти возможности теперь стандартны и не нуждаются в префиксах поставщиков для Internet Explorer 10 или для приложений Магазина Windows (хотя, их вариант с префиксом ms* всё еще работает).

Таблица 4.2.
МетодыОписание
msMatchesSelectorОпределяет, удовлетворяет ли элемент управления некоторым условиям.
ms[Set | Get | Release]PointerCaptureОсуществляет захват, восстановление и освобождение указателя для элемента.
Свойства стиля (в element.style)Описание
msGrid*, msRow*Получает или задаёт расположение элемента внутри CSS-сетки.
События (добавьте "on" для свойств события)Описание
mscontentzoomВызывается, когда пользователь меняет масштаб элемента (Ctrl+ +/-, Ctrl +колесо мыши), жесты сжатия и расширения.
msgesture[change | end | hold | tap | pointercapture]Жест событий ввода (смотрите лекцию 3 курса "Пользовательский интерфейс приложений для Windows 8 с использованием HTML, CSS и JavaScript").
msinertiastartЖесты событий ввода (смотрите лекцию 3 курса "Пользовательский интерфейс приложений для Windows 8 с использованием HTML, CSS и JavaScript").
mslostpointercaptureЭлемент теряет указатель (ранее установленный msSetPointerCapture.
mspointer[cancel | down | hover | move | out | over | up]События ввода указателя (смотрите лекцию 3 курса "Пользовательский интерфейс приложений для Windows 8 с использованием HTML, CSS и JavaScript").
msmanipulationstatechangedСостояние элемента изменилось после манипуляции

Элементы управления WinJS

Windows 8 определяет множество элементов управления, которые помогают приложениям удовлетворить требования к дизайну. Как отмечено ранее, они реализованы в WinJS а не в WinRT для приложений, написанных на HTML, CSS и JavaScript. Это позволяет данным элементам управления естественным образом интегрироваться с другими DOM-элементами. Каждый элемент управления определяется как часть пространства имен WinJS.UI с использованием WinJS.Class.define, где имя конструктора совпадает с именем элемента управления. В итоге, полное имя конструктора для элемента управления наподобие Rating (Оценка) выглядит как WinJS.UI.Rating.

Самые простые элементы, которые мы рассмотрим здесь, это DatePicker, Rating, ToggleSwitch, и Tooltip, стиль которых по умолчанию показан на Рис. 4.3.

Стили по умолчанию (светлые) для простых элементов управления WinJS


Рис. 4.3.  Стили по умолчанию (светлые) для простых элементов управления WinJS

Элемент управления WinJS.UI.Tooltip, вам нужно это знать, может использовать любой HTML-код, включая другие элементы управления, поэтому он идёт дальше простых текстовых всплывающих подсказок, где HTML автоматически предоставляется для атрибута title. Мы увидим дополнительные примеры дальше.

Итак, еще раз, элементы управления WinJS объявляют в разметке, присоединяя атрибуты data-win-control и data-win-options к некоторым корневым элементам. Это обычно элементы div (они задают блоки) или span (внутренние элементы), так как они не добавляют слишком много лишнего, но использовать можно любой элемент. Эти элементы, конечно, могут иметь атрибуты id и class, если они нужны. Параметры, доступные для этих элементов, приведены в таблице, ниже, они включают в себя события, которые могут быть подключены посредством строки data-win-options, если нужно. Для того, чтобы найти полную документацию по этим элементам управления, начните со "Списка элементов управления" (http://msdn.microsoft.com/library/windows/apps/hh465453.aspx) и следуйте по ссылкам, ведущим к подробным описаниям конкретных элементов.

Таблица 4.3.
Полное имя конструктора в data-win-controlПараметры data-win-options (обратите внимание, что имена событий используют префикс "on" в синтаксисе атрибутов)
WinJS.UI.DatePickerСвойства: calendar, current, datePattern, disabled, maxYear, minYear, monthPattern, yearPattern События: onchange
WinJS.UI.RatingСвойства: averageRating, disabled, enableClear, maxRating, tooltipStrings (массив строк размера maxRating), userRating События: oncancel, onchange, onpreviewchange
WinJS.UI.TimePickerСвойства: clock, current, disabled, hourPattern, minuteIncrement, periodPattern. (Обратите внимание, что данные в current всегда будут 15 Июля 2011 так как в этот день нет известных переходов на летнее время.) События: onchange
WinJS.UI.ToggleSwitchСвойства: checked, disabled, labelOff, labelOn, title События: onchange
WinJS.UI.TooltipСвойства: contentElement, innerHTML, infotip, extraClass, placement События: onbeforeclose, onbeforeopen, onclosed, onopened Методы: open, close

Что касается строки data-win-options, она содержит пары ключ-значение, одну для каждого свойства или события, разделенные запятыми, в форме { <key1>: <value1>, <key1>: <value2>, ... }. Для событий, имена которых находятся в строке параметров, они всегда начинаются с on, вы должны задать имя обработчика событий, который вы хотите им назначить.

В коде JavaScript вы так же можете назначить обработчики событий, используя <element>.addEventListener ("<event>", ...), где <element> это элемент, для которого был объявлен элемент управления и <event> без "on", как обычно. Для того чтобы получить прямой доступ к свойствам и событиями, используйте <element>.winControl.<property>. Объект winControl создаётся, когда создаётся экземпляр элемента управления WinJS и прикрепляется к элементу, в итоге, там доступны эти параметры.

Создание экземпляров объектов элементов управления WinJS

Как мы уже видели много раз, элементы управления WinJS объявляются в разметке с атрибутом data-* и их экземпляры не создаются до тех пор, пока не будет вызван WinJS.UI.process(<element>) для отдельного элемента или WinJS.UI.processAll для всех подобных элементов в DOM. Для того, чтобы понять этот процесс, опишем, что делает WinJS.UI.process для отдельного элемента.

  1. Превращает строку data-win-options в объект, содержащий параметры.
  2. Извлекает конструктор, заданный в data-win-control и вызывает команду new с обнаруженной функцией, передавая сведения о корневом элементе и объекте с параметрами.
  3. Конструктор создаёт любые необходимые ему элементы-потомки в корневом элементе.
  4. Объект возвращённый из конструктора - объект элемента управления - сохраняется в свойстве winControl корневого элемента.

Очевидно, что большая часть работы, на самом деле, происходит в конструкторе. Как только она будет выполнена, другой JavaScript-код (как в методе activated) может вызывать методы, манипулировать свойствами, добавлять прослушиватели событий и к корневому элементу и к объекту winControl. Последние, очевидно, должны быть методами, свойствами и событиями, специфичными для элементов управления WinJS. WinJS.UI.processAll, в свою очередь, просто обходит DOM в поиске атрибутов data-win-control и выполняет для каждого из них. Как вы воспользуетесь тем и другим, это, на самом деле, ваш выбор: processAll проходится по всей странице (или по элементу управления страницы - там, где объект document на неё ссылается), в то время как process позволяет вам контролировать последовательность происходящего или создавать экземпляры элементов управления, для которых вы динамически добавляете разметку. Обратите внимание, что в обоих случаях возвращённое значение является promise-объектом, в итоге, если вам нужно предпринять дополнительные шаги после того, как обработка завершена, предоставьте обработчик завершения методу done promise-объекта.

Кроме того, полезно понимать, что и process и processAll - это лишь вспомогательные функции. Если вам нужно, вы можете напрямую вызвать new для конструктора элемента управления, передав ему элемент и объект с параметрами. Это позволит создать элемент управления и автоматически присоединить его к переданному элементу. Кроме того, вы можете передать для элемента null, в таком случае, конструктор элемента управления WinJS создаст новый div-элемент, содержащий элемент управления, который в подобном случае отсоединён от DOM. Это позволит вам, например, создать элемент управления в скрытом виде и присоединить его к DOM тогда, когда он будет нужен.

Для того чтобы увидеть всё это в действии, мы скоро взглянем на некоторые примеры с элементами управления Rating (Оценка) и Tooltip (Подсказка). В первую очередь, однако, нам нужно обсудить то, что касается строгой обработки (strict processing).

Строгая обработка и функции processAll

WinJS имеет три функции для обхода DOM: WinJS.UI.processAll, WinJS.Binding.processAll (которую мы рассмотрим позже в этой лекции), и WinJS.Resources.processAll (которую мы увидим в лекции 6 Курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"). Каждая из этих функций ищет специфические атрибуты data-win-* и затем предпринимает дополнительные действия с использованием данного содержимого. Эти действия, однако, могут включать в себя вызов некоторого количества функций различных типов:

Подобные действия представляют риск атак методом внедрения, если функция processAll вызывается для недоверенного HTML, такого, как любая разметка, полученная из веб. Для уменьшения этого риска, WinJS имеет понятие строгая обработка (strict processing), которая принудительно используется внутри всех HTML/JavaScript-приложений. Эффект строгой обработки заключается в том, что любая функция, отображающаяся в разметке, которую могут встретить методы processAll, должна быть "маркирована для обработки", в противном случае обработка не состоится. Сама по себе маркировка - это обычное свойство, названное supportedForProcessing в объекте функции, которая установлено в true.

Функции, возвращаемые из WinJS.Class.define, WinJS.Class.derive, WinJS.UI.Pages.define, и WinJS.Binding.converter автоматически маркированы подобным образом. Для других функций, вы можете либо установить свойство supportedForProcessing в true напрямую, либо воспользоваться одной из маркирующих функций:

WinJS.Utilities.markSupportedForProcessing(myfunction); WinJS.UI.eventHandler(myHandler);
WinJS.Binding.initializer(myInitializer);

//Так тоже можно
<namespace>.myfunction = WinJS.UI.eventHandler(function () {
});

Обратите внимание на то, что функции, поступающие напрямую из WinJS, такие, как конструкторы элементов управления WinJS.UI.*, и функции WinJS.Binding.* маркированы по умолчанию.

В итоге, если вы ссылаетесь на пользовательскую функцию из вашей разметки, убедитесь в том, что вы маркировали её соответствующим образом. Но это только для ссылок из разметки: вам не нужно маркировать функции, которые вы присваиваете свойствам on<event> в JavaScript или передаёте в addEventListener.

Упражнение: Элемент управления WinJS.UI.Rating (Оценки)

Хорошо, теперь, когда мы обсудили вопрос строгой обработки, посмотрим конкретные примеры работы с элементами управления WinJS.

Для начинающих, вот разметка для элемента управления WinJS.UI.Rating, где параметры задают значения двух начальных свойств и обработчик событий:

<div id="rating1" data-win-control="WinJS.UI.Rating"
data-win-options="{averageRating: 3.4, userRating: 4, onchange: changeRating}">
</div>

Для создания экземпляра этого элемента управления, нам нужен один из следующих вызовов:

WinJS.UI.process(document.getElementById("rating1")); WinJS.UI.processAll();

Повторюсь, обе эти функции возвращают promise-объект, но нет необходимости вызывать done, только если вы не нуждаетесь в дальнейшей обработке созданного экземпляра объекта, или в обработке исключений, которые могут произойти (в противном случае они будут потеряны). Кроме того, обратите внимание на то, что функция changeRating заданная в разметке, должна быть глобально видима и маркирована для обработки, иначе экземпляр элемента управления не будет создан.

Альтернативным образом мы можем создать экземпляр элемента управления и настроить его параметры программно. В разметке:

<div id="rating1" data-win-control="WinJS.UI.Rating"></div>

И в коде:

var element = document.getElementById("rating1");
WinJS.UI.process(element);	
element.winControl.averageRating = 3.4;	
element.winControl.userRating = 4;	
element.winControl.onchange = changeRating;

Последние три строки выше, кроме того, могут быть записаны как показано ниже, с использованием метода WinJS.UI.setOptions, но подобный подход не рекомендуется, так как он сложнее для отладки:

var options = { averageRating: 3.4, userRating: 4, onchange: changeRating }; WinJS.UI.setOptions(element.winControl, options);

Так же, мы можем напрямую создать экземпляр элемента, в таком случае разметка неспецифична:

<div id="rating1"></div>

И мы вызываем new в конструкторе самостоятельно:

var newControl = new WinJS.UI.Rating(document.getElementById("rating1"));
newControl.averageRating = 3.4;	
newControl.userRating = 4;	
newControl.onchange = changeRating;

Или, как упомянуто выше, мы можем полностью убрать разметку, позволить конструктору создать элемент для нас (div) и присоединить его к DOM самостоятельно:

var newControl = new WinJS.UI.Rating(null,	
{ averageRating: 3.4, userRating: 4, onchange: changeRating });
newControl.element.id = "rating1";	
document.body.appendChild(newControl.element);
Подсказка. Если вы сталкиваетесь со странными ошибками при создании экземпляров объектов в последних двух случаях, проверьте, не забыли ли вы new и таким образом попытались напрямую вызвать функцию конструктора.

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

Для того, чтобы увидеть этот элемент управления в действии, пожалуйста посмотрите пример "HTML-Элемент управления Rating" (http://code.msdn.microsoft.com/windowsapps/Rating-control-sample-4666c750).

Упражнение: элемент управления WinJS.UI.Tooltip (Подсказка)

С большинством других простых элементов управления, а именно, DatePicker, TimePicker и ToggleSwitch, вы можете работать так же, как мы только что работали с Ratings. Все изменения касаются особенностей их свойств и событий. Снова, начните со страницы "Список элементов управления" (http://msdn.microsoft.com/library/windows/apps/hh465453.aspx) и просмотрите информацию по каждому из элементов управления для того, чтобы узнать подробности о них. Кроме того, обратитесь за работающими примерами к примерам "HTML-элементы управления DatePicker и TimePicker" (http://code.msdn.microsoft.com/windowsapps/Date-and-time-picker-sample-0424c7c2) и "HTML-элемент управления ToggleSwitch" (http://code.msdn.microsoft.com/windowsapps/ToggleSwitch-control-sample-84c0aacb).

Элемент управления WinJS.UI.Tooltip, однако, слегка отличается от вышеупомянутых, поэтому я показал особенности его использования. Во-первых, для того, чтобы присоединить подсказку к конкретному элементу, вы можете либо добавить атрибут data-win-control к этому элементу, либо поместить сам элемент внутрь данного элемента управления:

<!-- Прямо присоединяем подсказку к целевому элементу -->
<targetElement data-win-control="WinJS.UI.Tooltip">
</targetElement>

<!-- Размещаем элемент внутри подсказки -->
<span data-win-control="WinJS.UI.Tooltip">
<!--Здесь будет элемент, для которого предназначена подсказка -->
</span>

<div data-win-control="WinJS.UI.Tooltip">
<!--Здесь будет элемент, для которого предназначена подсказка -->
</div>

Во-вторых, свойство contentElement элемента управления-подсказки вполне может содержать имя другого элемента, который будет отображён, когда подсказка будет вызвана. Например, рассмотрим этот фрагмент скрытого HTML в нашей разметке, который содержит другие элементы управления:

<div style="display: none;">
<!--Здесь находится элемент содержимого. Он помещен внутрь скрытого контейнера
в итоге, невидим для пользователя, пока не будет отображён благодаря элементу управления Tooltip.-->
<div id="myContentElement">
<div id="myContentElement_rating">
<div data-win-control="WinJS.UI.Rating" class="win-small movieRating"
data-win-options="{userRating: 3}">
</div>
</div>
<div id="myContentElement_description">
<p> Вы можете разместить любой DOM-элемент в качестве содержимого, 
даже имеющий внутри элементы управления WinJS. 
Элемент управления TootTip сделает родительским объектом для 
размещенного элемента собственный контейнер и 
блокирует события взаимодействия в этом элементе,
 так как не предлагает модель взаимодействия.p>
</div>
<div id="myContentElement_picture">
</div>
</div>
</div>

Мы можем сослаться на это, например, так:

<div data-win-control="WinJS.UI.Tooltip"	
data-win-options="{infotip: true, contentElement: myContentElement}">
<span>My piece of data</span>	
</div>

Когда вы проводите над текстом (мышью, или с помощью жеста на поддерживающем подобную функцию оборудовании), появится подсказка:

Код этого упражнения взят из примера "HTML-элемент управления Tooltip" (http://code.msdn.microsoft.com/windowsapps/Tooltip-control-sample-cb24c2ce), вы можете перейти к этому примеру и самостоятельно посмотреть, как это работает.

Работа с элементами управления в Blend

Прежде чем мы займёмся стилизацией объектов, полезно будет выделить некоторые дополнительные функции в Blend для Visual Studio, которые касаются элементов управления. Как я говорил в Видео 2-2, закладка Активы (Assets) в Blend предоставляет вам быстрый доступ ко всем HTML-элементам и элементам управления WinJS (среди многих других элементов), которые вы можете просто перетащить и поместить на любую страницу, которая отображается на монтажной панели (смотрите Рис. 4.4). Это действие создаст базовую разметку, такую, как div с атрибутом data-win-control для элементов управления WinJS. Затем вы можете перейти на панель Атрибуты HTML (HTML Attributes) (справа) для настройки параметров в разметке (Рис. 4.5).

HTML-элементы (слева) и элементы управления WinJS (справа), на закладке Активы (Assets) в Blend


увеличить изображение

Рис. 4.4.  HTML-элементы (слева) и элементы управления WinJS (справа), на закладке Активы (Assets) в Blend

Закладка Атрибуты HTML (HTML Attributes) в Blend показывает параметры элемента управления WinJS, их правка повлияет на атрибут data-win-options в разметке


Рис. 4.5.  Закладка Атрибуты HTML (HTML Attributes) в Blend показывает параметры элемента управления WinJS, их правка повлияет на атрибут data-win-options в разметке

Далее, найдите время для того, чтобы загрузить пример "Основные HTML-элементы управления" (http://code.msdn.microsoft.com/windowsapps/Common-HTML-controls-and-09a72a24) в Blend. Это - отличная возможность для того, чтобы опробовать Интерактивный режим (Interactive mode) Blend, пройдя на страницу и посмотрев на взаимодействие между монтажной панелью и закладкой Динамическая DOM (Live DOM) (Рис. 4.6). Открыв проект, перейдите в интерактивный режим, выбрав в меню Вид > Интерактивный режим (View > Interactive Mode), нажав Ctrl+Shift+I или щёлкнув на крайнюю слева маленькую кнопку в правом верхнем углу монтажной панели. Затем выделите Сценарий 5 (Progress Introduction, Введение в индикаторы выполнения) в выпадающем списке, который откроет страницу, показанную на Рис. 4.6. Затем выйдите из интерактивного режима (с помощью тех же команд) и вы сможете кликать всюду на этой странице. Краткая демонстрация использования интерактивного режима подобным образом дана в файле Video 4-1 среди материалов, которые прилагаются к курсу.

Взаимодействие между монтажной панелью и Динамической DOM в Blend


увеличить изображение

Рис. 4.6.  Взаимодействие между монтажной панелью и Динамической DOM в Blend

Щелчок по элементу либо в панели динамической DOM, либо на монтажной панели выделяет этот элемент и там, и там. Это сделано для упрощения выделения и стилизации объектов. (Clicking on an element in either live DOM or designer highlight it in the other. This makes for easy selection and styling.)

В примере, посвященном основным HTML-элементам управления, вы увидите, что для встроенных элементов управления в Динамической DOM присутствует лишь единственный элемент, как это и должно быть, в то время как все внутренние детали являются неотъемлемой частью подсистемы рендеринга HTML/CSS. С другой стороны, загрузите вместо этого примера пример "HTML-элемент управления Rating" (http://code.msdn.microsoft.com/windowsapps/Rating-control-sample-4666c750) и разверните элемент div, который содержит один из подобных элементов управления. Здесь вы увидите дополнительные дочерние элементы, которые настраивают этот элемент управления (Рис. 4.7), и вы можете воспользоваться панелями HTML-атрибутов и CSS-свойств, которые находятся справа. Вы можете видеть что-то подобное (с даже большим количеством подробной информации) в Проводнике DOM (DOM Explorer) в Visual Studio, когда приложение исполняется (Рис. 4.8).

Развернув структуру элемента управления WinJS в панели Динамическая DOM в Blend, можно увидеть элементы, использованные для его построения


Рис. 4.7.  Развернув структуру элемента управления WinJS в панели Динамическая DOM в Blend, можно увидеть элементы, использованные для его построения

Развернув структуру элемента управления WinJS в Проводнике DOM в Visual Studio, так же можно увидеть полные подробности об элементе управления


увеличить изображение

Рис. 4.8.  Развернув структуру элемента управления WinJS в Проводнике DOM в Visual Studio, так же можно увидеть полные подробности об элементе управления

Стилизация элементов управления

Сейчас мы подошли к материалу, где мы, преимущественно, будем разглядывать множество красивых картинок: различные способы, которыми могут быть стилизованы элементы управления HTML и WinJS. Как мы уже обсудили, это производится, в любом случае, с помощью CSS, либо в таблице стилей, либо путём присваивания свойств style.*, имея в виду, что приложение имеет полный контроль над внешним видом элементов управления. На самом деле, абсолютно вся разница между HTML-элементами управления в приложениях для Магазина Windows и теми же самыми элементами на веб-страницах, заключается во внешнем виде и только в нём.

Для элементов управления HTML и WinJS, применимы CSS-стандарты, включая псевдо-селекторы наподобие :hover, :active, :checked и так далее, вместе со стилями с префиксами -ms-* для новых стандартов.

Для HTML-элементов управления, кроме того существуют дополнительные -ms-* стили, которые не являются частью CSS3 - для изоляции специфических частей этих элементов управления. То есть, так как составляющие подобных элементов управления не существуют раздельно в DOM, псевдо-селекторы, наподобие ::-ms-check для изоляции значка флажка, или ::-ms-fill-lower для изоляции левой или нижней части ползунка - позволяют вам связать стилизацию с внутренними механизмами системы рендеринга. В противоположность этому, все подобные части элементов управления WinJS могут быть адресованы через DOM, в итоге они просто стилизуются с помощью специальных классов win-*, определенных в таблицах стилей WinJS. То есть, эти элементы управления просто отрисовываются с данными классами стилей. Стили по умолчанию определены в таблицах стилей WinJS, но приложение может переопределить любой аспект этих стилей, приведя элемент управления к любому необходимому вам внешнему виду.

В некоторых случаях, как уже говорилось, определенные win-* классы определяют пакеты стилей для использования с элементами управления HTML, такие, как win-backbutton, win-vertical (для ползунка) и win-ring (для кольцевого индикатора выполнения). Они предназначены для приведения внешнего вида стандартных элементов управления к виду, похожему на вид специальных системных элементов управления.

Кроме того, существует несколько стилей -ms-* общего назначения, которые могут быть применены ко многим элементам управления (и, в целом, к любым элементам), вместе с некоторыми общими win-* классами стилей WinJS. Информация об этом собрана в следующей таблице.

Таблица 4.4.
Стиль или классОписание
-ms-user-select: none | inherit | element | text | autoВключает или отключает возможность выделения элемента. Установка в none особенно полезна для предотвращения выделения в текстовых элементах.
-ms-zoom: <percentage>Оптический зум (увеличение).
-ms-touch-action: auto | none (и больше)Производит специальную адаптацию элементов управления для сенсорного взаимодействия, активируя усовершенствованную модель взаимодействия.
win-interactiveПредотвращает стандартное поведение элементов управления, включенных в элементы FlipView и ListView (смотрите лекцию 5).
win-swipeableУстанавливает стили -ms-touch-action для элементов управления внутри ListView, так, что по ним можно провести (для выделения) в одном направлении, что не вызывает прокрутки в другом направлении.
win-small, win-medium, win-largeВарианты размеров для некоторых элементов управления.
win-textarealУстанавливает типичные стили для правки текста.

Для лучшего изучения вышеупомянутых вопросов, да и других тоже, потратьте некоторое время на следующие материалы: "WinJS CSS-классы для типографики" (http://msdn.microsoft.com/library/windows/apps/hh770582.aspx), "WinJS CSS-классы для HTML-элементов управления" (http://msdn.microsoft.com/en-us/library/windows/apps/Hh770562.aspx), и "CSS-классы для элементов управления WinJS" (http://msdn.microsoft.com/library/windows/apps/hh440966.aspx). Мне так же хочется предоставить вам краткие сведения обо всех других стилях с префиксами поставщиков (или селекторах), которые поддерживаются подсистемой CSS приложений для Магазина Windows. Обратитесь к следующей таблице. Стили с префиксами поставщиков для анимаций, трансформаций и переходов всё еще поддерживаются, хотя они больше не нужны, так как эти стандарты уже приняты. Я составил данный список, так как документация по этим вопросам может быть довольно сложной: вы можете перемещаться по страницам материала "Каскадные таблицы стилей" (http://msdn.microsoft.com/library/windows/apps/Hh996828.aspx) в документах для того, чтобы увидеть, как мало добавлено к CSS, которую вы уже знаете.

Таблица 4.5.
ОбластьСтили
Фоны и границы-ms-background-position-[x | y]
Модель контейнера-ms-overflow-[x | y]
Базовый пользовательский интерфейс-ms-text-overflow (для вывода многоточий) -ms-user-select (задаёт или возвращает информацию о возможности выделения пользователем текста внутри элемента) -ms-zoom (Оптическое увеличение)
Гибкое окно-ms-[inline-]flexbox (значения для display); -ms-flex и -ms-flex-[align | direction | order | pack | wrap]
Градиенты-ms-[repeating-]linear-gradient, -ms-[repeating-]radial-gradient
Сетка-ms-grid и -ms-grid-[column | column-align | columns | column-span | grid-layer | row | row-align | rows | row-span]
Высокая контрастность-ms-high-contrast-adjust
Области-ms-flow-[from | into] вместе с методом MSRangeCollection
Текст-ms-block-progression, -ms-hyphens и -ms-hypenate-limit-[chars | lines | zone], -ms-text-align-last, -ms-word-break, -ms-word-wrap, -ms-ime-mode, -ms-layout-grid и -ms-layout-grid-[char | line | mode | type], и -ms-text-[autospace | kashida-space | overflow | underline-position]
Другое-ms-writing-mode

Галерея стилей: элементы управления HTML

Сейчас пришло время насладиться внешним видом возможностей стилизации приложений для Магазина Winsows. Многое может быть сделано с помощью стандартных стилей, и, затем, можно воспользоваться специальными стилями и классами, как показано в иллюстрациях к этому разделу. Подробности всех этих примеров можно найти в примере "Основные элементы управления HTML" (http://code.msdn.microsoft.com/windowsapps/Common-HTML-controls-and-09a72a24).

Кроме того, взгляните на хороший пример "Применение цветовой темы приложения" (http://code.msdn.microsoft.com/windowsapps/Theme-roller-sample-64b679f2). Этот замечательный пример позволяет вам настроить основной и дополнительный цвета для приложения, увидеть, как эти цвета воздействуют на различные элементы управления, и создаёт около 200 строк отличного CSS-кода, который вы можете скопировать в собственную таблицу стилей. Это сильно поможет вам создавать цветовые темы для приложения, которые поощряются как средства создания индивидуальности приложения в русле рекомендаций по дизайну Windows 8 и позволяют приложению отличаться от системы (Отметим, что элементы управления в элементах интерфейса, предоставляемых системой, наподобие всплывающего элемента для подтверждения при создании дополнительных плиток, будут стилизованы с использованием системных цветов. Приложение не может этим управлять.)

Кнопка (Button)

Текстовая область (Text Area)

Индикатор выполнения (Progress)

Выделение (Select)

Флажок/переключатель (Checkbox/Radiobutton)

Отправка файла (File upload)

Ввод обычного текста (Text input (most forms))

Индикатор выполнения (Progress)

Ввод пароля (Text input (password))

Ползунки (Range/Sliders)

Поле со списком/список (Combo/list box)

Примечание. Хотя здесь это не показано, вы можете использовать стили -ms-scrollbar-* для настройки полос прокрутки, которые появляются у содержимого приложения, которое можно прокручивать.

Галерея стилей: элементы управления WinJS

Аналогично, здесь показан внешний вид стилизованных элементов управления WinJS, иллюстрации взяты из SDK-примеров "HTML-элементы управления DatePicker и TimePicker" (http://code.msdn.microsoft.com/windowsapps/Date-and-time-picker-sample-0424c7c2), "HTML-элемент управления Rating" (http://code.msdn.microsoft.com/windowsapps/Rating-control-sample-4666c750), "HTML-элемент управления ToggleSwitch" (http://code.msdn.microsoft.com/windowsapps/ToggleSwitch-control-sample-84c0aacb) и "HTML-элемент управления Tooltip" ( http://code.msdn.microsoft.com/windowsapps/Tooltip-control-sample-cb24c2ce).

В случае с элементами управления WinJS DatePicker и TimePicker, применяют стилизацию HTML-элемента select вместе с псевдо-элементами ::-ms-value и ::-ms-expand. Хочу отметить, что примеры нельзя назвать всеобъемлющими, поэтому на иллюстрациях ниже выделены основные моменты:

Элемент управления Rating (Оценки) имеет состояния, которые могут быть стилизованы в дополнение к его звёздочкам и элементу управления в целом. Классы win-* идентифицируют эти индивидуальные особенности. Различные комбинации стилей приведены в таблице

Таблица 4.6.
Класс стиля Часть
win-rating Задаёт стиль всего элемента управления
win-star Общий стиль звездочек
win-empty Стиль пустых звездочек
win-fullСтиль заполненных звездочек
Классы .win-starСостояние
win-average Управляет отображением средней оценки (пользователь не указал оценку и свойство averageRating не равно нулю)
win-disabledЭлемент управления заблокирован
win-tentativeЭлемент отображает предварительную оценку
win-userЭлемент отображает оценку, которую выбрал пользователь
ВариантыКлассы (селекторы)
Среднее количество пустых звезд.win-star.win-average.win-empty
Среднее количество заполненных звезд.win-star.win-average.win-full
Отключить пустые звезды.win-star.win-disabled.win-empty
Отключить заполненные звезды.win-star.win-disabled.win-full
Пустые звезды предварительной оценки.win-star.win-tentative.win-empty
Заполненные звезды предварительной оценки.win-star.win-tentative.win-full
Пустые звезды пользовательской оценки.win-star.win-user.win-empty
Заполненные звезды пользовательской оценки.win-star.win-user.win-full

В случае с элементом управления ToggleSwitch (Тумблер), классы win-* идентифицируют его части; состояния задаются неявно. Обратите внимание на то, что часть win-switch - это обычный HTML-ползунок (<input type="range">), поэтому для него вы можете использовать все псевдо-элементы.

И, наконец, элемент управления Tooltip (Подсказка), win-tooltip - это отдельный класс для элементов-подсказок в целом. Элемент может содержать любой другой HTML-код, к которому применяется CSS с использованием обычных селекторов:

У этой подсказки скруглённые углы (This tooltip has rounded corner.)

Эта подсказка овальная (This tooltip is oval.)

Некоторые советы и секреты

Пользовательские элементы управления

Как ни обширен набор элементов управления HTML и WinJS, всегда будет что-то, что вам нужно, но чего система делать не умеет. "Есть ли здесь элемент управления для календаря?", - это вопрос, который мне часто приходится слышать. "А как насчёт элементов для вывода графиков?". Всё это не включено в Windows 8, и несмотря на пожелания обратного, это означает, что вы, или другие разработчики, нуждаются в создании пользовательских элементов управления.

К счастью, всё, что мы уже узнали, особенно об элементах управления WinJS, применимо к пользовательским элементам управления. На самом деле, элементы управления WinJS полностью реализованы с использованием той же модели, которую вы можете использовать напрямую, и если вы посмотрите код WinJS, вы сможете найти множество примеров подобной реализации.

Возвращаясь к уже пройденному материалу, отметим, что элементы управления - это лишь декларативная разметка (создающая элементы в DOM), плюс - применимый CSS, методы, свойства и события, доступные из JavaScript. Для создания подобного элемента управления в модели WinJS, воспользуйтесь следующей стандартной последовательностью шагов:

  1. Задайте пространство имен для вашего элемента (элементов), используя WinJS.Namespace.define для обеспечения области видимости имён и для того, чтобы не включать дополнительные идентификаторы в глобальное пространство имен. (Не добавляйте элементы управления в пространство имен WinJS). Помните, что вы можете вызывать WinJS.Namespace.define множество раз для добавления новых членов, таким образом, приложение обычно имеет единое пространство имен для всех его пользовательских элементов управления.
  2. Внутри этого пространства имен задайте конструктор, используя WinJS.Class.define (или derive), присваивая возвращаемое значение имени, которое вы хотите использовать в атрибутах data-win-control. Его полное имя будет выглядеть как <namespace>.<constructor>.
  3. Внутри конструктора (в форме <constructor>(element, options)):
    • Вы можете распознать любой необходимый вам набор параметров, переданный конструктору; он может быть произвольным. Просто игнорируйте то, что вы не распознали.
    • Если element равен null или undefined, создайте div для использования вместо него.
    • Считая element корневым элементом, содержащим элемент управления, убедитесь, что установили element.winControl=this и this.element=element для того, чтобы ваше решение соответствовало шаблону WinJS.
  4. Внутри WinJS.Class.define, второй аргумент - это объект, который содержит ваши публичные методы и свойства (доступные в созданном экземпляре объекта). Третий аргумент - это объект со статическими методами и свойствами (которые доступны путём обращения к имени класса, без необходимости вызывать new).
  5. Для собственных событий, дополните (WinJS.Class.mix) ваш класс результатами из WinJS.Utilities.createEventProperties(<events>), где <events> это массив имён событий (без префиксов on). Это создаст свойства on<event> в вашем классе для каждого имени из списка.
  6. Кроме того, дополните ваш класс WinJS.UI.DOMEventMixin для того, чтобы добавить стандартные реализации addEventListener, removeEventListener, dispatchEvent и setOptions.1)
  7. В вашей реализации (в разметке и коде), ссылайтесь на определенные вами классы в применяемых по умолчанию таблицах стилей, но это может быть переназначено тем, кто использует элемент управления. Используйте существующие win-* классы для того, чтобы ваши элементы управления соответствовали общему подходу к стилизации.
  8. Обычно хорошим подходом считается организация пользовательских элементов управления в отдельных папках, которые содержат все необходимые HTML, CSS, JS-файлы. Помните, кроме того, что вызов WinJS.Namespace.define для одного и того же пространства имён аддитивен, таким образом, вы можете заполнить одно пространство имён элементами управления, определенными в различных файлах.

Вы можете решить использовать WinJS.UI.Pages, если всё, что вам нужно - это фрагмент HTML/CSS/JavaScript, подходящий для повторного использования, для которого нет особой необходимости во множестве методов, свойств и событий. WinJS.UI.Pages, на самом деле, реализован как пользовательский элемент управления. Точно так же, если то, что вам нужно - это участок HTML, подходящий для повторного использования, с которым вы хотите связать некоторые данные во время выполнения программы, обратитесь к WinJS.Binding.Template, о котором мы поговорим далее. Это не элемент управления - в том виде, как мы его здесь описали - он не поддерживает события, например, но он может быть в точности тем, что вам нужно.

Важно напомнить, что всё в WinJS, вроде WinJS.Class.define и WinJS.UI.DOMEventMixin - это лишь вспомогательные функции для широко распространённых шаблонов. Вам не нужно безальтернативно использовать их, так как в итоге, пользовательский элемент управления - это лишь элемент DOM, такой же, как и все остальные и вы можете создавать такие элементы и управлять ими так, как вы того хотите. Утилиты WinJS лишь ускоряют и упрощают выполнение основных задач.

Примеры пользовательских элементов управления

Вот пара примеров, которые помогут увидеть эти рекомендации в действии. Первый - это то, что Крис Траверс, один из инженеров WinJS, который оказал мне неоценимую помощь с этим курсом, назвал "самым тупым элементом управления, который можно себе представить". Однако, он чётко показывает самые главные структуры:

WinJS.Namespace.define("AppControls", {	
HelloControl: WinJS.Class.define(function (element, options) {
element.winControl = this;	
this.element = element;	
if (options.message) {	
element.innerText = options.message;
}	
})	
});

После того, как элемент определен, вы можете использовать следующую разметку, чтобы WinJS.UI.process/processAll создали экземпляр объекта элемента управления (как встроенного элемента, так как мы использует span в качестве корневого элемента)

<span data-win-control="AppControls.HelloControl" data-win-options="{ message: 'Hello, World'}">
</span>

Обратите внимание на то, что код определения элемента должен быть исполнен перед вызовами WinJS.UI.process/processAll, то есть, чтобы функция конструктора, упомянутая в data-win-control, уже существовала в этот момент.

Для того, чтобы увидеть более полно реализованный элемент управления, вы можете взглянуть на пример "HTML SemanticZoom для пользовательских элементов управления" (http://code.msdn.microsoft.com/windowsapps/SemanticZoom-for-custom-4749edab). Мой друг Кеничиро Танака, из отделения Microsoft в Токио, кроме того, создал элемент управления, показанный на Рис. 4.9 и предоставил упражнение CalendarControl для этой лекции. (Обратите внимание на то, что этот пример лишь частично восприимчив к локализованным параметрам календаря, его не собирались делать полнофункциональным).

Следуя указаниям, данным выше, этот элемент управления определен с использованием WinJS.Class.define внутри пространства имен Control (calendar.js, строки 4-10 показанные здесь [комментарии опущены]).

WinJS.Namespace.define("Controls", {	
Calendar : WinJS.Class.define(	
function (element, options) {	
this.element = element || document.createElement("div");
this.element.className = "control-calendar";	
this.element.winControl = this;

Оставшаяся часть конструктора (строки 12 - 63) создают дочерние элементы, которые определяют элемент управления, обеспечивая то, что каждый участок имеет собственное имя класса, будучи помещенным в пространство имен класса control-calendar, помещенного в корневой элемент, что позволяет независимо стилизовать отдельные части. Таблица стилей по умолчанию, применяемая здесь - это calendar.css; некоторые параметры переназначены в default.css, это отличает два элемента управления, приведенных на Рис. 4.9.

Внешний вид элемента управления Calendar (календарь)


увеличить изображение

Рис. 4.9.  Внешний вид элемента управления Calendar (календарь)

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

Строки 63 - 127 определяют членов элемента управления. Здесь присутствуют два внутренних метода, _setClass и _update, за которыми следуют два публичных метода nextMonth и prevMonth, и три публичных свойства - year, month и date. Эти свойства можно установить посредством строки data-win-options в разметке, или напрямую через объект элемента управления, как мы скоро увидим.

В конце calendar.js вы можете увидеть два вызова WinJS.Class.mix для добавления свойств для событий (здесь лишь одно свойство), и методов стандартных событий DOM, таких, как addEventListener, removeEventListener и dispatchEvent вместе с setOptions:

WinJS.Class.mix(Controls.Calendar, WinJS.Utilities.createEventProperties("dateselected")); 
WinJS.Class.mix(Controls.Calendar, WinJS.UI.DOMEventMixin);

Очень хорошо то, что всё это весьма просто добавлять. WinJS, спасибо!2)

Между calendar.js и calendar.css у нас есть объявление элемента управления. В default.html и default.js мы можем увидеть, как используется элемент управления. На Рис. 4.9 элемент управления слева объявлен в разметке и его экземпляр создан благодаря вызову WinJS.UI.processAll в default.js.

<div id="calendar1" class="control-calendar" aria-label="Calendar 1"	
data-win-control="Controls.Calendar"	
data-win-options="{ year: 2012, month: 5, ondateselected: CalendarDemo.dateselected}">
</div>

Вы можете видеть, как мы используем полное имя конструктора, так же, как и обработчика событий, который мы назначаем событию ondateselected. Но помните, что функции, на которые мы ссылаемся в разметке, как на эти, должны быть маркированы для строгой обработки. Конструктор автоматически маркирован с помощью WinJS.Class.Define, но обработчик события требует дополнительных усилий: мы помещаем функцию в пространство имён (для того, чтобы обеспечить её глобальную видимость) и используем WinJS.UI.eventHandler для того, чтобы выполнить маркировку:

WinJS.Namespace.define("CalendarDemo", {	
dateselected: WinJS.UI.eventHandler(function (e) {	
document.getElementById("message").innerText = JSON.stringify(e.detail) + " selected";
})	
});

Опять же, если вы забыли маркировать функцию подобным образом, экземпляр элемента управления не будет создан. (Уберите контейнер WinJS.UI.eventHandler для того, чтобы это увидеть).

Для демонстрации создания элемента управления за пределами разметки, элемент управления справа на Рис. 4.9 создан так, как показано ниже в div-элементе calendar2:

//Как только мы создали данный элемент управления в коде, мы не зависим от WinJS.UI.processAll. 
var element = document.getElementById("calendar2");

//Как только мы предоставили элемент, он будет автоматически добавлен в DOM
var calendar2 = new Controls.Calendar(element);

//Так как этот обработчик не участвует в процессе обработки разметки, 
calendar2.ondateselected = function (e) {
document.getElementById("message").innerText = JSON.stringify(e.detail) + " selected";

}

Всё готово!

Примечание. При создании управления, которые вы намереваетесь передавать другим разработчикам, есть смысл включить в их код необходимые комментарии, которые обеспечивают метаданные для IntelliSense. Смотрите врезку "Помощь IntelliSense" в лекции 3. Кроме того, следует убедиться в том, что элемент управления полностью поддерживает соглашения, касающиеся специальных возможностей и локализации, как показано в лекции 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой".

Пользовательские элементы управления в Blend

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

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

Далее, элементы управления могут определять, исполняются ли они внутри Blend, в режиме дизайна, если свойство Windows.ApplicationModel.DesignMode.designModeEnabled установлено в true. Один из случаев, когда это весьма полезно, заключается в обработке строк ресурсов. Мы не будем подробно говорить о ресурсах до лекции 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", но это важно знать уже сейчас, что разрешение ресурсов посредством Windows.ApplicationModel.Resources.ResourceLoader, не работает в режиме дизайна в Blend, как это происходит при реальном исполнении приложения. Система напрямую заявляет об этом, выдавая исключение! Таким образом, вы можете использовать в режиме дизайна флаг для того, чтобы предоставить некоторые данные по умолчанию, вместо того, чтобы выполнять поиск данных.

Например, один из первых партнёров, с которым я работал, имел метод для извлечения локализованного URI со своего сервиса поддержки приложения, который не работал в режиме дизайна. Используя флаг режима дизайна, таким образом, мы просто изменили код, приведя его к такому виду:

WinJS.Namespace.define("App.Localization", {
getBaseUri: function () {
if (Windows.ApplicationModel.DesignMode.designModeEnabled) {
return "www.default-base-service.com";
} else {
var resources = new Windows.ApplicationModel.Resources.ResourceLoader();
var baseUri = resources.getString("baseUrl");
return baseUri;
}
}
});

И, наконец, возможно сделать так, чтобы пользовательские элементы управления отображались на закладке Активы (Assets) среди прочих HTML-элементов и элементов управления WinJS. Для того, чтобы это сделать, вам, во-первых, нужен файл OpenAjax Metadata XML (OAM) (http://www.openajax.org/member/wiki/OpenAjax_Metadata_1.0_Specification_Descriptive), который предоставляет всю необходимую информацию для элемента управления, и у вас уже есть достаточно мест, где можно посмотреть подобные файлы. Для того, чтобы их найти, выполните поиск файлов по запросу *._oam.xml в папке Program Files (x86). Вы найдёте кое-что в папке Microsoft Visual Studio 11.0 и в папках с большей глубиной вложенности, в Microsoft SDKs, где располагаются метаданные WinJS. И там и там вы, кроме того, найдёте множество примеров значков размерами 12х12 и 16х16, которые понадобятся для вашего элемента управления.

Если вы посмотрите в папку controls/calendar упражнения CalendarControl к этой лекции, вы найдёте файл calendar_oam.xml и два значка вместе с файлами .js и .css. OAM-файл (который должен иметь окончание имени файла в виде _oam.xml) сообщает Blend о том, как сохранять элемент управления в его панели Активы (Assets), и какой код следует вставить, когда вы перетаскиваете элемент в HTML-файл. Вот содержимое этого файла:

<?xml version="1.0" encoding="utf-8"?>	
<!--Используйте символы подчеркивания или точки в id и name, но не пробелы. -->	
<widget version="1.0"	
spec="1.0"	
id="http://www.kraigbrockschmidt.com/scehmas/ProgrammingWin8_JS/Controls/Calendar"

name="ProgWin8_JS.Controls.Calendar"	
xmlns="http://openajax.org/metadata">

	
<author name="Kenichiro Tanaka" />	

<!-- title предоставляет имя, которое появляется в панели Активы (Assets) в Blend
(иначе используется widget.name). -->
<title type="text/plain"><![CDATA[Calendar Control]]></title>

<!--обеспечивает подсказку для панели Активы (Assets). -->
<description type="text/plain"><![CDATA[A single month calendar]]></description>

<!-- значок (12x12 и 16x16) предоставляет маленький значок
в панели Активы (Assets). -->	
<icons>	
<icon src="calendar.16x16.png" width="16" height="16" />	
<icon src="calendar.12x12.png" width="12" height="12" />	
</icons>	

<!-- Этот элемент описывает то, что будет вставлено в .html файл;
закомментируйте всё лишнее -->	
<requires>	
<!-- Код элемента управления -->	
<require type="javascript" src="calendar.js" />	

<!-- Код таблицы стилей элемента -->
<require type="css" src="calendar.css" />

<!-- Встроенные скрипты для заголовка документа -->
<require type="javascript"><![CDATA[WinJS.UI.processAll();]]></require>

<!-- Встроенный CSS для раздела стилей в заголовке документа -->
<!--<require type="css"><![CDATA[.control-calendar{}]]></require>-->
</requires>

<!-- Что вставлять в тело элемента управления; убедитесь в том, что это корректный HTML
иначе Blend не позволит вставить его -->
<content>
<![CDATA[
<div class="control-calendar" data-win-control="Controls.Calendar" 
  data-win-options="{ year: 2012, month: 6 }"></div>
]]>
</content>
</widget>

Когда вы добавите все пять файлов в проект в Blend, вы увидите значок и подпись элемента управления в панели Активы (Assets) (а если поместить указатель мыши над элементом, появится всплывающая подсказка):

Если вы перетащите этот элемент управления на HTML-страницу, вы увидите, как в её различные части добавлено следующее:

<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<script src="calendar.js" type="text/javascript"></script>
<link href="calendar.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="control-calendar" data-win-control="Controls.Calendar" data-win-options="{month:6, year:2012}"></div>
</body>
</html>

Но подождите! Что случилось с вызовом WinJS.UI.processAll(), который отмечен в XML тегом script в заголовке? Blend проверил этот участок кода на предмет того, встречается ли подобный вызов в уже загруженных скриптах. Если это так (как обычно бывает с шаблонами проектов), Blend не станет повторять его. Если он сочтёт нужным включить данный вызов, или если вы зададите здесь другой код, Blend вставит его в тег <script> в заголовке.

Кроме того, ошибки в вашем OAM-файле принудят Blend не вставлять элемент управления, таким образом, вам нужно исправить эти ошибки. Когда вы вносите изменения, Blend не перезагружает метаданные до тех пор, пока вы не перезагрузите проект или не переименуете OAM-файл (сохранив часть _oam.xml). Я нахожу последнее более простым, так как blend не заботит то, как выглядит остальная часть имени файла. В процессе переименования, так же, если вы обнаружите, что элемент управления исчез из панели Активы (Assets), это означает, что имеются ошибки в самой структуре OAM XML, такие, как неподходящие символы в значениях атрибутов. В подобных случаях вам понадобится метод проб и ошибок, и, конечно, вы можете обратиться к уже существующих на вашем компьютере OAM-файлам для того, чтобы разобраться с подробностями.

Кроме того, вы можете сделать ваш элемент управления доступным для всех проектов в Blend. Для того, чтобы сделать это, перейдите в папку Program Files (x86)\Microsoft Visual Studio 11.0\Blend, создайте папку Addins, если её там еще нет, создайте в ней подпапку для вашего элемента управления (используя подходящее уникальное имя), и скопируйте все активы вашего элемента управления туда. Когда вы перезапустите Blend, вы увидите, что ваш элемент управления находится в списке в категории Add-ins в панели Активы (Assets):

Подобным подходом можно воспользоваться, если вы создаете элементы управления для использования их другими разработчиками. Вашей программе установки следует просто расположить ваши активы в папку Addins. Что касается использования подобных элементов управления, когда вы перетаскиваете элемент в HTML-файл, необходимо, чтобы активы, необходимые элементу управления (но не значки и не OAM-файл), были скопированы в корневую папку проекта. Затем вы можете перемещать их так, как вам нужно, меняя ссылки на файлы, конечно.

Привязка данных

Как я упоминал во введении к этой лекции, вопросы привязки данных тесно связаны с элементами управления, так как они касаются создания взаимоотношений между свойствами объектов, хранящих данные, и свойствами элементов управления (и стилями в том числе). В этом смысле, элементы управления отражают то, что происходит с данными, а часто это именно то, что разработчик хочет предоставить пользователю приложения.

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

Основная идея привязки данных (data binding) заключается в соединении вместе или "привязывании" свойств двух различных объектов, обычно объекта данных (контекста) и объекта пользовательского интерфейса, о которых обычно говорят как об объекте-источнике и об объекте-приемнике (целевом объекте). Ключевой момент здесь заключается в том, что привязка данных обычно осуществляется для свойств, но не для объектов.

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

Обычный сценарий привязки данных показан на Рис. 4.10, где у нас есть свойства дух элементов пользовательского интерфейса - span и img, привязанные к свойствам объекта, содержащего данные. Таким образом, здесь три связи: (1) свойство span.innerText привязано к свойству source.name; (2) свойство img.src привязано к свойству source.photoUrl; и (3) свойство span.style.color связано с выходом функции преобразования данных, которая преобразует свойство source.userType в цвет.

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


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

Как эти связи ведут себя во время выполнения программы, зависит от направления (direction) каждой из связей, которое может быть одним из следующих:

Единовременная привязка (One-time): значение в свойстве источника (возможно, с конверсией) копируется в целевое свойство в некоторый момент, после чего между ними больше нет никаких взаимоотношений. Это то, что вы автоматически выполняете, передавая переменные конструктору элемента управления, например, или просто присваивая целевому свойству значения, используя свойство-источник. Удобно то, что для подобного рода присваивания применяется декларативный подход, позволяющий обращаться напрямую к атрибутам элементов, как мы увидим позже.

Объект данных (Data Object)

Элемент управления пользовательского интерфейса (UI/Control)

Значение элемента (Item Value)

Инициализация (Initialization)

Нет непрерывного соединения (No ongoing connection)

Односторонняя привязка (One-way): целевой объект прослушивает событие изменения связанного свойства объекта-источника, в результате он может обновиться, используя новые значения. Это обычно используется для обновления элементов пользовательского интерфейса на основе изменений в некоторых данных. Изменения в целевых элементах (в элементах управления пользовательского интерфейса), однако, не отражаются на данных (однако, они могут быть отправлены, как, например, при отправке некоей заполненной формы, что может, в свою очередь, обновить данные, используя иной способ).

Объект данных (Data Object)

Элемент управления пользовательского интерфейса (UI/Control)

Значение элемента (Item Value)

Инициализация (Initialization)

Изменения в данных находят отнажение в пользовательском интерфейсе (Changes to data are reflected in UI)

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

Объект данных (Data Object)

Элемент управления пользовательского интерфейса (UI/Control)

Значение элемента (Item Value)

Инициализация (Initialization)

Изменения в любом месте отражаются и в другом (Changes in either place are reflected in the other)

Привязка данных в WinJS

Сейчас, когда мы узнали о том, что такое привязка данных, мы можем посмотреть, как она может быть реализована в приложениях для Windows 8. Если хотите, вы можете создать любую схему для привязки данных или использовать библиотеку JavaScript стороннего разработчика для работы: всё это, в конечном счете, касается связи свойств объекта-источника данных со свойствами объекта-приёмника.

Если вы похожи на моих предков по отцовской линии, которые не полагались ни на кого, кроме себя, и делали всё сами (бурили скважины, добывали уголь и делали запасные части для машин), вы будете довольны, разрабатывая собственное решение для привязки данных. Но если вы более умеренны по своей природе, как я (благодаря материнской линии), вам понравится, как и мне, если кто-то достаточно умный выполнит какую-то работу за вас. Я благодарен команде разработки WinJS, которая, зная основные нужды привязки данных, создала API WinJS.Binding. Оно поддерживает и единовременную, и одностороннюю привязку, как декларативно, так и программно, вместе с функциями преобразования значений. Сейчас WinJS не предоставляет инструментов для двусторонней привязки, но подобные структуры несложно создать в коде.

Внутри структур WinJS, множественные целевые элементы могут быть привязаны к единственному источнику данных. WinJS.Binding, на самом деле, предоставляет то, что называется шаблонами (templates), обычно - коллекции целевых элементов, которые привязаны к одному и тому же источнику данных. Хотя мы не рекомендуем это делать, однако, можно привязать один целевой элемент к нескольким источникам, но такой конструкцией сложно управлять. Лучший подход в подобной ситуации заключается в том, чтобы заключить разные источники данных в оболочку одного объекта и привязать целевой объект именно к нему.

Лучший способ понять WinJS.Binding - посмотреть, для начала, на то, как мы пишем собственный код для связывания и потом изучить решение, которое предлагает WinJS. Для этого примера мы использует тот же самый сценарий, который показан на Рис. 4.10, где мы имеем объект-источник, привязанный к двум отдельным элементам пользовательского интерфейса, с одним конвертером, который превращает одно из свойств источника в цвет.

Единовременная привязка

Единовременная привязка, как упоминалось выше, это то, что вы делаете, просто присваивая значения свойствам элемента. Вот HTML-код для этого примера:

<!-- Разметка: элемент пользовательского интерфейса, который мы привяжем к объекту-источнику данных -->
<section id="loginDisplay1">	
<p>You are logged in as <span id="loginName1"></span></p>
<img id="photo1"></img>	
</section>

И объект-источник данных:

var login1 = { name: "liam", id: "12345678",
photoURL: "http://www.kraigbrockschmidt.com/images/Liam07.png", userType: "kid"};

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

//"Связывание" одного свойства за один раз, с функцией-конвертером, вызываемой напрямую
var name = document.getElementById("loginName1");	
name.innerText = login1.name;	
name.style.color = userTypeToColor1(login1.userType);	
document.getElementById("photo1").src = login1.photoURL;	
function userTypeToColor1(type) {
return type == "kid" ? "Orange" : "Black";
}

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

Этот код можно найти в Test 1, в упражнении BindingTests для этой лекции. С помощью WinJS мы можем выполнить то же самое, используя декларативный синтаксис и функцию обработки. В разметке, мы используем атрибут data-win-bind для того, чтобы отобразить целевые свойства содержащего их элемента на свойства объекта-источника, который передан функции обработки WinJS.Process.All.

Значение data-win-bind - это строка с парами свойств. Синтаксис каждой пары выглядит как <target property> : <source property> [<converter>], где converter необязателен. Каждый идентификатор свойства может использовать запись с помощью точек, если нужно, и пары свойств разделены двоеточиями, как показано в HTML:

<section id="loginDisplay2">	
<p>You are logged in as	
<span id="loginName2"	
data-win-bind="innerText: name; style.color: userType Tests.userTypeToColor">
</span>	
</p>	
<img id="photo2" data-win-bind="src: photoURL"/>	
</section>

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

<span data-win-bind="this['funky-property']: source"></span>

Похожий синтаксис нужен для атрибутов цели привязки данных, таких, как атрибуты aria-* для реализации специальных возможностей. Так как это не свойства JavaScript, на которые можно сослаться, используя идентификатор с дефисами, необходим специальный конвертер (или инициализатор, как его лучше называть), который называется WinJS.Binding.setAttribute.

<label data-win-bind="this['aria-label']: title WinJS.Binding.setAttribute"></label>

Кроме того, обратите внимание на WinJS.Binding.setAttributeOneTime для единовременной привязки атрибутов.

В любом случае, в итоге мы получаем такой же источник данных, как раньше:

var login2 = { name: "liamb", id: "12345678",
photoURL: "http://www.kraigbrockschmidt.com/images/Liam07.png", userType: "kid"};

Мы превращаем разметку в реальные связи, используя WinJS.Binding.processAll.

//processAll сканирует дерево элементов в поисках data-win-bind, используя заданный объект как контекст данных
WinJS.Binding.processAll(document.getElementById("loginDisplay2"), login2);

Этот код, Test 2 в упражнении, производит тот же результат, что и Test1. Единственное, что нужно добавить - это объявление функции конвертера, которая должна быть глобально доступной и маркирована для обработки. Это можно сделать с помощью пространства имен, которое содержит функцию (опять же, её называют инициализатором, как мы обсудим в разделе "Инициализаторы привязки" ближе к концу этой лекции), созданную с помощью WinJS.Binding.converter:

//Используем пространство имен для экспорта функции из текущего модуля, 
//таким образом WinJS.Binding может найти её
WinJS.Namespace.define("Tests", {	
userTypeToColor: WinJS.Binding.converter(function (type) {	
return type == "kid" ? "Orange" : "Black";	
})
});

Как и в случае с конструктором элементов управления WinJS.Class.define, WinJS.Binding.converter автоматически маркирует функцию, которую он возвращает, как безопасную для обработки.

Кроме того, мы можем поместить объект-источник данных и применимый конвертер в одно и то же пространство имен.1) Например (в Test 3), мы можем поместить объект данных login и фукнцию userTypeToColor в пространство имен LoginData, в итоге, разметка и код будут выглядеть так:

<span id="loginName3"
data-win-bind="innerText: name; style.color: userType LoginData.userTypeToColor">
</span>


WinJS.Binding.processAll(document.getElementById("loginDisplay3"), LoginData.login);

WinJS.Namespace.define("LoginData", {	
login : {	
name: "liamb", id: "12345678",	
photoURL: "http://www.kraigbrockschmidt.com/images/Liam07.png",

userType: "kid"	
},	

userTypeToColor: WinJS.Binding.converter(function (type) {
return type == "kid" ? "Orange" : "Black";	
})	
});

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

Во-первых, WinJS.Binding.processAll, на самом деле, асинхронная функция, которая возвращает promise-объект. Любой обработчик завершения, переданный его методу done, будет вызван при завершении обработки, если у вас есть дополнительный код, который основан на данном состоянии. Во-вторых, вы можете вызвать WinJS.Binding.processAll больше, чем один раз для того же самого целевого элемента, каждый раз задавая различные объекты-источники (контексты данных). Это не заменит существующие привязки, учтите - лишь добавит новые, подразумевая, что вы хотите привязать то же самое целевое свойство к более, чем одному источнику, что, в итоге, приведет к большому беспорядку. Итак, снова, лучше всего собрать эти источники в один объект и привязать целевой объект к нему, используя запись с точкой для идентификации вложенных свойств.

Врезка: свойства привязки данных элеменов управления WinJS

Когда вы задаете в качестве целевых свойства элементов управления WinJS, а не корневого (содержащего их) элемента, имена целевых свойств должны начинаться с winControl. В противном случае вы осуществите привязку к несуществующим свойствам корневого элемента. При использовании winControl, привязываемое свойство служит той же цели, что и задаваемое фиксированное значение в data-win-options. Например, разметка, использованная выше в разделе "Пример: Элемент управления WinJS.UI.Rating (Оценки)" может использовать привязку данных для своих свойств averageRating и userRating, как показано ниже (подразумевая, что myData - это подходящий источник данных):

<div id="rating1" data-win-control="WinJS.UI.Rating"	
data-win-options="{onchange: changeRating}"	
data-win-bind="{winControl.averageRating: myData.average,
winControl.userRating: myData.rating}">	
</div>

Односторонняя привязка

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

В коде, который мы видели выше, если мы меняем login.name после вызова WinJS.Binding.processAll, с элементом управления, выводящим данные, ничего не происходит. Как же нам автоматически обновить выводимую информацию?

Вообще говоря, это требует, чтобы источник данных поддерживал список привязок (bindings), где каждая привязка может описывать свойство источника, свойство целевого объекта и функцию-конвертер. Источник данных, кроме того, нуждается в методах для управления этим списком, наподобие addBinding, removeBinding и так далее. И третье, если привязываемое (или наблюдаемое (observable)) свойство меняется, нужно пройтись по списку привязок и обновить соответствующим образом любые связанные свойства целевых объектов.

Эти требования выглядят достаточно обобщенными. Вы можете представить, что их реализация будет в значительной мере походить на классический шаблонный код. Поэтому, конечно, WinJS предоставляет такую реализацию! В этом контексте источники называются наблюдаемыми объектами (observable objects) и функция WinJS.Binding.as заключает любые объекты в объекты подобной структуры (она возвращает пустой оператор для необъектных сущностей). Наоборот, функция WinJS.Binding.unwrap удаляет эти структуры, если в том есть необходимость. Более того, функция WinJS.Binding.define создаёт конструктор для наблюдаемых объектов вокруг набора свойств (описанных с помощью чего-то вроде пустого объекта, содержащего лишь имена свойств). Подобный конструктор позволяет вам создать экземпляр объекта-источника динамически, как при обработке данных, полученных от онлайнового сервиса.

Давайте посмотрим на код. Вернемся к последнему примеру выше (Test 3), в любое время до или после WinJS.Binding.processAll мы можем взять объект LoginData.Login и сделать его наблюдаемым, как показано ниже:

var loginObservable = WinJS.Binding.as(LoginData.login)

На самом деле, это всё, что нам надо сделать - всё остальное будет таким же, как раньше. Мы можем сейчас изменить связанное значение в объекте loginObservable:

loginObservable.name = "liambro";

Эта команда обновит целевое свойство:

Вот как мы затем создаём и используем многократно используемый класс для наблюдаемого объекта (Test 4 в упражнении BindingTest). Обратите внимание на то, что объект, который мы передаем WinJS.Binding.define содержит имена свойств, но не значения (они будут проигнорированы):

WinJS.Namespace.define("LoginData", {
//...
//LoginClass становится конструктором для прикрепляемого объекта с заданными свойствами
LoginClass: WinJS.Binding.define({name: "", id: "", photoURL: "", userType: "" }),
});

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

var login4 = new LoginData.LoginClass({ name: "liamb",
photoURL: "http://www.kraigbrockschmidt.com/images/Liam08.jpg" });

Осуществляя привязку к этому объекту login, мы видим, что имя пользователя первоначально имеет черный цвет:

//Выполняем связывание 
(первоначально имя пользователя выведено черным цветом) 
WinJS.Binding.processAll(document.getElementById("loginDisplay"), login4);

Обновление свойства userType в источнике (как показано ниже), приведет к обновлению цвета целевого свойства, что, посредством конвертера, происходит автоматически:

login4.userType = "kid";

Реализация двусторонней привязки

Процесс реализации двусторонней привязки достаточно очевиден:

  1. Добавить прослушиватели к подходящим событиям элемента пользовательского интерфейса, которые имеют отношение к привязанным свойствам источника данных.
  2. В обработчиках событий обновить свойства источника данных.

Источник данных должен быть достаточно интеллектуальным для того, чтобы знать, когда новое значение свойства уже является тем же, что и в целевом свойстве, в таком случае он не попытается обновить целевое свойство. Иначе вы попадёте в бесконечный цикл. Код наблюдаемого объекта, который предоставляет WinJS, уже выполняет такую проверку.

Для того, чтобы увидеть пример реализации подобного механизма, обратитесь к примеру "Декларативная привязка" (http://code.msdn.microsoft.com/windowsapps/DeclarativeBinding-bfcb42a5) в SDK, где показано прослушивание события change в текстовых полях и соответствующее обновление свойств в их источниках данных.

Дополнительные возможности привязки

Если вы посмотрите на описание WinJS.Binding (http://msdn.microsoft.com/library/windows/apps/br229775.aspx) в документации, вы увидите в этом пространстве имен множество других полезных вещей. Позвольте мне кратко описать их особенности. (Кроме того, обратитесь к примеру "Программная привязка данных" (http://code.msdn.microsoft.com/windowsapps/ProgrammaticBinding-de038b64) для демонстрации некоторых возможностей).

Если вы уже определили класс (из WinJS.Class.define) и хотите сделать его наблюдаемым, используйте WinJS.Class.mix, как показано ниже:

var MyObservableClass = WinJS.Class.mix(MyClass, WinJS.Binding.mixin, WinJS.Binding.expandProperties(MyClass));

WinJS.Binding.mixin содержит стандартную реализацию функций связывания, которые ожидает WinJS. WinJS.Binding.expandProperties создаёт объект, свойства которого соответствуют свойствам данного объекта (те же самые имена), каждое из которых заключено в подходящую для связывания структуру. На самом деле, этот тип операции полезен только тогда, когда выполняют смешивание, и это то, что WinJS.Binding.define делает с теми самыми странными, не имеющими значений объектами, которые мы передаем ему.

Если вы помните из предыдущего материала, одно из требований для наблюдаемых объектов заключается в том, чтобы они содержали методы для управления списком привязок. Реализация подобных методов содержится в объекте WinJS.Binding.observableMixin. Вот эти методы:

На это опирается еще один mixin-объект, WinJS.Binding.dynamicObservableMixin (то же, что и WinJS.Binding.mixin), который добавляет методы для управления свойствами объекта-источника:

Зачем нам всё это? Это открывает простор для творчества. Вы можете вызвать WinJS.Binding.bind, например, напрямую для любого наблюдаемого источника, когда вы хотите использовать другую функцию для свойства источника. Это похоже на добавление прослушивателей событий для изменения свойства источника, и у вас может быть столько прослушивателей, сколько хотите. Это полезно для настройки двунаправленной привязки данных, и эта возможность не связана с манипуляцией пользовательским интерфейсом. Функция просто вызывается при изменении свойства. Это можно использовать для автоматической синхронизации серверной службы с объектом данных.

Пример "Декларативная привязка данных" (http://code.msdn.microsoft.com/windowsapps/DeclarativeBinding-bfcb42a5), кроме того, показывает вызов bind с объектом в качестве второго параметра, форма, которая позволяет осуществлять привязку к вложенным членам источника данных. Синтаксис похож на этот bind(rootObject, { property: { sub-property: function(value) { ... } } } - что соответствует объекту-источнику. С подобным объектом в качестве второго параметра, bind убеждается в том, что активированы все функции, назначенные вложенным свойствам. В подобном случае значение, возвращаемое bind - это объект с методом cancel, который очистит эту сложную привязку данных.

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

WinJS.Binding, кроме того, имеет несколько интеллектуальных обработчиков множественных изменений одного и того же свойства объекта-источника. После первоначального связывания, дальнейшие уведомления об изменениях асинхронны, и множественные запросы на изменение того же самого свойства объединяются. В итоге, если в нашем примере мы выполним несколько изменений свойства name в быстрой последовательности:

login.name = "Kenichiro"; 
login.name = "Josh"; 
login.name = "Chris";

будет отправлено только одно оповещение о последнем значении, которое и станет значением, которое будет отображено на связанном целевом элементе.

И, наконец, имеется еще несколько функций, находящихся в WinJS.Binding:

Инициализаторы привязки

В наших первых упражнениях мы видели использование функций-конвертеров, которые превращают некоторое количество данных из источника в формат, который ожидает получить свойство-приемник. Но функции, которые вы задаёте в data-win-bind правильнее будет называть инициализаторами (initializer), так как, на самом деле, они вызываются лишь один раз.

Интересно, почему? Разве конвертеры не используются всякий раз, когда связанное свойство источника копируется в целевое свойство? Хорошо, да, но мы, на самом деле, говорим здесь о двух разных функциях. Посмотрите вниманительно на структуру кода для функции userTypeColor, которую мы использовали ранее:

userTypeToColor: WinJS.Binding.converter(function (type) {
return type == "kid" ? "Orange" : "Black";
})

Функция userTypeColor - это инициализатор. Когда она вызывается - однажды и только однажды - она возвращает значение из WinJS.Binding.converter - конвертер, который затем будет использоваться для каждого обновления свойства. Это и есть настоящая функция-конвертер, а не userTypeColor - на самом деле - это структура, которая упаковывает анонимную функцию, заданную в WinJS.Binding.converter.

Если смотреть глубже, WinJS.Binding.converter, на самом деле, использует bind для того, чтобы установить взаимоотношения между свойством-источником и целевым свойством и вставляет анонимную функцию-конвертер в эти взаимоотношения. К счастью, вам не придётся иметь дело с подобными сложными механизмами и вы можете просто предоставить функцию-конвертер, как показано выше.

Тем не менее, если вы хотите увидеть пример подобного низкоуровневого кода, обратитесь снова к примеру "Декларативная привязка данных" (http://code.msdn.microsoft.com/windowsapps/DeclarativeBinding-bfcb42a5), так как он показывает как создать конвертер для сложных объектов напрямую в коде, без использования WinJS.Binding.converter. В данномм случае функция должна быть маркирована как безопасная для обработки, если на неё ссылаются в разметке. Другая функция, WinJS.Binding.initializer, существует для тех же целей; значение, возвращаемое WinJS.Binding.converter проходит через тот же самый метод, прежде чем оно вернется обратно в ваше приложение.

Шаблоны привязки данных и списки

Вы думаете, что мы уже завершили разговор о WinJS.Bindint? Не вполне, друзья! Есть две части этого обширного API, которые ведут нас прямо в следующую лекцию. (И сейчас вы узнаете настоящую причину, по которой я разместил весь этот раздел там, где разместил!) Первая - это WinJS.Binding.List -источник данных-коллекция, подходящий для привязки, который - не удивительно - весьма полезен при работе с элементами управления-коллекциями.

WinJS.Binding.Template, так же, уникальный вид пользовательского элемента управления. При использовании, как вы могли видеть в примере "Декларативная привязка", вы объявляете элемент (обычно div) с data-win-control = "WinJS.Binding.Template". В той же самой разметке вы задаёте содержимое шаблона в качестве элементов-потомков, каждый из которых может иметь собственные атрибуты data-win-bind. Что в этом уникально, так это то, что когда WinJS.UI.process или processAll находят эту разметку, они создают экземпляр шаблона и убирают всё, кроме корневого элемента, из DOM. Так что же в этом хорошего?

Итак, так как шаблон существует, кто угодно может вызвать его метод render для того, чтобы создать копию этого шаблона внутри какого-нибудь другого элемента, используя какой-нибудь контекст данных для обработки любых атрибутов data-win-bind в нём (обычно пропуская корневой элемент, отсюда и параметр skipRoot в методе WinJS.Binding.declarativeBind). Кроме того, вывод шаблона несколко раз в том же самом элементе позволяет создать множество элементов одного уровня, каждый из которых может иметь различные источники данных.

Вот оно что! Теперь мы можем начать рассматривать, какой всё это имеет смысл для элементов управления для вывода коллекций и источников данных-коллекций. Имея источник данных-коллекцию и шаблон, вы можете обойти этот источник и вывести копию шаблона для каждого отдельного элемента в источнике в его собственном элементе. Добавьте немного возможностей навигации или макет, внутри которого содержится элемент, и вуаля! Вы у истоков того, что, как мы узнаем, является элементами управления WinJS.UI.FlipView и WinJS.UI.ListView, когда разберемся со следующей лекцией.

Что мы только что изучили

Лекция 5. Коллекции и элементы управления для вывода коллекций

Эта лекция посвящена работе с коллекциями данных и их визуализации. В частности, особое внимание уделено элементу управления ListView, рассмотрены возможности элемента управления FlipView

Файлы к данной лекции Вы можете скачать  здесь.

Можно с уверенностью говорить о том, что везде, где бы вы ни были, вы окружены множеством коллекций. Книги, которые вы обычно читаете - это коллекция глав, а глава - это коллекция страниц. Страницы, в свою очередь, являются коллекциями абзацев, абзацы - коллекции слов, слова - коллекции букв, а буквы (если вы читаете это в электронном виде) - это коллекции пикселей. Всё дальше и дальше…

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

Мы воспринимаем всё это как коллекции, так как мы знаем, как обобщить отдельные объекты - как листья или страницы, или игрушки - в категории или группы. Это даёт нам мощный инструмент для организации этих вещей и управления ими (за исключением одежды в моём шкафу, моя жена может это подтвердить). И так же, как физический мир вокруг нас во многом состоит из коллекций, цифровой мир, который мы используем для того, чтобы представить объекты реального мира, так же полон коллекций. Языки программирования, наподобие JavaScript, имеют конструкции, такие, как массивы, для организации коллекций данных и управления ими, и окружение, наподобие Windows 8, обеспечивает элементы управления для коллекций, с помощью которых мы можем визуализировать данные и управлять ими.

В данной лекции мы обратим наше внимание на два элемента управления для коллекций, которые предоставлены WinJS. Это FlipView (представление отражения), который отображает один элемент коллекции, и ListView (представление списка), который отображает множество элементов, организуя их по-разному. Как вы можете ожидать, ListView обладает большей функциональностью. Так как он является одной из центральных деталей во многих подходах к проектированию приложений, мы потратим основное количество времени на то, чтобы глубоко его изучить, а так же изучим концепцию и реализацию семантического масштабирования (semantic zoom) (на самом деле, другого элемента управления).

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

Но не будем перегружать наши мозги здесь, в начале лекции, теорией или архитектурными сложностями! Рассмотрим код, который позволит нам понять основные аспекты обоих элементов управления.

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

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

Быстрый старт №1: пример использования элемента управления FlipWiew

Как показано на Рис. 5.1, пример "Элемент управления FlipView" (http://code.msdn.microsoft.com/windowsapps/FlipView-control-sample-18e434b4) содержит неплохой код для этого элемента управления и его визуальное представление, позволяющее исследовать этот элемент управления. (Я испытываю особую благодарность за то, что мне не пришлось писать подобные примеры для этого курса!). Для целей быстрого старта, посмотрим на первый сценарий, касающийся заполнения элемента управления из простого источника данных и использования шаблона для рендеринга этого элемента, так как такие же механизмы применяются в ListView. Позже мы вернемся к другим сценариям использования FlipView.

Пример использования элемента управления FlipView, где элемент управления отображает картинку


увеличить изображение

Рис. 5.1.  Пример использования элемента управления FlipView, где элемент управления отображает картинку

Так как FlipView - это элемент управления WinJS, с конструктором WinJS.UI.FlipView, мы объявляем его в разметке с атрибутами data-win-control и data-win-options (смотрите html/simpleFlipView.html):

<div id="simple_FlipView" class="flipView" data-win-control="WinJS.UI.FlipView"
data-win-options="{ itemDataSource: DefaultData.bindingList.dataSource,	
itemTemplate: simple_ItemTemplate }">	
</div>

И, конечно, в процессе загрузки сраницы вызывается WinJS.UI.processAll для создания экземпляра элемента управления. В параметрах FlipView мы можем сразу же увидеть два критически важных участка, благодаря которым он работает: это источник данных, который предоставляет содержимое, нужное каждому элементу, и шаблон для вывода элемента.

Если вы обратили внимание на конец лекции 4, вы, возможно, догадываетесь, что шаблон - это экземпляр WinJS.Binding.Template. И вы правы! Эта часть разметки, на самом деле, расположена перед объявлением элемента управления в html/simpleFlipView.html.

<div id="simple_ItemTemplate" data-win-control="WinJS.Binding.Template" style="display: none">
<div class="overlaidItemTemplate">
<img class="image" data-win-bind="src: picture; alt: title" />
<div class="overlay">
<h2 class="ItemTitle" data-win-bind="innerText: title"></h2>
</div>
</div>
</div>

Обратите внимание на то, что шаблон должен быть всегда объявлен в разметке до элемента управления, который на него ссылается: WinJS.UI.processAll должен создать экземпляр шаблона прежде чем элемент управления запросит шаблон для вывода своего содержимого для каждого элемента источника данных. Так же вспомните, из лекции 4, что создание экземпляра шаблона убирает его содержимое из DOM, и, таким образом, оно не может быть изменено во время выполнения программы. Вы можете видеть это, исполняя пример: разверните узлы в Проводнике DOM (DOM Explorer) в Visual Studio, или в панели Динамическая DOM (Live DOM) в Blend, и вы увидите лишь корневой элемент шаблона div, который не имеет элементов-потомков.

В примере, прозаически названный ItemTemplate, шаблон создан из элемента img и еще одного div, который содержит h2. Класс overlay в этом div, если вы присмотритесь к Рис. 5.1, стилизован с использованием частично прозрачного фонового цвета (смотрите css/default.css на предмет селектора .overlaidItemTemplate.overlay). Это свидетельствует о том, что вы можете использовать в шаблоне любые элементы, в том числе - другие элементы управления WinJS. В последнем случае, они собираются, когда WinJS.UI.process/ processAll выполняется над шаблоном1).

Кроме того, вы увидите, что шаблон использует атрибуты привязки данных WinJS, где свойства img.src, img.alt и h2.innerText привязаны к свойствам источника данных, которые называются picture и title. Это показывает, как свойства двух целевых элементов могут быть привязаны к одному источнику. (Помните, что если вы осуществляете привязку к свойствам элемента управления WinJS, а не к его элементам-потомкам, эти свойства должны начинаться с winControl).

Что касается источника данных, то параметру itemDataSource элемента управления FlipView присвоено значение DefaultData.bindingList.dataSource, описание источника вы можете найти в js/DefaultData.js:

var array = [
{ type: "item", title: "Cliff", picture: "images/Cliff.jpg" },
{ type: "item", title: "Grapes", picture: "images/Grapes.jpg" },
{ type: "item", title: "Rainier", picture: "images/Rainier.jpg" },
{ type: "item", title: "Sunset", picture: "images/Sunset.jpg" },
{ type: "item", title: "Valley", picture: "images/Valley.jpg" }
];
var bindingList = new WinJS.Binding.List(array);

WinJS.Namespace.define("DefaultData", {
bindingList: bindingList, 
array: array
});

Мы кратко ознакомились с WinJS.Binding.List в конце лекции 4. Его цель заключается в том, чтобы превратить массив, хранящийся в памяти, в наблюдаемый источник данных для односторонней привязки данных. Контейнер WinJS.Binding.List, кроме того, необходим, так как FlipView и ListView не могут работать напрямую с простыми массивами, даже при единовременной привязке данных. Они ожидают от источников данных наличия у них методов интерфейса WinJS.UI.IListDataSource. Свойство dataSource WinJS.Binding.List, как в bindingList.dataSource, предоставляет то же самое, и вы всегда будете использовать это свойство вместе с FlipView и ListView (Оно, на самом деле, существует лишь для этого). Если вы об этом забудете и попытаетесь осуществить прямую привязку к WinJS.Binding.List, вы увидите исключение, в котором говорится о том, что "Объект не поддерживает свойство или метод 'createListBinding'" ("Object doesn't support property or method 'createListBinding'.")

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

Отметим, что WinJS.Binding.List полностью поддерживает динамические данные. Если вы посмотрите на его описание (http://msdn.microsoft.com/library/windows/apps/hh700774.aspx) в документации, вы увидите, что он выглядит практически так же, как JavaScript-массив, со свойством length и полным набором методов массивов - от concat и indexOf до push, pop и unshift. Они устроенны именно так, как можно этого ожидать: от вас не требуется повторное изучение основ.

Кроме того, важно отметить, что и для FlipView, и для ListView, подобная установка свойства элемента управления itemDataSource автоматически создаёт одностороннюю привязку данных, в итоге, любое изменение в объекте-списке, или даже в массиве, на основании которого он построен, запустит автоматическое обновление связанного элемента управления.

Быстрый старт №2a: основные возможности HTML ListView

Как я уже говорил раньше, основные механизмы, касающиеся источников данных и шаблонов, применимые к ListView, это те же, что применимы к FlipView. Вы можете увидеть всё это в примере "Основные возможности HTML ListView" (http://code.msdn.microsoft.com/windowsapps/ListView-basic-usage-sample-fcc451db), Рис. 5.2. Обратите внимание на первых два сценария, которые касаются создания элемента управления и ответа на события отдельных элементов, которые он отображает.

Так как ListView способен одновременно отображать множество элементов, ему нужно еще кое-что, в дополнение к источнику данных и шаблону. Что-то, что описывало бы, как эти элементы визуально соотносятся друг с другом. Это - свойство layout элемента управления ListView, которое мы увидим в разметке, в Сценарии 1 данного примера, вместе с некоторыми другими параметрами поведения (html/scenario1.html).

<div id="listView" data-win-control="WinJS.UI.ListView"	
data-win-options="{ itemDataSource: myData.dataSource,	
itemTemplate: select('#smallListIconTextTemplate'), selectionMode: 'none',	
tapBehavior: 'none', swipeBehavior: 'none', layout: { type: WinJS.UI.GridLayout } }">
</div>

Пример "Основные возможности HTML ListView"


увеличить изображение

Рис. 5.2.  Пример "Основные возможности HTML ListView"

Конструктор ListView, WinJS.UI.ListView, конечно, вызывается вездесущим WinJS.UI.processAll, когда загружен элемент управления страницей. Источник данных для данного списка установлен в myData.dataSource, где myData - это, опять же, WinJS.Binding.List (определенный в js/data.js, на основе обычного массива) и его свойство dataSource обеспечивает необходимый интерфейс.

Шаблон отдельного элемента определен ранее в default.html, с параметром id, равным smallListIconTextTemplate, и это, в основном, то же самое, что мы видим в FlipView (img и текстовые элементы), поэтому я не привожу здесь их описание.

В параметрах элемента управления мы видим три свойства, влияющих на поведение элемента: selectionMode, tapBehavior и swipeBegavior. Все они в этом примере установлены в 'none' для того, чтобы полностью отключить возможность выделения элементов и щелчков мышью по ним, превращая ListView в средство пассивного отображения объектов. Содержимое в нём можно перематывать, но элементы не отвечают на ввод. (Смотрите, кроме того, врезку "Стилизация элементов при зависании над ними указателя мыши")

Что касается свойства layout, то это объект, свойство type которого показывает, какой макет использовать. WinJS.UI.GridLayout, который используется здесь, отображает элементы сверху вниз и слева направо, что подходит для горизонтальной прокрутки. WinJS предоставляет и другой тип макета, который называется WinJS.UI.ListLayout. Это одномерный список, размещающий элементы сверху вниз, который подходит для вертикальной прокрутки, особенно - в прикрепленном режиме просмотра. (Мы скоро увидим это при рассмотрении шаблона Приложение таблицы. Пример ListView, который мы здесь рассматриваем, не имеет хорошего варианта для прикрепленного режима просмотра).

Сейчас элемент управления ListView в Сценарии 1 лишь отображает элементы, часто нужно, чтобы они реагировали на щелчок мыши или прикосновения. Сценарий 2 показывает это, здесь свойство tapBehavior установлено в 'invoke' (смотрите html/scenario2.htm). Это равносильно использованию WinJS.UI.tapBehaviortoggleSelect, как показано в справке по перечислению tapBehavior (http://msdn.microsoft.com/library/windows/apps/hh701303.aspx) для "invoke". Данный вариант поведения позволяет выделить элемент или снять с него выделение в зависимости от его состояния, и затем активирует его. Другой вариант - это directSelect, где элемент всегда выделен, и затем активируется, при этом invokeOnly исполняется лишь тогда, когда элемент активирован без изменения состояния выделения. Кроме того, вы можете установить параметры поведения элемента в none, в итоге прикосновение или щелчок мышью будут проигнорированы.

Когда отображаемый элемент активируется, элемент управления ListView запускает событие itemInvoked. Вы можете подключить обработчик события, используя либо addEventListener, либо свойство oniteminvoked элемента ListView. Вот как это выполняется в Сценарии 2 (слегка реорганизованный код из js/scenario2.js):

var listView = element.querySelector('#listView').winControl;
listView.addEventListener("iteminvoked", itemInvokedHandler, false);
}

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

В вышеприведенном коде, вы можете так же назначить обработчик, используя напрямую свойство listView.oniteminvoked , или вы можете задать обработчик в свойстве itemInvoked в data-win-options (в таком случае функция должна быть отмечена безопасной для обработки). Объект события, который вы затем получите в обработчике, содержит promise-объект для активированного элемента, а не сам элемент, поэтому вам нужно вызвать его метод done или then для того, чтобы получить данные элемента. Кроме того, полезно знать, что вам никогда не следует менять свойство источника данных ListView напрямую, внутри обработчика itemInvoked, так как это, возможно, вызовет исключение. Если вам нужно это сделать, заключите код, вносящий изменения, в setImmediate, так вы сможете вернуться в поток пользовательского интерфейса.

Врезка: стилизация элементов при зависании над ними указателя мыши

В то время, как отключение возможности выделения и реакции на прикосновения в ListView приводит к созданию пассивного элемента управления, зависание над элементом указателя мыши (или соответствующее событие на подходящем для этого сенсорном оборудовании), приводит к подсветке каждого элемента. Вернитесь к Рис. 5.2. Вы можете управлять этим, используя псевдо-селектор .win-container:hover для элементов управления. Например. следующее правило стиля полностью убирает эффект, проявляющийся при зависании указателя мыши:

#myListView .win-container:hover {
background-color: transparent;
outline: 0px;	
	}

Быстрый старт №2b: пример группировки элементов в ListView

Отображение списка элементов - это замечательно, но чаще коллекции нуждаются в ином уровне организации - в том, что мы называем группировкой. Это вполне очевидно, когда я открываю ящик с документами около рабочего стола, который содержит набор документов, одни из которых более важные, другие - менее. На ярлыках папок с документами я вижу надписи, показывающие их отношение к той или иной группе: Налоги, Финансы, Сообщества, Страхование, Машина, Литературный проект и Разное (среди прочих). Очевидно, нам нужно средство для группировки элементов внутри элемента управления и ListView счастлив нам в этом помочь.

Базовую демонстрацию группировки можно найти в примере "Группировка в HTML ListView и семантическое масштабирование" (http://code.msdn.microsoft.com/windowsapps/ListView-grouping-and-6d032cc1) (Рис. 5.3). Как и в случае с примером, демонстрирующим основные возможности, код в js/groupedData.js содержит массив, располагаемый в памяти, на основе которого мы создаём WinJS.Binding.List. Вот выдержка, показывающая структуру элемента (я показал бы весь массив, но после этого мне захочется чего-нибудь сладкого!):

var myList = new WinJS.Binding.List([	
{ title: "Banana Blast", text: "Low-fat frozen yogurt", picture: "images/60Banana.png" },
{ title: "Lavish Lemon Ice", text: "Sorbet", picture: "images/60Lemon.png" },	
{ title: "Creamy Orange", text: "Sorbet", picture: "images/60Orange.png" },	
...

Здесь есть набор элементов со свойствами title, text и picture. Мы можем сгруппировать их так, как нам захочется, и даже изменить группировку "на лету". Как показывает Рис. 5.3, здесь образцы сгруппированы по первым буквам свойства title.

Группировка HTML ListView и пример использования семантического масштабирования


увеличить изображение

Рис. 5.3.  Группировка HTML ListView и пример использования семантического масштабирования

Если вы взглянете на описание ListView (http://msdn.microsoft.com/library/windows/apps/br211833.aspx), вы увидите, что элемент управления работает с двумя шаблонами и двумя коллекциями: то есть, наряду со свойствами itemTemplate и itemDataSource, это свойства groupHeaderTemplate (шаблон заголовка группы) и groupDataSource (источник данных группы). Они используются с gridLayout элемента управления ListView (по умолчанию) для организации групп и создания заголовков над элементами.

Шаблон заголовка в html/scenario1.html очень прост (и шаблон элемента очень похож на то, что мы уже видели):

<div id="headerTemplate" data-win-control="WinJS.Binding.Template">
<div class="simpleHeaderItem">	
<h1 data-win-bind="innerText: title"></h1>	
</div>	
</div>

На него есть ссылка в объявлении элемента управления (другие параметры опущены):

<div id="listView" data-win-control="WinJS.UI.ListView"	
data-win-options="{ groupDataSource: myGroupedList.groups.dataSource,
groupHeaderTemplate: headerTemplate }">	
</div>

В случае с источником данных, вы можете видеть, что мы используем переменную, которая называется myGroupedList со свойством внутри, которое называется groups. Что всё это значит?

Давайте устроим короткое концептуальное отступление. Хотя компьютеры без проблем обрабатывают большие объёмы исходных данных, вроде массива myList, людям удобнее видеть информацию в более организованном виде. Три основных способа достижения этого - группировка (grouping), сортировка (sorting) и фильтрация (filtering). Группировка организует элементы в группы, как показано на Рис. 5.3. Сортировка располагает их в соответствии с различными правилами. Фильтрация позволяет выбирать подмножества элементов, которые удовлетворяют определенным критериям. Во всех трёх случаях, однако, вам не нужно, чтобы эти операции меняли базовые данные: пользователь может захотеть группировать, сортировать или фильтровать одни и те же данные различными способами в разное время.

Группировка, сортировка и фильтрация, таким образом, относятся к проекции данных: все они связаны с теми же самыми базовыми данными, таким образом, изменение элемента в проекции будет распространено на источник, так же, как изменения в исходных данных отразятся на проекции.

Объект WinJS.Binding.List предоставляет методы для создания этих проекций: createGrouped (http://msdn.microsoft.com/library/windows/apps/Hh700742.aspx), createSorted (http://msdn.microsoft.com/library/windows/apps/hh700743.aspx) и createFiltered (http://msdn.microsoft.com/library/windows/apps/hh700741.aspx). Каждый из методов создаёт особую форму WinJS.Binding.List: GroupedSortedListProjection, SortedListProjection и FilteredListProjection соответственно. Таким образом, каждая из проекций представляет собой список, подходящий для привязки данных, с некоторыми дополнительными методами и свойствами, которые специфичны для каждой из проекций. Вы даже можете создать одну проекцию из другой. Например, команда createGrouped(...).createFiltered(...) создаст отфильтрованную проекцию на основе сгруппированной проекции. (Обратите внимание на то, что метод sort не создаёт проекцию. Он применяет операцию сортировки на проекции, для которой вызывается, как команда sort для массивов JavaScript).

Теперь, когда мы знаем о проекциях, мы можем увидеть, как создан myGroupedList:

var myGroupedList = myList.createGrouped(getGroupKey, getGroupData, compareGroups);

Этот метод принимает в качестве параметров три функции. Первая - функция ключа группы (group key), связывает элемент с группой: она получает элемент и возвращает подходящую строку группы, известную как ключ. Ключ, который должен быть строкой, может быть чем-то, что прямо включено в элемент, или может быть получен из свойств элемента. В примере, функция getGroupKey возвращает первый символ свойства элемента title (в верхнем регистре). Обратите, однако, внимание, что исходный пример просто использует charAt для получения характеристики группировки, однако этот подход не работает для большого количества языков. Вместо этого, используйте класс Windows.Globalization.Collation.CharacterGroupings (http://msdn.microsoft.com/library/windows/apps/windows.globalization.collation.charactergroupings.aspx) и его метод lookup (http://msdn.microsoft.com/library/windows/apps/windows.globalization.collation.charactergroupings.lookup.aspx), как показано ниже, который нормализует регистр символов автоматически, в итоге, вызов toLocaleUpperCase (http://msdn.microsoft.com/library/6t6xaca8.aspx) необязателен:

var cg = Windows.Globalization.Collation.CharacterGroupings();

function getGroupKey(dataItem) {
return cg.lookup(dataItem.title);
}

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

Знайте, что эта первая функция, которую мы называем функция ключа группы, определяет только связь между элементом и группой, и ничего больше. Она так же вызывается для каждого элемента в коллекции, когда осуществляется вызов createGrouped, таким образом, она должна работать быстро. По этой причине создание CharacterGroupings осуществляется за пределами функции. Совет. Если извлекать ключ группы из элемента во время выполнения приложения нужно для обеспечения работоспособности связанных механизмов, вы улучшите общую производительность, сохраняя заранее полученный ключ в элементе, вместо того, чтобы просто получать его из функции ключа группы.

Данные для групп, которые являются коллекциями, к которым присоединен шаблон заголовка, на самом деле, не создаются до того, как будет вызван метод проекции группы group, что происходит, когда обрабатывается параметр groupedDataSource элемента управления ListView. В этот момент вызывается вторая функция, переданная createGrouped - функция данных группы. Она вызывается лишь однажды для каждой группы с представляющим (representative) элементом для данной группы. В ответ, функция возвращает объект для данной группы, который содержит все свойства, которые нужны для привязки данных.

В примере, функция gerGroupData (переданная createGroup) просто возвращает объект с единственным свойством groupTitle, которое является тем же самым, что и ключ группы, но, конечно, вы можете сделать это значение любым. Этот код так же модифицирован, в сравнении с исходным примером, для того, чтобы подходить для целей глобализации. Мы добиваемся этого, повторно используя getGroupKey:

function getGroupData(dataItem) {
return {	
groupTitle: getGroupKey(dataItem)
};	
}	

В модифицированном примере я изменил имя свойства объекта данных этой группы title на более явное groupTitle для того, чтобы было совершенно понятно, что он ни коим образом не влияет на свойство title отдельных элементов. Это подразумевает изменение шаблонов заголовков в html/scenario1.html и html/scenario2.html, чтобы они ссылались на groupTitle. Это поможет нам быть уверенными в том, что контексты данных шаблона элемента и заголовка отличаются. В случае с шаблоном заголовка, это коллекция, созданная из значений, возвращённых функцией данных группы. В случае с шаблоном элемента, это проекция группы из WinJS.Binding.List.createGrouped. Две разные коллекции - помните об этом.

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

В измененном примере, я использовал WinJS.Binding.List.createFiltered для того, чтобы получить проекцию списка, отфильтрованного по текущему ключу1). Свойство length этой проекции - это количество элементов в группе:

function getGroupData(dataItem) {
var key = getGroupKey(dataItem);
	
//Получает отфильтрованную проекцию для нашего списка, проверяет на совпадение ключей
var filteredList = myList.createFiltered(function (item) {
return key == getGroupKey(item);
});	
return {
title: key,
count: filteredList.length
};	
	}

Что касается свойства count в коллекции, мы можем использовать его в шаблоне заголовка:

<div id="headerTemplate" data-win-control="WinJS.Binding.Template" style="display: none">
<div class="simpleHeaderItem">	
<h1 data-win-bind="innerText: groupTitle"></h1>	
<h6><span data-win-bind="innerText: count"></span> items</h6>	
</div>	
</div>

После незначительных манипуляций в css/scenario1.css - изменения высоты класса simpleHeaderItem до 65 пикселей для того, чтобы получить немного дополнительного места - список будет выглядеть так, как показано ниже:

И, наконец, вернемся к WinJS.Binding.List.createGrouped, третья (и необязательная) функция, представленная здесь - это функция сортировки групп, которая вызывается для сортировки коллекции данных групп и, таким образом, для изменения порядка, в котором группы отображаются в ListView2). Эта функция принимает два ключа группы и возвращает ноль, если они равны, отрицательное число, если первый ключ при сортировке расположен перед вторым, и положительное число, если второй ключ расположен перед первым. Функция compareGroups в примере выполняет сортировку по алфавиту, которую я обновил в модифицированной версии для того, чтобы она соответствовала особенностям приложений, рассчитанных на глобальный рынок:

function compareGroups(left, right) {
return groupCompareGlobalized(left, right);
}

function groupCompareGlobalized(left, right) {
var charLeft = cg.lookup(left);
var charRight = cg.lookup(right);
// Если оба имеют одинаковый символ группы, воспринимает их как одинаковые
if (charLeft.localeCompare(charRight) == 0) {	
return 0;	
}	

// В разных группах, мы должны полагаться на сортировку с учетом языкового стандарта так как
// имена групп не сортируются так же, как сами группы, для некоторых языков.	
return left.localeCompare(right);	
 }

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

function compareGroups2(left, right) {
var leftLen = filteredLengthFromKey(left);
var rightLen = filteredLengthFromKey(right);

if (leftLen != rightLen) {
return rightLen - leftLen;
}
return groupCompareGlobalized(left, right);
}
function filteredLengthFromKey(key) {	
var filteredList = myList.createFiltered(function (item) {
return key == getGroupKey(item);	
});	
return filteredList.length;
}

Отладка функций группировки

Если кажется, что различные функции группировки работают неправильно, вы можете устанавливать точки останова и пошагово исполнять код несколько раз, но это может превратиться в утомительную задачу, так как функции вызываются очень много раз даже для коллекций умеренного размера. Вместо этого, попробуйте воспользоваться console.log для того, чтобы узнать о том, какие параметры передаются данным функциям и/или какие значения возвращаются, что позволит вам получить итоговый результат гораздо быстрее. Для того, чтобы увидеть, что происходит в функции сортировки группы, например, попробуйте такой код:

console.log("Comparing left = " + left + " to right = " + right);

Элемент управления ListView в шаблоне Приложение таблицы

Теперь, когда мы рассмотрели подробности об элементе управления ListView и об источниках данных, расположенных в памяти, мы можем полностью понять устройство шаблона Приложение таблицы (Grid App) в Visual Studio и Blend. Как было упомянуто в подразделе "Процесс и стили навигации" раздела "Элементы управления страниц и навигация" лекции 3, этот шаблон проекта предоставляет структуру приложения, построенную вокруг навигации по страницам: домашняя страница (pages/groupedItems) отображает коллекцию примеров данных (js/data.js) в элементе управления ListView, где каждое представление элемента описано с помощью WinJS.Binding.Template, так же, как и заголовки групп. Рис. 5.4 показывает макет домашней страницы и идентифицирует соответствующие элементы ListView. Как мы уже обсуждали, жест прикосновения к элементу вызывает перемещение на страницу pages/groupDetail, и сейчас мы сможем увидеть, как всё это работает с ListView.

Элемент управления ListView на Рис. 5.4 занимает нижнюю часть области содержимого приложения. Так как он поддерживает горизонтальную прокрутку, он, на самом деле, выходит за края. Использованы различные CSS-поля для выравнивания первого элемента и элементов макета, которые позволяют ему заходить за левый край при прокрутке ListView.

Элементы в ListView на домашней странице шаблона Приложение таблицы (Все цветные элементы - это добавленные для разъяснений метки и линии)


увеличить изображение

Рис. 5.4.  Элементы в ListView на домашней странице шаблона Приложение таблицы (Все цветные элементы - это добавленные для разъяснений метки и линии)

Заголовки групп (Group headers)

Элементы, выведенные из шаблона (Items rendered from template)

Элемент управления ListView (Полностью примыкает к краям) (ListView Control (full bleed to sides))

С ListView в этом проекте происходит не так уж и много всего, поэтому рассмотрим происходящее пошагово. Для начинающих, разметка элемента управления в pages/groupedItems/groupedItems.html весьма стандартна, среди параметров - лишь тот, который указывает на то, что элементы никак не реагируют на выделение:

<div class="groupeditemslist win-selectionstylefilled"
aria-label="List of groups" data-win-control="WinJS.UI.ListView" 
data-win-options="{ selectionMode: 'none' }">
</div>

Переходя к pages/groupedItems/groupedItems.js, мы можем сказать, что метод ready обрабатывает инициализацию:

ready: function (element, options) {	
var listView = element.querySelector(".groupeditemslist").winControl;	
listView.groupHeaderTemplate = element.querySelector(".headerTemplate");
listView.itemTemplate = element.querySelector(".itemtemplate");	
listView.oniteminvoked = this._itemInvoked.bind(this);	
// (Инициализация обработчика событий клавиатуры опущена)...

this.initializeLayout(listView, appView.value);
listView.element.focus();
},

Здесь вы можете видеть, что шаблон элемента управления может быть задан в коде так же просто, как это делается в разметке, и в этом случае мы используем класс для того, чтобы определить местоположение элемента шаблона, вместо использования id. Почему это работает? Потому, что мы, на самом деле, ссылаемся на элемент всё время: хост-процесс приложения автоматически создаёт переменную для элемента, которая имеет то же имя, что и id. Это - одно и то же. В коде вы, кроме того, можете предоставить функцию вместо декларативно описанного шаблона, которая позволит вам динамически выводить каждый элемент по отдельности. Больше об этом позже.

Вы можете, кроме того, видеть, как эта страница назначает обработчик для событий itemInvoked (выше ready), вызывая WinJS.Navigation.navigate для того, чтобы перейти к страница groupDetail или itemDetail, как мы видели в лекции 3:

_itemInvoked: function (args) {
if (appView.value === appViewState.snapped) {
// Если страница в прикрепленном режиме, пользователь открывает группу. 
var group = Data.groups.getAt(args.detail.itemIndex); this.navigateToGroup(group.key);
} else {
// Если страница не в прикрепленном режиме, пользователь открывает элемент. 
var item = Data.items.getAt(args.detail.itemIndex); nav.navigate("/pages/itemDetail/itemDetail.html", {
item: Data.getItemReference(item) });
}
}

navigateToGroup: function (key) {
nav.navigate("/pages/groupDetail/groupDetail.html", { groupKey: key });
},

В таком случае, мы получаем данные элемента из коллекции (методы getAt) вместо того, чтобы использовать данные самого элемента. Это так, потому что необходимая информация о группе, необходимая для первого случая, не является, напрямую, частью элемента. Кроме того, мы видим здесь, что страницы по-разному интерпретируют активацию, в зависимости от состояния просмотра. Это так, потому что переход в новый режим просмотра меняет и макет, и источник данных. Это обрабатывается во внешнем методе страницы _initializeLayout, вызываемом и при старте приложения, и из функции страницы updateLayout:

initializeLayout: function (listView, viewState) {
if (viewState === appViewState.snapped) { listView.itemDataSource = Data.groups.dataSource; 
listView.groupDataSource = null;
listView.layout = new ui.ListLayout();
} else {
listView.itemDataSource = Data.items.dataSource;
listView.groupDataSource = Data.groups.dataSource;
listView.layout = new ui.GridLayout({ groupHeaderPosition: "top" });
}
},

Макет элемента управления ListView может быть изменен в любое время путём установки его свойства property. Когда программа находится в прикрепленном режиме просмотра, он установлен в WinJS.UI.ListLayout, в иных случаях - в WinJS.UI.GridLayout (свойство которого groupHeaderPosition может быть "top" или "left"). Кроме того, вы можете увидеть, что вы можете "на лету" поменять источник данных для ListView: в прикрепленном режиме просмотра это - список групп, в противном случае - список элементов.

Надеюсь, теперь вы понимаете, почему я как следует разъяснил особенности навигации по страницам, прежде чем мы добрались до ListView, так как этот проект, при ближайшем рассмотрении, выглядит довольно сложно. В любом случае, посмотрим сейчас на шаблоны для этой страницы (pages/groupedItems/groupedItems.html):

<div class="headertemplate" data-win-control="WinJS.Binding.Template">	
<button class="group-header win-type-x-large win-type-interactive"	
data-win-bind="groupKey: key" role="link" tabindex="-1" type="button"	
onclick="Application.navigator.pageControl.navigateToGroup(event.srcElement.groupKey)" >	
<span class="group-title win-type-ellipsis" data-win-bind="textContent: title"></span>
<span class="group-chevron"></span>	
</button>	
</div>	
<div class="itemtemplate" data-win-control="WinJS.Binding.Template">
<div class="item">
<img class="item-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
<div class="item-overlay">
<h4 class="item-title" data-win-bind="textContent: title"></h4>
<h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle"></h6>
</div>
</div>
</div>

Опять же, мы так же используем WinJS.Binding.Template и различные части синтаксиса привязки данных, разбросанные по разметке, не говоря уже об обработчике click, присвоенном тексту заголовка, который, как и элемент в прикрепленном режиме просмотра, ведет нас к странице подробной информации о группе.

Как и в случае с данными (которые вы, вероятнее всего, замените), это задаётся в js/data.js, как массив, расположенный в памяти, который используется для WinJS.Binding.List. В массиве sampleItems каждый элемент заполнен либо внутренними данными, либо значениями других переменных. Каждый элемент, кроме того, имеет свойство group, которое берется из массива sampleGroups. К несчастью, этот последний массив имеет почти такие же свойства, как и массив элементов, что может показаться запутанным. Для того, чтобы помочь в чётком различении этих массивов, вот полная структура свойств элемента:

{
group : { 
key, 
title, 
subtitle,
backgroundImage,
description
}, title, 
subtitle, 
description, 
content,
backgroundImage
}

Как мы видели в примере группировки ListView ранее, проект на основе шаблона Приложение таблицы использует createGrouped для задания источника данных. Интересно увидеть здесь, что он задаёт изначально пустой список, создаёт сгруппированную проекцию (опуская необязательную функцию сортировки) и затем добавляет элементы, используя метод списка push:

var list = new WinJS.Binding.List();	
var groupedItems = list.createGrouped(	
function groupKeySelector(item) { return item.group.key; },
function groupDataSelector(item) { return item.group; }	
);	
generateSampleData().forEach(function (item) {
list.push(item);
});

Это чётко указывает на динамическую природу списков и ListView: вы можете добавлять и удалять элементы из источника данных, а односторонняя привязка данных позволит сохранять уверенность в том, что ListView соответствующим образом обновится. В подобном случае вам не нужно обновлять макет ListView - это произойдёт автоматически. Я говорю это, так как обычно имеется некоторое непонимание в вопросе использования метода ListView forceLayout, который вам нужно вызывать лишь тогда, как сказано в документации: "когда вы делаете ListView снова видимым после того, как его свойство style.display будет установлено в 'none'". Вы обнаружите, однако, что код из шаблона Приложение таблицы вовсе не использует этот метод.

В js/data.js имеются и другие полезные функции, такие, как getItemsFromGroup, которая использует WinJS.Binding.List.createFiltered, как мы делали ранее. Другие функции предназначены для организации взаимосвязи между группами и элементами, которая нужна для перемещения между списком элементов, подробностями о группах (подобная страница отображает лишь элементы в определенной группе), и подробностями об элементах. Все эти функции заключены в пространство имен, которое называется Data, в верхней части js/data.js, в итоге, ссылка на всё, что описано в этом файле, должна иметь префикс Data.

И, имея эти знания, я думаю, вы сможете понять всё, что происходит в приложении, построенном по шаблону Приложение таблицы, для того, чтобы адаптировать его под свои нужды. Просто помните о том, что все данные-образцы, вроде логотипа по умолчанию и изображения экрана-заставки, нужно полностью заменить реальными данными, полученными из других ресурсов, наподобие файла или WinJS.xhr, и их вы можете заключить в WinJS.Binding.List. Некоторые дальнейшие руководства вы можете найти в материале "Создание программы для чтения блогов" (http://msdn.microsoft.com/library/windows/apps/Hh974582.aspx) в Центре разработчиков Windows, и хотя в руководстве используется шаблон проекта Приложение с разделением (Split App), здесь много общего с шаблоном Приложение таблицы, и этот рассказ, на самом деле, применим и к тому и к другому шаблонам.

Элемент управления Semantic Zoom (семантическое масштабирование)

С тех пор, как мы загрузили пример "Группировка HTML ListView и семантическое масштабирование" (http://code.msdn.microsoft.com/windowsapps/ListView-grouping-and-6d032cc1), и завершили первый разговор об элементах управления для коллекций, сейчас подходящее время для того, чтобы рассмотреть еще один очень интересный элемент управления WinJS: SemanticZoom (http://msdn.microsoft.com/library/windows/apps/br229690.aspx).

Семантическое масштабирование позволяет пользователям легко переключаться между двумя режимами просмотра одних и тех же данных, это режим детализированного представления (zoomed-in), который отображает детали, и режим общего представления (zoomed-out), который предоставляет более общую информацию. Основная область использования семантического масштабирования - это длинные списки элементов (особенно - несгруппированных), где пользователю, скорее всего, надоест постоянно прокручивать контент из конца в конец, не взирая даже на то, что довольно забавно водить пальцем по сенсорному экрану. С помощью семантического зуммирования вы можете уменьшить масштаб для того, чтобы увидеть заголовки, категории, или другие формы краткого представления общей информации о данных, и затем, кликнув по одному из отображаемых элементов, вернуться в его раздел или группу. Руководство по дизайну рекомендует поддерживать режим общего представления данных, в котором они занимают от одного до трёх экранов максимум, что делает очень простым просмотр всего набора данных и понимание их особенностей.

Попробуем в деле семантическое масштабирование из Сценария 2 примера, посвященного группировке HTML ListView и семантическому масштабированию. Для переключения между режимами просмотра, используйте жесты сведения и разведения пальцев (pinch-zoom), сочетания клавиш Ctrl+/Ctrl-, нажатие клавиши Ctrl, сопровождающееся прокруткой колеса мыши, либо маленькие кнопки масштабирования, которые автоматически появляются в правом нижнем углу элемента управления, как показано на Рис. 5.5. Когда вы переходите к режиму общего представления (zoom-out), вы видите отображение заголовков групп, что так же показано на рисунке.

Семантическое масштабирование между двумя режимами просмотра в примере о группировке ListView и применении семантического масштабирования


увеличить изображение

Рис. 5.5.  Семантическое масштабирование между двумя режимами просмотра в примере о группировке ListView и применении семантического масштабирования

Режим детализированного представления (Zoomed in view)

Переход в режим общего представления (Zoom out)

Элемент управления масштабированием (наложение)(Zoom control (overlay))

Режим общего представления (Zoomed out view)

Прикоснитесь к элементу или выполните команду увеличения масштаба, когда фокус установлен на данном элементе (Tap an item or zoom in with focus on that item)

Элемент управления довольно просто и понятно использовать. Объявите в разметке элемент управления WinJS, используя конструктор WinJS.UI.SemanticZoom. Внутри элемента вы, затем, объявите два (и только два) дочерних элемента: первый определяет режим детализированного представления, второй - режим общего представления - всегда в таком порядке. Здесь показан пример работы с двумя элементами управления ListView (плюс - шаблон, используемый для режима общего представления; Я показал код в измененном примере, который включен в дополнительные материалы к курсу):

<div id="semanticZoomTemplate" data-win-control="WinJS.Binding.Template" >	
<div class="semanticZoomItem">	
<h2 class="semanticZoomItem-Text" data-win-bind="innerText: groupTitle"></h2>
</div>	
</div>	

<div id="semanticZoomDiv" data-win-control="WinJS.UI.SemanticZoom">
<div id="zoomedInListView" data-win-control="WinJS.UI.ListView"
data-win-options="{ itemDataSource: myGroupedList.dataSource, itemTemplate: mediumListIconTextTemplate,
groupDataSource: myGroupedList.groups.dataSource, groupHeaderTemplate: headerTemplate,
selectionMode: 'none', tapBehavior: 'none', swipeBehavior: 'none' }">
</div>

<div id="zoomedOutListView" data-win-control="WinJS.UI.ListView"
data-win-options="{ itemDataSource: myGroupedList.groups.dataSource, itemTemplate: semanticZoomTemplate,
selectionMode: 'none', tapBehavior: 'invoke', swipeBehavior: 'none' }" >
</div>
</div>

Первый дочерний элемент zoomedInListView, очень похож на ListView из Сценария 1, с заголовками групп и элеметами. Второй элемент zoomedOutListView, использует группы в роли элементов и выводит их с использованием другого шаблона. Элемент управления семантического масштабирования просто перключается между двумя режимами отображения в ответ на соответствующие жесты. Когда масштаб отображения меняется, элемент управления SemanticZoom вызывает событие zoomchanged, у которого значение args.detail равняется true в случае общего представления, и false при детализированном представлении. Вы можете использовать это событие для того, чтобы сделать, для различных режимов отображения, доступными определенными команды панели приложения, например - в режиме общего представления активировать команды для изменения сортировки или фильтрации, результат работы который затем подействует на режим детализированного представления. Панели приложения (app bar) мы рассмотрим в лекции 1 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript".

Элемент управления имеет некоторые другие свойства, такие, как enableButtonl (логическое значение, управляющее видимостью накладываемых кнопок; значение по умолчанию - true), locked (логическое значение, отключающее изменение масштаба в обоих направлениях, может быть установлено динамически - для блогировки текущего режима отображения; по умолчанию - false), и zoomedOut (логическое, показывает, находится ли элемент в режиме общего представления, таким образом, вы можете инициализировать его подобным способом; по умолчанию - false). У него есть метод forceLayout, который используется в том же случае, что и метод forceLayout элемента управления ListView: а именно, когда вы отключаете стиль display: none.

Свойство zoomFactor весьма интересно, оно определяет, какая анимация используется при переходе между двумя режимами просмотра. Анимация - это комбинация масштабирования и затухания, которые делают переход в режим общего представления выглядящим как опускание вглубь плоскости экрана или подъем оттуда, в завиимости направления переключения, в то время как переход в режим детального представления может выглядеть, как "утопание" или "поднятие". Если быть более точным, то в режим детализированного просмотра масштабирование производится между 1/zoomFactor и 1, в то время, как значение прозрачности лежит между 0 и 1. Значение zoomFactor по умолчанию - 0.65, что создаёт эффект умеренной силы. Более низкие значения (минимум - 0.2) усиливают эффект, более высокие (максимум - 0.8) ослабляют его.

Когда особенности стилизации понятны, вы можете сделать большинство из того, что вам нужно, прямо с дочерними элементами SemanticZoom. Тем не менее, стилизуя элемент управления SemanticZoom вы можете переопределить стили в win-semanticzoom (для всего элемента) и win-semanticzoomactive (для активного режима просмотра). Стиль win-semanticzoombutton позволяет вам, если нужно, стилизовать кнопки для управления масштабом.

Важно понимать, что SemanticZoom предназначен для переключения между двумя режимами отображения одних и тех же данных, а не для переключения между полностью различными наборами данных (смотрите "Руководство по контекстному масштабированию" (http://msdn.microsoft.com/ru-ru/library/windows/apps/hh465319.aspx)). Кроме того, элемент управления не поддерживает вложенность (то есть - многократное изменение масштаба для разных уровней данных). Однако, это не означает, что вы вынуждены использовать элементы управления одного вида и для одного, и для другого режимов просмотра: режим детального просмотра может быть списком, а режим общего просмотра - диаграммой, календарём, или чем угодно другим. Режим общего просмотра, другими словами, это отличное место для показа сводных данных, которые сложно получить в режиме детального просмотра. Например, используя те же модификации кода, которые мы использовали для включения количества элементов в данные групп для Сценария 1 (смотрите "Быстрый старт №2b, выше), нам достаточно лишь внести некоторые дополнения в шаблон элемента для режима общего просмотра (как сделано в модифицированном примере к этой лекции в дополнительных материалах к курсу):

Кроме того, вам нужно знать, что элемент управления ZemanticZoom не работает с любыми дочерними элементами. Об этом вам может сообщить исключение, вызванное, если элемент не имеет свойства zoomableView. Каждый дочерний элемент должен обеспечивать реализацию интерфейса WinJS.UI.IZoomableView (http://msdn.microsoft.com/library/windows/apps/br229794.aspx) посредством свойства zoomableView. Среди всех встроенных элементов управления HTML и WinJS этому требованию удовлетворяет лишь ListView, и именно поэтому его обычно используют с ZoomView. Однако, вы можете обеспечить данный интерфейс в пользовательском элементе управления, когда объект, возвращаемый конструктором, содержит свойство zoomableView, которое является объектом, содержащим методы интерфейса. Среди этих методов задачи beginZoom (начало масштабирования) и endZoom (завершение масштабирования) вполне ясны, методы getCurrentItem (получить текущий элемент) и setCurrentItem (установить текущий элемент) позволяют элементу управления выполнять масштабирование для правильной группы, когда пользователь коснётся её в режиме общего просмотра.

Подробности вы можете найти в примере "HTML SemanticZoom для пользовательских элементов управления" (http://code.msdn.microsoft.com/windowsapps/SemanticZoom-for-custom-4749edab), который, кроме того, предоставляет другие образцы использования пользовательских элементов управления.

Особенности FlipView и его стилизация

Нам не стоит забывать о скромном элементе управления FlipView, рассматривая ListView - один из самых сложных и богатых возможностями элементов управления WinJS. Поэтому, прежде чем мы продолжим углубляться в его особенности, посвятим несколько страниц описанию FlipView, через рассмотрение сценариев его использования, показанных в примере "Элемент управления FlipView" (http://code.msdn.microsoft.com/windowsapps/FlipView-control-sample-18e434b4). Так же важно отметить, что хотя этот пример показыват возможности элемента управления в сравнительно малой области, FlipView может иметь любой физический размер, даже занимать большую часть экрана. Обычное использование этого элемента управления, на самом деле, позволяет пользователю просматривать полноразмерные изображения в фотогалерее, переходя между ними путём пролистывания. Конечно, FlipView можно использовать везде, где он может понадобиться, в большом или малом размере. Смотрите материал "Руководство по элементу управления FlipView" (http://msdn.microsoft.com/library/windows/apps/hh850405) для того, чтобы узнать, как им лучше пользоваться.

Во всяком случае, Сценарий 2 в примере ("Ориентация и расстояние между элементами") демонстрирует свойство orientation этого элемента управления. Оно определяет местоположение элементов управления со стрелкой: слева и справа (horizontal), или сверху и снизу (vertical), как показано ниже. Кроме того, оно определяет начальные и конечные анимации элемента, и то, использует ли элемент управления клавиши вправо/влево или вверх/вниз для навигации с помощью клавиатуры. Данный сценарий так же позволит вам установить свойство itemspacing, которое задаёт размер свободного места между элементами, когда вы перематываете их, используя сенсорные жесты (ниже справа) Эти эффекты невидны при использовании клавиатуры или мыши для тех же целей. Для того, чтобы их увидеть, вам понадобится использовать эмуляцию сенсорного дисплея в имитаторе Visual Studio для перемещения между элементами.

Сценарий 3 ("Использование интерактивного содержимого") показывает использование функции шаблона (template function) вместо декларативно описанного шаблона. Больше об этом мы поговорим в разделе "Как, на самом деле, работают шаблоны" ниже в этой лекции, но, говоря простым языком, функция шаблона или визуализатор (renderer) создаёт элементы и устанавливает их свойства программно, делая то, что обычно выполняет WinJS.Binding.Template на основе разметки шаблона, которую вы ему предоставляете. Это позволяет вам выводить элементы по-разному (то есть, создавать разные элементы классов настройки стилей) в зависимости от их реальных данных. В Сценарии 3, источник данных содержит "оглавление" элементов в начале, для которого визуализатор (функция, которая называется myTemplate в js/interactiveContent.js) создаёт полностью различные элементы:

Сценарий так же задаёт прослушиватель события click в записях "оглавления", обработчик которого переходит на соответствующий элемент, уставливая свойство currentPage элемента управления FlipView. Элемент-изображение имеет обратную ссылку на "оглавление". Посмотрите функцию clickHandler в коде для того, чтобы увидеть оба этих действия.

Сценарий 4 ("Создание контекстного элемента управления") показывает добавление навигационных элементов управления путём их наложения на каждый из элементов:

Элементы выводятся с использованием декларативного шаблона, который в данном случае содержит элемент-заполнитель для навигационых элементов управления (html/contextControl.html), div, который называется ContextContainer.

<div>	
<div id="contextControl_FlipView" class="flipView" data-win-control="WinJS.UI.FlipView"
data-win-options="{ itemDataSource: DefaultData.bindingList.dataSource,	
itemTemplate: contextControl_ItemTemplate }">	
</div>	
<div id="ContextContainer"></div>	
</div>

Когда элемент управления инициализируется в методе processed в js/contextControl.js, в примере вызывается асинхронный метод FlipView count. Обработчик завершения, countRetrieved, затем создаёт навигационные элементы управления, используя строку стилизованных переключателей. Обработчик onpropertychange для каждого переключателя, затем, устанавливает свойство currentPage FlipView.

Сценарий 4, кроме того, настраивает слушатели для событий pageselected и pagevisibilitychanged элемента управления FlipView. Первый используется для обновления навигационных переключателей, когда пользователь перемещается между страницами. Второй используется для предотвращения нажатия на переключатель в ходе листания. (Событие, происходит, когда элемент меняет видимость и вызывается дважды для каждого перелистывания, один раз - для предыдущего элемента, и еще раз - для нового).

Сценарий 5 ("Стилизация навигационных кнопок") показывает особенности стилизации FlipView, включающую в себя различные win-* стили и псевдо-классы, как показано здесь:

Используется для границ, отбивок и так далее (Use for margins, padding, etc.)

Для фона, если расстояние между элементами больше 0, задаёт стиль конкретного элемента управления (For background with item spacing > 0, style the specific control).

Если вы, в шаблоне, используете собственные навигационные кнопки (подключённые к методам next и previous), скройте кнопки по умолчанию, добавив display.none к правилу стиля <control selector> .win-navbutton.

И, наконец, есть еще несколько методов и событий элемента управления FlipView, которые не используются в примере:

Для того, чтобы узнать об этом подробности, обратитесь к документации по WinJS.UI.FlipView (http://msdn.microsoft.com/library/windows/apps/br211711.aspx).

Источники данных

Во всех случаях, которые мы рассмотрели, мы использовали источники данных, построенные на основе WinJS.Binding.List и находящиеся в памяти. Очевидно, существуют и другие типы источников данных, и их не обязательно сразу же загружать в память. Как же работать с такими источниками?

WinJS предоставляет некоторую помощь в этой области. Первое средство - это объект WinJS.UI.StorageDataSource, который работает с файлами в файловой системе, как в следующем разделе, в демонстрации работы FlipView с библиотекой изображений. Следующее средство - это WinJS.UI.VirtualizedDataSource, которое подразумевает использование базового класса для пользовательских источников данных, это расширенный сценарий, которого мы коснёмся лишь кратко.

FlipView и библиотека изображений

Всё, что мы видели в примере, касающемся FlipView, ведет нас к совершенно очевидной возможности: перемещаться по файлам изображений в папке. Используя то, что мы узнали, как нам реализовать это? У нас уже есть шаблон элемента, который содержит тег img, таким образом, возможно, нам лишь нужны некоторые URI для таких файлов. Возможно, нам следует создать их массив, используя API наподобие Windows.Storage.KnownFolders.picturesLibrary.getFilesAsync (конечно, объявив возможность Библиотека изображений (Pictures Library) в манифесте). Это даст нам множество объектов StrorageFile, для которых мы можем вызвать URL.createObjectURL. Найденные URI мы можем сохранить в массиве и затем поместить их в WinJS.Binding.List:

var myFlipView = document.getElementById("pictures_FlipView").winControl;

Windows.Storage.KnownFolders.picturesLibrary.getFilesAsync()
.done(function (files) {
var pixURLs = [];

files.forEach(function (item) {
var url = URL.createObjectURL(item, {oneTimeOnly: true });
pixURLs.push({type: "item", title: item.name, picture: url });
});

var pixList = new WinJS.Binding.List(pixURLs);
myFlipView.itemDataSource = pixList.dataSource;
});

Хотя подобный подход работает, он может потребовать немного памяти при работе с большим числом изображений высокого разрешения, так как каждое изображение должно быть полностью загружено для того, чтобы оно отобразилось во FlipView. Возможно, это как раз то, что вам нужно, но в других случаях подобный подход потребует больше ресурсов, чем необходимо. Кроме того, минус такого подхода заключается в том, что изображения лишь сжимаются и растягиваются для того, чтобы отобразиться во FlipView, без какого-либо учёта соотношения сторон, а это не позволит получить наилучший результат.

Лучший подход заключается в использовании WinJS.UI.StorageDataSource (http://msdn.microsoft.com/library/windows/apps/br212650.aspx), который работает напрямую с файловой системой, вместо того, чтобы работать с массивом, расположенным в памяти. Я реализовал это в виде Сценария 8 в измененном примере использования FlipView в дополнительных материалах к этой лекции (Другие материалы можно найти в примере "Использование StorageDataSource и GetVirtualizedFilesVector" (http://code.msdn.microsoft.com/windowsapps/Data-source-adapter-sample-3d32e535)). Здесь мы можем использовать короткое имя для того, чтобы получить источник данных из библиотеки изображений:

myFlipView.itemDataSource = new WinJS.UI.StorageDataSource("Pictures");

"Pictures" это короткое имя, так как первый аргумент StorageSource, это файловый запрос, который исходит из API Windows.Storage.Search, подробнее об этом мы поговорим в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Эти запросы, которые попадают в мощную функцию Windows.Storage.StorageFolder.createFileQueryWithOptions (http://msdn.microsoft.com/library/windows/apps/br211591.aspx) - это способы для перебора файлов в папках вместе с метаданными - такими, как обложки альбомов, подробности о записях, и эскизы, которые кадрированы так, чтобы сохранить соотношение сторон. Короткие имена, наподобие"Pictures" (а так же "Music", "Documents", "Videos" которые нуждаются в соответствующих возможностях, объявленных в манифесте) лишь создают типичные запросы для подобных библиотек документов.

Нужно отметить, что StorageDataSource не поддерживает напрямую одностороннюю привязку данных, поэтому вы получите исключение, если вы попытаетесь сослаться на элемент напрямую в шаблоне. Чтобы это обойти, нужно явно использовать функцию-инициализатор WinJS.Binding.oneTime для каждого свойства:

<div id="pictures_ItemTemplate" data-win-control="WinJS.Binding.Template">
<div class="overlaidItemTemplate">
<img class="image" data-win-bind="src: thumbnail InitFunctions.thumbURL;
alt: name WinJS.Binding.oneTime" />
<div class="overlay">
<h2 class="ItemTitle" data-win-bind="innerText: name WinJS.Binding.oneTime"></h2>
</div>
</div>
</div>

В случае со свойством img.src, файловый запрос возвращает нам элементы типа Windows.Storage.BulkAccess.FileInformation (http://msdn.microsoft.com/library/windows/apps/windows.storage.bulkaccess.fileinformation.aspx) (переменная source в коде ниже), которая содержит эскизы изображений, а не URI. Для того чтобы конвертировать эти данные об изображениях в URI, нам нужно использовать свой собственный инициализатор привязки:

WinJS.Namespace.define("InitFunctions", {	
thumbURL: WinJS.Binding.initializer(function (source, sourceProp, dest, destProp) {
if (source.thumbnail) {	
dest.src = URL.createObjectURL(source.thumbnail, { oneTimeOnly: true });	
}	
})	
});

В этом инициализаторе часть data-win-bind src : thumbnail, на самом деле, игнорируется, так как мы устанавливаем свойство изображения src напрямую в значение source.thumbnail. Это - лишь форма односторонней привязки данных.

Обратите внимание на то, что эскизы не всегда сразу доступны в объекте FileInformation, именно поэтому мы проверяем, действительно ли у нас имеется эскиз, прежде чем создаём URI для него. Это означает, что быстрое перелистывание изображений может показать пользователю пустые места для изображений. Для того, чтобы справиться с этим, мы можем прослушивать событие FileInformation.onthumbnailupdated и обновлять элементы в это время. Лучший способ добиться этого заключается в использовании вспомогательного метода StorageDataSource.loadThumbnail (http://msdn.microsoft.com/library/windows/apps/jj553712.aspx), который позволяет нам убедиться в вызове removeEventListener для этого события WinRT (смотрите раздел "События WinRT и removeEventListener" в лекции 3).

Вы можете использовать этот метод внутри инициализатора привязки, как показано в Сценарии 1 вышеупомянутого примера "Использование StorageDataSource и GetVirtualizedFilesVector" (http://code.msdn.microsoft.com/windowsapps/Data-source-adapter-sample-3d32e535), или внутри функции рендеринга, которая имеется в декларативном шаблоне. Мы сделаем это для нашего примера использования FlipView позже, в разделе "Как, на самом деле, работают шаблоны", что, кроме того, позволит нам избежать трюков с односторонней привязкой данных.

В качестве последнего замечания, Сценарий 6 в примере работы с FlipView, содержит некоторые образцы использования различных источников данных, особенно те, которые работают со службой поиска Bing. Поэтому, взглянем на пользовательские источники данных.

Пользовательские источники данных

Теперь, когда мы видели элемент управления для коллекций, наподобие FlipView, который работал с двумя разными источниками данных, вы, вероятно, начинаете догадываться о том, что все источники данных имеют некоторые общие характеристики и общий программный интерфейс. Это снова продемонстрировано в Сценарии 6 примера использования FlipView, так же, как и в примере "Работа HTML ListView с источниками данных" (http://code.msdn.microsoft.com/windowsapps/ListView-custom-data-4dcfb128), показанного на Рис. 5.6, который мы рассмотрим в этом разделе.

Пример работы HTML ListView с источниками данных


увеличить изображение

Рис. 5.6.  Пример работы HTML ListView с источниками данных

Сценарии 2 и 3 этого примера работают на основе источника данных WinJS.Binding.List, как мы уже видели, и предоставляют кнопки для управления этим источником данных. Эти изменения отражаются на выходных данных. Разница между двумя сценариями заключается в том, что Сценарий 2 манипулирует данными посредством методов WinJS.Binding.List наподобие move, в то время как Сценарий 3 манипулирует источником данных, на котором он основан, посредством более общего API ListDataSource (http://msdn.microsoft.com/library/windows/apps/br211786.aspx).

Из-за привязки данных, изменения в данных отражаются на элементе управления ListView в любом случае, но есть три важных различия. Первое, интерфейс ListDataSource - обычный для всех источников данных, в итоге любой код, написанный с его использованием, будет работать для любого источника данных. Второе - его методы обычно асинхронны, так как источник данных может быть подключён к онлайновому сервису или другому подобному ресурсу. Третье - ListDataSource обеспечивает вызов beginEdits для пакетного изменения данных, что подавляет любые сообщения об изменениях, направленные внешним присоединённым объектам, до вызова endEdits. Это позволяет вам выполнять изменение больших объемов данных способом, который может улучшить производительность ListView.

Сценарии 1 и 4 в примере показывают, как можно создать пользовательский источник данных. Сценарий 1 создаёт источник данных для поиска Bing. Сценарий 4 создаёт один источник в виде массива в памяти, который вы можете приспособить для работы с некоторыми полученными извне данными, которые поступают от некоего сервиса небольшими порциями. Важно здесь то, что при всех этих подходах реализуется то, что называется адаптером обработки данных (data adapter), который является объектом с методами интерфейса WinJS.UI.IListDataAdapter (http://msdn.microsoft.com/library/windows/apps/br212603.aspx). Это обеспечивает такие возможности, как кэширование, виртуализация, обнаружение изменений и так далее. К счастью, вы получаете большинство из этих методов, наследуя ваш класс от WinJS.UI.VirtualizedDataSource (http://msdn.microsoft.com/library/windows/apps/hh701413.aspx) и затем реализуя те методы, которые вам нужно настроить. В примере, например, bindingImageDataSource определен так, как показано ниже (смотрите js/BingImageSearchDataSource.js):

bingImageSearchDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function (devkey, query) {
this._baseDataSourceConstructor(new bingImageSearchDataAdapter(devkey, query));
});

Где класс bingImageSearchDataAdapter реализует напрямую лишь методы getCount и itemsFromIndex.

Для получения более глубоких знаний по данному вопросу, выходящих за пределы этого примера, я отсылаю вас к сессии конференции Build 2011 года: "APP210-T: Построение коллекций, управляемых данными и приложений со списками с использованием ListView в HTML5" (http://channel9.msdn.com/Events/BUILD/BUILD2011/APP-210T). Кое-что с тех пор изменилось (так, ArrayDataSource теперь WinJS.Binding.List), но в целом здесь хорошо разъяснены механизмы. Кроме того, полезно помнить, что вы так же можете использовать другие языки, наподобие C# или C++ для того, чтобы описать пользовательский источник данных. Подобные языки могут предложить более высокую производительность внутри источника данных и позволят получить доступ к более производительным API, нежели те, к которым даёт доступ JavaScript.

Как, на самом деле, работают шаблоны

Ранее, когда мы рассматривали шаблон Приложение таблицы, я упоминал, что вы можете использовать функцию вместо декларативного описания шаблона для свойств наподобие itemTemplate (FlipView и ListView) и groupHeaderTemplate (ListView). Это важная возможность, так как она позволяет вам динамически выводить отдельные элементы в коллекции, используя содержимое каждого из них для настройки его внешнего вида. Это, кроме того, позволяет вам инициализировать отдельные элементы способами, которые нельзя выполнить в декларативной форме, такими, как объединение ячеек, отложенная загрузка изображений, добавление обработчиков событий для отдельных частей элемента и оптимизация производительности.

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

Ссылки на шаблоны

Как я упоминал выше, что если вы ссылаетесь на декларативный шаблон в элементах управления FlipView или ListView, то вы ссылаетесь на элемент, а не на id элемента. id работает, так как хост-процесс приложения создаёт переменные с подобными именами для элементов, которые они идентифицируют. Однако, мы не рекомендуем подобный подход, особенно внутри элементов управления страниц (что вы, вероятно, часто используете). Первое ограничение касается того, что только один элемент может иметь конкретный id, что означает, что вы столкнётесь с по-настоящему странным поведением, если вы попытаетесь вывести элемент управления страницы дважды в той же самой DOM.

Второе ограничение касается проблем с выравниванием по времени. Переменная, соответствующая id элемента, которую предоставляет хост-процесс приложения, не создаётся до тех пор, пока HTML-код, содержащий элемент, будет добавлен в DOM. В случае с элементом управления страницы, WinJS.UI.processAll вызывается до этого, что означает, что переменные, соответствующие id элемента для шаблонов в данной странице еще не будут доступны. В результате, любые элементы управления, использующие id для шаблона, либо станут причиной выдачи исключения, либо - показа пустого экрана. Оба происшествия весьма нежелательны.

Для того, чтобы избежать подобной проблемы в декларативных шаблонах, разместите имя шаблона в его атрибуте class:

<div data-win-control="WinJS.Binding.Template" class="myItemTemplate" ...></div>

Затем, в декларативном описании элемента управления, используйте синтаксис select("<selector>") в записях свойств, где <selector> - это всё что угодно, поддерживаемое element.querySelector:

<div data-win-control="WinJS.UI.ListView"
data-win-options="{ itemTemplate: select('.myItemTemplate') }"></div>

Здесь происходит больше всего, нежели просто вызов querySelector. Функция select внутри параметров, выполняет поиск, начиная с корня его элемента управления страницы. Если совпадения не найдено, она ищет другой элемент управления страницы, выше в DOM, затем просматривает его, продолжая процесс до нахождения совпадения. Это позволяет вам безопасно использовать два элемента управления страницы, оба из которых содержат те же имена классов для разных шаблонов, и каждая страница будет использовать локальный шаблон.

Кроме того, вы можете получить элемент шаблона, используя напрямую в коде querySelector и присваивая результат свойству itemTemplate. Обычно это может быть выполнено в функции страницы ready, как показано в проекте Приложение таблицы, и выполнение этого удовлетворяет обоим ограничениям, приведенным здесь, так как querySelector будет ограничен содержимым страницы и будет исполнен после WinJS.UI.processAll.

Элементы шаблона и рендеринг

Следующий интересный вопрос о шаблонах заключается в том, что мы, на самом деле, получаем, когда создаём экземпляр объекта WinJS.Binding.Template (http://msdn.microsoft.com/library/windows/apps/br229723.aspx)?

Это, в большей или меньшей степени, другой элемент управления WinJS, который превращается в элемент при вызове WinJS.UI.processAll. Разница в том, что он удаляет все дочерние элементы из DOM, таким образом, сам по себе он никогда не отображается. Он даже не устанавливает свойство winControl элемента, который содержит его.

Что у него действительно имеется, однако, это весьма полезная функция, которая называется render. Получая контекст данных (объект со свойствами) и элемент, render создаёт полную копию шаблона внутри элемента, разрешая любые взаимоотношения в шаблоне, касающиеся привязки данных (и в атрибуте data-win-bind, и в data-win-options), используя контекст данных. Коротко говоря, воспринимайте декларативные шаблоны как набор инструкций, которые использует метод render для выполнения всех необходимых методов createElement вместе с установкой свойств и выполнением привязки данных.

Как показано в материале "Использование шаблонов для привязки данных" (http://msdn.microsoft.com/library/windows/apps/hh700356.aspx), вы можете просто создать экземпляр шаблона и отобразить шаблон везде, где хотите:

var templateElement = document.getElementById("templateDiv");
var renderHere = document.getElementById("targetElement");
renderHere.innerHTML = "";

WinJS.UI.process(templateElement).then(function (templateControl) {
templateControl.render(myDataItem, renderHere);
});

Должно быть полностью очевидно, что это то, что действительно выполняют элементы управления FlipView и ListView для каждого элемента в заданном источнике данных. В случае с FlipView, он вызывает метод render каждый раз, когда вы переходите на другой элемент в источнике данных. ListView перебирает itemDataSource и вызывает renderer шаблона элемента для каждого элемента, и делает что-то подобное для собственных groupDataSource и groupHeaderTemplate.

Функции шаблонов (Часть 1)

Зная теперь, что элемент управления WinJS.Binding.Template, это, в своей основе, просто набор декларативных инструкций для его функции render, вы можете создать пользовательскую функцию, которая напрямую выполняет то же самое. Таким образом, в дополнение к элементу, свойство itemTemplate у FlipView и ListView и свойство ListView groupHeaderTemplate может так же принимать функцию рендеринга (отображения). Элементы управления используют typeof во время выполнения для того, чтобы определить, что вы присвоили данным свойствам, в итоге, если вы предоставите элемент шаблона, элемент управления вызовет его метод render. Если вы предоставили функцию, элемент управления просто вызовет данную функцию для каждого элемента, который нужно отобразить. Это предоставляет большую гибкость в настройке шаблона, основываясь на индивидуальных данных элемента.

На самом деле, функция рендеринга позволяет вам индивидуально контролировать не только то, как конструируются отдельные части каждого элемента, но и то, когда это происходит. Как таковая, функция рендеринга - это основное средство, с помощью которого вы можете реализовать пять прогрессивных уровней оптимизации, особенно для ListView. Осторожно! Впереди promise-объекты! Хорошо, я оставил большую часть этого разговора для конца лекции, потому что прежде нам нужно взглянуть на другие особенности ListView. Но здесь давайте, по крайней мере, взглянём на базовую структуру функции рендеринга, которая применима и к FlipView и к ListView, и которую вы можете видеть в примерах "Шаблоны элемента для HTML ListView" (http://code.msdn.microsoft.com/windowsapps/ListView-item-templates-7d74826f) и "Оптимизация производительности HTML ListView" (http://code.msdn.microsoft.com/windowsapps/ListView-performance-39fb71f0). Ниже мы воспользуемся кодом, взятым из них.

Для начинающих, вы можете указать функцию для рендеринга, используя её имя в data-win-options и для элемента управления ListView, и для элемента управления FlipView. Эта функция должна быть отмечена для обработки, как обсуждалось в лекции 4, так как она участвует в WinJS.UI.processAll, в итоге, это отличное место для использования WinJS.Utilities. markSupportForProcessing. Обратите внимание на то, что если вы присваиваете данную функцию itemTemplate или groupHeaderTemplate в JavaScript, она не нуждается в маркировке.

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

function basicRenderer(itemPromise) {
return itemPromise.then(buildElement);
};

function buildElement (item) {
var result = document.createElement("div");

//Создаёт элемент, обычно используя innerHTML
return result;
}

Функция рендеринга здесь первая. Она просто сообщает нам: "Когда itemPromise будет исполнен, что означает доступность элемента, вызвать функцию buildElement для данного элемента". Возвращая promise-объект из itemPromise.then (не done, обратите внимание), мы позволяем элементу управления для коллекций, который использует данную функцию рендеринга, объединить в цепочку promise-объекты элементов и promise-объекты, создающие элементы. Это особенно полезно, когда данные для элементов поступают из некоего сервиса или другого потенциально медленного источника данных, и это весьма полезно при инкрементной загрузке страницы, так как это позволяет элементу управления отменить promise-цепочку, если страница прокручена в другое место до того, как данная операция завершится. Коротко говоря, это - хорошая идея.

Просто для демонстрации, здесь показано, как мы можем сделать функцию рендеринга напрямую доступной из разметки, как в data-win-options = "{itemTemplate: Renderers.basic }":

WinJS.Namespace.define("Renderers", {
basic: WinJS.Utilities.markSupportedForProcessing(function (itemPromise) {
return itemPromise.then(buildElement);
})	
 }

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

function basicRenderer(itemPromise) {
return itemPromise.then(function (item) {
var result = document.createElement("div");

//Создание элемента, обычно с использованием innerHTML

return result;
})
};

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

<div id="pictures_ItemTemplate" data-win-control="WinJS.Binding.Template">
<div class="overlaidItemTemplate">
<img class="image" data-win-bind="src: thumbnail InitFunctions.thumbURL;
alt: name WinJS.Binding.oneTime" />
<div class="overlay">
<h2 class="ItemTitle" data-win-bind="innerText: name WinJS.Binding.oneTime"></h2>
</div>
</div>
</div>

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

//Ранее: присваивание шаблона в коде
myFlipView.itemTemplate = thumbFlipRenderer;


//Функция рендеринга (смотрите "Функции шаблонов (Часть 2) ниже для оптимизации)
function thumbFlipRenderer(itemPromise) {	
return itemPromise.then(buildElement);	
};	

//Функция, которая строит дерево элементов	
function buildElement (item) {	
var result = document.createElement("div");
result.className = "overlaidItemTemplate";	

var innerHTML = "<img class='thumbImage'>";	
var innerHTML += "<div class='overlay'>";	
innerHTML += "<h2 class='ItemTitle'>" + item.data.name + "</h2>";
innerHTML += "</div>";	

result.innerHTML = innerHTML;

//Задание слушателя для thumbnailUpdated который осуществляет вывод в элемент img 
var img = result.querySelector("img");
WinJS.UI.StorageDataSource.loadThumbnail(item, img).then();

return result;
}

Так как у нас уже есть отдельные элементы, нам не нужно отвлекаться на детали декларативной привязки данных и конвертеров: мы можем просто напрямую использовать нужные нам свойства из item.data. Как и ранее, помните, что свойство thumbnail (эскиз) элемента FileInformation может быть еще не установлено. Это то место, где мы можем использовать метод StorageDataSource.loadThumbnail для прослушивания события FileInformation.onthumbnailupdated. Эта вспомогательная функция выведет эскиз в наш элемент img, когда эскиз станет доступен (с небольшой анимацией в придачу!).

Совет. Вы могли, кроме того, заметить, что я создавал большинство элементов, используя корневое свойство div.innerHTML вместо вызова createElement и appendChild и установки конкретных свойств напрямую. За исключением очень простых структур, установка innerHTML в корневом элементе более эффективна, так как мы минимизируем количество вызовов API DOM. Это не играет большой роли для элемента управления FlipView, элементы которого выводятся по одному за раз, но это становится очень важным для ListView, который вполне может иметь тысячи элементов. На самом деле, когда мы начинаем задумываться об оптимизации производительности, мы, кроме того, хотим выводить элемент на разных стадиях, как при отложенной загрузке изображений. Мы увидим подробности в разделе "Функции шаблонов (Часть 2): Promise-объекты!" в конце этой лекции.

Особенности и стилизация ListView

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

Когда ListView - это неправильный выбор?

ListView - это один из богатейших по возможностей элементов управления во всей Windows. Он очень мощный, гибкий, и, как мы уже знаем, весьма глубокий и сложный. Но по всем этим причинам, иногда, он является не лучшим выбором! В зависимости от дизайна, может быть проще использовать обычный HTML/CSS макет

Концептуально ListView определяется путём задания взаимоотношений трех частей: источника данных, шаблонов и макета. То есть, объекты из источника данных, которые могут быть сгруппированы, отсортированы и отфильтрованы, выводятся с использованеим шаблонов и организуются с помощью макета (обычно - с помощью групп и заголовков групп). В подобном определении, ListView предназначен для того, чтобы помочь в визуализации коллекций похожих и/или взаимосвязанных объектов, где группировка тоже подразумевает взаимоотношения определенного рода.

Имея это в виду, можно признать следующие факторы убедительно свидетельствующими о том, что ListView - это хороший выбор для вывода некоторой коллекции:

С другой стороны, противоположные факторы говорят о том, что ListView не является правильным выбором:

Позвольте мне пояснить, что я не говорю о дизайне - ваш дизайнер может передать вам любой макет, который он хочет, и, как разработчик, именно вы должны решить, как реализовать его! Я говорю о том, как вы выбираете подход для подобной реализации, с использованием ли элементов управления наподобие ListView, или с применением обычного HTML/CSS макета.

Я говорю об этом, так как в работе с разработчиками, которые создают самые первые приложения для Магазина Windows, мы часто видим, как они пытаются использовать ListView в ситуации, где он просто не нужен. Домашняя страница приложения, например, может содержать комбинацию из лент новостей, списков друзей и календаря. Страница детальной информации об элементе может включать в себя изображение, текстовое описание и медиа-библиотеку. В обоих случаях, страница содержит ограниченное количество разделов и разделы содержат очень разное содержимое, о котором можно сказать, что оно не имеет межгрупповых связей. Поэтому, использование ListView более сложно, чем простое использование одного div, поддерживающего прокрутку с CSS-сеткой, в которой вы можете разместить любые разделы, которые вам нужны.

Внутри данных разделов, конечно, вы можете использовать элементы управления ListView для отображения коллекций элементов, но если говорить обо всей странице, то обычный макет, основанный на div - это всё, что вам нужно. Я показал подобный выбор на Рис. 5.7, используя изображение из материала "Проектирование навигации для приложений Магазина Windows" (http://msdn.microsoft.com/library/windows/apps/hh761500), так как вы, возможно, получите похожие изображения от вашего дизайнера. За исключением навигационных стрелок, хаб приложения и страницы детальной информации обычно используют div в качестве корневого элемента, в то время как страница раздела обычно представляет собой ListView. Внутри корневого узла приложения и страниц детальной информации могут быть элементы управления ListView, но там, где нужно отобразить фиксированное содержимое (вроде отдельного элемента), лучше подойдёт div.

Разбиение типичного дизайна Хаб-Раздел-Сведения (Hub-Section-Details), спроектированного с применением элементов div и элементов управления ListView


увеличить изображение

Рис. 5.7.  Разбиение типичного дизайна Хаб-Раздел-Сведения (Hub-Section-Details), спроектированного с применением элементов div и элементов управления ListView

Корневой раздел приложения (хаб): страница - это div; разделы в сетке - это либо div'ы, либо элементы управления ListView; если первый раздел содержит различные элементы, ей следует содержать ListView (Hub page: page is a div; sections in a grid are either divs or ListViews; if the first section has variable items, it could be a ListView).

Элемент div с макетом (div with layout)

Страница раздела: тело страницы (исключая заголовок) - это один ListView (Section page: the body of the page (excluding a page header) is a single ListView)

Страница детальной информации (сведений): страница - это div; разделы в сетке - либо элементы div, либо - элементы управления ListView (Detail page: page is a div; sections in a grid are either divs or ListViews)

Подсказывает о том, что вы идете по неверному пути, кстати, тот факт, что вы пытаетесь скомбинировать несколько коллекций несвязанных данных в единый источник данных, привязывая этот источник к ListView и реализуя функцию рендеринга для того, чтобы снова разделить эти данные, чтобы всё вывелось верно. Всей этой дополнительной работы можно избежать, просто используя макет HTML/CSS.

Для того, чтобы узнать больше о проектировании ListView, обратитесь к материалу: "Руководство и контрольный список для элементов управления ListView" (http://msdn.microsoft.com/library/windows/apps/hh465465.aspx).

Параметры, выделения и методы отдельного элемента

В предыдущем разделе мы уже видели некоторые параметры, которые вы можете использовать при создании ListView, параметры, которые связаны со свойствами элемента управления и доступны, кроме того, из JavaScript. Посмотрим на полный список свойств, методов и событий, которые я организовал в несколько групп, в конце концов, эти свойства и методы превратились во что-то вроде коллекции! Подробности о них вы можете найти на странице WinJS.UI.ListView (http://msdn.microsoft.com/library/windows/apps/br211837.aspx), самое важное сейчас - понять как взаимосвязаны члены данных групп:

Врезка: Поведение при щелчке и прикосновении

Когда вы прикасаетесь к элементу в ListView или щёлкаете по нему мышью, при том, что свойство tapBehavior установлено во что-то кроме none, происходит небольшая анимация, путём примерно 97% масштабирования для подтверждения данного действия. Если в списке есть некоторые элементы, которые не могут быть активированы (такие, как некоторые группы или те, которые вы показываете как деактивированные, так как данные, лежащие в их основе, пока не доступны), они продолжают показывать эту анимацию, так как установка tapBehavior применяется к элементу управления в целом. Для того чтобы отключить анимацию для любого конкретного объекта, вы можете добавить класс win-interactive к его элементу внутри функции рендеринга, что является способом указания на то, что объект самостоятельно обрабатывает события щелчка или прикосновения, даже если не делает ничего, кроме их приёма. Если позже элемент можно будет активировать, вы можете, конечно, убрать этот класс.

Если вам нужно предотвратить выделение элемента, добавьте обработчик для события ListView selectionchanging и вызовите его метод args.detail.preventtapBehavior. Это работает для всех методов выделения, включая сенсорный жест прокрутки, щелчок мыши и нажатие на клавишу клавиатуры Enter.

Стилизация

После того, как мы коснулись этого в лекции 4 и в последнем разделе, посвященном ListView, стилизацию лучше рассматривать на иллюстрациях, как на Рис. 5.8, где я применил некоторые яркие CSS-стили к некоторым из стилей win-* так, чтобы они выделялись. Я рекомендую вам взглянуть на материал "Стилизация ListView и его элементов" (http://msdn.microsoft.com/library/windows/apps/hh850406.aspx) в документации, где детализированы некоторые дополнительные стили, не показанные здесь

Классы стиля, использованные элементом управления ListView


увеличить изображение

Рис. 5.8.  Классы стиля, использованные элементом управления ListView

Весь элемент управления (entire control)

Зона, где не осуществляется прокрутка (non-scrolling area)

Зона вокруг элемента, на самом деле, лишь для стилизации полей и прозрачности (the area around an item really only to style margin and transparency)

Зона внутри элемента (the area inside an item)

Зона, где осуществляется прокрутка (the scrollable area)

Некоторые замечания о стилизации

Заставки

Есть еще один визуальный элемент ListView, который похож на стилизацию, но стилизация на него не воздействует. Его называют заставкой (backdrop), это эффект, который включен по умолчанию при использовании gridLayout. На аппаратном обеспечении невысокой производительности, особенно на мобильных устройствах, быстрая прокрутка содержимого ListView может легко опередить возможности элемента управления по загрузке и выводу элементов. Для того, чтобы пользователь видел результат своих действий, gridLayout показывает обычные заставки для элементов, основанные на размерах элементов по умолчанию и прокручивает их до момента вывода элементов. Как мы увидим в следующем разделе, вы можете выключить эту возможность с помощью свойства gridLayout disableBackgdrop и переопределить его серый цвет, применяемый по умолчанию, с помощью свойства backdropColor.

Макеты и объединение ячеек

Свойство layout элемента управления ListView, которое вы можете задать в любое время, содержит обект, который используется для организации элементов списка. WinJS предоставляет два предустановленных макета: WinJS.UI.GridLayout и WinJS.UI.ListLayout. Первый, уже описанный ранее, обеспечивает горизонтальную прокрутку двумерного макета, который располагает элементы в столбцах (сверху вниз) и затем в строках (слева направо). Второй - это одномерный макет, располагающий элементы сверху вниз, подходит для вертикальных списков (как в прикрепленном режиме просмотра). И тот и другой следуют рекомендованным для представления коллекций подходам к дизайну.

Говоря техническим языком, свойство layout - это объект, содержащий некоторое количество других параметров вместе со свойством type. Обычно вы видите синтаксис layout: { type: <layout> } в строке data-win-options ListView, где <layout> - это WinJS.UI.GridLayout или WinJS.UI.ListLayout (технически - имя функции-конструктора). При декларативном использовании, layout может так же содержать особенные параметры, зависящие от его типа. Например, следующий код конфигурирует gridLayout, содержащий заголовки слева и четыре строки:

layout: { type: WinJS.UI.GridLayout, groupHeaderPosition: 'left', maxRows: 4 }

Если вы создаете объект макета в JavaScript, используя new для вызова конструктора напрямую (и присваивая её свойству layout), вы можете задать дополнительные параметры в конструкторе. Это исполняется в приложении, построенном по шаблону Приложение таблицы в методе initializeLayout в файле pages/groupedItems/groupedItems.js:

listView.layout = new ui.GridLayout({ groupHeaderPosition: "top" });

Кроме того, вы можете задать свойства объекта layout элемента управления ListView в JavaScript, при его создании, если хотите воспользоваться подобным подходом. Обычно изменение свойств обновляет макет.

В любом случае, каждый макет имеет собственные уникальные параметры. Для gridLayout это следующие:

gridLayout так же имеет свойство только для чтения, которое называется horizontal и всегда имеет значение true. Для ListLayout свойство horizontal всегда false и не имеет других настраиваемых параметров.

Теперь, так как свойство layout ListView - это лишь объект (или имя конструктора для подобного объекта), можете ли вы создать собственную функцию, определяющую макет? Да, вы можете: создайте класс, который обеспечивет те же самые открытые методы, что и встроенные макеты, как описано в WinJS.UI.Layout (http://msdn.microsoft.com/library/windows/apps/br211781.aspx) (данная тема пока недостаточно документирована). Здесь объект макета может предоставлять любые другие параметры (свойства и методы), которые к нему применимы.

Сейчас, прежде чем вы начали размышлять над тем, нужен ли вам макет собственной разработки, хочу отметить, что gridLayout предоставляет кое-что, называемое объединением ячеек (cell spanning), что позволяет вам создавать элементы различных размеров (для ListLayout это неприменимо). Именно для этого существуют свойства groupInfo и itemInfo, как показано в Сценариях 4 и 5 примера "Шаблоны элемента для HTML ListView" (http://code.msdn.microsoft.com/windowsapps/ListView-item-templates-7d74826f), как показано на Рис. 5.9.

Пример шаблонов элемента ListView показывает использование элементов различных размеров с использованием объединения ячеек


увеличить изображение

Рис. 5.9.  Пример шаблонов элемента ListView показывает использование элементов различных размеров с использованием объединения ячеек

Основная идея объединения ячеек - это задать ячейку для gridLayout, основываясь на размере наименьшего элемента (включая стили отбивки (padding) и полей(margin)). Для лучшей производительности делайте ячейки настолько большими, насколько это возможно, чтобы размер любого другого элемента в ListView был кратен размеру этого элемента.

Включается возможность объединения ячеек посредством свойства groupInfo gridLayout. Это функция, которая возвращает объект с треямя свойствами: enableCellsSpanning, которое следует установить в true, cellWidth и cellHeight, которые содержат размеры минимальной ячейки в пикселях (что, кстати, использует возможность gridLayout для вывода заставки в подобной ситуации). В примере (смотрите js/data.js), эта функция названа groupInfo, так же как свойство макета. Здесь, для ясности, я дал ей другое имя:

function cellSpanningInfo() {
return {	
enableCellSpanning: true,
cellWidth: 310,	
cellHeight: 80	
};	
}

Затем эту функцию задают как часть свойства layout в data-win-options:

layout: { type: WinJS.UI.GridLayout, groupInfo: cellSpanningInfo }

Или вы можете задать layout.groupInfo из JavaScript. В любом слуае, как только вы объявили об использовании объединения ячеек, шаблон элемента должен установить свойства каждого элемента style.width и style.height, и - подходящие значения отбивки, для увеличения ваших cellWidth и cellHeight в соответствии со следующими формулами (которые являются разными представлениями одной и той же формулы):

templateSize = ((cellSize + margin) x multiplier) - margin (размерШаблона = ((размерЯчейки + поле) х множитель) - поле)

cellSize = ((templateSize + margin) / multiplier) - margin
(размерЯчейки = ((размерШаблона+поле)/множитель) - поле)

В примере, эти стили установлены путём задания каждому из элементов одного из трёх имён классов: smallListIconTextItem, mediumListIconTextItem, и largeListIconTextItem, CSS-код которых приведен ниже: (из css/scenario4.css иcss/scenario5.css):

.smallListIconTextItem {
width: 300px;	
height: 70px;	
padding: 5px;	
}
.mediumListIconTextItem {
width: 300px;	
height: 160px;	
padding: 5px;	
}
.largeListIconTextItem {
width: 300px;	
height: 250px;	
padding: 5px;	
}

Так как каждый из этих классов имеет отбивку, их реальные размеры из CSS равняются 310х80, 310х170 и 310х260. Поля, которые используются в формуле, исходят из стиля win-container в таблице стилей WinJS, где они равны 5 пикселей (px). Таким образом:

((80 + 10) * 1) - 10 = 80; минус 5px отбивка сверху и снизу =  высота 70px в CSS 
((80 + 10) * 2) - 10 = 170; минус 5px отбивка = высота 160px
((80 + 10) * 3) - 10 = 260; минус 5px отбивка = высота 250px

Разница между Сценарием 4 и Сценарием 5 лишь в том, что первый присваивает имена классов элементам посредством функции шаблона. Последний же делает это в декларативной разметке и привязывает данные об именах классов к полям данных элемента, содержащим эти значения.

Что касается функции itemInfo, это способ оптимизации производительности listView при использовании объединения ячеек. Без присваивания функции этому свойству, gridLayout "вручную" определяет ширину и высоту каждого элемента при выводе и это может замедлить прокрутку, если вы быстро прокручиваете большое число элементов. Так как вам, возможно, уже известны размеры элементов, вы можете предоставить эту информацию посредством функции itemInfo. Эта функция принимает индекс элемента и возвращает объект, содержащий свойства элемента width и height. (Скоро мы увидим работающий пример).

function itemInfo(itemIndex) {
//определяет значения itemWidth и itemHeight по заданному itemIndex
return {	
newColumn: false,	
itemWidth: itemWidth,	
itemHeight: itemHeight	
};	
}

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

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

Сейчас вы можете задаться вопросом: что произойдёт, если я задам различные размеры в шаблоне элемента, но не объявлю об объединении ячеек? Всё закончится перекрывающими (и весьма странно выглядящими) ячейками. Это происходит потому, что gridLayout воспринимает первый элемент в группе в качестве базового, задающего размеры всех остальных элементов (и, так же, сетки из заставок). Он не пытается автоматически изменить размер каждого элемента в зависимости от его содержимого. Испытайте это в Сценариях 4 и 5: удалите свойство layout.group из data-win-options ListView в html/scenario4.html или html/scenario5.html и перезапустите приложение. Вы увидите, как элементы среднего и большого размера заходят друг на друга, как показано ниже:

Затем, пройдите в js/data.js и установите стиль первого элемента в массиве myCellSpanningData на largeListIconTextItem, и перезапустите приложение. ListView теперь будет иметь макет с данным размером, установленным в качестве базового размера элемента:

Использование размеров первого элемента, как здесь, подчёркивает тот факт, что ListView с объединением ячеек требует больше времени на вывод, так как он должен измерить каждый из элементов в том виде, в котором он размещается, либо с помощью функции itemInfo, либо без неё. По этой причине, возможно, лучше избегать объединения ячеек при работе с большими списками.

Гораздо интереснее это всё становится, если подумать о том, как gridLayout поступает с элементами разной ширины. Да и в примере этого нет. В базовом алгоритме он всё еще располагает элементы сверху вниз и слева направо, но сейчас он заполняет пустые пространства небольшими элементами, когда большие элементы создают подобные пустоты. Для того, чтобы это показать, изменим пример таким образом, чтобы наименьший элемент имел размеры 155х80 (половина оригинального размера), средний был размером 310х80, и большой - 310х160. Вот какие изменения позволят нам это реализовать:

  1. Отменим любые изменения из предыдущих испытаний: в html/scenario4.html, вернем назад groupInfo в data-win-options, и в js/data.js, изменим класс первого элемента myCellSpanningData обратно к значению smallListIconTextItem.
  2. В js/data.js, изменим cellWidth в groupInfo на 155 (половина от 310), и оставим cellHeight в значении 80. Для ясности, кроме того, добавим значение приращения к началу текста каждого элемента в массиве myCellSpanningData.
  3. В css/scenario4.css:
    • Изменим ширину (width) smallListIconTextItem до 145px. Применим формулу, ((145+10)*1)-10=145. Высоту (height) изменим на 70px.
    • Изменим ширину mediumListIconTextItem на 310px, высоту - на 70px.
    • Изменим ширину largeListIconTextItem на 310px, выс оту на 160px. Для высоты применим формулу: ((80+10)*2)-10=170px.
    • Установим стиль width в правиле #listview в значение 800px, height - в 600px (для того, чтобы было больше места, в котором можно видеть макет)

Рекомендую выполнять эти изменения в Blend, где ваши правки отражаются на результате гораздо быстрее, чем когда вы, для проверки, запускаете приложение из Visual Studio. В любом случае, результаты, которые показаны на Рис. 5.10, где числа показывают нам порядок, в котором расположены элементы (извиняюсь за обрезанный текст… чем-то приходится жертвовать). Копию этого примера вы можете найти в дополнительных материалах к курсу.

Измененный пример работы с шаблонами элементов ListView, более полно показывающий объединение ячеек


увеличить изображение

Рис. 5.10.  Измененный пример работы с шаблонами элементов ListView, более полно показывающий объединение ячеек

В измененный пример я включил функцию itemInfo в js/data.js, как вы уже могли заметить. Она возвращает размеры элементов в соответствии с типами, заданными для этих элементов:

function itemInfo(index) {	
//getItem(index).data получает массив элементов из WinJS.Binding.List
var item = myCellSpanningData.getItem(index).data;	
var width, height;	
switch (item.type) {	
case "smallListIconTextItem":
width = 145;	
height = 70;	
break;	
case "mediumListIconTextItem":
width = 310;	
height = 70;	
break;	
case "largeListIconTextItem":
width = 310;	
height = 160;	
break;	
}	
return {	
newColumn: false,	
itemWidth: width,	
itemHeight: height
};	
}

Вы можете установить в этой функции точку останова и убедиться в том, что она вызывается для каждого элемента. Кроме того, вы можете увидеть, что это приводит к одному и тому же результату. Теперь измените возвращаемое значение newColumn так, как показано ниже, для того, чтобы принудительно начать вывод с новой колонки перед элементом №7 и №15 на Рис. 5.10, так как они странно располагаются по столбцам.

newColumn: (index == 6 || index == 14),  //Разрыв на элементах 7 и 15 (индексы 6 и 14)

Результат этого изменения показан на Рис. 5.11.

Вывод с новой колонки при объединении ячеек на элементах 7 и 15


Рис. 5.11.  Вывод с новой колонки при объединении ячеек на элементах 7 и 15

Последнее, что я отметил, экспериментируя с данным примером, это то, что если размеры элементов в правиле стиля наподобие smallListIconTextItem меньше, чем размер дочернего элемента, такого, как .regularlistIconTextItem (который включает поля и отбивки) больший размер выигрывает в макете. В ходе собственных экспериментов вы можете захотеть удалить поля по умолчанию в 5px, которые установлены для win-container. Это то, что создаёт пустые пространства между элементами на Рис. 5.10, но это должно быть добавлено в уравнение. Следующее правило устанавливает это поле в 0px:

#listView > .win-horizontal .win-container {
margin: 0px;
}

Оптимизация производительности ListView

Я часто говорю людям, что с ListView можно столько всего делать, о нём можно столько всего узнать, что он достоен отдельной книги! На самом деле, Microsoft могла просто создать базовый элемент управления, который позволял бы вам создавать элементы, настроенные по шаблону и на этом остановиться. Однако, зная, что ListView будет в центре огромного количества приложений (возможно, в составе большинства приложений вне игровых приложений), ожидая, что ListView будет вызываться для управления тысячами, или даже десятками тысяч элементов, высокопрофессиональная и увлеченная своим делом группа инженеров сделала всё возможное для обеспечения множества уровней усовершенствований, которые позволят вашим приложениям наилучшим образом выполнять своё предназначение.

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

Мне хотелось бы отметить, что материал "Использование элемента управления ListView" (http://msdn.microsoft.com/library/windows/apps/Hh781224.aspx) содержит даже больше советов, чем я способен привести здесь. (Мне нужно писать и другие главы!). Я надеюсь, что вы изучите данный материал, и кто знает, может быть вы станете тем, кто напишет всеобъемлющую книгу по ListView! Более того, дополнительное руководство по производительности приложения в целом можно найти в материале "Рекомендации по повышению производительности приложений Магазина Windows" (http://msdn.microsoft.com/library/windows/apps/hh465194.aspx), который содержит и подраздел об использовании ListView.

Произвольный доступ

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

Именно поэтому свойство loadingBehavior установлено по умолчанию в значение "randomaccess". В этом режиме полоса прокрутки ListView отражает общий размер списка, в итоге, пользователь сможет оценить его размер, но ListView в любой момент полностью хранит в памяти лишь пять полных экранов элементов (с общим лимитом в 1000 элементов). Для большинства страниц это означает видимую страницу (в области просмотра) и две буферных страницы впереди и позади неё. (Если вы просматриваете первую страницу, то буфер простирается на четыре страницы вперед; если вы на последней странице, то буфер простирается на четыре страницы позади неё - вы поняли идею).

Куда бы пользователь ни прокрутил список, любые страницы, не входящие в буферную зону или в область просмотра, отбрасываются (почти - мы сейчас к этому вернемся), после чего начинается загрузка новой видимой страницы и её буферных страниц. Таким образом, свойство ListView loadingState снова принимает значение itemLoading, затем принимает значение viewPortLoaded, когда выводятся видимые элементы, затем - itemsLoaded, когда загружены буферные страницы, и, затем, complete, когда всё выполнено. Опять же, в любое время, лишь пять страниц элементов загружены в память.

Теперь, когда я сказал, что ранее загруженные элементы отбрасываются, когда они выходят из диапазона видимой и буферных страниц, то, что с ними реально происходит можно назвать повторным использованием (recycling). Одна из наиболее ресурсоёмких частей вывода элементов - это создание элементов DOM, таким образом, ListView, на самом деле, перемещает эти элементы на новое место в списке и заполняет их новым содержимым. Это будет важно, когда мы посмотрим на оптимизацию в функциях шаблонов.

Инкрементная загрузка

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

Это то, для чего нужно значение "incremental" свойства loadingBehavior. В этом режиме полоса прокрутки ListView отражает лишь то, что загружено в список, но если пользователь пересечет определенный порог, например, он прокрутит список до конца - ListView запросит у источника данных другие страницы элементов и добавит их в список, обновив полосу прокрутки. В данном случае все загруженные элементы остаются загруженными, обеспечивая очень быстрое перемещение по списку загруженных данных, но с потенциально большим потреблением памяти, чем при работе в режиме произвольного доступа.

Работа инкрементной загрузки показана в Сценариях 2 и 3 примера "Режимы работы загрузки данных ListView" (http://code.msdn.microsoft.com/windowsapps/ListView-loading-behaviors-718a4673) (Сценарий 1 посвящен произвольному доступу, но там нет ничего нового). Режим инкрементной загрузки активирует следующие характеристики:

Функции шаблонов (Часть 2): Promise-объекты!

Как мы только что обсудили, параметры поведения ListView при загрузке данных имеют отношение к инкрементной загрузке страниц. Будет полезным скомбинировать их с инкрементной загрузкой элементов. Для этого нам нужно взглянуть на то, что называется конвейером визуализации (rendering pipeline), в том виде, в котором это реализовано в функциях шаблонов.

Когда мы ранее впервые смотрели на функции шаблонов (посмотрите "Как, на самом деле, работают шаблоны"), я отмечал, что они дают нам возможность контролировать и то, как конструируется элемент, и то, когда это происходит, и то, что подобные функции называют визуализаторами (renderers). Это - средство, с помощью которого вы можете реализовать пять прогрессивных уровней оптимизации для ListView (и для FlipView, хотя это распространено меньше). Сам факт использования визуализатора, с чем мы уже сталкивались, - это Уровень 1. Теперь мы готовы увидеть оставшиеся четыре уровня. Это захватывающая тема, так как она показывает усовершенствования, которые реализованы для нас в ListView!

В этом рассказе мы можем опираться на пример "Оптимизация производительности HTML ListView" (http://code.msdn.microsoft.com/windowsapps/ListView-performance-39fb71f0), который демонстрирует все эти уровни и позволяет вам видеть их эффект. Вот обзор этих возможностей:

Используя любой из этих визуализаторов, вы должны стремиться к тому, чтобы сделать их как можно более быстрыми. В особенности это касается минимизации использования вызовов DOM API, которые включают в себя установку индивидуальных свойств. Используйте строку innerHTML, там, где это возможно, для создания элементов, вместо того, чтобы выполнять отдельные вызовы, и сведите к минимуму использование getElementById, querySelector и других вызовов, предусматривающих обход DOM, кэшируя элементы, к которым вы обращаетесь чаще всего. Это значительно улучшит производительность. Для того, чтобы показать эффект от этих улучшений, следующий рисунок показывает пример того, как происходит вывод данных в неоптимизированном ListView:

Жёлтые столбцы показывают исполнение JavaScript-кода приложения - то есть - время, потраченное внутри визуализатора. Бежевые столбцы показывают время, потраченное в DOM-макете, и столбцы цвета морской волны показывают вывод данных на экран. Как вы можете видеть, когда элементы добавляются по одному, есть перерывы в исполнении кода, и сложность здесь в том, что большинство дисплеев обновляются лишь каждые 10-20 миллисекунд (50-100 Гц). В результате мы имеем множество разрывов в процессе визуализации.

После улучшений, график может выглядеть так, как показано ниже, когда работа приложения скомбинирована в одном блоке, таким образом, значительно уменьшилась нагрузка, связанная с DOM-макетом (бежевые столбцы):

Как и другое изображение, это получено от инструмента для анализа производительности, который называется XPerf и является частью Windows SDK (смотрите врезку). Без изучения деталей, самое важное, что мы здесь разобрали - это шаги, которые нужно предпринять для достижения подобного результата. А именно, различные формы визуализиторов, которые вы можете применять, как показано в примере.

Врезка: XPerf и msWriteProfilerMark

Средство XPerf в Windows SDK, документацию по которому можно найти на странице "Инструменты анализа производительности Windows" (http://msdn.microsoft.com/en-US/performance/cc825801.aspx), могут хорошо помочь вам понять реальное поведение вашего приложения в конкретной системе. Помимо прочего, он записывает в журнал вызовы, которые вы выполняете к msWriteProfilerMark (http://msdn.microsoft.com/library/windows/apps/dd433074.aspx), вы можете это заметить, просматривая исходный код WinJS. Для того чтобы отобразить это с помощью xperf, однако, вам нужно запустить протоколирование такой командой:

xperf -start user -on PerfTrack+Microsoft-IE:0x1300

и завершить протоколирование нижеприведенной командой, где <trace_filename> - это любой путь и имя файла по вашему выбору:

xperf -stop user -d <trace_filename>.etl

Открытие .etl-файла, который вы сохранили, приведет к запуску Windows Perfomance Analyzer (Анализатора производительности Windows) и отобразит график событий. Щелчкните правой кнопкой мыши по графику, затем щёлкните Summary Table (Сводная таблица). В этой таблице разверните Microsoft-IE и затем разверните узел Mshtml_DOM_CustomSiteEvents. Столбец Field3 должна содержать текст, который вы передаете msWriteProfilerMark, а столбец Time(s) поможет вам определить, сколько времени заняло действие.

В качестве основы для наших экспериментов, вот простой визуализатор:

function simpleRenderer(itemPromise) {
return itemPromise.then(function (item) {
var element = document.createElement("div");
element.className = "itemTempl";
element.innerHTML = "<img src='" + item.data.thumbnail +
"' alt='Databound image' /><div class='content'>" + item.data.title + "</div>";
return element;
});
}

Эта структура ожидает доступности данных элемента и возвращает promise-объект для элемента, данные которого будут получены.

Визуализатор с повторным использованием элементов-заполнителей создаёт элемент в два этапа. Возвращаемое значение - это объект, который содержит минимальный элемент-заполнитель в свойстве element, и promise-объект renderComplete, который выполняет, если необходимо, остальную работу:

function placeholderRenderer(itemPromise) {	
// создает базовый шаблон для элемента, не зависящий от данных
var element = document.createElement("div");	
element.className = "itemTempl";	
element.innerHTML = "<div class='content'>...</div>";	
// Возвращает элемент в виде заполнителя, и обратный вызов для его обновления, когда данные будут 
//доступны
return {
element: element,

// задаёт promise-объект, который завершит работу, когда завершится визуализация
// itemPromise завершит работу, когда будут доступны данные
renderComplete: itemPromise.then(function (item) {
// изменяет элемент для включения данных
element.querySelector(".content").innerText = item.data.title;
element.insertAdjacentHTML("afterBegin", "<img src='" +
item.data.thumbnail + "' alt='Databound image' />");
})
};
}

Свойство element, коротко говоря, определяет форму элемента и возвращается из визуализатора немедленно. Это позволяет ListView выполнять вывод макета, после чего он будет заполнен с помощью отложенного результата renderComplete. Вы можете видеть, что renderComplete, в целом, содержит то же самое, что возвращает простой визуализатор, за исключением уже созданного элементов-заполнителей. (Другой пример - добавленный Сценарий 8 упражнения FlipView в дополнительных материалах к этой лекции, имеет закомментированный код, который реализует данную возможность).

Визуализатор с повторным использованием элементов-заполнителей теперь знает о существования второго параметра, который называется recycled, и который ListView (но не FlipView) может предоставить вашей функции визуализации, когда параметр ListView loadingBehavior установлен в "randomaccess". Если задан recycled, вы можете просто очистить элементы, вернув их в качестве элементов-заполнителей, и затем заполнить данными внутри promise-объекта renderComplete, как ранее.

Если он не предоставлен (когда ListView только что создан, или если loadingBehavior установлено в значение "incremental"), вы создаёте новый элемент:

function recyclingPlaceholderRenderer(itemPromise, recycled) {
var element, img, label;
if (!recycled) {
// создает базовый шаблон для элемента, не зависящий от данных 
element = document.createElement("div");
element.className = "itemTempl";
element.innerHTML = "<img alt='Databound image' style='visibility:hidden;'/>" + "<div class='content'>...</div>";
}
else {
// очищает элемент, после чего мы можем повторно использовать его 
element = recycled;
label = element.querySelector(".content");
label.innerHTML = "...";
img = element.querySelector("img");
img.style.visibility = "hidden";
}
return {
element: element,
renderComplete: itemPromise.then(function (item) {
// изменяет элемент для включения данных 
if (!label) {
label = element.querySelector(".content");
img = element.querySelector("img");
}
label.innerText = item.data.title; img.src = item.data.thumbnail; img.style.visibility = "visible";
})
};
}

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

Если вы хотите очистить элементы, которые используются повторно, вы можете предоставить функцию для свойства ListView resetItem, которая будет содержать тот же код, как показано выше для данного случая. То же самое справедливо для свойства resetGroupHeader, так как вы можете использовать функции шаблона для заголовков групп, так же, как и для элементов. Мы не говорим много об этом, так как заголовков групп обычно немного, и они обычно не играют в производительности такой же роли. Несмотря на это, подобная возможность присутствует.

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

Ключевую роль здесь играют три метода: ready, loadImage и isOnScreen, которые прикрепляются к элементу, предоставленному itemPromise. Следующий код показывает, как этим пользоваться (здесь element.querySelector обходит лишь небольшую часть DOM, в итоге, это не проблема):

renderComplete: itemPromise.then(function (item) {	
// преобразует элемент для обновления его названия (title)	
if (!label) { label = element.querySelector(".content"); }
label.innerText = item.data.title;	
// использует promise-объект item.ready для того, чтобы отложить более
// ресурсоёмкие процедуры
 return item.ready;
// использует возможность объединения promise-объектов в цеочку
// для получения возможности отмены задания
}).then(function (item) {
//использует загрузчик изображений для того, чтобы
//поставить в очередь загрузку изображения
if (!img) { img = element.querySelector("img"); }
return item.loadImage(item.data.thumbnail, img).then(function () {
//как только загружено, проверяет видимость элемента 
return item.isOnScreen();
});
}).then(function (onscreen) {
if (!onscreen) {
//если элемент не видим, его прозрачность не анимируется 
img.style.opacity = 1;
} else {
//если элемент видим, анимировать прозрачность изображения
WinJS.UI.Animation.fadeIn(img);
}
})

Хочу предупредить, что в данной оптимизации производительности используется много promise-объектов! Но всё, что здесь имеется - это стандартная структура promise-объектов, объединенных в цепочку. Первая асинхронная операция в визуализаторе обновляет простые частки элемента, такие, как текст. Затем она возвращает promise-объект в item.ready. Когда данный вызов завершится, или, точнее, если он будет исполнен - вы можете использовать асинхронный метод элемента loadImage для загрузки изображения, возвращая promise-объект item.isOnScreen из его обработчика завершения. Когда и если отложенный результат isOnScreen будет получен, вы можете выполнить необходимые действия, которые нужны только для видимых элементов.

Я выделял "если" в этих описаниях, так как весьме вероятно то, что пользователь будет прокручивать содержимое ListView, пока всё это происходит. Когда все эти promise-объекты объединены в цепочку, ListView может отменить асинхронные операции в любой момент, когда элемент выходит из области видимости или из области буферных страниц. Достаточно сказать, что элемент управления ListView прошёл через великое множество испытаний производительности!

Теперь мы пришли к многошаговому пакетному визуализатору, который комбинирует вставку изображений в DOM для минимизации задач, связанных с макетом и перерисовкой. В нашем примере, для этого используется функция, которая называется createBatch, которая использует метод WinJS.Promise.timeout с 64-миллисекундным периодом для комбинации promise-вызовов, загружающих изображения в многошаговом визуализаторе. Честно говоря, вам придётся поверить мне на слово, так как вам надо стать настоящим экспертом в использовании promise-объектов для того, чтобы понять, как это работает!

//При инициализации (за пределами визуализатора)
thumbnailBatch = createBatch();

//Внутри цепочки renderComplete 
//...

}).then(function () {
return item.loadImage(item.data.thumbnail);
}).then(thumbnailBatch()
).then(function (newimg) {
img = newimg;
element.insertBefore(img, element.firstElementChild);
return item.isOnScreen();
}).then(function (onscreen) {

//...
//Реализация createBatch

function createBatch(waitPeriod) {
var batchTimeout = WinJS.Promise.as();
var batchedItems = [];

function completeBatch() {
var callbacks = batchedItems;
batchedItems = [];
for (var i = 0; i < callbacks.length; i++) {
callbacks[i]();	
}	
}	
return function () {
batchTimeout.cancel();
batchTimeout = WinJS.Promise.timeout(waitPeriod || 64).then(completeBatch);
var delayedPromise = new WinJS.Promise(function (c) {
batchedItems.push(c);	
});	
return function (v) { return delayedPromise.then(function () { return v; }); };
};	
 }

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

Что мы только что изучили

Лекция 6. Макет

Здесь читателю предлагается методика проектирования макетов приложений для Магазина Windows с использованием средств CSS, а так же - проектирование макетов с учетом различных состояний просмотра приложения

Файлы к данной лекции Вы можете скачать  здесь.

В сравнении с другими членами моей семьи, я меньше сплю и часто встаю поздно ночью или перед рассветом. Для того, чтобы не будить остальных, я обычно не включаю свет и перемещаюсь в темноте (здесь, в сельских предгорьях Сьерра-Невады, бывает по-настоящему темно!) Так как я знаю планировку дома и расстановку мебели, мне не нужно многого видеть. Я лишь нуждаюсь в нескольких контрольных точках, вроде дверного проёма, углов стен или края кровати, для того, чтобы точно знать, где я нахожусь. Более того, моё тело обладает мышечной памятью, в которой зафиксировано расположение дверных ручек, количество ступенек на лестнице, количество шагов, необходимое, чтобы обойти кровать и так далее. Это по-настоящему помогло мне понять, как слабовидящие люди "видят" их мир.

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

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

Хороший дизайн приложения, очевидно, следует тем же принципам. Именно поэтому Microsoft рекомендует следовать единообразным шаблонам поведения приложений, как описано в материалах "Разработка интерфейсов пользователя для приложений" (http://msdn.microsoft.com/library/windows/apps/hh779072.aspx) и "Руководство по проектированию приложений Магазина Windows" (http://msdn.microsoft.com/library/windows/apps/hh770552.aspx). Эти рекомендации, в любом случае, нельзя считать ни странными, ни случайными, так как они являются результатов многих лет исследований, поисков того, что будет работать лучше всего и для Windows 8 в целом, и для отдельных приложений. Особенности расположения панели чудо-кнопок, команд на панели приложения (как мы увидим в лекции 1 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"), продиктованы особенностями анатомии людей, а именно, тому, как далеко мы можем перемещать пальцы по экрану, удерживая при этом в руках планшетный компьютер.

В случае с макетами страниц, рекомендации, "Создание макета приложения" (http://msdn.microsoft.com/library/windows/apps/hh872191.aspx), где показано идеальное расположение тела страницы и заголовков, где выверены расстояния между элементами страницы, могут показаться серьезно ограничивающими ваши возможности, если не драконовскими. Общий стиль Windows, однако - это отличная отправная точка, но не жёсткое правило. Гораздо важнее, что форма макета приложения помогает пользователям развивать визуальную и мышечную память, которая применима ко многим приложениям. Исследования показали, что пользователь вырабатывает подобные привычки довольно быстро, за минуты. Но, конечно, эти привычки не имеют попиксельной точности. Другими словами, общий стиль показывает общую форму, которая помогает пользователю немедленно понять, как работает приложение и где нужно искать определенные его функции. Это похоже на то, как вы легко узнаете букву "S", набранную разными шрифтами. Такой подход эффективен и продуктивен. С другой стороны, когда существует приложение, которые использует совершенно иной макет (или, что хуже, когда макет похож на макет в стиле Windows 8, а работает он иначе), пользователь может потратить больше сил на то, чтобы понять, куда посмотреть, куда кликнуть, так же, как я вынужден был бы быть гораздо более внимательным ночью, если бы вы передвинули всю мою мебель!

Суть заключается в том, что за всеми рекомендациями по проектированию приложений для Магазина Windows лежат веские причины. Как я сказал раньше, если вы выполняете роль дизайнера своего приложения, изучите те руководства, ссылки на которые приведены выше. Если эту роль взял на себя кто-то другой, убедитесь в том, что он изучил эти руководства! В любом случае, мы рассмотрим основные принципы в первом разделе данной лекции.

После этого мы скоцентрируемся на том, как реализовать дизайн макета, а не на создании дизайна как такового. (Хотя я, очевидно, унаследовал от моих родителей гены, которые наградили меня способностями к технической коммуникации, мой брат получил больше генов, которые в ответе за искусство!). Например, мы ответим на вопросы о том, как приложение должно отвечать на изменение режима просмотра для того, чтобы показать страницы в правильном дизайне (для полноэкранного альбомного, заполняющего, прикрепленного, портретного режимов)? Как приложение ведет себя на экранах различных размеров и различной плотности пикселей?

Кроме того, мы потратим некоторое время на работу с CSS-сеткой и некоторыми другими возможностями CSS, имеющими отношение к макетам, такими, как гибкие окна или многоколоночный текст. Вообще говоря, всё это - стандарты CSS, поэтому я ожидаю, что вы уже знаете что-то о них, или можете разыскать полную документацию по ним1). Мы, таким образом, рассмотрим лишь некоторые основы, потратив больше времени на то, чтобы понять, как лучше всего применять эти возможности в приложениях, и на аспекты, уникальные для среды Windows 8 (на то, например, что называется точкой прикрепления (snap point) в элементе div, поддерживающем сдвиг / прокрутку (pan / scroll)).

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

Принципы построения макетов приложений для Магазина Windows

Макет, очевидно, является одним из центральных элементов, которого касаются рекомендации по дизайну приложений для Windows. Принцип "прежде всего содержимое, и лишь затем внешнее оформление" ("content before chrome") означает, что большая часть того, что вы отображаете на каждой из страниц приложения - это содержимое, с небольшим количеством управляющих поверхностей, постоянных навигационных панелей и пассивных графических элементов наподобие разделителей, размытий, градиентов, которые сами по себе ничего не значат. Другой вариант реализации подобного заключается в том, что содержимое само по себе следует сделать интерактивным, вместо того, чтобы конструкировать пассивные элементы, управляющее воздействие на которые оказывают команды, активируемые пользователем не посредством самих этих элементов. Семантическое масштабирование - хороший пример подобного интерактивного содержимого - вместо необходимости иметь кнопки или меню для изменения масштаба где-то в приложении, эту возможность реализует сам элемент управления, иногда, когда с приложением работают с помощью мыши, выводя небольшую кнопку зуммирования. Другие команды приложения, по большей части, похожим образом привязаны к элементам пользовательского интерфейса, которые появляются тогда, когда они нужны, посредством панелей приложения и других всплывающих элементов, о которых мы поговорим в лекции 1 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript".

Коротко говоря, принцип "прежде всего содержимое, и лишь затем внешнее оформление" подразумевает создание эффекта погружения в содержимое для пользователя, работающего с приложением, вместо того, чтобы отвлекать его на несущественные детали. В проектировании приложений для Магазина Windows особое внимание уделяется пустому пространству вокруг содержимого и между отдельными его блоками, которое служит для организации и группировки содержимого без необходимости в линиях и рамках. Обычно прозрачные "рамки из пустого пространства" помогают взгляду пользователя притягиваться к содержимому, которое имеет значение. В дизайне приложений для Windows используются принципы типографики (размер шрифта, его насыщенность, цвет и так далее) для создания ощущения структуры, иерархии и сравнительной важности различного содержимого. Это так по той причине, что текст на странице - это уже содержимое, почему бы не использовать его характеристики - типографику - для того чтобы достичь тех же целей, которые обычно достигаются за счёт внешних элементов оформления? (Если говорить об общем стиле макета, широкое использование шрифта Segoe UI в дизайне приложения не является жёстким правилом, скорее начальной точкой. Применение однородных наборов параметров шрифтов (type ramp) для различных заголовков важнее, нежели сами шрифты).

В качестве примера, на Рис. 6.1 показан типичный дизайн настольного или веб-приложения для чтения RSS-лент. Обратите внимание на постоянные элементы внешнего оформления сверху и снизу: команда поиска, закладки для навигации, элементы управления для навигации и так далее. Это занимает примерно 20% пространства экрана. В том, что остаётся примерно две трети занимают служебные элементы, оставляя лишь 20-25% на содержимое, которое нам действительно нужно - на статью.

Рис. 6.2 показывает приложение для тех же целей, дизайн которого соответствует дизайну для Магазина Windows. Обратите внимание на то, как вспомогательные команды убраны с основного экрана. Поиск можно выпонить с помощью чудо-кнопки Поиск. Настроить параметры можно посредством чудо-кнопки Параметры. Добавление новых лент, обновление, навигация - реализованы на панели приложения. А переключение между режимами просмотра реализовано на основе контекстного масштабирования. Для отражения иерархии содержимого используется типографика вместо элементов управления, изображающих папки. В итоге, для содержимого доступна большая часть экрана - около 75%. В результате, мы можем увидеть гораздо больше содержимого, чем раньше, что создаёт гораздо более глубокий и захватывающий опыт взаимодействия пользователя и приложения. Не так ли?

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


увеличить изображение

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

То же самое приложение, что и на Рис. 6.1., преображенное с использованием нового подхода к дизайну Windows-приложений, где большая часть элементов оформления исчезает, оставляя гораздо больше места для содержимого. В альтернативном варианте такого дизайна изображения могут выделяться сильнее, чем текст


увеличить изображение

Рис. 6.2.  То же самое приложение, что и на Рис. 6.1., преображенное с использованием нового подхода к дизайну Windows-приложений, где большая часть элементов оформления исчезает, оставляя гораздо больше места для содержимого. В альтернативном варианте такого дизайна изображения могут выделяться сильнее, чем текст

Даже там, где применяются правила типографики, дизайн для Windows приветствует использование различных размеров шрифта, набора типографских параметров, для того, чтобы создать ощущение иерархии. Таблиц стилей WinJS по умолчанию - ui-light.css и ui-dark.css - предоставляют четыре фиксированных размеров шрифта, где каждый следующий уровень пропорционально больше предыдущего (42 пункта = 80 пикселей, 20 пунктов = 40 пикселей и так далее, как показано на Рис. 6.3. Эти пропорции позволяют пользователю легко, с одного взгляда, выяснять и понимать структуру содержимого. Опять же, это касается привычек и мышечной памяти, и исследования Microsoft показали, что без подобной разницы в размерах, пользователь обычно не может чётко определить роль того или иного содержимого в общей иерархии.

Набор типографских параметров в дизайне приложений для Магазина Windows, показанный для таблиц стилей ui-light.css (слева) и ui-dark.css (справа)


Рис. 6.3.  Набор типографских параметров в дизайне приложений для Магазина Windows, показанный для таблиц стилей ui-light.css (слева) и ui-dark.css (справа)

Внутри основного содержимого дизайн для Windows приветствует следующие принципы построения макетов:

Как мы упоминали выше, шаблоны проектов в Visual Studio и Blend созданы с учётом этих принципов, и, таким образом, предоставляют удобную стартовую точку для приложений. Даже если вы начинаете с шаблона Пустое приложение, другие, наподобние шаблона Приложение таблицы, будут служить справочным материалом. Именно это мы сделали с приложением "Here My Am!" в лекции 2.

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

Быстрый старт: Сдвигаемые разделы и точки прикрепления

В лекции 5 мы потратили некоторое время на разговор о том, когда ListView - это правильный выбор, а когда - нет. Один из главных случаев, когда разработчики неоправданно пытаются использовать ListView, заключается в реализации домашней страницы или хаба приложения, который содержит множество различных групп содержимого, организованных в столбцы, как показано на Рис. 6.4 и разъяснено в материале "Проектирование навигации для приложений Магазина Windows" (http://msdn.microsoft.com/library/windows/apps/hh761500.aspx). На первый взгляд это выгдялит как ListView, но так как данные, на самом деле, не являются коллекцией, это лишь макет, выводящий фиксированное содержимое, в итоге, имеет смысл использовать для подобной работы HTML и CSS, через метод проб и ошибок.

Я особо обратил на это ваше внимание, так как все отличные элементы управления, которые предоставляет WinJS, позволяют просто забыть о том, что всё, что вы знаете об HTML и CSS, всё еще применимо в приложениях для Магазина Windows. В конце концов, эти элементы управления - лишь блоки HTML и CSS с некоторыми дополнительными методами, свойствами и событиями.

Макет типичной домашней страницы (хаба) приложения для Магазина Windows с фиксированным заголовком (1) и горазонтально сдвигаемым разделом (2), и разделами или категориями содержимого (3)


увеличить изображение

Рис. 6.4.  Макет типичной домашней страницы (хаба) приложения для Магазина Windows с фиксированным заголовком (1) и горазонтально сдвигаемым разделом (2), и разделами или категориями содержимого (3)

Создание макета хаба

Посмотрим, как использовать HTML и CSS для того, чтобы реализовать раздел стартовой страницы приложения (хаба), который поддерживает сдвиг, как на Рис. 6.4. Вспомнив, для начала, к материалу "Создание макета приложения" (http://msdn.microsoft.com/library/windows/apps/hh872191.aspx), мы можем сказать, что отступ между группами должен составлять четыре единицы по 20 пикселей каждая, то есть - 80 пикселей. Большинству групп следует иметь квадратную форму, за исключением второй, ширина которой вдвое меньше. Основываясь на размерах дисплея, 1366х768, высота каждого раздела должна составлять 768 пикселей минус 128 пикселей (это место для заголовка), минус, как минимум, 50 пикселей снизу. В итоге остаётся 590 пикселей (если мы добаим заголовки групп для каждого раздела, нужно будет вычесть еще 40 пикселей). В итоге, квадратная группа на базовом дисплее будет иметь 590 пикселей в ширину (мы установили реальную высоту в 100% содержащей её ячейки сетки). Полная ширина раздела будет, таким образом, (590*4 полноразмерных раздела)+(295*один раздел половинной ширины)+(80*4 разделительные пустые пространства). Результат равняется 2975 пикселей. К этому мы добавим колонки по краям - 120 пикселей слева (в соовтетствии с общим стилем Windows) и 80 пикселей справа. В итоге, мы получаем 3175 пикселей.

Для того, чтобы создать раздел с подобным макетом, мы можем использовать CSS-сетку внутри элемента-контейнера. Для того, чтобы это увидеть, запустим Blend, создадим новый проект с шаблоном Приложение навигации (у нас будет базовая страница, но не все вспомогательные страницы, всё будет оформлено в нужном стиле). Внутри элемента section в pages/home/home.html, создадим новый div-элемент и зададим его параметр класс как hubSections:

<section aria-label="Main content" role="main">
<div class="hubSections">	
</div>	
</section>

По адресу pages/home/home.css добавим несоколько правил стилей. Зададим параметр overflow-x: auto элементу section, и расположим сетку в div'е hubSections, используем добавление колонок слева и справа для создания отступов (удалив margin-left: 120px из section и добавив это в качестве первого столбца в div):

.homepage section[role=main] {
overflow-x: auto;
}
.homepage .hubSections { 
width: 2975px; 
height: 100%; 
display: -ms-grid;
-ms-grid-rows: 1fr 50px;
-ms-grid-columns: 120px 2fr 80px 1fr 80px 2fr 80px 2fr 80px 2fr 80px;
}

Применив эти стили, мы уже можем увидеть, как начальная страница принимает форму, уменьшив масштаб отображения данных на монтажной панели:

Теперь создадим отдельные разделы в pages/home/home.html, каждый из них представляет собой div.

<section aria-label="Main content" role="main">
<div class="hubSections">	
<div class="hubSection1"></div>	
<div class="hubSection2"></div>	
<div class="hubSection3"></div>	
<div class="hubSection4"></div>	
<div class="hubSection5"></div>	
</div>	
</section>

И стилизуем их в соответствии с занимаемыми ими ячейками сетки, в 100% по ширине (width) и высоте (height). Я показал здесь hubSection1, другие выглядят точно так же, имея лишь другие номера столбцов (4, 6, 8 и, соответственно, 10).

.homepage .hubSection1 {	
-ms-grid-row: 1;	
-ms-grid-column: 2; /* 4 для hubSection2, 6 для hubSection3, и так далее. */
width: 100%;	
height: 100%;	
 }

Создание макетов разделов

Сейчас мы можем взглянуть на содержимое каждого раздела. В зависимости от того, что вы хотите отобразить, и как отображенные разделы должны взаимодействовать, вы можете снова просто использовать макет (CSS-сетку, или, возможно, гибкое окно), или использовать элементы управления наподобие ListView. Разделы hubSection3 и hubSection5 имеют пустые пространства в низу, поэтому они могут быть элементами управления ListView с изменяющимся количеством элементов. Обтатите внимание на то, что если мы создаём список с более чем 9 или 6 элементами, соответственно, мы захотим, настроить размеры столбцов во всей сетке, захотим сделать ширину элемента section больше, но давайте предположим, что дизайн требуется для максимум 6 и 9 элементов в разделах.

Скажем так же, что мы хотим, чтобы каждый из разделов был интерактивен, когда прикосновение к элементу запускает навигацию к странице детальной информации. (В этом примере не показаны заголовки групп, используемые для навигации к странице группы). Мы просто используем ListView в каждом из разделов, где каждый из ListView будет иметь собственный источник данных. Для hubSection1 нам понадобится использовать объединение ячеек, в остальных же группах вполне подойдут декларативные шаблоны. Ключевое соглашение, касающееся всех этих групп, заключается в том, чтобы стилизовать элементы таким образом, чтобы они хорошо вписались в базовые размеры, которые мы используем. И, возвращаясь к общему стилю приложения, расстояние между изображениями элементов должно быть 10 пикселей, расстояние между столбцами со смешанным содержимым (hubSection4 и hubSection5) должно быть 40 пикселей (что может быть установленно с помощью подходящих CSS-полей).

Подсказка. Если вы хотите сделать определенные области вашего содержимого невыделяемыми, используйте атрибут -ms-user-select в CSS для элемента div. Обратитесь к примеру: "Невыделяемые области содержимого с CSS-атрибутом -ms-user-select" (http://code.msdn.microsoft.com/windowsapps/Unselectable-content-areas-963eccd9).

Точки прикрепления

Если вы запустите упражнение HubPage и поработаете немного с ним, используя жесты инерционной прокрутки (то есть, такие, когда сдвиг элемента продолжается даже после того, как вы убрали палец; подробнее об этом - в лекции 3 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"), вы заметите, что сдвиг может остановиться в любом месте. Вам и вашему дизайнеру это может понравится, но во многих сценариях имеет смысл автоматическая остановка сдвига на границе раздела или группы. Для сенсорного взаимодействия это можно реализовать с использованием CSS-стилей для точек прикрепления (snap points), как описано в нижеприведенной таблице. Это - стили, которые вы добавляете к элементу, поддерживающему сдвиг, вместе со стилями, отвечающими за переполнение, иначе они не окажут нужного воздействия. Документацию по этому вопросу (и по некоторым другим) можно найти в материале "Сенсорное взаимодействие: изменение масштаба и сдвиг" (http://msdn.microsoft.com/ru-ru/library/windows/apps/hh453816.aspx).

Таблица 6.1.
СтильОписаниеСинтаксис значений
-ms-scroll-snap-points-xЗадаёт точку прикрепления для оси Х snapInterval(start<length>, step<length>) | snapList(list<lengths>)
-ms-scroll-snap-points-yЗадаёт точку прикрепления по оси YsnapInterval(start<length>, step<length>) | snapList(list<lengths>)
-ms-scroll-snap-typeЗадаёт, точка прикрепления какого типа должна быть использована для элемента: none отключает точку прикрепления, mandatory всегда осуществлять сдвиг до остановки на точке прикрепления (что включает в себя окончание инерционного сдвига), и proximity воздействовать на процесс сдвига лишь в том случае, если движение, на самом деле, заканчивается "очень близко" к точке прикрепления. Использование mandatory, таким образом, приводит к сдвигу с шагом в один раздел/элемент, в то время, как proximity позволит "проскочить" очередную точку прикрепления при инерционном сдвиге. Так же обратите внимание на то, что перетаскивание пальцем (то есть, использование жеста, где нет инерционного движения) позволяет пользователю передвинуть элемент в позиции, отличающиеся от точек прикрепления.none | proximity | mandatory
-ms-scroll-snap-xСокращение для комбинации -ms-scroll-snap-type и -ms-scroll-snap-points-x<-ms-scroll-snap-type> <-ms-scroll-snap-points-x>
-ms-scroll-snap-yСокращение для комбинации -ms-scroll-snap-type и -ms-scroll-snap-points-y<-ms-scroll-snap-type> <-ms-scroll-snap-points-y>

В таблице, <length> - это число с плавающей запятой, за которым следует указатель абсолютной единицы (cm, mm, in, pt или pc), или относительной единицы измерения (em, ex или px).

Для того, чтобы добавить точку прикрепления к каждому из разделов нашей стартовой страницы, нам лишь нужно добавить два стиля точек прикрепления после overflow-x:

.homepage section[role=main] {	
overflow-x: auto;	
-ms-scroll-snap-type: mandatory;	
-ms-scroll-snap-points-x: snapList(0px, 670px, 1045px, 1715px, 1795px);
}

Обратите внимание на то, что показанные здесь точки прикрепления включают 120-пиксельную левую границу, таким образом, каждая выравнивается с разделом, расположенным под текстом заголовка. Таким образом, точка прикрепления 0px соответствует первому разделу, 670px - второму (80-пиксельный разделитель и 590-пиксельная ширина первого раздела) и так далее. Последняя точка прикрепления в 1895px, однако, не следует этому правилу, так как div не может быть сдвинут за эту точку. Это означает, что мы прикрепляем точку где-то в предпоследней секции, но отображаем последнюю секцию и её 80-пиксельную границу.

После этих изменений, мы обнаружите, что при прокрутке осуществляются остановки (приятно анимированные) точно на границах разделов. Отметьте, что для стартовой страницы, наподобие этой, использование точек прикрепления типа proximity обычно подходит больше. Обязательные точки прикрепления (mandatory) предназначены больше для элементов, с которыми невозможно взаимодействовать или работать без того, чтобы видеть их целиком, как, например, при пролистывании наборов фотографий, статей и так далее. (Элемент управления FlipView это использует).

Для того, чтобы узнать об этом больше, в том числе - о некоторых других стилях -ms-scroll-* и -ms-content-zoom-*, об ограничителях прокрутки, обратитесь к примеру: "HTML-прокрутка, сдвиг, изменение масштаба" (http://code.msdn.microsoft.com/windowsapps/Scrolling-panning-and-47d70d4c). Обратите, так же внимание на то, что точки прикрепления предназначены не только для использования с элементом управления ListView, они предназначены и для использования в ваших собственных макетах.

Разные состояния просмотра приложения

Если в чем-то, касающимся макета приложения для Магазина Windows, и можно быть уверенным, так в том, что режим его отображения, весьма вероятно, будет часто меняться. Во-первых, авто-поворот экрана, особенно, на планшетных, переносных устройствах - делает очень простым и быстрым переключение между альбомной и портретной ориентациями экрана (пользователю не придётся столкнуться с настройкой драйвера дисплея). Во-вторых, устройство может быть подключено к внешнему дисплею, а это означает, что приложение нуждается в самостоятельной настройке на различные разрешения, и, возможно, на различные плотности пикселей. В-третьих, у пользователя есть возможность, в альбомном режиме, "прикреплять" приложение у левой или правой части экрана, когда прикрепленное приложение отображается в области шириной в 320 пикселей, а другое приложение выводится в зоне "заполняющего" просмотра, занимая остаток дисплея. Это можно сделать, используя жесты, мышь, или используя сочетания клавиш Win+. (точка), Win+> (Shift+точка). (Для прикрепленного режима требуется, как минимум, дисплей с разрешением 1366х768, иначе он будет отключен).

Вам, определенно, захочется протестировать своё приложение со всеми этими вариантами: состояния просмотра, размеры дисплеев, плотностью пикселей. Работу в разных состояниях просмотра можно тестировать напрямую, на любом компьютере, а вот для двух последних вариантов нужны специальные инструменты, которые предоставляют имитатор Visual Stuido и закладка Устройство (Device) в Blend ,позволяя вам имитировать различные условия. Нас сейчас интересует вопрос, как приложение будет действовать при различных условиях просмотра.

Состояния просмотра

В лекции 1 были представлены четыре состояния просмотра, вспомнить о них вы можете, посмотрев на Рис. 1.6. Добавим сейчас следующий уровень точности к их описанию, рассмотрев следующую таблицу. Она включает в себя изображения пространства, которое занимают приложения, описания состояний просмотра и идентификаторов этих состояний и в WinRT (в перечислении Windows.UI.ViewManagement.ApplicationViewState (http://msdn.microsoft.com/library/windows/apps/windows.ui.viewmanagement.applicationviewstate.aspx), и в характеристике -ms-view-state (http://msdn.microsoft.com/library/windows/apps/hh465826.aspx) CSS-медиазапросов.

Таблица 6.2.
Пространство, занимаемое приложением (Голубое)Подробности
Приложение занимает весь экран в альбомном режиме. WinRT: fullScreenLandscape -ms-view-state: fullscreen-landscape
Приложение занимает либо левую, либо правую часть экрана в альбомном режиме, на области, ограниченной шириной в 320 пикселей. Это означает, что вам не нужно создавать дизайн для всех возможных размеров среди прикрепленных, заполненных (смотрите ниже) и полноэкранных состояний просмотра. WinRT: snapped -ms-view-state: snapped
Приложение занимает область экрана, находящуюся рядом с прикрепленным приложением. Ширина области, доступной приложению, равняется ширине экрана за вычетом 320 пикселей и 22 пикселей для элемента-разделителя. WinRT: filled -ms-view-state: filled
Приложение в портретном режиме WinRT: fullScreenPortrait -ms-view-state: fullscreen-portrait

Снова хочу напомнить, что каждую страницу вашего приложения нужно подготовить для всех четырех режимов просмотра (с некоторыми исключениями, которые описаны во врезке "Предпочтительная ориентация и блокировка ориентации"). Режимы просмотра всегда находятся под контролем пользователя, поэтому любая страница может оказаться в любом режиме просмотра в любое время, даже при старте. Повторяйте этот как мантру, так как многие разработчики и дизайнеры об этом забывают!

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

Дизайн приложения, таким образом, включает все режимы просмотра для каждой страницы, так же, как мы поступили с описаниями страниц "Here My Am!" в лекции 2. В то же время, обработка режимов просмотра для каждой страницы не подразумевает четыре разных реализации приложения. Режимы просмотра это не более чем разные визуальные представления одного и того же содержимого страниц, как описано в "Руководстве по прикрепленному и заполненному представлениям" (http://msdn.microsoft.com/library/windows/apps/hh465371.aspx). Таким образом, переключение между режимами просмотра всегда сохраняет состояние приложения и его страниц - оно никогда не изменяет режим работы приложения или не осуществляет навигацию на другую страницу. Единственное исключение из этого правила сущестует, если приложение по веским причинам не может работать в прикрепленном режиме (как, например, игра, которой нужно определенное экранное пространство). В таком случае приложение может вывести сообщение об этом, вместе с инструкцией вроде "Прикоснитесь здесь, чтобы продолжить", что позволяет пользователю выразить таким образом своё намерение. В ответ на команду пользователя, приложение может вызвать Windows.UI.ViewManagement.Application-View.tryUnsnap, (http://msdn.microsoft.com/library/windows/apps/windows.ui.viewmanagement.applicationview.aspx), как показано в примере "Прикрепленный режим", (http://code.msdn.microsoft.com/windowsapps/Snap-Sample-2dc21ee3)1). Не используйте, однако, эту возможность для того, чтобы "срезать углы". Попытайтесь сохранить как можно больше возможностей приложения в прикрепленном режиме.

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

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

Врезка: Предпочтительная ориентация и блокировка ориентации

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

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

Для того чтобы сообщить Windows об этих предустановках, установите подходящие флаги в группе параметров Поддерживаемые ориентации (Supported Orientations) на закладке Интерфейс приложения (Application UI) в редакторе манифеста:

Множество подробностей о том, как всё это работает, можно найти в справке по InitialRotationPreference (http://msdn.microsoft.com/library/windows/apps/Hh700342.aspx). Я хочу, кроме того, расссказать о свойствах Windows.Graphics.Display.DisplayProperties.autoRotationPreferences (http://msdn.microsoft.com/library/windows/apps/windows.graphics.display.displayproperties.autorotationpreferences.aspx) и currentOrientation (http://msdn.microsoft.com/library/windows/apps/windows.graphics.display.displayproperties.currentorientation.aspx) дл программного управления ориентацией. Для демонстрации, обратитесь к примеру "Предустановки автоповорота устройства" (http://code.msdn.microsoft.com/windowsapps/Auto-Rotation-Preferences-87ae2902).

Обработка состояний просмотра

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

Лучше всего думать о состояниях просмотра в терминах видимости элементов, их размеров, взаимного расположения на странице. При таком подходе, основная часть работы может быть выполнена посредством CSS-медиа запросы с использованием возможности -ms-view-state. Мы видели это в приложении "Here My Am!" из лекции 2. Шаблон проекта Приложение таблицы так же это демонстрирует. Вот как этим медиа-запросы выглядят в CSS:

@media screen and (-ms-view-state: fullscreen-landscape) {
/* ... */
}

@media screen and (-ms-view-state: filled) {
/* ... */
}

@media screen and (-ms-view-state: snapped) {
/* ... */
}

@media screen and (-ms-view-state: fullscreen-portrait) {
/* ... */
}

/* Синтаксис для комбинирования медиа-запросов (разделены запятыми) */	
@media screen and (-ms-view-state: fullscreen-landscape),

screen and (-ms-view-state: fullscreen-portrait), screen and (-ms-view-state: filled) {
/* ... */	
}

Кроме того, весьма разумно добавить другие выражения к этим запросам, такие, как and (min-width: "1600px"), так как вы можете выполнять множество различных настроек, основываясь на разрешении экрана.

Для приложений Магазина Windows применяйте возможности обработки режимов просмотра в медиа-запросах вместо состояний, хранящихся в свойстве CSS orientation (альбомная (landscape) и портретная (portrait)), которые просто получаются путём анализа относительной ширины и высоты дисплея и не отличают состояния наподобие прикрепленного (snapped). Другими словами, состояния просмотра в Windows специфичны для платформы и отражают состояния, описаний которых стандартный CSS не имеет, помогая вашему приложению знать не только о доступном ему пространстве экрана, но и о режиме, в котором оно исполняется2).

Например, следуя стандартному алгоритму CSS, и полноэкранный портретный (fullscreen portrait), и прикрепленный (snapped) режимы детектируются как orientation: portrait, так как соотношение сторон экрана в таких режимах указывает на его вертикальное расположение. Однако, прикрепленный режим подразумевает иное намерение пользователя, чем полноэкранный портретный. В прикрепленном режиме вы скорее хотите видеть наиболее важные части приложения, нежели копию портретного макета в пространстве шириной 320 пикселей.

Обычный подход заключается в том, чтобы размещать правила, касающиеся полноэкранного альбомного режима просмотра, в верхней части CSS-файла и затем выполнять тонкие настройки внутри конкретных медиа-запросов. Мы сделали это с "Here My Am!" в лекции 2, где стиль по умолчанию работает для режимов fullscreen-landscape и filled, и нам нужно было задать специфические правила лишь для режимов snapped и fullscreen-portrait.

Совет. При стилизации приложения в Blend, в панели Правила стилей (Style Rules)существуют интуитивно понятные визуальные подсказки, которые позволяют контролировать точную точку вставки любого нового CSS-стиля в заданной таблице стилей. С помощью этого средства, которое выглядит как оранжевая линия на рисунке ниже, и как показано в Video 2.2., вы можете указать точное место вставки стилей для конкретных медиа-запросов внутри медиа-запроса.

В некоторых случаях обработка медиа-запросов в декларативном CSS недостататочна. Когда основное содержимое отображается на странице, содержащей ListView с макетом gridLayout, которая прокручивается в горизонтальном направлении, обычно макет переключают на ListLayout при переходе в прикрепленный режим. Вы можете, кроме того, как показано в материале: "Руководство по прикрепленному и заполненному представлениях" (http://msdn.microsoft.com/library/windows/apps/hh465371.aspx), преобразовать список кнопок в единый выпадающий элемент select для того, чтобы предложить пользователю те же функциональные возможности посредством более компактного пользовательского интерфейса. Для реализации подобного вам понадобится JavaScript.

Для подобных целей вы можете задействовать стандартное Media Query Listener API в JavaScript. Данный интерфейс (часть W3C CSSOM View Module, http://dev.w3.org/csswg/cssom-view/) позволяет вам добавлять обработчики к изменениям состояний медиа-запросов. Для того, чтобы прослушивать событие перехода в режим прикрепленного просмотра, вы можете использовать код, подобный этому:

var mql = window.matchMedia("(-ms-view-state: snapped)");
mql.addListener(styleForSnapped);
function styleForSnapped() {
if (mql.matches) {	
//...	
}

// Создайте прослушиватели для других состояний просмотра: full-screen, fill, и device-portrait
// или обработайте все меди-запросы в едином обработчике, проверяя в нём текущее состояние просмотра.

Вы можете видеть, что строка медиа-запроса, которую передают в window.matchMedia, это та же строка, котрая используется в CSS, и в обработчике вы, конечно, можете сделать всё, что нужно с помощью JavaScript.

Совет. Убедитесь в том, что протестировали режимы просмотра приложения при возникновении события resuming, так как характеристики дисплея могут измениться, например, при подключении другого монитора или увеличение экранных элементов, выполненного с помощью команды панели чудо-кнопок Параметры > Изменение параметров компьютера > Специальные возможности (Settings > Change PC Setings > Ease of Access), которая ведет к окну, содержащему тумблер Увеличить все элементы на экране (Make Everything on the Screen Bigger). Воззможно, что ваше приложение будет восстановлено из фонового режима (из приостановленного состояния) в прикрепленный режим просмотра, пока ваше приложение приостановлено, могут поменяться размеры экрана. Поэтому тестируйте макет на корректную обработку события resuming при открытии приложения в прикрепленном режиме просмотра и при изменении параметров экрана.

Обрабатывая изменения режимов просмотра (или события window.onresize), вы можете получить точные размеры окна приложения посредством свойств window.innerWidth и window.innerHeight. Свойства document.body.clientWidth и document.body.clientHeight позволяют узнать ту же информацию, что и из свойств clientWidth и clientHeight любого элемента (наподобие div), который занимает 100% тела документа. Внутри события resize так же доступны свойства args.view.outerWidth и args.view.outerHeight.

В CSS так же доступны сведения о высоте и ширине окна просмотра (vh и vw, соответственно). Вы можете поставить перед ними префикс в виде процентов, например, 100vh - это 100% высоты окна просмотра, и 3.5vw - это 3.5% ширины окна просмотра. Эти переменные так же могут быть использованы в выражениях CSS calc.

Текущий режим просмотра доступен посредством свойства Windows.UI.ViewManagement.ApplicationView.value. Его значения берутся из перечисления Windows.UI.ViewManagement.ApplicationViewState, как показано в вышеприведенной таблице. В предыдущих лекциях мы видели применение этих механизмов. Например, элементы управления страниц (речь о них шла в лекции 3) обычно проверяют состояние просмотра в их методе ready и напрямую получают эти состояния в своём методе updateLayout. На самом деле, каждый метод элемента управления страницы groupedItem в проекте Приложение таблицы чувствителен к изменению состояния просмотра. Взгляните на код, взятый из pages/groupedItems/groupedItems.js:

// Несколько строк и комментариев опущено	
var appView = Windows.UI.ViewManagement.ApplicationView;	
var appViewState = Windows.UI.ViewManagement.ApplicationViewState;
var nav = WinJS.Navigation;	
var ui = WinJS.UI;	

ui.Pages.define("/pages/groupedItems/groupedItems.html", {
initializeLayout: function (listView, viewState) {
if (viewState === appViewState.snapped) { listView.itemDataSource = Data.groups.dataSource; listView.groupDataSource = null;
listView.layout = new ui.ListLayout();
} else {
listView.itemDataSource = Data.items.dataSource;
listView.groupDataSource = Data.groups.dataSource;
listView.layout = new ui.GridLayout({ groupHeaderPosition: "top" });
}
},

itemInvoked: function (args) {
if (appView.value === appViewState.snapped) {
// Если страница в прикрепленном режиме, пользователь вызывает группу. 
var group = Data.groups.getAt(args.detail.itemIndex); nav.navigate("/pages/groupDetail/groupDetail.html", { groupKey: group.key });
} else {
// Если страница не в прикрепленном режиме, пользователь активирует элемент. 
var item = Data.items.getAt(args.detail.itemIndex); nav.navigate("/pages/itemDetail/itemDetail.html",
{ item: Data.getItemReference(item) });
}
},

ready: function (element, options) {
// ...	
this.initializeLayout(listView, appView.value);
// ...	
},	
// Эта функция обновляет макет страницы в ответ на изменения viewState. 
updateLayout: function (element, viewState, lastViewState) {
var listView = element.querySelector(".groupeditemslist").winControl;
if (lastViewState !== viewState) {
if (lastViewState === appViewState.snapped ||
viewState === appViewState.snapped) {
var handler = function (e) {
listView.removeEventListener("contentanimating", handler, false);
e.preventDefault();
}
listView.addEventListener("contentanimating", handler, false);
this.initializeLayout(listView, viewState);
}
}
}
}
});

В первую очередь, метод initializeLayout, который вызывается и из ready и из updateLayout проверяет текущее состояние просмотра и соответствующим образом настраивает элемент управления ListView. Если вы помните из лекции 5, во время исполнения программы можно и настраивать ListView и менять его источник данных. Здесь мы используем ListLayout со списком групп в прикрепленном состоянии просмотра и gridLayout со сгруппированными элементами в других режимах. Это показывает, как мы показываем то же самое содержимое, но в более сжатом формате, скрывая отдельные элементы в прикрепленном режиме. Из-за этого itemInvoked так же проверяет режим отображения, так как элементы списка - это группы в прикрепленном режиме и в таком случае навигация осуществляется на страницу сведений о группе вместо перехода на страницу детальной информации об элементе.

Что касается updateLayout, он активируется из обработчика события window.onresize в коде PageControlNavigator (смотрите js/navigator.js в шаблоне проекта Приложение таблицы). Этот обработчик передаёт сведения о новом и предыдущем состоянии просмотра в updateLayout. Если эта функция обнаруживает, что мы переключились в прикрепленный режим или переключились из него, она сбрасывает к исходному состоянию ListView посредством initializeLayout. И, так как мы меняем источник данных ListView, здесь нет нужды в воспроизведении анимации входа или перехода. Небольшая уловка, работающая с событием contentanimating просто подавляет анимацию.

Врезка: Физическая ориентация экрана

Полноэкранный альбомный и полноэкранный портретный режимы просмотра предоставляют некоторую информацию о том, как устройство, на самом деле, ориентировано в пространстве, но подобная информация может быть гораздо точнее получена из свойств объекта Windows.Graphics.Display.DisplayProperties (http://msdn.microsoft.com/library/windows/apps/windows.graphics.display.displayproperties.aspx). В частности, свойство currentOrientation содержит значение из перечисления Windows.Graphics.Display.DisplayOrientations (http://msdn.microsoft.com/library/windows/apps/windows.graphics.display.displayorientations.aspx), что показывает, как устройство повёрнуто по отношению к nativeOrientation (и вызывает, при необходимости, событие orientationchanged). Эти данные позволят вам узнать, например, находится ли устройство экраном вниз, по отношению к небу, что может быть полезным для любого приложения, реализующего дополненную реальность, такую, как звёздная карта.

Похожим образом, API Windows.Devices.Sensors (http://msdn.microsoft.com/library/windows/apps/windows.devices.sensors.aspx), в частности классы, SimpleOrientationSensor и OrientationSensor могут предоставить больше информации от сенсоров. Разговор об этом пойдёт в лекции 3 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript".

Размер экрана, плотность пикселей и масштабирование

Не знаю о вас, но я, когда впервые узнал, что зона прикрепленного просмотра всегда имеет 320 пикселей в ширину - реальных пикселей, а не некое значение, выраженное в процентном выражении от ширины экрана - это меня озадачило. Не даст ли это совершенно разные впечатления от программы на разных дисплеях? Ответ на этот вопрос отрицательный. 320 пикселей - это около 25% от базового размера монитора 1366х768, что означает, что оставшиеся 75% - это вполне знакомые нам 1024х768. На 10-дюймовом дисплее это примерно 2.5 дюйма физического пространства дисплея. Пока всё понятно.

На более крупных мониторах, с другой стороны, таких, как 2560x1440, эти 320 пикселей займут лишь 12,5% ширины, таким образом, макет полного экрана выглядит совершенно иначе. Однако, учитывая то, что подобные мониторы имеют 24-дюймовую диагональ, эти 320 пикселей снова занимают примерно 2.5 дюйма физического пространства экрана, что означает, что зона прикрепленного просмотра выглядит так же, как и ранее. Она лишь имеет больше вертикального пространства и оставляет после себя больше свободного места на экране.

Это приводит нас к вопросу о плотности пикселей (pixel density). Что произойдёт, если ваше приложение окажется на по-настоящему маленьком дисплее, имеющем высокое разрешение? Очевидно, что на подобном дисплее зона в 320 пикселей будет немногим более дюйма. Есть у кого-нибудь увеличительное стекло?

К счастью, это не тот вопрос, о котором беспокоятся приложения для Магазина Windows… Почти. Основное преимущество, которое получает пользователь от подобного дисплея - это более высокая чёткость изображения, но не большая плотность информации. Сенсорные цели должны быть одинаковых размеров на мониторах любых размеров, не важно, сколько пикселей они занимают. Ведь человеческие пальцы не меняются с развитием технологий! Для того, чтобы это учитывать, Windows автоматически уменьшает масштаб эффективного разрешения, которое сообщается приложению, что означает, что любые координаты, которые вы используете внутри приложения (в HTML, CSS и JavaScript) автоматически масштабируются к разрешению того устройства, на котором осуществляется вывод пользовательского интерфейса приложения. Это производится на низком уровне подсистем рендеринга HTML/CSS в хост-процессе приложения, всё выводится в соответствии с особенностями пикселей конкретного устройства для максимальной чёткости изображения.

Что касается "почти", сказанного выше, единственное место, где вам нужно заботиться о плотности пикселей, касается растровой графики для экрана-заставки и плиток приложения, как мы уже говорили в лекции 3. Скоро мы к этому вернемся, в разделе "Графические элементы, которые хорошо масштабируются" ниже.

Разные размеры экрана, разная плотность пикселей может быть протестирована с помощью имитатора в Visual Studio, или на закладке Устройства (Device) в Blend. Последний вариант показан на Рис. 6.5. Здесь отображены применимый DPI и фактор масштабирования. 100% масштаб означает, что приложению напрямую сообщают о разрешении устройства. 140% и 180%, с другой стороны, что означает, что имеет место масштабирование. При размерах экрана 10.6 дюйма, разрешении 2560х1440 и масштабировании в 180%, например, приложение увидит данный экран как экран с размерами 1422х800 (2560/1.8 на 1440/1.8), что очень близко к стандартному дисплею 1366х768. Похожим образом, при размерах экрана 10.6 дюйма и разрешении 1920х1080 масштабирование в 140% покажет приложению экран размером 1371х771 (1920/1.4 на 1080/1.4). И в том и в другом случаях макет, разработанный в расчёте на экран 1366х768 полностью подходит, хотя вы, конечно, можете настроить его так точно, как захотите.

Совет. Если ваше приложение имеет фиксированный макет (fixed layout) (смотрите раздел "Фиксированные макеты и элемент управления ViewBox" ниже), вы можете разрешить вопрос разной плотности пикселей просто используя графические ресурсы, масштабированные до 200% по отношению к вашему стандартному дизайну. Это так, потому что фиксированный макет может быть масштабирован к произвольным размерам, в итоге, изображения, имеющие изначально масштаб 200% хорошо масштабируются в любом случае. Подобное приложение не нуждается в предоставлении вариантов изображений, которые использует, в масштабах 100%, 140% и 180%.

Параметры для задания размеров дисплея и плотностей пикселей на закладке Устройство (Device) в Blend


Рис. 6.5.  Параметры для задания размеров дисплея и плотностей пикселей на закладке Устройство (Device) в Blend

Как отмечено ранее, работая с состояниями просмотра, вы можете программным способом определять точный размер окна вашего приложения посредством свойств window.innderWidth и window.innerHeight, свойствами document.body.clientWidth и document.body.clientHeight, и свойствами clientWidth и clientHeight любого элемента, который занимает 100% тела страницы. Внутри window.onresize вы можете использовать их (или свойства args.view.outerWidth и args.view.outerHeight) для того, чтобы подстроить макет приложения под изменения, касающиеся общего режима отображения приложения. Конечно, если вы используете что-то наподобие CSS-сетки с дробными размерами строк и столбцов, большинство подобных настроек макета будет выполнено автоматически.

В любом случае, размер уже отражает автоматическое масштабирование с учетом плотности пикселей, таким образом, это - размеры, под которые вы адаптируете макет. Если вы хотите знать, каковы физические размеры дисплея, с другой стороны, вы можете воспользоваться свойствами window.screen.width и window.screen.height. Другие параметры дисплея можно обнаружить в объекте Windows.Graphics.Display.DisplayProperties (http://msdn.microsoft.com/library/windows/apps/br226143.aspx), в частности, такие, как logicalDPI и текущее значение resolutionScale. Последнее является значением перечисления Windows.Graphics.Display.ResolutionScale (http://msdn.microsoft.com/library/windows/apps/windows.graphics.display.resolutionscale.aspx), среди них - scale100Percent, scale140Percent, и scale180Percent. Возвращаемые значения этих идентификаторов - 100, 140 и 180, таким образом, вы можете использовать resolutionScale напрямую в вычислениях.

Врезка: Хорошая возможность для удалённой отладки

Работа с различными возможностями устройств предоставляет хорошую возможность поработать с удалённой отладкой, как описано в материале "Выполнение приложений для Магазина Windows на удаленном компьютере" (http://msdn.microsoft.com/library/windows/apps/hh441469.aspx). Такой подход позволит вам протестировать программу на различных дисплеях без необходимости устанавливать на каждом из устройств Visual Studio, и, кроме того, даст вам преимущества отладки с использованием нескольких мониторов. Вам лишь нужно установить и запустить средства удалённой отладки на целевом устройстве и убедиться, что оно подсоединено с помощью кабеля к той же сети, к которой подключен компьютер, на котором вы выполняете разработку. (Вам может понадобиться приобрести маленький USB-Ethernet-адаптер, если ваше устройство не имеет подходящего порта - удалённая отладка не работает через Интернет и при соединении устройств по беспроводной сети). Монитор удалённой отладки, выполняющийся на удалённой машине, сообщает о себе Visual Studio, исполняющемся на машине разработчика. Обратите внимание на то, что когда вы попытаетесь приступить к удалённой отладке в первый раз, вам предложат получить лицензию разработчика для целевого устройства, таким образом, это устройство должно быть подключено к Интернету в это время.

Графические элементы, которые хорошо масштабируются

Различные размеры экранов и плотности пикселей задают приложениям еще одну задачу, не связанную с макетом, касающуюся графических элементов, которые всегда должны выглядеть наилучшим образом. Конечно, вы можете рисовать графические элементы напрямую, используя элемен HTML5 canvas, на что мне хотелось бы обратить ваше внимание, так это на заранее сформированные графические ресурсы.

Масштабируемая векторная графика HTML5 (scalable vector graphics, SVG) в данном случае очень кстати. Вы включаете встроенный SVG-код в свой HTML (включая фрагменты страниц), или вы можете хранить их в отдельных файлах и ссылаться на них как на атрибуты img.src. Один из самых простых способов использовать SVG - разместить элемент img внутри пропорционально настраиваемой ячейки CSS-сетки и задать стили width и height этого элемента в 100%. SVG автоматически масштабируется для заполнения ячейки, и так как ячейка изменяет размер вместе с элементом, являющимся контейнером для SVG, всё обрабатывается автоматически.

У такого подхода есть одна проблема - SVG масштабируется с учетом соотношения сторон ячейки CSS-сетки, в которой находится, а оно не всегда может быть таким, как вам нужно. Для того, чтобы управлять этим поведением, убедитесь, что SVG имеет атрибуты viewBox и preserveAspectRatio, где cоотношение сторон, хранящееся в viewBox соответствует тому, которое задано свойствами SVG width и height:

<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" 
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0"
width="300"
height="150" viewBox="0 0 300 150" preserveAspectRatio="xMidYMid meet">

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

Что касается ресурсов в пакете приложения, мы уже видели, как работать с различными плотностями пикселей в лекции 3, посредством суффиксов к именам файлов .scale-100, .scale-140, и .scale-180. Этот подход работает для любых изображений в вашем приложении, используемых во всех случаях, будь это изображение для экрана-заставки, изображения плиток и другие графические ресурсы, на которые есть ссылки в манифесте. Таким образом, если у вас есть растровое изображение, имеющее имя banner.png, вы создатите три изображения в пакете приложения, которые будут называться banner.scale-100.png, banner.scale-140.png, и banner.scale-180.png. Затем вы можете просто ссылаться на базовое имя элемента в CSS или в конструкциях вида <img src= "images/banner.png"> и background-image: url('images/banner.png'), и загрузчик ресурсов Windows чудесным образом автоматически загрузит изображение, имеющее подходящий масштаб. (Если файлы с суффиксами .scale-* не найдены, загрузчик будет искать файл banner.png). Мы увидим даже больше подобных чудес в лекции 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", где мы так же добавим варианты для различных языков и контрастных цветовых схем, которые представляют дополнительные собственные суффиксы.

Если вам не по душе подобная схема именования файлов, знайте, что вместо этого вы можете использовать похожим образом именованные папки. Таким образом, для реализации этого подхода вам понадобятся папки с именами scale-100, scale-140, и scale-180, расположенные в папке изображений и содержащие файлы с неизмененными именами (наподобие banner.png).

В CSS вы так же можете использовать медиа-запросы с установками max-resolution и min-resolution для управления тем, какие изображения будут загружены. Помните, однако, что CSS опирается на логическое DPI, а не на физическое DPI, граница для каждого фактора масштабирования указана далее (значения DPI здесь слегка отличаются от тех, которые даны в документации, так как они получены из эмпирических тетстов; документация предлагает, соответственно, значения в 134, 135 и 174 dpi).

@media all and (max-resolution: 134dpi) {
/* масштаб 100% */
}
 
@media all and (min-resolution: 135dpi) {
/* масштаб 140% */
}

@media all and (min-resolution: 174dpi) {
/* масштаб 180% */
}

Как разъяснено в "Руководстве по масштабированию в зависимости от плотности пикселей" (http://msdn.microsoft.com/library/windows/apps/hh465362.aspx), подобные медиа-запросы особенно полезны для изображений, которые вы получаете из удалённого источника, где вам может понадобиться вносить поправки в URI или в строку запроса URI. Смотрите лекцию 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", "Плитки, уведомления, экран блокировки и фоновые задачи", раздел "Использование локальных изображений и изображений, полученных из Web" для того, чтобы найти сведения о том, как при обновлении плиток данные механизмы используются для масштабирования, учёта контрастных схем и языковых особенностей.

Вы можете программным образом получить свойства logicalDpi и resolutionScale из объекта Windows.Graphics.Display.DisplayProperties. Событие logicaldpichanged (событие WinRT) может быть, так же, использовано для проверки изменений resolutionScale, так как эти два параметра всегда связаны. Использование данных API проиллюстрировано примером "Масштабирование в соответствии с DPI" (http://code.msdn.microsoft.com/windowsapps/Scaling-sample-cf072f4f).

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

Адаптивные и фиксированные макеты для дисплеев различных размеров

Так же, как каждая страница вашего приложения нуждается в подготовке для различных состояний просмотра, страницы нуждаются и в подготовке для экранов различных размеров. Я рекомендую почитать "Руководство по масштабированию для различных экранов" (http://msdn.microsoft.com/library/windows/apps/hh780612.aspx), где есть ценная информация о том, с дисплеями каких размеров может столкнуться ваше приложение. Из этого руководства мы можем сделать вывод о том, что размер самой маленькой области прикрепленного просмотра равняется 320х768, минимальный размер области заполняющего просмотра - 1024х768, и минимальный размер области полноэкранного просмотра (портретного и альбомного) - 1280х800 и 1366х768. Это - базовые параметры для вашего дизайна.

Таким образом, дисплеи лишь увеличиваются, по сравнению с базовыми параметрами, в итоге мы приходим к вопросу: "Что делать с дополнительным пространством?". Первая часть ответа заключается в том, чтобы заполнить экран. Ничего не выглядит глупее, чем приложение, запущенное на 27-дюймовом мониторе и использующее при этом лишь область размером 1366х768, для которой оно было разработано. В подобной ситуации приложение займёт лишь от четверти экрана, до, в лучшем случае, половины. Как я уже говорил много раз, представьте себе, какие обзоры и оценки может получить ваше приложение в Магазине Windows, если вы не будете обращать внимание на подобные детали!

Вторая часть ответа зависит от содержимого вашего приложения. Если у вас есть лишь фиксированное содержимое, что обычно, например, для игр, тогда вы можете использовать масштабирование фиксированного макета для заполнения им свободного пространства. Если содержимое не фиксировано, что означает, что вам следует показать больше информации при увеличении размеров дисплея, тогда вы можете воспользоваться адаптивным макетом. Посмотрим на оба этих подхода.

Врезка: Параметр "Увеличить все элементы на экране"

Среди параметров компьютера (чудо-кнопка Параметры > Изменение параметров компьютера > Специальные возможности (Settings > Change PC Setings > Ease of Access)) есть тумблер Увеличить все элементы на экране (Make Everything on the Screen Bigger). Включение этого параметра позволяет эффективно увеличить размер экранных элементов примерно на 40%, что подразумевает то, что система сообщает приложениям о том, что размер экрана примерно на 30% меньше, чем текущее разрешение (похоже на уровень масштабирования в 140%). К сачтью, этот параметр отключён, если в итоге система вынуждена будет сообщить об экране, который меньше, чем 1024х768, в итоге, подобное разрешение - это самое меньшее, на что может рассчитывать ваше приложение. В любом случае, при изменении данного параметра вызывается событие Windows.Graphics.Display.DisplayProperties.logicalDpiChanged.

Фиксированные макеты и элемент управления ViewBox

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

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

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

Так как это - более распространённый подход, WinJS предоставляет встроенный элемент управления макетом именно для этой цели: WinJS.UI.ViewBox (http://msdn.microsoft.com/library/windows/apps/br229771.aspx) (не следует путать с атрибутом SVG viewBox). Как и в случае с другими элементами управления WinJS, вы можете объявить его, используя data-win-control в HTML, как показано ниже, где элемент viewBox может содержать один и только один дочерний элемент:

<div data-win-control="WinJS.UI.ViewBox">
<div class="fixedlayout">	
<p>Content goes here</p>	
</div>	
</div>

На самом деле, это всё, на что можно посмотреть при работе с ViewBox. У него нет параметров или свойств, нет методов и событий - всё очень просто! Обратите так же внимание на то, что так как ViewBox - это лишь элемент управления, вы можете использовать для любого содержимого с постоянным соотношением сторон в адаптивном макете. Он предназначен не только для создания макета целой страницы.

Для установки базисного размера ViewBox - размерностей, на которых будет основан ваш код - просто задайте параметры height и width его дочернего элемента в CSS. Например, для установки базисного размера в 1024х768, мы устанавливаем данные свойства в правиле для класса fixedLayout:

fixedlayout { width: 1024px; height: 768px;
}

Как только создан экземпляр объекта ViewBox, он прослушивает события window.onresize и затем применяет 2D-трансформацию CSS к дочерним элементам, основываясь на разнице между базисным размером и реальным размером. Это позволяет сохранить соотношение сторон. Это работает и для увеличения и для уменьшения масштабов отображения содержимого. Автоматически применяется и размещение полос пустого пространства сверху и снизу или справа и слева от дочернего элемента, вы можете задать внешний вид этих областей (на самом деле, любых областей, которые не перекрывает дочерний элемент), используя класс win-viewbox. Как всегда, используйте данный селектор в области видимости конкретного элемента управления, если вы в приложении пользуетесь более чем одним элементом ViewBox, если только вы не хотите, чтобы подобный стиль был применен ко всем элементам.

Базовая структура выше - это то, что вы получаете при создании нового приложения по шаблону Приложение с фиксированным макетом в Visual Studio и Blend. Как показано здесь, создаётся макет с базисным размером 1024х768, но вы можете использовать любой желаемый размер.

CSS для этого шаблона проекта показывает, что вся страница стилизована как гибкое окно CSS для того, чтобы ViewBox был отцентрован, и то, что элементу fixedLayout задана сетка по умолчанию:

html, body {	
height: 100%;
margin: 0;	
padding: 0;	
 }
body {	
-ms-flex-align: center;	
-ms-flex-direction: column;
-ms-flex-pack: center;	
display: -ms-flexbox;	
}
.fixedlayout {	
-ms-grid-columns: 1fr;
-ms-grid-rows: 1fr;	
display: -ms-grid;	
height: 768px;
width: 1024px;
}

Если вы создаете проект по этому шаблону в Blend, добавьте стиль границы к правилу fixedLayout (наподобие border: 2px solid Red;), и поэкспериментируйте с режимами отображения и настройками дисплея на закладке Устройство (Device). Таким образом вы можете понаблюдать за тем, как ViewBox реализует возможности масштабирования без каких-либо усилий с вашей стороны. Для того, чтобы показать это ярче, упражнение fixedLayout для этой лекции содержит измененный на canvas дочерний элемент ViewBox, в котором нарисована сетка 4х3 (соответствующая соотношению сторон экрана 1024х768) из квадратных ячеек размером по 256 пикселей, содержащих окружности. Как показано на Рис. 6.6, квадраты и круги не превращаются в прямоугольники и овалы, когда мы переключаем состояния просмотра и размеры экранов, автоматически добавляются полосы пустого пространства (применяя стиль background-color к классу win-viewbox).

Врезка: Растровая графика и фиксированные макеты

Если вы используете с ViewBox растровую графику, настройте их в соответствии с максимальным разрешением 2560х1440, таким образом, они будут хорошо выглядеть на больших дисплеях и будут отображаться с уменьшением масштаба (вместо увеличения) на дисплеях меньшего размера. Вы можете воспользоваться и альтернативным подходом, загружая различные изображения (посредством различных URI для img.src), которые наилучшим образом подходят для наиболее широко распространённых размеров дисплея.

Обратите внимание на то, что всё еще применимо масштабирование разрешения. Если приложение исполняется на дисплее высокой плотности размером 10.6 дюймов и разрешением 2560х1440 (масштабирование 180%), приложение, и, таким образом, ViewBox опознают такой дисплей как дисплей меньшего размера. Но если вы поддерживаете графические элементы для собственного разрешения устройства, они будут выглядеть чётко при выводе на экран.

Масштабирование фиксированного макета с использованием элемента управления WinJS.UI.ViewBox, показано добавление полос по краям эркрана в полноэкранном режиме 1366х768 (слева) и в прикрепленном режиме просмотра (справа)


увеличить изображение

Рис. 6.6.  Масштабирование фиксированного макета с использованием элемента управления WinJS.UI.ViewBox, показано добавление полос по краям эркрана в полноэкранном режиме 1366х768 (слева) и в прикрепленном режиме просмотра (справа)

Адаптивные макеты

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

Подобные макеты легче всего реализовать с использованием CSS-сетки, где пропорциональные строки и столбцы автоматически увеличиваются или уменьшаются; элементы внутри ячеек сетки соответствующим образом подстраивают свои размеры. Это можно наблюдать в шаблонах проектов для Visual Studio и Blend, особенно - в шаблоне Приложение таблицы. На типичном дисплее с разрешением 1366х768 вы можете видеть некоторое количество элементов, как показано в верхней части Рис. 6.7. Переход на 27-дюймовый дисплей с разрешением 2560х1440 позволит вам увидеть гораздо больше данных, это можно видеть в нижней части рисунка.

Адаптивный макет проекта, построенного по шаблону Приложение таблицы, для экрана с разрешением 1366х768 (сверху) и для экрана с разрешением 2560х1440 (снизу)


Рис. 6.7.  Адаптивный макет проекта, построенного по шаблону Приложение таблицы, для экрана с разрешением 1366х768 (сверху) и для экрана с разрешением 2560х1440 (снизу)

Честно говоря, проект Приложение таблицы не выполняет особой обработки макета при изменении размеров экрана, отличающейся от того, что он выполняет для различных режимов просмотра. Из-за использования CSS-сеток и пропорциональных размеров ячеек, ячейка, содержащая элемент управления ListView, автоматически увеличивается. Элемент управления ListView самостоятельно просшушивает событие window.onresize, в итоге, нам не нужно отдельно управлять обновлением его макета.

Общая стратегия работы с адаптивным макетом, таким образом, не вызывает затруднений:

В качестве другого ориентира вы можете воспользоваться примером "Адаптивный макет с использованием CSS" (http://code.msdn.microsoft.com/windowsapps/Adaptive-layout-with-sample-062e7fe2), который применяет тот же подход, что и шаблон проекта Приложение таблицы, полагаясь на самостоятельную подстройку своих параметров элементами управления. В примере, вы увидите, что приложение не выполняет никаких прямых расчётов, основанных на размерах окна.

Подсказка. Если у вас есть адаптивный макет и вы хотите использовать фоновое изображение, заданное в CSS, которое масштабируется вместе с собственным контейнером (вместо того, чтобы заполнять его, повторяясь), стилизуйте background-size либо задав значение contains, либо 100% 100%.

Вам, как разработчику, должно быть понятно и то, что то, как приложение работает с экранами различных размеров - это вопрос дизайна. Вышеприведенная стратегия - это то, что вы используете для реализации дизайна, но дизайн определяет то, как всё будет выглядеть. Эти соображения, которых я здесь лишь кратко коснулся, описаны в материале "Руководство по масштабированию для различных экранов" (http://msdn.microsoft.com/library/windows/apps/hh780612.aspx):

Ответы на подобные вопросы помогут вам понять, как макету следует адаптироваться.

Использование CSS-сетки

Начиная с лекции 2, мы уже использовали CSS-сетку для многих целей. В особенности модель сетки нравится мне потому, что она позволяет вам без особых усилий задавать относительное расположение элементов и легко масштабируется под различные размеры экранов.

Так как этот курс направлен на особенности разработки для Windows 8, я оставляю задачу разъяснения всех подробностей о сетке спецификациям W3C: http://dev.w3.org/csswg/css3-grid-layout/ и http://www.w3.org/TR/css3-grid-layout/. Эти материалы представляют собой общие руководства, которые помогут в понмании того, как настраиваются размеры строк и столбцов, особенно когда некоторые из них объявлены с фиксированными размерами, кого размер некоторых зависит от содержимого, а другие заданы так, чтобы они заполняли оставшееся пространство. Здесь много тонкостей!

Так как эти спецификации, когда я пишу это, всё еще находятся в разработке, полезно точно знать, какие части спецификация реально поддерживаются подсистемой HTML/CSS, которой пользуются приложения для Магазина Windows.

Для элемента, содержащего сетку, поддерживаемые стили весьма просты. Во-первых, используйте модели отображения -ms-grid и -ms-inline-grid (стиль display:). Позже мы вернемся к -ms-inline-grid.

Во вторых, в элементах сетки используйте -ms-grid-columns и -ms-grid-rows для задания их расположения. Если оставить эти параметры незаданными, значение по умолчанию - это один столбец и одна строка. Поддерживается и синтаксис для задания повторяющихся конструкций, такой, как -ms-grid-columns: (1fr)[3];, что особенно удобно, когда у вас имеется повторяющиеся серии строк или столбцов, которые появляются внутри строк описаний. Как пример, следующие команды эквивалентны:

-ms-grid-rows:10px 10px 10px 20px 10px 20px 10px;
-ms-grid-rows:(10px)[3] (20px 10px)[2];	
-ms-grid-rows:(10px)[3] (20px 10px) 20px 10px;	
-ms-grid-rows:(10px)[2] (10px 20px)[2] 10px;

То, как вы задаёте строки и столбцы - вопрос достаточно интересный, так как вы можете сделать некоторые из них фиксированными, некоторые - гибко меняющими размеры, а некоторые - ориентированными на размер содержимого. Этих целей можно достичь, используя следующие значения. Опять же, посмотрите документацию по особенностями использования спецификаторов max-content, min-content, minmax, auto, и fit-content, вместе со значениями, которые задаются в таких единицах измерения, как px, em, %, и fr. Приложения для Магазина Windows, так же, поддерживают, в качестве единиц измерения, такие показатели, как vh (высота окна просмотра) и vw (ширина окна просмотра).

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

Обратите особое внимание на то, что нумерация строк и столбцов начинается с 1, а не с 0. Перепрограммируйте ваш ум, ориентированный на JavaScript для того, чтобы это запомнить, так как вам понадобится небольшая трансляция адресов в том случае, если вы храните дочерние элементы в массиве, нумерация которого начинается с 0.

Кроме того, обращаясь к любому из этих -ms-grid* стилей как к свойствам в JavaScript, опустите тире и переключитесь на написание адресов в "верблюжьем" (camel casing) стиле: msGrid, msGridColumns, msGridRowAlign, msGridLayer.

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

Переполнение ячейки сетки

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

Пример содержимого, которое выступает за пределы содержащей его ячейки можно найти в упражнении GridOverflow в дополнительных материалах к этой лекции. Здесь создана сетка из прямоугольных элементов, 4х4, но нижеприведенный код, размещенный в конце функции doLayout (js/default.js) размещает первый прямоугольник за пределами ячейки:

children[0].style.width = "350px"; children[0].style.marginLeft = "150px"; children[0].style.background = "#fbb";

Это делает первый элемент в сетке шире и смешает его вправо, таким образом, он появляется внутри ячейки второго элемента (фон изменен для того, чтобы это было очевидно). В итоге макет сетки остаётся неизменным.

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

Вертикальная центровка содержимого

Где-то, в собственном опыте работы с CSS вы, возможно, сталкивались с неприятным поведением стиля vertical-align в попытке разместить фрагмент текста в центре или в нижней части элемента div. К несчастью, это не работает: этот стиль предназначен для ячеек таблицы и для встроенного содержимого (для того, чтобы задать, как текст и изображения, например, выравниваются относительно друг друга).

В результате, для того, чтобы это реализовать, было разработано множество методов. Например, об этом идет речь здесь: http://blog.themeforest.net/tutorials/vertical-centering-with-css/. К несчастью, практически все эти методы основаны на фиксированной высоте - а это может работать для веб-сайта, но не подходит для адаптивных макетов, в которых нуждаются приложения для Магазина Windows. И единственный метод, который не использует фиксированную высоту, основан на применении встраиваемой таблицы. Эх.

К счастью, и CSS-сетка, и гибкое окно (смотрите раздел "Макет элемента" ниже) позволяют легко решить эту проблему. В случае с сеткой, вы можете просто создать родительский элемент div с сеткой 1х1 и использовать стиль -ms-grid-row-align: center для дочернего элемента div (значение по умолчанию для которого - ячейка 1,1):

<!-- В HTML -->	
<div id="divMain">	
<div id="divChild">	
<p>Centered Text</p>
</div>	
</div>

/* В CSS */
#divMain { width: 100%; height: 100%; display: -ms-grid;
-ms-grid-rows: 1fr;
-ms-grid-columns: 1fr;
}

#divChild {
-ms-grid-row-align: center;
-ms-grid-column-align: center;

/* Горизонтальное выравнивание текста так же работает с */
/* text-align: center; */	
 }

Решение этой проблемы даже проще с помощью макета на основе flexbox, где flex-align: center задаёт вертикальную центровку, flex-pack: center отвечает за горизонтальную центровку, и дочерний элемент div не нужен. Это тот же подход к стилизации, что применен в шаблоне Приложение с фиксированным макетом для центровки элемента ViewBox:

<!-- В HTML -->	
<div id="divMain">	
<p>Centered Text</p>
</div>	
/* В CSS */
#divMain { 
width: 100%; height: 100%;
display: -ms-flexbox;
-ms-flex-align: center;
-ms-flex-direction: column;
-ms-flex-pack: center;
}

Код обоих методов можно найти в упражнении CenteredText к этой лекции. (Упражнение так же используется для демонстрации применения многоточий, в итоге он выглядит не точно так же, как показано выше).

Масштабирование размеров шрифтов

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

При работе с адаптивным макетом, обычно нужно, чтобы размер шрифта был пропорционален размерностям его родительского элемента. (Это не проблема, если родительский элемент имеет фиксированный размер, потому что так вы можете использовать шрифт фиксированного размера). К несчастью, процентные значения, используемые в стиле font-size в CSS, основаны на размере шрифта по умолчанию (1em), а не на размере родительского элемента, как в случае с height и width. Вам, вероятно, понравилось бы использование выражения наподобие font-size: calc(height * .4), но значения других CSS-стилей того же самого элемнта не доступны calc.

Одно исключение из этого правила касается значения vh (которое можно использовать в calc). Если вы знаете, например, что текст, который вы хотите масштабировать, содержится в ячейке сетки, которая всегда занимает 10% от высоты окна вывода, и вы хотели бы, чтобы размер шрифта равнялся половине от этого значения, вы можете воспользоваться выражением font-size: 5vh (5% от высоты окна просмотра).

Другой метод заключается в использовании SVG для вывода текста, где вы можете установить атрибут viewBox и задать font-size относительно этого viewBox. Затем, масштабирование SVG по отношению к ячейке сетки эффективно масштабирует и шрифт:

<svg viewBox="0 0 600 400" preserveAspectRatio="xMaxYMax">
<text x="0" y="150" font-size="200" font-family="Verdana"> Big SVG Text
</text>
</svg>

Так же вы можете воспользоваться JavaScript для вычисления желаемого размера шрифта, основываясь на свойстве clientHeight родительского элемента. Если данный элемент находится в ячейке сетки, размер шрифта (и высотра строки) могут составить некоторый процент от высоты ячейки, таким образом, позволяя масштабировать шрифт вместе с ячейкой.

Так же вы можете попытаться использовать элемент управления WinJS.UI.ViewBox. Если вам нужно, чтобы текст занял 50% элемента, содержащего его, поместите ViewBox в div, который настроен на размер, равняющийся 50% размера контейнера, и стилизуйте дочерний элемент элемента ViewBox с помощью position: absolute. Попытайтесь, для того, чтобы увидеть это, поместить нижеприведеннй код в default.html нового проекта, созданного на основе шаблона Пустое приложение:

<div style="height:50%;">	
<div data-win-control="WinJS.UI.ViewBox">	
<p style="position:absolute;">Big text!</p>
</div>	
</div>

Макет элемента

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

Важна и работа с шаблонами отдельных элементов, которые расположены в изменяющихся областях страницы. Таким образом, если вы задали сетку, определяющую общий вид страницы и получили с её помощью некоторое количество областей фиксированного размера (для заголовков, изображений в заголовке, панелей управления и так далее), оставшаяся область может сильно меняться в размере при изменении размеров окна. В этом разделе, таким образом, давайте взглянем на некоторые средства, которые мы можем применять в подобных областях: CSS-трансформации, гибкое окно, вложенные и встроенные сетки, многоколоночный текст, CSS-выражения и объединенные CSS-рамки. Информацию по этим и другим возможностям CSS, которые поддерживаются приложениями для Магазина Windows (такие, как фоны, границы, градиенты), можно найти в материале "Каскадные таблицы стилей" (http://msdn.microsoft.com/library/windows/apps/hh996828.aspx).

2D и 3D-трансформации в CSS

Практически невозможно размышлять о макетах для элементов не принимая во внимание CSS-трансформации. Трансформации - очень мощное средство, так как они делают возможным изменение отображения элемента, не воздействуя ни на последовательность элементов в документе, ни на общий макет. Это очень полезно для реализации анимаций и переходов. Трансформации широко используются библиотекой анимации WinJS, которые обеспечивают внешний вид и восприятие встроенных элементов управления, характерные для Windows 8. Как мы узнаем в лекции 5 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript", вы можете так же напрямую пользоваться этой библиотекой.

CSS-трансформации можно использовать непосредственно, конечно, в любое время, когда вам нужно преобразовать, масштабировать, повернуть элемент. Приложения для Магазина Windows поддерживают и 2D (двумерные) и 3D (трехмерные) трансформации (http://dev.w3.org/csswg/css3-2d-transforms/ и http://www.w3.org/TR/css3-3d-transforms/), в частности, эти стили1):

Таблица 6.3.
CSS-стильСвойство JavaScript (element.style.)
backface-visibilitybackfaceVisibility
perspective, perspective-origin perspective, perspectiveOrigin
transform, transform-origin, и transform-styletransform, transformOrigin, и transformStyle

Подробности можно найти на странице "Трансформации" (http://msdn.microsoft.com/library/windows/apps/hh453377.aspx). Знайте так же, что так как хост-процесс приложения использует те же механизмы, что и Internet Explorer, трансформации используют все преимущества аппаратного ускорения.

Гибкое окно (flexbox)

Так же, как сетка - отличный инструмент для решения проблем макетов страниц, модуль гибкое окно CSS, описанный в http://www.w3.org/TR/css3-flexbox/, прекрасно подходит для работы с областями, размеры которых могут меняться, содержимое в которых нуждается в "заполнении" доступного пространства. Процититуем спецификацию W3C:

В данной оконной модели, дочерние элементы окна размещаются либо горизонтально, либо вертикально, неиспользованное пространство может быть назначено конкретным дочерним элементом или распределено между такими элементами путём назначения 'flex' тем дочерним элементам, которые должны заполнять это пространство. Вложенность подобных окон (горизонтальное внутри вертикалного, или вертикальное внутри горизонтального) может быть использована для построения двумерных макетов.

Так как спецификации гибкого окна представлены в форме чернового стандарта, конкретные стили, используемые в приложениях для Магазина Windows - это display: -ms-flexbox (уровень блоков) и display: -ms-inline-flexbox (встроенный)2). Полную справочную информацию по другим поддерживаемым свойствам вы можете найти на странице "Макет на основе гибкого окна ("Flexbox")" (http://msdn.microsoft.com/library/windows/apps/hh453474.aspx):

Таблица 6.4.
CSS-стильСвойство JavaScript (element.style.)Значения
-ms-flex-align msFlexAlign start | end | center | baseline | stretch
-ms-flex-direction msFlexDirection row | column | row-reverse | column-reverse | inherit
-ms-flex-flow msFlexFlow <direction> <pack> где <direction> это значение -ms-flex-direction и <pack> это значение -ms-flex-pack
-ms-flex-orientmsFlexOrienthorizontal | vertical | inline-axis | block-axis | inherit
-ms-flex-item-align msFlexItemAlign auto | start | end | center | baseline | stretch
-ms-flex-line-pack msFlexLinePack start | end | center | justify | distribute | stretch
-ms-flex-order msFlexOrder <integer> (порядковый номер группы)
-ms-flex-pack msFlexPack start | end | center | justify
-ms-flex-wrap msFlexWrap none | wrap | wrapreverse

Как и для всех остальных стилей, Blend - это отличный инструмент, с помощью которого можно экспериментировать с различными стилями гибких окон, так как вы можете сразу же видеть эффект от их применения. Также полезно знать, что гибкое окно используется во многих местах WinJS и в шаблонах проектов, как мы видели ранее в случае с шаблоном Приложение с фиксированным макетом. В частности, возможности гибкого окна использует элемент управления ListView, это позволяет ему отображать больше элементов, когда доступно больше пространства. Элемент управления FlipView использует гибкое окно для центровки элементов. Элементы управления Ratings, DataPicker и TimePicker организуют свои внутренние элементы с использованием встроенного гибкого окна. Весьма вероятно, что ваши собственные пользовательские элементы управления будут поступать так же.

Вложенные и встроенные сетки

Так же, как и гибкое окно имеет модели блочного уровня и встроенные модели, существуют и встроенные сетки: display: -ms-inline-grid. В отличе от сетки блочного уровня, встроенный вариант позволяет вам располагать несколько сеток на одной и той же линии. Это показано в упражнении InlineGrid, где у нас есть три элемента div в HTML, которые можно переключать между встроенной моделью (по умолчанию) и моделью блочного уровня:

//В обработчике activated	
document.getElementById("chkInline").addEventListener("click", function () {
setGridStyle(document.getElementById("chkInline").checked);	
});	
setGridStyle(true);

//В любом месте в default.js 
function setGridStyle(inline) {
var gridClass = inline ? "inline" : "block";

document.getElementById("grid1").className = gridClass;
document.getElementById("grid2").className = gridClass;
document.getElementById("grid3").className = gridClass;
}	

/* default.css */	
.inline {	
display: -ms-inline-grid;
}	

.block {
display: -ms-grid;
}

При использовании встроенной сетки элементы выглядят так:

При использовании сетки блочного уровня мы видим это:

Шрифты и переполнение текстом

Как упоминалось ранее, типографика - это важный элемент дизайна приложений для Магазина Windows, и, в основном, стандартные стили шрифтов, использующие Segoe UI, уже заданы в таблицах стилей WinJS. В Windows SDK есть очень полезный пример "CSS-типографика" (http://code.msdn.microsoft.com/windowsapps/typography-JS-sample-e2df9eb4), где сравнивается элементы заголовков HTML и стили win-type-*, показывая запасные варианты шрифтов и использование двунаправленных шрифтов (с направлением слева направо и справа налево).

Если говорить о шрифтах, пользовательские шрифтовые ресурсы, использующие правило @font-face в CSS, разрешены в приложениях для Магазина Windows. Для страниц локального контекста, свойство src правила должно ссылаться на шрифт файла, который находится в пакете (то есть, это URI, который начинается с / или с ms-appx:///). Страницы, исполняющиеся в веб-контексте, могут загружать шрифты из удалённых источников. Другой вопрос, касающийся текстов и типографики касается текста, который выходит за пределы выделенной для него области. Вы можете использовать стиль CSS text-overflow: ellipsis; для обрезки текста и добавления в конец текста …, и таблицы стилей WinJS содержат класс win-type-ellipsis для этих целей. В дополнение к установке text-overflow, этот класс так же добавляет overflow: hidden (для подавления появления полос прокрутки), и white-space: nowrap. Эти стили вы можете добавлять к любому текстовому элементу, когда вы хотите реализовать поведение, касающееся усечения текста.

Спецификация W3C, касающася переполнения текстом, http://dev.w3.org/csswg/css3-ui/#text-overflow, содержит полезные сведения о том, что можно и что нельзя сделать в этой области. Одно из ограничений текущей спецификации заключается в том, что многострочный обтекающий текст не работает с усечением текста. Таким образом, вы можете использовать обтекание текстом с помощью стиля word-wrap: breakpword, но не можете совместить его с text-overflow: ellipsis (word-wrap имеет преимущество). Я так же узнал, что перетекающий текст из многострочной CSS-области (смотрите следующий раздел) работает в однострочной области с возможностью усечения текста, но text-overflow не применим к областям. В итоге, сейчас вы нуждаетесь в сокращении текста и ручной вставке многоточий, если текст занимает несколько строк.

Для демонстрации сокращения текста и обтекания словами, посмотрите упражнение к этой лекции CenteredText.

Многоколоночные элементы и области

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

CSS3 предоставляет средства для создания многоколоночных макетов внутри элемента (смотрите http://www.w3.org/TR/css3-multicol/). С помощью этих средств вы можете настроить отдельный элемент так, чтобы он расположил своё содержимое в несколько колонок и указать множество параметров подобного макета. Соответствующие стили поддерживают и приложения для Магазина Windows (без необходимости использования префиксов поставщика):

Таблица 6.5.
CSS-стилиСвойство JavaScript (element.style.)
column-width и column-count (columns это сокращение) columnWidth, columnCount, и columns
column-gap, column-fill, и column-span columnGap, columnFill, и columnSpan
column-rule-color, column-rule-style, и columnRuleColor, columnRuleStyle, и columnRuleWidth
column-rule-width (column-rule это сокращение для разделителей колонок)(columnRule это сокращение)
break-before, break-inside, и break-after breakBefore, breakInside, и breakAfter
overflow: scroll (для отображения в контейнере полос прокрутки) overflow

Соответствующую документацию можно найти на странице "Многоколоночный макет" (http://msdn.microsoft.com/library/windows/apps/hh441204.aspx).

Blend предоставляет отличное окружение для того, чтобы увидеть, как работают эти стили. Если вы размещаете мнокоголоночный элемент внутри ячейки сетки, которая может менять размер, вы можете задать параметр column-width и позволить подсистеме макетов добавлять и удалять колонки при необходимости, или вы можете использовать медиа-запросы или JavaScript для самостоятельной установки свойства column-count.

Многоколоночная верстка CSS3, опять же, может быть применена лишь к содержимому одного отдельного элемента. Будучи весьма полезной, она имеет ограничения на использование прямоугольных элементов-контейнеров и прямоугольных колонок (увеличение их числа происходит в бок). Определенные приложения, наподобие электронных журналов, нуждаются в большей гибкости, в такой, как перетекание содержимого между несколькими элементами с более разнообразной формой, и в колонках, которые имеют смещение относительно друг друга. Эти взаимоотношения показаны на Рис. 6.8, где прямоугольник в левой верхней части может быть заголовком, блок-вставка может содержать изображение, и текстовое содержимое распределено между двумя колонками неправильной формы.

Использование областей CSS для создания более сложных макетов с текстовыми колонками неправильной формы


Рис. 6.8.  Использование областей CSS для создания более сложных макетов с текстовыми колонками неправильной формы

Для поддержки колонок неправильной формы, области CSS (CSS Regions) (http://dev.w3.org/csswg/css3-regions/) приходят в онлайн и поддерживаются приложениями для Магазина Windows (смотрите материал "Области" (http://msdn.microsoft.com/library/windows/apps/hh453722.aspx)). Области позволяют произвольно (это так - абсолютно) позиционировать элементы для взаимодействия со встроенным содержимым. На Рис. 6.8 изображение может быть позиционировано с использованием абсолютных параметров на странице и содержимое колонок будет его обтекать.

Ключевый стиль для позиционирования элемента это float: -ms-positioned, который следует сопровождать position: absolute. Обычно это всё, что вам нужно: разместить в нужном месте позиционированный элемент, а подсистема макета сделает всё остальне. Следует отметить, что CSS-перенос (Hyphenation), еще один модуль, близко связан со всем этим, так как выполняет динамическое макетирование текста, мгновенно предоставляя подобные возможности. К счастью, приложения для Магазина Windows поддерживают -ms-hyphens и стили -ms-hyphenation* (их эквивалентные им свойства JavaScript). Переносы описаны в http://www.w3.org/TR/css3-text/, документацию, относящуюся к приложениям для Магазина Windows можно найти в материале "Области" (http://msdn.microsoft.com/library/windows/apps/hh453722.aspx).

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

Цепочка CSS-областей позволяет содержимому перетекать между множеством элементов


увеличить изображение

Рис. 6.9.  Цепочка CSS-областей позволяет содержимому перетекать между множеством элементов

Всё это работает следующим образом. Источник контента определяет элемент iframe, который указывает на HTML-файл (и iframe, конечно, может принадлежать и локальному и веб-контексту). Он стилизуется с помощью ms-flow-into: <element> (в JavaScript - msFlowInfo), где <element> - это id первого контейнера:

<!-- HTML -->	
<iframe id="s1-content-source" src="/html/content.html"></iframe>
<div class="s1-container"></div>	
<div class="s1-container"></div>	
<div class="s1-container"></div>	

/* CSS */
#s1-content-source {
-ms-flow-into: content;
}

Обратите внимание на то, что -ms-flow-into предотвращает отображение содержимого лишь в iframe.

Контейнером может быть любой незаменяемый (nonreplaced) элемент. То есть, любой элемент, внешний вид и размеры которого не определяются внешним источником, такой, как img. Он должен поддерживать включение содержимого между его открывающим и закрывающим тегами, наподобие div (он используется чаще всего), или p. Каждый контейнер стилизуется с помощью -ms-flow-from: <element> (msFlowFrom в JavaScript), где <element> - это первый контейнер в потоке. Расположение содержимого затем производится в том порядке, в котором элементы появляются в HTML (как выше):

.s1-container {
-ms-flow-from: content;
/* Другие стили */
}

Этот простой пример взят из примера "Статические области CSS" (http://code.msdn.microsoft.com/windowsapps/Static-Regions-sample-f2158049), который так же содержит несколько других сценариев. Есть еще два интересных проекта, пример "Динамические области CSS" (http://code.msdn.microsoft.com/windowsapps/Dynamic-Region-Templates-94bc9c95), и "Шаблоны динамических областей CSS" (http://code.msdn.microsoft.com/windowsapps/Dynamic-Region-Templates-94bc9c95), последний является источником рисунка 6.8. Во всех этих случаях знайте, что стилизация областей ограничена свойствами, которые воздействуют на контейнер, но не на содержимое. Стиль содержимого взят из HTML-источника iframe. Именно поэтому text-overflow: ellipsis не работает, так же не работает font-color и так далее. Но стили, наподобие height и width, вместе со стилями границ, полей, отступов и другие свойства, которые не влияют на содержимое, применять можно.

Что мы только что изучили

Дополнения


Литература