Если вы занимаетесь разработкой для платформы Windows, то теперь можете писать приложения, ориентированные как на Windows 8.1, так и на Windows Phone 8.1, на основе одной общей кодовой базы, используя концепцию Universal App. Универсальные приложения — новинка в Windows. Они позволяют делать общими значительные части вашего кода на JavaScript, C#/Visual Basic и C++ во всей экосистеме Windows.
На конференции Build 2014 Microsoft заявила, что в будущем расширит концепцию Universal App на Xbox (bit.ly/1p19O7O). Вы можете сделать это и сейчас, но тогда вам понадобится Visual Studio 2012 и несколько обходных решений. Эти новости особенно порадуют тех, кто создает игры и медийные программы. Теперь вы можете начать с разработки для разнообразных платформ Windows, а проект для Xbox добавить позже. В этой статье я буду говорить только о Windows и Windows Phone.
Концепцию Universal App вне платформ Windows и Xbox можно использовать для написания кросс-платформенных приложений на основе HTML и JavaScript. Вы можете писать строго под HTML5. Вы можете отказаться от Windows Library for JavaScript (WinJS) для iOS и Android и применять WinJS только для поддержки «родной» функциональности в экосистеме Windows. Если вы используете XAML с C#, то можете задействовать такой инструмент, как Xamarin, для публикации на основных платформах.
Приступаем к работе с универсальными приложениями
Универсальные приложения создаются в Visual Studio 2013 Update 2 на платформе Windows 8.1 с использованием шаблонов проектов для JavaScript, C#/Visual Basic и C++. Это означает, что вы можете по-прежнему работать на своем любимом языке.
Структура нового Universal App — решение Visual Studio с тремя проектами минимум: Windows, Windows Phone и Shared. Так уж совпало, что предлагается выбор из трех типов проектов Universal Apps: шаблонов Blank, Hub и Navigation. Откройте диалог New Project в Visual Studio, чтобы создать Universal App с любым из этих трех новых шаблонов.
Поскольку Visual Studio создает решение из трех проектов, логично предположить, что проект Shared — то место, куда помещается общий код. Размещайте там как можно больше кода. Совместное и повторное использование кода дает много преимуществ, например упрощает сопровождение и отладку. Конечно, весь код общим не сделать. Если вы попытаетесь совместно использовать все, то закончите со слишком большим количеством конструкций ветвления в своем коде, и такие выражения, как if и switch станут просто неуправляемыми. Эмпирическое правило подсказывает: если вы копируете и вставляете код для использования в нескольких местах, вы должны разделить код на отдельные проекты. Эта концепция называется DRY (Don’t Repeat Yourself). Чем меньше вы будете повторяться, тем лучше.
Shared — это файл проекта с расширением .shproj. Общие проекты ведут себя как обычные с тем исключением, что они не создают скомпилированный вывод или пакет. Это просто способ сделать код доступным для двух или более проектов без ссылок на него или копирования/вставки. Вы можете делать код общим в таком стиле, потому что Windows Runtime в Windows 8.1 действует в обеих операционных системах. Некоторые API-члены не совместимы между платформами, но их вызовы возможны в соответствующих проектах.
На рис. 1 показана архитектура простого Universal App. Это приложение с именем Countdown отображает число дней до даты какого-то события, которую пользователь указывает наряду с количеством дней до него.
Рис. 1. Представления Windows 8.1, Windows Phone 8.1 и проекта Shared, расположенные бок о бок
Как видно на рис. 1, каждый проект содержит собственный UI-код. Проект Shared включает несколько JavaScript-файлов и общую начальную страницу.
Общий код между проектами
В проекте для каждой платформы есть default.html и файл .js, который действует как стартовая точка приложения. Эти файлы ссылаются на соответствующие JavaScript-файлы, специфичные для платформы. В этом примере вам не потребуется какой-то особый код для управления жизненным циклом. Однако, если такое вам нужно, вам, возможно, придется отделить эти файлы в зависимости от специфических требований ОС к вашему приложению.
Вот ссылки на default.html в Windows:
<script src="//Microsoft.WinJS.2.0/js/base.js"></script>
<script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
А это ссылки на default.html в Windows Phone:
<script src="//Microsoft.Phone.WinJS.2.1/js/base.js"></script>
<script src="//Microsoft.Phone.WinJS.2.1/js/ui.js"></script>
Это ссылки на WinJS 2.0 for Windows и WinJS 2.1 for Windows Phone. Последняя содержит JavaScript, использующий преимущества всех отличий в Windows Phone.
В приложении-примере Countdown в каждом проекте для платформы есть каталог page. И каждый каталог содержит подкаталоги для каждой страницы. Подкаталоги включают саму страницу и связанные с ней элементы вроде CSS- или JS-файлов. На рис. 1 вы видите страницы home, addEvent и privacy. Эти папки объединяются между проектами платформы и Shared в период выполнения. В каждой папке может быть только один файл с уникальным именем. Например, вы сохраняете страницу home.html в каталоге /pages/home/, но лишь в одном проекте. В данном случае я поместила его в папку проекта Shared.
Страница home — главная часть приложения, где отображаются события обратного отсчета (countdown events). На рис. 2 показан общий UI-код для home.html. Заметьте, что соответствующие файлы home.css находятся в папках своих проектов. То есть, если вы используете одинаковые имена CSS-класса между проектами, вы можете применять один и тот же HTML. Но его стиль будет изменяться разными CSS для каждой платформы, как видно на рис. 2. У вас может быть один базовый HTML и стиль с различающимися CSS для каждого проекта, специфичного для платформы.
Рис. 2. HTML и CSS, которые создают страницу home для обоих проектов
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Countdown</title>
<link href="/pages/home/home.css" rel="stylesheet" />
<script src="/js/MobileServices.js"></script>
<script src="/js/data.js"></script>
<script src="/pages/home/home.js"></script>
</head>
<body>
<div id="contenthost">
<h1 id="title" class="title">Countdown from</h1>
<div id="maincontent">
<div id="listViewTemplate" data-win-control="WinJS.Binding.Template"
class="win-container">
<div data-win-bind="style.background: color" class="win-item">
<h1 class="h1" data-win-bind="textContent: daysToGo"></h1>
<h2 class="h2"> days to go until</h2>
<h2 class="h2" data-win-bind="textContent: eventTitle"></h2>
<h2 class="h2" data-win-bind="textContent: eventDate"></h2>
</div>
</div>
<div id="listView" data-win-control="WinJS.UI.ListView"
class="win-listview"
data-win-options="{ selectionMode: 'single' }">
</div>
</div>
<div id="addEventHTMLControl">
</div>
</div>
</body>
</html>
<!-- CSS для Windows ListView -->
#listView {
height:500px;
}
#listView .win-viewport {
height:415px;
}
#listView .win-item {
width:300px;
height:185px;
padding: 0 0 0 5px;
}
.h1 {
font-size:1.5em;
}
.h2 {
font-size:1em;
}
<!-- CSS для Windows.Phone ListView -->
#listView .win-item {
width:100%;
height:125px;
padding: 5px 0 0 5px;
}
.h1 {
font-size:2em;
}
.h2 {
font-size:1.5em;
}
На рис. 3 элемент <div id="addEventHTMLControl"> с рис. 2 преобразуется в WinJS.UI.HtmlControl на платформе Windows. Это означает, что код добавления события можно хранить в файле addEvent.html.
Рис. 3. Фрагмент функции home.js ready, которая определяет ОС и формирует UI
var listView = document.querySelector("#listView").winControl;
var packageName = Windows.ApplicationModel.Package.current.id.name;
if (packageName === "Countdown.WindowsPhone") {
document.querySelector("#privacyButton").addEventListener("click",
this.navigateToPrivacyPage);
document.querySelector("#addButton").addEventListener("click",
this.navigateToaddEventPage);
listView.layout = new WinJS.UI.ListLayout();
}
else if (packageName === "Countdown.Windows") {
document.querySelector("#addButton").addEventListener("click",
this.showAddEventFlyout);
var htmlControl =
new WinJS.UI.HtmlControl(document.querySelector("#addEventHTMLControl"),
{ uri: '/pages/addEvent/addEvent.html' });
listView.layout = new WinJS.UI.GridLayout();
}
В общем home.js есть несколько точек, где нужно написать кое-какой условный код. Он определяет, какую платформу вы используете, и действует соответственно. Проверьте имя пакета, чтобы определить, какая платформа работает. Имя пакета находится в package.appmanifest под вкладкой Packaging. По умолчанию это GUID. Вы можете сменить его на что-нибудь более описательное, чтобы задействовать в коде. Сделав это, вы можете запрашивать его, чтобы определять в период выполнения текущую платформу. На рис. 3 показан пример того, как выглядит этот код; он определяет, какие события следует подключить и какой UI-компонент задействовать.
Как видите, на рис. 3 кнопки appBar подключаются к функциям, которые различаются для каждого типа приложения. Это может быть Flyout или целая страница как для добавления событий, так и для просмотра параметров конфиденциальности. В Windows это не сложно. Страница privacy является частью настроек приложения, запускаемой из чудо-меню Settings. Код, настраивающий Charms, обычно помещается в default.js при активированном событии. Поскольку в Windows Phone нет концепции Charms (чуло-кнопок), используйте appBar как средство навигации к странице privacy, что отражено на рис. 3.
Лучший способ добавлять события — задействовать Flyout в Windows. Тогда, из-за различий в форм-факторах, создавайте полную HTML-страницу в Windows Phone. Всплывающие окна и диалоги не слишком хорошо подходят для устройств с размером телефона. addEvent Flyout или страница — т оместо, где пользователь вводит описание нового события. HTML для addEvent Flyout также показан на рис. 3.
Также обратите внимание на то, что разметка ListView на рис. 3 меняется с помощью кода между сеткой и списком в зависимости от того, какая платформа была обнаружена. Свойство ListLayout удобно, так как вам требуется лишь один блок HTML с рис. 2. Комбинируя его с одной общей частью кода с рис. 3, позволяет легко переключаться между разметками.
В папке js вы найдете те же файлы data.js и navigator.js, что и их эквиваленты в любом решении на основе шаблона Navigation. Приложениям на обеих платформах нужны те же данные и схема навигации, поэтому эти файлы могут оставаться в проекте Shared. Следующий код является определением пространства имен Data, знакомым разработчикам на WinJS:
var list = new WinJS.Binding.List();
WinJS.Namespace.define("Data", {
loadEventData: loadEventData,
saveEventItem: saveEventItem,
deleteEventItem: deleteEventItem,
items: list,
});
Оба проекта, специфичные для платформы, совершенно одинаково используют данные, помещая JSON-данные в объект WinJS.Binding.List. Единственное отличие в том, как в них применяются стили к данным.
Разрабатывая универсальные приложения, вы должны писать модульные тесты и вносить изменения в общую кодовую базу. А значит, в какой-то момент вам потребуется запустить проект, чтобы проверить результаты. При отладке выберите нужный проект, щелкнув его правой кнопкой мыши в Solution Explorer и указав Set as Start Project.
После этого имя проекта выделяется полужирным в Solution Explorer. Когда вы щелкнете Run, нажмете F5 или запустите проект иным способом, будет запущен стартовый проект (Start Project). Вы можете переназначать проекты в качестве стартовых по мере необходимости.
Но проект Shared не может быть стартовым. Когда вы запускаете конкретный проект, Visual Studio упаковывает, развертывает и загружает ресурсы проекта, в том числе из проекта Shared. Затем он запускает соответствующий эмулятор, если он еще не работает, и вы можете вести отладку. Чтобы узнать больше об отладке приложений Windows Store, см. статью в моем блоге по ссылке bit.ly/1hTpxHB.
Создание «родных» UI
Объем общего кода для UI будет самым малым. Различные форм-факторы диктуют то, как представлять данные и в каком количестве. Представляйте данные с помощью HTML и придавайте им красивый вид с помощью CSS. Иметь дело с разными HTML и CSS проще, если вы разделите их на разные проекты, включая CSS Media Queries для каждого диапазона устройств на каждую платформу.
В случае приложения Countdown вы должны выводить сетку в Windows и вертикальный список на смартфонах. Для этого помещайте файл home.html в проект Shared, а файлы home.css в отдельные проекты. Страницы addEvent и privacy тоже помещаются в соответствующие проекты для платформ. Увидеть эту структуру папок можно на рис. 1.
В приложении Countdown.Windows пользователь добавляет событие, касаясь или щелкая кнопку Add на appBar. Это приводит к появлению всплывающего диалога с применением элемента управления Flyout. В Countdown.WindowsPhone пользователь попадает на полную страницу addEvent. Вот код, который обеспечивает дизайн Flyout в Countdown.Windows:
<div id="eventFlyoutPanel" data-win-control="WinJS.UI.Flyout">
<table width="100%" height="100%">
<tr><td>Date:</td><td><span id="eventDate"
data-win-control="WinJS.UI.DatePicker"></span></td></tr>
<tr><td>Event:</td><td><input type="text" id="eventTitle" /></td></tr>
<tr><td> </td><td><input type="button" id="confirmButton"
value="Submit" /></td></tr>
</table>
</div>
Это небольшая таблица, содержащая DatePicker и TextBox. Но ни Flyout, ни DatePicker не работают в Windows Phone. Этот Flyout сконфигурирован в home.html как WinJS.UI.HtmlControl. На рис. 4 показано, как выглядит экран, создаваемый этим кодом.
Рис. 4. Внешний вид элемента Flyout при выполнении приложения Countdown
Проект Countdown.WindowsPhone сильно отличается. Вместо использования Flyout нужно переходить на страницу addEvent. Отсутствие DatePicker не является проблемой. Дату события можно получить проверенным в мобильных технологиях способом: вы создаете три элемента <select>, по одному для каждой части даты (день/месяц/год). На рис. 5 дан пример HTML-кода Countdown.Windows addEvent, а на рис. 6 показано, как она выглядит в Windows Phone в период выполнения.
Рис. 5. Пример HTML-кода Countdown.Windows addEvent
<h1 id="title" class="title">
Add event
</h1>
<div>
Month
<select id="eventMonth">
<option value="--Select--">--Select--</option>
<option value="01">January</option>
<option value="02">February</option>
<option value="03">March</option>
<option value="04">April</option>
<option value="05">May</option>
<!-- options 6-11 удалены для краткости -->
<option value="12">December</option>
</select>
Day
<select name="day" id="day">
<option value="--Select--">--Select--</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<!-- options 6-30 удалены для краткости -->
<option value="31">31</option>
</select>
Year<select id="eventYear">
<option value="--Select--">--Select--</option>
<!-- Остальные option созданы через клиентский скрипт -->
</select>
</div>
<div>Event<input type="text" id="eventTitle" /></div>
<div>
<input type="button" id="backButton" value="Back" />
<input type="button" id="confirmButton" value="Submit" />
</div>
<script type="text/javascript">
(function () {
var yearSelect = document.querySelector("#eventYear");
for (var i = 2014; i < 2075; i++) {
yearSelect.options[i] = new Option(i, i);
}
})();
</script>
Вы должны убедиться, что нужные вам элементы управления доступны в разных ОС. Элементы управления Hub, Flyout, DatePicker и TimePicker отсутствуют в Windows Phone. Элемент управления Pivot недоступен в Windows. К счастью, если вы используете популярные элементы управления Hub или Pivot, они обычно взаимозаменяемы. Поэтому вы можете задействовать Hub в Windows и Pivot в Phone и получить одно и то же. Для получения дат годятся простые раскрывающиеся элементы управления. На рис. 6 показано, как это будет выглядеть в Windows Phone.
Рис. 6. Приложение Countdown и страница addEvent, сгенерированные в Windows Phone
Анализируйте свой код и выделяйте код, ориентированные на задачи, специфичные для конкретной ОС. Чем больше задач таких типов, тем больше кода нужно выносить в отдельный проект. Если таких блоков всего несколько, их можно оставить и в проекте Shared.
Другие способы сделать код общим
Вы можете применять архитектурные шаблоны как способ организации, сопровождения и совместного использования кода, например Model-View-ViewModel (MVVM) или Model-View-Controller (MVC). В случае приложения Countdown совместное использование общего кода в проекте Shared работает прекрасно.
Другое интересное объявление на конференции Build заключается в том, что Windows Runtime теперь поддерживает любую стороннюю JavaScript-инфраструктуру. То есть вы можете использовать популярные пакеты, такие как Knockout, Angular или Breeze.js, для реализации этих шаблонов.
Models и ViewModels в проекте Shared бесшовно работают с другими проектами с минимальной модификацией или вообще без изменений. Остается лишь код в Views, которые могут потребовать дублирования или адаптации. Иногда это неизбежно. Слишком много устройств всевозможных размеров и форм-факторов. Кроме того, широко разнообразие устройств ввода, в том числе сенсорных, клавиатуры, мышь, голосового ввода и т. д. Код для обработки этих непостоянных факторов зачастую нужно отделять.
В JavaScript-приложениях код Process Lifecycle Management можно поместить в default.js в каждый индивидуальный проект. Вы также можете хранить в нем модели, ViewModels и любую прикладную логику. Операционные системы по-разному обрабатывают протоколы активации. Однако в большинстве проектов есть какой-то универсальный вспомогательный код, который должен быть добавлен в скрипт в проекте Shared.
Сделать код общим между проектами можно и несколькими другими способами.
- Условная компиляция Определенные блоки кода компилируются только для конкретной ОС. Это применимо к C#/Visual Basic и C++. В WinJS используйте обычные выражения if. Узнать больше можно по ссылке bit.ly/UzdBAa.
- Компоненты Windows Runtime Компоненты, созданные с помощью Windows Runtime, предоставляют код для приложений Windows и Windows Phone, что обеспечивает совместное использование кода.
- Добавление ссылки Вы можете обрабатывать указатель на файл в другом проекте так, будто он был частью текущего проекта.
- Portable Class Library (PCL) Это способ совместного использования кода, принятый в Microsoft .NET Framework. Windows Runtime (WinRT) поддерживает эти устаревшие компоненты, поэтому их можно применять в приложениях. Лучше всего использовать их, если у вас уже есть PCL. Создавая новые компоненты, вы, вероятно, предпочтете добавить WinRT-компоненты.
Очевидно, что проект Shared — не единственный способ создания общего кода. Вы можете повторно использовать .NET-код, который уже имеется у вас. Например, вы можете предъявлять весьма жесткие требования к производительности, создавая игру в стиле «action», которой нужна помощь со стороны C++. Применение WinRT-компонентов — отличный способ сделать такой код общим.
По возможности всегда используйте Fragments. Это просто фрагменты HTML, которые могут быть использованы в обоих проектах. Такие UI-компоненты, как appBar и Flyout, не слишком хорошо работают на разных форм-факторах, поэтому их желательно хранить в соответствующих проектах. Код, определяющий общие настройки приложения и тому подобное, можно отправить в проект Shared.
Заключение
Хотя универсальные приложения не полностью реализуют концепцию «написано один раз и может выполняться где угодно», они все же довольно близки к этому. Есть некоторые изменения в API, но благодаря наличию единой Windows Runtime для этих приложений, довольно легко работать над приложением, которое выполняется во всей экосистеме Windows. Вы можете даже добиться работы своих приложений на платформах, отличных от Microsoft, используя расширения Visual Studio от Xamarin. На конференции Build также была обещана будущая поддержка Xbox One Achievements, Challenges, OneGuide и др. Сейчас происходит много интересного для разработчиков приложений на основе технологий Microsoft.