Douglas Boling

 

Programming the Pocket PC

The Pocket PC is one of the most successful Windows CE–based systems. The combination of a small, PDA-size dimensions and a powerful CPU has provided a portable but fast platform for the Windows CE operating system. In addition, an extensive reworking of the user interface for the Pocket PC devices makes for an interesting platform for application developers.

While the look of the Pocket PC shell is completely different from other Windows CE devices, the underlying plumbing is still Windows CE. Pocket PC applications are Windows CE applications and therefore similar to Windows applications written for the desktop. However, the enhancements to the Pocket PC shell do require that applications perform some extra duty to support the device's unique look and feel. Also, the small portrait-mode screen affects how applications present data. Finally, the way users expect a PDA to act requires that Pocket PC applications differ in action from their desktop cousins.

The Pocket PC shell provides a number of helper functions to assist applications in providing a consistent look and feel. These include functions that deal with the soft input panel (SIP), which displays an on-screen pop-up keyboard, and functions that help dialog boxes automatically expand to fill up the screen, thereby providing a simpler user interface. The Pocket PC shell provides its own menu control called a menu bar, which hosts application menus, buttons, and the button that displays the SIP. New functions help applications configure the Today screen, which is the closest thing the Pocket PC has to a desktop. The Pocket PC even supports a series of functions to help game developers port their games to Windows CE.

The Pocket PC Screen

Before I jump into a discussion of a Pocket PC application, let's look at the elements of the Pocket PC screen. Figure 14-1 shows the Pocket PC's Today screen.

 Across the top of the Pocket PC screen is the navigation bar. This element of the screen contains the title of the foreground window, the current time, and (when a dialog is displayed) an OK button for dismissing a dialog. Tapping on the navigation bar displays the Start menu, allowing the user to launch applications or to switch to running applications.

The Today screen contains information about the device. Today screen panels can be configured through the control panel. (In the next chapter, I'll discuss how developers can add custom Today screen panels.) The bottom of the Pocket PC screen is reserved for the menu bar. The Today screen menu bar is unique in that it displays taskbar anunciators created using the same API that I described in

When the user starts an application, the screen layout is similar to that seen with the Today screen in view. The navigation bar is at the top, the application window takes up the main screen area, and the menu bar holds its place at the bottom of the screen. The best way to learn about programming this platform is to go right to an example.

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.)

Enough about requirements. Let's move on to some code. Figure 14-2 shows two screen shots of a simple Pocket PC application called HelloPPC. The left image shows the window with the 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 the size and position.

 Figure 14-3 shows the source code for HelloPPC. Fundamentally, what you'll notice about HelloPPC is that it is predominantly a standard Windows CE application. The differences between this code and that shown in 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.
HelloPPC.rc

//======================================================================
// Resource file
//
// Written for the book Programming Windows CE
// Copyright (C) 2001 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) 2001 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
//
int InitApp (HINSTANCE);
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) 2001 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 application.
    rc = InitApp (hInstance);
    if (rc) return rc;
 
    // 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);
 }
//---------------------------------------------------------------
// InitApp - Application initialization
//
int InitApp (HINSTANCE hInstance) {
    WNDCLASS wc;
 
    // Allow only one instance of the application.
    HWND hWnd = FindWindow (szAppName, NULL);
    if (hWnd) {
        SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));    
        return -1;
    }
    // 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 1;
    return 0;
}
//------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow) {
    HWND hWnd;
 
    // Save program instance handle in global variable.
    hInst = hInstance;
 
    // 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))) 
        cy -= 26;  // Height of menu bar control
    
    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;
}

Figure 14-3 The HelloPPC application

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 on 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.

Use a Menu Bar, not a Command Bar

The next few changes to HelloPPC are all in the WM_CREATE message handler. Instead of creating a command bar or command band control, a Pocket PC application creates a menu bar control. 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. I'll fully describe the workings of the menu bar later in the chapter. For now, remember that the menu bar is supported only in the Pocket PC, so if you want to make an application that runs on both a Handheld PC and a Pocket PC, you will have to dynamically load and call the ShCreateMenuBar function.

 

 

Building HelloPPC

The HelloPPC project files are based on the Pocket PC application template project. This is a different project template from the other examples in this book. However, the differences between the Windows CE application project template and the Pocket PC application project template are quite minor.

When you decide to base your project on the Pocket PC application template, eMbedded Visual C++ links an additional library, aygshell.lib, to the program. This library resolves the Pocket PC–specific functions such as SHCreateMenuBar, SHHandleWMActivate, and SHHandleWMSettingChange. There are other differences between the way that Pocket PC and other Windows CE devices are handled which aren't dependent on the project template that's used. For example, when you select the Pocket PC as the target device, the compiled file is automatically downloaded to the Windows CE device's \Windows\Start Menu directory, instead of downloading to the root directory.

One issue I haven't yet mentioned is that for a number of examples you need to create a menu bar—and in some cases a menu—if you want to correctly run these applications on the Pocket PC. I did not want one project for the Windows CE systems example and a separate project for the Pocket PC. To avoid this, and to avoid adding extra code to explicitly load the Pocket PC functions, code is conditionally compiled into the application that instructs the linker to link the aygshell library when compiling for a Pocket PC target. The following code is taken from the KeyTrac example in

#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.

The Menu Bar

Clearly one of the major differences between a Pocket PC application and other Windows CE applications is the menu bar control. This control, unique to the Pocket PC, provides a command bar–like function, yet has a different programming interface and is managed differently by the system. The menu bar control is a subtly complex control that does not lend itself to manual programming. The designers of the menu bar control seem to have intended that most programming and resource generation for the menu bar control would be done through code wizards and the resource editor. While this is the way most Windows programmers code, it's still important to know how the menu bar control actually works, especially for situations in which the tools aren't quite up to the job. For this reason, I'm going to present the menu bar at the basic API level in this section. I can therefore present exactly what the control is looking for, especially in the way of resources. For later examples in the chapter, I'll use the code wizards to generate the menu bar menus.

Before I jump into programming the menu bar, I'd like to say a few words about how the control is designed. The menu bar control differs in a number of ways from the command bar control used on other Windows CE systems. First, the menu is not managed as a single unit on the menu bar. Instead, while the menu is specified as a single resource, it is managed by the menu bar as a series of separate submenus. Each submenu is displayed as a properly positioned pop-up menu when a particular button on the menu bar is tapped. So in this sense, the menu bar is more like a toolbar than its cousin the command bar.

A user sees little difference between a menu bar and a command bar because the menu buttons are positioned as expected—next to each other on the far left side of the bar. However, to the programmer, understanding this difference is the key to understanding how to manage and manipulate the menu bar.

Another difference is that unlike the command bar, the menu bar is not a true child of the window that creates it. The control itself is a pop-up window created by the system and placed at the bottom of the screen. The window that creates a menu bar can accidentally obscure the menu bar by covering it. Alternatively, parts of a menu bar can be drawn on top of its owner. To avoid this, the application must size its window to leave room for the menu bar on the desktop. This dance with the menu bar is why Pocket PC applications manually resize their main windows.

Creating a Menu Bar

Though I used a menu bar in the HelloPPC example, I didn't formally introduce the function and structure used to create it. To create a menu bar, call

BOOL SHCreateMenuBar (SHMENUBARINFO *pmb);

The only parameter is the address of an SHMENUBARINFO structure, which is defined as

typedef struct tagSHMENUBARINFO{
    DWORD cbSize; 
    HWND hwndParent; 
    DWORD dwFlags; 
    UINT nToolBarId; 
    HINSTANCE hInstRes; 
    int nBmpId; 
    int cBmpImages; 
    HWND hwndMB;
} SHMENUBARINFO;

The cbSize field must be filled with the size of the SHMENUBARINFO structure. The second field, hwndParent, should be set to the window that is creating the menu bar. The dwFlags field can be set to a combination of three flags:

SHCMBF_EMPTYBAR, used to create a menu bar with no menu.

SHCMBF_HIDDEN, which creates a menu bar that is initially hidden.

SHCMBF_HIDESIPBUTTON, which creates the menu bar without a SIP button on the right-hand side of the bar.

Unless you specify the SHCMBF_EMPTYBAR flag, you must set the nToolBarId field to the resource that describes the menu and button structure of the menu bar. This resource is not a simple menu resource. It is a combination of a generic resource data block and a menu resource that together describe the menus and the positions of the buttons on the menu bar. I'll describe this resource later in this section.

The next field, hInstRes, should be set to the instance handle of the module that contains the menu bar resource. The next two fields, nBmpId and cBmpImages, describe the bitmap images that can be used to define the look of buttons on the menu bar. If the menu bar is to have graphical buttons, you can set the field nBmpId to a bitmap resource ID. This bitmap should be 16 pixels in height and each image in the bitmap should be 16 pixels wide. Thus if the bitmap has three images, it should be 48 pixels wide by 16 pixels high. The cBmpImages field should be set to the number of images in the bitmap. For you graphic artists out there, the current Pocket PC guidelines indicate that graphics should present a simple, flat appearance instead of the three-dimensional shaded look used on the desktop applications and the H/PC.

The SHCreateMenuBar function returns TRUE if the menu bar was successfully created. If so, the hwndMB field of SHMENUBARINFO will contain the handle of the menu bar. You need to save this window handle since there is no other way to determine the menu bar handle after it has been created.

Working with a Menu Bar

Once you've created the menu bar, you still might need to configure it. While the menu bar looks different from a command bar, it is built upon the same toolbar foundation. So while you can't expect a menu bar to always act like a command bar, you can use some of the command bar functions and toolbar messages. For example, one handy feature of the common controls is that they contain a series of bitmaps for commonly used toolbar buttons. Instead of creating these images yourself—and thereby possibly creating a non-standard image—you can use the system-provided images for actions such as cut, copy, and paste.

Using the Common Control Bitmaps in a Menu Bar

To use the system-provided bitmaps, simply add them to the menu bar as you would add them to a command bar. These images are added to the menu bar after the addition of any bitmap specified in the SHMENUBARINFO structure when the menu bar was created. So, if you had a bitmap of three images, and you added the standard set of images, the Cut bitmap image would be specified as STD_CUT+3. (See  for details about how to add the predefined bitmap images to a command bar.) In the following code fragment, the menu bar is created and the set of standard images is added to the bar.

if (!SHCreateMenuBar(&mbi)) 
    return NULL;
CommandBar_AddBitmap (mbi.hwndMB, HINST_COMMCTRL, 
                      IDB_STD_SMALL_COLOR,        
                      STD_PRINT, 16, 16);

The simplest way to use these images is to specify the correct index in the button item in the menu bar resource. Remember that the first field in the menu bar item resource is the index to the bitmap image. Just set that bitmap index to point to the proper bitmap for the button.

Working with Menu Bar Menus

Sometimes applications need to manipulate menus by setting or clearing check marks or by enabling or disabling items. The standard set of menu functions (CheckMenuItem, for example) works as expected on menus maintained by a menu bar. The trick is to get the handle of the menu so that you can modify its items. The menu bar supports three messages you can use to get and set menu handles: SHCMBM_GETMENU, SHCMBM_GETSUBMENU, and SHCMBM_SETSUBMENU. The messages SHCMBM_GETMENU and SHCMBM_GETSUBMENU can be sent to the menu bar to query the menu handle or a specific submenu. The following line shows how to query the root menu handle using SHCMBM_GETMENU.

hMenu = (HMENU)SendMessage (hwndMenuBar, SHCMBM_GETMENU, 0, 0);

You can then use this menu handle to modify any of the menu items that the menu bar might display. To query a submenu attached to a specific menu bar item, use SHCMBM_GETSUBMENU, as in

hSubMenu = (HMENU)SendMessage (hwndMenuBar, SHCMBM_GETSUBMENU, 0,
                               ID_VIEWMENU);

The lParam value is set to the ID of a specific button on the menu bar. In this example, the menu handle attached to the button with the ID_VIEWMENU ID value.

To change the menu of a particular button on the menu bar, you can use SHCMBM_SETSUBMENU with wParam set to the ID of the button and lParam set to the new menu handle, as in

hOldMenu = (HMENU)SendMessage (hwndMenuBar, SHCMBM_SETSUBMENU,  
                                ID_VIEWMENU, (LPARAM)hNewMenu);

Dialog Boxes

In my experience, creating a well-designed dialog box is one of the programmer's more difficult tasks. The problem lies in presenting the user with an intuitive interface that allows quick interaction with an application. The task is doubly difficult on a Pocket PC, which has a small screen and a keyboard that keeps popping up over the bottom third of the screen. In this section, I'll explain creating dialog boxes and the assistance that the Pocket PC shell provides. However, it is always good to remember the cardinal rule: keep it simple. The Pocket PC provides a number of functions that help with dialog boxes, but the best programs don't use all these functions at once.

Since the Pocket PC is based on Windows CE, dialog boxes act by default as they do in any Windows system: They are created with the standard Win32 functions such as CreateDialog, they are created by the dialog manager based on dialog box resource templates, and they have dialog box procedures. However, the user interface guidelines for the Pocket PC specify that dialog boxes should be full screen so as not to confuse the user. In addition, property sheets on the Pocket PC have their tabs on the bottom of the window instead of the top. Windows CE doesn't support these characteristics by default; conveniently though, the Pocket PC provides extensions to assist the developer.

Full-Screen Dialog Boxes

To assist programmers in creating full-size dialog boxes, the Pocket PC shell implements a function named SHInitDialog. As the name implies, the function should be called during the handling of the WM_INITDIALOG message. The function is prototyped as

BOOL SHInitDialog (PSHINITDLGINFO pshidi);

The function takes a single parameter, a point to an SHINITDLGINFO structure defined as

typedef struct tagSHINITDIALOG{ 
   DWORD dwMask; 
   HWND hDlg; 
   DWORD dwFlags; 
} SHINITDLGINFO;

The dwMask field must be set to the single flag currently supported, SHIDIM_FLAGS. The hDlg field should be set to the window handle of the dialog. The third parameter, dwFlags, specifies a number of different initialization options. The SHIDIF_DONEBUTTON specifies that the navigation bar across the top of the screen contain an OK button in the upper right corner. This flag is typically set since the user interface guidelines specify that dialogs have an OK button in the navigation bar, and the guidelines specify that there be no Cancel button. While one could argue with this specification, the user interface provides no automatic way to provide a Cancel button.

The SHIDIF_SIPDOWN flag closes the SIP when the dialog is displayed. This flag should be set for informational dialogs that have no text input fields. Note that the absence this flag doesn't automatically display the SIP. It simply means that the state of the SIP remains unchanged when the dialog box is displayed.

Three other flags can be set in the dwFlags field:

These flags deal with how the dialog box will be sized. The SHIDIF_SIZEDLG flag tells the system to size the dialog box depending on the state of the SIP. If the SIP is displayed, the dialog box will be sized to fit above the SIP. If the SIP is hidden, the dialog will be sized to fit just above the menu bar. If, however, you have a floating SIP, the dialog box doesn't size correctly. This is a rare occurrence, since neither of the bundled input methods that ship with the Pocket PC can be undocked. However, the example input method in  does have the ability to float.

The SHIDIF_SIZEDLGFULLSCREEN and SHIDIF_FULLSCREENNOMENUBAR flags size the dialog to fit the entire screen regardless of the state of the SIP. The difference between the two flags is that SHIDIF_FULLSCREENNOMENUBAR does not leave room for the menu bar at the bottom of the screen.

Input Dialogs

In general, it's helpful to divide dialogs into information dialogs and input dialogs. Information dialogs deliver information to the user and for the most part don't need text input. Input dialogs are dialogs that require lines of text to be entered, such as passwords or IP addresses. For input dialogs, you can group the controls in the top two thirds of the dialog so that those fields aren't covered up by the SIP, which will almost always be displayed.

Whether the dialog is an input dialog or an informational dialog, another Pocket PC function that is typically called during WM_INITDIALOG is

BOOL SHSipPreference (HWND hwnd, SIPSTATE st);

This function sets the preferred state of the SIP. I say preferred state since the action of this function depends on the state of the SIP previous to when it was called. The two parameters are the handle to the window, which can be a dialog box or a custom control, and a set of SIP state flags listed here:

SHSipPreference is quite useful for writing custom controls that require SIP input. When the control receives the focus, it can call SHSipPreference to request the SIP be displayed. When the control loses the focus, it can call SHSipPreference to request the SIP be hidden. If the control receiving focus then calls SHSipPreference to display the SIP, this call will override the request to hide the SIP and the SIP will remain displayed without an annoying flash of the SIP.

If the dialog is an informational dialog, the call to SHSipPreference requests that the SIP be lowered. The dialog box can then display information in the entire area of the dialog. However, using SHInitDialog and SHSipPreference doesn't change the state of the SIP when the dialog is displayed. The dialog box should handle the WM_ACTIVEATE message and call SHHandleWMActivate, as in the HelloPPC example earlier in the chapter. This call ensures that if the user switches away from the dialog and displays the SIP in another application, switching back to the informational dialog will hide the SIP.

For input dialogs, managing the SIP is somewhat more difficult. You must display the SIP as needed when the focus window is a control that requires text input. The Pocket PC provides a couple of ways to interactively manage the SIP for your dialog. First, the dialog box can display the SIP when the dialog is created and keep it up for the life of the dialog. Another technique is to display the SIP only when the user is working with a control that requires keyboard input.

To display the SIP and keep it displayed while the dialog has focus, simply insert a call to the function SHInputDialog in your dialog procedure so that it is called for every message sent to the dialog box. The function prototype for SHInputDialog is

void SHInputDialog (HWND hwnd, UINT uMsg, WPARAM wParam);

The parameters are the window handle, message, and wParam for the current message. This helper function appropriately commands the SIP to show or hide, depending on whether the dialog box is gaining or losing focus.

To have the SIP interactively show and hide itself depending on the control that has focus in the dialog box, you use a special control, WC_SIPPREF, which can be inserted into a dialog box. Typically you'll do this by specifying a line in the dialog box template. The resource editor doesn't insert this control by default. You must either insert it by inserting a User Control in the dialog box editor or by manually editing the dialog box resource. Editing the resource file manually might be more reliable because the WM_SIPPREF control must be the last control specified in the dialog box template. Adding the control is as simple as inserting the following text as the last line in the dialog box template:

CONTROL         "",-1,"SIPPREF",NOT WS_VISIBLE,-10,-10,6,6

Since this control is one of the Pocket PC special controls, your application must initialize it by calling

BOOL SHInitExtraControls (void);

SHInitExtraControls should be called once during your application's initialization to initialize any of the Pocket PC special controls such as CAPEDIT and SIPPREF.

AutoRun

The Pocket PC has a feature that can automatically launch an application when any new external storage is detected such as the insertion of a CompactFlash card or PCMCIA card. This feature is typically used to provide an auto-install feature for software. However, there is no reason the application launched has to be an installation program.

When the system detects that a storage card has been inserted, it looks in the root directory of that card for a directory with a specific name. If that directory exists and contains an application named autorun.exe, the application is first copied to the \windows directory, and then launched with a command line string install. When the card is removed, the copy of autorun in the \windows directory is again launched, this time with a command line of uninstall.

The directory that the Pocket PC searches for depends on the type of CPU in the device because an application must be compiled specifically for a CPU. The autorun directory names match the CPU type value returned from the GetSystemInfo function. The following list shows the values for a few of the more popular CPUs. All the CPU values are defined in winnt.h.

  • MIPS (41xx series and 3910)

4000

  • SH3

10003

  • SH4

10005

  • Motorola 821

821

  • StrongARM

2577

If the Pocket PC doesn't find the appropriate directory, the device looks for an additional directory named 0. If this directory exists, the autorun.exe application contained within it is assumed to be a Common Executable Format (CEF) file and is copied to the Windows directory and launched.

NOTE
CEF (pronounced keff) stands for Common Executable Format. This is a CPU neutral executable type that is converted to native code when the application is launched. CEF is unrelated to the Common Language Runtime supported by Microsoft .NET.

When autorun.exe is launched, it might need to know which directory it was copied from on the storage card. The application can't use GetModuleFileName since it was copied and launched from the \windows directory. To determine the fully specified autorun path, an application can call

BOOL SHGetAutoRunPath (LPTSTR pAutoRunPath);

The single parameter is the address of a TCHAR buffer of at least MAX_PATH characters. The function will fail if no storage card is found. If a card is inserted, the appropriate CPU specific directory exists, and autorun.exe is found within that directory, that CPU specific directory is returned. For example, for a Pocket PC with an SH3 CPU and an autorun.exe file in the appropriate directory, the directory returned is \storage card\10003\autorun.exe.

If the directory 10003 didn't exist or autorun.exe wasn't found within the directory, SHGetAutoRunPath would return \storage card\0\autorun.exe,even if there's no 0 directory on the storage card. If no storage card is inserted in the system, SHGetAutoRunPath returns FALSE, indicating no autorun path exists.

Additional Pocket PC Shell Functions

The Pocket PC has a few functions provided to support applications. Most of these functions are unique to the Pocket PC and are available to solve specific issues that Pocket PC applications need to deal with occasionally. The SHFullScreen function allows an application to control the visibility of items such as the Start icon on the navigation bar, the navigation bar itself, and the SIP button. The function is prototyped as

BOOL SHFullScreen (HWND hwndRequester, DWORD dwState);

The first parameter is the handle of the window requesting the change. The dwState parameter can be a combination of the following:

The flags that hide the navigation bar, SIP, and the Start icon can be passed only if the handle passed in the first parameter of SHFullScreen is the handle to the foreground window.

Another handy function allows an application to request that the system free a specified amount of memory so that memory can be allocated. The function is

BOOL SHCloseApps (DWORD dwMemSought);

This parameter is the amount of memory that the application needs. When this function is called, the Pocket PC checks the current memory state to determine whether the amount of memory requested is available. If so, the function returns immediately. If not, the Pocket PC uses various methods, including closing applications, to attempt to free that amount of memory. SHCloseApps will return TRUE if the amount of memory is available and FALSE if it could not free the amount requested. Because this function closes applications and therefore must wait for each application to properly shut down, it can take a few seconds to complete.

The Game API

Windows CE devices sport microprocessors of surprising power. These small CPUs provide the oomph to support a full 32-bit operating system with virtual memory, an extensive window manager, and a RAM-based, transaction-based file system. For game developers, this would be nirvana—if only the operating system weren't there. Game developers love powerful CPUs but they dislike the layers of operating systems that, though helpful to the typical developer, hinder the developer who likes to write code directly to the hardware. To provide a path to the hardware, the Pocket PC is the first Windows CE system to support the Game API (GAPI), a lightweight set of functions to provide the game developer access to the screen and keyboard of a Windows CE device.

GAPI isn't DirectX, which provides a much more extensive set of functions to the game developer. While Windows CE supports Direct X, Microsoft decided not to provide the DirectX support on the Pocket PC. In an attempt to make up for this slight, GAPI is supported instead.

GAPI contains a handful of functions that provides access to the display's frame buffer, the area of memory that holds the pixel information displayed on screen. In addition, GAPI enables an application to assume control of all the buttons in a Pocket PC, even those that are normally captured by the shell. Finally, and perhaps most important, GAPI provides information about the display and the button layout in a consistent way across the divergent hardware provided by different Pocket PC manufacturers.

GAPI is provided as a single DLL, GX.DLL. This DLL is not distributed with Pocket PC devices. Instead, it is distributed by the application that uses it. When an application is installed, it should place GX.DLL in its install directory, not in the \windows directory. The current versions of GAPI don't support any type of versioning. Instead, an application is required to keep its own version of the GAPI DLL in its own application directory to avoid the problem lovingly called DLL Hell. In DLL Hell, one application installs an older copy of a shared DLL in the place of newer version of the DLL, thereby causing problems for the previously installed applications. While there are a number of ways to avoid DLL Hell—including some that require entire operating system revisions—the simplest solution is to distribute version-sensitive DLLs with the application and keep them in the application's directory. As it stands today, GX.DLL is smaller than 20K, so the overhead of maintaining a few of these DLLs in a system is not huge.

To build a GAPI application, the program must include gx.h, which specifies the function prototypes and necessary structures. To provide the proper DLL import information, the program must also link to gx.lib. These files are available from Microsoft. This book's companion CD also contains the necessary GAPI files.

Figure 14-10 lists the GAPI functions.

GXOpenDisplay

Initializes GAPI. Can be called only once in an application.

GXCloseDisplay

Closes GAPI. Cleans up GAPI resources.

GXBeginDraw

Called to access the frame buffer for drawing.

GXEndDraw

Called when drawing is complete.

GXGetDisplayProperties

Provides information on the display device.

GXOpenInput

Captures the buttons for the game.

GXCloseInput

Frees the buttons for normal use.

GXGetDefaultKeys

Provides information on the suggested buttons.

GXSuspend

Suspends GAPI subsystem to allow other applications to gain focus.

GXResume

Resumes GAPI operation when the game regains focus.

GXIsDisplayDRAMBuffer

Suspends GAPI operations.

GXSetViewport

Allows GDI drawing and GAPI access to the same frame buffer.

Figure 14-10 GAPI functions

GAPI Initialization

An application using GAPI must initialize the GAPI subsystem by calling the following function:

int GXOpenDisplay (HWND hWnd, DWORD dwFlags);

The two parameters are the handle to the application's window and a flag parameter that can either be 0 or the constant GX_FULLSCREEN. Using GX_FULLSCREEN indicates to GAPI that the application will assume control over the entire screen. If the flag isn't set, GAPI assumes the application won't be overwriting the navigation bar. GXOpenDisplay should be called only once during the life of an application. Subsequent calls will fail.

Getting Display Information

GAPI provides three functions to query the hardware support. The first function, GXGetDisplayProperties, returns information about the display and is prototyped as

GXDisplayProperties GXGetDisplayProperties();

The function returns a GXDisplayProperties structure, defined as

struct GXDisplayProperties {
    DWORD cxWidth;
    DWORD cyHeight;    
    long cbxPitch;    
    long cbyPitch;    
    long cBPP;    
    DWORD ffFormat;    
};

The first two fields, cxWidth and cyHeight, specify the width and height of the display in pixels. The next two fields, cbxPitch and cbyPitch, specify the distance, in bytes, between adjacent pixels in the frame buffer. For example, if the application has a pointer to pixel x and needs to address the pixel to the immediate right of the current pixel, the address would be at the current address plus the value in cbxPitch. To access the pixel immediately below the current pixel, the value in cbyPitch would be added to the address of the current pixel. These values aren't necessarily obvious and can even be negative depending on the layout of the frame buffer.

For frame buffers that have less than 8 bits per pixel (bpp), the addressing is somewhat more complex. In these cases, the pixel offset must be divided by the pixels per byte, which in a 4-bpp display is 8 / 4 = 2. So the formula to compute the address in the frame buffer of a pixel that has a 4-bpp display would be

pPxl = frame_base + ((x / 2) + (y * cbyPitch);

This line isn't complete. To get to the specific pixel, the application has to read the byte, modify only the appropriate upper or lower half, and then write the byte back. This example also assumes the frame buffer is in a portrait configuration, in which the adjacent bytes of the display are on the same row. In a landscape configuration, adjacent bytes are in the same column.

The final field in the GXDisplayProperties structure is the ffFormat field, which describes the format of the frame buffer. The flags in this field are

Querying Button Information

The next informational function, GXGetDefaultKeys, returns the suggested layout for the buttons. The prototype for this function is

GXKeyList GXGetDefaultKeys (int iOptions);

The one parameter is the system orientation: GX_NORMALKEYS for portrait orientation and GX_LANDSCAPEKEYS for landscape orientation.

The structure returned is defined as

struct GXKeyList {
  short vkUp;
  POINT ptUp;
  short vkDown;
  POINT ptDown;
  short vkLeft;
  POINT ptLeft;
  short vkRight;
  POINT ptRight;
  short vkA;
  POINT ptA;
  short vkB;
  POINT ptB;
  short vkC;
  POINT ptC;
  short vkStart;
  POINT ptStart;
};

Each field starting with vk in the structure specifies the suggested virtual key code to use for that action. The pt fields represent the physical coordinates of the buttons in relation to the screen.

Accessing the Buttons

When a GAPI application is ready to start its game, it can take control of the buttons on the Pocket PC by calling

int GXOpenInput();

This function redirects all button input to the GAPI application. Clearly, once this function is called it is the responsibility of the GAPI application to provide a way to quit the game and restore the buttons to the system.

Drawing to the Screen

Of course, the meat of GAPI is the ability it provides an application to write to the display buffer. To gain access to the buffer, a GAPI application calls

void * GXBeginDraw();

This function returns the address of the frame buffer, or 0 if the buffer cannot be accessed for some reason. At this point, a GAPI application has free reign to modify the frame buffer using the pixel computations described in the previous section.

The pointer returned isn't necessarily the lowest address of the frame buffer. Some systems are configured with negative offsets in the cbxPitch or cbyPitch values. This really isn't important as long as you rigorously use the pitch values to compute pixel addresses in the frame buffer.

NOTE
One word of caution: While having a pointer to the frame buffer is powerful, it is also dangerous. The pointer directly accesses an area of system memory that itself directly accesses the physical address space of the hardware. Errant pointers can, and most likely will, be destructive to data on your device. A classic symptom is the file system reporting corrupt data in the object store. This can easily happen if incorrect pointer arithmetic results in writing of the physical RAM that contains the object store. Programmers should be exceedingly careful when checking that they access only the frame buffer and not other parts of the system address space.

When the drawing to the frame buffer is complete, call the following function:

int GXEndDraw();

This call does little on systems with direct access to the frame buffer. However, on systems that don't provide direct access to the frame buffer, calling GXEndDraw signals the display driver to copy the data from the phantom frame buffer to the actual frame buffer. Regardless of whether the application has direct access to the frame buffer, all GAPI applications should call GXEndDraw, if only for forward compatibility.

Indirect Access to the Frame Buffer

On some systems, applications can't directly access the frame buffer using GAPI. For these systems, the display driver provides a phantom frame buffer for the application and then copies the data to the real frame buffer. While this scheme hinders performance somewhat, it does provide compatibility for GAPI applications. One side effect is that it is difficult for GAPI applications to merge their directly written pixel data with the GDI's pixel data, which is natively written to the frame buffer.

While many games just want to take over the entire display, some GAPI applications require that the system display GAPI data on one part of the display and paint standard Windows controls on the other part. To merge the two streams of data, GAPI provides a function called GXSetViewport to indicate what part of the screen the GAPI program controls. The display driver can then use the GAPI data for that area of the screen and the GDI data for the remainder of the frame buffer. The GXSetViewport function looks like this:

int GXSetViewport (DWORD dwTop, DWORD dwHeight, DWORD dwReserved1, 
                   DWORD dwReserved2);

The current implementation of GXSetViewport is somewhat limited in that it can describe only a band across the screen where the GAPI data will be written. The parameter dwTop specifies the first line on the display reserved for GAPI. Any lines above this value are written by the system. The dwHeight parameter is the height of the band of data, in lines, that the GAPI program will write. Any lines below dwTop+dwHeight will be written by GDI.

It's important to note that GXSetViewport doesn't clip data. It simply defines the area that GDI won't write. An errant GAPI application certainly can overwrite the screen area reserved for GDI.

To determine if the system is exposing a phantom frame buffer to GAPI instead of the real frame buffer, an application can call

BOOL GXIsDisplayDRAMBuffer();

This function returns TRUE if the application is using a phantom frame buffer and FALSE if the application will be accessing the actual frame buffer. An application can do little with this information except to ensure that it's calling GXSetViewport if it's mixing GAPI and GDI data and to indicate somewhat reduced performance for the dual buffer systems.

GAPI Maintenance

You can suspend the GAPI application in place to allow other application access to the screen and keyboard. The two functions that suspend and resume the GAPI functions are appropriately named

int GXSuspend();

and

int GXResume();

When the GAPI application calls GXSuspend, the GAPI library temporarily releases its control over the buttons in the system, allowing other applications to operate normally. The desktop is also redrawn. When GXResume is called, the buttons are redirected back to the GAPI application. The GAPI application is responsible for restoring the screen to the state it was in before GXSuspend was called. It's the responsibility of the GAPI application to stop accessing the frame buffer when another application gains the focus.

The suggested place for these two functions is in the WM_SETFOCUS and WM_KILLFOCUS message handlers of your main window. This way, if another application rudely interrupts your game by setting itself into the foreground, your application will handle it gracefully.

Cleaning Up

When the game has ended, a GAPI application should release the buttons by calling

int GXCloseInput();

In addition, the display should be release a call to

int GXCloseDisplay();

This function instructs the GAPI DLL to free any resources it was maintaining to support the frame buffer access of the application.

Programming Microsoft Windows CE. Sekond edition" by Douglas Boling: http://zabud.chat.ru/text/pwce.zip

Back