ФЕВРАЛЯ 2016

ТОМ 31 НОМЕР 2

Windows 10 - Приложения Universal Windows Platform для веб-разработчиков

Тим Кульп | ФЕВРАЛЯ 2016

Продукты и технологии:

HTML/CSS/JavaScript, XAML, C#, Universal Windows Platform (UWP)

В статье рассматриваются:

  • определение ключевых навыков для создания адаптивных UWP-приложений;
  • как использовать то, что вы уже знаете в HTML/CSS/JavaScript, при создании UWP-приложений;
  • применение различных подходов к созданию приложений для нескольких форм-факторов;
  • использование единой кодовой базы для кросс-платформенной разработки.

Как корпоративный веб-разработчик вы отлично знаете HTML, CSS и JavaScript. Вы умеете создавать адаптивные веб-приложения, которые подстраиваются под размер экрана, в то же время сохраняя свою функциональность на поддерживаемых вами устройствах и браузерах. Те же навыки можно использовать в создании приложений Universal Windows Platform (UWP). Какие бы приложения вы ни создавали — для настольных компьютеров, мобильных устройств или для любой платформы Windows 10, ваш опыт в работе над адаптивными веб-приложениями для множества браузеров даст вам фору в разработке для UWP.

В этой статье я исследую, как задействовать познания в области веб-разработки для перехода от создания кросс-браузерных приложений к созданию гибких UWP-приложений, выполняемых на любых устройствах под управлением Windows 10. Для этого я начну с анализа того, как основополагающие принципы создания адаптивных интерфейсов транслируются из CSS и HTML в UWP. Затем я рассмотрю применение VisualStates и XAML Views для структурирования приложения на основе возможностей конкретного устройства. Наконец, я задействую Adaptive Code для разработки под целевые устройства по аналогии с тем, как используется Plain Old JavaScript для разработки под конкретные браузеры.

Зачем XAML веб-разработчикам?

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

Распространенный пример — использование сеточной разметки на XAML в сравнении с HTML. В XAML создание сеточной разметки начинается с добавления элемента управления grid, определения столбцов и строк, а затем присваивания каждого элемента управления внутри сетки конкретной ячейке или строке. В HTML сеточная разметка создается разнообразными способами, в частности использованием:

  • элементов float с определенными высотой и шириной для создания ячеек с зазорами (clears) для начала новой строки;
  • display:grid в элементе container, где у каждого дочернего элемента определены столбец и строка;
  • таблицы с элементами tr и td.

Что вы реализуете таким образом, полностью зависит от ваших знаний CSS или HTML, и эти подходы не позволят вам получать помощь от таких средств, как IntelliSense, в отличие от элемента управления grid в XAML. Строго типизированные элементы управления XAML упрощают создание UI благодаря поддержке IntelliSense, что крайне полезно для разработчиков — новичков в UWP.

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

Если вы являетесь энтузиастом HTML/CSS/JavaScript, то UWP предоставит вам платформу для использования существующего у вас кода и создания на его основе великолепных приложений. HTML/CSS/JavaScript и XAML — отличные средства со своими преимуществами и недостатками. Есть две статьи о том, почему один автор предпочитает XAML, а не JavaScript (bit.ly/1NxUxqh), а другой — JavaScript, а не XAML (bit.ly/1RSLZ2G). Мне нравится HTML для веб-приложений, но я советую изучить XAML, если вы новичок в UWP. Это позволит вам освоить элементы управления в рамках UWP, снизить расходы на поддержку благодаря строго типизированному коду с интеграцией IntelliSense и получить удовольствие от обучения чему-то новому.

Опирайтесь на то, что вы уже знаете

В UWP много сходств с фундаментальными принципами веб-дизайна. Базовые идеи вроде разделения обязанностей между HTML и JavaScript в веб-разработке в UWP транслируются в XAML и файл отделенного кода XAML.cs. Вся логика помещается в отделенный код (codebehind), тогда как весь презентационный уровень поддерживается в XAML-файле (по аналогии с тем, как вся логика содержится в JavaScript-коде, а презентационный уровень — в HTML в сочетании с CSS). Более того, многие современные веб-приложения используют такие инфраструктуры, как Knockout и AngularJS, для реализации связывания с данными с помощью проектировочного шаблона Model-View-ViewModel (MVVM). Знание этих инфраструктур и MVVM в целом является базисом для понимания связывания с данными в UWP. Соответствующий синтаксис в веб-разработке и UWP отличается, но, когда дело доходит до базовых концепций, оказывается, что веб-разработчики обладают прочным фундаментом для создания приложений, способных работать на разных устройствах и в разных браузерах.

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

Позиционирование: float и clear в сравнении с RelativePanel

В HTML вы определяете позиционирование каждого элемента тем, где он находится в Document Object Model. HTML выполняет рендеринг каждого элемента сверху вниз в порядке от первого объявленного элемента до последнего. Когда ввели CSS, это позволило придавать элементам более сложные разметки, задавая стиль отображения элемента, позицию (относительную или абсолютную), а также float и clear. С помощью float веб-разработчики могли изымать какой-либо HTML-элемент из процесса рендеринга сверху вниз и помещать его слева (float: left) или справа (float: right) от содержащего его элемента.

Представьте простую разметку, включающую заголовок, основной контент, боковую колонку (sidebar) и нижнюю сроку. Использование float инструктирует браузер выполнить рендеринг боковой колонки по правому краю контейнера, а основной контент — по левому. Используя float, элементы будут размещаться рядом друг с другом слева или справа в зависимости от того, какое значение float было указано. Clear используется для прекращения свободного размещения элементов и возврата к стандартному в HTML потоку сверху вниз. На рис. 1 показан пример использования float для создания простой разметки.

Рис. 1. Применение float для создания простой разметки

div {
  width: 100%;
}
mainContent {
  width: 60%; float: left;
}
  sidebar{
  width: 40%; float: right;
}
clearer {
  clear: both;
}
CSS для дизайна
<header>
</header>
<div>
  <section class="content"></section>
  <section class="sidebar"></section>
  <div class="clearer"></div>
</div>
<footer>
</footer>
HTML для дизайна

В тех случаях, где веб-разработчики используют float и clear для создания разметки, UWP предоставляет элемент управления RelativePanel, который, как и предполагает его название, позволяет определять разметку относительно других элементов управления. Как и float, RelativePanel дает возможность разработчикам контролировать то, как элементы позиционируются относительно элемента управления привязки (anchor control). CSS-классы служат для определения позиционирования элементов веб-страницы. Чтобы воспроизвести ту же разметку, используйте RelativePanel и его подключенные свойства (attached properties) внутри элементов управления:

<RelativePanel>
  <!--Header – объект привязки (anchor object) для панели с относительным позиционированием--> 
  <TextBlock Text="Header" Name="tbHeader"></TextBlock>
  <TextBlock Text="Content" RelativePanel.Below="tbHeader"
    Name="tbContent"></TextBlock>
  <TextBlock Text="SideBar" RelativePanel.RightOf="tbContent"
    RelativePanel.Below="tbHeader" Name="tbSideBar"></TextBlock>
  <TextBlock Text="Footer" RelativePanel.Below="tbSideBar"
    Name="tbFooter"></TextBlock>
</RelativePanel>

В этом блоке кода позиционирование каждого элемента управления относительно позиции элемента управления привязки (anchor control) (в данном случае в его роли выступает TextBlock заголовка). Используя RelativePanel, каждый элемент управления можно назначить на то место, где он должен появиться на экране относительно других элементов управления. Там где веб-разработчики применили бы float: left, разработчики под UWP используют RelativePanel.LeftOf или RelativePanel.RightOf (эквивалент float: right) для позиционирования контента. Хотя это похоже на применение float, концепции использования clear для возврата к обычному потоку рендеринга нет; вместо этого просто отметьте, что под предыдущим элементом что-то есть. Это упрощает диагностику проблем с разметкой, поскольку элементы float и clear могут оказаться крепким орешком для разработчиков, не имеющих глубоких знаний в области CSS. RelativePanel предоставляет декларативный способ задания того, где должен появиться некий элемент управления относительно других. Когда RelativePanel закрывается, приложение возвращается к обычному потоку рендеринга в XAML (сверху вниз, как и в HTML).

Масштабирование: процентные доли от пикселей

Создание адаптивного веб-приложения за счет масштабирования означает работу с относительными размерами элементов. Используя разметку страницы с заголовком, контентом, боковой панелью и нижним заголовком, вообразите, что этот UI изначально был предназначен для экрана настольного компьютера. Веб-дизайнеры сначала определили бы оптимальную ширину страницы в пикселях для этой разметки. В данном примере ширина страницы будет 1000 пикселей. В процессе дизайна каждый элемент создается с такой шириной в пикселях, которая учитывает ширину контейнера в 1000 пикселей. В HTML раздел контента был бы шириной 800 пикселей, а раздел боковой панели — шириной 200 пикселей. Применяем формулу: мишень / контекст = процентная доля; тогда ширина раздела контента составляет 80% контекста (контекст равен странице шириной 1000 пикселей), а ширина врезки — 20%.

Использование процентных долей в веб-дизайне позволяет масштабировать разметку по мере изменения размеров контейнера. В данном случае, если объект страницы размером 1000 пикселей был отмасштабирован пользователем до 659 пикселей, размеры контента и боковой панели уменьшатся до 527 и 131 пикселей соответственно. Аналогично создание стилей под использование em вместо конкретных размеров точках или пикселях дает возможность масштабировать шрифт соответственно контексту. Такая практика помогает добиться сохранения пропорциональности дизайна независимо от размера окна.

Хотя применение процентных долей выглядит простой математикой, есть и другие факторы, влияющие на то, как масштабируются элементы, например плотность пикселей на устройстве и его ориентация, и они вносят элемент непредсказуемости в ваш дизайн. UWP упрощает масштабирование на основе концепции «эффективный пиксель» для всех размеров. Эффективный пиксель — вовсе не то же самое, что единственный пиксель. Эффективные пиксели используют алгоритм масштабирования из UWP, чтобы узнать, как представить один эффективный пиксель на основе стандартного удаления (standard distance) устройства от пользователя и плотности пикселей.

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

VisualState и media-запросы

В Web-разработке CSS предоставляет информацию о разметке для приложения. Работа с процентными долями позволяет масштабировать окно приложения, но в какой-то момент дизайн потребуется нарушить и подстроиться под изменяющиеся требования, связанные с разрешением экрана. Разметка, создаваемая для мобильного устройства вроде планшета, не будет идентична таковой, создаваемой для презентационного устройства с экраном от 80 дюймов и выше, например для Surface Hub. Более старая аналогия для веб-дизайна — случай, где пользователь захотел бы напечатать экран. CSS дает возможность справиться с этой дилеммой на основе media-запросов CSS. Когда пользователь стал бы распечатывать экран, браузер задействовал бы CSS печати вместо CSS экрана. По мере развития адаптивного веб-дизайна media-запросы стали поддерживать более детализированную информацию. Вот пример media-запроса CSS:

<link type="text/css" rel="stylesheet" 
  href="styles/719style.css"
  media="screen and (max-device-width: 719px)"/>

В этом запросе применяется файл 719style.css, если media, где отображается веб-приложение, — это экран шириной, меньшей или равной 719 пикселям. В качестве примера этот media-запрос можно использовать для очистки значений float и представления элементов контента и боковой панели стопкой, а не встык. С помощью media-запросов веб-разработчики могут настраивать отображение в соответствии с размером экрана, разрешением, ориентацией и многими другими параметрами (полный список для CSS3 см. по ссылке bit.ly/1riUA2h).

Как и media-запросы, VisualState можно использовать для изменения позиций или перепланировки элементов управления на основе триггеров.

VisualStateManager в UWP можно задействовать вместо media-запросов для изменения дизайна приложения на основе определенных параметров. VisualStateManager содержит один или более VisualStateGroups — объект-контейнер с несколькими VisualState. Каждый VisualState хранит элементы setter (какие свойства обновляются для каждого элемента управления) и триггеры (что заставляет элементы setter вносить изменения). VisualStateManager управляет триггерами, чтобы знать, когда следует применять значения конкретных элементов setter к VisualState. В терминах CSS триггеры подобны media-запросам, а элементы setter являются значениями стиля в рамках таблицы стилей, на которую ссылается media-запрос (пример показан на рис. 2).

Рис. 2. Изменение позиций элементов управления на основе триггеров VisualState

<VisualStateManager.VisualStateGroups>
  <VisualStateGroup x:Name="ResponseStateGroup">
    <VisualState x:Name="LessThan720">
      <VisualState.Setters>
      <Setter Target="tbSideBar.(RelativePanel.Below)" Value="tbContent"/>
        <Setter Target="tbSideBar.(RelativePanel.RightOf)" Value=""/>
      </VisualState.Setters>
      <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="1"/>
      </VisualState.StateTriggers>
    </VisualState>
    <VisualState x:Name="GreaterThan720">
      <VisualState.Setters>
        <Setter Target="tbSideBar.(RelativePanel.Below)" Value="tbHeader"/>
        <Setter Target=" tbSideBar.(RelativePanel.RightOf)" Value="tbContent"/>
      </VisualState.Setters>
      <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="720"/>
      </VisualState.StateTriggers>
    </VisualState>
  </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

В этом коде подготавливаются два VisualState. VisualState с именем LessThan720 срабатывает, когда ширина окна находится в диапазоне от 1 до 719 пикселей включительно. Когда окно расширяется до 720 пикселей или более, срабатывает VisualState с именем GreaterThan720. Каждый из этих VisualState работает с параметрами RelativePanel элемента управления tbSideBar. Если размер окна менее 720, значит, экран не достаточно велик для поддержки дизайна, где контент размещается рядом с боковой панелью. В этой ситуации сработает VisualState с именем LessThan720, что приведет к размещению боковой панели стопкой под TextBlock основного контента. Это похоже на использование media-запроса в том плане, что я могу задать float: none в файле LessThan719.css.

Как и media-запросы, VisualState можно использовать для изменения позиций или перепланировки элементов управления на основе триггеров. Управление VisualState и ViewStateGroups может усложниться по мере усложнения интерфейса. Проще всего управлять изменениями сложного VisualState с помощью Blend for Visual Studio 2015. Применяя редактор Blend, можно создавать новые VisualState и наглядно видеть изменения в дизайне приложения по мере их создания. Blend возьмет на себя генерацию всего XAML за вас и обеспечит, чтобы вы предоставили необходимые данные для инициации изменения через VisualState. Отличные видеоролики, обучающие применению Blend для управления VisualState, см. на сайте Microsoft Virtual Academy по ссылке bit.ly/1P94e32.

Использование представлений для перепланировки UI

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

UWP предоставляет ту же возможность через представления (Views). В зависимости от приложения работа с разными семействами устройств может потребовать столь сильных отличий в UI, для которых VisualStateManager может оказаться неподходящим средством. Представления позволяют разработчикам использовать существующий серверный код с новым XAML UI. Вы можете упростить работу с представлениями за счет применения хорошо структурированных ViewModel, где возможно использование одного объекта ViewModel для нескольких View. Знание Knockout или AngularJS поможет вам создать должные ViewModel в UWP для UI, адаптируемых под конкретное семейство устройств.

Каждый объект View подготавливается для конкретного устройства, начиная с создания подпапки в папке Views приложения. В папке Views создайте новую папку DeviceFamily-Mobile. Это указывает Modern Resource Technology использовать представление MainPage.xaml, находящееся в папке DeviceFamily-Mobile, когда на устройстве из этого семейства (например, на смартфоне) запрашивается MainPage. Если запрос к MainPage исходит от устройства из какого-либо другого семейства, ответом будет стандартная страница MainPage. Эта функциональность позволяет UWP-разработчикам создавать адресные UI для сценариев применения, уникальных для конкретных семейств устройств с Windows 10.

Адаптивный код

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

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

Первый шаг в использовании специфичных для устройства возможностей — убедиться, что в проект включены подходящие Extension API. Например, в приложении для заметок должны быть доступны аппаратные кнопки на смартфоне. Чтобы использовать эти кнопки, вы должны добавить ссылку на Windows Mobile Extensions для UWP. Для этого вы добавляете ссылку в проект (как и любую другую), выбираете Universal Windows, а затем Extensions. Вы увидите список возможных расширений, таких как Desktop, Mobile и Team (для Surface Hub). Выберите, какие расширения вам нужно добавить в проект и щелкните OK.

Если JavaScript распознает возможности браузера серией проверок навигатора (navigator), то в UWP наличие нужного API проверяется методом IsTypePresent. В приложении для заметок можно проверить доступность аппаратной кнопки для использования камеры с помощью следующего кода:

string apiName = "Windows.Phone.UI.Input.HardwareButtons";
if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent(apiName))
{
  Windows.Phone.UI.Input.HardwareButtons.CameraPressed +=
    HardwareButtons_CameraPressed;
}

Этот короткий код позволяет приложению ориентироваться на специфические аппаратные возможности, поддержка которых добавлена через Extension API. Обертывая объявление обработчика событий CameraPressed в метод IsTypePresent, вы избежите попыток регистрации этого обработчика в отсутствие необходимого API. Существуют средства, помогающие гарантировать, что происходят проверки API, чтобы приложению не рухнуло, если этого API нет. PlatformSpecific — отличный NuGet-пакет, который упрощает идентификацию и обертывание любой ссылки на Extension API, наличие которого не проверяется методом ApiInformation.IsTypePresent. Узнать больше об этом NuGet-пакете можно на сайте GitHub по ссылке bit.ly/1GvhkF0.

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

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

if(Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent(apiName, 3))
{
  newKillerFunction();
}

В этом фрагменте кода приложение будет выполнять newKillerFunction, только если apiName передается при наличии версии 3. Если версия ниже 3, newKillerFunction не выполняется, а если версия выше 3 (например, версия 4), newKillerFunction будет выполнена.

Заключение

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


Тим Кульп (Tim Kulp) — старший технический архитектор, живет в Балтиморе (штат Мериленд). Занимается разработкой для Web, мобильных устройств и UWP, а также является автором, художником и папой. Вы найдете его в Twitter (@seccode) или через LinkedIn (linkedin.com/in/timkulp).

Выражаю благодарность за рецензирование статьи эксперту Microsoft Кевину Хиллу (Kevin Hill).