本节我们将讲述第一个Windows预定义子窗口控件—button按钮控件的创建及使用。
本节必须掌握的知识点:
第48练:按钮控件
创建预定义子窗口
子窗口与父窗口信息传递
按钮
控件和颜色
第49练:单选框和复选框状态的判断
第50练:自定义按钮控件
8.11 第48练:按钮控件
/*------------------------------------------------------------------
048 WIN32 API 每日一练
第48个例子BTNLOOK.C:按钮控件
WM_DRAWITEM 消息
WM_COMMAND消息
GetDialogBaseUnits函数
SetBkMode函数
ScrollWindow函数
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
struct
{
int iStyle; //按钮样式
TCHAR * szText;//显示文本
}
button[] =
{
BS_PUSHBUTTON, TEXT("PUSHBUTTON"),
BS_DEFPUSHBUTTON, TEXT("DEFPUSHBUTTON"),
BS_CHECKBOX, TEXT("CHECKBOX"),
BS_AUTOCHECKBOX, TEXT("AUTOCHECKBOX"),
BS_RADIOBUTTON, TEXT("RADIOBUTTON"),
//创建与复选框相同的按钮,只不过该框可以灰显,也可以选中或清除。
BS_3STATE, TEXT("3STATE"),
BS_AUTO3STATE, TEXT("AUTO3STATE"),
BS_GROUPBOX, TEXT("GROUPBOX"),
BS_AUTORADIOBUTTON, TEXT("AUTORADIO"),
BS_OWNERDRAW, TEXT("OWNERDRAW")
} ;
#define NUM (sizeof button / sizeof button[0]) //NUM = 10
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;
…(略)
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("_______ ______ ______"),
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,//可见的子窗口
cxChar,cyChar*(1 + 2 * i),//左上角坐标
20 * cxChar,7 * cyChar / 4,//每个按钮宽20个cxChar,高度为7/4
hwnd,(HMENU)i, //子窗口ID
((LPCREATESTRUCT)lParam)->hInstance,NULL);
}
return 0;
//按钮控件坐标
case WM_SIZE:
//指定重绘窗口客户区的rect区域
rect.left = 24 * cxChar;
rect.top = 2 * cyChar;
rect.right = LOWORD(lParam);
rect.bottom = HIWORD(lParam);
return 0;
case WM_PAINT:
/*
1、窗口句柄(HWND)都是由操作系统内核管理的,系统内部有一个z-order序列,记录着当前从屏幕底部(假象的从屏幕到眼睛的方向),到屏幕最高层的一个窗口句柄的排序,这个排序不关注父窗口还是子窗口。当任意一个窗口接受到WM_PAINT消息产生重绘,更新区绘制完成以后,就搜索它的前面的一个窗口,如果此窗口的范围和更新区有交集,就向这个发送WM_PAINT消息,周而复始,直到执行到顶层窗口才算完成。
2、主窗口接受WM_PAINT绘制完成后,会引起更新区上所有子窗口的重绘(所有子窗口也是从底到外排序的)
3、子窗口无效不会引起父窗口重绘。父窗口无效,如果父窗口(没有WS_CLIPCHILDREN属性)收到WM_PAINT,
则所有子窗口都会在父窗口处理WM_PAINT之后收到WM_PAINT重绘消息。当然,如果父窗口带有属性WS_CLIPCHILDREN,则不会引起子窗口重绘。
*/
/*以下将客户区刷白,因子窗口范围与无效区可能有交集,会导致子窗口重绘,因此本例中WM_DRAWITEM会被触发,而WM_DRAWITEM过程中的SrollWindow会引起WM_PAINT,可在WM_DRAWITEM里,将无效区有效化,阻止ScrollWindow引发的WM_PAINT,从而防止死循环。*/
InvalidateRect(hwnd,&rect,TRUE);
//因为在WM_COMMAND过程中绘制的内容,在WM_PAINT中无法重绘出来,
//为防止主窗口被遮挡,再移开时,出现WM_COMMMAND中绘制的无法重现,
//最省事的做法是,那些内容全部不要了。
hdc = BeginPaint(hwnd,&ps);//将无效区有效化,阻止重复发送WM_PAINT消息。
SelectObject(hdc,GetStockObject(SYSTEM_FIXED_FONT));
SetBkMode(hdc,TRANSPARENT);//设置透明背景模式
TextOut(hdc,24 * cxChar,cyChar,szTop,lstrlen(szTop));//绘制标题栏
TextOut(hdc,24 * cxChar,cyChar,szUnd,lstrlen(szUnd));//绘制下划线
EndPaint(hwnd,&ps);
return 0;
//当按钮、组合框、列表框或菜单的视觉对象发生更改时
case WM_DRAWITEM:
//当用户从菜单中选择命令项、控件向其父窗口发送通知消息或翻译快捷键击键时
case WM_COMMAND:
//滚动窗口,会产生一个rect无效区域
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, 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);//更新指定窗口的无效矩形区域,使之有效。
//而ValidateRect会将无效区有效化,阻止发送WM_PAINT消息。
//测试:如果注释掉ValidateRect,将引起不断的重绘。
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}
/***************************************************************************
CreateWindow--------按钮类:
类名 TEXT (“button”)
窗口文本 button[i]. szText
窗口样式 WS_CHILD 丨 WS_VISIBLE 丨 button[i]. iStyle
x 坐标 cxChar
y 坐标 cyChar* (1 + 2* i)
宽度 20 * xChar
高度 7*yChar/4
父窗口 hwnd
子窗口 ID (HMENU) i
进程句柄 ((LPCREATESTRUCT) IParam)—>hlnstance
额外参数 NULL
默认情况下,DefWindowProc函数为所有者绘制的列表框项目绘制焦点矩形
******************************************************************************
WM_DRAWITEM 消息://当按钮,组合框,列表框或菜单的外观改变时,发送到所有者绘制的按钮,组合框,列表框或菜单的父窗口
WM_COMMAND消息://当用户从菜单中选择命令项,控件将通知消息发送到其父窗口或翻译了加速键时发送。
消息源 wParam (高位字) wParam (低字) lParam
菜单 0 菜单标识符 _ * 0
快捷键 1 快捷键标识符 _ * 0
控件 控件定义的通知代码 控件标识符 控制窗口的句柄
******************************************************************************
GetDialogBaseUnits函数:检索系统的对话框基本单位,即系统字体中字符的平均宽度和高度
long GetDialogBaseUnits();
******************************************************************************
SetBkMode函数:指定设备上下文的背景混合模式
int SetBkMode(
HDC hdc,
int mode//后台模式,透明或不透明
);
******************************************************************************
ScrollWindow函数:滚动指定窗口的客户区的内容
BOOL ScrollWindow(
HWND hWnd,
int XAmount,
int YAmount,
const RECT *lpRect,//指向RECT结构的指针,该结构指定要滚动的客户区域的一部分。如果此参数为NULL,则滚动整个客户区
const RECT *lpClipRect//指向包含裁剪矩形坐标的RECT结构的指针
);
*/
运行结果:
图8-1 BUTTON控件
总结
实例BTNLOOK.C绘制了十种不同样式的BUTTON子窗口控件,并在主窗口客户区rect矩形区域显示子窗口控件WM_DRAWITEM消息和WM_COMMAND消息的wParam参数和lParam参数。
窗口过程:
处理WM_CREATE消息时,首先调用GetDialogBaseUnits函数获取默认对话框字体宽度和高度。然后再for循环语句中,通过CreateWindow函数创建10种不同样式的BUTTON控件(BUTTON控件ID值0~9)。
处理WM_SIZE消息,重绘rect矩形区域。
处理WM_PAINT消息,首先调用InvalidateRect函数重绘窗口客户区。原因在代码注释中已经表述。Windows系统绘制窗口时有一个规则,从底向上的顺序绘制窗口。如果重绘主窗口,通常主窗口附属的子窗口也会一并重绘。如果子窗口重绘,通常不需要重绘主窗口。实例中的BUTTON子窗口和主窗口存在交集,需要按照这个规则重绘主窗口和子窗口。接下来将绘图背景模式改为透明模式,绘制标题和下划线。
处理WM_DRAWITEM消息,认真测试的读者会发现,只有当我们点击"OWNERDRAW"自定义的BUTTON控件时,才会产生WM_DRAWITEM消息。
处理WM_COMMAND消息,所有的子窗口控件都会产生WM_COMMAND消息消息。当我们点击任意一个BUTTON控件时,在主窗口的rect矩形区域滚动显示WM_DRAWITEM消息或WM_COMMAND消息,并显示其wParam参数和lParam参数的值。
最后调用ValidateRect函数,使窗口的rect矩形区域变为有效,因为ScrollWindow会产生一个rect无效区域。请读者屏蔽ValidateRect函数测试。
【注意】菜单、快捷键和子窗口控件都会产生WM_COMMAND消息,但是它们的wParam参数和lParam参数的值具有不同的含义(参考注释)。
8.1.2 创建预定义子窗口
创建窗口使用CreateWindow函数,创建自定义的子窗口和创建预定义的子窗口控件同样使用CreateWindow函数。区别就在于Windows预定义子窗口控件的窗口类不需要自己注册,Windows系统已经预定义了。
CreateWindow--------按钮类:
类名 TEXT (“button”)
窗口文本 button[i]. szText 窗口标题
窗口样式 WS_CHILD 丨 WS_VISIBLE 丨 button[i]. iStyle
x 坐标 cxChar
y 坐标 cyChar* (1 + 2* i)
宽度 20 * xChar
高度 7*yChar/4
父窗口 hwnd
子窗口 ID (HMENU) i
进程句柄 ( (LPCREATESTRUCT) IParam)—>hlnstance
额外参数 NULL
参数类名:不同的预定义子窗口控件有不同的预定义类名,BUTTON控件类名为”button”,接下来我们讲述的静态文本控件、滚动条控件、Edit空间和列表框控件都有各自的预定义类名。
子窗口控件的样式:通常都会具有WS_CHILD | WS_VISIBLE样式。其余的样式,不同子窗口控件会有不同的样式,例如BUTTON控件就有10种不同的控件样式。
子窗口控件使用左上角坐标、宽和高表示窗口的大小。
子窗口控件必须拥有父窗口,引用父窗口句柄表示。
子窗口的ID标识符使用菜单ID来表示。
【注意】第十一章学习的对话框窗口则不一定拥有父窗口。
■获取当前进程句柄的4种方法:
●子窗口所在进程的句柄,可以调用GetWindowLong来获得实例句柄。
hInstance =GetWindowLong(hwnd,GWL_INSTANCE);
●可以通过WM_CREATE消息的lParam参数获取:
((LPCREATESTRUCT)lParam)->hInstance)。
●在WinMain函数中我们已经有了Windows系统给的进程句柄,将其赋给一个进程句柄全局变量也是可以的。
设置全局变量hInst,在WinMain函数中 hInst = hInstance;
●调用GetModuleHandle函数获取:
hInstance =GetModuleHandle(NULL);
8.1.3 子窗口与父窗口信息传递
是否还记得我们在第六章的鼠标击中测试的实例中讲述过父窗口与子窗口的消息传递,同样作为子窗口的子窗口控件与父窗口之间也存在消息传递。
■子窗口传递信息给父窗口
运行实例BTNLOOK时,会看到不同的按钮类型显示在左侧客户区。正如我前面提到的, 在用鼠标单击一个按钮时,子窗口控件发送WM_COMMAND消息给其父窗口。BTNLOOK俘获WM_COMMAND消息并显示wParam与IParam的值。这几个值的含义如下:
LOWORD (wParam) 子窗口 ID
HIWORD (wParam) 通知码
IParam 子窗口句柄
子窗口 ID是新建子窗口时传递给CreateWindow的值。在BTNLOOK中,这些ID的值是0到9,代表显示在客户区的10个按钮。子窗口句柄是Windows从CreateWindow函数调用返回的值。
通知码进一步给出每条消息的意思。按钮通知码的可能值定义在Windows头文件中,如下表所示:
按钮通知码标识符 | 值 |
BN_CLICKED | 0 |
BN_PAINT | 1 |
BN_HILITE或BN_PUSHED | 2 |
BN_UNHILITE或BN_UNPUSHED | 3 |
BN_DISABLE | 4 |
BN_DOUBLECLICKED或BN_DBLCLK | 5 |
BN_SETFOCUS | 6 |
BN_KILLFOCUS | 7 |
实际上,大多数的按钮值你都不会见到。通知码1〜4用于过时的按钮样式 BS_USERBUTTON。 (它已经被BS_OWNERDRAW和另外一套通知机制所取代。)通知码 6和7仅当按钮样式包含BS_NOTIFY标志才会被发送。通知码5只用于 BS_RADIOBUTTON, BS_AUTORADIOBUTTON 和 BS_OWNERDRAW,或者是按钮样式包含BS_NOTIFY的其他按钮。
你会发现,在用鼠标单击按钮时,按钮文本的周围会出现一圈虚线。这表明该按钮拥有输入焦点。所有的键盘输入消息会送到这个子窗口按钮控件而不是主窗口。按钮控件一旦获得输入焦点,便会忽略所有按键操作,但空格键除外,此时的空格键具有和单击鼠标相同的效果。
●单击按钮,子窗口会向父窗口发送WM_COMMAND消息。各参数如下:
参数 | 值 | 备注 | |
lParam | 子窗口句柄 | ||
wParam | LOWORD(wParam) :子窗口ID | ||
HIWORD(wParam):通知码 通知码以BN_开头,表示消息传输的方向是给父窗口的通知消息。 | BN_CLICKED (0) | 释放鼠标时发生 | |
BN_PAINT (1) | 为过时的按钮样式BS_USERBUTTON所用(己被BS_OWNERDRAW和另一套通知机制取代) | ||
BN_HILITE或 BN_PUSHED (2) | |||
BN_UNHILITE或 BN_UNPUSHED (3) | |||
BN_DISABLE (4) | |||
BN_DOUBLECLICKED或 BN_DBLCLK (5) | 仅用于BS_RADIOBUTTON、BS_AUTORADIOBUTTON和BS_OWNERDRAW或BS_NOTIFY的按钮使用 | ||
BN_SETFOCUS (6) | 仅用于BS_NOTIFY样式的按钮 | ||
BN_KILLFOCUS (7) |
■父窗口传递信息给子窗口
主窗口过程也可以发送消息给子窗口控件。这些消息包括前缀为WM的许多窗口消息。此外,在WINUSER.H中定义了 8个专用于按钮的消息:每个消息都以字母BM开头,以表示按钮消息。这些按钮消息如下表所示:
按钮消息 | 值 |
BM_GETCHECK | 0x00F0 |
BM_SETCHECK | 0x00F1 |
BM_GETSTATE | 0x00F2 |
BM_SETSTATE | 0x00F3 |
BM_SETSTYLE | 0x00F4 |
BM_CLICK | 0x00F5 |
BM_GETIMAGE | 0x00F6 |
BM_SETIMAGE | 0x00F7 |
父窗口发送BM_GETCHECK和BM_SETCHECK消息给子窗口控件,以获取和设置复选框和单选按钮的选择状态。在用鼠标或空格键单击窗口时,BM_GETSTATE和 BM_SETSTATE消息将反映一个窗口的状态是正常的还是被单击了。在我们介绍每一种类型的按钮时,会了解这些消息的工作机制。BM_SETSTYLE消息允许在创建按钮后改变按钮样式。
●获取子窗口ID或子窗口句柄或方法
每个子窗口有一个窗口句柄和唯一的ID。知道其中一个就可以得到另一个。如果知道子窗口句柄,则通过以下函数调用,获得其ID:
id = GetWindowLong (hwndChild, GWL_ID);
此函数(和SetWindowLong函数一起)在第六章的CHECKER3程序中被用来维护一个 特定区域内的数据,这个区域是在窗口类被注册时产生的。创建子窗口的时候,Windows 会保留这个区域,只有用GWL_ID标识符才能访问。也可以用另一个函数,如下所示:
id = GeEDlgCtrllD (hwndChild);
然函数名称中的“Dig”会使人联想到对话框,但它的确是一个通用的函数。
在知道子窗口ID和父窗口句柄后,可以得到子窗口句柄:
hwndChild = GecDlglCem (hwndParent, id);
●SentMessage的message(消息)设置
按钮消息 | 备注 |
BM_GETCHECK (0x00F0) | 单击或复选框按钮 |
BM_SETCHECK (0x00F1) | |
BM_GETSTATE (0x00F2) | 返回 BST_CHECKED:按钮被选中 BST_UNCHECKED:按钮已清除 BST_INDETERMINATE:按钮显示为灰色 BST_FOCUS:具有输入焦点 BST_PUSHED:按住鼠标 …… |
BM_SETSTATE (0x00F3) | |
BM_CLICK (0x00F4) | 模拟按钮单击 |
BM_GETIMAGE (0x00F5) | 检索与按钮关联的图像(图标或位图)的句柄 |
BM_SETIMAGE (0x00F6) | 将新图像(图标或位图)与按钮相关联 |