一、Windows模态对话框—定义自己的控件
尽管Windows承揽了许多维护对话框和子窗口控件的工作,它同时也为您提供了各种加入程序代码的方法。前面我们已经看到了在对话框上绘图的方法。您也可以使用第九章中讨论的窗口子类别化来改变子窗口控件的操作。
您还可以定义自己的子窗口控件,并将它们用到对话框中。例如,假定您特别不喜欢普通的矩形按键,而倾向于建立椭圆形按键,那么您可以通过注册一个窗口类别,并使用自己编写的窗口消息处理程序处理来自您所建立窗口的消息,从而建立椭圆形按键。在Developer Studio中,您可以在与自订控件相联系的「Properties」对话框中指定这个窗口类别,这将转换成对话框模板中的CONTROL叙述。程序11-3所示的ABOUT3程序正是这样做的。
ABOUT3.C /*----------------------------------------------------------------------------- ABOUT3.C -- About Box Demo Program No. 3 (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ; LRESULT CALLBACK EllipPushWndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("About3") ; MSG msg ; HWND hwnd ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (hInstance, szAppName) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = EllipPushWndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = NULL ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = TEXT ("EllipPush") ; RegisterClass (&wndclass) ; hwnd = CreateWindow ( szAppName, TEXT ("About Box Demo Program"), 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 HINSTANCE hInstance ; switch (message) { case WM_CREATE : hInstance = ((LPCREATESTRUCT) lParam)->hInstance ; return 0 ; case WM_COMMAND : switch (LOWORD (wParam)) { case IDM_APP_ABOUT : DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc) ; return 0 ; } break ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG : return TRUE ; case WM_COMMAND : switch (LOWORD (wParam)) { case IDOK : EndDialog (hDlg, 0) ; return TRUE ; } break ; } return FALSE ; } LRESULT CALLBACK EllipPushWndProc (HWND hwnd, UINT message,WPARAM wParam, LPARAM lParam) { TCHAR szText[40] ; HBRUSH hBrush ; HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_PAINT : GetClientRect (hwnd, &rect) ; GetWindowText (hwnd, szText, sizeof (szText)) ; hdc = BeginPaint (hwnd, &ps) ; hBrush = CreateSolidBrush (GetSysColor (COLOR_WINDOW)) ; hBrush = (HBRUSH) SelectObject (hdc, hBrush) ; SetBkColor (hdc, GetSysColor (COLOR_WINDOW)) ; SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ; Ellipse (hdc, rect.left, rect.top, rect.right, rect.bottom) ; DrawText (hdc, szText, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; DeleteObject (SelectObject (hdc, hBrush)) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_KEYUP : if (wParam != VK_SPACE) break ;// fall through case WM_LBUTTONUP : SendMessage (GetParent (hwnd), WM_COMMAND, GetWindowLong (hwnd, GWL_ID), (LPARAM) hwnd) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
//Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Dialog ABOUTBOX DIALOG DISCARDABLE 32, 32, 180, 100 STYLE DS_MODALFRAME | WS_POPUP FONT 8, "MS Sans Serif" BEGIN CONTROL "OK",IDOK,"EllipPush",WS_GROUP | WS_TABSTOP,73,79,32,14 ICON "ABOUT3",IDC_STATIC,7,7,20,20 CTEXT "About3",IDC_STATIC,40,12,100,8 CTEXT "About Box Demo Program",IDC_STATIC,7,40,166,8 CTEXT "(c) Charles Petzold, 1998",IDC_STATIC,7,52,166,8 END ///////////////////////////////////////////////////////////////////////////// // Menu ABOUT3 MENU DISCARDABLE BEGIN POPUP "&Help" BEGIN MENUITEM "&About About3...", IDM_APP_ABOUT END END ///////////////////////////////////////////////////////////////////////////// // Icon ABOUT3 ICON DISCARDABLE "icon1.ico"
// Microsoft Developer Studio generated include file. // Used by About3.rc #define IDM_APP_ABOUT 40001 #define IDC_STATIC -1
ABOUT3.ICO |
我们所注册的窗口类别叫做「EllipPush」(椭圆形按键)。在Developer Studio的对话框编辑器中,删除「Cancel」和「OK」按钮。要添加依据此窗口类别的控件,请从「 Controls」工具列选择「Custom Control」。在此控件的「Properties」对话框的「 Class」字段输入「EllipPush」。在对话框模板中我们没有使用DEFPUSHBUTTON叙述,而是用CONTROL叙述来指定此窗口类别:
CONTROL "OK" IDOK, "EllipPush", TABGRP, 64, 60, 32, 14
当在对话框中建立子窗口控件时,对话框管理器把这个窗口类别用于CreateWindow呼叫中。
ABOUT3.C程序在WinMain中注册了EllipPush窗口类别:
wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = EllipPushWndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = NULL ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = TEXT ("EllipPush") ; RegisterClass (&wndclass) ;
该窗口类别指定窗口消息处理程序为EllipPushWndProc,在ABOUT3.C中正是这样。
EllipPushWndProc窗口消息处理程序只处理三种消息:WM_PAINT、WM_KEYUP和WM_LBUTTONUP。在处理WM_PAINT消息时,它从GetClientRect中取得窗口的大小,从GetWindowText中取得显示在按键上的文字,用Windows函数Ellipse和DrawText来输出椭圆和文字。
WM_KEYUP和WM_LBUTTONUP消息的处理非常简单:
case WM_KEYUP : if (wParam != VK_SPACE) break ; // fall through case WM_LBUTTONUP : SendMessage (GetParent (hwnd), WM_COMMAND, GetWindowLong (hwnd, GWL_ID), (LPARAM) hwnd) ; return 0 ;
窗口消息处理程序使用GetParent来取得其父窗口(即对话框)的句柄,并发送一个WM_COMMAND消息,消息的wParam等于控件的ID,这个ID是用GetWindowLong取得的。然后,对话框窗口消息处理程序将这个消息传给ABOUT3内的对话框程序,结果得到一个使用者自订的按键,如图11-3所示。您可以用同样的方法来建立其它自订对话框控件。
这就是全部要做的吗?其实不然。通常,对于维护子窗口控件所需要的处理而言,EllipPushWndProc只是一个空架子。例如,按钮不会像普通的按键那样闪烁。要翻转按键内的颜色,窗口消息处理程序必须处理WM_KEYDOWN(来自空格键)和WM_LBUTTONDOWN消息。窗口消息处理程序还必须在收到WM_LBUTTONDOWN消息时拦截鼠标,并且,如果当按钮还处于按下状态,而鼠标移到了子窗口的显示区域之外,那么得要释放鼠标拦截(并将按钮的内部颜色回复为正常状态)。只有在鼠标被拦截时松开该按钮,子窗口才会给其父窗口送回一个WM_COMMAND消息。
EllipPushWndProc也不处理WM_ENABLE消息。如上所述,对话框程序可以使用EnableWindow函数来禁用某窗口。于是,子窗口将显示灰色文字,而不再是黑色文字,以表示它已经被禁用,并且不能再接收任何消息了。
如果子窗口控件的窗口消息处理程序需要为所建立的每个窗口存放各自不同的数据,那么它可以通过使用窗口类别结构中的cbWndExtra值来做到。这样就在内部窗口结构中保留了空间,并可以用SetWindowLong和GetWindowLong来存取该数据。