📄 见招拆招《windows程序设计》(三) .txt
字号:
PAINTSTRUCT中的rcPaint矩形不仅是无效矩形,它还是一个「剪取」矩形。这意味着Windows将绘图操作限制在剪取矩形内(更确切地说,如果无效矩形区域不为矩形,则Windows将绘图操作限制在这个区域内)。
在处理WM_PAINT消息时,为了在更新的矩形外绘图,可以使用如下呼叫:
invoke InvalidateRect, hWnd,NULL,FALSE
该呼叫在BeginPaint呼叫之前进行,它使整个显示区域变为无效,并擦除背景。但是,如果最后一个参数等于FALSE,则不擦除背景,原有的东西将保留在原处。
通常这是Windows程序在无论何时收到WM_PAINT消息而不考虑rcPaint结构的情况下简单地重画整个显示区域最方便的方法。例如,如果在显示区域的显示输出中包括了一个圆,但是只有圆的一部分落到了无效矩形中,它就使仅绘制圆的无效部分变得没有意义。这需要画整个圆。在您使用从BeginPaint传回的设备内容句柄时,Windows不会绘制rcPaint矩形外的任何部分。
在前面的HelloWin中,我们并不关心处理WM_PAINT消息时的无效矩形。如果文字显示区域恰巧在无效矩形内,则由DrawText恢复之。否则,在处理DrawText呼叫的某个时刻,Windows会确定它无须向显示器上输出。不过,这一决定需要时间。关心程序性能和速度的程序写作者希望在处理WM_PAINT期间使用无效矩形范围,以避免不必要的GDI呼叫。如果绘制时需要存取例如位图这样的磁盘文件,则这就显得尤其重要。
取得设备内容句柄:方法二
虽然最好是在处理WM_PAINT消息处理期间更新整个显示区域,但是您也会发现在处理非WM_PAINT消息处理期间绘制显示区域的某个部分也是非常有用的。或者您需要将设备内容句柄用于其它目的,如取得设备内容的信息。
要得到窗口显示区域的设备内容句柄,可以呼叫GetDC来取得句柄,在使用完后呼叫ReleaseDC:
invoke GetDC,hwnd
mov hdc,eax
使用GDI函数
ReleaseDC (hwnd, hdc)
Invoke ReleaseDC,hwn,hdc
与BeginPaint和EndPaint一样,GetDC和ReleaseDC函数必须成对地使用。如果在处理某消息时呼叫GetDC,则必须在退出窗口消息处理程序之前呼叫ReleaseDC。不要在一个消息中呼叫GetDC却在另一个消息呼叫ReleaseDC。
与从BeginPaint传回设备内容句柄不同,GetDC传回的设备内容句柄具有一个剪取矩形,它等于整个显示区域。可以在显示区域的某一部分绘图,而不只是在无效矩形上绘图(如果确实存在无效矩形)。与BeginPaint不同,GetDC不会使任何无效区域变为有效。如果需要使整个显示区域有效,可以呼叫Invoke ValidateRect,hwnd,NULL
一般可以呼叫GetDC和ReleaseDC来对键盘消息(如在字处理程序中)和鼠标消息(如在画图程序中)作出反应。此时,程序可以立刻根据使用者的键盘或鼠标输入来更新显示区域,而不需要考虑为了窗口的无效区域而使用WM_PAINT消息。不过,一旦确实收到了WM_PAINT消息,程序就必须要收集足够的信息后才能更新显示。
与GetDC相似的函数是GetWindowDC。GetDC传回用于写入窗口显示区域的设备内容句柄,而GetWindowDC传回写入整个窗口的设备内容句柄。例如,您的程序可以使用从GetWindowDC传回的设备内容句柄在窗口的标题列上写入文字。然而,程序同样也应该处理WM_NCPAINT (「非显示区域绘制」)消息。
TextOut:细节
TextOut是用于显示文字的最常用的GDI函数。语法是:
TextOut (hdc, x, y, psText, iLength) ;
以下将详细地讨论这个函数。
第一个参数是设备内容句柄,它既可以是GetDC的传回值,也可以是在处理WM_PAINT消息时BeginPaint的传回值。
设备内容的属性控制了被显示的字符串的特征。例如,设备内容中有一个属性指定文字颜色,内定颜色为黑色;内定设备内容还定义了白色的背景。在程序向显示器输出文字时,Windows使用这个背景色来填入字符周围的矩形空间(称为「字符框」)。
该文字背景色与定义窗口类别时设置的背景并不相同。窗口类别中的背景是一个画刷,它是一种纯色或者非纯色组成的画刷,Windows用它来擦除显示区域,它不是设备内容结构的一部分。在定义窗口类别结构时,大多数Windows应用程序使用WHITE_BRUSH,以便内定设备内容中的内定文字背景颜色与Windows用以擦除显示区域背景的画刷颜色相同。
psText参数是指向字符串的指针,iLength是字符串中字符的个数。如果psText指向Unicode字符串,则字符串中的字节数就是iLength值的两倍。字符串中不能包含任何ASCII控制字符(如回车、换行、制表或退格),Windows会将这些控制字符显示为实心块。Text0ut不识别作为字符串结束标志的内容为零的字节(对于Unicode,是一个短整数型态的0),而需要由nLength参数指明长度。
TextOut中的x和y定义显示区域内字符串的开始位置,x是水平位置,y是垂直位置。字符串中第一个字符的左上角位于坐标点(x,y)。在内定的设备内容中,原点(x和y均为0的点)是显示区域的左上角。如果在TextOut中将x和y设为0,则将从显示区域左上角开始输出字符串。
当您阅读GDI绘图函数(例如TextOut)的文件时,就会发现传递给函数的坐标常常被称为「逻辑坐标」。在后面我们会详细地解释这种情况。现在请注意,Windows有许多「坐标映像方式」,它们用来控制GDI函数指定的逻辑坐标转换为显示器的实际图素坐标的方式。映像方式在设备内容中定义,内定映像方式是MM_TEXT(使用WINGDI.H中定义的标识符)。在MM_TEXT映像方式下,逻辑单位与实际单位相同,都是像素;x的值从左向右递增,y的值从上向下递增(参看图4-2)。MM_TEXT坐标系与Windows在PAINTSTRUCT结构中定义无效矩形时使用的坐标系相同,这为我们带来了很多方便(但是,其它映像方式并非如此)。
图4-2 MM_TEXT映像方式下的x坐标和y坐标
设备内容也定义了一个剪裁区域。您已经看到,对于从GetDC取得的设备内容句柄,内定剪裁区域是整个显示区域;而对于从BeginPaint取得的设备内容句柄,则为无效区域。Windows不会在剪裁区域之外的任何位置显示字符串。如果一个字符有一部分在剪裁区域外,则Windows将只显示此区域内的那部分。要想将输出写到窗口的显示区域之外不是那么容易的,所以不用担心会无意间出现这种事情。
系统字体
设备内容还定义了在您呼叫TextOut显示文字时Windows使用的字体。内定字体为「系统字体」,或用Windows表头文件中的标识符,即SYSTEM_FONT。系统字体是Windows用来在标题列、菜单和对话框中显示字符串的内定字体。
在Windows的早期版本中,系统字体是等宽(fixed-pitch)字体,这意味着所有字符均具有同样的宽度,非常类似于打字机。然而,从Windows 3.0开始,系统字体成为一种变宽(variable-pitch)字体,这意味着不同的字符具有不同的大小,比如,「W」要比「i」宽。变宽字体比等宽字体好读,这已经是公认的事实。不过,可以想见,这一转变使很多原来的Windows程序代码不再适用,从而要求程序写作者学习一些使用字体的新技术。(题外话:英文报纸的编辑比中文报纸编辑的痛苦得多,他们每次都要精确的调整标题长度,而中文都是方块字很容易计算标题的长度)
系统字体是一种「点阵字体」,这意味着字符被定义为像素块(在后面,将讨论TrueType字体,它是由轮廓定义的)。至于确切的大小,系统字体的字符大小取决于显示器的大小。系统字体设计为至少能在显示器上显示25行80列文字。
字符大小
要用TextOut显示多行文字,就必须确定字体的字符大小,可以根据字符的高度来定位字符的后续行,以及根据字符的宽度来定位字符的后续列。
系统字体的字符高度和平均宽度是多少?这个问题取决于视讯显示器的图素大小。Windows需要的最小显示大小是640×480,但是许多使用者更喜欢800×600或1024×768的显示大小。另外,对于这些较大的显示尺寸,Windows允许使用者选择不同大小的系统字体。
程序可以呼叫GetSystemMetrics函数以取使用者接口上各类视觉组件大小的信息,呼叫GetTextMetrics取得字体大小。GetTextMetrics传回设备内容中目前选取的字体信息,因此它需要设备内容句柄。Windows将文字大小的不同值复制到在Windows.h中定义的TEXTMETRIC型态的结构中(实际上在windows.inc和gdi32.inc都定义了)。TEXTMETRIC结构有20个字段,我们只使用前七个:
TEXTMETRICA STRUCT
tmHeight DWORD ?
tmAscent DWORD ?
tmDescent DWORD ?
tmInternalLeading DWORD ?
tmExternalLeading DWORD ?
tmAveCharWidth DWORD ?
tmMaxCharWidth DWORD ?
tmWeight DWORD ?
tmOverhang DWORD ?
tmDigitizedAspectX DWORD ?
tmDigitizedAspectY DWORD ?
tmFirstChar BYTE ?
tmLastChar BYTE ?
tmDefaultChar BYTE ?
tmBreakChar BYTE ?
tmItalic BYTE ?
tmUnderlined BYTE ?
tmStruckOut BYTE ?
tmPitchAndFamily BYTE ?
tmCharSet BYTE ?
TEXTMETRICA ENDS
TEXTMETRIC equ <TEXTMETRICA>
这些字段值的单位取决于选定的设备内容映像方式。在内定设备内容下,映像方式是MM_TEXT,因此值的大小是以图素为单位。
要使用GetTextMetrics函数,需要先定义一个结构变量(通常称为tm):
TEXTMETRIC tm ;
在需要确定文字大小时,先取得设备内容句柄,再呼叫GetTextMetrics:
invoke GetDC,hwnd
invoke hdc,eax
Invoke GetTextMetrics,hdc,addr tm
Invoke ReleaseDC,hwnd,hdc
此后,您就可以查看文字尺寸结构中的值,并有可能保存其中的一些以备将来使用。
文字大小:细节
TEXTMETRIC结构提供了关于目前设备内容中选用的字体的丰富信息。但是,字体的纵向大小只由5个值确定,其中4个值如图4-3所示。
图4-3 定义字体中纵向字符大小的4个值
最重要的值是tmHeight,它是tmAscent和tmDescent的和。这两个值表示了基准在线下字符的最大纵向高度。「间距」(leading)指打印机在两行文字间插入的空间。在TEXTMETRIC结构中,内部的间距包括在tmAscent中(因此也在tmHeight中),并且它经常是重音符号出现的地方。tmInternalLeading字段可被设成0,在这种情况下,加重音的字母会稍稍缩短以便容纳重音符号。
TEXTMETRIC结构还包括一个不包含在tmHeight值中的字段tmExternalLeading。它是字体设计者建议加在横向字符之间的空间大小。在安排文字行之间的空隙时,您可以接受设计者建议的值,也可以拒绝它。在系统字体中tmExternalLeading可以为0,因此我没有在图4-3中显示它。(尽管我不想告诉你们,图4-3确实就是Windows在640×480的显示分辨率中使用的系统字体。)
TEXTMETRICS结构包含有描述字符宽度的两个字段,即tmAveCharWidth(小写字母加权平均宽度)和tmMaxCharWidth(字体中最宽字符的宽度)。对于定宽字体,这两个值是相等的(图4-3中这些值分别为7和14)。
本章的范例程序还需要另一种字符宽度,即大写字母的平均宽度,这可以用tmAveCharWidth乘以150%大致计算出来。
必须认识到,系统字体的大小取决于Windows所执行的视讯显示器的分辨率,在某些情况下,取决于使用者选取的系统字体的大小。Windows提供了一个与设备无关的图形接口,但程序写作者还是有事情要处理的。不要想当然耳地猜测字体大小来写作Windows程序,也不要把值定死,您可以使用GetTextMetrics函数取得这一信息。
总结:这个地方不妨结合本期的《MasmPlus》一文仔细研究看看。尽量多做一些试验来验证各种想法。
格式化文字
Windows启动后,系统字体的大小就不会发生改变,所以在程序执行过程中,程序写作者只需要呼叫一次GetTexMetrics。最好是在窗口消息处理程序中处理WM_CREATE消息时进行此呼叫,WM_CREATE消息是窗口消息处理程序接收的第一个消息。在WinMain中呼叫CreateWindow时,Windows会以一个WM_CREATE消息呼叫窗口消息处理程序。
假设要编写一个Windows程序,在显示区域显示几行文字,这需要先取得字符宽度和高度。您可以在窗口消息处理程序内定义两个变量来保存平均字符宽度(cxChar)和总的字符高度(cyChar):
cxChar DWORD ?
cyChar DWORD ?
变量名的前缀c代表「count」,在这里指图素数,与x和y结合,分别指宽和高。(原文后面还有“这些变量定义为static静态变量,因为它们在窗口消息处理程序中处理其它消息(如WM_PAINT)时也应该是有效的。如果变量在函数外面定义,则不需要定义为static。” C语言中“static静态变量”只的是一种定义在函数中的变量。它有一个特点:当你在函数中修改之后,下次再调用这个函数这个变量的值仍然是你修改之后的值,不会因为你再次进入而被重新初始化,故而有“静态”之称。 细心的读者可以发现,cxChar, cxCaps, cyChar 这几个变量我特意提取成为全局变量,就是上述原因。)
下面是取得系统字体的字符宽度和高度的WM_CREATE程序代码:
.if message==WM_CREATE
invoke GetDC,hwnd
mov hdc,eax
invoke GetTextMetrics,hdc,addr tm
mov eax,tm.tmAveCharWidth
mov cxChar,eax ;cxChar = tm.tmAveCharWidth
mov eax,2 ;cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2
test DWORD ptr tm.tmPitchAndFamily,1
jz @f
inc eax
@@:
push eax
mov eax,cxChar
pop ebx
mul ebx
shr eax,1
mov cxCaps,eax
mov eax,tm.tmHeight ;cyChar = tm.tmHeight + tm.tmExternalLeading
add eax,DWORD ptr tm.tmExternalLeading
mov cyChar,eax
invoke ReleaseDC,hwnd, hdc
ret
注意我在计算cyChar时包括了tmExternalLeading字段,虽然该字段在系统字体中为0,但是因为它使得文字的可读性更好,所以还是应该把它包括进去。沿着窗口向下每隔cyChar图素就会显示一行文字。
您会发现常常需要显示格式化的数字跟简单的字符串。我们这里使用的是wsprintf函数,这个函数与printf相似,只是把格式化字符串放到字符串中。然后,可以用TextOut将字符串输出到显示器上。非常方便的是,从sprintf和wsprintf传回的值就是字符串的长度。您可以将这个值传递给TextOut作为iLength参数。
综合使用
现在,我们似乎已经具备了在屏幕上显示多行文字所需要的所有知识。我们知道如何在WM_PAINT消息处理期间取得一个设备内容句柄,如何使用TextOut函数以及如何根据字符大小来安排字距,剩下的就是显示一点有意义的东西了。
前面,我们大概知道从Windows的GetSystemMetrics函数中取得的信息是很有意义的,该函数传回Windows中不同视觉组件的大小信息,如图标、光标、标题列和滚动条等。它们的大小因显示卡和驱动程序的不同而有所不同。GetSystemMetrics是在程序中完成与设备无关图形输出的重要函数。
该函数需要一个参数,叫做「索引」,在Windows表头文件定义了75个整数索引标识符(标识符的数量随着每个版本的Windows的发布而不断地增加,在Windows 1.0的程序写作者文件中仅列出了26个)。GetSystemMetrics传回一个整数,这个整数通常就是参数中指定的图形组件大小。
让我们来编写一个程序,显示一些可以从GetSystemMetrics呼叫中取得的信息,显示格式为每种视觉组件一行。一般来说,这个地方最好另外定义为一个文件,使用 include包到主文件中,不过
下面这个程序中,我为了看起来容易一些,特意定义在一起。
显示信息的程序命名为SYSMETS1。SYSMETS1.ASM的源程序如程序4-2所示。现在大多数程序代码看起来都很熟悉。WinMain中的程序代码实际上与HELLOWIN中的程序代码相同,并且WndProc中的大部分程序代码都已经讨论过了。
程序4-2
;MASMPlus 代码模板 - 普通的 Windows 程序代码
.386
.Model Flat, StdCall
Option Casemap :None
Include windows.inc
Include user32.inc
Include kernel32.inc
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -