The CHECKER1 program, shown in Figure 4-4, demonstrates some simple hit-testing. The program divides the client area into a 5-by-5 array of 25 rectangles. If you click the mouse on one of the rectangles, the rectangle is filled with an X. If you click there again, the X is removed.
CHECKER1.MAK
#------------------------
# CHECKER1.MAK make file
#------------------------
checker1.exe : checker1.obj checker1.def
link checker1, /align:16, NUL, /nod slibcew libw, checker1
rc checker1.exe
checker1.obj : checker1.c
cl -c -Gsw -Ow -W2 -Zp checker1.c
CHECKER1.C
/*-------------------------------------------------
CHECKER1.C -- Mouse Hit-Test Demo Program No. 1
(c) Charles Petzold, 1990
-------------------------------------------------*/
#include <windows.h>
#define DIVISIONS 5
long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
static char szAppName[] = "Checker1" ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
if (!hPrevInstance)
{
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = NULL ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
RegisterClass (&wndclass) ;
}
hwnd = CreateWindow (szAppName, "Checker1 Mouse Hit-Test Demo",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, nCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
{
static BOOL fState[DIVISIONS][DIVISIONS] ;
static short cxBlock, cyBlock ;
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
short x, y ;
switch (message)
{
case WM_SIZE :
cxBlock = LOWORD (lParam) / DIVISIONS ;
cyBlock = HIWORD (lParam) / DIVISIONS ;
return 0 ;
case WM_LBUTTONDOWN :
x = LOWORD (lParam) / cxBlock ;
y = HIWORD (lParam) / cyBlock ;
if (x < DIVISIONS && y < DIVISIONS)
{
fState [x][y] ^= 1 ;
rect.left = x * cxBlock ;
rect.top = y * cyBlock ;
rect.right = (x + 1) * cxBlock ;
rect.bottom = (y + 1) * cyBlock ;
InvalidateRect (hwnd, &rect, FALSE) ;
}
else
MessageBeep (0) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
for (x = 0 ; x < DIVISIONS ; x++)
for (y = 0 ; y < DIVISIONS ; y++)
{
Rectangle (hdc, x * cxBlock, y * cyBlock,
(x + 1) * cxBlock, (y + 1) * cyBlock) ;
if (fState [x][y])
{
MoveTo (hdc, x * cxBlock, y * cyBlock) ;
LineTo (hdc, (x+1) * cxBlock, (y+1) * cyBlock) ;
MoveTo (hdc, x * cxBlock, (y+1) * cyBlock) ;
LineTo (hdc, (x+1) * cxBlock, y * cyBlock) ;
}
}
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
CHECKER1.DEF
;-------------------------------------
; CHECKER1.DEF module definition file
;-------------------------------------
NAME CHECKER1
DESCRIPTION 'Mouse Hit-Test Demo Program No. 1 (c) Charles Petzold, 1990'
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 8192
EXPORTS WndProc
Figure 4-5 shows the CHECKER1 display. All 25 rectangles have the same width and height. These width and height values are stored in cxBlock and cyBlock and are recalculated when the size of the client area changes. The WM_LBUTTONDOWN logic uses the mouse coordinates to determine which rectangle has been clicked. It flags the current state of the rectangle in the array fState and invalidates the rectangle to generate a WM- PAINT message. If the width or height of the client area is not evenly divisible by five, a small strip of client area at the left or bottom will not be covered by a rectangle. For error processing, CHECKER1 responds to a mouse click in this area by calling MessageBeep.
When CHECKER1 receives a WM_PAINT message, it repaints the entire client area by drawing rectangles using the GDI Rectangle function. If the fState value is set,
CHECKER1 draws two lines using the MoveTo and LineTo functions. During WM_PAINT processing, CHECKER1 does not check the validity of each rectangular section before repainting it, but it could. One method for checking validity involves building a RECT structure for each rectangular block within the loop (using the same formulas as in the WM_LBUTTONDOWN logic) and checking whether it intersects the invalid rectangle (ps.rcPaint) by using the function IntersectRect. Another method is to use PtInRect to determine if any of the four corners of the rectangular block are within the invalid rectangle.