四、程序中的命中测试—子窗口和键盘
为CHECKER3添加键盘接口就像CHECKER系列构想中的最后一步。但在这样做的时候,可能有更适当的做法。在CHECKER2中,鼠标光标的位置决定按下Spacebar键时哪个区域将获得标记符号。当我们处理子窗口时,我们能从对话框功能中获得提示。在对话框中,带有闪烁的插入符号或点划的矩形的子窗口表示它有输入焦点(当然也可以用键盘进行定位)。
我们不需要把Windows内部已有的对话框处理方式重新写过,我只是要告诉您大致上应该如何在应用程序中仿真对话框。研究过程中,您会发现这样一件事:父窗口和子窗口可能要共享同键盘消息处理。按下Spacebar键和Enter键时,子窗口将锁定复选标记。按下方向键时,父窗口将在子窗口之间移动输入焦点。实际上,当您在子窗口上单击时,情况会有些复杂,这时是父窗口而不是子窗口获得输入焦点。
CHECKER4.C如程序7-5所示。
CHECKER4.C
/*---------------------------------------------------------------------------
CHECKER4.C -- Mouse Hit-Test Demo Program No. 4
(c) Charles Petzold, 1998
---------------------------------------------------------------------------*/
#include <windows.h>
#define DIVISIONS 5
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
LRESULT CALLBACK ChildWndProc (HWND, UINT, WPARAM, LPARAM) ;
int idFocus = 0 ;
TCHAR szChildClass[] = TEXT ("Checker4_Child") ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("Checker4") ;
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 ("Checker4 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 ;
// On set-focus message, set focus to child window
case WM_SETFOCUS:
SetFocus (GetDlgItem (hwnd, idFocus)) ;
return 0 ;
// On key-down message, possibly change the focus window
case WM_KEYDOWN:
x = idFocus & 0xFF ;
y = idFocus >> 8 ;
switch (wParam)
{
case VK_UP: y-- ; break ;
case VK_DOWN: y++ ; break ;
case VK_LEFT: x-- ; break ;
case VK_RIGHT: x++ ; break ;
case VK_HOME: x = y = 0 ; break ;
case VK_END: x = y = DIVISIONS - 1 ; break ;
default: return 0 ;
}
x = (x + DIVISIONS) % DIVISIONS ;
y = (y + DIVISIONS) % DIVISIONS ;
idFocus = y << 8 | x ;
SetFocus (GetDlgItem (hwnd, idFocus)) ;
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_KEYDOWN:
// Send most key presses to the parent window
if (wParam != VK_RETURN && wParam != VK_SPACE)
{
SendMessage (GetParent (hwnd), message, wParam, lParam) ;
return 0 ;
}
// For Return and Space, fall through to toggle the square
case WM_LBUTTONDOWN :
SetWindowLong (hwnd, 0, 1 ^ GetWindowLong (hwnd, 0)) ;
SetFocus (hwnd) ;
InvalidateRect (hwnd, NULL, FALSE) ;
return 0 ;
// For focus messages, invalidate the window for repaint
case WM_SETFOCUS:
idFocus = GetWindowLong (hwnd, GWL_ID) ;
// Fall through
case WM_KILLFOCUS:
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
Rectangle (hdc, 0, 0, rect.right, rect.bottom) ;
// Draw the "x" mark
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) ;
}
// Draw the "focus" rectangle
if (hwnd == GetFocus ())
{
rect.left += rect.right / 10 ;
rect.right -= rect.left ;
rect.top += rect.bottom / 10 ;
rect.bottom -= rect.top ;
SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;
SelectObject (hdc, CreatePen (PS_DASH, 0, 0)) ;
Rectangle (hdc, rect.left, rect.top, rect.right, rect.bottom) ;
DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN))) ;
}
EndPaint (hwnd, &ps) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
您应该能回忆起每一个子窗口有唯一的子窗口ID,该ID在呼叫CreateWindow建立窗口时定义。在CHECKER3中,此ID是矩形的x和y位置的组合。一个程序可以通过下面的呼叫来获得一个特定子窗口的子窗口ID:
idChild = GetWindowLong (hwndChild, GWL_ID) ;
下面的函数也有同样的功能:
idChild = GetDlgCtrlID (hwndChild) ;
正如函数名称所表示的,它主要用于对话框和控制窗口。如果您知道父窗口的句柄和子窗口ID,此函数也可以获得子窗口的句柄:
hwndChild = GetDlgItem (hwndParent, idChild) ;
在CHECKER4中,整体变量idFocus用于保存目前输入焦点窗口的子窗口ID。我在前面说过,当您在子窗口上面单击鼠标时,它们不会自动获得输入焦点。因此,CHECKER4中的父窗口将通过呼叫下面的函数来处理WM_SETFOCUS消息:
SetFocus (GetDlgItem (hwnd, idFocus)) ;
这样设定一个子窗口为输入焦点。
ChildWndProc处理WM_SETFOCUS和WM_KILLFOCUS消息。对于WM_SETFOCUS,它将保存在整体变量idFocus中接收输入焦点的子窗口ID。对于这两种消息,窗口是无效的,并产生一个WM_PAINT消息。如果WM_PAINT消息画出了有输入焦点的子窗口,则它将用PS_DASH画笔的风格画一个矩形以表示此窗口有输入焦点。
ChildWndProc也处理WM_KEYDOWN消息。对于除了Spacebar和Enter键以外的其它消息,WM_KEYDOWN都将给父窗口发送消息。另外,窗口消息处理程序也处理类似WM_LBUTTONDOWN消息的消息。
处理方向移动键是父窗口的事情。在风格相似的CHECKER2中,此程序可获得有输入焦点的子窗口的x和y坐标,并根据按下的特定方向键来改变它们。然后通过呼叫SetFocus将输入焦点设定给新的子窗口。
