Hello Pocket PC
A Pocket PC application is still a Windows application, so it has a message loop, a main window, and window procedures. However, some new requirements do change the design a bit. First, a Pocket PC application must make sure that only one copy of itself is running at any one time. The operating system doesn't ensure this—that is the application's job. Second, instead of using a command bar—as do other Windows CE applications—Pocket PC applications use the menu bar. In many ways, the menu bar acts like an updated command bar, but it does have some peculiarities. A Pocket PC application should not have a Close button, an Exit command, or a Close command in its menus. This is because PDA users don't use applications; they use their PDAs. (The user interface gurus that work on this stuff have decided that users would rather not know when a particular application is running or not.)Let's move on to some code. Figure 17-2 shows two screen shots of a simple Pocket PC application called HelloPPC. The left image shows the window with the soft input panel, or SIP, hidden; the image on the right shows HelloPPC with the SIP showing. Notice how the text centers itself in the visible portion of the workspace. The HelloPPC window has a red outline to highlight its size and position.
Figure 17-2: The HelloPPC application with the SIP both hidden and showing
Chapter 1 have to do with the difference between the Pocket PC and the Explorer shells. I'll talk about these differences in the sections following the code.Listing 17-1: The HelloPPC application
HelloPPC.rc
//======================================================================
// Resource file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include "windows.h" // Windows stuff
#include "commctrl.h" // Common ctl stuff
#include "aygshell.h" // Pocket PC stuff
#include "HelloPPC.h" // Program-specific stuff
//----------------------------------------------------------------------
// Icons and bitmaps
//
ID_ICON ICON "HelloPPC.ico" // Program icon
//----------------------------------------------------------------------
// Accelerator keys
//
ID_ACCEL ACCELERATORS DISCARDABLE
BEGIN
"Q", IDM_EXIT, VIRTKEY, CONTROL, NOINVERT
END
HelloPPC.h
//======================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//
//================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT { // Structure associates
UINT Code; // messages
// with a function.
LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD { // Structure associates
UINT Code; // menu IDs with a
LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function.
};
//----------------------------------------------------------------------
// Generic defines used by application
#define ID_ACCEL 1 // Accelerator table ID
#define IDM_EXIT 100
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
// Message handlers
LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoCommandMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoSettingChangeMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoActivateMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoHibernateMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);
// WM_COMMAND message handlers
LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD);
HelloPPC.c
//======================================================================
// HelloPPC - A simple application for the Pocket PC
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//
//======================================================================
#include <windows.h> // For all that Windows stuff
#include <commctrl.h> // Command bar includes
#include <aygshell.h> // Pocket PC includes
#include "helloppc.h" // Program-specific stuff
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("HelloPPC");
HINSTANCE hInst; // Program instance handle
// Pocket PC globals
HWND hwndMenuBar = NULL; // Handle of menu bar control
BOOL fHibernated = FALSE; // Indicates hibernated state
SHACTIVATEINFO sai; // Used to adjust window for SIP
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
WM_CREATE, DoCreateMain,
WM_PAINT, DoPaintMain,
WM_COMMAND, DoCommandMain,
WM_SETTINGCHANGE, DoSettingChangeMain,
WM_ACTIVATE, DoActivateMain,
WM_HIBERNATE, DoHibernateMain,
WM_DESTROY, DoDestroyMain,
};
// Command Message dispatch for MainWindowProc
const struct decodeCMD MainCommandItems[] = {
IDM_EXIT, DoMainCommandExit,
};
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow) {
MSG msg;
int rc = 0;
HWND hwndMain;
HACCEL hAccel;
// Initialize this instance.
hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
if (hwndMain == 0) return 0x10;
hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE (ID_ACCEL));
// Application message loop
while (GetMessage (&msg, NULL, 0, 0)) {
// Translate accelerator keys.
if (!TranslateAccelerator(hwndMain, hAccel, &msg)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
// Instance cleanup
return TermInstance (hInstance, msg.wParam);
}
//------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow) {
WNDCLASS wc;
HWND hWnd;
// Save program instance handle in global variable.
hInst = hInstance;
// Allow only one instance of the application.
hWnd = FindWindow (szAppName, NULL);
if (hWnd) {
SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));
return 0;
}
// Register application main window class.
wc.style = CS_VREDRAW | CS_HREDRAW; // Window style
wc.lpfnWndProc = MainWndProc; // Callback function
wc.cbClsExtra = 0; // Extra class data
wc.cbWndExtra = 0; // Extra window data
wc.hInstance = hInstance; // Owner handle
wc.hIcon = NULL, // Application icon
wc.hCursor = LoadCursor (NULL, IDC_ARROW); // Default cursor
wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
wc.lpszMenuName = NULL; // Menu name
wc.lpszClassName = szAppName; // Window class name
if (RegisterClass (&wc) == 0) return 0;
// Create main window.
hWnd = CreateWindow (szAppName, // Window class
TEXT ("Hello"), // Window title
WS_VISIBLE, // Style flags
CW_USEDEFAULT, // x position
CW_USEDEFAULT, // y position
CW_USEDEFAULT, // Initial width
CW_USEDEFAULT, // Initial height
NULL, // Parent
NULL, // Menu, must be null
hInstance, // Application instance
NULL); // Pointer to create
// parameters
if (!IsWindow (hWnd)) return 0; // Fail if not created.
// Standard show and update calls
ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);
return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
return nDefRC;
}
//======================================================================
// Message handling procedures for main window
//
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
INT i;
//
// Search message list to see if we need to handle this
// message. If in list, call procedure.
//
for (i = 0; i < dim(MainMessages); i++) {
if (wMsg == MainMessages[i].Code)
return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
}
return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoCreateMain - Process WM_CREATE message for window.
//
LRESULT DoCreateMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
SHMENUBARINFO mbi;
SIPINFO si;
int cx, cy;
// Initialize the shell to activate info structure.
memset (&sai, 0, sizeof (sai));
sai.cbSize = sizeof (sai);
// Create a menu bar.
memset(&mbi, 0, sizeof(SHMENUBARINFO)); // Zero structure
mbi.cbSize = sizeof(SHMENUBARINFO); // Size field
mbi.hwndParent = hWnd; // Parent window
mbi.dwFlags = SHCMBF_EMPTYBAR; // Flags like hide SIP btn
mbi.nToolBarId = 0; // ID of toolbar resource
mbi.hInstRes = 0; // Inst handle of app
mbi.nBmpId = 0; // ID of bitmap resource
mbi.cBmpImages = 0; // Num of images in bitmap
mbi.hwndMB = 0; // Handle of bar returned
// Create menu bar and check for errors.
if (!SHCreateMenuBar(&mbi)) {
MessageBox (hWnd, TEXT ("Couldn\'t create menu bar"),
szAppName, MB_OK);
DestroyWindow (hWnd);
}
hwndMenuBar = mbi.hwndMB; // Save the menu bar handle.
// Query the sip state and size our window appropriately.
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 the sip is not shown, or showing but not docked, the
// desktop rect doesn't include the height of the menu bar.
if (!(si.fdwFlags & SIPF_ON) ||
((si.fdwFlags & SIPF_ON) && !(si.fdwFlags & SIPF_DOCKED))) {
RECT rectMB;
GetWindowRect (hwndMenuBar, &rectMB);
cy -= (rectMB.bottom - rectMB.top);
}
SetWindowPos (hWnd, NULL, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER);
return 0;
}
//----------------------------------------------------------------------
// DoCommandMain - Process WM_COMMAND message for window.
//
LRESULT DoCommandMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
WORD idItem, wNotifyCode;
HWND hwndCtl;
INT i;
// Parse the parameters.
idItem = (WORD) LOWORD (wParam);
wNotifyCode = (WORD) HIWORD (wParam);
hwndCtl = (HWND) lParam;
// Call routine to handle control message.
for (i = 0; i < dim(MainCommandItems); i++) {
if (idItem == MainCommandItems[i].Code)
return (*MainCommandItems[i].Fxn)(hWnd, idItem, hwndCtl,
wNotifyCode);
}
return 0;
}
//----------------------------------------------------------------------
// DoPaintMain - Process WM_PAINT message for window.
//
LRESULT DoPaintMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
PAINTSTRUCT ps;
HPEN hPen, hOld;
RECT rect;
HDC hdc;
hdc = BeginPaint (hWnd, &ps);
GetClientRect (hWnd, &rect);
// Draw a red rectangle around the window.
hPen = CreatePen (PS_SOLID, 1, RGB (255, 0, 0));
hOld = (HPEN)SelectObject (hdc, hPen);
Rectangle (hdc, rect.left, rect.top, rect.right, rect.bottom);
SelectObject (hdc, hOld);
DeleteObject (hPen);
// Draw the standard hello text centered in the window.
DrawText (hdc, TEXT ("Hello Pocket PC! "), -1, &rect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
EndPaint (hWnd, &ps);
return 0;
}
//----------------------------------------------------------------------
// DoSettingChangeMain - Process WM_SETTINGCHANGE message for window.
//
LRESULT DoSettingChangeMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
// Notify shell of our WM_SETTINGCHANGE message.
SHHandleWMSettingChange(hWnd, wParam, lParam, &sai);
return 0;
}
//----------------------------------------------------------------------
// DoActivateMain - Process WM_ACTIVATE message for window.
//
LRESULT DoActivateMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
// If activating, restore any hibernated stuff.
if ((LOWORD (wParam) != WA_INACTIVE) && fHibernated) {
fHibernated = FALSE;
}
// Notify shell of our activate message.
SHHandleWMActivate(hWnd, wParam, lParam, &sai, 0);
return 0;
}
//----------------------------------------------------------------------
// DoHibernateMain - Process WM_HIBERNATE message for window.
//
LRESULT DoHibernateMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
// If not the active window, reduce our memory footprint.
if (GetActiveWindow() != hWnd) {
fHibernated = TRUE;
}
return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
PostQuitMessage (0);
return 0;
}
//======================================================================
// Command handler routines
//----------------------------------------------------------------------
// DoMainCommandExit - Process Program Exit command.
//
LPARAM DoMainCommandExit (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
SendMessage (hWnd, WM_CLOSE, 0, 0);
return 0;
}
The HelloPPC application creates a main window and prints Hello Pocket PC in the center of the window. It also draws a red rectangle around the border of its window to clearly show the extent of the window. The program creates a menu bar without a menu but with a button to display the SIP. If you tap the SIP button, you will see the main window resize to avoid being covered by the SIP. If you attempt to start a second copy of HelloPPC, the system will instead switch to the copy currently running. Finally, if you open the SIP and tap Ctrl-Q, the application will quit. Each of these little features takes a little bit of code to conform to the standards of a Pocket PC application. Now let's examine these code fragments and learn how it's done.
Differences in a Pocket PC Application
Comparing the Chapter 1 shows a fair amount of new code specifically added to handle the requirements of the Pocket PC shell. The first issue is the requirement that a Pocket PC application only have one instance of itself running at any one time. The Pocket PC shell won't enforce this requirement; it's up to the application.
Single Instance
The single instance requirement of a Pocket PC is accomplished with the FindWindow code that has appeared in almost every example in this book. This code ensures that only one copy of the application is running at any given time. The following code fragment shows how this is accomplished.
// Allow only one instance of the application.
HWND hWnd = FindWindow (szAppName, NULL);
if (hWnd) {
SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));
return -1;
}
The call to FindWindow looks for a top-level window with the same class name as HelloPPC. If the window is found, the code calls SetForegroundWindow to put that window into the foreground, and then the second copy of the application terminates. Notice the rather strange logical ORing of a 1 to the window handle. This is an internal hack that tells Windows to restore the window being set to the foreground in case it has been minimized. Without this bit, you could accidentally set a minimized window to the foreground, and under the Pocket PC shell, the user would never see this minimized window.
Use a Menu Bar, Not a Command Bar
The next few Chapter 5, but here is a short review. The following code fragment creates a simple menu bar.
SHMENUBARINFO mbi;
// Create a menu bar.
mbi.hwndParent = hWnd; // Parent window
mbi.dwFlags = SHCMBF_EMPTYBAR; // Flags like hide SIP btn
mbi.nToolBarId = 0; // ID of toolbar resource
mbi.hInstRes = 0; // Inst handle of app
mbi.nBmpId = 0; // ID of bitmap resource
mbi.cBmpImages = 0; // Num of images in bitmap
mbi.hwndMB = 0; // Handle of bar returned
// Create menu bar and check for errors.
if (SHCreateMenuBar(&mbi))
hwndMenuBar = mbi.hwndMB; // Save the menu bar handle.
This code initializes a SHMENUBARINFO structure and passes it to SHCreateMenuBar to create the main window's associated menu bar. The menu bar control can contain a menu, toolbar buttons, and the button that displays the SIP. For HelloPPC, the menu bar has no menu and thus the SHCMBF_EMPTYBAR flag is set in the dwFlags field. The only other field that requires initialization for this simple configuration is the hwndParent field that is set to the HelloPPC window handle. After the menu bar is created, the handle of the returned control is saved.
Manually Sizing the Main Window
A Pocket PC application must also deal with the menu bar and the SIP. The key is to size the application's top-level window so that the SIP doesn't obscure it. Also, if you create the top-level window following the Windows CE tradition of using CW_USEDEFAULT in the position and size parameters of CreateWindow, the window will be created over the top of the area used by the menu bar. To avoid covering up the menu bar with a window, or the window being covered by the SIP, the WM_CREATE handler includes the following code:
// Query the SIP state and size our window appropriately.
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 the SIP is not shown, or is showing but not docked, the
// desktop rect doesn't include the height of the menu bar.
if (!(si.fdwFlags & SIPF_ON) ||
((si.fdwFlags & SIPF_ON) && !(si.fdwFlags & SIPF_DOCKED))) {
RECT rectMB;
GetWindowRect (hwndMenuBar, &rectMB);
cy -= (rectMB.bottom - rectMB.top);
}
SetWindowPos (hWnd, NULL, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER);
The preceding code uses the function SHSipInfo to query the current state of the SIP. Included in the information returned by this call is the visible portion of the desktop that is not obscured by a docked SIP. The code computes the size of this rectangle and uses it if the SIP is displayed in a docked state and positioned at the bottom of the screen. If, however, the SIP is not visible, or if it is floating, the main window still must be sized, because the system default window size does not leave room for the menu bar. The preceding code tests whether the SIP is hidden or floating and, if it is, shortens the window height by the height of the menu bar. On a Pocket PC, this value is documented to be 26 pixels, but it isn't guaranteed to be the same on Pocket PC–like devices. Because of this, the value should be computed instead of hard-coded.
This code to specify the size of the window can be placed elsewhere in a Pocket PC application. For example, you could resize the window after CreateWindow returns instead of within the WM_CREATE message handler. Either way, you must manually size the window, depending on the state of the SIP and whether you want to use a menu bar control in your application. I choose to place the size code in the WM_CREATE message handler as a matter of style.
Dealing with Changes in the SIP
Once HelloPPC is running, it must still deal with the user displaying and hiding the SIP. The standard technique for handling the SIP is to resize your application's main window whenever the SIP is displayed or hidden. This technique allows your standard window code to deal with SIP changes as it would with any window resize. Of course, you aren't required to resize your main window in reaction to the SIP, but you must provide some way of insuring that the SIP does not obscure data that the user is interacting with when the SIP is shown. The Pocket PC shell provides some simple hooks to monitor the SIP and automatically resize a window. This method is the easiest to use and the one I'll describe here.To automatically resize your window in response to the SIP, you must globally declare an SHACTIVATEINFO structure in your program. While you can find the structure declared in the include files required for the program, the internal structure, aside from one field, is irrelevant. This structure should be initialized to 0 and the cbSize field should be set to the size of the structure, as in the following code fragment:
SHACTIVATEINFO sai; // Declare globally.
// Initialize the shell activate info structure.
memset (&sai, 0, sizeof (sai));
sai.cbSize = sizeof (sai);
This initialization should happen either before your main window is created or in the WM_CREATE handler.Your main window's window procedure must handle the WM_ACTIVATE and WM_SETTINGCHANGE messages. The WM_SETTINGCHANGE message is used in Windows to indicate that some basic system setting has changed. In the Pocket PC, WM_SETTINGCHANGE is also used to notify an application that the state of the SIP has changed. While the application could manually determine the state of the SIP and handle it in its own WM_SETTINGCHANGE message handler, the Pocket PC shell provides a simple function that can be called to do the work for the application. The function prototype for this function is
BOOL SHHandleWMSettingChange (HWND hwnd, WPARAM wParam, LPARAM lParam,
SHACTIVATEINFO *psai);
The first three parameters of this function are the handle to the window receiving the WM_SETTINGCHANGE message and the message's wParam and lParam parameters. The final parameter is the address of the SHACTIVATEINFO structure declared and initialized earlier in the code. The use of this function is quite simple; just call this function whenever the top-level window receives a WM_SETTINGCHANGE message. The function resizes the window if necessary.The second function to call to help with the SIP is
BOOL SHHandleWMActivate (HWND hwnd, WPARAM wParam, LPARAM lParam,
SHACTIVATEINFO *psai, DWORD dwFlags);
As you might expect from the name of the function, SHHandleWMActivate should be called in response to a WM_ACTIVATE message sent to the top-level window. The parameters are the same as for SHHandleWMSettingChange, with the addition of the dwFlags parameter. The dwFlags parameter can be either 0 or SHA_INPUTDIALOG, if the top-level window is a dialog box with child controls. For dialog boxes, the SHA_INPUTDIALOG flag will prevent the SIP from automatically popping up and down when the focus switches between the different child controls.In addition to SHHandleWMActivate and SHHandleWMSettingChange, another difference between HelloPPC and many of the other examples in this book is the use of the CS_HREDRAW and CS_VREDRAW flags when registering the window class. These flags automatically invalidate the main window whenever the window is resized. The interaction of these functions along with the redraw style flags will result in the top-level window being redrawn when the SIP is shown or hidden. This interaction provides a simple way for a Pocket PC program to automatically adjust the presentation of its data simply by handling the WM_PAINT message and drawing the appropriate data in the format necessary for the situation.Be aware of one issue with some versions of Microsoft eMbedded Visual C++: The code wizard that produces the example Pocket PC application does not initialize the SHACTIVATEINFO structure. Therefore, the example code does not automatically resize the window when the SIP is displayed, even though it calls the SHHandleWMSettingChange function. In addition, the wizard code does not call SHHandleWMActivate, so if you plan to base your application on this wizard, you should add the code described in this section. It just goes to show that a programmer should never depend on code wizards. While wizards can be handy tools for rapid code generation, if you don't understand the code they produce, you're going to have problems.
Ctrl-Q Closes a Pocket PC Application
The final Pocket PC modification in HelloPPC comes in WinMain and is part of a tradition of Pocket PC applications. Notice that in WinMain, HelloPPC loads a keyboard accelerator table. The message loop is modified to enable that accelerator table. A quick look in the

Building HelloPPC
The HelloPPC project filesChapter 3:
#if defined(WIN32_PLATFORM_PSPC) // Compile only for Pocket PC.
#include <aygshell.h> // Add Pocket PC includes.
#pragma comment( lib, "aygshell" ) // Link Pocket PC lib for menu bar.
#endif
The first line is a conditional compile preprocessor command that tells the compiler to compile the enclosed lines only if the symbol WIN32_PLATFORM_PSPC is defined. As you might expect, that symbol is defined if you compile to either the Pocket PC or old Palm-size PC targets. The second line tells the compiler to include the Aygshell.h include file that provides the function prototypes and type definitions necessary for using the Pocket PC–specific functions. Finally, the #pragma line instructs the linker to link in the aygshell library so that the Pocket PC functions can be resolved.Aygshell is now available in the Windows CE Platform Builder for OEMs to include in their custom devices. The component is optional, though, so you should check with the specific platform SDK documentation to see whether it's included on your target device.