Figure 4-8 shows CHECKER3. This version of the program creates 25 child windows to process mouse clicks. It does not have a keyboard interface, but one could be easily added.
CHECKER3 has two window procedures called WndProc and ChildWndProc. WndProc is still the window procedure for the main (or parent) window. ChildWndProc is the window procedure for the 25 child windows. The names of both window procedures must appear as EXPORTS in the CHECKER3.DEF file because both procedures are called from Windows.
Because the window procedure is defined by the window class structure that you register with Windows using the RegisterClass call, the two window procedures in CHECKER3.C require two window classes. The first window class is for the main window and has the name ”Checker3“. The second window class is given the name ”Checker3_Child“.
Most of the fields of the wndclass structure variable are simply reused when ”Checker3_Child“ is registered in WinMain. The lpszClassName field is set to ”Checker3_Child“—the name of the class. The lpfnWndProc field is set to ChildWndProc, the window procedure for this window class, and the hIcon field is set to NULL, because icons are not used with child windows. For the ”Checker3_Child“ window class, the cbWndExtra field in the wndclass structure variable is set to 2 bytes, or more precisely, sizeof(WORD). This field tells Windows to reserve 2 bytes of extra space in a structure that Windows maintains for each window based on this window class. You can use this space to store information that may be different for each window.
CHECKER3.MAK
#------------------------
# CHECKER3.MAK make file
#------------------------
checker3.exe : checker3.obj checker3.def
link checker3, /align:16, NUL, /nod slibcew libw, checker3
rc checker3.exe
checker3.obj : checker3.c
cl -c -Gsw -Ow -W2 -Zp checker3.c
CHECKER3.C
/*-------------------------------------------------
CHECKER3.C -- Mouse Hit-Test Demo Program No. 3
(c) Charles Petzold, 1990
-------------------------------------------------*/
#include <windows.h>
#define DIVISIONS 5
long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;
long FAR PASCAL ChildWndProc (HWND, WORD, WORD, LONG) ;
char szChildClass[] = "Checker3_Child" ;
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
static char szAppName[] = "Checker3" ;
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 = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
RegisterClass (&wndclass) ;
wndclass.lpfnWndProc = ChildWndProc ;
wndclass.cbWndExtra = sizeof (WORD) ;
wndclass.hIcon = NULL ;
wndclass.lpszClassName = szChildClass ;
RegisterClass (&wndclass) ;
}
hwnd = CreateWindow (szAppName, "Checker3 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 HWND hwndChild [DIVISIONS] [DIVISIONS] ;
short cxBlock, cyBlock, x, y ;
switch (message)
{
case WM_CREATE :
for (x = 0 ; x < DIVISIONS ; x++)
for (y = 0 ; y < DIVISIONS ; y++)
{
hwndChild [x][y] = CreateWindow (szChildClass, NULL,
WS_CHILDWINDOW | WS_VISIBLE,
0, 0, 0, 0,
hwnd, y << 8 | x,
GetWindowWord (hwnd, GWW_HINSTANCE), NULL) ;
}
return 0 ;
case WM_SIZE :
cxBlock = LOWORD (lParam) / DIVISIONS ;
cyBlock = HIWORD (lParam) / DIVISIONS ;
for (x = 0 ; x < DIVISIONS ; x++)
for (y = 0 ; y < DIVISIONS ; y++)
MoveWindow (hwndChild [x][y],
x * cxBlock, y * cyBlock,
cxBlock, cyBlock, TRUE) ;
return 0 ;
case WM_LBUTTONDOWN :
MessageBeep (0) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
long FAR PASCAL ChildWndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
{
case WM_CREATE :
SetWindowWord (hwnd, 0, 0) ; // on/off flag
return 0 ;
case WM_LBUTTONDOWN :
SetWindowWord (hwnd, 0, 1 ^ GetWindowWord (hwnd, 0)) ;
InvalidateRect (hwnd, NULL, FALSE) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
Rectangle (hdc, 0, 0, rect.right, rect.bottom) ;
if (GetWindowWord (hwnd, 0))
{
MoveTo (hdc, 0, 0) ;
LineTo (hdc, rect.right, rect.bottom) ;
MoveTo (hdc, 0, rect.bottom) ;
LineTo (hdc, rect.right, 0) ;
}
EndPaint (hwnd, &ps) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
CHECKER3.DEF
;-------------------------------------
; CHECKER3.DEF module definition file
;-------------------------------------
NAME CHECKER3
DESCRIPTION 'Mouse Hit-Test Demo Program No. 3 (c) Charles Petzold, 1990'
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 8192
EXPORTS WndProc
ChildWndProc
The CreateWindow call in WinMain creates the main window based on the ”Checker3“ class. This is normal. However, when WndProc receives a WM_CREATE message, it calls CreateWindow 25 times to create 25 child windows based on the ”Checker3_Child“ window class. Here's a comparison of the parameters to the CreateWindow call in WinMain that creates the main window and the CreateWindow call in WndProc that creates the 25 child windows:
Parameter | Main Window | Child Window |
window class | ”Checker3“ | ”Checker3_Child“ |
window caption | ”Checker3...“ | NULL |
window style | WS_OVERLAPPEDWINDOW | WS_CHILDWINDOW | WS_VISIBLE |
horizontal position | CW_USEDEFAULT | 0 |
vertical position | CW_USEDEFAULT | 0 |
width | CW_USEDEFAULT | 0 |
height | CW_USEDEFAULT | 0 |
parent window handle | NULL | hwnd |
menu handle / child ID | NULL | y << 8 | x |
instance handle | hInstance | GetWindowWord (hwnd,
GWW_HINSTANCE) |
extra parameters | NULL | NULL |
Normally, the position, width, and height parameters are required for child windows, but in CHECKER3 the child windows are positioned and resized later in WndProc. The parent window handle is NULL for the main window because it is the parent. The parent window handle is required when using the CreateWindow call to create a child window.
The main window doesn't have a menu, so that parameter is NULL. For child windows, the same parameter position is called a ”child ID.“ This is a number that uniquely identifies the child window. The child ID becomes much more important when working with child window controls because messages to the parent window are identified by this child ID. For CHECKER3, I've used the child ID to identify the position in the 5-by-5 array that each child window occupies within the main window.
The instance handle is hInstance in both cases. When the child window is created, the hInstance value is extracted using the function GetWindowWord from the structure that Windows maintains for the window. (Rather than use GetWindowWord, I could have saved the value of hInstance in a global variable and used it directly.)
Each child window has a different window handle that is stored in the hwndChild array. When WndProc receives a WM_SIZE message, it calls MoveWindow for each of the 25 child windows. The parameters indicate the upper left corner of the child window relative to the parent window client-area coordinates, the width and height of the child window, and whether the child window needs repainting.
Now let's take a look at ChildWndProc. This window procedure processes messages for all 25 child windows. The hwnd parameter to ChildWndProc is the handle to the child window receiving the message. When ChildWndProc processes a WM_CREATE message (which will happen 25 times because there are 25 child windows), it uses SetWindowWord to store a 0 in the extra area reserved within the window structure. (Recall that we reserved this space by using the cbWndExtra field when defining the window class structure.) ChildWndProc uses this value to store the current state (X or no X) of the rectangle. When the child window is clicked, the WM_LBUTTONDOWN logic simply flips the value of this word (from 0 to 1 or from 1 to 0) and invalidates the entire child window client area. This area is the single rectangle being clicked. The WM_PAINT processing is trivial, because the size of the rectangle it draws is the same size as the client window.
Because the C source code file and the .EXE file of CHECKER3 are larger than those for CHECKER1 (to say nothing of my explanation of the programs), I will not try to convince you that CHECKER3 is ”simpler“ than CHECKER1. But note that we no longer have to do any mouse hit-testing! If a child window in CHECKER3 gets a WM_LBUTTONDOWN message, the window has been hit, and that's all it needs to know.
If you want to add a keyboard interface to CHECKER3, be aware that the main window still gets keyboard messages because it has the input focus. We'll explore child windows more in Chapter 6.