Displaying GPS data in WPF

One of the great things about Windows Presentation Foundation (WPF) is that it uses Extensible Application Markup Language (XAML). XAML is an XML based language that graphic artists can easily create using Microsoft Expression Blend. A programmer can then write the program logic in any .NET language such as C# or Visual Basic.

A big advantage to this separation beyond separation of tasks during initial development is that a non-programmer can change what the application looks like without knowing a programming language; only the XAML needs to be modified.

I built a simple test application in WPF using Visual Studio 2008 that reads data from a GPS and displays the position in latitude and longitude. Also displayed is the date and time which is updated from a timer. A screen shot of the application appears below:

image

This solution is rather simple and its structure is shown below. The two items of interest are Window1.xaml and NMEAParse.cs As an aside, Window1.xaml.cs is just as generated by Visual Studio, it contains no additional code.

image

Here is Window1.xaml:
___________________________________________________________

<Window x:Class="NMEATest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:NMEATest="clr-namespace:NMEATest"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <StackPanel Orientation="Vertical">
            <TextBox Height="34" Width="Auto" Text="{Binding Mode=OneWay, Path=FormattedDateTime}"
                     TextWrapping="Wrap" x:Name="DispValue"  Foreground="#FFE61919" FontSize="18">
                <TextBox.DataContext>
                    <NMEATest:UpdatingDtTm/>
                </TextBox.DataContext>
            </TextBox>

            <TextBox x:Name="lat"  Text="{Binding Mode=OneWay, Path=GPSPosition}">
                <TextBox.DataContext>
                    <NMEATest:UpdateGPS/>
                </TextBox.DataContext>
            </TextBox>
        </StackPanel>
    </Grid>
</Window>

________________________________________________________

This interface is rather simple, consisting primarily of two text boxes, one for the date-time and the other for the position. The key to getting the data into these textboxes is data binding. In this case the binding mode is set to OneWay since we are only displaying data, not updating it. The Path specifies the variable name being referenced (FormattedDateTime & GPSPosition) while the DataContext specifies the namespace and class name (NMEATest:UpdatingDtTm & NMEATest:UpdateGPS).

Below is NMEAParse.cs, the C# code that provides the data that is displayed in the XAML. Since this code is in a different class, it will run on a separate thread and thus the user interface will remain responsive even if the c# code is heavily loaded.

The key to implementing the data binding is to implement the

INotifyPropertyChanged interface, providing a PropertyChangedEventHandler and then calling PropertyChanged when there is new data to be displayed.
________________________________________________________

using System;
using System.IO.Ports;
using System.ComponentModel;
using System.Windows.Threading;

namespace NMEATest
{
    public class UpdateGPS : INotifyPropertyChanged
    {
            private string _GPSPosition = "lost";
            private bool bPortOpen = false;
            private SerialPort port;
            private string serBuff = "";

            #region INotifyPropertyChanged Members
            public event PropertyChangedEventHandler PropertyChanged;
            #endregion

            public string GPSPosition
            {
                get { return _GPSPosition; }
            }

            public UpdateGPS()
            {
                port = new SerialPort("COM5", 4800);
                port.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
                port.Open();
                bPortOpen = true;
                //port.Close();
            }
            void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                bool newData = false;
                serBuff = serBuff + port.ReadExisting();
                while (serBuff.Contains("\r\n"))
                {
                    int i = serBuff.IndexOf("\r\n");
                    string line = serBuff.Substring(0,i);
                    if (‘$’ == line[0])
                    {
                        string[] tok = line.Split(‘,’);
                        switch (tok[0])
                        {
                            case "$GPAPB":      // Auto Pilot B sentence
                                break;
                            case "$GPBOD":      // Bearing Origin to Destination
                                break;
                            case "$GPBWC":      // Bearing using Great Circle route
                                break;
                            case "$GPGGA":      //  Fix information
                                if (tok[1].Length > 0)
                                {
                                    _GPSPosition =
                                        tok[2].Substring(0,2) + " " + tok[2].Substring(2, tok[2].Length-2) + " " +tok[3] +
                                         " – " +
                                        tok[4].Substring(0,3) + " " + tok[4].Substring(3, tok[4].Length-3) +
                                        " " + tok[5];
                                }
                                else
                                {
                                    _GPSPosition = line;
                                }
                                newData = true;
                                break;
                            case "$GPGLL":      // Lat/Lon data
                                break;
                            case "$GPGSA":      //  Overall Satellite data
                                break;
                            case "$GPGSV":      // Detailed Satellite data
                                //_GPSPosition = line;
                                //newData = true;
                                break;
                            case "$GPRMB":      // recommended navigation data for gps
                                break;
                            case "$GPRMC":      // recommended minimum data for gps
                                break;
                            case "$GPRTE":      // route message
                                break;
                            case "$GPVTG":      // Vector track an Speed over the Ground
                                break;
                            case "$GPXTE":      // measured cross track error
                                break;
                            case "$PGRME":     
                                break;
                            case "$PGRMM":
                                break;
                            case "$PGRMZ":
                                break;
                            default:
                                //string un = line;
                                //Console.WriteLine(un);
                                break;
                        }
                    }
                    serBuff = serBuff.Substring(i+2);
                }

                //_GPSPosition = serBuff;
                if (newData)
                {
                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("GPSPosition"));
                    }
                }
            }
   }

        public class UpdatingDtTm : INotifyPropertyChanged
        {
            private string _someText = "Foo";
            private DispatcherTimer tmr = new DispatcherTimer();

            #region INotifyPropertyChanged Members
            public event PropertyChangedEventHandler PropertyChanged;
            #endregion

            public string FormattedDateTime
            {
                get { return _someText; }
            }

            public UpdatingDtTm()
            {
                tmr.Interval = TimeSpan.FromMilliseconds(10);
                tmr.Tick += new EventHandler(tmr_Tick);
                tmr.Start();
            }

            void tmr_Tick(object sender, EventArgs e)
            {
                _someText = DateTime.Now.ToString();
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("FormattedDateTime"));
            }
        }
}

_________________________________________________________________

If you are not familiar with WPF development, you may be surprised if you open this project in Visual Studio. The design interface will update the data in real-time in design mode; even before you run the program! This is to be expected.

One other thing, I used a Microsoft GPS puck on a USB port for testing. This is the one that ships with some versions of Streets & Trips. On my computer it installed on Comm port 5 and runs on 4800 baud. If your GPS uses a different port or speed, you will need to change the line that opens the serial port. If you don’t have a GPS don’t worry, the date and time will still update.

Advertisements

3 Responses to “Displaying GPS data in WPF”

  1. Pat Says:

    Thanks for a great example of using a serial port in WPF. Saved me a lot of experimenting.

  2. Todd Says:

    This example will help me with a task I am trying to accomplish. I have integrated it pretty much as is into my app but get an error “Could not create an instance of UpdateGPS”. The compiler finds the UpdatingDtTm instance just fine so I must be missing something else, perhaps in the project setup. Could you please provide a link to the project so I can make that work first, then move the code to my app.
    Thanks,
    Todd

  3. Todd Says:

    Todd again. I went ahead and created the project and got the source code to compile and run. I don’t have a GPS connected so it just prints “lost”. It would comile and run as long as the line
    port = new SerialPort(“COM5”, 4800);
    was not modified to point to something valid. Once I did that, for example
    port = new SerialPort(“COM2”, 57600, Parity.None, 8, StopBits.One);
    Then I got the error above, or a run time error that complains about an invocation error when starting up.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: