Sample Location-Aware WPF Application

Hi there, started playing with Windows Location Platform bits in Windows 7 M3 that was given on PDC 2008 to all attendees, and decided to quickly write a WPF app that shows the Virtual Earth map and my location on it.

Quick Facts

In M3 build, all Location API coclasses are marked with ThreadingModel = Free (MTA). So in order to call them from a different threading model (STA), you will have to call using proxy stubs. Looks like the interop assembly generated by VS (LocationDispLib.dll) does not include the proxy stubs for COM apartment marshaling.

Thus we will need to access Location APIs in another thread to use its data in our WPF application.

What I used to create sample application

Scenarios this application enables

  • Show me on the map based on my location
  • Show me my current trip based on my location with automatic changes
  • Clear map

Screenshots

image

app started

image

button “Show Me on Map” pressed, and from Windows Location Platform app got my coordinates, the push pin shows my current location

image

app tracks my movement on the map, pinning push pins each second

How it is implemented

Windows Location Platform APIs are provided as COM-based and native Win32-based methods. To make our life easy we’ll use COM-based APIs that are buried in LocationDisp.dll that is located in %Windir%\System32\LocationDisp.dll (for x86 machines). To use these APIs we need to add this library as a reference to our project.

1. As LocationApis are in MTA apartment state, and WPF App is in STA apartment state, we need to use another thread inside our WPF application to call LocationApis and retrieve needed information.

  • For that, the BackgroundWorker m_worker to process location changes, and second BackgroundWorker m_workerStatus were added to Window1 definition:
 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;

timers will be used to process location changes (m_timerAutoTracking) and to process Windows Location Platform changes. LatLongReport is a .NET wrapper for DispLatLongReport class from Interop.LocationDisp.dll that was generated from LocationDisp.dll by Visual Studio, it is needed to make try..catch inside this class and hide complexity of COM-functionality calling inside .NET application.

  • In Window1 constructor we initialize both background workers and timers:
 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();
        }

Here we just initialize DoWork and RunWorkerCompleted async event handlers for both Background Workers, set up 1 sec tick interval for both Timers and event handlers for them.

  • Calling Windows Location Platform APIs

by pressing button “Show Me On Map”, user invokes a corresponding command in the app, and it runs the Background Worker m_worker async:

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

there is a small issue. As we are not subscribing to events via LocationApis but are calling them using timers, sometimes background workers might be in the situation when worker just didn’t finish his work yet, thus to not make app complex, I added try..catch block here

the RunWorkerAsync() invokes DoWork() event of Background Worker m_worker:

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

this is where the call to real Windows Location APIs is processed. I.e., we need to instantiate LatLongReportFactoryClass and get it’s LatLongReport to use in our application.

In real life, however, we need to first of all check the LatLongFactoryClass.Status field to be sure that both the Platform and location sensor(s) are available and are up and running.

  • When our Background Worker m_worker will finish retrieving LatLongReport, we will need to create a new PushPin on the map, give it current coordinates and show the PushPin on the map:
 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;

               this.VirtualEarth.PushPins.Add(pushPin);
               pushPin.CenterInMap();
           }

       }

in this method as you can see we also Center the map so that our newly created push pin was shown in the center of the Virtual Earth.

Now, if Location Platform is present, and location sensor is present, enabled and running, pressing on “Show Me on Map” will show on the Virtual Earth map a candy red Push Pin in the center of the map showing our position on the map.

2. To enable automatic updates of the trip, we now will use the Dispatcher Timer m_timer to enable automatic start of new Background Worker m_worker instances to retrieve our latest position on the map and show it on it.

For that, we’ll just add to “Show/Stop showing My Trip on Map Automatically” ribbon button’s handler call to Start/Stop m_timer, and for it’s Tick event we’ll call m_worker.RunWorkerAsync(), this will lead us to get added new push pin on the Virtual Earth Map automatically.

3. To clear map, we just need to call VirtualEarth control’s collection “PushPins” method “Clear”.

So, concluding, now you know how to call Windows 7 Location Platform APIs to retrieve user’s location and use this information inside WPF application.

In next series we’ll look into how to check the platform status, what to do if Access is Denied and how to use Default Location Provider.

Stay tuned!