见招拆招 Windows 程序设计 (五) 第二部分
相关的例子:下载>>> 作者:Zoologist 于2008-1-17上传 

紧接上一部分

外语键盘问题

    如果您执行美国英语版本的Windows,那么您可安装不同的键盘布局,并输入外语。可以在 控制面板的区域和语言选项中安装外语键盘布局。选择 语言页面标签,按下详细信息,再选择新增 键。要查看死键的工作方式,您可能想安装「德语」键盘。此外,我还要讨论「俄语」和「希腊语」的键盘布局,因此您也可安装这些键盘布局。如果在「键盘」显示的列表中找不到「俄语」和「希腊语」的键盘布局,则需要安装多语系支持:从「控制面板」中选择 新增/删除程序,然后选择 Windows安装程序页面卷标,确认选中 多语系支持复选框。在任何情况下,这些变更都需要原始的Windows光盘。

    安装完其它键盘布局后,您将在工作列右侧的通知区看到一个带有两个字母代码的蓝色框。如果内定的是英语,那么这两个字母是「EN」。单击此图标,将得到所有已安装键盘布局的列表。从中单击需要的键盘布局即可更改目前活动程序的键盘。此改变只影响目前活动的程序。

    现在开始进行实验。不使用UNICODE标识符定义来编译KEYVIEW1程序。在美国英语版本的Windows下执行该程序,并输入字符『abcde』。 WM_CHAR消息与您所期望的一样:ASCII字符代码0x61、0x62、0x63、0x64和0x65以及字母a、b、c、d和e。

    现在,KEYVIEW1还在执行,选择德语键盘布局。按下=键然后输入一个元音(a、e、i、o或者u)。=键将产生一个WM_DEADCHAR消息,元音产生一个WM_CHAR消息和(单独的)字符代码0xE1、0xE9、0xED、0xF3、0xFA和字符á、é、í、ó或ú。这就是死键的工作方式。

现在切换到俄语键盘并重新输入『abcde』。现在您得到WM_CHAR消息和字符代码0xF4、0xE8、0xF1、0xE2和0xF3,以及字符(.....)。而且,还是有些字母不能正常显示。您应从斯拉夫字母表中得到这些字母。

    问题在于:您已经切换键盘以产生不同的字符代码,但您还没有将此切换通知GDI,好让GDI能选择适当的符号来显示解释这些字符代码。

    如果您非常勇敢,还有可用的备用PC,并且是专业或全球版Microsoft Developer Network(MSDN)的订阅户,那么您也许想安装(例如)希腊版的Windows,您还可以把那四种键盘布局(英语、希腊语、德语和俄语)安装上去。现在执行KEYLOOK1,切换到英语键盘布局,然后输入『abcde』。您应得到ASCII字符代码0x61、0x62、0x63、0x64和0x65以及字符a、b、c、d和e(并且您可以放心:即使在希腊版,ASCII还是正常通行的)。

    在希腊版的Windows中,切换到希腊键盘布局并输入『abcde』。您将得到WM_CHAR消息和字符代码0xE1、0xE2、0xF8、0xE4和0xE5。这与您在安装希腊键盘布局的英语版Windows中得到的字符代码相同。但现在显示的字符是?、?、?、?和?。这些确实是小写的希腊字母alpha、beta、psi、delta和epsilon(gamma怎么了?是这样,如果使用希腊版的Windows,那么您将使用键帽上带有希腊字母的键盘。与英语c相对应的键正好是psi。gamma由与英语g相对应的键产生。您可在Nadine Kano编写的《Developing International Software for Windows 95 and Windows NT》的第587页看到完整的希腊字母表)。

     继续在希腊版的Windows下运行KEYVIEW1,切换到德语键盘布局。输入『=』键,然后依次输入a、e、i、o和u。您将得到WM_CHAR消息和字符代码0xE1、0xE9、0xED、0xF3和0xFA。这些字符代码与安装德语键盘布局的英语版Windows中的一样。不过,显示的字符却是?、?、?、?和?,而不是正确的á、é、í、ó和ú。

现在切换到俄语键盘并输入『abcde』。您会得到字符代码0xF4、0xE8、0xF1、0xE2和0xF3,这与安装俄语键盘的英语版Windows中得到的一样。不过,显示的字符是?、?、?、?和?,而不是斯拉夫字母表中的字母。

    您还可安装俄语版的Windows。现在您可以猜到,英语和俄语键盘都可以工作,而德语和希腊语则不行。

    现在,如果您真的很勇敢,您还可安装日语版的Windows并执行KEYVIEW1。如果再依美国键盘输入,那么您将输入英语文字,一切似乎都正常。不过,如果切换到德语、希腊语或者俄语键盘布局,并且试著作上述介绍的任何练习,您将看到以点显示的字符。如果输入大写的字母-无论是带重音符号的德语字母、希腊语字母还是俄语字母-您将看到这些字母显示为日语中用于拼写外来语的片假名。您也许对输入片假名感兴趣,但那不是德语、希腊语或者俄语。

    远东版本的Windows包括一个称作「输入法编辑器」(IME)的实用程序,该程序显示为浮动的工具列,它允许您用标准键盘输入象形文字,即汉语、日语和朝鲜语中使用的复杂字符。一般来说,输入一组字母后,组成的字符将显示在另一个浮动窗口内。然后按 Enter键,合成的字符代码就发送到了活动窗口(即KEYVIEW1)。KEYVIEW1几乎没什么响应-WM_CHAR消息带来的字符代码大于128,但这些代码没有意义(Nadine Kano的书中有许多关于使用IME的内容)。

    这时,我们已经看到了许多KEYLOOK1显示错误字符的例子-当执行安装了俄语或希腊语键盘布局的英语版Windows时,当执行安装了俄语或德语键盘布局的希腊版Windows时,以及执行安装了德语、俄语或者希腊语键盘布局的俄语版Windows时,都是这样。我们也看到了从日语版Windows的输入法编辑器输入字符时的错误显示。

字符集和字体

    KEYLOOK1的问题是字体问题。用于在屏幕上显示字符的字体和键盘接收的字符代码不一致。因此,让我们看一下字体。

    我将在后面一期进行详细讨论,Windows支持三类字体-点阵字体、向量字体和(从Windows 3.1开始的)TrueType字体。

    事实上向量字体已经过时了。这些字体中的字符由简单的线段组成,但这些线段没有定义填入区域。向量字体可以较好地缩放到任意大小,但字符通常看上去有些单薄。

    TrueType字体是定义了填入区域的文字轮廓字体。TrueType字体可缩放;而且该字符的定义包括「提示」,以消除可能带来的文字不可见或者不可读的圆整问题。使用TrueType字体,Windows就真正实现了WYSIWYG(「所见即所得」),即文字在视讯显示器显示与打印机输出完全一致。

    在点阵字体中,每个字符都定义为与视讯显示器上的图素对应的位点阵。点阵字体可拉伸到较大的尺寸,但看上去带有锯齿。点阵字体通常被设计成方便在显示器上阅读的字体。因此,Windows中的标题列、菜单、按钮和对话框的显示文字都使用点阵字体。

    在内定的设备内容下获得的点阵字体称为系统字体。您可通过呼叫带有SYSTEM_FONT标识符的GetStockObject函数来获得字体句柄。KEYVIEW1程序选择使用SYSTEM_FIXED_FONT表示的等宽系统字体。GetStockObject函数的另一个选项是OEM_FIXED_FONT。

    这三种字体有(各自的)字体名称-System、FixedSys和Terminal。程序可以在CreateFont或者CreateFontIndirect函数呼叫中使用字体名称来指定字体。这三种字体储存在两组放在Windows目录内的FONTS子目录下的三个文件中。Windows使用哪一组文件取决于「控制面板」里的「显示」是选择显示「小字体」还是「大字体」(亦即,您希望Windows假定显示器是96 dpi的分辨率还是120 dpi的分辨率)。表6-14总结了所有的情况:

表6-14

GetStockObject标识符

字体名称

小字体文件

大字体文件

SYSTEM_FONT System VGASYS.FON 8514SYS.FON
SYSTEM_FIXED_FONT FixedSys VGAFIX.FON 8514FIX.FON
OEM_FIXED_FONT Terminal VGAOEM.FON 8514OEM.FON

    在文件名称中,「VGA」指的是视频图形数组(Video Graphics Array),IBM在1987年推出的显示卡。这是IBM第一块可显示640×480图素大小的PC显示卡。如果在「控制面板」的「显示」中选择了「小字体」(表示您希望Windows假定显示的分辨率为96 dpi),则Windows使用的这三种字体文件名将以「VGA」开头。如果选择了「大字体」(表示您希望分辨率为120 dpi),Windows使用的文件名将以「8514」开头。8514是IBM在1987年推出的另一种显示卡,它的最大显示尺寸为1024×768。(Z.t注:我查了一下 XP 已经不使用这样的字库文件了)

Windows不希望您看到这些文件。这些文件的属性设定为系统和隐藏,如果用Windows Explorer来查看FONTS子目录的内容,您是不会看到它们的,即使选择了查看系统和隐藏文件也不行。从开始菜单选择「寻找」选项来寻找文件名满足 *.FON限定条件的文件。这时,您可以双击文件名来查看字体字符是些什么。

对于许多标准控件和使用者接口组件,Windows不使用系统字体。相反地,使用名称为MS Sans Serif的字体(「MS」代表Microsoft)。这也是一种点阵字体。文件(名为SSERIFE.FON)包含依据96 dpi显示器的字体,点值为8、10、12、14、18和24。您可在GetStockObject函数中使用DEFAULT_GUI_FONT标识符来得到该字体。Windows使用的点值取决于「控制面板」的「显示」中选择的显示分辨率。

到目前为止,我已提到四种标识符,利用这四种标识符,您可以用GetStockObject来获得用于设备内容的字体。还有三种其它字体标识符:ANSI_FIXED_FONT、ANSI_VAR_FONT和DEVICE_DEFAULT_FONT。为了开始处理键盘和字符显示问题,让我们先看一下Windows中的所有备用字体。显示这些字体的程序是STOKFONT,如程序6-3所示。

程序6-3 STOKFONT

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

.386
.Model Flat, StdCall
Option Casemap :None

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

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
IncludeLib winmm.lib
include macro.asm
	
	WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
	WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.DATA
	szAppName   db "StokFont",0
	
 	szMsg0      db "OEM_FIXED_FONT",0
 	szMsg1      db "ANSI_FIXED_FONT",0  
 	szMsg2      db "ANSI_VAR_FONT",0
 	szMsg3      db "SYSTEM_FONT",0
 	szMsg4      db "DEVICE_DEFAULT_FONT",0
 	szMsg5      db "SYSTEM_FIXED_FONT",0
 	szMsg6      db "DEFAULT_GUI_FONT",0


stockfont 	  		dd	OEM_FIXED_FONT,offset szMsg0
						dd	ANSI_FIXED_FONT,offset szMsg1
						dd	ANSI_VAR_FONT,offset szMsg2
						dd	SYSTEM_FONT,offset szMsg3
						dd	DEVICE_DEFAULT_FONT,offset szMsg4
						dd	SYSTEM_FIXED_FONT,offset szMsg5
						dd	DEFAULT_GUI_FONT,offset szMsg6
cFonts			dd	7
iFont				dd	0
.DATA?
	hInstance	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("Stock Fonts"), ;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,message:DWORD,wParam :DWORD,lParam :DWORD

	LOCAL hdc 				:HDC
	LOCAL ps  				:PAINTSTRUCT 
	LOCAL szBuffer		[LF_FACESIZE + 64]	:BYTE
	LOCAL szFaceName	[LF_FACESIZE]	:BYTE	
	LOCAL tm					:TEXTMETRIC
	LOCAL i,x,y,tmpx,tmpy:DWORD	
	LOCAL cxGrid,cyGrid 	:DWORD	

	.if message==WM_CREATE
		
		mov	ebx,cFonts
		dec	ebx
		;SetScrollRange (hwnd, SB_VERT, 0, cFonts - 1, TRUE) ;
      invoke	SetScrollRange,hwnd, SB_VERT, 0, ebx, TRUE
       
       
      ret

      .elseif message == WM_DISPLAYCHANGE
		;InvalidateRect (hwnd, NULL, TRUE) 
		invoke	InvalidateRect,hwnd, NULL, TRUE
 		ret
     
      .elseif message == WM_VSCROLL
      			mov	eax,wParam		
      			and	eax,0FFFFh			;LOWORD (wParam)


      		.if	eax==SB_TOP
          		xor	eax,eax
          		mov	iFont,eax

				.elseif eax==SB_BOTTOM
		         mov	eax,cFonts
		         dec	eax
		         mov	iFont,eax

		
      		.elseif	(eax==SB_LINEUP) || (eax==SB_PAGEUP)
					mov	eax,iFont
					dec	eax
					mov	iFont,eax
       		
				.elseif (eax==SB_LINEDOWN) || (eax==SB_PAGEDOWN )
					mov	eax,iFont
					inc	eax
					mov	iFont,eax
					
				.elseif ax==SB_THUMBPOSITION      
					mov	eax,wParam			
					shr	eax,16
					mov	iFont,eax					
 		      .endif 

				;iFont = max (0, min (cFonts - 1, iFont)) ;
				mov	eax,cFonts
				dec	eax
				cmp	iFont,eax
				jg		@f
						mov	eax,iFont
				@@:		
			
				cmp		eax,0						;
				jg			@f
				xor		eax,eax	
				@@:    
				mov	iFont,eax
		
            invoke	SetScrollPos,hwnd, SB_VERT, iFont, TRUE
        
				invoke	InvalidateRect,hwnd, NULL, TRUE
   
				ret     
		.elseif	message == WM_KEYDOWN
				mov	eax,wParam
	
				.if		eax	== VK_HOME
						invoke	SendMessage,hwnd, WM_VSCROLL, SB_TOP, 0
				.elseif	eax == VK_END
						invoke	SendMessage,hwnd, WM_VSCROLL, SB_BOTTOM, 0
				.elseif	eax	== VK_PRIOR || eax == VK_LEFT || eax == VK_UP
						invoke	SendMessage,hwnd, WM_VSCROLL, SB_LINEUP, 0
				.elseif	eax	== VK_NEXT || eax == VK_RIGHT || eax == VK_DOWN
						invoke	SendMessage,hwnd, WM_VSCROLL, SB_PAGEDOWN, 0
				.endif
			ret
			
		.elseif message == WM_PAINT
 	
			
		invoke	BeginPaint,hwnd,addr ps
		mov		hdc,eax
		;SelectObject (hdc, GetStockObject (stockfont[iFont].idStockFont)) ;
		lea		esi,stockfont
		mov		eax,iFont
		shl		eax,3
		add		esi,eax
		mov		ebx,[esi]
		invoke	GetStockObject ,ebx
		invoke	SelectObject ,hdc,eax

		invoke	GetTextFace,hdc,LF_FACESIZE,addr	szFaceName

		invoke	GetTextMetrics,hdc,addr	tm
		
		;cxGrid = max (3 * tm.tmAveCharWidth, 2 * tm.tmMaxCharWidth
		mov		eax,tm.tmAveCharWidth
		mov		ecx,3
		mul		ecx
		
		mov		ebx,tm.tmMaxCharWidth
		shl		ebx,1
		.if		(eax

这个程序相当简单。它使用滚动条和光标移动键让您选择显示七种备用字体之一。该程序在一个网格中显示一种字体的256个字符。顶部的标题和网格的左侧显示字符代码的十六进制值。

在显示区域的顶部,STOKFONT用GetStockObject函数显示用于选择字体的标识符。它还显示由GetTextFace函数得到的字体样式名称和TEXTMETRIC结构的tmCharSet字段。这个「字符集标识符」对理解Windows如何处理外语版本的Windows是非常重要的。

如果在美国英语版本的Windows中执行STOKFONT,那么您看到的第一个画面将显示使用OEM_FIXED_FONT标识符呼叫GetStockObject函数得到的字体。如图6-3所示。


 

图6-3 美国版Windows中的OEM_FIXED_FONT

    在本字符集中(与本章其它部分一样),您将看到一些ASCII。但请记住ASCII是7位代码,它定义了从代码0x20到0x7E的可显示字符。到IBM开发出IBM PC原型机时,8位字节代码已被稳固地建立起来,因此可使用全8位代码作为字符代码。IBM决定使用一系列由线和方块组成的字符、带重音字母、希腊字母、数学符号和一些其它字符来扩展ASCII字符集。许多文字模式的MS-DOS程序在其屏幕显示中都使用绘图字符,并且许多MS-DOS程序都在文件中使用了一些扩展字符。

    这个特殊的字符集给Windows最初的开发者带来了一个问题。一方面,因为Windows有完整的图形程序设计语言,所以线和方块字元在Windows中不需要。因此,这些字符使用的48个代码最好用于许多西欧语言所需要的附带重音字母。另一方面,IBM字符集定义了一个无法完全忽略的标准。

    因此,Windows最初的开发者决定支持IBM字符集,但将其重要性降低到第二位-它们大多用于在窗口中执行的旧MS-DOS应用程序,和需要使用由MS-DOS应用程序建立文件的Windows程序。Windows应用程序不使用IBM字符集,并且随着时间的推移,其重要性日渐衰退。然而,如果需要,您还是可以使用。在此环境下,「OEM」指的就是「IBM」。

   (您应知道外语版本的Windows不必支持与美国英语版相同的OEM字符集。其它国家有其自己的MS-DOS字符集。这是个独立的问题,就不在本书中讨论了。)

     因为IBM字符集被认为不适合Windows,于是选择了另一种扩展字符集。此字符集称作「ANSI字符集」,由美国国家标准协会(American National Standards Institute)制定,但它实际上是ISO(International Standards Organization,国际标准化组织)标准,也就是ISO标准8859。它还称为Latin 1、Western European、或者代码页1252。图6-4显示了ANSI字符集的一个版本-美国英语版Windows的系统字体。


 

图6-4 美国版Windows中的SYSTEM_FONT

粗的垂直条表示这些字符代码没有定义。注意,代码0x20到0x7E还是ASCII。此外,ASCII控制字符(0x00到0x1F以及0x7F)并不是可显示字符。它们本应如此。

代码0xC0到0xFF使得ANSI字符集对外语版Windows来说非常重要。这些代码提供64个在西欧语言中普遍使用的字符。字符0xA0,看起来像空格,但实际上定义为非断开空格,例如「WW II」中的空格。

之所以说这是ANSI字符集的「一个版本」,是因为存在代码0x80到0x9F的字符。等宽的系统字体只包括其中的两个字符,如图6-5所示。


 

图6-5 美国版Windows中的SYSTEM_FIXED_FONT

在Unicode中,代码0x0000到0x007F与ASCII相同,代码0x0080到0x009F复制了0x0000到0x001F的控制字符,代码0x00A0到0x00FF与Windows中使用的ANSI字符集相同。

如果执行德语版的Windows,那么当您用SYSTEM_FONT或者SYSTEM_FIXED_FONT标识符来呼叫GetStockObject函数时会得到同样的ANSI字符集。其它西欧版Windows也是如此。ANSI字符集中含有这些语言所需要的所有字符。

不过,当您执行希腊版的Windows时,内定的字符集就改变了。相反地,SYSTEM_FONT如图6-6所示。


 

图6-6 希腊版Windows中的SYSTEM_FONT

SYSTEM_FIXED_FONT有同样的字符。注意从0xC0到0xFF的代码。这些代码包含希腊字母表中的大写字母和小写字母。当您执行俄语版Windows时,内定的字符集如图6-7所示。


 

图6-7 俄语版Windows中的SYSTEM_FONT

此外, 注意斯拉夫字母表中的大写和小写字母占用了代码0xC0和0xFF。

图6-8显示了日语版Windows的SYSTEM_FONT。从0xA5到0xDF的字符都是片假名字母表的一部分。


 

图6-8 日语版Windows中的SYSTEM_FONT

     图6-8所示的日文系统字体不同于前面显示的那些,因为它实际上是双字节字符集(DBCS),称为「Shift-JIS」(「JIS」代表日本工业标准,Japanese Industrial Standard)。从0x81到0x9F以及从0xE0到0xFF的大多数字符代码实际上只是双字节代码的第一个字节,其第二个字节通常在0x40到0xFC的范围内(关于这些代码的完整表格,请参见Nadine Kano书中的附录G)。

     现在,我们就可以看看KEYVIEW1中的问题在哪里:如果您安装了希腊键盘布局并键入『abcde』,不考虑执行的Windows版本,Windows将产生WM_CHAR消息和字符代码0xE1、0xE2、0xF8、0xE4和0xE5。但只有执行带有希腊系统字体的希腊版Windows时,这些字符代码才能与κ、χ、υ、μ相对应。

    如果您安装了俄语键盘布局并敲入『abcde』,不考虑所使用的Windows版本,Windows将产生WM_CHAR消息和字符代码0xF4、0xE8、0xF1、0xE2和0xF3。但只有在使用俄语版Windows或者使用斯拉夫字母表的其它语言版,并且使用斯拉夫系统字体时,这些字符代码才会与字符φ、и、с、в和у相对应。

    如果您安装了德语键盘布局并按下=键(或者位于同一位置的键),然后按下a、e、i、o或者u键,不考虑使用的Windows版本,Windows将产生WM_CHAR消息和字符代码0xE1、0xE9、0xED、0xF3和0xFA。只有执行西欧版或者美国版的Windows时,也就是说有西欧系统字体,这些字符代码才会和字符á、é、í、ó和ú相对应。

    如果安装了美国英语键盘布局,则您可在键盘上键入任何字符,Windows将产生WM_CHAR消息以及与字符正确匹配的字符代码。

Unicode怎么样?

    我在第二章谈到过Windows NT支持的Unicode有助于为国际市场程序写作。让我们编译一下定义了UNICODE标识符的KEYVIEW1,并在不同版本的Windows NT下执行(在本书附带的光盘中,Unicode版的KEYVIEW1位于DEBUG目录中)。

    如果程序编译时定义了UNICODE标识符,则「KeyView1」窗口类别就用RegisterClassW函数注册,而不是RegisterClassA函数。这意味着任何带有字符或文字数据的消息传递给WndProc时都将使用16位字符而不是8位字符。特别是WM_CHAR消息,将传递16位字符代码而不是8位字符代码。

    请在美国英语版的Windows NT下执行Unicode版的KEYVIEW1。这里假定您已经安装了至少三种我们试验过的键盘布局-即德语、希腊语和俄语。

    使用美国英语版的Windows NT,并安装了英语或者德语的键盘布局,Unicode版的KEYVIEW1在工作时将与非Unicode版相同。它将接收相同的字符代码(所有0xFF或者更低的值),并显示同样正确的字符。这是因为最初的256个Unicode字符与Windows中使用的ANSI字符集相同。

    现在切换到希腊键盘布局,并键入『abcde』。WM_CHAR消息将含有Unicode字符代码0x03B1、 0x03B2、0x03C8、 0x03B4和0x03B5。注意,我们先看到的字符代码值比0xFF高。这些Unicode字符代码与希腊字母edybt0x03B40x03B5相对应。不过,所有这五个字符都显示为方块!这是因为SYSTEM_FIXED_FONT只含有256个字符。

    现在切换到俄语键盘布局,并键入『abcde』。KEYVIEW1显示WM_CHAR消息和Unicode字符代码0x0444、0x0438、0x0441、0x0432和0x0443,这些字符对应于斯拉夫字母φ、и、с、в和у。不过,所有这五个字母也显示为实心方块。

    简言之,非Unicode版的KEYVIEW1显示错误字符的地方,Unicode版的KEYVIEW1就显示实心方块,以表示目前的字体没有那种特殊字符。虽然我不愿说Unicode版的KEYVIEW1是非Unicode版的改进,但事实确实如此。非Unicode版显示错误字符,而Unicode版不会这样。

    Unicode和非Unicode版KEYVIEW1的不同之处主要在两个方面。

    首先,WM_CHAR消息伴随一个16位字符代码,而不是8位字符代码。在非Unicode版本的KEYVIEW1中,8位字符代码的含义取决于目前活动的键盘布局。如果来自德语键盘,则0xE1代码表示á,如果来自希腊语键盘则代表?,如果来自俄语键盘则代表?。在Unicode版本程序中,16位字符代码的含义很明确:a字符是0x00E1,?字符是0x03B1,而?字符是0x0431。

    第二,Unicode的TextOutW函数显示的字符依据16位字符代码,而不是非Unicode的TextOutA函数的8位字符代码。因为这些16位字符代码含义明确,GDI可以确定目前在设备内容中选择的字体是否可显示每个字符。

    在美国英语版Windows NT下执行Unicode版的KEYVIEW1多少让人感到有些迷惑,因为它所显示的就好像GDI只显示了0x0000到0x00FF之间的字符代码,而没有显示高于0x00FF的代码。也就是说,只是在字符代码和系统字体中256个字符之间简单的一对一映射。

    然而,如果安装了希腊或者俄语版的Windows NT,您将发现情况就大不一样了。例如,如果安装了希腊版的Windows NT,则美国英语、德语、希腊语和俄语键盘将会产生与美国英语版Windows NT同样的Unicode字符代码。不过,希腊版的Windows NT将不显示德语重音字符或者俄语字符,因为这些字符并不在希腊系统字体中。同样,俄语版的Windows NT也不显示德语重音字符或者希腊字符,因为这些字符也不在俄语系统字体中。

    其中,Unicode版的KEYVIEW1的区别在日语版Windows NT下更具戏剧性。您从IME输入日文字符,这些字符可以正确显示。唯一的问题是格式:因为日文字符通常看起来非常复杂,它们的显示宽度是其它字符的两倍。

TrueType 和大字体

    我们使用的点阵字体(在日文版Windows中带有附加字体)最多包括256个字符。这是我们所希望的,因为当假定字符代码是8位时,点阵字体文件的格式就跟早期Windows时代的样子一样了。这就是为什么当我们使用SYSTEM_FONT或者SYSTEM_FIXED_FONT时,某些语言中一些字符总不能正确显示(日本系统字体有点不同,因为它是双字节字符集;大多数字符实际上保存在TrueType集合文件中,文件扩展名是.TTC)。

    TrueType字体包含的字符可以多于256个。并不是所有TrueType字体中的字符都多于256个,但Windows 98和Windows NT中的字体包含多于256个字符。或者,安装了多语系支持后,TrueType字体中也包含多于256个字符。在「 控制面板」的「新增 /删除程序」中,单击「Windows 安装程序」页面卷标,并确保选中了「 多语系支持」。这个多语系支持包括五个字符集:波罗的海语系、中欧语系、斯拉夫语系、希腊语系和土耳其语系。波罗的海语系字符集用于爱沙尼亚语、拉脱维亚语和立陶宛语。中欧字符集用于阿尔巴尼亚语、捷克语、克罗地亚语、匈牙利语、波兰语、罗马尼亚语、斯洛伐克语和斯洛文尼亚语。斯拉夫字符集用于保加利亚语、白俄罗斯语、俄语、塞尔维亚语和乌克兰语。

    Windows 98中的TrueType字体支持这五种字符集,再加上西欧(ANSI)字符集,西欧字符集实际上用于其它所有语言,但远东语言(汉语、日语和朝鲜语)除外。支持多种字符集的TrueType字体有时也称为「大字体」。在这种情况下的「大」并不是指字符的大小,而是指数量。

即    使在非Unicode程序中也可利用大字体,这意味着可以用大字体显示几种不同字母表中的字符。然而,为了要将得到的字体选进设备内容,还需要GetStockObject以外的函数。

   函数CreateFont和CreateFontIndirect建立了一种逻辑字体,这与CreatePen建立逻辑画笔以及CreateBrush建立逻辑画刷的方式类似。CreateFont用14个参数描述要建立的字体。CreateFontIndirect只有一个参数,但该参数是指向LOGFONT结构的指针。LOGFONT结构有14个字段,分别对应于CreateFont函数的参数。现在,让我们看一下CreateFont函数,但我们只注意其中两个参数,其它参数都设定为0。

如果需要等宽字体(就像KEYVIEW1程序中使用的),将CreateFont的第13个参数设定为FIXED_PITCH。如果需要非内定字符集的字体(这也是我们所需要的),将CreateFont的第9个参数设定为某个「字符集ID」。此字符集ID将是WINGDI.H中定义的下列值之一。我已给出注释,指出和这些字符集相关的代码页:

#define ANSI_CHARSET 0 // 1252 Latin 1 (ANSI)
#define DEFAULT_CHARSET 1  
#define SYMBOL_CHARSET 2  
#define MAC_CHARSET 77  
#define SHIFTJIS_CHARSET 128 // 932 (DBCS, 日本)
#define HANGEUL_CHARSET 129 // 949 (DBCS, 韩文)
#define HANGUL_CHARSET 129 // " "
#define JOHAB_CHARSET 130 // 1361 (DBCS, 韩文)
#define GB2312_CHARSET 134 // 936 (DBCS, 简体中文)
#define CHINESEBIG5_CHARSET 136 // 950 (DBCS, 繁体中文)
#define GREEK_CHARSET 161 // 1253希腊文
#define TURKISH_CHARSET 162 // 1254 Latin 5 (土耳其文)
#define VIETNAMESE_CHARSET 163 // 1258越南文
#define HEBREW_CHARSET 177 // 1255希伯来文
#define ARABIC_CHARSET 178 // 1256阿拉伯文
#define BALTIC_CHARSET 186 // 1257波罗的海字集
#define RUSSIAN_CHARSET 204 // 1251俄文 (斯拉夫语系)
#define THAI_CHARSET 222 // 874泰文
#define EASTEUROPE_CHARSET 238 // 1250 Latin 2 (中欧语系)
#define OEM_CHARSET 255 // 地区自订

    为什么Windows对同一个字符集有两个不同的ID:字符集ID和代码页ID?这只是Windows中的一种怪癖。注意,字符集ID只需要1字节的储存空间,这是LOGFONT结构中字符集字段的大小(试回忆Windows 1.0时期,内存和储存空间有限,每个字节都必须斤斤计较)。注意,有许多不同的MS-DOS代码页用于其它国家,但只有一种字符集ID-OEM_CHARSET-用于MS-DOS字符集。

    您还会注意到,这些字符集的值与STOKFONT程序最上头的「CharSet」值一致。在美国英语版Windows中,我们看到常备字体的字符集ID是0 (ANSI_CHARSET)和255(OEM_CHARSET)。希腊版Windows中的是161(GREEK_CHARSET),在俄语版中的是204(RUSSIAN_CHARSET),在日语版中是128(SHIFTJIS_CHARSET)。

    在上面的代码中,DBCS代表双字节字符集,用于远东版的Windows。其它版的Windows不支持DBCS字体,因此不能使用那些字符集ID。

    CreateFont传回HFONT值-逻辑字体的句柄。您可以使用SelectObject将此字体选进设备内容。实际上,您必须呼叫DeleteObject来删除您建立的所有逻辑字体。

    大字体解决方案的其它部分是WM_INPUTLANGCHANGE消息。一旦您使用桌面下端的弹出式菜单来改变键盘布局,Windows都会向您的窗口消息处理程序发送WM_INPUTLANGCHANGE消息。wParam消息参数是新键盘布局的字符集ID。

程序6-4所示的KEYVIEW2程序实作了键盘布局改变时改变字体的逻辑。

程序6-4 KEYVIEW2

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

.386
.Model Flat, StdCall
Option Casemap :None

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

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
IncludeLib msvcrt.lib
include macro.asm
	
	WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
	WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.DATA
	szAppName   db "KeyView2",0
	
   szTop 		DB "Message         Key       Char    Repeat  Scan Ext   ALT Prev Tran",0
   szUnd 		DB "_______         ___       ____    ______  ____ ___   ___ ____ ____",0
        
	szFormat0	DB "%-13s %3d %-15s",0
	szFormat1	db "%-13s            0x%04X%1s%c",0
        
   szYes  		DB "  Yes",0
	szNo   		DB "  No ",0
	szDown 		DB " Down",0
	szUp   		DB	" Up  ",0

	szMessage 	DB	"WM_KEYDOWN    ",0,
						"WM_KEYUP      ",0,
						"WM_CHAR       ",0,
						"WM_DEADCHAR   ",0,
						"WM_SYSKEYDOWN ",0,
						"WM_SYSKEYUP   ",0,
						"WM_SYSCHAR    ",0,
						"WM_SYSDEADCHAR",0
						
   dwCharSet 	DD DEFAULT_CHARSET 
.DATA?
   cLinesMax	DD	?
   cLines 		DD ?
   pmsg 			DD ?
   rectScroll	RECT  <>           
	hInstance	dd ?
   cxClientMax	dd	?
   cyClientMax	dd	?
   cxClient		dd	?
   cyClient		dd	?
   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("Keyboard Message Viewer #2"), ;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,message:DWORD,wParam :DWORD,lParam :DWORD
	LOCAL hdc 				:HDC
   LOCAL i, iType			:DWORD
	LOCAL ps  				:PAINTSTRUCT 
	LOCAL szBuffer[256]	:BYTE        
	LOCAL szTmpbuf[128]	:BYTE 
	LOCAL szKeyName[32]	:BYTE	
	LOCAL tm					:TEXTMETRIC

	.if	  (message==WM_INPUTLANGCHANGE)
        
        		mov	eax,wParam
            mov	dwCharSet,eax

				jmp		@f
	.elseif (message==WM_CREATE) || (message==WM_DISPLAYCHANGE)
		@@:
		
		; Get maximum size of client area
      invoke	GetSystemMetrics,SM_CXMAXIMIZED
        mov		cxClientMax,eax
      invoke	GetSystemMetrics,SM_CYMAXIMIZED
        mov		cyClientMax,eax      
		
		; Get character size for fixed-pitch font
		invoke	GetDC,hwnd
		mov		hdc,eax

		invoke	CreateFont ,0, 0, 0, 0, 0, 0, 0, 0,dwCharSet, 0, 0, 0, FIXED_PITCH, NULL
		invoke	SelectObject,hdc,eax
		        
      ;GetTextMetrics (hdc, &tm) 
      invoke	GetTextMetrics,hdc, addr tm

	   ;cxChar = tm.tmAveCharWidth ;
   	;cyChar = tm.tmHeight ;
      mov		eax,tm.tmAveCharWidth
      mov		cxChar,eax
      
      mov		eax,tm.tmHeight
      mov		cyChar,eax
      
      invoke	GetStockObject,SYSTEM_FONT
      invoke	SelectObject,hdc, eax
      invoke DeleteObject,eax


      invoke   ReleaseDC,hwnd, hdc
      
      ;Allocate memory for display lines
      .if		(pmsg!=NULL)
	      invoke	free,addr pmsg
      .endif
      xor		edx,edx
      mov		eax,cyClientMax
      mov		ecx,cyChar
      div		ecx
      mov		cLinesMax,eax
      mov		ecx,sizeof MSG
      mul		ecx
      invoke	malloc,eax
      mov		pmsg,eax
      mov		cLines,0
      
      jmp		@f		;原文是case语句,这个地方处理很特殊
		.elseif message == WM_SIZE
		@@:
		
			.if	(message == WM_SIZE)
      		mov		eax,lParam			;cxClient = LOWORD (lParam)
      		and		eax,0FFFFh
      		mov		cxClient,eax
      		
      		mov		eax,lParam
      		shr		eax,16
      		mov		cyClient,eax		;cyClient = HIWORD (lParam)
      	.endif
      	
            ;Calculate scrolling rectangle
				xor		eax,eax        
            mov		rectScroll.left,eax
        
        		mov		eax,cxClient
            mov		rectScroll.right,eax
        
        		mov		eax,cyChar
            mov		rectScroll.top,eax 
        
        		xor		edx,edx
        		mov		eax,cyClient
        		mov		ecx,cyChar
        		div		ecx
        		mov		ecx,cyChar
        		mul		ecx
        		mov		rectScroll.bottom,eax
        
            invoke	InvalidateRect,hwnd, NULL, TRUE
        
   		ret
   			
   	.elseif ((message==WM_KEYDOWN) || \
   				(message==WM_KEYUP) || \
   			   (message==WM_CHAR) || \
   				(message==WM_DEADCHAR) || \
   				(message==WM_SYSKEYDOWN) || \
   				(message==WM_SYSKEYUP) || \
   				(message==WM_SYSCHAR) || \
   				(message==WM_SYSDEADCHAR))  	   	

        ;Rearrange storage array
        mov		ecx,cLinesMax
        dec		ecx
        mov		edi,pmsg
        mov		eax,ecx
        mov		ebx,sizeof(MSG)
        mul		ebx
        add		edi,eax
        @@:
        mov		eax,[edi-28]		;这个地方写的复杂,功能很简单就是把前面保存的搬到后面
        mov		[edi],eax			;需要注意的是 sizeof(MSG)=28bytes
        mov		eax,[edi-24]
        mov		[edi+4],eax
        mov		eax,[edi-20]
        mov		[edi+8],eax
        mov		eax,[edi-16]
        mov		[edi+12],eax
        mov		eax,[edi-12]
        mov		[edi+16],eax
        mov		eax,[edi-8]
        mov		[edi+20],eax
        mov		eax,[edi-4]
        mov		[edi+24],eax
        sub		edi,sizeof(MSG)
		  loop	@b
		 
       ;Store new message
        mov		edi,pmsg
        assume	edi:PTR MSG
        mov		eax,hwnd
        mov		[edi].hwnd,eax
        mov		eax,message
        mov		[edi].message,eax  
        mov		eax,wParam
        mov		[edi].wParam,eax
        mov		eax,lParam
        mov		[edi].lParam,eax
        assume edi:nothing
        
        mov		eax,cLines
        inc		eax
        .if		(eax>cLinesMax)
        			mov		eax,cLinesMax
        .endif
        mov		cLines,eax
        
        mov		ebx,cyChar
        neg		ebx		;-cyChar
        ; Scroll up the display
        invoke ScrollWindow,hwnd, 0, ebx, addr rectScroll, addr rectScroll
        
        jmp	UseDefWindowProc ; i.e., call DefWindowProc so Sys messages work
        
		.elseif message == WM_PAINT
		invoke	BeginPaint,hwnd,addr ps
		mov		hdc,eax
		invoke	CreateFont,0, 0, 0, 0, 0, 0, 0, 0,dwCharSet, 0, 0, 0, FIXED_PITCH, NULL
		invoke	SelectObject,hdc,eax
        
		invoke	SetBkMode,hdc, TRANSPARENT
		
		invoke	lstrlen,addr szTop
		mov		ebx,eax
		invoke	TextOut,hdc, 0, 0, addr szTop, ebx
		
		invoke	lstrlen,addr szUnd
		mov		ebx,eax		
      invoke	TextOut,hdc, 0, 0, addr szUnd, ebx
      
		mov		esi,pmsg
      assume	esi:PTR MSG
      mov		i,0
iLoop:      
      xor		edx,edx
      mov		eax,cyClient
      mov		ecx,cyChar
      div		ecx
      dec		eax
      .if		(cLines

    注意,键盘输入语言改变后,KEYVIEW2就清除画面并重新分配储存空间。这样做有两个原因:第一,因为KEYVIEW2并不是某种字体专用的,当输入语言改变时字体文字的大小也会改变。程序需要根据新字符大小重新计算某些变量。第二,在接收每个字符消息时,KEYVIEW2并不有效地保留字符集ID。因此,如果键盘输入语言改变了,而且KEYVIEW2需要重画显示区域时,所有的字符将用新字体显示。

插入符号(不是光标)

    当您往程序中输入文字时,通常有一个底线、竖条或者方框来指示输入的下一个字符将出现在屏幕上的位置。这个标志通常称为「光标」,但是在Windows下写程序,您必须改变这个习惯。在Windows中,它称为「插入符号」。「光标」是指表示鼠标位置的那个位图图像。

插入符号函数

主要有五个插入符号函数:

    另外还有取得插入符号目前位置(GetCaretPos)和取得以及设定插入符号闪烁时间(GetCaretBlinkTime和SetCaretBlinkTime)的函数。

    在Windows中,插入符号定义为水平线、与字符大小相同的方框,或者与字符同高的竖线。如果使用调和字体,例如Windows内定的系统字体,则推荐使用竖线插入符号。因为调和字体中的字符没有固定大小,水平线或方框不能设定为字符的大小。

    如果程序中需要插入符号,那么您不应该简单地在窗口消息处理程序的WM_CREATE消息处理期间建立它,然后在WM_DESTROY消息处理期间撤消。其原因显而易见:一个消息队列只能支持一个插入符号。因此,如果您的程序有多个窗口,那么各个窗口必须有效地共享相同的插入符号。

    其实,它并不像听起来那么多限制。您再想想就会发现,只有在窗口有输入焦点时,窗口内显示插入符号才有意义。事实上,闪烁的插入符号只是一种视觉提示:您可以在程序中输入文字。因为任何时候都只有一个窗口拥有输入焦点,所以多个窗口同时都有闪烁的插入符号是没有意义的。

    通过处理WM_SETFOCUS和WM_KILLFOCUS消息,程序就可以确定它是否有输入焦点。正如名称所暗示的,窗口消息处理程序在有输入焦点的时候接收到WM_SETFOCUS消息,失去输入焦点的时候接收到WM_KILLFOCUS消息。这些消息成对出现:窗口消息处理程序在接收到WM_KILLFOCUS消息之前将一直接收到WM_SETFOCUS消息,并且在窗口打开期间,此窗口总是接收到相同数量的WM_SETFOCUS和WM_KILLFOCUS消息。

    使用插入符号的主要规则很简单:窗口消息处理程序在WM_SETFOCUS消息处理期间呼叫CreateCaret,在WM_KILLFOCUS消息处理期间呼叫DestroyCaret。

    这里还有几条其它规则:插入符号刚建立时是隐蔽的。如果想使插入符号可见,那么您在呼叫CreateCaret之后,窗口消息处理程序还必须呼叫ShowCaret。另外,当窗口消息处理程序处理一条非WM_PAINT消息而且希望在窗口内绘制某些东西时,它必须呼叫HideCaret隐藏插入符号。在绘制完毕后,再呼叫ShowCaret显示插入符号。HideCaret的影响具有累积效果,如果多次呼叫HideCaret而不呼叫ShowCaret,那么只有呼叫ShowCaret相同次数时,才能看到插入符号。

TYPER程序

    程序6-5所示的TYPER程序使用了本章讨论的所有内容,您可以认为TYPER是一个相当简单的文字编辑器。在窗口中,您可以输入字符,用光标移动键(也可以称为插入符号移动键)来移动光标(I型标),按下Escape键清除窗口的内容等。缩放窗口、改变键盘输入语言时都会清除窗口的内容。本程序没有卷动,没有文字寻找和定位功能,不能储存文件,没有拼写检查,但它确实是写作一个文字编辑器的开始。

    (Z.t: 我对这个程序做了一些化简,它只能处理8位的ASCII,不能接收汉字。)

程序6-5 TYPER

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

.386
.Model Flat, StdCall
Option Casemap :None

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

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
IncludeLib msvcrt.lib
include macro.asm
	
	WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
	WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.DATA
	szAppName   db "TYPER",0
	
   dwCharSet 	dd DEFAULT_CHARSET 
        
   pBuffer 		dd	NULL

.DATA?
   cxChar		dd ?
   cyChar		dd ?
   cxClient		dd ? 
   cyClient		dd ? 
   cxBuffer		dd ? 
   cyBuffer		dd ?
   xCaret		dd ? 
   yCaret 		dd	?
   hInstance	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("Typing Program"), ;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,message:DWORD,wParam :DWORD,lParam :DWORD

	LOCAL hdc 				:HDC	
	LOCAL x,y,i				:DWORD
	LOCAL ps  				:PAINTSTRUCT 	
	LOCAL tm					:TEXTMETRIC
	LOCAL tmpx,tmpy		:DWORD
	LOCAL szBuffer[10]	:BYTE
	
	.if	  (message==WM_INPUTLANGCHANGE)
        
        		mov	eax,wParam
            mov	dwCharSet,eax

				jmp		@f
	.elseif (message==WM_CREATE) 
		@@:
	
		; Get character size for fixed-pitch font
		invoke	GetDC,hwnd
		mov		hdc,eax

		invoke	CreateFont ,0, 0, 0, 0, 0, 0, 0, 0,dwCharSet, 0, 0, 0, FIXED_PITCH, NULL
		invoke	SelectObject,hdc,eax
		        
      ;GetTextMetrics (hdc, &tm) 
      invoke	GetTextMetrics,hdc, addr tm

	   ;cxChar = tm.tmAveCharWidth ;
   	;cyChar = tm.tmHeight ;
      mov		eax,tm.tmAveCharWidth
      mov		cxChar,eax
      
      mov		eax,tm.tmHeight
      mov		cyChar,eax
      
      invoke	GetStockObject,SYSTEM_FONT
      invoke	SelectObject,hdc, eax
      invoke   DeleteObject,eax

      invoke   ReleaseDC,hwnd, hdc
      
      
      jmp		@f		;原文是case语句,这个地方处理很特殊
		.elseif message == WM_SIZE
		@@:

			.if	(message == WM_SIZE)
      		mov		eax,lParam			;cxClient = LOWORD (lParam)
      		and		eax,0FFFFh
      		mov		cxClient,eax
      		
      		mov		eax,lParam
      		shr		eax,16
      		mov		cyClient,eax		;cyClient = HIWORD (lParam)
      	.endif
      	
            ;Calculate scrolling rectangle
        		xor		edx,edx
        		mov		eax,cxClient
        		mov		ecx,cxChar
        		div		ecx
				.if		(eax<1)
				mov	eax,1
				.endif
				mov		cxBuffer,eax
				
        		xor		edx,edx
        		mov		eax,cyClient
        		mov		ecx,cyChar
        		div		ecx
				.if		(eax<1)
				mov	eax,1
				.endif
				mov		cyBuffer,eax				
				
      ;Allocate memory for display lines
      .if		(pBuffer !=NULL)
	      invoke	free,addr pBuffer 
      .endif
      xor		edx,edx
      mov		eax,cxBuffer 
      mov		ecx,cyBuffer 
      mul		ecx
      mov		ecx,1	;	sizeof TCHAR
      mul		ecx
      push		eax
      invoke	malloc,eax
      mov		pBuffer,eax
      pop		eax
      invoke	RtlZeroMemory,pBuffer,eax
			
		mov	esi,pBuffer
		xor	eax,eax
		mov	y,eax
loopy1:
		mov	x,0
loopx1:
		mov	byte ptr [esi],' '
		inc	esi
		inc	x
		mov	eax,x
		cmp	eax,cxBuffer
		jb		loopx1
		
		inc	y
		mov	eax,y
		cmp	eax,cyBuffer
		ja		loopy1
      
      ;set caret to upper left corner
		xor	eax,eax
      mov	xCaret,eax
      mov	yCaret,eax

		invoke	GetFocus
		
      .if (hwnd == eax)
      		mov		eax,xCaret
      		mov		ecx,cxChar
      		mul		ecx
      		mov		tmpx,eax
      		
      		mov		eax,yCaret
      		mov		ecx,cyChar
      		mul		ecx
      		mov		tmpy,eax
            invoke	SetCaretPos ,tmpx,tmpy
		.endif
		
      invoke	InvalidateRect,hwnd, NULL, TRUE
        
   		ret

    .elseif	(message==WM_SETFOCUS)
        
        
            invoke CreateCaret,hwnd, NULL, cxChar, cyChar

      		mov		eax,xCaret
      		mov		ecx,cxChar
      		mul		ecx
      		mov		tmpx,eax
      		
      		mov		eax,yCaret
      		mov		ecx,cyChar
      		mul		ecx
      		mov		tmpy,eax
      		
            invoke	SetCaretPos,tmpx,tmpy
        
           invoke	ShowCaret,hwnd
        
            ret

		.elseif	(message== WM_KILLFOCUS)
        
            invoke	HideCaret,hwnd
        
            invoke	DestroyCaret 
        
            ret
   			
   	.elseif (message==WM_KEYDOWN) 
  			mov		eax,wParam
			.if		(eax==VK_HOME)
						mov	xCaret,0
			.elseif  (eax==VK_END)
						mov	eax,cxBuffer
						dec	eax
						mov	xCaret,eax
			.elseif  (eax==VK_PRIOR)
                  mov	yCaret ,0 
			.elseif  (eax==VK_NEXT)
						mov	eax,cyBuffer
						dec	eax
						mov	yCaret,eax				

			.elseif  (eax==VK_LEFT)
						mov	eax,xCaret
						dec	eax
						cmp	eax,0
						jg		@f
						xor	eax,eax
					@@:	
						mov	xCaret,eax

			.elseif  (eax==VK_RIGHT)
						mov	eax,xCaret
						inc	eax
						mov	ebx,cxBuffer
						dec	ebx
						cmp	eax,ebx
						jl		@f
						mov	eax,ebx
					@@:	
						mov	xCaret,eax        

			.elseif  (eax==VK_UP)
						mov	eax,yCaret
						dec	eax

						cmp	eax,0
						jg		@f
						xor	eax,eax
					@@:	
						mov	yCaret,eax  	
						
			.elseif  (eax==VK_DOWN)
						mov	eax,yCaret
						inc	eax
						mov	ebx,cyBuffer
						dec	ebx
						cmp	eax,ebx
						jl		@f
						mov	eax,ebx
					@@:	
						mov	yCaret,eax              
        
			.elseif  (eax==VK_DELETE)
				mov	esi,pBuffer
				
				mov	eax,yCaret
				mov	ecx,cxBuffer
				mul	ecx
				add	eax,xCaret
				mov	ecx,1		;sizeof TCHAR
				mul	ecx
				
				add	esi,eax		;esi=BUFFER
				
				mov	eax,xCaret
				mov	x,eax
				@@:				
				mov	al,[esi+1]
				mov	[esi],al
				mov	ebx,cxBuffer
				dec	ebx
				inc	x
				inc	esi
				cmp	x,ebx
				jb		@b

				mov	esi,pBuffer
				mov	eax,yCaret
				mov	ecx,cxBuffer
				mul	ecx
				add	eax,cxBuffer
				dec	eax
				add	esi,eax
				mov	BYTE ptr [esi],' '
			
			invoke	HideCaret,hwnd
         invoke	GetDC,hwnd
         mov		hdc,eax

		invoke	CreateFont,0, 0, 0, 0, 0, 0, 0, 0,dwCharSet, 0, 0, 0, FIXED_PITCH, NULL
		invoke	SelectObject,hdc,eax
		
		mov		eax,xCaret
		mov		ecx,cxChar
		mul		ecx
		mov		tmpx,eax

		mov		eax,yCaret
		mov		ecx,cyChar
		mul		ecx
		mov		tmpy,eax		
		
		mov		esi,pBuffer
		mov		eax,yCaret
		mov		ecx,cxBuffer
		mul		ecx
		add		eax,xCaret
		add		esi,eax
		
		mov		ebx,cxBuffer
		sub		ebx,xCaret
		invoke	TextOut ,hdc, tmpx,tmpy,esi,ebx

				
		invoke	GetStockObject,SYSTEM_FONT
		invoke	SelectObject,hdc, eax
      invoke	DeleteObject,eax
        
        
       invoke   ReleaseDC,hwnd, hdc
        
       invoke	ShowCaret,hwnd
       .endif 
		mov		eax,xCaret
		mov		ecx,cxChar
		mul		ecx
		mov		tmpx,eax

		mov		eax,yCaret
		mov		ecx,cyChar
		mul		ecx
		mov		tmpy,eax
		 invoke  SetCaretPos,tmpx,tmpy
			

        ret
      .elseif	message == WM_CHAR
       mov	i,0
loopi:
			 mov	eax,wParam
			 and	eax,0ffh
		;invoke	wsprintf,addr szBuffer,CTXT("%d"),eax
		;invoke	MessageBox,hwnd,addr szBuffer,NULL,NULL 			 		
	 
			 				 
			 .if (eax == 8h)		 ;backspace

			 		mov	eax,xCaret
			 		cmp	eax,0
			 		jle		@f
				 		dec	xCaret
				 		invoke	SendMessage,hwnd, WM_KEYDOWN, VK_DELETE, 1
			 		@@:
			 .elseif (eax == 9h)	;tab
			     @@:
			 		invoke	SendMessage,hwnd, WM_CHAR, ' ', 1
			 		mov		eax,xCaret
			 		and		eax,111b
			 		cmp		eax,0
			 		jnz		@b
		 		
			 .elseif (eax == 0dh)		;line feed
					inc		yCaret
					mov		eax,yCaret
					.if		eax==cyBuffer
								mov	yCaret,0
					.endif	
				
			 .elseif (eax == 0Ah )		 ;Shift+Enter or Ctrl + Enter
			 	mov	xCaret,0
			 	inc	yCaret
			 	mov	eax,yCaret 
			 	.if	eax==cyBuffer
			 			mov	yCaret,0
			 	.endif	
		 	
			 .elseif (eax == 1Bh)		 
								mov	esi,pBuffer
								mov	y,0
						loopy2:
								mov	x,0
						loopx2:
								mov	BYTE ptr [esi],' '
								inc	esi
								inc	x
								mov	eax,x
								cmp	eax,cxBuffer
								jb		loopx2
								
								inc	y
								mov	eax,y
								cmp	eax,cyBuffer
								ja		loopy2
								
								xor	eax,eax
								mov   xCaret ,eax
	                     mov	yCaret ,eax
	                     invoke  InvalidateRect,hwnd, NULL, FALSE
	                     
			 .else	 
			 				 mov		esi,pBuffer
			 				 mov		eax,yCaret
			 				 mov		ecx,cxBuffer
			 				 mul		ecx
			 				 add		eax,xCaret
			 				 add		esi,eax
			 				 mov		eax,wParam
			 				 mov		[esi],al
	                   
	                   invoke	HideCaret,hwnd        
	                   invoke	GetDC ,hwnd
	                   mov	hdc,eax
	
			invoke	CreateFont ,0, 0, 0, 0, 0, 0, 0, 0,dwCharSet, 0, 0, 0, FIXED_PITCH, NULL
			invoke	SelectObject,hdc,eax

		mov		eax,xCaret
		mov		ecx,cxChar
		mul		ecx
		mov		tmpx,eax

		mov		eax,yCaret
		mov		ecx,cyChar
		mul		ecx
		mov		tmpy,eax		
		mov		esi,pBuffer
		mov		eax,yCaret
		mov		ecx,cxBuffer
		mul		ecx
		add		eax,xCaret
		add		esi,eax
		
		invoke	TextOut ,hdc, tmpx,tmpy,esi,1
		
	                           
			invoke	GetStockObject,SYSTEM_FONT
			invoke	SelectObject,hdc, eax
	      invoke	DeleteObject,eax                           
	        
	      invoke	ReleaseDC,hwnd, hdc
			invoke	ShowCaret,hwnd
	
			inc	xCaret
			mov	eax,xCaret
			.if	eax==cxBuffer
					mov	xCaret,0
					inc	yCaret
					mov	eax,yCaret
					.if	eax==cyBuffer
							mov	yCaret,0
					.endif	
			.endif	
			 .endif

		inc	i
		mov	eax,lParam
		and	eax,0FFFFh
		cmp	i,eax
		jb		loopi

		mov	eax,xCaret
		mov	ecx,cxChar
		mul	ecx
		mov	tmpx,eax
		
		mov	eax,yCaret
		mov	ecx,cyChar
		mul	ecx
		mov	tmpy	,eax	
      invoke	SetCaretPos,tmpx,tmpy

		ret

		.elseif message == WM_PAINT
		invoke	BeginPaint,hwnd,addr ps
		mov		hdc,eax
		invoke	CreateFont,0, 0, 0, 0, 0, 0, 0, 0,dwCharSet, 0, 0, 0, FIXED_PITCH, NULL
		invoke	SelectObject,hdc,eax
        
		mov		y,0
@@:	
		mov		esi,pBuffer
		mov		eax,y
		mov		ecx,cxBuffer
		mul		ecx
		add		esi,eax
		mov		eax,y
		mov		ecx,cyChar
		mul		ecx
		mov		tmpy,eax
		invoke	TextOut ,hdc, 0, tmpy, esi, cxBuffer
		inc		y
		mov		eax,y
		cmp		eax,cyBuffer
		jb			@b

		invoke	GetStockObject,SYSTEM_FONT
		invoke	SelectObject,hdc, eax
      invoke	DeleteObject,eax
        

     	invoke	EndPaint,hwnd,addr ps
		ret	  	
        
	.elseif message == WM_DESTROY
		invoke PostQuitMessage,NULL		
	.endif
UseDefWindowProc:
	invoke DefWindowProc,hwnd, message, wParam, lParam
	ret
WndProc endp

END START

        

                9.JPG (13756 字节)

    为了简单起见,TYPER程序使用一种等宽字体,因为编写处理调和字体的文字编辑器要困难得多。程序在好几个地方取得设备内容:在WM_CREATE消息处理期间,在WM_KEYDOWN消息处理期间,在WM_CHAR消息处理期间以及在WM_PAINT消息处理期间,每次都通过GetStockObject和SelectObject呼叫来选择等宽字体。

    在WM_SIZE消息处理期间,TYPER计算窗口的字符宽度和高度并把值保存在cxBuffer和cyBuffer变量中,然后使用malloc分配缓冲区以保存在窗口内输入的所有字符。注意,缓冲区的字节大小取决于cxBuffer、cyBuffer和sizeof(TCHAR),它可以是1或2,这依赖于程序是以8位的字符处理还是以Unicode方式编译的。

    xCaret和yCaret变量保存插入符号位置。在WM_SETFOCUS消息处理期间,TYPER呼叫CreateCaret来建立与字符有相同宽度和高度的插入符号,呼叫SetCaretPos来设定插入符号的位置,呼叫ShowCaret使插入符号可见。在WM_KILLFOCUS消息处理期间,TYPER呼叫HideCaret和DestroyCaret。

    对WM_KEYDOWN的处理大多要涉及光标移动键。Home和End把插入符号送至一行的开始和末尾处,Page Up和Page Down把插入符号送至窗口的顶端和底部,箭头的用法不变。对Delete键,TYPER将缓冲区中从插入符号之后的那个位置开始到行尾的所有内容向前移动,并在行尾显示空格。

    WM_CHAR处理Backspace、Tab、Linefeed(Ctrl-Enter)、Enter、Escape和字符键。注意,在处理WM_CHAR消息时(假设使用者输入的每个字符都非常重要),我使用了lParam中的重复计数;而在处理WM_KEYDOWN消息时却不这么作(避免有害的重复卷动)。对Backspace和Tab的处理由于使用了SendMessage函数而得到简化,Backspace与Delete做法相仿,而Tab则如同输入了若干个空格。

    前面我已经提到过,在非WM_PAINT消息处理期间,如果要在窗口中绘制内容,则应该隐蔽光标。TYPER为Delete键处理WM_KEYDOWN消息和为字符键处理WM_CHAR消息时即是如此。在这两种情况下,TYPER改变缓冲区中的内容,然后在窗口中绘制一个或者多个新字符。

    虽然TYPER使用了与KEYVIEW2相同的做法以在字符集之间切换(就像使用者切换键盘布局一样),但对于远东版的Windows,它还是不能正常工作。TYPER不允许使用两倍宽度的字符。此问题将在后面讨论,那时我们将详细讨论字体与文字输出。



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