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];<