四、程序中的命中测试—CHECKER中的子窗口
程序7-4所示的CHECKER3程序,这一版本建立了25个处理鼠标单击的子窗口。它没有键盘接口,但是可以按本章后面的CHECKER4程序范例的方法添加。
CHECKER3.C
/*---------------------------------------------------------------------------
CHECKER3.C -- Mouse Hit-Test Demo Program No. 3
(c) Charles Petzold, 1998
----------------------------------------------------------------------------*/
#include <windows.h>
#define DIVISIONS 5
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
LRESULT CALLBACK ChildWndProc (HWND, UINT, WPARAM, LPARAM) ;
TCHAR szChildClass[] = TEXT ("Checker3_Child") ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("Checker3") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
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 = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox ( NULL, TEXT ("Program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
wndclass.lpfnWndProc = ChildWndProc ;
wndclass.cbWndExtra = sizeof (long) ;
wndclass.hIcon = NULL ;
wndclass.lpszClassName = szChildClass ;
RegisterClass (&wndclass) ;
hwnd = CreateWindow (szAppName, TEXT ("Checker3 Mouse Hit-Test Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
static HWND hwndChild[DIVISIONS][DIVISIONS] ;
int 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, (HMENU) (y << 8 | x),
(HINSTANCE) GetWindowLong (hwnd, GWL_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) ;
}
LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
{
case WM_CREATE :
SetWindowLong (hwnd, 0, 0) ; // on/off flag
return 0 ;
case WM_LBUTTONDOWN :
SetWindowLong (hwnd, 0, 1 ^ GetWindowLong (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 (GetWindowLong (hwnd, 0))
{
MoveToEx (hdc, 0, 0, NULL) ;
LineTo (hdc, rect.right, rect.bottom) ;
MoveToEx (hdc, 0, rect.bottom, NULL) ;
LineTo (hdc, rect.right, 0) ;
}
EndPaint (hwnd, &ps) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
CHECKER3有两个窗口消息处理程序WndProc和ChildWndProc。WndProc还是主(或父)窗口的窗口消息处理程序。ChildWndProc是针对25个子窗口的窗口消息处理程序。这两个窗口消息处理程序都必须定义为CALLBACK函数。
因为窗口消息处理程序与特定的窗口类别结构相关联,该窗口类别结构由Windows呼叫RegisterClass函数来注册,CHECKER3需要两个窗口类别。第一个窗口类别用于主窗口,名为「Checker3」。第二个窗口类别名为「Checker3_Child」。当然,您不必选择像这样有意义的名字。
CHECKER3在WinMain函数中注册了这两个窗口类别。注册完常规的窗口类别之后,CHECKER3只是简单地重新使用wndclass结构中的大多数的字段来注册Checker3_Child类别。无论如何,有四个字段根据子窗口类别而设定为不同的值:
-
pfnWndProc字段设定为ChildWndProc,子窗口类别的窗口消息处理程序。
-
cbWndExtra字段设定为4字节,或者更确切地用sizeof (long)。该字段告诉Windows在其为依据此窗口类别的窗口保留的内部结构中,预留了4字节额外的空间。您能使用此空间来保存每个窗口的可能有所不同的信息。
-
因为像CHECKER3中的子窗口不需要图标,所以hIcon字段设定为NULL 。
-
pszClassName字段设定为「Checker3_Child」,是类别的名称。
通常,在WinMain中,CreateWindow呼叫建立依据Checker3类别的主窗口。然而,当WndProc收到WM_CREATE消息后,它呼叫CreateWindow 25次以建立25个Checker3_Child类别的子窗口。表7-3是在WinMain中CreateWindow呼叫的参数,与在建立25个子窗口的WndProc中CreateWindow呼叫的参数间的比较。
|
表7-3 |
|
参数 |
主窗口 |
子窗口 |
|
窗口类别 |
「Checker3」 |
「Checker3_Child」 |
|
窗口标题 |
「Checker3...」 |
NULL |
|
窗口样式 |
WS_OVERLAPPEDWINDOW |
WS_CHILDWINDOW | WS_VISIBLE |
|
水平位置 |
CW_USEDEFAULT |
0 |
|
垂直位置 |
CW_USEDEFAULT |
0 |
|
宽度 |
CW_USEDEFAULT |
0 |
|
高度 |
CW_USEDEFAULT |
0 |
|
父窗口句柄 |
NULL |
hwnd |
|
菜单句柄/子ID |
NULL |
(HMENU) (y << 8 | x) |
|
执行实体句柄 |
hInstance |
(HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE) |
|
额外参数 |
NULL |
NULL |
一般情况下,子窗口要求有关位置和大小的参数,但是在CHECKER3中的子窗口由WndProc确定位置和大小。对于主窗口,因为它本身就是父窗口,所以它的父窗口句柄是NULL。当使用CreateWindow呼叫来建立一个子窗口时,就需要父窗口句柄了。
主窗口没有菜单,因此参数是NULL。对于子窗口,相同位置的参数称为子ID(或子窗口ID)。这是唯一代表子窗口的数字。像我们在第十一章将看到的一样,在处理对话框的子窗口控件时,子ID显得更为重要。对于CHECKER3来说,我只是简单地将子ID设定为一个数值,该数值是每个子窗口在5×5的主窗口中的x和y位置的组合。
CreateWindow函数需要一个执行实体句柄。在WinMain中,执行实体句柄可以很容易地取得,因为它是WinMain的一个参数。在建立子窗口时, CHECKER3必须用GetWindowLong来从Windows为窗口保留的结构中取得hInstance值(相对于GetWindowLong,我也能将hInstance的值保存到整体变量,并直接使用它)。
每一个子窗口都在hwndChild数组中保存了不同的窗口句柄。当WndProc接收到一个WM_SIZE消息后,它将为这25个子窗口呼叫MoveWindow。MoveWindow的参数表示子窗口左上角相对于父窗口显示区域的坐标、子窗口的宽度和高度以及子窗口是否需要重画。
现在让我们看一下ChildWndProc。此窗口消息处理程序为所有这25个子窗口处理消息。ChildWndProc的hwnd参数是子窗口接收消息的句柄。当ChildWndProc处理WM_CREATE消息时(因为有25个子窗口,所以要发生25次),它用SetWindowWord在窗口结构保留的额外区域中储存一个0值(通过在定义窗口类别时使用的cbWndExtra来保留的空间)。ChildWndProc用此值来恢复目前矩形的状态(有X或没有X)。在子窗口中单击时,WM_LBUTTONDOWN处理例程简单地修改这个整数值(从0到1,或从1到0),并使整个子窗口无效。此区域是被单击的矩形。WM_PAINT的处理很简单,因为它所绘制的矩形与显示区域一样大。
因为CHECKER3的C原始码文件和.EXE文件比CHECKER1的大(更不用说程序的说明了),我不会试着告诉你说CHECKER3比CHECKER1更简单。但请注意,我们没有做任何的鼠标命中测试!我们所要的,就是知道CHECKER3中是否有个子窗口得到了命中窗口的WM_LBUTTONDOWN消息。
