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

自动键盘接口

滚动条控件也能处理键盘输入,但是只有在拥有输入焦点时才行。下表说明怎样将键盘光标键转变为卷动消息:

表9-5

游标键

卷动消息的wParam值

Home SB_TOP
End SB_BOTTOM
Page Up SB_PAGEUP
Page Down SB_PAGEDOWN
左或上 SB_LINEUP
右或下 SB_LINEDOWN

事实上,SB_TOP和SB_BOTTOM卷动消息只能用键盘产生。在使用鼠标按动卷动列时,如果想使该卷动列获得输入焦点,那么您必须将WS_TABSTOP标识符包含到CreateWindow呼叫的窗口类别参数中。当滚动条拥有输入焦点时,在该滚动条的小方框上将显示一个闪烁的灰色块。

为了给滚动条提供全面的键盘接口,还需要另外一些工作。首先,WndProc窗口消息处理程序必须使滚动条拥有输入焦点,它是通过处理WM_SETFOCUS消息来完成这一点的,该WM_SETFOCUS消息是当滚动条获得输入焦点时其父窗口接收到的。WndProc给其中一个滚动条设定输入焦点。

SetFocus (hwndScroll[idFocus]) ;
        

其中idFocus是一个整体变量。

但是,还需要一些借助键盘尤其是Tab键,来从一个滚动条转换到另一个滚动条的方法。这比较困难,因为一旦某个滚动条拥有了输入焦点,它就处理所有的键盘输入,但滚动条只关心光标键,而忽略Tab键。解决这一两难处境的方法是「窗口子类别化」。我们将用它来给COLORS1增加使用Tab键从一个滚动条跳到另一个滚动条的功能。

窗口子类别化(Window Subclassing)

滚动条控件的窗口消息处理程序是Windows内部的。但是,将GWL_WNDPROC标识符作为参数来呼叫GetWindowLong,您就可以得到这个窗口消息处理程序的地址。另外,您可以呼叫SetWindowLong给该滚动条设定一个新的窗口消息处理程序,这个技术叫做「窗口子类别化」,非常有用。它能让您给现存的窗口消息处理程序设定「挂勾」,以便在自己的程序中处理一些消息,同时将其它所有消息传递给旧的窗口消息处理程序。

在COLORS1中对卷动消息进行初步处理的窗口消息处理程序叫做ScrollProc,它在COLORS1.C文件的尾部。由于ScrollProc是COLORS1中的函数,而Windows将呼叫COLORS1,所以ScrollProc必须被定义为callback函数。

对三个滚动条中的每一个,COLORS1使用SetWindowLong来设定新的滚动条窗口消息处理程序的地址,并取得现存滚动条窗口消息处理程序的地址:

OldScroll[i] = (WNDPROC) SetWindowLong (hwndScroll[i], GWL_WNDPROC,
        
         (LONG) ScrollProc)) ;
        

现在,函数ScrollProc得到了Windows发送到COLORS1中三个滚动条(当然不是其它程序中的滚动条)的滚动条窗口消息处理程序的全部消息。ScrollProc窗口消息处理程序在接收到Tab或者Shift-Tab键时,就将输入焦点改变到下一个(或者上一个)滚动条。它使用CallWindowProc呼叫旧的滚动条窗口消息处理程序。

给背景着色

当COLORS1定义它的窗口类别时,也为其显示区域背景定义了一个实心的黑色画刷:

wndclass.hbrBackground = CreateSolidBrush (0) ;
        

当您改变COLORS1的滚动条设定时,程序必须建立一个新的画刷,并将该新画刷句柄放入窗口类别结构中。如同使用GetWindowLong和SetWindowLong能得到并设定滚动条窗口消息处理程序一样,用GetClassWord和SetClassWord能得到这个画刷的句柄。

您可以建立新的画刷并将其句柄插入窗口类别结构中,然后删除旧的画刷:

DeleteObject ((HBRUSH)
        
           SetClassLong (hwnd, GCL_HBRBACKGROUND, (LONG)
        
                  CreateSolidBrush (RGB (color[0], color[1], color[2])))) ;
        

Windows下一次重新为窗口的背景着色时,将使用这个新画刷。为了强迫Windows抹掉背景,我们将使整个显示区域无效:

InvalidateRect (hwnd, &rcColor, TRUE) ;
        

TRUE(非零)值作为第三个参数,表示希望在重新着色之前删去背景。

InvalidateRect使Windows在窗口消息处理程序的消息队列中放进一个WM_PAINT消息。由于WM_PAINT消息的优先等级比较低,所以,如果您还在使用鼠标或者光标键移动滚动条的话,这个消息将不会立即被处理。如果您想在颜色改变之后使该窗口立即变成最新的(目前的),那么您可以在InvalidateRect之后增加下面的叙述:

UpdateWindow (hwnd) ;
        

但这会使得键盘和鼠标处理变慢。

COLORS1中的WndProc函数不处理WM_PAINT消息,而是将其传给DefWindowProc。Windows对WM_PAINT消息的内定处理只是呼叫BeginPaint和EndPaint使窗口生效。因为在InvalidateRect呼叫中已经指定背景要被抹掉,所以BeginPaint呼叫使Windows发出一个WM_ERASEBKGND(删除背景)消息,WndProc也将忽略这个消息。Windows用窗口类别中指定的画刷将显示区域的背景抹去,这样就处理了这个消息。

在终止以前进行清除总是一个好主意,因此在处理WM_DESTROY消息处理期间,再一次呼叫DeleteObject:

DeleteObject ((HBRUSH)
        
           SetClassLong (hwnd, GCL_HBRBACKGROUND,
        
                  (LONG) GetStockObject (WHITE_BRUSH))) ;
        

给滚动条和静态文字着色

在COLORS1中,三个滚动条的内部和六个文字字段中的文字着色为红、绿和蓝色。滚动条的着色是通过处理WM_CTLCOLORSCROLLBAR消息来完成的。

在WndProc中,我们为画刷定义了一个由三个句柄组成的静态数组:

static HBRUSH hBrush [3] ;
        

在处理WM_CREATE期间,我们建立三个画刷:

for (I = 0 ; I < 3 ; I++)
        
    hBrush[0] = CreateSolidBrush (crPrim [I]) ;
        

其中crPrim数组中包含三种原色的RGB值。在WM_CTLCOLORSCROLLBAR处理期间窗口消息处理程序传回这三画刷中的一个:

case        WM_CTLCOLORSCROLLBAR:
        
           i = GetWindowLong ((HWND) lParam, GWL_ID) ;
        
           return (LRESULT) hBrush [i] ;
        

在处理WM_DESTROY消息的过程中,这些画刷必须被删除:

for (i = 0 ; i < 3 ; i++)
        
           DeleteObject (hBrush [i])) ;
        

同样地,静态文字字段中的文字是在处理WM_CTLCOLORSTATIC消息中呼叫SetTextColor来着色的。文字背景用SetBkColor函数设定为系统颜色COLOR_BTNHIGHLIGHT,这导致文字背景颜色和滚动条与文字后面的静态矩形控件的颜色一样。对于静态文字控件,这种文字背景颜色只用于字符串中每个字符后面的矩形,而不会用于整个控件窗口。为了实作这一点,窗口消息处理程序还必须传回COLOR_BTNHIGHLIGHT颜色画刷的句柄。这个画刷被称为hBrushStatic,它在WM_CREATE消息处理期间建立,在WM_DESTROY消息处理期间清除。

在WM_CREATE消息处理期间依据COLOR_BTNHIGHLIGHT颜色建立画刷,并且在执行期间使用这一画刷时,我们遇到了一个小问题。如果程序在执行期间改变了COLOR_BTNHIGHLIGHT颜色,那么静态矩形的颜色将发生变化,并且文字背景的颜色也会变化,但是文字窗口控件的整个背景将保持原有的COLOR_BTNHIGHLIGHT颜色。

为了解决这个问题,COLORS1也简单地通过使用新颜色重新建立hBrushStatic来处理WM_SYSCOLORCHANGE消息。

编辑类别

在某些方面,编辑类别是最简单的预先定义窗口类别;在另一方面,它又是最复杂的窗口类别。当您使用类别名称「edit」建立子窗口时,您根据CreateWindow呼叫中的x位置、y位置、宽度和高度这些参数定义了一个矩形。此矩形含有可编辑文字。当子窗口控件拥有输入焦点时,您可以输入文字,移动光标,使用鼠标或者Shift键与一个光标键来选取部分文字,按Ctrl-X来删除所选文字或按Ctrl-C来复制所选文字、并送到剪贴簿上,按Ctrl-V键插入剪贴簿上的文字。

编辑控件的最简单的应用之一是作为单行输入区域。但是编辑控件并不仅限于单行,这一点我将在程序9-4 POPPAD1中说明。和我们在这本书中所遇到的各种其它问题一样,POPPAD程序将逐步增强以使用菜单、对话框(加载与储存文件)和打印。最后的版本将是一个简单而完整的文字编辑器,且其程序代码将非常简洁。

程序9-4 POPPAD1

        
POPPAD1.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
	ID_EDIT		equ	1

.DATA
	szAppName   db "PopPad1",0
.DATA?
        hwndEdit    HWND	?
.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
					ADDR szAppName, 
					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


	.if uMsg==WM_CREATE
		mov	esi,lParam
		mov	ebx,[esi+4]
		invoke	CreateWindowEx,NULL,CTXT("edit"),NULL,
	               		WS_CHILD or WS_VISIBLE or WS_HSCROLL or WS_VSCROLL or \
	               		WS_BORDER or ES_LEFT or ES_MULTILINE or ES_AUTOHSCROLL \
	               		or ES_AUTOVSCROLL,
	                        0, 0, 0, 0, hwnd, ID_EDIT,
               			ebx, NULL
                mov	hwndEdit,eax
                
	        xor	eax,eax
	        ret
	.elseif uMsg ==  WM_SETFOCUS
		invoke	SetFocus,hwndEdit
                xor	eax,eax
	        ret	        
	.elseif uMsg ==  WM_SIZE
		push	TRUE
		mov	eax,lParam
		shr	eax,16
		push	eax
		mov	eax,lParam
		and	eax,0FFFFh
		push	eax
		push	0
		push	0
		push	hwndEdit
		call	MoveWindow
		
                xor	eax,eax
	        ret

	.elseif uMsg == WM_COMMAND
		mov	eax,wParam
		and	eax,0FFFFh
		.if	eax == ID_EDIT
			mov	eax,wParam
			shr	eax,16
			.if	(eax == EN_ERRSPACE) || (eax == EN_MAXTEXT)
				invoke	MessageBox,hwnd,CTXT("Edit control out of space"),
				 szAppName, MB_OK or MB_ICONSTOP
			.endif
		.endif
	        xor	eax,eax
	        ret
	.elseif uMsg == WM_DESTROY

	        invoke 	PostQuitMessage,NULL
	        xor	eax,eax
	        ret
	.endif
	invoke DefWindowProc,hwnd,uMsg,wParam,lParam

	ret
WndProc endp
END START

wpe3.jpg (23141 字节)

POPPAD1是一个多行编辑器(只是没有文件I/O),其C语言原始码不到100行(不过,有一个缺陷,即预先定义的多行编辑控件只限于30,000字符的文字)。您可以看到,POPPAD1本身并没有做多少工作,预先定义的编辑控件完成了许多工作,这样,您可以知道,无需额外的程序时编辑控件能做些什么。

编辑类别样式

如前面所提到的,在CreateWindow呼叫中将「edit」作为窗口类别建立了一个编辑控件,窗口样式是WS_CHILD加上几个选项。如同在静态子窗口控件中一样,编辑控件中的文字可以置左对齐、置右对齐或者居中,您使用窗口样式ES_LEFT、ES_RIGHT和ES_CENTER来指定这些格式。

内定状态下,编辑控件是单行的。您使用ES_MULTILINE窗口样式可以建立多行编辑控件。对于单行编辑控件,您一般只可以在编辑控件矩形的尾部输入文字。要建立一个自动水平卷动的编辑控件,您可以采用样式ES_AUTOHSCROLL。对一个多行编辑控件,文字会自动跳行,除非使用ES_AUTOHSCROLL样式。在这种情况下,您必须按Enter键来开始新的一行。您还可以便用样式ES_AUTOVSCROLL来将垂直滚动条包括在多行编辑控件中。

当您在多行编辑控件中包括这些卷动样式时,也许还想给编辑控件增加卷动列。要做到这些,可以对非子窗口使用同一窗口样式标识符WS_HSCROLL和WS_VSCROLL。内定状态下,编辑控件没有边界,利用样式WS_BORDER则可以增加边界。

当您在编辑控件中选择文字时,Windows将选择的文字反白显示。但是当编辑控件失去输入焦点时,被选择的文字将不再被加亮。如果希望在编辑控件没有输入焦点时被选择的文字仍然被加亮,您可以使用样式ES_NOHIDESEL。

在POPPAD1建立其编辑控件时,CreateWindow呼叫依如下形式给出样式:

WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
        
           WS_BORDER | ES_LEFT | ES_MULTILINE |
        
   ES_AUTOHSCROLL | ES_AUTOVSCROLL
        

在POPPAD1中,编辑控件的大小是后来当WndProc接收到WM_SIZE消息时通过呼叫MoveWindow来定义的。编辑控件的尺寸被简单地设定为主窗口的尺寸:

MoveWindow (hwndEdit, 0, 0, LOWORD (lParam),
        
                                 HIWORD (lParam), TRUE) ;
        

对于单行编辑控件,控件的高度必须可以容纳一个字符。如果编辑控件有边界(大多数都有),那么使用一个字符高度的1.5倍(包括外部间距)。

编辑控件通知

编辑控件给父窗口消息处理程序发送WM_COMMAND消息,对按钮控件来说,wParam和lParam变量的含义是相同的:

LOWORD (wParam)

HIWORD (wParam)

lParam

子窗口ID

通知码

子窗口句柄

通知码如下所示:

EN_SETFOCUS

EN_KILLFOCUS

EN_CHANGE

EN_UPDATE

EN_ERRSPACE

EN_MAXTEXT

EN_HSCROLL

EN_VSCROLL

编辑控件已经获得输入焦点

编辑控件已经失去输入焦点

编辑控件的内容将改变

编辑控件的内容已经改变

编辑控件执行已经超出中间

编辑控件在插入时执行超出空间

编辑控件的水平滚动条已经被按下

编辑控件的垂直滚动条已经被按下

POPPAD1只拦截EN_ERRSPACE和EN_MAXTEXT通知码,并显示一个消息框。

使用编辑控件

如果在您的主窗口上使用了几个单行编辑控件,那么您需要将窗口子类别化以便把输入焦点从一个控件转移到另一个控件。您可以通过拦截Tab键和Shift-Tab键来完成这种移动,非常像COLORS1中所做的(窗口子类别化的另一个例子在后面的HEAD程序中说明)。如何处理Enter键取决于您,可以像Tab键那样使用,也可以当成给程序的信号,表示所有的编辑字段都准备好了。

如果您想在编辑区中插入文字,那么可以使用SetWindowText来做到。将文字从编辑控件中取出涉及了GetWindowTextLength和GetWindowText,我们将在POPPAD程序的修订版本中看到这些操作的实例。

发送给编辑控件的消息

因为用SendMessage发送给编辑控件的消息很多,并且其中的几个还将在后面POPPAD修订版本中用到,所以这里不解说所有用SendMessage发送给编辑控件的消息,只概要地说明一下。

这些消息允许您剪下、复制或者清除目前被选择的文字。使用者使用鼠标或者Shift键加上光标控件键来选择文字并进行上面的操作,这样,在编辑控件中选中的文字将被加亮:

SendMessage (hwndEdit, WM_CUT, 0, 0) ;
        
SendMessage (hwndEdit, WM_COPY, 0, 0) ;
        
SendMessage (hwndEdit, WM_CLEAR, 0, 0) ;
        

WM_CUT将目前选择的文字从编辑控件中移走,并将其发送到剪贴簿中;WM_COPY将选择的文字复制到剪贴簿上并保持编辑控件中的内容完好无损;WM_CLEAR将选择的内容从编辑控件中删除,但是不向剪贴簿中发送。

您也可以将剪贴簿上的文字插入到编辑控件中的光标位置:

SendMessage (hwndEdit, WM_PASTE, 0, 0) ;
        

您可以取得目前选择的起始位置和末尾位置:

SendMessage (hwndEdit, EM_GETSEL, (WPARAM) &iStart,
        
                                 (LPARAM) &iEnd) ;
        

结束位置实际上是最后一个选择字符的位置加1。

您可以选择文字:

SendMessage (hwndEdit, EM_SETSEL, iStart, iEnd) ;
        

您还可以使用别的文字来置换目前的选择内容:

SendMessage (hwndEdit, EM_REPLACESEL, 0, (LPARAM) szString) ;
        

对多行编辑控件,您可以取得行数:

iCount = SendMessage (hwndEdit, EM_GETLINECOUNT, 0, 0) ;
        

对任何特定的行,您可以取得距离编辑缓冲区文字开头的偏移量:

iOffset = SendMessage (hwndEdit, EM_LINEINDEX, iLine, 0) ;
        

行数从0开始计算,iLine值为-1时传回包含游标所在行的偏移量。您可以取得行的长度:

iLength = SendMessage (hwndEdit, EM_LINELENGTH, iLine, 0) ;
        

并将行本身复制到一个缓冲区中:

iLength = SendMessage (hwndEdit, EM_GETLINE, iLine, (LPARAM) szBuffer) ;
        

清单方块类别

我在本章讨论的最后一个预先定义子窗口控件是清单方块。一个清单方块是字符串的集合,这些字符串是一个矩形中可以卷动显示的清单。-程序通过向清单方块窗口消息处理程序发送消息,可以在清单中增加或者删除字符串。当清单方块中的某项被选择时,清单方块控件就向其父窗口发送WM_COMMAND消息,父窗口也就可以确定选择的是哪一项。

一个清单方块可以是单选的,也可以是多选的,后者允许使用者从清单方块中选择多个项目。当清单方块拥有输入焦点时,其中项目的周围显示有虚线。在清单方块中,光标位置并不指明被选择的项目。被选择的项目被加亮显示,并且是反白显示的。

在单项选择的清单方块中,使用者按Spacebar键就可以选择光标所在位置的项目。方向键移动光标和目前选择指示,并且能够滚动清单方块的内容。Page Up和Page Down键也能滚动清单方块,但它移动的是光标而不是选择指示。按字母键能将光标和选择指示移到以此字母开头的第一个(或下一个)选项。也可以使用鼠标在要选择的项目上单击或者双击来选择它。

在多项选择清单方块中,Spacebar键可以切换光标所在位置的项目的选择状态(如果该项已经被选择,则取消选择)。如同在单项选择清单方块中一样,方向键取消前面选择过的项目,并且移动光标和选择指示。但是,Ctrl键和方向键能够在移动光标的同时不移动选择,Shift键加方向键能扩展一个选择。

在多项选择清单方块中,单击或者双击鼠标按键能取消之前所有的选择,而选择被点中的项目。但是,如果在鼠标点中某一项的同时也按下Shift键,则只能切换该项的选择状态,而不会改变任何其它项的选择状态。

清单方块样式

当您使用CreateWindow建立清单方块子窗口时,您应该将「listbox」作为窗口类别,将WS_CHILD作为窗口样式。但是,这个内定清单方块样式不向其父窗口发送WM_COMMAND消息,这样一来,程序必须向清单方块询问其中的项目的选择状态(借助于发送给清单方块控件的消息)。所以,清单方块控件通常都包括清单方块样式标识符LBS_NOTIFY,它允许父窗口接收来自清单方块的WM_COMMAND消息。如果您希望清单方块控件对清单方块中的项目进行排序,那么您可以使用另一种常用的样式LBS_SORT。

内定情况下,清单方块是单项选择的。多项选择的清单方块相当少。如果您想建立一个多项选择清单方块,那么您可以使用样式LBS_MULTIPLESEL。通常,当给有滚动条的清单方块增加新项目时,清单方块本身会自己重画。您可以通过将样式LBS_NOREDRAW包含进去来防止这种现象。但是您也许不想使用这种样式,这时可以使用WM_SETREDRAW消息来暂时防止清单方块控件重新画过,我将在稍后讨论WM_SETREDRAW消息。

内定状态下,清单方块窗口消息处理程序只显示列表项目,它的周围没有任何边界。您可以使用窗口样式标识符WS_BORDER来加上边界。另外,您可以使用窗口样式标识符WS_VSCROLL来增加垂直滚动条,以便用鼠标来滚动条表项目。

Windows表头文件定义了一个清单方块样式,叫做LBS_STANDARD,它包含了最常用的样式,其定义如下:

(LBS_NOTIFY | LBS_SORT | WS_VSCROLL | WS_BORDER)
        

您也可以采用WS_SIZEBOX和WS_CAPTION标识符,但是这两个标识符允许您重新定义清单方块的大小,也允许您在清单方块父窗口的显示区域中移动清单方块。

清单方块的宽度应该能够容纳最长字符串的宽度加上滚动条的宽度。您可以使用:

GetSystemMetrics (SM_CXVSCROLL) ;
        

来获得垂直滚动条的宽度。您用一个字符的高度乘以想要在视端口中显示的项目数来计算出清单方块的高度。

将字符串放入清单方块

建立清单方块之后,下一步是将字符串放入其中,您可以通过呼叫SendMessage为清单方块窗口消息处理程序发送消息来做到这一点。字符串通常通过以0开始计数的索引数来引用,其中0对应于最顶上的项目。在下面的例子中,hwndList是子窗口清单方块控件的句柄,而iIndex是索引值。在使用SendMessage传递字符串的情况下,lParam参数是指向以null字符结尾字符串的指针。

在大多数例子中,当窗口消息处理程序储存的清单方块内容超过了可用内存空间时,SendMessage将传回LB_ERRSPACE(定义为-2)。如果是因为其它原因而出错,那么SendMessage将传回LB_ERR(-1)。如果操作成功,那么SendMessage将传回LB_OKAY(0)。您可以通过测试SendMessage的非零值来判断这两种错误。

如果您采用LBS_SORT样式(或者如果您在清单方块中按照想要呈现的顺序排列字符串),那么填入清单方块最简单的方法是借助LB_ADDSTRING消息:

SendMessage (hwndList, LB_ADDSTRING, 0, (LPARAM) szString) ;
        

如果您没有采用LBS_SORT,那么可以使用LB_INSERTSTRING指定一个索引值,将字符串插入到清单方块中:

SendMessage (hwndList, LB_INSERTSTRING, iIndex, (LPARAM) szString) ;
        

例如,如果iIndex等于4,那么szString将变为索引值为4的字符串-从顶头开始算起的第5个字符串(因为是从0开始计数的),位于这个点后面的所有字符串都将向后推移。索引值为-1时,将字符串增加在最后。您可以对样式为LBS_SORT的清单方块使用LB_INSERTSTRING,但是这个清单方块的内容不能被重新排序(您也可以使用LB_DIR消息将字符串插入到清单方块中,这将在本章的最后进行讨论)。

您可以在指定索引值的同时使用LB_DELETESTRING参数,这就可以从清单方块中删除字符串:

SendMessage (hwndList, LB_DELETESTRING, iIndex, 0) ;
        

您可以使用LB_RESETCONTENT清除清单方块中的内容:

SendMessage (hwndList, LB_RESETCONTENT, 0, 0) ;
        

当在清单方块中增加或者删除字符串时,清单方块窗口消息处理程序将更新显示。如果您有许多字符串需要增加或者删除,那么您也许希望暂时阻止这一动作,其方法是关掉控件的重画旗标:

SendMessage (hwndList, WM_SETREDRAW, FALSE, 0) ;
        

当您完成后,可以再打开重画旗标:

SendMessage (hwndList, WM_SETREDRAW, TRUE, 0) ;
        

使用LBS_NOREDRAW样式建立的清单方块开始时其重画旗标是关闭的。

选择和取得项

SendMessage完成了下面所描述的任务之后,通常传回一个值。如果出错,那么这个值将被设定为LB_ERR(定义为-1)。

当清单方块中放入一些项目之后,您可以弄清楚清单方块中有多少项目:

iCount = SendMessage (hwndList, LB_GETCOUNT, 0, 0) ;
        

其它一些呼叫对单项选择清单方块和多项选择清单方块是不同的。让我们先来看看单项选择清单方块。

通常,您让使用者在清单方块中选择条目。但是如果您想加亮显示一个内定选择,则可以使用:

SendMessage (hwndList, LB_SETCURSEL, iIndex, 0) ;
        

将iParam设定为-1则取消所有选择。

您也可以根据项目的第一个字母来选择:

iIndex = SendMessage (hwndList, LB_SELECTSTRING, iIndex,
        
                     (LPARAM) szSearchString) ;
        

在SendMessage呼叫中将iIndex作为iParam参数时,iIndex是索引,可以根据它搜索其开头字符与szSearchString相匹配的项目。iIndex的值等于-1时从头开始搜索,SendMessage传回被选中项目的索引。如果没有开头字符与szSearchString相匹配的项目时,SendMessage传回LB_ERR。

当您得到来自清单方块的WM_COMMAND消息时(或者在任何其它时候),您可以使用LB_GETCURSEL来确定目前选项的索引:

iIndex = SendMessage (hwndList, LB_GETCURSEL, 0, 0) ;
        

如果没有项目被选中,那么从呼叫中传回的iIndex值为LB_ERR。

您可以确定清单方块中字符串的长度:

iLength = SendMessage (hwndList, LB_GETTEXTLEN, iIndex, 0) ;
        

并可以将某项目复制到文字缓冲区中:

iLength = SendMessage (    hwndList, LB_GETTEXT, iIndex,
        
                                                                (LPARAM) szBuffer) ;
        

在这两种情况下,从呼叫传回的iLength值是字符串的长度。对以NULL字符终结的字符串长度来说,szBuffer数组必须够大。您也许想用LB_GETTEXTLEN先分配一些局部内存来存放字符串。

对于一个多项选择清单方块,您不能使用LB_SETCURSEL、LB_GETCURSEL或者LB_SELECTSTRING,但是您可以使用LB_SETSEL来设定某特定项目的选择状态,而不影响有可能被选择的其它项:

SendMessage (hwndList, LB_SETSEL, wParam, iIndex) ;
        

wParam参数不为0时,选择并加亮某一项目;wParam为0时,取消选择。如果wParam等于-1,那么将选择所有项目或者取消所有被选中的项目。您可以如下确定某特定项目的选择状态:

iSelect = SendMessage (hwndList, LB_GETSEL, iIndex, 0) ;
        

其中,如果由iIndex指定的项目被选中,iSelect被设为非0,否则被设为0。

接收来自清单方块的消息

当使用者用鼠标单击清单方块时,清单方块将接收输入焦点。下面的操作可以使父窗口将输入焦点转交给清单方块控件:

SetFocus (hwndList) ;
        

当清单方块拥有输入焦点时,光标移动键、字母键和Spacebar键都可以用来在该清单方块中选择某项。

清单方块控件向其父窗口发送WM_COMMAND消息,对按钮和编辑控件来说,wParam和lParam变量的含义是相同的:

]

LOWORD (wParam) 子窗口ID
HIWORD (wParam)

通知码

lParam 子窗口句柄

通知码及其值如下所示:

LBN_ERRSPACE -2
LBN_SELCHANGE 1
LBN_DBLCLK 2
LBN_SELCANCEL 3
LBN_SETFOCUS 4
LBN_KILLFOCUS 5

只有清单方块窗口样式包括LBS_NOTIFY时,清单方块控件才会向父窗口发送LBN_SELCHANGE和LBN_DBLCLK。

LBN_ERRSPACE表示清单方块已经超出执行空间。LBN_SELCHANGE表示目前选择已经被改变。这些消息出现在下列情况下:使用者在清单方块中移动加亮的项目时,使用者使用Spacebar键切换选择状态或者使用鼠标单击某项时。LBN_DBLCLK说明某项目已经被鼠标双击(LBN_SELCHANGE和LBN_DBLCLK通知码的值表示鼠标按下的次数)。

根据应用的需要,您也许要使用LBN_SELCHANGE或LBN_DBLCLK,也许二者都要使用。您的程序会收到许多LBN_SELCHANGE消息,但是LBN_DBLCLK消息只有当使用者双击鼠标时才会出现。如果您的程序使用双击,那么您需要提供一个复制LBN_DBLCLK的键盘接口。

一个简单的清单方块应用程序

既然您知道了如何建立清单方块,如何使用文字项目填入清单方块,如何接收来自清单方块的控件以及如何取得字符串,现在是到了写一个应用程序的时候了。如程序9-5中所示,ENVIRON程序在显示区域中使用清单方块来显示目前操作系统环境变量(例如PATH和WINDIR)。当您选择一个环境变量时,其内容将显示在显示区域的顶部。

程序9-5 ENVIRON

        
ENVIRON.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
	ID_LIST 	equ	1
	ID_TEXT 	equ	2

.DATA
	szAppName   db "Environ",0
	szListBox   db "listbox",0
	szStatic    db "static",0
	szVarName   db 1000 dup(0)
	szVarValue  db 1000 dup(0)	
.DATA?
        hwndEdit    HWND	?
        hwndList    HWND	?
        hwndText    HWND	?
	szBuffer	db 100 dup(?)
.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("Environment List Box"), 
					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

CopyMemory   proc Dest:DWORD,   Source:DWORD,   mlength:DWORD  
	  cld 			;Work upwards  
	  mov	esi,   Source 	;Source address  
	  mov	edi,   Dest 	;Destination address  
	  mov 	ecx,   mlength 	;Get   size   in   bytes  
	  rep 	movsb 		;   repeat   copy   util   all   done  
  	  ret  
CopyMemory   endp   
  
FillListBox 	proc	hwndL:HWND 
	LOCAL	iLength:DWORD;
	LOCAL	pVarBlock,pVarBeg,pVarEnd,pszBtnStyle:DWORD
	
        invoke	GetEnvironmentStrings	; Get pointer to environment block
        mov	pVarBlock,eax
        
        mov	esi,pVarBlock
        mov	ax,[esi]
	.while	ax!=0
		mov	esi,pVarBlock
		mov	al,[esi]
		.if	(al!=3Dh)	; Skip variable names beginning with '='
			mov	eax,pVarBlock
			mov	pVarBeg,eax	; Beginning of variable name
			mov	esi,pVarBlock
			mov	al,[esi]
			.while	al!='='
				inc	esi
				mov	al,[esi]
			.endw	
			mov	pVarBlock,esi
			mov	pVarEnd,esi	;pVarEnd = pVarBlock -1
			
			mov	eax,pVarEnd
			sub	eax,pVarBeg
			mov	iLength,eax
		
	            	; Allocate memory for the variable name and terminating
            		; zero. Copy the variable name and append a zero.
			inc	eax        
      		
      			;我暂时无法搞清楚如何正确的给程序非配内存
      			;因此这里只好使用固定大小来代替
      			;pVarName = calloc (iLength + 1, sizeof (TCHAR)) ;

        		invoke	CopyMemory,addr szVarName, pVarBeg, iLength
        		
        		mov	ebx,iLength
        		mov	BYTE ptr szVarName[ebx],0
       
		        ; Put the variable name in the list box and free memory.
            		invoke	SendMessage ,hwndL, LB_ADDSTRING, 0, addr szVarName
        	.endif
       	
        	mov	esi,pVarBlock
        	mov	al,[esi]
		.while	(al!=0) 	;Scan until terminating zero
			inc	esi
			mov	al,[esi]
		.endw
		inc	esi
		mov	pVarBlock,esi
		mov	ax,[esi]
        .endw	
@@:        
        invoke	FreeEnvironmentStrings,pVarBlock ; Scan until terminating zero
	ret
FillListBox	endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
       LOCAL	iIndex, iLength, cxChar, cyChar:DWORD

	.if uMsg==WM_CREATE
                ; cxChar = LOWORD (GetDialogBaseUnits ()) ;
        	invoke	GetDialogBaseUnits
        	and	eax,0FFFFh
        	mov	cxChar,eax
        	
                ; cyChar = HIWORD (GetDialogBaseUnits ()) ;
        	invoke	GetDialogBaseUnits
        	shr	eax,16
        	mov	cyChar,eax
        	;Create listbox and static text windows.
		push	NULL
		invoke	GetWindowLong,hwnd,GWL_HINSTANCE
		push	eax
		push	ID_LIST
		push	hwnd
		mov	eax,cyChar
		shl	eax,4
		add	eax,cyChar
		push	eax
		invoke	GetSystemMetrics,SM_CXVSCROLL
		mov	ebx,cxChar
		shl	ebx,5
		add	eax,ebx
		push	eax
		mov	eax,cyChar
		shl	eax,2
		sub	eax,cyChar
		push	eax
		push	cxChar
		mov	eax,WS_CHILD or WS_VISIBLE or LBS_STANDARD
		push	eax
		push	NULL
		push	offset	szListBox
		push	NULL
		call	CreateWindowEx
		mov	hwndList,eax

		push	NULL
		invoke	GetWindowLong,hwnd,GWL_HINSTANCE
		push	eax
		push	ID_TEXT
		push	hwnd
		push	cyChar
		invoke	GetSystemMetrics,SM_CXSCREEN
		push	eax
		push	cyChar
		push	cxChar
		mov	eax,WS_CHILD or WS_VISIBLE or SS_LEFT
		push	eax
		push	NULL
		push	offset	szStatic
		push	NULL
		call	CreateWindowEx
		mov	hwndText,eax
		
		invoke	FillListBox,hwndList
	        xor	eax,eax
	        ret
	.elseif uMsg ==  WM_SETFOCUS
		invoke	SetFocus,hwndList
                xor	eax,eax
	        ret	        
	.elseif uMsg == WM_COMMAND
		
		mov	eax,wParam
		mov	ebx,(LBN_SELCHANGE SHL 16) OR (ID_LIST)
		.if	eax == ebx
		; Get current selection.
			invoke	SendMessage,hwndList,LB_GETCURSEL,0,0
			mov	iIndex,eax
			invoke	SendMessage,hwndList,LB_GETTEXT,iIndex,addr szVarName
			invoke	GetEnvironmentVariableA,addr szVarName, addr szVarValue, 1024
		;Show it in window
			invoke	SetWindowText,hwndText, addr szVarValue
		.endif
	        xor	eax,eax
	        ret
	.elseif uMsg == WM_DESTROY

	        invoke 	PostQuitMessage,NULL
	        xor	eax,eax
	        ret
	.endif
	invoke DefWindowProc,hwnd,uMsg,wParam,lParam

	ret
WndProc endp
END START

wpe4.jpg (35761 字节)

ENVIRON建立两个子窗口:一个是LBS_STANDARD样式的清单方块,另一个是SS_LEFT样式(置左对齐文字)的静态窗口。ENVIRON使用函数GetEnvironmentStrings来获得一个指标,该指标指向存有全部环境变量名及其值的内存区块。ENVIRON用FillListBox函数来分析此内存区块,并使用LB_ADDSTRING消息来指定清单方块窗口消息处理程序将每个字符串放入清单方块中。

当您执行ENVIRON时,可以使用鼠标或者键盘来选择环境变量。每次您改变选择时,清单方块都会给其父窗口WndProc发送一个WM_COMMAND消息。当WndProc收到WM_COMMAND消息时,它就检查wParam的低字组是否为ID_LIST(清单方块的子窗口ID)和wParam的高字组(通知码)是否等于LBN_SELCHANGE。如果是的,那么它就使用LB_GETCURSEL消息来获得选中项目的索引,并使用LB_GETTEXT来获得外部环境变量名的字符串本身。ENVIRON程序使用C语言函数GetEnvironmentVariable来获得与变量相对应的环境字符串,使用SetWindowText将该字符串传递到静态子窗口控件中,这个静态子窗口控件被用来显示文字。

文件列表

我将最好的留在最后:LB_DIR,这是功能最强的清单方块消息。它用文件目录列表填入清单方块,并且可以选择将子目录和有效的磁盘驱动器也包括进来:

SendMessage (hwndList, LB_DIR, iAttr, (LPARAM) szFileSpec) ;
        

使用文件属性码

iAttr参数是文件属性代码,其最低字节是文件属性代码,该代码可以是表9-6数据的组合:

表9-6

iAttr

属性

DDL_READWRITE 0x0000 普通文件
DDL_READONLY 0x0001 只读文件
DDL_HIDDEN 0x0002 隐藏文件
DDL_SYSTEM 0x0004 系统文件
DDL_DIRECTORY 0x0010 子目录
DDL_ARCHIVE 0x0020 归档位设立的档案

高字节提供了一些对所要求项目的附加控制:

表9-7

iAttr

属性

DDL_DRIVES 0x4000 包括磁盘驱动器句柄
DDL_EXCLUSIVE 0x8000 互斥搜索

前缀DDL表示「对话目录列表」。

当LB_DIR消息的iAttr值为DDL_READWRITE时,清单方块列出普通文件、只读文件和归档位设立的档案。当值为DDL_DIRECTORY时,清单方块除了列出上述文件之外,还列出子目录,目录位于中括号之内。当值为DDL_DRIVES | DDL_DIRECTORY时,那么列表将扩展到包括所有有效的磁盘驱动器,而磁盘驱动器句柄显示在虚线之间。

将iAttr的最高位设立就可以只列出符合条件的文件,而不包括其它文件。例如,对Windows的文件备份程序,也许您只想列出最后一次备份后修改过的文件,这种文件的归档位设立,因此您可以使用DDL_EXCLUSIVE | DDL_ARCHIVE。

文件列表的排序

lParam参数是指向文件指定字符串如「*.*」的指针,这个文件指定字符串不影响清单方块中的子目录。

您也许希望给列有文件清单的清单方块使用LBS_SORT消息。清单方块首先列出符合文件指定要求的文件,再(可选择)列出子目录名。列出的第一个子目录名将采用下面的格式:

[..]

这一个「两个点」的子目录项允许使用者向根目录回溯一层(在根目录下列出文件名时此项目不会出现)。最后,具体的子目录名称采用下面的形式:

[SUBDIR]

再来是以下列形式列出的有效磁盘驱动器(也是可选择的):

[-A-]

Windows的head程序

UNIX中有一个著名的实用程序叫做head,它显示文件开始的几行。让我们使用清单方块为Windows编写一个类似的程序。如程序9-6所示,HEAD将所有文件和子目录列在清单方块中。您可以挑选某个被选择的文件来显示,方法是在该文件上使用鼠标双击或者使用Enter键按下要选的文件。您也可以使用这两种方法之一来改变子目录。这个程序在HEAD窗口显示区域的右边,从文件的开头开始显示,它最多能够显示8 KB的内容。

程序9-6 HEAD

        
HEAD.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
	ID_LIST 	equ	1
	ID_TEXT 	equ	2
	MAXREAD     	equ	8192
	DIRATTR     	equ	(DDL_READWRITE or DDL_READONLY or DDL_HIDDEN or DDL_SYSTEM or DDL_DIRECTORY or DDL_ARCHIVE  or DDL_DRIVES)
	DTFLAGS     	equ	(DT_WORDBREAK or DT_EXPANDTABS or DT_NOCLIP or DT_NOPREFIX)


.DATA
	szAppName   db "head",0
	szListBox   db "listbox",0
	szStatic    db "static",0
	szStar	    db "*.*",0
	szBackSlant db "\\",0
.DATA?
   	buffer	    db 	MAXREAD	dup(?)
        szFile	    TCHAR MAX_PATH + 1 dup(?)
	bValidFile	BOOL	?
	hwndList	HWND	?
	hwndText 	HWND	?
	rect		RECT	<?>	
	OldList 	WNDPROC	?

.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("head"), 
					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

ListProc proc	hwnd:HWND, message:UINT, wParam:WPARAM,  lParam:LPARAM
        
           .if (message == WM_KEYDOWN) && (wParam == VK_RETURN)
        	;MAKELONG宏可以将两个16位的无符号数组合成一个32位的无符号数:   
		;The   MAKELONG   macro   creates   an   unsigned   32-bit   value   by   concatenating   two   given   16-bit   values.     
		;DWORD   MAKELONG(   
      		;	WORD   wLow,     //   low-order   word   of   long   value   
      		;	WORD   wHigh     //   high-order   word   of   long   value   
  		;)
  		mov	eax,LBN_DBLCLK
  		shl	eax,16
  		mov	ax,1
  		mov	ebx,eax
  		invoke	GetParent,hwnd
                invoke  SendMessage,eax, WM_COMMAND, ebx, hwnd
	   .endif 
	   invoke	CallWindowProc ,OldList, hwnd, message, wParam, lParam
           
        ret
ListProc endp


WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
	LOCAL	hFile:HANDLE
        LOCAL   hdc:HDC
        LOCAL	i, cxChar, cyChar:DWORD
        LOCAL	ps:PAINTSTRUCT
        LOCAL	szBuffer[MAX_PATH + 1]:TCHAR

	.if uMsg==WM_CREATE
                ; cxChar = LOWORD (GetDialogBaseUnits ()) ;
        	invoke	GetDialogBaseUnits
        	and	eax,0FFFFh
        	mov	cxChar,eax
        	
                ; cyChar = HIWORD (GetDialogBaseUnits ()) ;
        	invoke	GetDialogBaseUnits
        	shr	eax,16
        	mov	cyChar,eax
        	
        	mov	eax,cxChar
        	mov	ecx,20
        	mul	ecx
        	mov	rect.left,eax
        	
        	mov	eax,cyChar
        	mov	ecx,3
        	mul	ecx
        	mov	rect.top,eax
		;push	eax
		;invoke	wsprintf,addr szBuffer,CTXT("%X"),eax
		;invoke	MessageBox,hwnd,addr szBuffer,NULL,MB_APPLMODAL	  		
		;pop	eax        	
 		
        	;Create listbox and static text windows.
		push	NULL
		invoke	GetWindowLong,hwnd,GWL_HINSTANCE
		push	eax
		push	ID_LIST
		push	hwnd
		mov	eax,cyChar
		mov	ecx,10
		mul	ecx

		push	eax
		mov	eax,cxChar
		mov	ecx,13
		mul	ecx
		mov	ebx,eax
			
		invoke	GetSystemMetrics,SM_CXVSCROLL
		add	eax,ebx
	
		push	eax
		mov	eax,cyChar
		mov	ecx,3
		mul	ecx
		push	eax
		push	cxChar
		mov	eax, WS_CHILDWINDOW or WS_VISIBLE or LBS_STANDARD
		push	eax
		push	NULL
		push	offset szListBox
		push	NULL
		call	CreateWindowEx
		mov	hwndList,eax

		invoke	GetCurrentDirectory,MAX_PATH+1,addr szBuffer
		
		push	NULL
		invoke	GetWindowLong,hwnd,GWL_HINSTANCE
		push	eax
		push	ID_TEXT
		push	hwnd
		push	cyChar
		mov	eax,cxChar
		mov	ecx,MAX_PATH
		mul	ecx
		push	eax
		push	cyChar
		push	cxChar
		mov	eax,WS_CHILDWINDOW or WS_VISIBLE or SS_LEFT
		push	eax
		lea	eax,szBuffer
		push	eax
		lea	eax,szStatic
		push	eax
		push	NULL
		call	CreateWindowEx
		mov	hwndText,eax
		
		invoke	SetWindowLong,hwndList,GWL_WNDPROC,ListProc
		mov	OldList,eax
		
		invoke	SendMessage,hwndList, LB_DIR, DIRATTR, addr szStar
	        xor	eax,eax
	        ret
	.elseif	uMsg == WM_SIZE
		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_COMMAND
		
		mov	eax,(LBN_DBLCLK SHL 16) OR (ID_LIST)
		.if	eax == wParam
			invoke	SendMessage,hwndList,LB_GETCURSEL,0,0
			mov	i,eax
			.if	eax==LB_ERR
				jmp	@f
			.endif	
			invoke	SendMessage,hwndList,LB_GETTEXT,i,addr szBuffer
	
			invoke	CreateFile,addr szBuffer,GENERIC_READ,FILE_SHARE_READ,
					NULL,OPEN_EXISTING,0,NULL
			mov	hFile,eax
			.if	eax!=INVALID_HANDLE_VALUE
				invoke	CloseHandle,hFile
				mov	bValidFile,TRUE
				invoke	lstrcpy,addr szFile,addr szBuffer
				
				invoke	GetCurrentDirectory,MAX_PATH+1,addr szBuffer
				
				invoke	lstrlen,addr szBuffer
				dec	eax
				mov	ebx,eax
				mov	al,byte ptr szBuffer[ebx]
				.if	al!=2Fh
					invoke	lstrcat,addr szBuffer,CTXT("\")
				.endif
				invoke	lstrcat,addr szBuffer,addr szFile
				invoke	SetWindowText,hwndText,eax
				
			.else
						
				mov	bValidFile,FALSE
				invoke	lstrlen,addr szBuffer
				dec	eax
				mov	byte	ptr	szBuffer[eax],0
				;If setting the directory doesn't work, maybe it's
				;a drive change, so try that.
				invoke	SetCurrentDirectory,addr szBuffer +1
				.if	(!eax)
					mov	BYTE ptr szBuffer[3],':'
					mov	BYTE ptr szBuffer[4],0
					lea	eax,szBuffer
					add	eax,2
					invoke	SetCurrentDirectory,eax
					invoke	MessageBox,hwnd,addr szBuffer,NULL,MB_APPLMODAL	  
				.endif
				 
				; Get the new directory name and fill the list box.
				invoke	GetCurrentDirectory,MAX_PATH+1,addr szBuffer
				invoke	SetWindowText,hwndText,addr szBuffer
                                invoke	SendMessage,hwndList, LB_RESETCONTENT, 0, 0
                                invoke  SendMessage,hwndList, LB_DIR, DIRATTR,addr szStar				
			.endif			

			invoke	InvalidateRect,hwnd, NULL, TRUE
			
		.endif	 
	        xor	eax,eax
	        ret

	.elseif uMsg == WM_PAINT
		
		.if	(!bValidFile)
			jmp	@f
		.endif	
		
		invoke	CreateFile,addr szFile,GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL
		mov	hFile,eax
		.if	eax==INVALID_HANDLE_VALUE
			mov	bValidFile,FALSE
			jmp	@f
		.endif
		invoke	ReadFile,hFile,addr buffer, MAXREAD,addr i, NULL
		invoke	CloseHandle,hFile
		
                ;i now equals the number of bytes in buffer.
		;Commence getting a device context for displaying text.
        	invoke	BeginPaint,hwnd,addr ps
		mov	hdc,eax
        	invoke	GetStockObject,SYSTEM_FIXED_FONT
                invoke  SelectObject,hdc, eax
        	invoke	GetSysColor,COLOR_BTNTEXT
        	invoke  SetTextColor,hdc,eax
        	invoke	GetSysColor,COLOR_BTNFACE
                invoke  SetBkColor,hdc, eax
                ; Assume the file is ASCII 
             
                invoke  DrawText,hdc,addr buffer,i,offset rect,DTFLAGS
                invoke  EndPaint,hwnd, addr ps

	        xor	eax,eax
	        ret	        
	.elseif uMsg == WM_DESTROY

	        invoke 	PostQuitMessage,NULL
	        xor	eax,eax
	        ret	        
	.endif	   
  @@:	
	invoke DefWindowProc,hwnd,uMsg,wParam,lParam

	ret
WndProc endp
END START


	        
	        
        

wpe5.jpg (115325 字节)

在ENVIRON中,当我们选择一个环境变量时-无论是使用鼠标还是键盘-程序都将显示一个环境字符串。但是,如果我们在HEAD中使用这种选择显示方法,那么程序响应会很慢,这是因为在清单方块中移动选择时,程序仍然要不断地打开和关闭文件。然而,HEAD要求文件或者子目录被双击,从而引起一些问题,这是因为清单方块控件没有鼠标双击的自动键盘接口。前面讲过,如果可能,应该尽量提供键盘接口。

解决的方法是什么呢?当然是窗口子类别化。HEAD中的清单方块子类则函数叫做ListProc,它寻找wParam参数等于VK_RETURN的WM_KEYDOWN消息,并给其父窗口发送一条带有LBN_DBLCLK通知码的WM_COMMAND消息。在WndProc中,对WM_COMMAND的处理使用了Windows函数的CreateFile来检查清单方块中的选择。如果CreateFile传回一个错误信息,则表示该选择不是文件,而可能是一个子目录。然后HEAD使用SetCurrentDirectory来改变这个子目录。如果SetCurrentDirectory不能执行,程序将假定使用者已经选择了一个磁盘驱动器句柄。改变磁盘驱动器也需要呼叫SetCurrentDirectory,作为该函数参数的字符串则为是选择字符串中拿掉开头的斜线,并加上一个冒号。它向清单方块发送一条LB_RESETCONTENT消息来清除其中的内容,再发送一条LB_DIR消息,使用新子目录中的文件来填入清单方块。

WndProc中的WM_PAINT消息是用Windows的CreateFile函数来打开文件的,这将传回一个文件句柄,该句柄可以传递给Windows的ReadFile和CloseHandle函数。

现在,在本章中,我们第一次碰到这个问题:Unicode。我们所希望最完美的方式大概就是让操作系统辨认文本文件的种类,使ReadFile能将ASCII文件转换成Unicode文字,或者将Unicode文件转换成ASCII文字。但现实并非如此完美。ReadFile的功能只是读取文件中未经转换的字节,也就是说,DrawTextA(在编译好的可执行档中没有定义UNICODE标识符)会把文字解释为ASCII,而DrawTextW(Unicode版)会假设文字是Unicode的。

因此程序真正应该做的是去判别文件所包含的是ASCII文字还是Unicode文字,然后再恰当地呼叫DrawTextA或者DrawTextW。实际上,HEAD采用一个比较简单的方式,它只呼叫了DrawTextA。


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