Давайте сегодня поговорим о том, какие дополнительные возможность разработки приложений под Windows Phone есть у владеющих HTML5 и JavaScript.
Прежде всего разберёмся с тем, как это возможно. Все возможности, связанные с HTML5 + JS разработкой для Windows Phone связаны с присутствием на борту браузера Internet Explorer, который по поддерживаемому функционалу аналогичен настольному Internet Explorer 9. И это понятно, потому что они имеют общую базу кода. Ниже представлены основные (но не все) возможности, поддерживаемые браузером.
Наличие мобильного браузера с такой поддержкой уже предоставляет базовую возможность присутствия на мобильной платформе: создание богатого возможностями мобильного сайта. Но ведь мы хотели писать приложения! Разрабатывать приложения позволяет доступный разработчикам элемент управления WebBrowser:
<phone:WebBrowser Grid.Row="1" Name="MyBrowser" IsGeolocationEnabled="True" IsScriptEnabled="True" Background="{StaticResource PhoneBackgroundBrush}"/>
Наличие полнофункционально мобильного сайта и возможность вставить в своё приложение элемент управления WebBrowes приводит нас к первому типу HTML5 приложений для Windows Phone: приложениям, которое хостят мобильный веб-сайт в элементе управления WebBrowser и добавляют дополнительно сервисные возможности, связанные с платформой, например, Push Notification.
Давайте напишем пример такого приложения. Хостить будем мобильный сайт Яндекса. Для простоты не будем выдумывать дополнительные сервисы, а добавим ApplicationBar и несколько кнопок, которые будут предоставлять доступ к наиболее востребованному по нашему мнению функционалу.
Как обычно, начинаем с создания приложения на основе шаблона Windows Phone Application. Используя NuGet, добавим в приложение пакет Silverlight for Windows Phone Toolkit. После этого, на основную страницу приложения MainPaige.xaml добавим элемент управления WebBrowser и PerformanceProgressBar, чтобы показывать процесс загрузки страницы. В результате XAML код будет выглядеть следующим образом:
<toolkit:PerformanceProgressBar Name="Progress" Height="10" IsIndeterminate="False" Background="{StaticResource PhoneBackgroundBrush}"/>
<phone:WebBrowser Grid.Row="1" Name="Yandex" IsGeolocationEnabled="True" IsScriptEnabled="True" Background="{StaticResource PhoneBackgroundBrush}"/>
Не забудье доавить ссылку на пространство имён, чтобы использовать элементы управления из Silverlight for Windows Phone Toolkit:
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
Чтобы сделать приложение в стиле Windows Phone и добавить в него кнопок, я раскоментировал ApplicationBar, и воспользовался информацие из статьи Где взять иконки для Windows Phone приложений?, чтобы найти необходимые моему приложению иконки. Также я сразу добавил к кнопкам на ApplicationBar обработчики событий. В результате XAML код моего страницы MainPage.xaml моего приложения стал выглядеть следующим образом:
<phone:PhoneApplicationPage x:Class="MyYandexWithBJ.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="696" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait" shell:SystemTray.IsVisible="True"> <!--LayoutRoot is the root grid where all page content is placed--> <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions>
<toolkit:PerformanceProgressBar Name="Progress" Height="10" IsIndeterminate="False" Background="{StaticResource PhoneBackgroundBrush}"/>
<phone:WebBrowser Grid.Row="1" Name="Yandex" IsGeolocationEnabled="True" IsScriptEnabled="True" Background="{StaticResource PhoneBackgroundBrush}"/>
</Grid>
<!--Sample code showing usage of ApplicationBar--> <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True"> <shell:ApplicationBarIconButton IconUri="/icons/appbar.back.rest.png" Text="Назад" Click="ApplicationBarBackButton_Click"/> <shell:ApplicationBarIconButton IconUri="/icons/appbar.home.png" Text="Домой" Click="ApplicationBarHomeButton_Click"/> <shell:ApplicationBarIconButton IconUri="/icons/appbar.plane.rotated.45.png" Text="Самолёты" Click="ApplicationBarFlightsButton_Click"/> <shell:ApplicationBarIconButton IconUri="/icons/appbar.next.rest.png" Text="Вперёд" Click="ApplicationBarForwardButton_Click"/> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar>
</phone:PhoneApplicationPage>
Иконки были добавлиены в папку icons в проекте, тип содержания установлен в Content, Copy to Output в Copy if newer. Само приложение при запуске теперь выглядит следующим образом:
Добавим код, который будет при загрузке приложения открывать мобильную страницу Яндкса, по нажатии на кнопку с домиком, открыать её же, а при нажатии на самолёт – отервать мобильную страницу с расписание самолётов.
// Constructor
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e) { Yandex.Navigate(new Uri("http://m.yandex.ru")); }
private void ApplicationBarHomeButton_Click(object sender, EventArgs e) { Yandex.Navigate(new Uri("http://m.yandex.ru")); }
private void ApplicationBarFlightsButton_Click(object sender, EventArgs e) { Yandex.Navigate(new Uri("http://m.rasp.yandex.ru/station?plane=1")); }
Теперь приложение при запуске открывает мобильную страницу Яндекса, а при нажатии на кнопку с самолётом, открывает мобильную страницу с расписанием самолётов:
Осталось доавить обработку кнопок вперёд/назад и отображение ProgressBar при навигации на страницу. Для того, чтобы добавить отображение ProgressBar, нужно доавить оброботчики событий Navigating и Navigated элемента управления WebBrowser:
void MainPage_Loaded(object sender, RoutedEventArgs e) { Yandex.Navigated += new EventHandler<System.Windows.Navigation.NavigationEventArgs>(Yandex_Navigated); Yandex.Navigating += new EventHandler<NavigatingEventArgs>(Yandex_Navigating); Yandex.Navigate(new Uri(http://m.yandex.ru));
}
void Yandex_Navigating(object sender, NavigatingEventArgs e) { Progress.IsIndeterminate = true; }
void Yandex_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e) { Progress.IsIndeterminate = false; }
Чтобы обрабоать нажатия кнопок вперёд и назад, воспользуемся возмостями JavaScript и объекта history, вместе с возможностью вызвать его из кода приложения:
private void ApplicationBarBackButton_Click(object sender, EventArgs e) { Yandex.InvokeScript("eval", "history.go(-1)"); }
private void ApplicationBarForwardButton_Click(object sender, EventArgs e) { Yandex.InvokeScript("eval", "history.go(1)"); }
Теперь у нас есть собственное приложение “мобильный Яндекс” с кнопкой домой и расписанием самолётов. Хорошим завершением упражнения будет добавление определения возможности перехода по кнопкам назад/вперёд, чтобы иметь их активными только если переход можно действительно выполнить.
Это приложение использует удалёный HTML5+JS контент, логичным следующим шагом будет второй тип HTML5 приложений для Windows Phone, которое использует локальный контент на телефоне, не выходя, или минимально выходя, за пределы элемента управления WebBrowser.
Давайте напишем пример такого приложения. Это будте локальная HTML5 страничка, активное содержание которой будет генериться JS. Чтобы упростить разработку, будем использовать библиотеку jQuery.
Как обычно, начинаем с создания приложения на основе шаблона Windows Phone Application. Чтобы не писать HTML5 приложение самостоятельно, я взял SVG пример из доклада Кости Кичинского на TechEd Russia 2011.
В проекте создадим папку content и добавим в неё файлы svgdemo.htm и jquery-1.7.min.js. В файле svgdemo.html измените путь к скрипту jquery-1.7.min.js, так, чтобы тек script выглядел следубщим образом:
<script src="jquery-1.7.min.js" type="text/javascript"></script>
Также необходимо установить у файдлов svgdemo.htm и jquery-1.7.min.js Build Action в Content и Copy to Output в Copy if newer.
Теперь, добавим элемент управления WebBrowser на главную страницу приложения так, чтобы он заполнял всё пространство. В результате XAML код будет выглядеть следующим образом:
<phone:PhoneApplicationPage x:Class="IE9HTML.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait" shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed--> <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!--ContentPanel - place additional content here--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <phone:WebBrowser Name="myBrowser" IsScriptEnabled="True" /> </Grid> </Grid> </phone:PhoneApplicationPage>
На первый взгляд, собственно, разработка закончена (с учётом того, что мы взяли готовое HTML5 приложение), однако ещё остаётся один тонкий момент. Дело в том, что наш файл HTML и JS после развёртывания приложения будут недоступны элементу управления WebBrowser, так как они будут находиться не в Isolated Storage. Поэтому нам, при запуске приложения необходимо развернуть наше HTML5 приложение в Isolated Storage. Будем для этого использовать пример кода из MSDN, изменив в нём набор файлов на собственный:
private void SaveFilesToIsoStore() { //These files must match what is included in the application package, //or BinaryStream.Dispose below will throw an exception. string[] files = { "content/svgdemo.htm", "content/jquery-1.7.min.js" };
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
if (false == isoStore.FileExists(files[0])) { foreach (string f in files) { StreamResourceInfo sr = Application.GetResourceStream(new Uri(f, UriKind.Relative)); using (BinaryReader br = new BinaryReader(sr.Stream)) { byte[] data = br.ReadBytes((int)sr.Stream.Length); SaveToIsoStore(f, data); } } } } private void SaveToIsoStore(string fileName, byte[] data) { string strBaseDir = string.Empty; string delimStr = "/"; char[] delimiter = delimStr.ToCharArray(); string[] dirsPath = fileName.Split(delimiter);
//Get the IsoStore.
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
//Re-create the directory structure.
for (int i = 0; i < dirsPath.Length - 1; i++)
{
strBaseDir = System.IO.Path.Combine(strBaseDir, dirsPath[i]);
isoStore.CreateDirectory(strBaseDir);
}
//Remove the existing file.
if (isoStore.FileExists(fileName))
{
isoStore.DeleteFile(fileName);
}
//Write the file.
using (BinaryWriter bw = new BinaryWriter(isoStore.CreateFile(fileName)))
{
bw.Write(data);
bw.Close();
}
}
Метод SaveFilesToIsoStore() проверяет, что указанных файлов нет в Isolated Storage и копирует их, используя вспомогательный метод SaveToIsoStore, сохраняя иерархию директорий.
Теперь всё готово, что написать код, который закончит инициализацию нашего HTML5-приложения:
// Constructor
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e) { SaveFilesToIsoStore(); myBrowser.Navigate(new Uri("content/svgdemo.htm", UriKind.Relative)); }
Сохраняем файлы на IsolatedStorage и отображем основной HTML5 файл в элементе управления WebBrowser. Работающее приложение выглядит следующим образом:
Мне кажется, что это отличный пример. Спасибо за него Косте.
Если нам уже мало чистого HTML5 + JS и мы хотим выйти за пределы элемента управления браузера, можно говорить о третьем типе HTML5 приложений для Windows Phone, которые использует не только локальный контент на телефоне, но и выходят, за пределы элемента управления WebBrowser, используя возможности платформы (например, акселерометр). Мы можем сделать подобное приложение “с нуля “ самостоятельно, используя возможность оповестить платформу о происходящем (window.external.notify/ScriptNotify) или вызвать JS скрипт из C# кода (WebBrowser.InvokeScript). Но лучше воспользоваться уже существующим решнием – PhoneGap.
На момент написания статьи была доступна версия 1.3. Скачать его можно на сайте http://phonegap.com/, там же есть документация. После скачивания, распакуйте архив и из папки Windows Phone скопируйте файлы PhoneGapCustom.zip и PhoneGapStarter.zip в папку Visual Studio 2010\Templates\, выгрузите все запущенные версии Visual Studio. После запуска Visual Studio появится новый тип проектов:
Cоздадим проект на основе PhoneGapStarter. Псмотрим на состав проекта:
Собственно сам PhoneGap – это JS файл phonegap-1.3.0.js и ответная ему .NET CF библиотека WP7GapClassLib. Кроме этого, вместо встроенного элемента WebBrowser используется элемент управления PGView, который ��аходится в той же WP7GapClassLib.
Вспомогательный файл ManifestProcessor.js используется, чтобы перед сборкой приложения обновить файл GapSourceDictionary.xml, который содержит список файлов контента (директория www проекта), которые в дальнейшем, как мы уже знаем из предыдущего примера, необходимо будет развернуть в Isolated Storage.
Напишем простое приложение, которое будет считывать данные акселерометра и отображать его на странице. Для этого заменим содержимое тега <body> файла index.html на следующее:
<h1>Accelerometer sample</h1> <div id="valueX"></div> <div id="valueY"></div> <div id="valueZ"></div>
Теперь нужно заменить скрипт на этой странице на свой, который работает с акселерометром (код взят из статьи David Rousset, Tutorial: how to create HTML5 applications on Windows Phone thanks to PhoneGap):
<script type="text/javascript">
document.addEventListener("deviceready", onDeviceReady, false);
// variable to output the current x, y & z values of the accelerometer var valueX; var valueY; var valueZ;
// when PhoneGap tells us everything is ready, start watching the accelerometer function onDeviceReady() { valueX = document.getElementById("valueX"); valueY = document.getElementById("valueY"); valueZ = document.getElementById("valueZ"); startWatch(); }
// start monitoring the state of the accelerometer function startWatch() { var options = { frequency: 500 }; navigator.accelerometer.watchAcceleration(onSuccess, onError, options); }
// if the z-axis has moved outside of our sensitivity threshold, move the aarvark's head in the appropriate direction function onSuccess(acceleration) { valueX.innerHTML = "X: " + acceleration.x; valueY.innerHTML = "Y: " + acceleration.y; valueZ.innerHTML = "Z: " + acceleration.z; }
function onError() {
alert('onError!');
}
</script>
Собтвенно, единсвенное, что требует объяснения – это объект navigator, который нам позволил подключиться к акселерометру. Это собственно часть PhoneGap. Подробнее о PhoneGap API можно прочитать на сайте http://phonegap.com/.
Остаётся только запустить приложение и удостовериться, что оно работает:
В качестве примера возможностей платформы, хочу привести снимок экрана игры на HTML5, из той же статьи David Rousset:
Теперь, когда вы знаете практически всё о разработке на HTML5 + JS под Windows Phone – вам будет чем заняться долгими новогодними каникулами.
Проекты примеров:
MyYandex
SVG Demo
PhoneGap Accelerometr Sample