Знакомство начнём с элемента управления Map, который предоставляет интерфейс к картам на Windows Phone. Давайте создадим новый проект из шаблона Windows Phone Application и назовём его ExploreMapControl.
После того, как проект будет создан, посмотрите, на какие библиотеки он ссылается.
Далее, разверните Toolbox, если он свёрнут, и перетяните элемент управления Map в дизайнер интерфейса приложения.
Обратите внимание, что теперь проект ссылается на Microsoft.Phone.Controls.Map.
Двойным щелчком перейдем к странице MainPage.xaml и посмотрим, что изменилось в XAML коде. Добавился элемент управления Map из пространства имён my:
<my:Map Height="50" HorizontalAlignment="Left" Margin="201,198,0,0" Name="map1" VerticalAlignment="Top" Width="100" />
Посмотрев на заголовок XAML документа можно увидеть, что это за пространство имён:
xmlns:my="clr-namespace:Microsoft.Phone.Controls.Maps;assembly=Microsoft.Phone.Controls.Maps"
Давайте заменим my на map, чтобы название пространства имён соответствовало его содержанию:
xmlns:map="clr-namespace:Microsoft.Phone.Controls.Maps;assembly=Microsoft.Phone.Controls.Maps"
<map:Map Height="50" HorizontalAlignment="Left" Margin="201,198,0,0" Name="map1" VerticalAlignment="Top" Width="100" />
Отредактируем XAML код элемента управления Map или воспользуемся панелью Properites так, чтобы элемент занимал большую часть свободного пространства и переименуем элемент в MyMap:
<map:Map Name="MyMap"/>
Запустите приложение (F5) и посмотрите, как выглядит элемент управления во время исполнения.
Обратили внимание на белый баннер в центре экрана, который говорит, что у нас неправильные авторизационные данные? Это потому что этот элемент управления использует сервис карт от Bing и для его использования требуется регистрация. Зарегистрироваться и получить ключ можно на портале Bing Maps: http://www.bingmapsportal.com.
В завершение регистрации разработчик получает строковый ключ, который надо указать в свойстве CredentialsProvider элмента управления, также его можно вынести в ресурсы или данные.
Добавим простые элементы управления картой: уменьшение/увеличение масштаба, смена режима отображения карты.
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<map:Map Name="MyMap">
<Button Name="ZoomIn" Content="+" HorizontalAlignment="Center" VerticalAlignment="Bottom" Height="60" Width="60" Margin="-100,0,0,-5" Click="ZoomIn_Click" FontWeight="Bold" Padding="0,-9,0,0"/>
<Button Name="ZoomOut" Content="-" HorizontalAlignment="Center" VerticalAlignment="Bottom" Height="60" Width="60" Margin="100,0,0,-5" Click="ZoomOut_Click" FontWeight="Bold" Padding="0,-9,0,0" />
<Button Name="LayoutChange" Content="L" HorizontalAlignment="Center" VerticalAlignment="Bottom" Height="60" Width="60" Margin="0,0,0,-5" FontWeight="Bold" Padding="0,-9,0,0" Click="LayoutChange_Click"/>
</map:Map>
</Grid>
И обработаем эти события в коде приложения.
private void ZoomIn_Click(object sender, RoutedEventArgs e)
{
MyMap.ZoomLevel += 1;
}
private void ZoomOut_Click(object sender, RoutedEventArgs e)
{
MyMap.ZoomLevel -= 1;
}
private void LayoutChange_Click(object sender, RoutedEventArgs e)
{
if (MyMap.Mode is RoadMode)
{
MyMap.Mode = new AerialMode(true);
}
else
{
MyMap.Mode = new RoadMode();
}
}
Не забудьте добавить в блок using следующую директиву:
using Microsoft.Phone.Controls.Maps;
Запустите приложение (F5) и проверьте, что наши элементы управления работают, как предполагается. В соответствии с Metro-дизайном панель с кнопками внизу окна приложения мы должны были бы оформить в виде Application Bar, только в целях упрощения примера мы используем более простой вариант. В качестве самостоятельного упражнения можете попробовать убрать кнопки, раскоментировать пример кода Application Bar в XAML файле и переделать приложение в соответствии со стилем Metro.
Теперь перейдём к геолокационным сервисам, доступным на телефоне. Сервис предоставляет информацию, используя комбинацию информации получаемой от Wi-Fi, сотовой связи и данных от GPS приёмника. Добавим теперь в наще приложение возможности предоставляемые сервисами геолокации.
Для начала, добавим в блок using следующую директиву:
using Microsoft.Devices.Sensors;
Теперь мы готовы работать с сервисами локаций/местоположения.
Сначала напишем простое дополнение к нашей программе, которое будет центрировать карту в соответствии с гелолокационными данными, полученными от сервисов.
Добавим в класс определение переменной типа GeoCoordinateWatcher, которая позволит нам инициализировать сервисы геолокации и полчать от них данные.
private GeoCoordinateWatcher myGeoWatcher;
В конструктор класса, сразу же после кода относящегося к акселерометру добавим код инициализации и регистрации на события изменения статуса сервисов (они могут быть недоступны, могут быть не готовы и т.д.) и события изменения положения.
yGeoWatcher = new GeoCoordinateWatcher();
myGeoWatcher.MovementThreshold = 100.0f;
myGeoWatcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(myGeoWatcher_StatusChanged);
myGeoWatcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(myGeoWatcher_PositionChanged);
Хорошее приложение должно правильно обрабатывать статусы геосервисов, т.к. они не всегда могут выдавать данные и могут тратить достаточно большое время на инициализацию. Для начала мы просто оставим обработчик пустым, так как тестировать приложением мы будем на эмуляторе, и там эти проблемы отсутствуют.
Также правильнее будет поместить запуск сервиса геолокации в отдельный поток, чтобы не тормозить загрузку приложения, в нашем первом варианте приложения, с учётом использования эмулятора мы пока будем запускать сервис прямо в конструкторе класса:
myGeoWatcher.TryStart(false, TimeSpan.FromSeconds(60));
Если Visual Studio автоматически сгенерировала нам обработчики событий StatusChanged и PositionChanged, закомментируйте или сотрите код в этих методах, вызывающий исключение NotImplemented:
throw new NotImplementedException();
В обработчик события PositionChanged добавьте код, центрирующий карту при изменении позиции:
void myGeoWatcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
MyMap.Center = e.Position.Location;
}
Запустите приложение (F5) и воспользуйтесь возможностями эмулятора по эмуляции геолкационных данных, чтобы проверить работу программы. Увеличьте масштаб так, чтобы убедиться, что позиционирование происходи правильно.
Следующим шагом, улучшения нашей программы может стать запуск сервисов в другом потоке, добавление строки статуса геолокационных данных, а также создание на карте точки, отмечающей наше местоположение.
Для использования потоков, добавим в блок using следующую директиву:
using System.Threading;
В конструкторе до запуска сервисов добавим код:
new Thread(startMyGeoWotcher).Start();
После этого создадим функцию, не принимающую и не возвращающую значений, с именем startMyGeoWotcher и перенесём в неё код запуска сервисов:
void startMyGeoWotcher()
{
myGeoWatcher.TryStart(false, TimeSpan.FromSeconds(60));
}
Теперь добавим элемент управление TextBlock для отображения статуса сервисов геолокации
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel>
<TextBlock Name="GeoStatus" HorizontalAlignment="Center" VerticalAlignment="Top" Text="Geo Status .." />
<map:Map Name="MyMap" Height="580">
<Button Name="ZoomIn" Content="+" HorizontalAlignment="Center" VerticalAlignment="Bottom" Height="60" Width="60" Margin="-100,0,0,-5" Click="ZoomIn_Click" FontWeight="Bold" Padding="0,-9,0,0"/>
<Button Name="ZoomOut" Content="-" HorizontalAlignment="Center" VerticalAlignment="Bottom" Height="60" Width="60" Margin="100,0,0,-5" Click="ZoomOut_Click" FontWeight="Bold" Padding="0,-9,0,0" />
<Button Name="LayoutChange" Content="L" HorizontalAlignment="Center" VerticalAlignment="Bottom" Height="60" Width="60" Margin="0,0,0,-5" FontWeight="Bold" Padding="0,-9,0,0" Click="LayoutChange_Click"/>
</map:Map>
</StackPanel>
</Grid>
И допишем вывод статусов в обработчик StatusChanged:
void myGeoWatcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
{
switch (e.Status)
{
case GeoPositionStatus.Disabled:
if (myGeoWatcher.Permission == GeoPositionPermission.Denied)
{
GeoStatus.Text = "Сервис выключен";
}
else
{
GeoStatus.Text = "На этом устройстве сервис недоступен";
}
break;
case GeoPositionStatus.Initializing:
GeoStatus.Text = "Сервис инициализируется";
break;
case GeoPositionStatus.NoData:
GeoStatus.Text = "Данные о месположении недоступны";
break;
case GeoPositionStatus.Ready:
GeoStatus.Text = "Данные о местоположении доступны";
break;
}
}
Наконец, в обработчик события изменения позиции добавим установку точки на карте.
В класс добавим переменную типа Pushpin:
private Pushpin myPushpin;
Создадим её в конструкторе класса:
myPushpin = new Pushpin();
В обработчике изменения позиции установим её на текущую позицию и добавим на карту, если её там нет:
myPushpin.Location = e.Position.Location;
if (!MyMap.Children.Contains(myPushpin)) MyMap.Children.Add(myPushpin);
Запустите приложение (F5) и воспользуйтесь возможностями эмулятора по эмуляции геолкационных данных, чтобы проверить работу программы. Увеличьте масштаб так, чтобы убедиться, что позиционирование и установка точки происходит правильно; также проверьте отображаемый статус сервисов геолокации.
UPD: Как правильно написал в коментариях andrew_kane. Работать с потоками из пула — это более правльная практика.
Ниже привожу код, который необходимо поменять, чтобы работать с потоками из пула, а не вызывать
new Thread(...).Start();
Вместо
new Thread(startMyGeoWotcher).Start();
нужно написать следующий код:
ThreadPool.QueueUserWorkItem(startMyGeoWotcher, myGeoWatcher);
Мы вынуждены передвать объект в метод, потому что использование пула требует статическогом метода и у нас есть 2 варианта, либо делать объект статическим, либо передать его в качестве параметра.
Теперь нужно переписать метод
startMyGeoWotcher
Он должен теперь выглядеть следующим образом:static void startMyGeoWotcher(object GeoWatcher)
{
((GeoCoordinateWatcher)GeoWatcher).TryStart(false, TimeSpan.FromSeconds(60));
}
Полезные ссылки:
Центр разработки Windows Phone на MSDN
Windows Phone SDK 7.1
Форумы по разработке под Windows Phone на русском языке