8.1.4 按钮
在BTNLOOK中显示的前两个按钮是“按键”按钮(push button)。此类按钮是一种带有 文本的矩形,这些文本是在CreateWindow调用的窗口文本参数中提供的。而CreateWindow 或MoveWindow调用中指定的宽度和高度则确定了矩形的大小。文本显示在矩形的中心。
按键按钮控件主要用于立即启动某些行动而不必保留任何类型的开/关指示。有两种类 型的按键按钮控件,它们的窗口样式分别是BS_PUSHBUTTON和BS_DEFPUSHBUTTON。 BS_DEFPUSHBUTTON的“DEF”表示“默认值”。在被用于设计对话框时, BS_PUSHBUTTON控件和BS_DEFPUSHBUTTON控件功能是完全不同的。但在被用作子窗口控件时,两种类型按钮的表现基本相同,虽然BS_DEFPUSHBUTTON会有一个较重的轮廓。
按键按钮的最佳视觉高度是字符高度的7/4,这是我们在BTNLOOK中使用的高度。这种按钮的宽度至少需要容纳文本的宽度,外加两个额外的字符宽度。
当鼠标指针显示在按钮内部时,按下鼠标按钮将会导致按钮用三维样式的阴影重绘自 己,仿佛它被按下似的。当释放鼠标,按钮会恢复原貌,并发出一个通知码为BN_CLICKED 的WM_COMMAND消息到父窗口。像其他按钮类型一样,当一个按钮有输入焦点时,文本会被虚线包围。按下和释放空格键的效果同按下和释放鼠标一样。默认情况下,DefWindowProc函数为所有者绘制的列表框项目绘制焦点矩形。
通过给Windows发送一个BM_SETSTATE消息,可以模拟按键按钮的状态变化。下面的语句将导致按钮看上去被按住一样:
SendMessage (hwndButton, BM_SETSTATE, 1, 0);
调用下面的函数则会让按钮回到正常状态:
SendMessage (hwndButCon, BM_SETSTATE, 0, 0);
窗口句柄hwndButton是CreateWindow调用的返回值。
也可以给按键按钮发送一个BM_GETSTATE消息。子窗口控件返回当前按钮的状态:如果按键是按下的,则返回TRUE;如果没有被按下,则返回FALSE。然而大多数应用程序不需要此信息。而因为这种按钮不保留任何开/关信息,所以BM_SETCHECK和 BM_GETCHECK消息没有被用到。
■复选框
复选框(check box)是一个带文本的正方形框,文本通常出现在复选框的右侧。(如果在创建按钮时包括BS_LEFTTEXT样式,则文本会出现在按钮左侧:还可以组合使用 BS_RIGHT样式使文本右对齐。)复选框通常用在应用程序中,以允许用户选择多个选项。复选框的常见职能是作为切换开关:单击方框即可选中此功能选项(出现一个选中标记), 再单击方框即可取消此功能选项(选中标记消失)。
复选框最常见的两类样式是BS_CHECKBOX和BS_AUTOCHECKBOX。在使用 BS_CHECKBOX样式时,必须自己给控件发送一个BM_SETCHECK消息来设置其选中标记。wParam参数设置为1会创建一个选中标记,设置为0则清除标记。可以向控件发送 BM_GETCHECK信息来获取复选框当前的被选状态。在处理来自控件的 WM_COMMAND消息时,可使用下面的代码切换选中标记:
SendMessage ((HWND) IParam, BM_SETCHECK, (WPARAM)
!SendMessage ( (HWND) IParam, BM_GETCHECK, 0, 0), 0);
【注意】在第二行中,SendMessage函数前面有个操作符!。IParam值是 WM_COMMAND消息传递给窗口过程的子窗口句柄。以后需要知道按钮状态的时候,可以发送另一条BM_GETCHECK消息。或者,可以把当前的选择状态保存在窗口过程的静态变量中。也可以发送一条BM_SETCHECK信息,借此将BS_CHECKBOX复选框初始化为选中状态。
对于BS_AUTOCHECKBOX样式,按钮控件本身负责切换选定和取消标记。窗口过程可以忽略WM_COMMAND消息。在需要当前按钮的状态时,可以向控件发送 BM_GETCHECK 消息:
iCheck = (int) SendMessage (hwndButton, BM_GETCHECK, 0, 0);
如果复选框被选中,iCheck的值就为TRUE或非零。如果没有被选中,则为FALSE或0。
其他两个复选框样式分别为BS_3STATE和BS_AUTO3STATE。正如其名称所表明的,这些样式可以显示第二种状态一个灰色的复选框。这发生在向控件发送BM_SETCHECK消息(wParam被设为2)时。灰色复选框告诉用户,选择是不确定的或无关紧要的。
CreateWindow函数调用指定了控件矩形的大小,复选框会在矩形的左边缘对齐,并在 矩形的顶端和底部居中。单击矩形内的任何地方都会导致WM_COMMAND消息被发送到父窗口。复选框的域低高度是一个字符的高度。最小宽度是现有字符数再加2个字符的宽度。
样式 | 状态 |
BS_CHECKBOX | 1、SendMessage(hwndButton,BM_SETCHECK,1,0); //选中 2、SendMessage(hwndButton,BM_SETCHECK,0,0); //取消选中 3、SendMessage((HWND)lParam,BM_SETCHECK,(WPARAM) //切换 !SendMessage((HWND)lParam,BM_GETCHECK,0,0),0); |
BS_AUTOCHECKBOX | 1、自动切换 2、获取状态:iCheck = (int)SendMessage(hwndButton,BM_GETCHECK,0,0); |
BS_3STATE | SendMessage(hwndButton,BM_SETCHECK,2,0); //第3种状态 |
BS_AUTO3STATE | //自动切换 |
●宽度和高度:最低高度——1个字符的高度,最小宽度——字符数+2个字符的宽度。
●复选框中的文本位置及文本对齐。
■单选按钮
单选按钮的名称来源于汽车收音机的一排选择按钮,它曾风靡一时。汽车收音机的每个按钮设定为不同的电台。在任意时刻只有一个按钮可以被按下。在对话框中,一组申选按钮依据约定用于相互排斥的选项。不同于复选框,单选按钮没有状态切换,也就是说,如果单击按钮第二次,其状态仍保持不变。
单选按钮很像一个复选框,但它包含一个小圆圈,而不是一个方框。圆圈内有一个大黑点表明该单选按钮已被选中。单选按钮的窗口样式为BS_RADIOBUTTON或 BS_AUTORADIOBUTTON,但后者只用于对话框。
在收到来自单选按钮的WM_COMMAND消息时,应通过向它发送一条wParam等于1的BM_SETCHECK消息来显示其选中标记:
SendMessage (hwndButton, BM_SETCHECK, 1, 0);
而对于其他在同一组中的所有单选按钮,则可以通过向它们发送wParam设置为0的 BM_SETCHECK消息来取消其选中标记:
SendMessage (hwndButton, BM_SETCHECK, 0, 0);
■组合框
带有BS_GROUPBOX样式的组合框,是一个古怪的按钮类。它既不处理鼠标或键盘输入也不发送消息到WM_COMMAND父窗口。组合框是一种矩形外框,窗口文本显示在矩形顶部。组合框常常被用来包容其他按钮控件。
■改变按钮文本
可以调用SetWindowText来改变按钮中的文本:
SetWindowText (hwnd, pszString);
这里的hwnd是要被改变文本的窗口的句柄,pszString是一个指向以零结尾的字符串的指针。对于普通窗口来说,该文本就是指窗口标题栏中的文本。而对于按钮控件来说,该文 本是和按钮一起显示的文本。
也可以获得一个窗口的当前文本,如下所示:
iLength = GetWindowText (hwnd, pszBuffer, iMaxLengCh);
这里的iMaxLength指定了 pszBuffer所指向的缓冲区所能接收的最大字符串长度。函数会 返回被复制的字符串长度。可以事先调用如下函数,使程序对可接收的特定文本长度有所 准备;
iLength = GetWindowTextLength (hwnd);
■可见按钮和启动按钮
要接收鼠标和键盘输入,子窗口必须是可见的(显示)并且是启用的。如果一个子窗口 是可见的,但没有启用,那么文本在窗口中的显示是灰色的,而不是黑色的。
如果在创建子窗口时在窗口类中没有包括WS_VISIBLE,子窗口将不会显示,除非调用 ShowWindow:
ShowWindow(hwndChild, SW_SHOWNORMAL);
但如果在窗口类中包含了 WS_VISIBLE,就无需调用ShowWindow。不过,可以调用以下 ShowWindow来隐藏这个子窗口:
ShowWindow (hwndGhild, SW_HIDE);
可以调用以下函数来判断窗口是否可见:
IsWindowVisible (hwndChild);
还可以启用或者禁用一个子窗口。在默认情况下,窗口是处于启用状态的。可以调用下面的函数来禁用子窗口:
EnableWindow (hwndChild, FALSE);
对于按钮控件,此函数可以把按钮的文本字符串变成灰色。按钮不再响应鼠标或键盘输入。 这是表明按钮选项目前无法使用的最佳方法。
通过如下调用可以重新启用子窗口 :
EnableWindow(hChildWnd, TRUE);
可以调用以下函数了解子窗口是否已被启用:
IsMindowEnab1ed (hwndChild);
■按钮和输入焦点
当我们用鼠标单击时,按键按钮、复选框、复选按钮、自绘按钮得到输入焦点。如果按钮上的文本被虚线包围,则表明它已经获得输入焦点。子窗口控件得到输入焦点后,父窗口便会失去输入焦点。之后所有的键盘输入将送到控件子窗口而不是其父窗口。然而,子窗口控件只对空格键做出响应,空格键现在的功能类似于鼠标。 这种情况引发一个明显的问题:程序已经失去了对键盘处理的控制。让我们看看如何解决这个问题。
正如我在第六章讨论的,在Windows把输入焦点从一个窗口(如父窗口)切换到另一个 窗口(如子窗口控件)时,它首先会向将要失去输入焦点的窗口发送一条消息 WM_KILLFOCUS。相应的wParam参数是将要获得输入焦点的窗口的句柄。Windows然后向要接收输入焦点的窗口发送WM_SETFOCUS消息,用wParam指定失去输入焦点的窗口的句柄。(在这两种情况wParam都可能是NULL,表明没有窗口具有或正在接收输入焦点。)
父窗口可以通过对WM_KILLFOCUS消息的处理来阻止子窗口控件获得输入焦点。假定数组hwndChild包含所有子窗口的窗口句柄。(这些句柄都是在调用CreateWindow创建窗口时保存在数组中的。)NUM是子窗口的数目。
case WM_KILLFOCUS :
for ( i = 0 ; i < NUM ; i++) {
if (hwndChild [i] == (HWND) wParam)
{
SetFocus (hwnd) ;
break ;
}
}
return 0 ;
在此代码中,当父窗口检测到它将失去输入焦点并将焦点转给它的一个子窗口控件时,它调用SetFocus来重新获得本身的输入焦点。
下面有一个更简单(但不太明显)的方法来实现我们的想法:
case WM_KILLFOCUS :
if (hwnd == GecParent ((HWND) wParam))
SetFocus (hwnd);
return 0;
但这两种方法都有一个缺陷,就是他们会使按钮不再回应空格键,因为按钮从未得到输入焦点。一个更好的办法是让按钮获得输入焦点,而且还能让用户用Tab键从一个按钮移动到另外一个按钮。这乍听起来好像不可能,但我会在本章后面的COLORS1程序中指出如何用“窗口子类”这一技术来实现它。
8.1.5 控件和颜色
许多按钮的显示看起来不太对。按键按钮还好,但其他按钮都有一个本不该有的长方形灰色背景。这是因为按钮是被设计用于在对话框中显示的,而Windows 98中的对话框的表面是灰色的。我们的窗口表面是白色的,因为这是我们在WNDCLASS 结构中已经定义好的:
wndclass.hbrBackground = (HBRUSH) GetStoekObject (WHITE_BRUSH);
我们之所以这样设置,是因为我们常常要在客户区显示文本,GDI使用了定义在默认设备环境中的文本颜色与背景颜色。这些颜色总是黑色和白色。为了使这些按钮更耐看。我们必须要么改变客户区的颜色,让它与按钮的背景颜色相同,要么就更改按钮的背景颜色为白色。
要解决这个问题,首先要理解Windows是如何使用系统颜色的。
■系统颜色
Windows有29种系统颜色来支持各部分的显示。可以使用GetSysColor和 SetSysColors获取并设置这些颜色。定义在Windows头文件中的标识符指定了系统颜色。 用SetSysColors设置系统颜色仅仅影响到当前的窗口会话。
可以用Windows控制面板的【显示】工具来改变某些(但不是所有的)系统颜色。选定 的颜色分别存储在Windows NT的注册表(Registry)中或Windows 98的WIN.INI文件中。 注册表和WIN.INI文件使用关键字来表示29种系统颜色(这些关键字不同于GetSysColor 和SetSysColors的标识符),紧接着是范围0〜255的红、绿、蓝RGB值。下表列出了29 种系统颜色的定义,它使用GetSysColor、SetSysColors的常量和WIN.INI中的关键字。
GetSysColor和SetSysColors | 注册表键值或WIN.INI标识符 | 默认RGB值 |
COLOR_SCROLLBAR | Scrollbar | C0-C0-C0 |
COLOR_BACKGROUND | Background | 00-80-80 |
COLOR_ACTIVECAPTION | ActiveTitle | 00-00-80 |
COLOR_INACTIVECAPTION | InactiveTitle | 80-80-80 |
COLOR_MENU | Menu | C0-C0-C0 |
COLOR_WINDOW | Window | FF-FF-FF |
COLOR_WINDOWFRAME | WindowFrame | 00-00-00 |
COLOR_MENUTEXT | MenuText | C0-C0-C0 |
COLOR_WINDOWTEXT | WindowText | 00-00-00 |
COLOR_CAPTIONTEXT | TitleText | FF-FF-FF |
COLOR_ACTIVEBORDER | ActiveBorder | C0-C0-C0 |
COLOR_INACTIVEBORDER | InactiveBorder | C0-C0-C0 |
COLOR_APPWORKSPACE | App Workspace | 80-80-80 |
COLOR_HIGHLIGHT | Highlight | 00-00-80 |
COLOR_HIGHLIGHTTEXT | HighlightText | FF-FF-FF |
COLOR_BTNFACE | ButtonFace | C0-C0-C0 |
COLOR_BTNSHADOW | ButtonShadow | 80-80-80 |
COLOR_BTNTEXT | ButtonText | 00-00-00 |
COLOR_INACTINVECAPTIONTEXT | InactiveTitleText | C0-C0-C0 |
COLOR_BTNHIGHLIGHT | ButtonHighlight | FF-FF-FF |
COLOR_3DDKSHADOW | ButtonDKShadow | 00-00-00 |
COLOR_3DLIGHT | ButtonLight | C0-C0-C0 |
COLOR_INFOTEXT | InfoText | 00-00-00 |
COLOR_INFOBK | InfoWindow | FF-FF-FF |
[没有标识符,使用值25] | ButtonAlternateFace | B8-B4-B8 |
COLOR_HOTLIGHT | HotTrackingColor | 00-00-FF |
COLOR_GRADIENTACTIVECAPTION | GradientActiveTitle | 00-00-80 |
COLOR_GRADIENTINACTIVECAPTION | GradientInactiveTitle | 80-80-80 |
这29种颜色的默认值是由显示驱动程序提供的,在不同机器上颜色可能稍有不同。
■按钮颜色
颜色 | 说明 |
COLOR_BTNFACE | 按钮主表面颜色或其他按钮的背景颜色 |
COLOR_BTNSHADOW | 按钮右侧和底部的阴影颜色 |
COLOR_BTNTEXT | 对按键按钮而言的,为上面的文本颜色 |
COLOR_WINDOWTEXT | 其他控件的文本颜色 |
这个问题对按钮而言尤为明显,因为每种按钮都要用到多种颜色。COLOR_BTNFACE 用于按键按钮的主表面颜色和其他按钮的背景颜色。(这也是对话框和消息框使用的系统颜 色。)COLOR_BTNSHADOW用在按键按钮的右侧和底部、复选框方块的内部和单选按钮的圆圈内,用来表示阴影。对于按键按钮,COLOR_BTNTEXT用于文本的颜色;其他控件的文本颜色使用的则是COLOR_WINDOWTEXT。还有好几种其他系统颜色也用于按钮的设计。
因此,如果我们要在客户区表面显示按钮,一个避免颜色冲突的途径就是使用这些系统颜色。首先,在设计窗口类时,使用COLOR_BTNFACE作为客户区的背景颜色:
wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ;
可以在BTNLOOK程序中自行尝试一下这种做法。Windows知道,在WNDCLASS结构中hbrBackground值很低时,它实际上指的是系统颜色,而不是一个实际的句柄。 Windows要求在使用这些标识符时,要加上1,并在WNDCLASS结构的hbrBackground 字段中加以指定,但这样做并没有什么深远的目的,只是为了防止出现空值(NULL)。如果系统颜色发生了改变,而你的程序正在运行,那么客户区表面会变为无效,Windows将使用新的COLOR_BTNFACE值。但现在我们又引发了另一个问题。在使用TextOut 表示文本时,Windows使用设备环境中定义的值来作为文本的背景颜色(它清除了文本的背景)和文本的颜色。预设值是白色(背景)和黑色(文本),而不管系统颜色或是窗口类结构的 hbrBackground定义的颜色。所以需要使用SetTextColor和SetBkColor改变文本和文本背景颜色以匹配系统颜色。可以在获得设备环境的句柄之后做这件事情:
SetBkColor (hdc, GetSysColor (COLOR_btnfaCE));
SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT));
现在,客户区的背景、文本背景和文本颜色与按钮的颜色都是一致的。但是,当程序正在运行时,如果用户更改了系统颜色,则需要更改文本的背景颜色和文本颜色。为此,使用以下代码即可:
case WM_SYSCOLORCHANGE:
InvalidateRect (hwnd, NULL, TRUE) ;
break ;
■WM_CTLCOLORBTN 消息
前面介绍了如何调整客户区的颜色和文本颜色以匹配按钮背景颜色。这是否意味着可 以在程序中把按钮的颜色也相应调为自己喜欢的颜色呢?在理论上是可以的,但在实际中 有问题。最好不要用SetSysColors改变按钮的外观。这将影响到Windows环境下正在运行 的所有程序,用户不会喜欢这样的。
一个更好的办法(也是理论上如此)是处理WM_CTLCOLORBTN消息。当子窗口即将重绘其客户区时,按钮控件会把这个消息发送给其父窗口的窗口过程。父窗口可以利用这个机会来改变子窗口的背景颜色。
当父窗口的窗口过程收到WM_CTLCOLORBTN消息时,wParam消息参数是按钮的设备环境的句柄,IParam是按钮的窗口句柄。当父窗口的窗口过程收到此消息,按钮控件便已经取得其设备环境。当在窗口过程中处理WM_CTLCOLORBTN信息时,可以选择以做法:
●使用SetTextColor设置文本颜色。
●使用SetBkColor设置文本的背景颜色。
●返回子窗口的画刷句柄。
从理论上讲,子窗口使用这个画刷来着色背景。在不再需要画刷时,你需要负责销毁画刷。
这里有一个关于WM_CTLCOLORBTN的问题:只有按键按钮和自绘按钮会发送 WM_CTLCOLORBTN到它们的父窗口,而只有自绘按钮需要对父窗口使用画刷绘制背景的消息处理过程作出反应。这是相当没有必要的,因为父窗口负责绘制自绘按钮。
在本章后面,我们将讨论类似于WM_CTLCOLORBTN的消息的有用情况,不过这些消息是应用于其他类型控件的。
8.1.6 第49练:单选框和复选框状态的判断
/*------------------------------------------------------------------
049 WIN32 API 每日一练
第49个例子BTNSTATE.C:单选框和复选框状态的判断
CreateFont函数
GetWindowText函数
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE hInst;//全局变量
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("Button State");
…(略)
return msg.wParam;
}
//窗口过程
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
//设置缓冲区
static TCHAR szBufSex[10];
static TCHAR szBufMarriage[10];
static TCHAR szBufPet[20];
static TCHAR szBufSubmit[100];
static TCHAR szBufTmp[10];
static HFONT hFont; //逻辑字体
//一组单选按钮---BS_AUTORADIOBUTTON
static HWND labSex; //静态文本框--性别
static HWND radioMale; //单选按钮--男
static HWND radioFemale; //单选按钮--女
//一组单选按钮---BS_AUTORADIOBUTTON
static HWND labMarriage; //静态文本框--婚姻状况
static HWND radioMarried; //单选按钮--已婚
static HWND radioSingle; //单选按钮--未婚
static HWND radioSecrecy; //单选按钮--保密
//一组复选框---BS_AUTOCHECKBOX
static HWND labPet; //静态文本框--你的宠物
static HWND checkboxDog; //复选框--狗
static HWND checkboxCat; //复选框--猫
static HWND checkboxFish;//复选框--鱼
static HWND checkboxOther;//复选框--其他
static HWND btnSubmit; //按钮控件
switch (message) {
case WM_CREATE:
hFont = CreateFont(
-14, -7, 0, 0, 400,
FALSE, FALSE, FALSE, DEFAULT_CHARSET,
OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY,
FF_DONTCARE, TEXT("微软雅黑")
);
//选择性别
labSex = CreateWindow(
TEXT("static"), TEXT("你的性别:"),
WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_RIGHT/*文字居右*/,
10, 10, 80, 26,
hWnd, (HMENU)1, hInst, NULL
);
radioMale = CreateWindow(
TEXT("button"), TEXT("男"),
WS_CHILD | WS_VISIBLE | BS_LEFT/*文字居左*/
| BS_AUTORADIOBUTTON /*单选按钮*/
| WS_GROUP/*一组控件中的第一个控件。*/,
95, 10, 50, 26,
hWnd, (HMENU)2, hInst, NULL
);
radioFemale = CreateWindow(
TEXT("button"), TEXT("女"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON,
150, 10, 50, 26,
hWnd, (HMENU)3, hInst, NULL
);
//选择婚姻状况
labMarriage = CreateWindow(
TEXT("static"), TEXT("婚姻状况:"),
WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_RIGHT,
10, 40, 80, 26,
hWnd, (HMENU)4, hInst, NULL
);
radioMarried = CreateWindow(
TEXT("button"), TEXT("已婚"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON | WS_GROUP,
95, 40, 65, 26,
hWnd, (HMENU)5, hInst, NULL
);
radioSingle = CreateWindow(
TEXT("button"), TEXT("未婚"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON,
165, 40, 65, 26,
hWnd, (HMENU)6, hInst, NULL
);
radioSecrecy = CreateWindow(
TEXT("button"), TEXT("保密"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON,
235, 40, 100, 26,
hWnd, (HMENU)7, hInst, NULL
);
//你的宠物
labPet = CreateWindow(
TEXT("static"), TEXT("你的宠物:"),
WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_RIGHT,
10, 70, 80, 26,
hWnd, (HMENU)8, hInst, NULL
);
checkboxDog = CreateWindow(
TEXT("button"), TEXT("狗"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX/*复选框*/,
95, 70, 50, 26,
hWnd, (HMENU)9, hInst, NULL
);
checkboxCat = CreateWindow(
TEXT("button"), TEXT("猫"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX,
150, 70, 50, 26,
hWnd, (HMENU)10, hInst, NULL
);
checkboxFish = CreateWindow(
TEXT("button"), TEXT("鱼"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX,
205, 70, 50, 26,
hWnd, (HMENU)11, hInst, NULL
);
checkboxOther = CreateWindow(
TEXT("button"), TEXT("其他"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX,
260, 70, 65, 26,
hWnd, (HMENU)12, hInst, NULL
);
//创建按钮控件
btnSubmit = CreateWindow(TEXT("button"), TEXT("提 交"),
WS_CHILD | WS_VISIBLE | WS_BORDER | BS_FLAT,
95, 110, 200, 36,
hWnd, (HMENU)13, hInst, NULL
);
SendMessage(labSex, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(radioMale, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(radioFemale, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(labMarriage, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(radioMarried, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(radioSingle, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(radioSecrecy, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(labPet, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(checkboxDog, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(checkboxCat, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(checkboxFish, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(checkboxOther, WM_SETFONT, (WPARAM)hFont, 0);
SendMessage(btnSubmit, WM_SETFONT, (WPARAM)hFont, 0);
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
if (wmEvent == BN_CLICKED) {
switch (wmId) {
//获取性别
case 2:
case 3:
GetWindowText((HWND)lParam, szBufSex, 10);
break;
//获取婚姻状况
case 5:
case 6:
case 7:
GetWindowText((HWND)lParam, szBufMarriage, 10);
break;
//获取宠物
case 9:
case 10:
case 11:
case 12:
//memset(szBufPet, 0, sizeof(szBufPet));
RtlZeroMemory(szBufPet, 20);
//是否选中狗
if (SendMessage(checkboxDog, BM_GETCHECK, 0, 0) == BST_CHECKED)
{
GetWindowText(checkboxDog, szBufTmp, 10);
wsprintf(szBufPet, TEXT("%s %s"), szBufPet, szBufTmp);
RtlZeroMemory(szBufTmp, 10);
}
//是否选中猫
if (SendMessage(checkboxCat, BM_GETCHECK, 0, 0) == BST_CHECKED)
{
GetWindowText(checkboxCat, szBufTmp, 10);
wsprintf(szBufPet, TEXT("%s %s"), szBufPet, szBufTmp);
RtlZeroMemory(szBufTmp, 10);
}
//是否选中鱼
if (SendMessage(checkboxFish,
BM_GETCHECK, 0, 0) == BST_CHECKED)
{
GetWindowText(checkboxFish, szBufTmp, 10);
wsprintf(szBufPet, TEXT("%s %s"), szBufPet, szBufTmp);
RtlZeroMemory(szBufTmp, 10);
}
//是否选中其他
if (SendMessage(checkboxOther, BM_GETCHECK, 0, 0)
== BST_CHECKED)
{
GetWindowText(checkboxOther, szBufTmp, 10);
wsprintf(szBufPet, TEXT("%s %s"), szBufPet, szBufTmp);
RtlZeroMemory(szBufTmp, 10);
}
break;
case 13:
wsprintf(szBufSubmit,
TEXT("你的性别:%s\n婚姻状况:%s\r\n你的宠物:%s"), szBufSex, szBufMarriage, szBufPet);
MessageBox(hWnd, szBufSubmit, TEXT("信息提示"),
MB_ICONINFORMATION);
break;
}
}
return 0;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
//绘制...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
DeleteObject(hFont);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
/******************************************************************************
CreateFont函数:创建具有指定特性的逻辑字体。随后可以将逻辑字体选择为任何设备的字体。
HFONT CreateFontA(
int cHeight, //字体的字符单元格或字符的高度(以逻辑单位为单位)。
int cWidth, //所请求字体中字符的平均宽度(以逻辑单位表示)
int cEscapement, //擒纵矢量和设备x轴之间的角度,以十分之一度为单位。擒纵向量平行于一行文本的基线。
int cOrientation, //每个字符的基线和设备的x轴之间的角度,以十分之一度为单位。
int cWeight, //字体的权重范围为0到1000。例如,正常时为400,粗体为700。如果该值为零,则使用默认权重。
DWORD bItalic, //如果设置为TRUE,则指定斜体字体。
DWORD bUnderline, //如果设置为TRUE,则指定带下划线的字体。
DWORD bStrikeOut, //删除线字体,如果设置为TRUE。
DWORD iCharSet, //字符集。DEFAULT_CHARSET
DWORD iOutPrecision, //输出精度OUT_CHARACTER_PRECIS
DWORD iClipPrecision, //裁剪精度OUT_CHARACTER_PRECIS
DWORD iQuality, //输出质量。DEFAULT_QUALITY
DWORD iPitchAndFamily,//字体的间距和系列。FF_DONTCARE
LPCSTR pszFaceName //指向以空字符结尾的字符串的指针,该字符串指定字体的字体名称。
);
*******************************************************************************
GetWindowText函数:将指定窗口标题栏(如果有的话)的文本复制到缓冲区中
int GetWindowTextA(
HWND hWnd, //包含文本的窗口或控件的句柄
LPSTR lpString, //将接收文本的缓冲区
int nMaxCount //要复制到缓冲区的最大字符数,包括空字符。如果文本超出此限制,则会被截断
);
*/
运行结果:
图8-2 单选框和复选框状态判断
总结
实例BTNSTATE.C绘制了3个静态文本控件(8.2节详细讲述),两组单选按钮控件,一组复选按钮控件和一个单独的“提交”按钮。点击“提交”按钮时,会弹出一个MessageBox框,显示用户选择的按钮控件信息。
在窗口过程的WM-CREATE消息中,首先调用CreateFont函数创建一个新的逻辑字体,接着调用CreateWindow函数分别创建3个静态文本控件和10个按钮控件。然后调用SendMessage函数由主窗口向所有控件子窗口发送WM_SETFONT消息,设置子窗口控件的字体使用新创建的逻辑字体。
处理WM-COMMAND消息,wParam参数的低字为子窗口控件ID,高字为控件通知码。如果主窗口捕捉的通知码为BN_CLICKED时,依据子窗口控件ID分别调用GetWindowText函数获取子窗口控件文本信息存入缓冲区。
【注意】如果是复选框按钮控件,需要先调用SendMessage函数向复选框按钮控件发送BM_GETCHECK消息获取选中状态,如果返回值是BST_CHECKED则表示已选中状态。
最后如果点击“提交”按钮控件,则调用MessageBox函数在对话框中显示按钮控件选中信息。
●主窗口与子窗口控件的信息交互
主窗口使用SendMessage函数向子窗口控件发送消息,获取或设置状态信息。
子窗口控件通过向主窗口过程发送WM_COMMAND消息传递信息。通过WM_COMMAND消息的wParam参数低字传递控件ID,高字传递控件通知码,lParam参数为控件句柄。
8.1.7 第50练:自定义按钮控件
/*------------------------------------------------------------------
050 WIN32 API 每日一练
第50个例子OWNDRAW.C:自定义按钮控件
WM_DRAWITEM 消息
WM_COMMAND消息
BS_OWNERDRAW按钮控件样式
MoveWindow函数
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#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");
…(略)
return msg.wParam;
}
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 ());//获取系统默认对话框字体高度
// 创建自绘按钮
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 (LOWORD(wParam))
{
case ID_SMALLER:
rc.left += cxClient / 20; //左边缩小5%
rc.right -= cxClient / 20; //左边缩小5%
rc.top += cyClient / 20; //上边缩小5%
rc.bottom -= cyClient / 20; //下边缩小5%
break;
case ID_LARGER:
rc.left -= cxClient / 20; //左边扩大5%
rc.right += cxClient / 20; //左边扩大5%
rc.top -= cyClient / 20; //上边扩大5%
rc.bottom += cyClient / 20; //下边扩大5%
break;
}
//更改指定窗口的位置和尺寸,将按钮移动到新中心
MoveWindow ( hwnd, rc.left, rc.top, rc.right - rc.left,
rc.bottom - rc.top, TRUE) ;
return 0 ;
//收到按钮外观改变消息--自绘按钮
case WM_DRAWITEM :
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 ;
switch (pdis->CtlID)
{
case ID_SMALLER:
//上边的三角形
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;
}
//如果按钮被选中,则反转矩形
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) ;
}
/******************************************************************************
LPDRAWITEMSTRUCT pdis ; //提供所有者窗口用来确定如何绘制所有者绘制的控件或菜单项的信息
typedef struct tagDRAWITEMSTRUCT {
UINT CtlType; //控件类型
UINT CtlID; //组合框、列表框、按钮或静态控件的标识符。此成员不用于菜单项。
UINT itemID; //菜单项的菜单项标识符或列表框或组合框中的项的索引。
UINT itemAction; //所需的绘图操作
UINT itemState; //当前绘制操作发生后项目的视觉状态
HWND hwndItem; //组合框、列表框、按钮和静态控件的控件句柄。
HDC hDC; //设备上下文的句柄;在控件上执行绘图操作时必须使用此设备上下文。
RECT rcItem; //一个矩形,用于定义要绘制的控件的边界。
ULONG_PTR itemData; //与菜单项关联的应用程序定义的值。
} DRAWITEMSTRUCT, *PDRAWITEMSTRUCT, *LPDRAWITEMSTRUCT;
******************************************************************************
在WM_DRAWITEM消息中,IParam参数是一个指针,指向一个DRAWITEMSTRUCT 类型的结构。
OWNDRAW程序将这个指针保存在变量pdis中。这个结构存有必要的信息 来帮助程序绘制按钮。
(相同的结构也可用于自绘制列表框和菜单项。)
这个结构中与按钮 有非常重要关系的字段是:hDC(按钮设备环境),
rcItem(RECT结构,提供按钮的尺寸),
CtllD(控件窗口 ID)和itemState(它显示按钮是否按下或有输入焦点)。
******************************************************************************
MoveWindow函数:更改指定窗口的位置和尺寸。
对于顶级窗口,位置和尺寸是相对于屏幕的左上角的。
对于子窗口,它们相对于父窗口客户区的左上角。
BOOL MoveWindow(
HWND hWnd, //窗口的句柄。
int X, //窗口左侧的新位置。
int Y, //窗口顶部的新位置。
int nWidth, //窗口的新宽度。
int nHeight,//窗口的新高度。
BOOL bRepaint//指示是否要重新绘制窗口。
);
*/
运行结果:
图8-3 自定义按钮控件
总结
实例OWNDRAW.C绘制了两个自定义按钮控件。如图8-3所示,一个用于扩大窗口,一个缩小窗口。
窗口过程在处理WM_CREATE消息时,首先获取对话框字体的宽和高,然后调用CreateWindow函数创建两个自定义按钮控件,创建的方法非常简单,在窗口样式参数中添加BS_OWNERDRAW样式就可以了。
在WM_SIZE消息中,将自定义按钮控件移至窗口中心并重绘。
在WM_COMMAND消息中,根据子窗口控件ID缩放窗口的宽和高。
在WM_DRAWITEM消息中,绘制自定义按钮控件的图形,分别绘制4个向内的三角形和4个向外的三角形,并填充黑色。当按钮被选中时,调用InvertRect函数将按钮矩形颜色反转,并绘制一个焦点矩形。
●DRAWITEMSTRUCT结构
成员 | 常见值 | 备注 |
CtlType | ODT_BUTTON: 按钮控件 ODT_COMBOBOX:组合框控件 ODT_LISTBOX: 列表框控件 ODT_LISTVIEW:列表视图控件 ODT_MENU: 菜单项 ODT_STATIC: 静态文本控件 ODT_TAB: Tab控件 | 控件类型 |
CtlID | 自绘控件ID,而对于菜单项则不需要使用该成员 | |
itemID | 菜单项ID | |
itemAction | ODA_DRAWENTIRE:要整个控件被绘制,设置该值 ODA_FOCUS: 要在获得或失去焦点时被绘制。 ODA_SELECT:要在选中状态改变时被绘制。 | 指定绘制行为,其取值可以为右边中所示值的一个或者多个的联合 |
itemState | ODS_CHECKED:要菜单被选中,可设置该值 ODS_COMBOBOXEDIT:只绘制组合框中选择区域 ODS_DEFAULT:默认值 ODS_DISABLED:禁用控件 ODS_FOCUS:控件需要焦点,则该置该值 ODS_GRAYED:控件被灰色 ODS_SELECTED:选中的菜单项 …… | 所绘项的可见状态 |
hwndItem | 自绘控件的窗口句柄;如果自绘的对象时菜单项,则表示包含该菜单项的菜单句柄。 | |
hDC | 指定了绘制操作所使用的设备环境 | |
rcItem | 指定了将被绘制的矩形区域 | |
itemData |