什么是控件呢?简单的说,控件的其实就是一个个窗口(如果不是窗口,怎么能或得鼠标点击或者空格键的消息呢)。所以理论上,我们也可以不使用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) ;