One of the most challenging aspects about being a developer for Windows Mobile is the fact that Windows Mobile is not a single platform. Over the past decade, Microsoft has layered multiple user interface shells over the core Windows CE Operating System, and today, two contenders still remain. The first, formerly named Pocket PC, and now, Windows Mobile Classic or Windows Mobile Professional, is a touch-screen based design where a stylus or finger is the primary input device. The second, formerly named SmartPhone and now Windows Mobile Standard, uses only the keypad and navigation buttons.
In recent years, Microsoft has worked hard to converge the two user interfaces. They have even blurred the marketing distinctions with the Windows Mobile Classic/Standard/Professional naming scheme (so much so that the rest of this article refers to the two species as touchscreen and non-touchscreen). However, for the developer, differences certainly still exist.
Foremost, Microsoft has different logo certification requirements for the two platforms. In fact, each has their own PDF document outlining specific behavior which must be implemented. Obtaining logo certification for an application is a good idea, because it lets customers know that the application is robust and that it follows standards that will ease deployment and training. Even more important, if an application works with restricted low level system calls, it must obtain a Microsoft Mobile2Market privileged signature. To get a privileged signature, logo certification is now a requirement, not an option! This article shows how to abstract some of the most common issues a developer will encounter when creating a native code application that must be logo certified for each platform. The routines demonstrated by the demo application are included in a single source file, should work for Windows Mobile 5.0 and higher, and can easily be added to any project.
Creating source code that compiles on multiple different systems is called cross platform development. Most code, if it is generic enough, will compile on any system. But in some cases, code will need to be specifically targeted to a certain platform. Usually, this involves inserting the
#if/#endif preprocessor directives. For touchscreen device platform configurations,
WIN32_PLATFORM_PSPC is defined. For non-touchscreen device platform configurations,
WIN32_PLATFORM_WFSP is defined. The code in this article will key off of those two definitions to tell the compiler which code to use and which code to ignore. Because these are preprocessor directives, a separate binary needs to be generated for each platform.
Using the Code
The SIP (Or Lack Thereof)
Many touchscreen devices lack a keyboard, and only have the screen available as a way to enter text. To solve this problem, all touchscreen devices offer a Soft Input Panel (SIP). The user can then enter text using the SIP. The application user interface residing behind the SIP should be notified about the limited screen real estate now available, so that it can resize itself as best as it can. Implementing the above is one of the requirements for Windows Mobile Professional logo certification, as stated in the Designed for Windows Mobile 6 Professional Software Application Handbook:
Required: Applications Must Handle the Input Panel Appearing/Disappearing
From the UI perspective, the application must be designed to account for the fact that an 80-pixel tall Soft Input Panel (SIP) may be docked in the area immediately above the command bar at any time, requiring the application to be resized out of the way. The specific requirements are:
- All text entry fields must be accessible with the SIP up. This can be accomplished either by placing all edit fields high enough on the screen so that they will not be obscured by an 80 pixel tall SIP, or by having a scroll bar when the SIP is displayed (or both).
- Applications must resize in response to docked input methods. This includes resizing and/or shifting things like dialog tabs, status bars, and other elements as needed, and/or resizing or drawing scroll bars to ensure all UI is accessible while an 80-pixel tall SIP is up. Note that a docked input method may be as tall as 140 pixels, but applications are not expected to be optimized for an SIP height greater than 80 pixels.
Handling the SIP and resizing any dialog affected by it involves just a few system calls. All of them revolve around the
SHACTIVATEINFO structure, which keeps state information for the SIP and how it is affecting screen real estate. This structure needs to be initialized during
WM_INITDIALOG, and updated during
LH_SIPCreate(HWND hwnd,SHACTIVATEINFO *psai)
memset(psai, 0, sizeof (SHACTIVATEINFO));
psai->cbSize = sizeof (SHACTIVATEINFO);
memset(&si, 0, sizeof(si));
si.cbSize = sizeof(si);
SHSipInfo(SPI_GETSIPINFO, 0, (PVOID) &si, FALSE);
cx = si.rcVisibleDesktop.right - si.rcVisibleDesktop.left;
cy = si.rcVisibleDesktop.bottom - si.rcVisibleDesktop.top;
if (!(si.fdwFlags & SIPF_ON) ||
((si.fdwFlags & SIPF_ON) && !(si.fdwFlags & SIPF_DOCKED)))
hwndMenuBar = SHFindMenuBar(hwnd);
cy -= (rcMenu.bottom-rcMenu.top);
SetWindowPos(hwnd, NULL, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER);
LH_SIPCreate() function is called. Here, the
SHACTIVATEINFO structure is initialized. The dialog is then sized properly, given the initial status of the SIP and the dimensions of the menu bar. That's a lot of work, but luckily, it only has to be done once.
LH_SIPActivate(HWND hwnd,WPARAM wParam,LPARAM lParam,SHACTIVATEINFO *psai)
SHHandleWMActivate(hwnd, wParam, lParam, psai, FALSE);
LH_SIPSettingChange(HWND hwnd,WPARAM wParam,LPARAM lParam,SHACTIVATEINFO *psai)
SHHandleWMSettingChange(hwnd, wParam, lParam, psai);
After that, on
SHHandleWMActivate() to update
SHACTIVATEINFO and do the work of sending
WM_SIZE events to the dialog. Similarly, for
SHHandleWMSettingChange() to handle the task.
Of course, non-touchscreen devices don't have SIPs--there is no way to tap on the panel. Instead, these devices have full keyboards, or at least a number pad that can enter alphanumeric characters. The question, then, is how to write clean code that will deal with both SIP and non-SIP devices. This is done by bracketing platform specific code with
#if WIN32_PLATFORM_PSPC/#endif. The
LH_SIPSettingChange() functions can be called by both platforms, but on non-touchscreen devices, they are no-ops.
SIP not handled
The image on the left shows what happens when the SIP is not handled correctly. When docked, the SIP overlays the application's user interface, obscuring the edit control. This looks ugly, appears unprofessional, and would not satisfy the SIP logo requirement. On the other hand, the image on the right shows what happens when the application responds correctly. Here, the sample application processes the
SHACTIVATEINFO structure in
WM_SETTINGCHANGE. As a result, the application is correctly informed about the available screen space via
WM_SIZE, and can resize itself to best take advantage of the new dimensions. In this case, the edit control stretches to fill all the available space. Even if there is a large amount of text, if the SIP appears and the edit control must shrink, text can still be viewed using the edit control's scrollbar. The logo requirement is thus accomplished.
The Mysterious Back Button
The back button performs very different operations depending on which platform you are using. For touchscreen devices, the button sends the application to the rear. For non-touchscreen devices, the back button is actually a delete key in some cases. The proper use of the back button is a requirement for Windows Mobile 6 Standard logo certification. Thus, it is important for a developer to handle this correctly, as stated in the Designed for Windows Mobile 6 Standard Software Application Handbook:
Required: Back Button Performs Backspace in an Edit Control
On a screen where text can be edited (for example, in a mail message), the back button must enable the user to backspace on the entire screen, but not exit.
Ironically, in order to achieve this standard functionality for non-touchscreen devices, we will actually have to override the way the back button works! If the dialog contains an edit control, we need the button to work as a delete key. If it doesn't have an edit control, the back button should behave normally, and either send the application to the rear or close a sub-dialog.
LH_BackKeyBehavior() will toggle how the back button works. Depending on the parameters, sending a
SHCMBM_OVERRIDEKEY message specifying the back button (
VK_TBACK) will either redirect presses to
WM_HOTKEY or force it back to normal default behavior. This call should be made any time edit controls appear or completely disappear from a dialog.
LH_BackKeyBehavior(HWND hwnd,BOOL bHasEditControl)
hwndMenuBar = SHFindMenuBar(hwnd);
lparam = MAKELPARAM(SHMBOF_NODEFAULT | SHMBOF_NOTIFY,
SHMBOF_NODEFAULT | SHMBOF_NOTIFY);
lparam = MAKELPARAM(SHMBOF_NODEFAULT | SHMBOF_NOTIFY, 0);
SendMessage(hwndMenuBar, SHCMBM_OVERRIDEKEY, VK_TBACK, lparam);
LH_BackKeyHotKey() is called. If
VK_TBACK is the hotkey in question, the back key has been overridden and deletion needs to be supported. In that case, the information in the notification is relayed to
SHSendBackToFocusWindow() in order to send delete events to the proper edit control.
LH_BackKeyHotKey(HWND hwnd,UINT uMessage,WPARAM wParam,LPARAM lParam)
if(HIWORD(lParam) == VK_TBACK)
SHSendBackToFocusWindow(uMessage, wParam, lParam);
Touchscreen devices, of course, do not honor this deletion behavior. The back key is always used to close dialogs or send applications to the rear. While maintaining the
LH_BackKeyHotKey() calls, this code is eliminated in that platform by bracketing with
#if WIN32_PLATFORM_WFSP/#endif, just as was done in the SIP case described previously.
In the image on the left, there is an edit control visible. According to the logo certification guidelines, the back button should be able to delete in this case. For the image on the right, the edit control has been disabled and hidden. Without any edit controls, the back button should perform its default behavior. For the main screen of an application, that behavior is to force the application to the back.
Spinners or Combo Boxes?
Complicating life for the Windows Mobile developer is the fact that not all common controls are acceptable for logo certification on both platforms. One of the biggest differences is regarding combo boxes. On touchscreen devices, combo boxes are A-OK. Clicking on one brings up a dropdown list and the user can select an item. However, on non-touchscreen devices, spinners are preferred. Instead of a combo box, the user is presented with a single line containing the selected entry. Alongside are left and right arrows which allow the user to scroll through alternate selections. If the user clicks on the entry field, a full screen listbox is displayed, allowing the user to see all the choices at once.
Non-touchscreen devices do support combo boxes. However, the Windows Mobile 6 Standard logo certification guidelines do not allow their use, as stated in the Designed for Windows Mobile 6 Standard Software Application Handbook:
Required: Spinner Controls
If an application requires radio-button or drop-down list behavior, spinner controls (left and right arrows) must be used.
If desktop code is being ported to Windows Mobile non-touchscreen devices, it most likely depends on combo boxes. The same is true for code being brought over from Windows Mobile touchscreen devices. Rewriting that code to use spinners instead would be a lot of work, and might risk breaking the existing implementation. How best to abstract this behavior? Subclassing!
SetProp(hWnd, TEXT("SpinComboData"), psc);
psc->origCombo = (WNDPROC)SetWindowLong (hWnd, GWL_WNDPROC,
MapWindowPoints (NULL, GetParent(hWnd), (LPPOINT)&rc, 2);
psc->hwndSpin = CreateWindow (TEXT("listbox"), NULL,
WS_VISIBLE | WS_TABSTOP | LBS_NOTIFY,
rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top,
GetParent(hWnd), (HMENU)GetDlgCtrlID(hWnd), NULL, NULL);
psc->hwndUpDown = CreateWindow (UPDOWN_CLASS, NULL,
WS_VISIBLE | UDS_HORZ | UDS_ALIGNRIGHT | UDS_ARROWKEYS |
UDS_SETBUDDYINT | UDS_WRAP | UDS_EXPANDABLE,
0, 0, 0, 0, GetParent(hWnd), NULL, NULL, NULL);
SendMessage (psc->hwndUpDown, UDM_SETBUDDY, (WPARAM)psc->hwndSpin, 0);
psc->hFont = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0);
Instead of worrying about combo boxes on non-touchscreen devices, a spinner control can be created that will sit on top of the hidden combo box and intercept all of its communication. In
_InitSpinCombo() subclasses the existing combo box, creates a new spinner control, sizes it to the same dimensions as the combo box, puts it in the correct tab order, sets the font to be the same, and finally, hides and disables the old combo box. Since this code is only needed for non-touchscreen devices, the contents of
LH_InitSpinCombo() are bracketed by
SubclassComboProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
psc = (PSPINNERCOMBO)GetProp(hWnd, TEXT("SpinComboData"));
MapWindowPoints (NULL, GetParent(hWnd), (LPPOINT)&rc, 2);
SendMessage (psc->hwndUpDown, UDM_SETBUDDY, (WPARAM)psc->hwndSpin, 0);
SetWindowLong (hWnd, GWL_WNDPROC, (LONG_PTR)psc->origCombo);
return CallWindowProc (origCombo, hWnd, message, wParam, lParam);
SubclassComboProc() does the work of translating combo box messages to something the new spinner control can use.
WM_SIZE reports any movement or resizing of the combo box control so the spinner control can shadow it.
WM_DESTROY cleans up subclassing and memory allocation used to initialize the spinner control. The other messages populate the spinner or report on the selections. It certainly helps here that both the combo box and the spinner control appear to be based on the listbox and seem to be direct cousins--not a lot of work needs to be done here.
The image on the left shows a non-touchscreen device running with code using a combo box. This implementation does not follow the spinner rule from the Windows Mobile 6 Standard Software Application Handbook, and would not pass logo certification. The image on the right is the same code, only with the
LH_InitSpinCombo() call added. The combo box is there, it is just hidden and disabled. All of the combo box messages generated by the application are still being sent. However, now they are redirected to the new spinner control instead. The spinner logo requirement is solved with a single function call.
Putting it All Together
This demo shows how to use all of the above code to write an application that satisfies the logo certification requirements for either platform. On touchscreen devices, the SIP properly resizes the application window. On non-touchscreen devices, the back button is correctly enabled when the edit dialog is displayed. Finally, spinner controls appear on non-touchscreen devices, without any changes to the underlying combo box based code.
DlgProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
ld=(LOGODATA *)GetWindowLong (hWnd, GWL_USERDATA);
SendMessage (ld->hwndCombo, CB_ADDSTRING, 0,
(LPARAM)TEXT("Edit Control Disabled"));
ld->iEnable=SendMessage (ld->hwndCombo, CB_ADDSTRING, 0,
(LPARAM)TEXT("Edit Control Enabled"));
SendMessage (ld->hwndCombo, CB_SETCURSEL,
LH_BackKeyBehavior(hWnd,TRUE); return TRUE;
LH_SIPActivate(hWnd, wParam, lParam, &(ld->sai));
LH_SIPSettingChange(hWnd, wParam, lParam, &(ld->sai));
switch (LOWORD (wParam))
case IDC_EXIT: EndDialog(hWnd, TRUE);
case IDCANCEL: SHNavigateBack();
LH_InitSpinCombo() is called in order to subclass the combo box on non-touchscreen devices and turn it into a spinner control. Afterwards, the combo box is initialized with the choices for the state of the edit control, with enabled being the default state. Note that these messages are redirected to the new spinner control for non-touchscreen devices via subclassing.
LH_SIPCreate() is then called to initialize SIP handling for touchscreen devices. Finally,
LH_BackKeyBehavior() configures the back key on non-touchscreen devices to allow deletion inside the enabled edit control. In the case where the the back key must provide deletion on non-touchscreen devices, we forward the
WM_HOTKEY message to the helper function.
Whenever there is a change in the combo box/spinner selection, the edit control needs to be updated with its new status, and the back key must be properly configured depending on that status. Inside
CBN_SELCHANGE notification is processed. If the edit control is available, the back key should do deletion. If there is no edit control, pressing the back key should minimize the dialog, or, in the case of a sub-dialog, close the dialog. On non-touchscreen devices, if the back button is not overridden, a
IDCANCEL command will be sent. Since this dialog is the entire application, it is minimized by calling
SHNavigateBack(). If the dialog were a sub-dialog, the correct response here would be
Adding the six helper functions outlined here to an application will allow it to satisfy all of the logo certification issues discussed in this document. Because these functions abstract the platform specific operations, the rest of the hosting application's code does not need to worry about platform idiosyncrasies. Best of all, the hosting application should be able to pass logo certification on both platforms with a minimum of modifications.
- November 19, 2008 - Initial version (WJB).