Начинаем цикл публикаций, призванных помочь начинающим программистам в создании контекстно-зависимых приложений для Windows 7. Об этом много говорили на PDC да и мы неоднократно писали о этом новом классе приложений. Пользователи Windows 7 build 6801 могли заметить появление в гаджете погоды опции автоматического определения местоположения компьютера. И сегодня у вас появилась уникальная возможность попробовать себя в создании подобных приложений.
Я решил немного поиграть с Windows Location Platform, входящей в состав Windows 7 build 6801, которая была роздана посетителям конференции PDC 2008, и решил попробовать создать свое WPF-приложение, которое показывало бы карту Virtual Earth и мое местоположение на ней.
В сборке 6801 всем СОМ-классы Location API обозначены как ThreadingModel = Free (MTA). Поэтому, для того чтобы вызвать их из другой потоковой модели (STA), придется прибегнуть к промежуточным классам. Похоже, что сборка, генерируемая VS (LocationDispLib.dll), не включает в себя эти самые промежуточные классы для COM-распределения. Таким образом, для получения доступа к Location API нам необходимо использовать другой поток, который позволит использовать эти данные в нашем WPF-приложении.
Что нужно для создания приложения


Сценарии, которые позволяет реализовать приложение
  • Показывать карту на базе местоположения пользователя
  • Показывать схему движения, автоматически обновляя данные
  • Очищать карту

Запускаем приложение:

Нажимаем на кнопку Show Me on Map, Windows Location Platform получает координаты, приложение отмечает текущее местоположение на карте.

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

Как это реализовано
Windows Location Platform API представлены COM- и Win32-объектами. Для упрощения программирования мы воспользуемся COM-версией API, которая внедрена в файл LocationDisp.dll в папке %Windir%\System32\ (в x86-версии). Для использования данного API нам необходимо добавить в проект ссылку на эту библиотеку.
1. Так как API запускаются в режиме MTA, а основное WPF-приложение в STA, нам нужно использовать отдельный поток для того, чтобы наше приложение могло получить информацию от API.

  • BackgroundWorker m_worker предназначен для обработки изменения местоположения, а второй BackgroundWorker m_workerStatus добавляем в определение Window1:

    Code:

    BackgroundWorker m_worker = new BackgroundWorker();
    BackgroundWorker m_workerStatus = new
    BackgroundWorker();
    private LatLongReport m_report = new LatLongReport();
    private LocationPlatformStatus m_status = new LocationPlatformStatus();
    DispatcherTimer m_timerAutoTracking = new DispatcherTimer();
    DispatcherTimer m_timerLocPlatformStatus = new DispatcherTimer();
    bool m_useAutoTracking = false;

    Таймеры будут использоваться для обработки изменения местоположения (m_timerAutoTracking). LatLongReport - .NET упаковщик для класса DispLatLongReport из Interop.LocationDisp.dll, которая была создана из LocationDisp.dll силами Visual Studio. Она необходима для того, чтобы несколько упростить COM-функцию, вызываемую в .NET-приложении.

  • В конструкторе Window1 мы инициализируем и фоновые исполнители, и таймеры:

    Code:

    public Window1()
            {
                InitializeComponent();
                m_worker.DoWork += new DoWorkEventHandler(worker_DoWork);
                m_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
                m_workerStatus.DoWork += new DoWorkEventHandler(m_workerStatus_DoWork);
                m_workerStatus.RunWorkerCompleted += new RunWorkerCompletedEventHandler
    (m_workerStatus_RunWorkerCompleted);
                m_timerAutoTracking.Interval = new TimeSpan(0, 0, 0, 0, 1000);
                m_timerAutoTracking.Tick += new EventHandler(m_timer_Tick);
                m_timerLocPlatformStatus.Interval = new TimeSpan(0, 0, 0, 0, 1000);
                m_timerLocPlatformStatus.Tick += new EventHandler(m_timerLocPlatformStatus_Tick);
                // enabling Status Timer
                m_timerLocPlatformStatus.Start();
            }

    Здесь мы просто инициализировали асинхронные обработчики событий DoWork и RunWorkerCompleted для обоих исполнителей Background Worker, настров интервал в 1 секунду для таймеров и обработчиков их событий.

  • Вызов Windows Location Platform API
    Нажав кнопку Show Me On Map, пользователь инициализирует в приложении соответствующую команду и она запускает асинхронный исполнитель Background Worker m_worker:

    Code:

    public void ShowMeOnMapCommand_Executed(object sender, ExecutedRoutedEventArgs e)
    {
    try
    { m_worker.RunWorkerAsync();
    } catch
    {
    // some times it can't do the job at 1 sec
    }
    }

    Тут, однако, есть маленькая проблема. Так как мы не подписываемся на события через Location API, но при этом вызываем их, используя таймеры, исполнители Background Worker могут столкнуться с проблемой, при которой они не успели завершить свою работу. Чтобы не усложнять приложение, я решил добавить здесь блок.
    RunWorkerAsync() включает событие DoWork() из фонового исполнителя m_worker:

    Code:

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
    LatLongReportFactoryClass factory = new LatLongReportFactoryClass();
    m_report = new LatLongReport(factory.LatLongReport);
    }

    Именно здесь происходит реальное обращение к Windows Location API. То есть нам необходимо сослаться на LatLongReportFactoryClass и получить от него LatLongReport, который и будет использоваться в приложении. В действительности, первое, что нам необходимо сделать - проверить поле LatLongFactoryClass.Status, чтобы убедится, что сенсоры доступны и работают.

  • Когда фоновый исполнитель m_worker завершит получение LatLongReport, нам нужно создать на карте новый PushPin, дать ему текущие координаты и показать на карте.

    Code:

    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
    if (e.Cancelled)
    {
    //Cancelled
    }
    else if (e.Error != null)
    {
    //Exception Thrown }
    else
    {
    //Completed
    PushPin pushPin = new PushPin();
    pushPin.Latitude = m_report.Latitude;
    pushPin.Longitude = m_report.Longitude; t
    his.VirtualEarth.PushPins.Add(pushPin);
    pushPin.CenterInMap();
    }
    }

    В данном методе мы центрируем карту, чтобы созданная отметка показывалась по центру Virtual Earth. В случае если сенсоры присутствуют, включены и доступны, нажатие кнопки Show Me on Map покажет в центре карты Virtual Earth нашу позицию.


2. Для реализации автоматического обновления местоположения мы используем таймер Dispatcher Timer m_timer, который автоматически будет запускать Background Worker m_worker для получения информации о местоположении.
Для этого мы просто добавим в обработчик вызова кнопки Show/Stop showing My Trip on Map Automatically вызов Start/Stop m_timer, а для отметки на карте - m_worker.RunWorkerA.
3. Чтобы очистить карту, требуется вызвать метод “Clear” из коллекции контролов “PushPins”.
Теперь вы знаете, как вызывать Windows 7 Location Platform API с целью получить информацию о местоположении пользователя, и использовать ее в WPF-приложении.
Вот, собственно, и все. В следующий раз мы поговорим о том, как проверить статус платформы, о том, что делать в случае, если доступ запрещен, и что делать с провайдером, используемым по умолчанию.