The third application in Chris Craft’s 30 Days of .NET is a GPS based Compass.
Interacting with GPS Devices
Windows Mobile 5.0 and above provides a unified API called the GPS Intermediate Driver that enables multiple applications to concurrently share a single GPS device. This API is highlevel and abstracts away the need to manually parse NEMA sentences etc.
To create a connection to the GPS Device we can include gpsapi.h and make use of the GPSOpenDevice API.
// Open a connection to the GPS Intermediate Driver HANDLE hGPS = GPSOpenDevice(NULL, NULL, NULL, 0);
// Close our connection to the GPS Intermdiate Driver GPSCloseHandle(hGPS);
Once a connection has been established we can call GPSGetPosition or GPSGetDeviceState to retrieve location and GPS device status information respectively. For example we can query the current location using a code sample such as the following:
GPS_POSITION pos; // Setup the data structure memset(&pos, 0, sizeof(pos)); pos.dwVersion = GPS_VERSION_CURRENT; pos.dwSize = sizeof(pos); // Ask the GPS intermediate driver to // fill out the structure. GPSGetPosition(hGPS, &pos, 500000, 0);
One thing to note is that the GPS_POSITION data structure contains a field named dwValidFlags. This field is a bitmask that tells you which fields contain valid data. For example if the dwValidFlags field does not contain the GPS_VALID_LATITUDE flag it means you can not rely on the dblLatitude field (perhaps a location fix has not been made yet).
The GPS Compass application makes use of an optional feature. By passing in two event handles to GPSOpenDevice we do not need to periodically call GPSGetPosition to gather new position updates. Instead we can wait until the GPS Intermediate Driver signals our events and only then call GPSGetPosition, safe in the knowledge that it will definatly return different results than the last call. This helps us be slightly more efficient with respect to battery lifetime due to less CPU load.
By default we do not have access to a nice string class such as System.String. A string within a C or C++ application is essentially a fixed size character array that uses a NULL character as a terminator.
To copy a string from one string buffer into another we can make use of a function called wcscpy.
// Allocate a string buffer capable of storing // 31 characters (the 32nd element is used to store a NULL // terminator) and then copy the string "Hello World" into // the buffer WCHAR szMyBuffer; wcscpy(szMyBuffer, L"Hello World");
Likewise to append a string onto the end of another we can make use of the wcscat function as follows:
// Add the string "Goodbye World" onto the end // of the string already in 'szMyBuffer' wcscat(szMyBuffer, L" Goodbye World");
While using these functions memory management is much more explicit than it is in C#. For example when you allocate a string buffer you must specify its maximum length and there is no bounds checking to ensure you don’t attempt to store 200 characters in a 100 character buffer. This leads to so called buffer overrun errors if you are not careful.
You may be more familiar with string functions called strcpy (string copy) and strcat (string concatenate). wcscpy and wcscat are essentially identical except that they work on “wide characters” (UTF16 encoded data) instead of ANSI.
Moving a Window
To move or resize a control (or any window for that matter) you can make use of the MoveWindow function.
// Move and resize the window 'hWnd' MoveWindow(hWnd, // the window to move 10, // new x location 20, // new y location 30, // new width 40); // new height
One problem with using the MoveWindow function is that you always need to specify both the window’s new location and size. Sometimes it can be useful to use a different function called SetWindowPos. This function accepts a few additional parameters, the most important one being a flags argument that enables you to specify which parameters should be taken notice of. For example to move a window without resizing it you may use a code snippet like the following:
SetWindowPos(hWnd, NULL, // hWndInsertAfter 10, // new x 20, // new y 0, // new width 0, // new height SWP_NOSIZE); // flags
The SWP_NOSIZE flag tells the SetWindowPos function that it should ignore the width and height parameters and leave the window at its current size. If you wanted to resize a window yet keep it’s current location you could use a similar SWP_NOMOVE flag.
Creating a Menu
This is the first sample application that has required the use of a menu. A menu is designed in the resource editor and loaded by the SHCreateMenuBar API. The call to SHCreateMenuBar is typically placed in the handler for the WM_INITDIALOG window message. This is convenient since this window message is received just before the dialog is made visible.
case WM_INITDIALOG: // Configure the menu SHMENUBARINFO mbi; memset(&mbi, 0, sizeof(mbi)); mbi.cbSize = sizeof(mbi); mbi.hWndParent = hWnd; // the dialog's handle mbi.nToolBarId = IDR_MENU; // the menu resource id mbi.hInstRes = GetModuleHandle(NULL); mbi.dwFlags = SHCMBF_HMENU; // Create the menu SHCreateMenuBar(&mbi); break;
Once a menu is visible there is a range of menu APIs that enable you to interact with the menu. For example to enable or disable a particular menu item you can make use of the EnableMenuItem API as shown below:
// Disable a menu item with id 'IDC_SOME_ITEM' EnableMenuItem(hMenu, IDC_SOME_ITEM, MF_BYCOMMAND | MF_GRAYED); // Enable a menu item with id 'IDC_SOME_ITEM' EnableMenuItem(hMenu, IDC_SOME_ITEM, MF_BYCOMMAND | MF_ENABLED);
Notice that the EnableMenuItem function is used to both enable and disable menu items, The MF_GRAYED or MF_ENABLED flags passed as part of the last argument determines which action you want to perform.
A handy place to put code that configures the state of menu items is the message handler for the WM_INITMENUPOPUP window message. This message is sent to the menu’s owner just before a menu becomes visible.
Menu items behave very similiar to buttons and send a WM_COMMAND window message when they are selected by the user. This fact can be used to your advantage. If you want a button and menu item to both perform the same task you can assign them the same command ID.