什么是控件呢?简单的说,控件的其实就是一个个窗口(如果不是窗口,怎么能或得鼠标点击或者空格键的消息呢)。所以理论上,我们也可以不使用windows自带的控件,而自己动手写一个当做控件用的窗口。那么我们大概需要做一下几件事情:
1.创建并注册这个窗口。
2.通过前面的雷区翻盖程序,我们可以想到:控件每次点击不同的相应,应该是就是贴图的效果,所以对于每次点击窗口,需要贴图。
3.建立一个数据结构,记录控件是否被选中。
而windos系统,自动帮我们完成了后两件事情,当我们使用控件时,只需要注册窗口就行了。
让我们先看一段程序:
#include <windows.h> struct { int iStyle ; TCHAR * szText ; } button[] = { BS_PUSHBUTTON, TEXT ("下压按钮"), BS_DEFPUSHBUTTON, TEXT ("默认下压按钮"), //该复选框需要程序员给它发出消息才能被选中 BS_CHECKBOX, TEXT ("复选框"), //自动复选框 BS_AUTOCHECKBOX, TEXT ("自动复选框"), BS_RADIOBUTTON, TEXT ("单选按钮"), BS_3STATE, TEXT ("3状态复选框"), BS_AUTO3STATE, TEXT ("自动3状态复选框"), //既不处理鼠标键盘输入,也不向父窗口发送WM_COMMAND消息,用来包含其他控制按钮 BS_GROUPBOX, TEXT ("分组框"), BS_AUTORADIOBUTTON, TEXT ("自动单选按钮"), //自绘按钮 BS_OWNERDRAW, TEXT ("OWNERDRAW") } ; //总控件数 #define NUM (sizeof button / sizeof button[0]) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("BtnLook") ; 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 ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Button Look"), 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 hwndButton[NUM] ; //矩形区域 static RECT rect ; static TCHAR szTop[] = TEXT ("message wParam lParam"), szUnd[] = TEXT ("_______ ______ ______"), //格式控制的具体含义:[标志][输出最小宽度][.精度][长度]类型 //左对齐,最小宽度为16,打印字符串 //用0填充,最小宽度为4,打印16进制 //注意上面两行程序中应注意对齐 szFormat[] = TEXT ("%-16s %04X-%04X %04X-%04X"), szBuffer[50] ; static int cxChar, cyChar ; HDC hdc ; PAINTSTRUCT ps ; int i ; switch (message) { case WM_CREATE : //系统字体的平均宽度 cxChar = LOWORD (GetDialogBaseUnits ()) ; //系统字体的平局高度 cyChar = HIWORD (GetDialogBaseUnits ()) ; for (i = 0 ; i < NUM ; i++) hwndButton[i] =CreateWindow ( TEXT("button"),button[i].szText,//注册类名,窗口名 //窗口类型:子窗口,可见,窗口类型 WS_CHILD | WS_VISIBLE | button[i].iStyle, //窗口左上角位置的x,y坐标 cxChar, cyChar * (1 + 2 * i), //窗口的宽度和高度: //当按钮的高度为文字字元高度的 7/4 倍时,按钮的外观看起来最好 20 * cxChar, 7 * cyChar / 4, //父窗口,子窗口ID(这个位置通常填的是菜单,所以要转换成菜单类型) hwnd, (HMENU) i, //对create消息的lParam参数进行强制类型转化后,得到的是指向 //LPCREATESTRUCT是指向CREATESTRUCT结构的指针,对lParam进行转化后结构体的hInstance就是窗口句柄 ((LPCREATESTRUCT) lParam)->hInstance, NULL) ; return 0 ; case WM_SIZE : rect.left = 24 * cxChar ; rect.top = 2 * cyChar ; rect.right = LOWORD (lParam) ; rect.bottom = HIWORD (lParam) ; return 0 ; case WM_PAINT : InvalidateRect (hwnd, &rect, TRUE) ; hdc = BeginPaint (hwnd, &ps) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; //背景设为透明 SetBkMode (hdc, TRANSPARENT) ; //输出的最上面两行表头 //输出文字:参数为窗口句柄,x坐标,y坐标,字符串,输出字数 TextOut (hdc, 24 * cxChar, cyChar, szTop, lstrlen (szTop)) ; TextOut (hdc, 24 * cxChar, cyChar, szUnd, lstrlen (szUnd)) ; EndPaint (hwnd, &ps) ; return 0 ; //自绘按钮BS_OWNERDRAW需要通过WM_DRAWITEM来绘制 case WM_DRAWITEM : case WM_COMMAND : //滚动指定矩形区域的内容 //参数为:窗口句柄,水平滚动量,垂直滚动量,滚动的区域, ScrollWindow (hwnd, 0, -cyChar, &rect, &rect) ; hdc = GetDC (hwnd) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; TextOut( hdc, 24 * cxChar, cyChar * (rect.bottom / cyChar - 1), szBuffer, //在缓冲区中以一定格式存储字符串,返回值为字符串的个数 //参数为:缓冲区,格式, //wParam的高字节是通知码,低字节是控件窗口ID(创建时自己定义的) wsprintf (szBuffer, szFormat, message == WM_DRAWITEM ? TEXT ("WM_DRAWITEM") : TEXT ("WM_COMMAND"),HIWORD (wParam), LOWORD (wParam),HIWORD (lParam), LOWORD (lParam))) ; ReleaseDC (hwnd, hdc) ; ValidateRect (hwnd, &rect) ; break ; //鼠标按键测试直接给复选框发消息 case WM_LBUTTONDOWN: //第三个参数为true时标明选中状态,为fasle时标明没有选中,第四个参数不用 SendMessage (hwndButton[5], BM_SETCHECK, 1, 0) ; Sleep(1000); SendMessage (hwndButton[5], BM_SETCHECK, 0, 0) ; return 0; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
程序中有几点需要注意:
第一,是按钮的类型,“单选框”,“复选框”,“分组框”(与之对应的是“自动***”),它们点击并没有效果,但是可以通过发消息来改变它们的显示效果,程序中通过鼠标左键点击发送。
第二,由于每个控件都是窗口,所以在创建它们时,使用的是CreateWindow函数,返回的是HWND型句柄。
第三,只有BS_OWNERDRAW会导致WM_DRAWITEM消息,这个程序中并没有绘制自己按钮,所以最后一个控件那里只有一层灰色。但是点击那里会显示WM_DRAWITEM消息。
第四,WM_COMMAND消息的wParam和lParam的值含义如下:
LOWORD (wParam) HIWORD (wParam) lParam |
子窗口ID 通知码 子窗口句柄 |
其中通知码:
按钮通知码标识符 |
值 |
BN_CLICKED |
0 |
BN_PAINT |
1 |
BN_HILITE or BN_PUSHED |
2 |
BN_UNHILITE or BN_UNPUSHED |
3 |
BN_DISABLE |
4 |
BN_DOUBLECLICKED or BN_DBLCLK |
5 |
BN_SETFOCUS |
6 |
BN_KILLFOCUS |
7 |
一般都能看到通知码0.但是从1到4的通知码是用于一种叫做BS_USERBUTTON的已不再使用的按钮;通知码6到7只有当按钮样式包括标识BS_NOTIFY才发送;通知码5只对BS_RADIOBUTTON、BS_AUTORADIOBUTTON和BS_OWNERDRAW按钮发送,或者当按钮样式中包括BS_NOTIFY时,也为其它按钮发送。
第五,可以通过 SetWindowText 来改变按钮(或者其他任何窗口)内的文字;如果在建立子视窗时,您没有将 WS_VISIBLE 包含在视窗类别中,可以通过ShowWindow (hwndChild, SW_SHOWNORMAL) ;显示窗口;或者是你包含了 WS_VISIBLE,通过ShowWindow (hwndChild, SW_HIDE) ;将它隐藏起来;或者设置子视窗被启用或者不被启用:EnableWindow (hwndChild, FALSE) ;