读书人

Windows程式开发设计指南(9)子视窗控

发布时间: 2012-09-21 15:47:26 作者: rapoo

Windows程式开发设计指南(九)子视窗控制项

9. 子视窗控制项

回忆第七章的CHECKER程式。这些程式显示了矩形网格。当您在一个矩形中按下滑鼠按键时,该程式就画一个x;如果您再按一次滑鼠按键,那么x就消失。虽然这个程式的CHECKER1和CHECKER2版本只使用一个主视窗,但CHECKER3版本却为每个矩形使用一个子视窗。这些矩形由一个叫做ChildProc的独立视窗讯息处理程式维护。

如果有必要,无论矩形是否被选中,都可以给ChildProc增加一种向其父视窗讯息处理程式(WndProc)发送讯息的手段。通过呼叫GetParent,子视窗讯息处理程式能确定其父视窗的视窗代号:

hwndParent = GetParent (hwnd) ;

其中,hwnd是子视窗的视窗代号。它可以向其父视窗讯息处理程式发送讯息:

SendMessage (hwndParent, message, wParam, lParam) ;

那么message应该设定为什么呢?您可以随意地设定,数值大小可以与WM_USER相同或更大,这些数字代表和预先定义的WM_ 讯息不冲突的讯息。也许对这个讯息,子视窗可以将wParam设定为它的子视窗ID。如果在该子视窗单击,那么lParam可以被设为1;如果未在该子视窗上单击,那么lParam将被设为0。这是处理方式的一种选择。

事实上,这是在建立一个「子视窗控制项」。当子视窗的状态改变时,子视窗处理滑鼠和键盘讯息并通知父视窗。使用这种方法,子视窗就变成了其父视窗的高阶输入装置。它将与自己在萤幕上的图形外观相应的处理,对使用者输入的回应以及在发生重要的输入事件时通知另一个视窗的方法给封装起来。

虽然您可以建立自己的子视窗控制项,但是也可以利用一些预先定义的视窗类别(和视窗讯息处理程式)来建立标准的子视窗控制项,您一定在别的Windows程式中看到过这些控制项。这些控制项采用的形式有:按钮、核取方块、编辑方块、清单方块、下拉式清单方块、字串标签和卷动列。例如,如果想在您的试算表程式的某个角落放置一个标有「Recalculate」的按钮,那么您可以通过呼叫CreateWindow来建立这个按钮。您不必担心滑鼠操作、按钮显示操作或按下该按钮时的自动闪烁操作,这些是由Windows内部完成的。您所要做的只是拦截WM_COMMAND讯息-当按钮被按下时,它通过这一讯息通知您的视窗讯息处理程式。真的这样简单吗?是的,一点也没错。

子视窗控制项在对话方块中最常用。在第十一章中您将会看到,子视窗控制项的位置和尺寸,是在范例程式的资源描述叙述中的对话方块模板里定义的。但是,您也可以使用预先定义的,在普通视窗显示区域里的子视窗控制项。您可以呼叫一次CreateWindow来建立一个子视窗,并通过呼叫MoveWindow来调整子视窗的位置和尺寸。父视窗讯息处理程式向子视窗控制项发送讯息,子视窗控制项向父视窗讯息处理程式传回讯息。

在建立普通视窗时,首先定义视窗类别,并使用RegisterClass将其注册到Windows中,然後用CreateWindow命令依据该视窗类别建立一个普通视窗,从第三章开始,我们就是这么做的。但是,当您使用预先定义的某个控制项时,不必为子视窗注册视窗类别,视窗类别已经存在於Windows之中,并且有一个预先定义的名字。您只需在CreateWindow中把它们用作视窗类别参数。CreateWindow中的视窗样式参数准确地定义了子视窗控制项的外形和功能。Windows内建了处理发送给依据这些视窗类别建立的子视窗讯息的视窗讯息处理程式。

直接在您的视窗上使用子视窗控制项完成某些任务,这些任务的层次低於在对话方块中使用子视窗控制项所要求的层次。这里,对话方块管理器在您的程式和控制项之间增加一个隔离层。值得一提的,您可能会发现在您的视窗上建立的子视窗控制项,没有利用Tab键或方向键将输入焦点从一个控制项移动到另一个控制项的内部功能。子视窗控制项能够获得输入焦点,但是获得後,它将不能把输入焦点传回给父视窗。这就是本章要解决的问题。

Windows程式设计的文件在两个地方讨论了子视窗控制项:首先是,简单的常用控制项,我们可以在/Platform SDK/User Interface Services/Controls的文件所描述的无数对话方块中看到。这些子视窗包括按钮(其中包括核取方块的单选按钮)、静态控制项(例如文字标签)、编辑方块(您可以在此编辑一行或多行文字)、卷动列、清单方块和下拉式清单方块。除下拉式清单方块以外,在Windows 1.0中就包括了这些控制项。这部分的Windows文件还包括Rich Text文字编辑控制项,它与编辑方块相似,但还允许编辑不同字体与样式的格式化文字,以及桌面应用工具列。

相对於「常用控制项」,还有一些神秘的特殊控制项。这些控制项在/Platform SDK/User Interface Services/Shell and Common Controls/Common Controls描述。本章不讨论常用控制项,但它们将出现在本书的其他部分。在这部分的Windows文件中,很容易找到您想从别的Windows应用程式中应用到您自己的应用程式里头那些部分资讯。

按钮类别
 

下面我们将通过叫做BTNLOOK(「button look」)的程式来开始介绍按钮视窗类别,如程式9-1所示。BTNLOOK建立10个子视窗按钮控制项,每个控制项对应一个标准的按钮样式,因此共有10种标准按钮样式。

 程式9-1  BTNLOOKBTNLOOK.C/*--------------------------------------   BTNLOOK.C -- Button Look Program         (c) Charles Petzold, 1998---------------------------------------*/#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])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 ("_______   ______  ______"),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,                               hwnd, (HMENU) i,               ((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) ;                    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 :          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) ;          break ;               case WM_DESTROY :          PostQuitMessage (0) ;          return 0 ;     }     return DefWindowProc (hwnd, message, wParam, lParam) ;}

单击按钮时,按钮就给父视窗讯息处理程式发送一个WM_COMMAND讯息,也就是我们所熟悉的WndProc。BTNLOOK的WndProc将该讯息的wParam参数和lParam参数显示在显示区域的右边,如图9-1所示。

具有BS_OWNERDRAW样式的按钮在视窗上显示为一个背景阴影,因为这种样式的按钮是由程式来负责绘制的。该按钮表示它需要由包含lParam讯息参数的WM_DRAWITEM讯息来绘制,而lParam讯息参数是一个指向DRAWITEMSTRUCT型态结构的指标。在BTNLOOK中,这些讯息也同样被显示。我将在本章的後面更详细地讨论这种拥有者绘制(owner draw)按钮。


 

Windows程式开发设计指南(9)子视窗控制项

图9-1 BTNLOOK的萤幕显示

建立子视窗
 

BTNLOOK定义了一个叫做button的结构,它包括了按钮视窗样式和描述性字串,它们对应於10个按钮型态,所有按钮视窗样式都以字母「BS」开头,它表示「按钮样式」。10个按钮子视窗是在WndProc中处理WM_CREATE讯息的过程中使用一个for回圈建立的。CreateWindow呼叫使用下面这些参数:

Class name(类别名称)

Window text(视窗文字)

Window style(视窗样式)

x position(x位置)

y position(y位置)

Width(宽度)

Height(高度)

Parent window(父视窗)

Child window ID(子视窗ID)

Instance handle(执行实体代号)

Extra parameters(附加参数)

TEXT ("button")

button[i].szText

WS_CHILD | WS_VISIBLE | button[i].iStyle

cxChar

cyChar * (1 + 2 * i)

20 * xChar

7 * yChar / 4

hwnd

(HMENU) i

((LPCREATESTRUCT) lParam) -> hInstance

NULL

类别名称参数是预先定义的名字。视窗样式使用WS_CHILD、WS_VISIBLE以及在button结构中定义的10个按钮样式之一(BS_PUSHBUTTON、BS_DEFPUSHBUTTON等等)。视窗文字参数(对於普通视窗来说,它是显示在标题列中的文字)将在每个按钮上显示出来。我简单地使用标识按钮样式文字的x位置和y位置参数,说明子视窗左上角相对於父视窗显示区域左上角的位置。宽度和高度参数规定了每个子视窗的宽度和高度。请注意,我用的是GetDialogBaseUnits函式来获得内定字体字元的宽度和高度。这是对话方块用来获得文字尺寸的函式。此函式传回一个32位元的值,其中低字组表示宽度,高字组表示高度。由於GetDialogBaseUnits传回的值与从GetTextMetrics获得的值大致上相同,但GetDialogBaseUnits有时使用起来会更方便些,而且能够与对话方块控制项更好地保持一致。

对每个子视窗,它的子视窗ID参数应该各不相同。在处理来自子视窗的WM_COMMAND讯息时,ID帮助您的视窗讯息处理程式识别出相应的子视窗。注意子视窗ID是作为CreateWindow的一个参数传递的,该参数通常用於指定程式的功能表,因此子视窗ID必须被强制转换为HMENU。

CreateWindow呼叫的执行实体代号看起来有点奇怪,但是它利用了如下的事实,亦即在处理WM_CREATE讯息的过程中,lParam实际上是指向CREATESTRUCT (「建立结构」)结构的指标,该结构有一个hInstance成员。所以将lParam转换成指向CREATESTRUCT结构的一个指标,并取出hInstance。

(有些Windows程式使用名为hInst的整体变数,使视窗讯息处理程式能存取WinMain中的执行实体代号。在WinMain中,您只需在建立主视窗之前设定:

hInst = hInstance ;

在第七章中的CHECKER3程式中,我们曾用GetWindowLong取得执行实体代号:

GetWindowLong (hwnd, GWL_HINSTANCE)

这几种方法都是正确的。)

在呼叫CreateWindow之後,我们不必再为这些子视窗做任何事情,由Windows中的按钮视窗讯息处理程式负责维护它们,并处理所有的重画工作(BS_OWNERDRAW样式的按钮例外,它要求程式绘制它,这些将在後面加以讨论)。在程式终止时,如果父视窗已经被清除,那么Windows将清除这些子视窗。

子视窗向父视窗发讯息
 

当您执行BTNLOOK时,将看到在显示区域的左边会显示出不同的按钮型态。我在前面已经提到过,用滑鼠单击按钮时,子视窗控制项就向其父视窗发送一个WM_COMMAND讯息。BTNLOOK拦截WM_COMMAND讯息并显示wParam和lParam的值,它们的含义如下:

LOWORD (wParam)

HIWORD (wParam)

lParam

子视窗ID

通知码

子视窗代号

如果您正在移植16位元Windows程式,那么要注意改变这些讯息参数以容纳32位元的代号。

子视窗ID是在建立子视窗时传递给CreateWindow的值。在BTNLOOK中,这些ID被显示在显示区域中,并使用0到9分别标识10个按钮。子视窗代号是Windows从CreateWindow传回的值。

通知码更详细表示了讯息的含义。按钮通知码的可能值在Windows表头档案中定义如下:

表9-1
按钮通知码识别字值BN_CLICKED0BN_PAINT1BN_HILITE or BN_PUSHED2BN_UNHILITE or BN_UNPUSHED3BN_DISABLE4BN_DOUBLECLICKED or BN_DBLCLK5BN_SETFOCUS6BN_KILLFOCUS7

实际上,您不会看到这些按钮值中的大多数。从1到4的通知码是用於一种叫做BS_USERBUTTON的已不再使用的按钮的(它已经由BS_OWNERDRAW和另一种不同的通知方式所替换)。通知码6到7只有当按钮样式包括标识BS_NOTIFY才发送。通知码5只对BS_RADIOBUTTON、BS_AUTORADIOBUTTON和BS_OWNERDRAW按钮发送,或者当按钮样式中包括BS_NOTIFY时,也为其他按钮发送。

您会注意到,在用滑鼠单击按钮时,该按钮文字的周围会有虚线。这表示该按钮拥有了输入焦点,所有键盘输入都将传送给子视窗按钮控制项,而不是传送给主视窗。但是,当该按钮控制项拥有输入焦点时,它将忽略所有的键盘输入,除了Spacebar键例外,此时Spacebar键与滑鼠具有相同的效果。

父视窗向子视窗发送讯息
 

虽然BTNLOOK中没有显示这一事实,但是父视窗讯息处理程式也能向子视窗控制项发送讯息。这些讯息包括以字首WM开头的许多讯息。另外,在WINUSER.H中还定义了8个按钮说明讯息;字首BM表示「按钮讯息」。这些按钮讯息如下表所示:

表9-2
按钮讯息值BM_GETCHECK0x00F0BM_SETCHECK0x00F1BM_GETSTATE0x00F2BM_SETSTATE0x00F3BM_SETSTYLE0x00F4BM_CLICK0x00F5BM_GETIMAGE0x00F6BM_SETIMAGE0x00F7

BM_GETCHECK和BM_SETCHECK讯息由父视窗发送给子视窗控制项,以取得或者设定核取方块和单选按钮的选中标记。BM_GETSTATE和BM_SETSTATE讯息表示按钮处於正常状态还是(滑鼠或Spacebar键按下时的)「按下」状态。我们将在讨论按钮的每种型态时,看到这些讯息是如何起作用的。BM_SETSTYLE讯息允许您在按钮建立之後改变按钮样式。

每个子视窗控制项都具有一个在其兄弟中唯一的视窗代号和ID值。对於代号和ID这两者,知道其中的一个您就可以获得另一个。如果您知道子视窗控制项的视窗代号,那么您可以用下面的叙述来获得ID:

id = GetWindowLong (hwndChild, GWL_ID) ;

第七章的CHECKER3程式曾用此函式(与SetWindowLong一起)来维护注册视窗类别时保留的特殊区域的资料。在建立子视窗时,Windows保留了GWL_ID识别字存取的资料。您也可以使用:

id = GetDlgCtrlID (hwndChild) ;

虽然函式中的「Dlg」部分指的是对话方块,但实际上这是一个通用的函式。

知道ID和父视窗代号,您就能获得子视窗代号:

hwndChild = GetDlgItem (hwndParent, id) ;

按键
 

在BTNLOOK中显示的前两个按钮是「压入」按钮。按钮是一个矩形,包括了CreateWindow呼叫中视窗文字参数所指定的文字。该矩形占用了在CreateWindow或者MoveWindow呼叫中给出的全部高度和宽度,而文字在矩形的中心。

按键控制项主要用来触发一个立即回应的动作,而不保留任何形式的开/关指示。两种型态的按钮控制项有两种视窗样式,分别叫做BS_PUSHBUTTON和BS_DEFPUSHBUTTON,BS_DEFPUSHBUTTON中的「DEF」代表「内定」。当用来设计对话方块时,BS_PUSHBUTTON控制项和BS_DEFPUSHBUTTON控制项的作用不同。但是当用作子视窗控制项时,两种型态的按钮作用相同,尽管BS_DEFPUSHBUTTON的边框要粗一些。

当按钮的高度为文字字元高度的7/4倍时,按钮的外观看起来最好,其中文字字元由BTNLOOK使用;而按钮的宽度至少调节到文字的宽度再加上两个字元的宽度。

当滑鼠游标在按钮中时,按下滑鼠按键将使按钮用三维阴影重画自己,就好像真的被按下一样。放开滑鼠按键时,就恢复按钮的原貌,并向父视窗发送一个WM_COMMAND讯息和BN_CLICKED通知码。与其他按钮型态相似,当按钮拥有输入焦点时,在文字的周围就有虚线,按下及释放Spacebar键与按下及释放滑鼠按键具有相同的效果。

您可以通过给视窗发送BM_SETSTATE讯息来模拟按钮闪动。以下的操作将导致按钮被按下:

SendMessage (hwndButton, BM_SETSTATE, 1, 0) ;

下面的呼叫使按钮恢复正常:

SendMessage (hwndButton, BM_SETSTATE, 0, 0) ;

hwndButton视窗代号是从CreateWindow呼叫传回的值。

您也可以向按键发送BM_GETSTATE讯息,子视窗控制项传回按钮目前的状态:如果按钮被按下,则传回TRUE;如果按钮处於正常状态,则传回FALSE。但是,绝大多数应用并不需要这一讯息。因为按钮不保留任何开/关资讯,所以BM_SETCHECK讯息和BM_GETCHECK讯息不会被用到。

核取方块
 

核取方块是一个文字方块,文字通常出现在核取方块的右边(如果您在建立按钮时指定了BS_LEFTTEXT样式,那么文字会出现在左边;您也许将用BS_RIGHT直接调整文字来组合此样式)。核取方块通常用於允许使用者对选项进行选择的应用程式中。核取方块的常用功能如同一个开关:单击框一次将显示勾选标记,再次单击清除勾选标记。

核取方块最常用的两种样式是BS_CHECKBOX和BS_AUTOCHECKBOX。在使用BS_CHECKBOX时,您需要自己向该控制项发送BM_SETCHECK讯息来设定勾选标记。wParam参数设1时设定勾选标记,设0时清除勾选标记。通过向该控制项发送BM_GETCHECK讯息,您可以得到该核取方块的目前状态。在处理来自控制项的WM_COMMAND讯息时,您可以用如下的指令来翻转X标记:

SendMessage ((HWND) lParam, BM_SETCHECK, (WPARAM)!SendMessage ((HWND) lParam, BM_GETCHECK, 0, 0), 0) ;

注意第二个SendMessage呼叫前面的运算子「!」,其中lParam是在WM_COMMAND讯息中传给使用者视窗讯息处理程式的子视窗代号。如果您以後又想知道按钮的状态,那么可以向它发送另一条BM_GETCHECK讯息;您也可以将目前状态储存在您的视窗讯息处理程式中的一个静态变数里,或者向它发送BM_SETCHECK讯息来初始化带勾选标记的BS_CHECKBOX核取方块:

SendMessage (hwndButton, BM_SETCHECK, 1, 0) ;

对BS_AUTOCHECKBOX样式,按钮自己触发勾选标记的开和关,所以您的视窗讯息处理程式可以忽略WM_COMMAND讯息。当您需要按钮目前的状态时,可以向控制项发送BM_GETCHECK讯息:

iCheck = (int) SendMessage (hwndButton, BM_GETCHECK, 0, 0) ;

如果该按钮被选中,则iCheck的值为TRUE或者非零数;如果按钮末被选中,则iCheck的值为FALSE或0。

其余两种核取方块样式是BS_3STATE和BS_AUTO3STATE,正如它们名字所暗示的,这两种样式能显示第三种状态-核取方块内是灰色-它出现在向控制项发送wParam等於2的WM_SETCHECK讯息时。灰色是向使用者表示此框不能被选本章的或者禁止使用。

核取方块沿矩形的左边框对齐,并集中在呼叫CreateWindow时规定的矩形的顶边和底边之间,在该矩形内的任何地方按下滑鼠都会向其父视窗发送一个WM_COMMAND讯息。核取方块的最小高度是一个字元的高度,最小宽度是文字中的字元数加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是一个指向以null为终结的字串指标。对於一般的视窗来说,这个文字是标题列的文字;对於按钮控制项来说,它是随著该按钮显示的文字。

您也可以取得视窗目前的文字:

iLength = GetWindowText (hwnd, pszBuffer, iMaxLength) ;

iMaxLength指定复制到pszBuffer指向的缓冲区中的最大字元数。该函式传回复制的字元数。您可以首先通过下面的呼叫来获得特定文字的长度:

iLength = GetWindowTextLength (hwnd) ;

可见的和启用的按钮
 

为了接收滑鼠和键盘输入,子视窗必须是可见的(被显示)和被启用的。当视窗是可见的而未被启用时,那么视窗将以灰色而非黑色显示文字。

如果在建立子视窗时,您没有将WS_VISIBLE包含在视窗类别中,那么直到呼叫ShowWindow时子视窗才会被显示出来:

ShowWindow (hwndChild, SW_SHOWNORMAL) ;

如果您将WS_VISIBLE包含在视窗类别中,就没有必要呼叫ShowWindow。但是,您可以通过呼叫ShowWindow将子视窗隐藏起来:

ShowWindow (hwndChild, SW_HIDE) ;

您可以通过下面的呼叫来确定子视窗是否可见:

IsWindowVisible (hwndChild) ;

您也可以使子视窗被启用或者不被启用。在内定情况下,视窗是被启用的。您可以通过下面的呼叫使视窗不被启用:

EnableWindow (hwndChild, FALSE) ;

对於按钮控制项,这具有使按钮字串变成灰色的作用。按钮将不再对滑鼠输入和键盘输入做出回应,这是表示按钮选项目前不可用的最好方法。

您可以通过下面的呼叫使子视窗再次被启用:

EnableWindow (hwndChild, TRUE) ;

您还可以使用下面的呼叫来确定子视窗是否被启用:

IsWindowEnabled (hwndChild) ;

按钮和输入焦点
 

我在本章前面已经提到过,当用滑鼠单击按钮、核取方块、单选框和拥有者绘制按钮时,它们接收到输入焦点。这些控制项使用文字周围的虚线来表示它拥有了输入焦点。当子视窗控制项得到输入焦点时,其父视窗就失去了输入焦点;所有的键盘输入都进入子视窗控制项,而不会进入父视窗中。但是,子视窗控制项只对Spacebar键作出回应,此时Spacebar键的作用就如同滑鼠按键一样。这种情形导致了一个明显的问题:您的程式失去了对键盘处理的控制项。让我们看看我们对此能做一些什么。

我在第六章中已经提到过,当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 == GetParent ((HWND) wParam))          SetFocus (hwnd) ;     return 0 ;

但是,这两种方法都有缺点:它们阻止按钮对Spacebar键作出回应,因为该按钮总是得不到输入焦点。一个更好的方法是使按钮得到输入焦点,也能让使用者用Tab键从一个按钮转移到另一个按钮。这听起来似乎不太可能,在本章的後面,我们将要说明在COLORS1程式中如何用「视窗子类别化」技术来实作这种方法。

控制项与颜色
 

您可以在图9-1中看到,许多按钮的显示看起来并不正确。按键还好,但是其他按钮却带有一个本不应该在那里的一个矩形灰色背景。这是因为这些按钮本来是为对话方块中的显示而设计的,而在Windows 98中,对话方块有一个灰色的表面。我们的视窗有一个白色的表面,这是因为我们在WNDCLASS结构中就是这样定义的。

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

我们已经这么做了,因为我们经常在显示区域中显示文字,而GDI使用在内定装置内容中定义的文字颜色和背景颜色,它们总是黑色和白色。为了使这些按钮更加美观一些,我们必须要改变显示区域的颜色使之和按钮的背景颜色一致,所以要以某种方法将按钮的背景颜色改为白色。

解决此问题的第一步,是理解Windows对「系统颜色」的使用。

系统颜色
 

Windows保留了29种系统颜色以供各种显示使用。您可以使用GetSysColor和SetSysColors来获得和设定这些颜色。在Windows表头档案中定义的识别字规定了系统颜色。使用SetSysColors设定的系统颜色只在目前Windows对话过程中有效。

借助Windows「控制台」程式的「显示器」部分,您可以改变一些(但不是全部)系统颜色。若是Microsoft Windows NT,选中的颜色会储存在系统登录中;若是Microsoft Windows 98,则储存在WIN.INI档案中。系统登录和WIN.INI档案都为这29种系统颜色使用了关键字(与GetSysColor和SetSysColors的识别字不同),在系统颜色的後面跟著红、绿、蓝三种颜色的值,该值的变化范围是0到255。下表说明了这29种系统颜色是如何在GetSysColor、SetSysColors以及WIN.INI关键字中用常数来标识的。这张表是按照COLOR_ 常数值(从0开始到28结束)顺序排列的:

表9-3
GetSysColor和SetSysColors系统登录键或WIN.INI识别字内定的RGB值COLOR_SCROLLBARScrollbarC0-C0-C0COLOR_BACKGROUNDBackground00-80-80COLOR_ACTIVECAPTIONActiveTitle00-00-80COLOR_INACTIVECAPTIONInactiveTitle80-80-80COLOR_MENUMenuC0-C0-C0COLOR_WINDOWWindowFF-FF-FFCOLOR_WINDOWFRAMEWindowFrame00-00-00COLOR_MENUTEXTMenuTextC0-C0-C0COLOR_WINDOWTEXTWindowText00-00-00COLOR_CAPTIONTEXTTitleTextFF-FF-FFCOLOR_ACTIVEBORDERActiveBorderC0-C0-C0COLOR_INACTIVEBORDERInactiveBorderC0-C0-C0COLOR_APPWORKSPACEAppWorkspace80-80-80COLOR_HIGHLIGHTHighlight00-00-80COLOR_HIGHLIGHTTEXTHighlightTextFF-FF-FFCOLOR_BTNFACEButtonFaceC0-C0-C0COLOR_BTNSHADOWButtonShadow80-80-80COLOR_GRAYTEXTGrayText80-80-80COLOR_BTNTEXTButtonText00-00-00COLOR_INACTIVECAPTIONTEXTInactiveTitleTextC0-C0-C0COLOR_BTNHIGHLIGHTButtonHighlightFF-FF-FFCOLOR_3DDKSHADOWButtonDkShadow00-00-00COLOR_3DLIGHTButtonLightC0-C0-C0COLOR_INFOTEXTInfoText00-00-00COLOR_INFOBKInfoWindowFF-FF-FF[no identifier; use value 25]ButtonAlternateFaceB8-B4-B8COLOR_HOTLIGHTHotTrackingColor00-00-FFCOLOR_GRADIENTACTIVECAPTIONGradientActiveTitle00-00-80COLOR_GRADIENTINACTIVECAPTIONGradientInactiveTitle80-80-80

这29种颜色的预设值是由显示驱动程式提供的,在不同的机器上可能略有不同。

坏消息:虽然这些颜色中有许多似乎都可以从颜色常数名称上了解其代表意义(例如,COLOR_BACKGROUND是所有视窗後面的桌面区域颜色),在最近版本的Windows中系统颜色的使用变得非常混乱。以前,Windows在视觉上要比今天简单得多。实际上,在Windows 3.0以前,只定义了前13种系统颜色。但随著使用看起来越来越难以控制的立体外观,相对应地也需要更多的系统颜色。

按钮颜色
 

对需要多种颜色的每一个按钮来说,这个问题更加地明显。COLOR_BTNFACE被用於按键主要的表面颜色,以及其他按钮主要的背景颜色(这也是用於对话方块和讯息方块的系统颜色)。COLOR_BTNSHADOW被建议用作按键右下边、以及核取方块内部和单选按钮圆点的阴影。对於按键,COLOR_BTNTEXT被用作文字颜色;而对於其他的按钮,则使用COLOR_WINDOWTEXT作为文字颜色。还有其他几种系统颜色用於按钮设计的各个部分。

因此,如果您想在我们的显示区域表面显示按钮,那么一种避免颜色冲突的方法便是屈服於这些系统颜色。首先,在定义视窗类别时使用COLOR_BTNFACE作为您显示区域的背景颜色:

wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ;

您可以在BTNLOOK程式中尝试这种方法。当WNDCLASS结构中的hbrBackground值是这个值时,Windows会明白这实际上指的是一种系统颜色而非一个实际的代号。Windows要求当您在WNDCLASS结构的hbrBackground栏中指定这些识别字时加上1,这样做的目的是防止其值为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讯息,这是当子视窗即将为其显示区域著色时,由按钮控制项发送给其父视窗讯息处理程式的一个讯息。父视窗可以利用这个机会来改变子视窗讯息处理程式将用来著色的颜色(在Windows的16位元版本中,一个称为WM_CTLCOLOR的讯息被用於所有的控制项,现在针对每种型态的标准控制项,分别代之以不同的讯息)。

当父视窗讯息处理程式收到WM_CTLCOLORBTN讯息时,wParam讯息参数是按钮的装置内容代号,lParam是按钮的视窗代号。当父视窗讯息处理程式得到这个讯息时,按钮控制项已经获得了它的装置内容。当您的视窗讯息处理程式处理一个WM_CTLCOLORBTN讯息时,您必须完成以下三个动作:

读书人网 >windows

热点推荐