30 Days of Windows Mobile – Day 04: Mileage Tracker

Screenshot of Mileage Tracker applicationWith petrol prices reaching record highs across the globe people are starting to pay more attention to fuel efficiency.

Day 4 of Chris Craft’s 30 Days of .NET introduces a Mileage Tracker application to help you check the efficiency of your vehicle and perhaps driving habits!

This blog post covers a number of aspects of porting the original C# source code into C++.

Transparent Labels

Windows CE does not support windows with true transparency. Instead we have to fake it. A common technique is to revert to manually drawing the elements of the user interface which require transparency over top of the background of their parent control.

// Draw the string "Hello World" in the rectangle
// (x1=10, y1=20) - (x2=60, y2=40) using a transparent
// background.
RECT rcBounds = {10, 20, 60, 40};
WCHAR szBuffer[] = L"Hello World";
 
SetBkMode(hDC, TRANSPARENT);
DrawText(hDC, szBuffer, wcslen(szBuffer), &rcCtrl, DT_LEFT);

Getting a little bit more advanced (with better design time support) the .NET Compact Framework solution suggested by Alex Yakhnin is equally implementable in native code and is the technique used in the source code available for download.

Gradient Background

The original C# application used a bitmap for a background. For this conversion I decided to demonstrate an additional API by implementing the background programatically. The solid colour part at the top is easily implemented by calling FillRect within the WM_PAINT message handler. The shaded gradient underneath can be implemented with a call to GradientFill.

Filtering edit controls

For this application it is desirable to restrict input in the edit controls to only decimal numbers (a distance of “abc” miles does not make much sense). Although the edit control has an ES_NUMBER window style which restricts input to numeric digits we can not utilise this as we also want to accept a decimal point.

Another approach is to subclass the edit control. Subclassing a window allows us to override or alter the existing behaviour of the control. The article “Safe Subclassing in Win32” provides a great introduction to this technique.

Similar to a dialog procedure, each window (control) has an associated window procedure. The first step in subclassing is to replace the window procedure with our own custom one.

// Replace the window procedure associated with the
// 'hWndCtrl' window with one called 'MaskedEditProc'
SetWindowLong(hWndCtrl, GWL_WNDPROC, (LONG)MaskedEditProc);

The new window procedure has the opportunity to process or filter window messages before they are passed along to the original window procedure. For example by filtering the WM_CHAR window messages seen by the original window procedure we can make certain key presses disappear.

static LRESULT CALLBACK MaskedEditProc(HWND hWnd,
         UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  WNDPROC pfnOldWndProc =
      (WNDPROC)GetWindowLong(hWnd, GWL_USERDATA);
 
  // Process messages of interest
  switch (uMsg)
  {
    case WM_CHAR:
      if (iswdigit(wParam)	// digits
        || wParam == '-'	// negativeSign
        || wParam == '.'	// decimalSeparator
        || wParam == '\b')	// backspace
        break; // This character is allowed
      else
        return 0; // This character isn't allowed
  }
 
  // Allow the original window procedure to process
  // the message.
  return CallWindowProc(pfnOldWndProc, hWnd, uMsg,
                                 wParam, lParam);
}

A more advanced (and reusable) implementation of this technique is described in James Brown’s Masked Edit Control Input tutorial.

Sample Application

[Download mileagetracker.zip - 61KB]

Within the sample application available for download there are a number of possible tasks left as exercises for the reader.

  1. Modify the MaskedEditProc window procedure to make it reject input such as “1.23.4″. I.e. accept a maximum of one decimal point.
  2. Modify the decimal number parsing to be locale aware. Some locales for instance use a semi colon (;) as a decimal separator.
  3. Handle screen orientation and resolution changes.

Leave a Reply