上一小节中虽然也提到了绘制自己控件,但是由于时间紧迫,只是光有一个空壳子,里面并没有实际的内容,这次专门设计一个自己的控件。在这个程序中,设计了两个控件,一个显示4个指向朝外的三角形,点击它会使客户区变大;另一个里面有4个指向朝里的三角形,点击它是得客户区缩小。程序的关键点有两个:1是如何响应自己画的按钮的消息,2是如何画自己的按钮。
先看程序:
/*--------------------------------------------- OWNDRAW.C -- Owner-Draw Button Demo Program (c) Charles Petzold, 1998 ---------------------------------------------*/ #include <windows.h> #define ID_SMALLER 1 #define ID_LARGER 2 #define BTN_WIDTH (8 * cxChar) #define BTN_HEIGHT (4 * cyChar) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; HINSTANCE hInst ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("OwnDraw") ; MSG msg ; HWND hwnd ; WNDCLASS wndclass ; hInst = hInstance ; 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 = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Owner-Draw Button 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 ; } //画三角形函数,参数为:设备内容句柄,POINT类型的数组 void Triangle (HDC hdc, POINT pt[]) { //选择黑刷子 SelectObject (hdc, GetStockObject (BLACK_BRUSH)) ; //画三角形 Polygon (hdc, pt, 3) ; // SelectObject (hdc, GetStockObject (WHITE_BRUSH)) ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hwndSmaller, hwndLarger ; static int cxClient, cyClient, cxChar, cyChar ; int cx, cy ; LPDRAWITEMSTRUCT pdis ; POINT pt[3] ; RECT rc ; switch (message) { case WM_CREATE : //获得系统字体的宽度和高度 cxChar = LOWORD (GetDialogBaseUnits ()) ; cyChar = HIWORD (GetDialogBaseUnits ()) ; // Create the owner-draw pushbuttons hwndSmaller = CreateWindow (TEXT ("button"), TEXT (""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, 0, 0, BTN_WIDTH, BTN_HEIGHT, hwnd, (HMENU) ID_SMALLER, hInst, NULL) ; hwndLarger = CreateWindow (TEXT ("button"), TEXT (""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, 0, 0, BTN_WIDTH, BTN_HEIGHT, hwnd, (HMENU) ID_LARGER, hInst, NULL) ; return 0 ; case WM_SIZE : cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; //将两个窗口移动到客户区的中心 MoveWindow (hwndSmaller, cxClient / 2 - 3 * BTN_WIDTH / 2, cyClient / 2 - BTN_HEIGHT / 2, BTN_WIDTH, BTN_HEIGHT, TRUE) ; MoveWindow (hwndLarger, cxClient / 2 + BTN_WIDTH / 2, cyClient / 2 - BTN_HEIGHT / 2, BTN_WIDTH, BTN_HEIGHT, TRUE) ; return 0 ; case WM_COMMAND : GetWindowRect (hwnd, &rc) ; // 是得客户区放大或者缩小10% switch (wParam) { case ID_SMALLER : rc.left += cxClient / 20 ; rc.right -= cxClient / 20 ; rc.top += cyClient / 20 ; rc.bottom -= cyClient / 20 ; break ; case ID_LARGER : rc.left -= cxClient / 20 ; rc.right += cxClient / 20 ; rc.top -= cyClient / 20 ; rc.bottom += cyClient / 20 ; break ; } MoveWindow (hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE) ; return 0 ; //WM_DRAWITEM消息的lParam参数指向 DRAWITEMSTRUCT结构 //这个结构包含了自己绘制的控件的信息 case WM_DRAWITEM : //DRAWITEMSTRUCT中的hDC表明设备内容句柄 // pdis = (LPDRAWITEMSTRUCT) lParam ; pdis = (LPDRAWITEMSTRUCT) lParam ; //给白色矩形加上黑色边框 FillRect (pdis->hDC, &pdis->rcItem, (HBRUSH) GetStockObject (WHITE_BRUSH)) ; FrameRect (pdis->hDC, &pdis->rcItem, (HBRUSH) GetStockObject (BLACK_BRUSH)) ; //画向内和向外的黑三角 cx = pdis->rcItem.right - pdis->rcItem.left ; cy = pdis->rcItem.bottom - pdis->rcItem.top ; // DRAWITEMSTRUCT结构中CtlID表明窗口控件ID switch (pdis->CtlID) { case ID_SMALLER : //需要定义3个点,然后调用画三角形函数画图 pt[0].x = 3 * cx / 8 ; pt[0].y = 1 * cy / 8 ; pt[1].x = 5 * cx / 8 ; pt[1].y = 1 * cy / 8 ; pt[2].x = 4 * cx / 8 ; pt[2].y = 3 * cy / 8 ; Triangle (pdis->hDC, pt) ; pt[0].x = 7 * cx / 8 ; pt[0].y = 3 * cy / 8 ; pt[1].x = 7 * cx / 8 ; pt[1].y = 5 * cy / 8 ; pt[2].x = 5 * cx / 8 ; pt[2].y = 4 * cy / 8 ; Triangle (pdis->hDC, pt) ; pt[0].x = 5 * cx / 8 ; pt[0].y = 7 * cy / 8 ; pt[1].x = 3 * cx / 8 ; pt[1].y = 7 * cy / 8 ; pt[2].x = 4 * cx / 8 ; pt[2].y = 5 * cy / 8 ; Triangle (pdis->hDC, pt) ; pt[0].x = 1 * cx / 8 ; pt[0].y = 5 * cy / 8 ; pt[1].x = 1 * cx / 8 ; pt[1].y = 3 * cy / 8 ; pt[2].x = 3 * cx / 8 ; pt[2].y = 4 * cy / 8 ; Triangle (pdis->hDC, pt) ; break ; case ID_LARGER : pt[0].x = 5 * cx / 8 ; pt[0].y = 3 * cy / 8 ; pt[1].x = 3 * cx / 8 ; pt[1].y = 3 * cy / 8 ; pt[2].x = 4 * cx / 8 ; pt[2].y = 1 * cy / 8 ; Triangle (pdis->hDC, pt) ; pt[0].x = 5 * cx / 8 ; pt[0].y = 5 * cy / 8 ; pt[1].x = 5 * cx / 8 ; pt[1].y = 3 * cy / 8 ; pt[2].x = 7 * cx / 8 ; pt[2].y = 4 * cy / 8 ; Triangle (pdis->hDC, pt) ; pt[0].x = 3 * cx / 8 ; pt[0].y = 5 * cy / 8 ; pt[1].x = 5 * cx / 8 ; pt[1].y = 5 * cy / 8 ; pt[2].x = 4 * cx / 8 ; pt[2].y = 7 * cy / 8 ; Triangle (pdis->hDC, pt) ; pt[0].x = 3 * cx / 8 ; pt[0].y = 3 * cy / 8 ; pt[1].x = 3 * cx / 8 ; pt[1].y = 5 * cy / 8 ; pt[2].x = 1 * cx / 8 ; pt[2].y = 4 * cy / 8 ; Triangle (pdis->hDC, pt) ; break ; } //当按下按钮时itemState字段中的某位将被设为1,用ODS_SELECTED常数来测试这些位 if (pdis->itemState & ODS_SELECTED) //翻转矩形区域 InvertRect (pdis->hDC, &pdis->rcItem) ; //绘制焦点矩形框 //判断是否处于焦点 if (pdis->itemState & ODS_FOCUS) { //焦点矩形框大小 pdis->rcItem.left += cx / 16 ; pdis->rcItem.top += cy / 16 ; pdis->rcItem.right -= cx / 16 ; pdis->rcItem.bottom -= cy / 16 ; //绘制焦点矩形框 DrawFocusRect (pdis->hDC, &pdis->rcItem) ; } return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
在WM_CREATE消息下,创建了两个子窗口;在WM_SIZE消息下,获得客户区的大小并把窗口移动到客户区的中间;每当点击控件时,父窗口将会接收到子窗口的WM_COMMAND消息,通过对消息的wParam参数判断,来确定点击的是哪个子窗口并进行放大或者缩小客户区的操作。
到此,子窗口的功能已经实现了,剩下的问题就是画出这个子窗口。这个工作在WM_DRAWITEM消息下完成。
这个消息的参数lParam非常关键,对它进行类型转换以后,就能得到指向DRAWITEMSTRUCT结构的指针。这个结构存储了你自己画的按钮的信息:比如CtlID表明窗口控件ID;当按下按钮时itemState字段中的某位将被设为1,用ODS_SELECTED常数来测试这些位;使用ODS_FOCUS来测试itemState判断窗口是否处于焦点等等。
在程序中,先画一个白色矩形,给他添上黑框;然后判断是哪个窗口,对应的窗口画出三角形;按下窗口时,翻转该窗口;判断其是否属于焦点,如果是,添加一个焦点框。
这就是程序的基本流程。