见招拆招《Windows程序设计》(八)
相关的例子:下载>>> 作者:Zoologist 于2008-6-16上传 

子窗口控件

回忆前面介绍过的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程序设计的文件在两个地方讨论了子窗口控件:首先是,简单的常用控件。这些子窗口包括按钮(其中包括复选框的单选按钮)、静态控件(例如文字卷标)、编辑方块(您可以在此编辑一行或多行文字)、卷动列、清单方块和下拉式清单方块。除下拉式清单方块以外,在Windows 1.0中就包括了这些控件。这部分的Windows文件还包括Rich Text文字编辑控件,它与编辑方块相似,但还允许编辑不同字体与样式的格式化文字,以及桌面应用工具列。

相对于「常用控件」,还有一些神秘的特殊控件。本章不讨论常用控件,但它们将出现在本系列文章的其它部分。在这部分的Windows文件中,很容易找到您想从别的Windows应用程序中应用到您自己的应用程序里头那些部分信息。

按钮类别

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

程序9-1 BTNLOOK

        
BTNLOOK.ASM
;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm
	
	WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
	WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
	NUM		equ	(buttonEnd - button) /4 /2
	
.DATA
	szAppName   db "BtnLook",0
	szBtn			db "button",0
	szDraw		db	"WM_DRAWITEM",0
	szCom			db	"WM_COMMAND",0
	
   szTop     db "message   wParam  lParam",0
	szUnd     db "_______   ______  ______",0
	szFormat  db "%-16s%04X-%04X   %04X-%04X",0
                                          
	szBtnStyle01		db	"PUSHBUTTON",0
	szBtnStyle02		db	"DEFPUSHBUTTON",0	
	szBtnStyle03		db	"CHECKBOX",0	
	szBtnStyle04		db	"AUTOCHECKBOX",0	
	szBtnStyle05		db	"RADIOBUTTON",0	
	szBtnStyle06		db	"3STATE",0	
	szBtnStyle07		db	"AUTO3STATE",0	
	szBtnStyle08		db	"GROUPBOX",0	
	szBtnStyle09		db	"AUTORADIO",0	
	szBtnStyle10		db	"OWNERDRAW",0	

button		dd		BS_PUSHBUTTON,			offset szBtnStyle01
				dd		BS_DEFPUSHBUTTON, 	offset szBtnStyle02
				dd		BS_CHECKBOX,			offset szBtnStyle03
				dd		BS_AUTOCHECKBOX,		offset szBtnStyle04		
				dd		BS_RADIOBUTTON,		offset szBtnStyle05				
				dd		BS_3STATE,				offset szBtnStyle06
				dd		BS_AUTO3STATE,			offset szBtnStyle07		
        		dd		BS_GROUPBOX,			offset szBtnStyle08
        		dd		BS_AUTORADIOBUTTON,	offset szBtnStyle09
        		dd		BS_OWNERDRAW,			offset szBtnStyle10
buttonEnd	label	DWORD

.DATA?
	hInstance	dd ?
	hwndButton	HWND NUM dup(?)
	rect		RECT	<?>
	cxChar		dd	?
	cyChar 		dd ?
.CODE
START:

	invoke GetModuleHandle,NULL
	mov hInstance,eax
	invoke WinMain,hInstance,NULL,NULL,SW_SHOWDEFAULT
	invoke ExitProcess,0

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,iCmdShow:DWORD
	LOCAL wndclass   :WNDCLASSEX
	LOCAL msg  :MSG
	local hWnd :HWND
	
	mov wndclass.cbSize,sizeof WNDCLASSEX	
	mov wndclass.style,CS_HREDRAW or CS_VREDRAW	
	mov wndclass.lpfnWndProc,offset WndProc

	mov wndclass.cbClsExtra,0
	mov wndclass.cbWndExtra,0
	
	push hInst
	pop wndclass.hInstance
	
	invoke LoadIcon,NULL,IDI_APPLICATION
	mov wndclass.hIcon,eax	
	
	invoke LoadCursor,NULL,IDC_ARROW
	mov wndclass.hCursor,eax	
	
	invoke GetStockObject,WHITE_BRUSH
	mov wndclass.hbrBackground,EAX
	
	mov wndclass.lpszMenuName,NULL
	mov wndclass.lpszClassName,offset szAppName

	mov wndclass.hIconSm,0
	
	invoke RegisterClassEx, ADDR wndclass
	.if (EAX==0)
		 invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),addr szAppName,MB_ICONERROR 		
		 ret
	.endif
        
	invoke CreateWindowEx,
					NULL,
					ADDR szAppName, 	;window class name
					CTXT("Button Look"), 	;window caption
					WS_OVERLAPPEDWINDOW,	;window style
					CW_USEDEFAULT,		;initial x position
					CW_USEDEFAULT,		;initial y position
					CW_USEDEFAULT, 		;initial x size
					CW_USEDEFAULT,		;initial y size
					NULL,			;parent window handle
					NULL,			;window menu handle
					hInstance,		;program instance handle
					NULL										;creation parameters
	mov hWnd,eax
	
	invoke ShowWindow,hWnd,iCmdShow
	invoke UpdateWindow,hWnd
	
	StartLoop:
		invoke GetMessage,ADDR msg,NULL,0,0
			cmp eax, 0
			je ExitLoop
				invoke TranslateMessage, ADDR msg
				invoke DispatchMessage,  ADDR msg
			jmp StartLoop
	ExitLoop:
	
	mov eax,msg.wParam
	ret

WinMain endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
	LOCAL szBuffer[100]	:BYTE
   	LOCAL hdc:HDC
   	LOCAL ps:PAINTSTRUCT
   	LOCAL i :DWORD

	.if uMsg==WM_CREATE
		invoke GetDialogBaseUnits ;cxChar = LOWORD (GetDialogBaseUnits ())
    		and	eax,0FFFFh
     		mov	cxChar,eax
      		
		invoke GetDialogBaseUnits
      		shr	eax,16
      		mov	cyChar,eax	;cyChar = HIWORD (GetDialogBaseUnits ())
        
;LPCREATESTRUCT不是结构,只是一个指向结构CREATESTRUCT的指针.以下是该结构的信息:
;The CREATESTRUCT structure defines the initialization parameters passed to the window procedure of an application.
;typedef struct tagCREATESTRUCT { // cs
;    LPVOID    lpCreateParams;
;    HINSTANCE hInstance;
;    HMENU     hMenu;
;    HWND      hwndParent;
;    int       cy;
;    int       cx;
;    int       y;
;    int       x;
;    LONG      style;
;    LPCTSTR   lpszName;
;    LPCTSTR   lpszClass;
;    DWORD     dwExStyle;
;} CREATESTRUCT;
;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) ;

			
        	mov	i,0
     	nexti:
     		push	NULL
     	  	mov	esi,lParam
     	  	mov	eax,[esi+4]
     	  	push	eax
     	  	push	i
	     	push	hwnd
	     	mov	eax,cyChar
	     	mov	ecx,7
	     	mul	ecx
	     	shr	eax,2
	     	push	eax
	     	mov	eax,cxChar
	     	mov	ecx,20
	     	mul	ecx
	     	push	eax
	 	mov	eax,i
		shl	eax,1
		inc	eax
		mov	ecx,cyChar
		mul	ecx
		push	eax
	  	push	cxChar
		lea	esi,button
		mov	eax,i
		shl	eax,3
		mov	ebx,eax
		mov	eax,button[ebx]
		or	eax,WS_CHILD
		or	eax,WS_VISIBLE
		push	eax
	  	mov	eax,button[ebx+4]
		push	eax
		lea	eax,szBtn
		push	eax
	  	push	NULL
		call	CreateWindowEx
		  
		lea	esi,button
		mov	ebx,i
		shl	ebx,2
		mov	hwndButton[ebx],eax
		  
     	  	inc		i
     	  	mov		eax,i
     	  	cmp		eax,NUM
     	  	jl		nexti
		xor	eax,eax
		ret
		
	.elseif uMsg ==  WM_SIZE
		 mov		eax,cxChar
		 mov		ecx,24
		 mul		ecx
		 mov		rect.left,eax
		 
		 mov		eax,cyChar
		 shl		eax,1
		 mov		rect.top,eax
		 
		 mov		eax,lParam
		 and		eax,0FFFFh
		 mov		rect.right,eax
		 
		 mov		eax,lParam
		 shr		eax,16
		 mov		rect.bottom,eax
		 
		 xor		eax,eax
       		 ret
 	.elseif uMsg ==  WM_PAINT
	        invoke		InvalidateRect,hwnd,addr rect, TRUE
	        invoke		BeginPaint,hwnd,addr ps
	        invoke		GetStockObject,SYSTEM_FIXED_FONT
	        invoke		SelectObject,hdc,eax
		invoke		SetBkMode,hdc, TRANSPARENT
	
	        ;TextOut (hdc, 24 * cxChar, cyChar, szTop, lstrlen (szTop)) ;        
	        invoke		lstrlen,addr szTop
	        push		eax
	        lea		eax,szTop
	        push		eax
	        push		cyChar
	        
	        mov		eax,cxChar
	        mov		ecx,24
	        mul		ecx
	        push		eax
	        push		hdc
	        call		TextOut
        

	        ;TextOut (hdc, 24 * cxChar, cyChar, szUnd, lstrlen (szUnd)) ;
	        invoke		lstrlen,addr szUnd
	        push		eax
	        lea		eax,szUnd
	        push		eax
	        push		cyChar
	        
	        mov		eax,cxChar
	        mov		ecx,24
	        mul		ecx
	        push		eax
	        push		hdc
	        call		TextOut        
	        
	        invoke		EndPaint,hwnd, addr ps
	        
	        xor			eax,eax
	        ret
	.elseif uMsg == WM_DRAWITEM 
		  jmp			@f
	.elseif uMsg == WM_COMMAND
	@@:
		mov		ebx,cyChar
		neg		ebx
		invoke	ScrollWindow ,hwnd, 0, ebx, addr rect, addr rect
         	invoke	GetDC,hwnd
         	mov		hdc,eax

		invoke	GetStockObject, SYSTEM_FIXED_FONT
		invoke	SelectObject ,hdc, eax
         
	        mov		eax,lParam	;LOWORD (lParam)
	        and		eax,0FFFFh
	        push		eax
	        
	        mov		eax,lParam	;HIWORD (lParam)
	        shr		eax,16
	        push		eax
	        
	        mov		eax,wParam	;LOWORD (wParam)
	        and		eax,0FFFFh
	        push		eax
	        
	        mov		eax,wParam	;HIWORD (wParam)
	        shr		eax,16
	        push		eax
        
		.if		uMsg == WM_DRAWITEM
			lea	eax,szDraw
		.else
			lea	eax,szCom
		.endif	
		push	eax
		lea	eax,szFormat
		push	eax
			
		lea	eax,szBuffer
		push	eax
		call	wsprintf
		push	eax
			
		lea	eax,szBuffer
		push	eax
			
		xor	edx,edx
		mov	eax,rect.bottom
		mov	ecx,cyChar
		div	ecx
		dec	eax
		mov	ecx,cyChar
		mul	ecx
		push	eax
			
		mov	eax,cxChar
		mov	ecx,24
		mul	ecx
		push	eax
			
		push	hdc
		call	TextOut
			
         	invoke   ReleaseDC,hwnd, hdc
         	invoke   ValidateRect ,hwnd, addr rect
        
         	jmp	@f

	.elseif uMsg == WM_DESTROY
		invoke PostQuitMessage,NULL
		xor	eax,eax
		ret
	.endif
   @@:		
		invoke DefWindowProc,hwnd,uMsg,wParam,lParam
	ret
WndProc endp

END START
        

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

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


 

wpe1.jpg (41247 字节)

图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_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

实际上,您不会看到这些按钮值中的大多数。从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_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消息表示按钮处于正常状态还是(鼠标或Spacebar键按下时的)「按下」状态。我们将在讨论按钮的每种型态时,看到这些消息是如何起作用的。BM_SETSTYLE消息允许您在按钮建立之后改变按钮样式。

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

id = GetWindowLong (hwndChild, GWL_ID) ;
        

第七章的CHECKER序3程曾用此函数(与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_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 AppWorkspace 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_GRAYTEXT GrayText 80-80-80
COLOR_BTNTEXT ButtonText 00-00-00
COLOR_INACTIVECAPTIONTEXT 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
[no identifier; use value 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_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消息时,您必须完成以下三个动作:

  • 使用SetTextColor选择设定一种文字颜色。
     
  • 使用SetBkColor选择设定一种文字背景颜色。
     
  • 将一个画刷句柄传回给子窗口。
     

理论上,子窗口使用该画刷来着色背景。当不再需要这个画刷时,您应该负责清除它。

下面是使用WM_CTLCOLORBTN的问题所在:只有按键和拥有者绘制按钮才给其父窗口发送WM_CTLCOLORBTN,而只有拥有者绘制按钮才会响应父窗口消息处理程序对消息的处理,而使用画刷来着色背景。这基本上是没有意义的,因为无论怎样都是由父窗口来负责绘制拥有者绘制按钮。

在本章后面,我们将说明,在某些情况下,一些类似于WM_CTLCOLORBTN但适用于其它型态控件的消息将更为有用。

拥有者绘制按钮

如果您想对按钮的所有可见部分实行全面控制,而不想被键盘和鼠标消息处理所干扰,那么您可以建立BS_OWNERDRAW样式的按钮,如程序9-2所展示的那样。

程序9-2 OWNDRAW

        
OWNDRAW.ASM
        

该程序在其显示区域的中央包含了两个按钮,如图9-2所示。左边的按钮有四个三角形指向按钮的中央,按下该按钮时,窗口的尺寸将缩小10%。右边的按钮有四个向外指的三角形,按下此按钮时,窗口的尺寸将增大10%。

如果您只需要在按钮中显示图标或位图,您可以用BS_ICON或BS_BITMAP样式,并用BM_SETIMAGE消息设定位图。但是,对于BS_OWNERDRAW样式的按钮,它允许完全自由地绘制按钮。


 

wpe1.jpg (22840 字节)

图9-2 OWNDRAW的屏幕显示

在处理WM_CREATE消息处理期间,OWNDRAW建立了两个BS_OWNERDRAW样式的按钮;按钮的宽度是系统字体的8倍,高度是系统字体的4倍(在使用预先定义好的位图绘制按钮时,这些尺寸在VGA上建立的按钮为64图素宽64图素高,知道这些数据将非常有用)。这些按钮尚未就定位,在处理WM_SIZE消息处理期间,通过呼叫MoveWindow函数,OWNDRAW将按钮位置放在显示区域的中心。

按下这些按钮时,它们就会产生WM_COMMAND消息。为了处理这些WM_COMMAND消息,OWNDRAW呼叫GetWindowRect,将整个窗口(不只是显示区域)的位置和尺寸存放在RECT(矩形)结构中,这个位置是相对于屏幕的。然后,根据按下的是左边还是右边的按钮,OWNDRAW调节这个矩形结构的各个字段值。程序再通过呼叫MoveWindow来重新确定位置和尺寸。这将产生另一个WM_SIZE消息,按钮被重新定位在显示区域的中央。

如果这是程序所做的全部处理,那么这完全可以,只不过按钮是不可见的。使用BS_OWNERDRAW样式建立的按钮会在需要重新着色的任何时候都向它的父窗口发送一个WM_DRAWITEM消息。这出现在以下几种情况中:当按钮被建立时,当按钮被按下或被放开时,当按钮得到或者失去输入焦点时,以及当按钮需要重新着色的任何时候。

在处理WM_DRAWITEM消息处理期间,lParam消息参数是指向型态DRAWITEMSTRUCT结构的指针,OWNDRAW程序将这个指针储存在pdis变量中,这个结构包含了画该按钮时程序所必需的消息(这个结构也可以让自绘清单方块和菜单使用)。对按钮而言非常重要的结构字段有hDC (按钮的设备内容)、rcItem(提供按钮尺寸的RECT结构)、CtlID(控件窗口ID)和itemState (它说明按钮是否被按下,或者按钮是否拥有输入焦点)。

呼叫FillRect用白色画刷抹掉按钮的内面,呼叫FrameRect在按钮的周围画上黑框,由此OWNDRAW便启动了WM_DRAWITEM处理过程。然后,通过呼叫Polygon,OWNDRAW在按钮上画出4个黑色实心的三角形。这是一般的情形。

如果按钮目前被按下,那么DRAWITEMSTRUCT的itemState字段中的某位将被设为1。您可以使用ODS_SELECTED常数来测试这些位。如果这些位被设立,那么OWNDRAW将通过呼叫InvertRect将按钮翻转为相反的颜色。如果按钮拥有输入焦点,那么itemState的ODS_FOCUS位将被设立。在这种情况下,OWNDRAW通过呼叫DrawFocusRect,在按钮的边界内画一个虚线的矩形。

在使用拥有者绘制按钮时,应该注意以下几个方面:Windows获得设备内容并将其作为DRAWITEMSTRUCT结构的一个字段。保持设备内容处于您找到它时所处的状态,任何被选进设备内容的GDI对象都必需被释放。另外,当心不要在定义按钮边界的矩形外面进行绘制。

静态类别

在CreateWindow函数中指定窗口类别为「static」,您就可以建立静态文字的子窗口控件。这些子窗口非常「文静」。它既不接收鼠标或键盘输入,也不向父窗口发送WM_COMMAND消息。

当您在静态子窗口上移动或者按下鼠标时,这个子窗口将拦截WM_NCHITTEST消息并将HTTRANSPARENT的值传回给Windows,这将使Windows向其下层窗口,通常是它的父窗口,发送相同的WM_NCHITTEST消息。父窗口常常将该消息传递给DefWindowProc,在这里,它被转换为显示区域的鼠标消息。

前六个静态窗口样式只简单地在子窗口的显示区域内画一个矩形或者边框。在下表的上部,「RECT」静态样式(左列)是填入图样的矩形样式;三个「FRAME」样式(右列)是没有填入图样的矩形轮廓:

SS_BLACKRECT

SS_GRAYRECT

SS_WHITERECT

SS_BLACKFRAME

SS_GRAYFRAME

SS_WHITEFRAME

「BLACK」、「GRAY」、「WHITE」并不意味着黑、灰和白色,这些颜色是由系统颜色决定的,如表9-4所示。

表9-4

静态控件

系统颜色

BLACK COLOR_3DDKSHADOW
GRAY COLOR_BTNSHADOW
WHITE COLOR_BTNHIGHLIGHT

对这些样式,CreateWindow呼叫中的窗口文字字段被忽略。矩形的左上角开始于x位置坐标和y位置坐标,这些坐标都相对于父窗口。您也可以使用SS_ETCHEDHORZ、SS_ETCHEDVERT或者SS_ETCHEDFRAME ,采用灰色和白色建立一个形似阴影的边框。

静态类别也包括了三种文字样式:SS_LEFT、SS_RIGHT和SS_CENTER。它们建立左对齐、置右对齐和居中文字。文字在CreateWindow呼叫的窗口文字参数中给出,并且在以后可以用SetWindowText来改变它。当静态控件的窗口消息处理程序显示文字时,它使用DrawText函数以及DT_WORDBREAK、DT_NOCLIP和DT_EXPANDTABS参数。文字在子窗口的矩形内可以按文字进行换行。

这三种文字样式子窗口的背景通常为COLOR_BTNFACE,而文字本身是COLOR_WINDOWTEXT。在拦截WM_CTLCOLORSTATIC消息时,您可以通过呼叫SetTextColor来改变文字颜色,通过SetBkColor来改变背景颜色,并传回背景画刷句柄。后面的COLORS1程序展示了这一点。

最后,静态类别还包括了窗口样式SS_ICON和SS_USERITEM,但是当它们被用作子窗口控件时却没有任何意义。我们在讨论对话框时还要提及它们。

滚动条类别

我在前面讨论过滚动条,也讨论了「窗口滚动条」和「滚动条控件」之间的一些区别。SYSMETS程序使用窗口滚动条,它出现在窗口的右边和底部。您可以在建立窗口时通过将标识符WS_VSCROLL、WS_HSCROLL或者两者都包含在窗口样式中,让窗口加上滚动条。现在我们准备建立一些滚动条控件,它们是能在父窗口的显示区域的任何地方出现的子窗口。您可以使用预先定义的窗口类别「scrollbar」以及两个滚动条样式SBS_VERT和SBS_HORZ中的一个来建立子窗口滚动条控件。

与按钮控件(以及将在后面讨论的编辑和清单方块控件)不同,滚动条控件不向父窗口发送WM_COMMAND消息,而是像窗口滚动条那样发送WM_VSCROLL和WM_HSCROLL消息。在处理卷动消息时,您可以通过lParam参数来区分窗口滚动条与滚动条控件。对子窗口滚动条其值为0,对于滚动条控件其值为滚动条窗口句柄。对窗口滚动条和滚动条控件来说,wParam参数的高字组和低字组的含义相同。

虽然窗口滚动条有固定的宽度,Windows使用CreateWindow呼叫中(或者在后面的MoveWindow呼叫中)给定的矩形尺寸来确定滚动条控件的尺寸。您可以建立细而长的滚动条控件,也可以建立短而粗的滚动条控件。

如果您想建立与窗口滚动条尺寸相同的滚动条控件,那么可以使用GetSystemMetrics取得水平滚动条的高度:

GetSystemMetrics (SM_CYHSCROLL) ;
        

或者垂直滚动条的宽度:

GetSystemMetrics (SM_CXVSCROLL) ;
        

根据Windows文件,滚动条窗样式标识符SBS_LEFTALIGN、SBS_RIGHTALIGN、SBS_TOP ALIGN和SBS_BOTTOMALIGN给出滚动条的标准尺寸,但是这些样式只在对话框中对滚动条有效。

对窗口滚动条,您可以使用同样的呼叫来建立滚动条控件的范围和位置:

SetScrollRange (hwndScroll, SB_CTL, iMin, iMax, bRedraw) ;
        
SetScrollPos (hwndScroll, SB_CTL, iPos, bRedraw) ;
        
SetScrollInfo (hwndScroll, SB_CTL, &si, bRedraw) ;
        

其区别在于:窗口滚动条将父窗口的句柄作为第一个参数,并且以SB_VERT或者SB_HORZ作为第二个参数。

令人吃惊的是,名为COLOR_SCROLLBAR的系统颜色不再用于滚动条。两端的按钮和小方块的颜色由COLOR_BTNFACE、COLOR_BTNHILIGHT、COLOR_BTNSHADOW、COLOR_BTNTEXT (用于小箭头)、COLOR_DKSHADOW和COLOR_BTNLIGHT决定。两端按钮之间区域的颜色由COLOR_BTNFACE和COLOR_BTNHIGHLIGHT决定。

如果您拦截了WM_CTLCOLORSCROLLBAR消息,那么可以在消息处理中传回画刷以取代该颜色。让我们来试一下。

COLORS1程序

为了解滚动条和静态子窗口的一些用法-也为了深入了解颜色-我们将使用COLORS1程序,如程序9-3所示。COLORS1在显示区域的左半部显示三种滚动条,并分别标以「Red」、「 Green」和「Blue」。当您挪动滚动条时,显示区域的右半部将变为三种原色混合而成的合成色,三种原色的数值显示在三个滚动条的下面。

程序9-3 COLORS1

        
COLORS1.ASM
;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm
	
	WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
	WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
	ScrollProc PROTO	hwnd:HWND,  message:UINT,wParam:WPARAM , lParam:LPARAM 
	
.DATA
	szAppName   db "Colors1",0

	szColorLabel	db	"Red    ",0
			db	"Green  ",0
			db	"Blue   ",0
.DATA?
	idFocus 	dd	 ?
	OldScroll	WNDPROC	 3 dup (?)
	crPrim		COLORREF 3 dup  (?)
   	hBrush		HBRUSH  3 dup(?)
   	hBrushStatic 	HBRUSH	?	
	hwndScroll	HWND    3 dup(?)
	hwndLabel	HWND    3 dup(?)
	hwndValue	HWND    3 dup(?)
	hwndRect 	HWND	?
	color		dd	3 dup(?)
	cyChar		dd	?
	rcColor		RECT	<>

.CODE
START:

	invoke GetModuleHandle,NULL
	invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
	invoke ExitProcess,0

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,iCmdShow:DWORD
	LOCAL wndclass   :WNDCLASSEX
	LOCAL msg  :MSG
	local hWnd :HWND
	
	mov wndclass.cbSize,sizeof WNDCLASSEX	
	mov wndclass.style,CS_HREDRAW or CS_VREDRAW	
	mov wndclass.lpfnWndProc,offset WndProc

	mov wndclass.cbClsExtra,0
	mov wndclass.cbWndExtra,0
	
	push hInst
	pop wndclass.hInstance
	
	invoke LoadIcon,NULL,IDI_APPLICATION
	mov wndclass.hIcon,eax	
	
	invoke LoadCursor,NULL,IDC_ARROW
	mov wndclass.hCursor,eax	
	
	invoke GetStockObject,WHITE_BRUSH
	mov wndclass.hbrBackground,EAX
	
	mov wndclass.lpszMenuName,NULL
	mov wndclass.lpszClassName,offset szAppName

	mov wndclass.hIconSm,0
	
	invoke RegisterClassEx, ADDR wndclass
	.if (EAX==0)
		 invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),addr szAppName,MB_ICONERROR 		
		 ret
	.endif
        
	invoke CreateWindowEx,
					NULL,
					ADDR szAppName, 	;window class name
					CTXT("Color Scroll"),   ;window caption
					WS_OVERLAPPEDWINDOW,	;window style
					CW_USEDEFAULT,		;initial x position
					CW_USEDEFAULT,		;initial y position
					CW_USEDEFAULT, 		;initial x size
					CW_USEDEFAULT,		;initial y size
					NULL,			;parent window handle
					NULL,			;window menu handle
					hInst,			;program instance handle
					NULL			;creation parameters
	mov hWnd,eax
	
	invoke ShowWindow,hWnd,iCmdShow
	invoke UpdateWindow,hWnd
	
	StartLoop:
		invoke GetMessage,ADDR msg,NULL,0,0
			cmp eax, 0
			je ExitLoop
				invoke TranslateMessage, ADDR msg
				invoke DispatchMessage,  ADDR msg
			jmp StartLoop
	ExitLoop:
	
	mov eax,msg.wParam
	ret

WinMain endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
	LOCAL szBuffer[100]	:BYTE
  	LOCAL i, cxClient, cyClient:DWORD
        LOCAL hInstance:HINSTANCE
   

	.if uMsg==WM_CREATE
		xor	ebx,ebx
		mov	crPrim[ebx],0FF0000h
		add	ebx,4
		mov	crPrim[ebx],000FF00h
		add	ebx,4
		mov	crPrim[ebx],00000FFh
		
		
                ;hInstance = (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE) ;
        	invoke	GetWindowLong,hwnd,GWL_HINSTANCE
        	mov	hInstance,eax
       
                ; Create the white-rectangle window against which the
                ; scroll bars will be positioned. The child window ID is 9.
                invoke	CreateWindowEx,NULL,CTXT("static"),NULL,
                	 WS_CHILD or WS_VISIBLE or SS_WHITERECT,
                	 0, 0, 0, 0,
                	 hwnd,9,hInstance,NULL
          	mov	hwndRect,eax
          	
                mov	i,0
             @@:
                ; The three scroll bars have IDs 0, 1, and 2, with
                ; scroll bar ranges from 0 through 255.
                invoke	CreateWindowEx,NULL,CTXT("scrollbar"),NULL,
                               WS_CHILD or WS_VISIBLE or WS_TABSTOP or SBS_VERT,
                               0, 0, 0, 0,
		               hwnd, i, hInstance, NULL
		mov	ebx,i
		shl	ebx,2
		mov	hwndScroll[ebx],eax
				
                invoke	SetScrollRange ,hwndScroll[ebx], SB_CTL, 0, 255, FALSE
                invoke	SetScrollPos   ,hwndScroll[ebx], SB_CTL, 0, FALSE

      		; The three color-name labels have IDs 3, 4, and 5,
     		; and text strings "Red", "Green", and "Blue".
     		mov	edx,i
     		shl	edx,3
     		lea	ecx,szColorLabel
     		add	ecx,edx
     		invoke	CreateWindowEx,NULL,CTXT("static"),ecx,
     				WS_CHILD or WS_VISIBLE or SS_CENTER,
                                0, 0, 0, 0,
                                hwnd, i+3,
                                hInstance, NULL
		mov	hwndLabel[ebx],eax     		

                ; The three color-value text fields have IDs 6, 7,
                ; and 8, and initial text strings of "0".
                invoke	CreateWindowEx,NULL,CTXT("static"),CTXT("0"),
                 		WS_CHILD or WS_VISIBLE or SS_CENTER,
                                0, 0, 0, 0,
                                hwnd, (i + 6),
                                hInstance, NULL
                mov	hwndValue[ebx],eax
                invoke	SetWindowLong,hwndScroll[ebx],GWL_WNDPROC,ScrollProc
                mov	OldScroll[ebx],eax
                invoke	CreateSolidBrush,crPrim[ebx]
                mov	hBrush[ebx],eax
               
             	inc	i
             	cmp	i,3
                jNz	@b
                
                invoke	GetSysColor,COLOR_BTNHIGHLIGHT
                invoke	CreateSolidBrush,eax
                mov	hBrushStatic,eax

		invoke	GetDialogBaseUnits
		shr	eax,16
		mov	cyChar,eax

	        xor	eax,eax
	        ret
	.elseif uMsg ==  WM_SIZE
        	mov	eax,lParam
        	and	eax,0FFFFh
        	mov	cxClient,eax
        	
        	mov	eax,lParam
		shr	eax,16
		mov	cyClient,eax

		push	cyClient
		push	cxClient
		push	0
		mov	eax,cxClient
		shr	eax,1
		push	eax
		push	offset	rcColor
		call	SetRect
        
        	push	TRUE
        	push	cyClient
        	mov	eax,cxClient
        	shr	eax,1
        	push	eax
        	push	0
        	push	0
        	push	hwndRect
        	call	MoveWindow

                mov	i,0
             @@:
                ;MoveWindow (hwndScroll[i],
                ;                 (2 * i + 1) * cxClient / 14, 2 * cyChar,
                ;                  cxClient / 14, cyClient - 4 * cyChar, TRUE) ;
        	push	TRUE
        	mov	eax,cyClient
        	mov	ebx,cyChar
        	shl	ebx,2
        	sub	eax,ebx
        	push	eax
        	xor	edx,edx
        	mov	eax,cxClient
        	mov	ecx,14
        	div	ecx
        	push	eax
        	mov	eax,cyChar
        	shl	eax,1
        	push	eax
        	mov	eax,i
        	shl	eax,1
        	inc	eax
        	mov	ecx,cxClient
        	mul	ecx
        	xor	edx,edx
        	mov	ecx,14
        	div	ecx
        	push	eax
 		mov	ebx,i
		shl	ebx,2
		mov	eax,hwndScroll[ebx]
		push	eax
		call	MoveWindow
		
                ;MoveWindow (hwndLabel[i],
                ;                 (4 * i + 1) * cxClient / 28, cyChar / 2,
                ;                 cxClient / 7, cyChar, TRUE)
        	push	TRUE
        	push	cyChar
        	xor	edx,edx
        	mov	eax,cxClient
        	mov	ecx,7
        	div	ecx
        	push	eax
        	mov	eax,cyChar
        	shr	eax,1
        	push	eax
        	mov	eax,i
        	shl	eax,2
        	inc	eax
        	mov	ecx,cxClient
        	mul	ecx
        	xor	edx,edx
        	mov	ecx,28
        	div	ecx
        	push	eax
 		mov	ebx,i
		shl	ebx,2        	
		mov	eax,hwndLabel[ebx]
		push	eax
		call	MoveWindow

                ;MoveWindow (hwndValue[i],
	        ;   (4 * i + 1) * cxClient / 28,
                ;   cyClient - 3 * cyChar / 2,
                ;   cxClient / 7, cyChar, TRUE)
                push	TRUE
                push	cyChar
                xor	edx,edx
                mov	eax,cxClient
                mov	ecx,7
                div	ecx
                push	eax
                mov	eax,cyClient
                mov	ebx,cyChar
                add	ebx,ebx
                add	ebx,cyChar
                shr	ebx,1
                sub	eax,ebx
                push	eax
                mov	eax,i
                shl	eax,2
                inc	eax
                mov	ecx,cxClient
                mul	ecx
                xor	edx,edx
                mov	ecx,28
                div	ecx
                push	eax
 		mov	ebx,i
		shl	ebx,2 
		mov	eax,hwndValue[ebx]
		push	eax
		call	MoveWindow

             	inc	i
             	cmp	i,3
                jNz	@b
                invoke  SetFocus,hwnd
        
                xor	eax,eax
	        ret
	.elseif uMsg ==  WM_SETFOCUS
		mov	ebx,idFocus
		shl	ebx,2
		invoke	SetFocus,hwndScroll[ebx]

                xor	eax,eax
	        ret	        
	.elseif uMsg == WM_VSCROLL
 		;i = GetWindowLong ((HWND) lParam, GWL_ID) ;
 		invoke	GetWindowLong,lParam,GWL_ID
 		mov	i,eax
 		
 		mov	ebx,i
 		shl	ebx,2
 		
 		mov	eax,wParam
 		and	eax,0FFFFh
 		.if	eax == SB_PAGEDOWN
                	mov	eax,color[ebx] ;color[i] += 15
                	add	eax,15
                	mov	color[ebx],eax
        
                        jmp	@f   
        	.elseif	eax == SB_LINEDOWN
                  @@:
                  	mov	eax,color[ebx] ;color[i] = min (255, color[i] + 1)
                  	inc	eax
                  	cmp	eax,255
                  	jl	@f
                  	mov	eax,255
                  @@:
        		mov	color[ebx],eax
        	.elseif	eax == SB_PAGEUP
                	mov	eax,color[ebx] ;color[i] -= 15
                	sub	eax,15
                	mov	color[ebx],eax
        
                        jmp	@f          
        	.elseif	eax == SB_LINEUP
                  @@:
                  	mov	eax,color[ebx] ;color[i] = max (0, color[i] - 1)
                  	dec	eax
                  	cmp	eax,0
                  	jg	@f
                  	mov	eax,0
                  @@:
        		mov	color[ebx],eax
        	.elseif	eax == SB_TOP
                	mov	color[ebx],0   ;color[i] = 0    
        	.elseif	eax == SB_BOTTOM
                	mov	color[ebx],255 ;color[i] = 255           
            
        	.elseif	(eax == SB_THUMBPOSITION)||(eax == SB_THUMBTRACK)
        		mov	eax,wParam     ;color[i] = HIWORD (wParam) ;
        		shr	eax,16
			mov	color[ebx],eax
		.endif
		
		mov	ebx,i
		shl	ebx,2
                invoke	SetScrollPos,hwndScroll[ebx], SB_CTL, color[ebx], TRUE
                invoke	wsprintf,addr szBuffer, CTEXT ("%i"), color[ebx]
                invoke	SetWindowText,hwndValue[ebx], addr szBuffer

		mov	eax,color[0]
		shl	eax,8
		mov	ebx,color[4]
		or	eax,ebx
		shl	eax,8
		mov	ebx,color[8]
		or	eax,ebx
		invoke	CreateSolidBrush,eax
		invoke	SetClassLong,hwnd,GCL_HBRBACKGROUND,eax
		invoke	DeleteObject,eax

		mov	eax,color[0]
		shl	eax,4
		mov	ebx,color[4]
		or	eax,ebx
		shl	eax,4
		mov	ebx,color[8]
		or	eax,ebx
		invoke  CreateSolidBrush,eax
        	invoke	InvalidateRect,hwnd, addr rcColor, TRUE
		
                xor	eax,eax
	        ret	        
	.elseif uMsg ==  WM_CTLCOLORSCROLLBAR
        	invoke	GetWindowLong,lParam,GWL_ID
        	mov	i,eax
        	mov	ebx,i
        	shl	ebx,2

                mov	eax,hBrush[ebx]
	        ret	        
	.elseif uMsg ==  WM_CTLCOLORSTATIC	        
        	invoke	GetWindowLong,lParam,GWL_ID
        	mov	i,eax		
		.if	(i>=3)&&(i<=8)
			mov	eax,i
			xor	edx,edx
			mov	ecx,3
			div	ecx
			mov	ebx,edx
			invoke	SetTextColor,wParam, crPrim[ebx]
			invoke	GetSysColor,COLOR_BTNHIGHLIGHT
	                invoke  SetBkColor,wParam, eax
	        
	                mov     eax,hBrushStatic 
	                ret
		.endif
	.elseif uMsg ==  WM_SYSCOLORCHANGE
		invoke	DeleteObject,hBrushStatic
		invoke	GetSysColor,COLOR_BTNHIGHLIGHT
		invoke	CreateSolidBrush,eax
		mov	hBrushStatic,eax
		
                xor	eax,eax
	        ret
		
	.elseif uMsg == WM_DESTROY
		invoke	GetStockObject,WHITE_BRUSH
		push	eax
		push	GCL_HBRBACKGROUND
		push	hwnd
		push	SetClassLong
		invoke	DeleteObject,eax
		invoke	DeleteObject,hBrush[0]
		invoke	DeleteObject,hBrush[4]		
		invoke	DeleteObject,hBrush[8]		
                invoke  DeleteObject,hBrushStatic
	        invoke 	PostQuitMessage,NULL
	        xor	eax,eax
	        ret
	.endif
	invoke DefWindowProc,hwnd,uMsg,wParam,lParam
	ret
WndProc endp

ScrollProc proc	hwnd:HWND,  message:UINT,wParam:WPARAM , lParam:LPARAM 
  	LOCAL id:DWORD
  	
  	invoke	GetWindowLong , hwnd, GWL_ID
  	mov	id,eax

	mov	eax,message
	.if	eax == WM_KEYDOWN 
        
                .if (wParam == VK_TAB)
	        	invoke	GetKeyState,VK_SHIFT
	        	cmp	eax,0
	        	jg	@f
	        	mov	eax,2
	        	jmp	Fini
	            @@:
	            	mov	eax,1
	            Fini:
	            	add	eax,id
	            	xor	edx,edx
	            	mov	ecx,3
	            	div	ecx
	            	push	eax
	            	invoke	GetParent,hwnd
	            	push	eax
	            	call	GetDlgItem
	            	
	            	invoke	SetFocus,eax
            	.endif
       .elseif	eax == WM_SETFOCUS
        	mov	eax,id
        	mov	idFocus,eax
	.endif        
	mov	ebx,id
	shl	ebx,2
        invoke	 CallWindowProc ,OldScroll[ebx], hwnd, message, wParam,lParam
        ret
ScrollProc endp

END START

COLORS1利用子窗口进行工作,该程序使用10个子窗口控件:3个滚动条、6个静态文字窗口和1个静态矩形框。COLORS1拦截WM_CTLCOLORSCROLLBAR消息来给红、绿、蓝3个滚动条的内部着色,并拦截WM_CTLCOLORSTATIC消息来着色静态文字。

您可以使用鼠标或者键盘来挪动滚动条,从而利用COLORS1作为一种实验颜色显示的开发工具,为您自己的Windows程序选择漂亮的颜色(或者,您可能更喜欢难看的颜色)。COLORS1的显示如图9-3所示。不幸的是,这些颜色在印表纸上被显示为不同深浅的灰色。


 

wpe2.jpg (30281 字节)

图9-3 COLORS1的屏幕显示

COLORS1不处理WM_PAINT消息,所有的工作几乎都是由子窗口完成的。

显示区域右半部显示的颜色实际上是窗口的背景颜色。SS_WHITERECT样式的静态子窗口显示在显示区域的左半部。三个滚动条是SBS_VERT样式的子窗口控件,它们被定位在SS_WHITERECT子窗口的顶部。另外六个SS_CENTER样式(居中文字)的静态子窗口提供卷标和颜色值。COLORS1在WinMain函数中用CreateWindow建立它的普通重迭式窗口和10个子窗口。SS_WHITERECT和SS_CENTER静态窗口使用窗口类别「static」;三个滚动条使用窗口类别「scrollbar」。

CreateWindow呼叫中的x位置、y位置、宽度和高度参数最初设为0,因为位置和大小都取决于显示区域的尺寸,而它目前尚未确定。COLORS1的窗口消息处理程序在接收到WM_SIZE消息时,就使用MoveWindow给10个子窗口重新确定大小。所以,每当您对COLORS1窗口进行缩放时,滚动条的尺寸就会按比例变化。

当WndProc窗口消息处理程序收到WM_VSCROLL消息时,lParam参数的高字组就是子窗口的句柄。我们可以使用GetWindowWord来得到子窗口的ID:

i = GetWindowLong ((HWND) lParam, GWL_ID) ;
        

对于这三个滚动条,我们已经按习惯将其ID设为0、1、2,所以WndProc能区别出是哪个滚动条在产生消息。

由于子窗口的句柄在建立时就被储存在数组中,所以WndProc就能对相对应的滚动条消息进行处理,并通过呼叫SetScrollPos来设定相对应的新值:

SetScrollPos (hwndScroll[i], SB_CTL, color[i], TRUE) ;
        

WndProc也改变滚动条底部子窗口的文字:

wsprintf (szBuffer, TEXT ("%i"), color[I]) ;
        
SetWindowText (hwndValue[i], szBuffer) ;
        



<<<上一篇
欢迎访问AoGo汇编小站:http://www.aogosoft.com
下一篇>>>