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:
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.
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.
August 7, 2009 at 7:35 am |
Thanks for a great example of using a serial port in WPF. Saved me a lot of experimenting.
August 8, 2009 at 9:08 pm |
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
August 8, 2009 at 11:06 pm |
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.