The Mouse and the Touch Screen
Unlike desktop PCs, Windows CE devices don't always have a mouse. Instead, many Windows CE devices have a touch screen and stylus combination. For Windows CE systems that do have a mouse, the programming interface is identical to the desktop.
Mouse Messages
Whenever the mouse cursor moves across the display, the topmost window at that point receives a WM_MOUSEMOVE message. If the user clicks the left or right mouse button, the window receives a WM_LBUTTONDOWN or LB_RBUTTONDOWN message. When the user releases the button, the window receives a WM_LBUTTONUP or WM_RBUTTONUP message. If the user presses and releases the mouse wheel, the window receives a WM_MBUTTONDOWN followed by a WM_MBUTTONUP message.For all of these messages, the wParam and lParam parameters are loaded with the same values. The wParam parameter contains a set of bit flags indicating whether the Ctrl or Shift keys on the keyboard are currently held down. As in other versions of Windows, the Alt key state isn't provided in these messages. To get the state of the Alt key when the message was sent, use the GetKeyState function.The lParam parameter contains two 16-bit values that indicate the position on the screen of the tap. The low-order 16 bits contain the x (horizontal) location relative to the upper left corner of the client area of the window, while the high-order 16 bits contain the y (vertical) position.If the user double-taps, that is, taps twice on the screen at the same location and within a predefined time, Windows sends a WM_LBUTTONDBLCLK message to the double-tapped window, but only if that window's class was registered with the CS_DBLCLKS style. The class style is set when the window class is registered with RegisterClass.You can differentiate between a tap and a double-tap by comparing the messages sent to the window. When a double-tap occurs, a window first receives the WM_LBUTTONDOWN and WM_LBUTTONUP messages from the original tap. Then a WM_LBUTTONDBLCLK is sent followed by another WM_LBUTTONUP. The trick is to refrain from acting on a WM_LBUTTONDOWN message in any way that precludes action on a subsequent WM_LBUTTONDBLCLK. This is usually not a problem because taps usually select an object, while double-tapping launches the default action for the object.
If the user rolls the mouse wheel, the window receives WM_MOUSEWHEEL messages. For this message, the contents of lParam is the same as the other mouse messages, the horizontal and vertical location of the mouse cursor. The low word of the wParam parameter contains the same bit flags indicating the the keys currently held down. The high work of wParam contains the distance the wheel was rotated expressed in multiples of a constant WHEEL_DELTA. If the value is positive, the rotation is away from the user. A negative value indicates the wheel was rotated back toward the user.
Working with the Touch Screen
The touch screen and stylus combination is relatively new to Windows platforms, but fortunately, its integration into Windows CE applications is relatively painless. The best way to deal with the stylus is to treat it as a single-button mouse. The stylus creates the same mouse messages that are provided by the mouse in other versions of Windows and by Windows CE systems that use a mouse. The differences that do appear between a mouse and a stylus are due to the different physical realities of the two input devices.Unlike a mouse, a stylus doesn't have a cursor to indicate its current position. Therefore, a stylus can't hover over a point on the screen in the way that the mouse cursor does. A cursor hovers when a user moves it over a window without pressing a mouse button. This concept can't be applied to programming for a stylus because the touch screen can't detect the position of the stylus when it isn't in contact with the screen.Another consequence of the difference between a stylus and a mouse is that without a mouse cursor, an application can't provide feedback to the user by means of changes in appearance of a hovering cursor. Touch screen–based Windows CE systems do support setting the cursor for one classic Windows method of user feedback. The busy hourglass cursor, indicating that the user must wait for the system to complete processing, is supported under Windows CE so that applications can display the busy hourglass in the same manner as applications running under other versions of Windows, using the SetCursor function.
Stylus Messages
When the user presses the stylus on the screen, the topmost window under that point receives the input focus if it didn't have it before and then receives a WM_LBUTTONDOWN message. When the user lifts the stylus, the window receives a WM_LBUTTONUP message. Moving the stylus within the same window while it's down causes WM_MOUSEMOVE messages to be sent to the window.
Inking
A typical application for a handheld device is capturing the user's writing on the screen and storing the result as ink. This process isn't handwriting recognition—simply ink storage. At first pass, the best way to accomplish this would be to store the stylus points passed in each WM_MOUSEMOVE message. The problem is that sometimes small CE-type devices can't send these messages fast enough to achieve a satisfactory resolution. Under Windows CE, a function call has been added to assist programmers in tracking the stylus.
BOOL GetMouseMovePoints (PPOINT pptBuf, UINT nBufPoints,
UINT *pnPointsRetrieved);
GetMouseMovePoints returns a number of stylus points that didn't result in WM_MOUSEMOVE messages. The function is passed an array of points, the size of the array (in points), and a pointer to an integer that will receive the number of points passed back to the application. Once received, these additional points can be used to fill in the blanks between the last WM_MOUSEMOVE message and the current one.GetMouseMovePoints does throw one curve at you. It returns points in the resolution of the touch panel, not the screen. This touch panel resolution is generally set at four times the screen resolution, so you need to divide the coordinates returned by GetMouseMovePoints by 4 to convert them to screen coordinates. The extra resolution helps programs such as handwriting recognizers.A short example program, PenTrac, illustrates the difference that GetMouseMovePoints can make. Figure 3-4 shows the PenTrac window. Notice the two lines of dots across the window. The top line was drawn using points from WM_MOUSEMOVE only. The second line included points that were queried with GetMouseMovePoints. The black dots were queried from WM_MOUSEMOVE, while the red (lighter) dots were locations queried with GetMouseMovePoints.

Figure 3-4: The PenTrac window showing two lines drawn
The source code for PenTrac is shown in Listing 3-2. The program places a dot on the screen for each WM_MOUSEMOVE or WM_LBUTTONDOWN message it receives. If the Shift key is held down during the mouse move messages, PenTrac also calls GetMouseMovePoints and marks those points in the window in red to distinguish them from the points returned by the mouse messages alone.
PenTrac cheats a little to enhance the effect of GetMouseMovePoints. The DoMouseMain routine, which handles WM_MOUSEMOVE and WM_LBUTTONDOWN messages, calls the function sleep to kill a few milliseconds. This delay simulates a slow-responding application that might not have time to process every mouse move message in a timely manner.Listing 3-2: The PenTrac program
PenTrac.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.
};
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
// Message handlers
LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoMouseMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);
PenTrac.cpp
//======================================================================
// PenTrac - Tracks stylus movement
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h> // For all that Windows stuff
#include "pentrac.h" // Program-specific stuff
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("PenTrac");
HINSTANCE hInst; // Program instance handle
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
WM_LBUTTONDOWN, DoMouseMain,
WM_MOUSEMOVE, DoMouseMain,
WM_DESTROY, DoDestroyMain,
};
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow) {
MSG msg;
int rc = 0;
HWND hwndMain;
// Initialize this instance.
hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
if (hwndMain == 0)
return 0x10;
// Application message loop
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
// Instance cleanup
return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitApp - Application initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow) {
WNDCLASS wc;
HWND hWnd;
#if defined(WIN32_PLATFORM_PSPC)
// If Pocket PC, allow only one instance of the application
hWnd = FindWindow (szAppName, NULL);
if (hWnd) {
SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));
return 0;
}
#endif
// Save program instance handle in global variable.
hInst = hInstance;
// Register application main window class.
wc.style = 0; // 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 = CreateWindowEx (WS_EX_NODRAG, szAppName, TEXT ("PenTrac"),
WS_VISIBLE | WS_CAPTION | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
// Return fail code if window not created.
if (!IsWindow (hWnd)) return 0;
// 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 MainWindow
//
//----------------------------------------------------------------------
// 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);
}
//----------------------------------------------------------------------
// DoMouseMain - Process WM_LBUTTONDOWN and WM_MOUSEMOVE messages
// for window.
//
LRESULT DoMouseMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
POINT pt[64];
POINT ptM;
UINT i, uPoints = 0;
HDC hdc;
ptM.x = LOWORD (lParam);
ptM.y = HIWORD (lParam);
hdc = GetDC (hWnd);
// If shift and mouse move, see if any lost points.
if (wMsg == WM_MOUSEMOVE) {
if (wParam & MK_SHIFT)
GetMouseMovePoints (pt, 64, &uPoints);
for (i = 0; i < uPoints; i++) {
pt[i].x /= 4; // Convert move pts to screen coords
pt[i].y /= 4;
// Covert screen coordinates to window coordinates
MapWindowPoints (HWND_DESKTOP, hWnd, &pt[i], 1);
SetPixel (hdc, pt[i].x, pt[i].y, RGB (255, 0, 0));
SetPixel (hdc, pt[i].x+1, pt[i].y, RGB (255, 0, 0));
SetPixel (hdc, pt[i].x, pt[i].y+1, RGB (255, 0, 0));
SetPixel (hdc, pt[i].x+1, pt[i].y+1, RGB (255, 0, 0));
}
}
// The original point is drawn last in case one of the points
// returned by GetMouseMovePoints overlaps it.
SetPixel (hdc, ptM.x, ptM.y, RGB (0, 0, 0));
SetPixel (hdc, ptM.x+1, ptM.y, RGB (0, 0, 0));
SetPixel (hdc, ptM.x, ptM.y+1, RGB (0, 0, 0));
SetPixel (hdc, ptM.x+1, ptM.y+1, RGB (0, 0, 0));
ReleaseDC (hWnd, hdc);
// Kill time to make believe we are busy.
Sleep(25);
return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
PostQuitMessage (0);
return 0;
}
Input Focus and Mouse Messages
Here are some subtleties to note about circumstances that rule how and when mouse messages initiated by stylus input are sent to different windows. As I mentioned previously, the input focus of the system changes when the stylus is pressed against a window. However, dragging the stylus from one window to the next won't cause the new window to receive the input focus. The down tap sets the focus, not the process of dragging the stylus across a window. When the stylus is dragged outside the window, that window stops receiving WM_MOUSEMOVE messages but retains input focus. Because the tip of the stylus is still down, no other window will receive the WM_MOUSEMOVE messages. This is akin to using a mouse and dragging the mouse outside a window with a button held down.To continue to receive mouse messages even if the stylus moves off its window, an application can call
HWND SetCapture (HWND hWnd);
passing the handle of the window to receive the mouse messages. The function returns the handle of the window that previously had captured the mouse or NULL if the mouse wasn't previously captured. To stop receiving the mouse messages initiated by stylus input, the window calls
BOOL ReleaseCapture (void);
Only one window can capture the stylus input at any one time. To determine whether the stylus has been captured, an application can call
HWND GetCapture (void);
which returns the handle of the window that has captured the stylus input or 0 if no window has captured the stylus input—although please note one caveat. The window that has captured the stylus must be in the same thread context as the window calling the function. This limitation means that if the stylus has been captured by a window in another application, GetCapture still returns 0.If a window has captured the stylus input and another window calls GetCapture, the window that had originally captured the stylus receives a WM_CAPTURECHANGED message. The lParam parameter of the message contains the handle of the window that has gained the capture. You shouldn't attempt to take back the capture by calling GetCapture in response to this message. In general, since the stylus is a shared resource, applications should be wary of capturing the stylus for any length of time and should be able to handle gracefully any loss of capture.Another interesting tidbit: Just because a window has captured the mouse, that doesn't prevent a tap on another window from gaining the input focus for that window. You can use other methods for preventing the change of input focus, but in almost all cases, it's better to let the user, not the applications, decide which top-level window should have the input focus.
Right-Button Clicks
When you click the right mouse button on an object in Windows systems, the action typically calls up a context menu, which is a stand-alone menu displaying a set of choices for what you can do with that particular object. On a system with a mouse, Windows sends WM_RBUTTONDOWN and WM_RBUTTONUP messages indicating a right-button click. When you use a stylus, you don't have a right button. The Windows CE guidelines, however, allow you to simulate a right-button click using a stylus. The guidelines specify that if a user holds down the Alt key while tapping the screen with the stylus, a program should act as if a right mouse button were being clicked and display any appropriate context menu. There's no MK_ALT flag in the wParam value of WM_LBUTTONDOWN, so the best way to determine whether the Alt key is pressed is to use GetKeyState with VK_MENU as the parameter and test for the most significant bit of the return value to be set. GetKeyState is more appropriate in this case because the value returned will be the state of the key at the time the mouse message was pulled from the message queue.
On systems without a keyboard, the tap-and-hold gesture is used to simulate a right mouse click. The function SHRecognizeGesture can be used on Pocket PCs and, with the proper shell componets, embedded Windows CE systems to detect a tap and hold. The function is prototyped as
WINSHELLAPI DWORD SHRecognizeGesture(SHRGINFO *shrg);
The only parameter is the address of a SHRGINFO structure defined as
typedef struct tagSHRGI {
DWORD cbSize;
HWND hwndClient;
POINT ptDown;
DWORD dwFlags;
} SHRGINFO, *PSHRGINFO;
The cbSize field must be filled with the size of the structure. The hwndClient field should be set to the handle of the window that is calling the function. The ptDown field is a structure that should be filled with the point where the gesture is being recognized. The dwFlags field can contain a number of flags. The SHRG_RETURNCMD flag causes the function to return GN_CONTEXTMENU if the user properly gestures with a tap and hold or zero otherwise. The SHRG_NOTIFYPARENT flag causes a WM_NOTIFY message to be sent to the parent window if the gesture is properly recognized. Finally, the SHRG_LONGDELAY flag requires the user to hold the tap for a longer period of time before the gesture is recognized.
The TicTac1 Example Program
To demonstrate stylus programming, I have written a trivial tic-tac-toe game. The TicTac1 window is shown in Figure 3-5. The source code for the program is shown in Listing 3-3. This program doesn't allow you to play the game against the computer, nor does it determine the end of the game—it simply draws the board and keeps track of the X's and O's. Nevertheless, it demonstrates basic stylus interaction.

Figure 3-5: The TicTac1 window
Listing 3-3: The TicTac1 program
TicTac1.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.
};
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
// Message handlers
LRESULT DoSizeMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoLButtonDownMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoLButtonUpMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);
// Game function prototypes
void DrawXO (HDC hdc, HPEN hPen, RECT *prect, INT nCell, INT nType);
void DrawBoard (HDC hdc, RECT *prect);
TicTac1.cpp
//======================================================================
// TicTac1 - Simple tic-tac-toe game
//
// 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 "tictac1.h" // Program-specific stuff
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("TicTac1");
HINSTANCE hInst; // Program instance handle
// State data for game
RECT rectBoard = {0, 0, 0, 0}; // Used to place game board.
RECT rectPrompt; // Used to place prompt.
BYTE bBoard[9]; // Keeps track of X's and O's.
BYTE bTurn = 0; // Keeps track of the turn.
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
WM_SIZE, DoSizeMain,
WM_PAINT, DoPaintMain,
WM_LBUTTONUP, DoLButtonUpMain,
WM_DESTROY, DoDestroyMain,
};
//======================================================================
//
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow) {
MSG msg;
HWND hwndMain;
// Initialize this instance.
hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
if (hwndMain == 0)
return 0x10;
// Application message loop
while (GetMessage (&msg, NULL, 0, 0)) {
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;
#if defined(WIN32_PLATFORM_PSPC)
// If Pocket PC, allow only one instance of the application.
hWnd = FindWindow (szAppName, NULL);
if (hWnd) {
SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));
return 0;
}
#endif
// Register application main window class.
wc.style = 0; // 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 = CreateWindowEx (WS_EX_NODRAG, szAppName, TEXT ("TicTac1"),
WS_VISIBLE | WS_CAPTION | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
// Return fail code if window not created.
if (!IsWindow (hWnd)) return 0;
// 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 MainWindow
//
//----------------------------------------------------------------------
// 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);
}
//----------------------------------------------------------------------
// DoSizeMain - Process WM_SIZE message for window.
//
LRESULT DoSizeMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
RECT rect;
INT i;
// Adjust the size of the client rect to take into account
// the command bar height.
GetClientRect (hWnd, &rect);
// Initialize the board rectangle if not yet initialized.
if (rectBoard.right == 0) {
// Initialize the board.
for (i = 0; i < dim(bBoard); i++)
bBoard[i] = 0;
}
// Define the playing board rect.
rectBoard = rect;
rectPrompt = rect;
// Layout depends on portrait or landscape screen.
if (rect.right - rect.left > rect.bottom - rect.top) {
rectBoard.left += 20;
rectBoard.top += 10;
rectBoard.bottom -= 10;
rectBoard.right = rectBoard.bottom - rectBoard.top + 10;
rectPrompt.left = rectBoard.right + 10;
} else {
rectBoard.left += 20;
rectBoard.right -= 20;
rectBoard.top += 10;
rectBoard.bottom = rectBoard.right - rectBoard.left + 10;
rectPrompt.top = rectBoard.bottom + 10;
}
return 0;
}
//----------------------------------------------------------------------
// DoPaintMain - Process WM_PAINT message for window.
//
LRESULT DoPaintMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
PAINTSTRUCT ps;
RECT rect;
HFONT hFont, hOldFont;
HDC hdc;
GetClientRect (hWnd, &rect);
hdc = BeginPaint (hWnd, &ps);
// Draw the board.
DrawBoard (hdc, &rectBoard);
// Write the prompt to the screen.
hFont = (HFONT)GetStockObject (SYSTEM_FONT);
hOldFont = (HFONT)SelectObject (hdc, hFont);
if (bTurn == 0)
DrawText (hdc, TEXT (" X's turn"), -1, &rectPrompt,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
else
DrawText (hdc, TEXT (" O's turn"), -1, &rectPrompt,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
SelectObject (hdc, hOldFont);
EndPaint (hWnd, &ps);
return 0;
}
//----------------------------------------------------------------------
// DoLButtonUpMain - Process WM_LBUTTONUP message for window.
//
LRESULT DoLButtonUpMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
POINT pt;
INT cx, cy, nCell = 0;
pt.x = LOWORD (lParam);
pt.y = HIWORD (lParam);
// See if pen on board. If so, determine which cell.
if (PtInRect (&rectBoard, pt)){
// Normalize point to upper left corner of board.
pt.x -= rectBoard.left;
pt.y -= rectBoard.top;
// Compute size of each cell.
cx = (rectBoard.right - rectBoard.left)/3;
cy = (rectBoard.bottom - rectBoard.top)/3;
// Find column.
nCell = (pt.x / cx);
// Find row.
nCell += (pt.y / cy) * 3;
// If cell empty, fill it with mark.
if (bBoard[nCell] == 0) {
if (bTurn) {
bBoard[nCell] = 2;
bTurn = 0;
} else {
bBoard[nCell] = 1;
bTurn = 1;
}
InvalidateRect (hWnd, NULL, FALSE);
} else {
// Inform the user of the filled cell.
MessageBeep (0);
return 0;
}
}
return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
PostQuitMessage (0);
return 0;
}
//======================================================================
// Game-specific routines
//
//----------------------------------------------------------------------
// DrawXO - Draw a single X or O in a square.
//
void DrawXO (HDC hdc, HPEN hPen, RECT *prect, INT nCell, INT nType) {
POINT pt[2];
INT cx, cy;
RECT rect;
cx = (prect->right - prect->left)/3;
cy = (prect->bottom - prect->top)/3;
// Compute the dimensions of the target cell.
rect.left = (cx * (nCell % 3) + prect->left) + 10;
rect.right = rect.right = rect.left + cx - 20;
rect.top = cy * (nCell / 3) + prect->top + 10;
rect.bottom = rect.top + cy - 20;
// Draw an X ?
if (nType == 1) {
pt[0].x = rect.left;
pt[0].y = rect.top;
pt[1].x = rect.right;
pt[1].y = rect.bottom;
Polyline (hdc, pt, 2);
pt[0].x = rect.right;
pt[1].x = rect.left;
Polyline (hdc, pt, 2);
// How about an O ?
} else if (nType == 2) {
Ellipse (hdc, rect.left, rect.top, rect.right, rect.bottom);
}
return;
}
//----------------------------------------------------------------------
// DrawBoard - Draw the tic-tac-toe board.
// VK_MENU
void DrawBoard (HDC hdc, RECT *prect) {
HPEN hPen, hOldPen;
POINT pt[2];
LOGPEN lp;
INT i, cx, cy;
// Create a nice thick pen.
lp.lopnStyle = PS_SOLID;
lp.lopnWidth.x = 5;
lp.lopnWidth.y = 5;
lp.lopnColor = RGB (0, 0, 0);
hPen = CreatePenIndirect (&lp);
hOldPen = (HPEN)SelectObject (hdc, hPen);
cx = (prect->right - prect->left)/3;
cy = (prect->bottom - prect->top)/3;
// Draw lines down.
pt[0].x = cx + prect->left;
pt[1].x = cx + prect->left;
pt[0].y = prect->top;
pt[1].y = prect->bottom;
Polyline (hdc, pt, 2);
pt[0].x += cx;
pt[1].x += cx;
Polyline (hdc, pt, 2);
// Draw lines across.
pt[0].x = prect->left;
pt[1].x = prect->right;
pt[0].y = cy + prect->top;
pt[1].y = cy + prect->top;
Polyline (hdc, pt, 2);
pt[0].y += cy;
pt[1].y += cy;
Polyline (hdc, pt, 2);
// Fill in X's and O's.
for (i = 0; i < dim (bBoard); i++)
DrawXO (hdc, hPen, &rectBoard, i, bBoard[i]);
SelectObject (hdc, hOldPen);
DeleteObject (hPen);
return;
}
The action in TicTac1 is centered around three routines: DrawBoard, DrawXO, and DoLButtonUpMain. The first two perform the tasks of drawing the playing board. The routine that determines the location of a tap on the board (and therefore is more relevant to our current train of thought) is DoLButtonUpMain. As the name suggests, this routine is called in response to a WM_LBUTTONUP message. The first action to take is to call
BOOL PtInRect (const RECT *lprc, POINT pt);
which determines whether the tap is even on the game board. The program knows the location of the tap because it's passed in the lParam value of the message. The board rectangle is computed when the program starts in DoSizeMain. Once the tap is localized to the board, the program determines the location of the relevant cell within the playing board by dividing the coordinates of the tap point within the board by the number of cells across and down.I mentioned that the board rectangle was computed during the DoSizeMain routine, which is called in response to a WM_SIZE message. While it might seem strange that Windows CE supports the WM_SIZE message common to other versions of Windows, it needs to support this message because a window is sized frequently: first right after it's created and then each time it's minimized and restored. You might think that another possibility for determining the size of the window would be during the WM_CREATE message. The lParam parameter points to a CREATESTRUCT structure that contains, among other things, the initial size and position of the window. The problem with using those numbers is that the size obtained is the total size of the window, not the size of the client area, which is what we need. Under Windows CE, most windows have no title bar and no border, but some have both and many have scroll bars, so using these values can cause trouble. So now, with the TicTac1 example, we have a simple program that uses the stylus effectively but isn't complete. To restart the game, we must exit and restart TicTac1. We can't take back a move or have O start first. We need a method for sending these commands to the program. Sure, using keys would work. Another solution would be to create hot spots on the screen that when tapped, provided the input necessary. Clearly this example needs some extra pieces to make it complete. I've taken the discussion of Windows as far as I can without a more complete discussion of the basic component of the operating system, the windows themselves. It's time to take a closer look at windows, child windows, and controls.