📄 见招拆招 windows 程序设计 (四) .txt
字号:
可以看到,这个程序非常类似前面的SYSMETS1程序。为了保持程序代码的短小,我没有使用滚动条,因为我知道信息可以在一个画面上显示出来。运行结果如下:
设备的大小
假定要绘制边长为1英寸的正方形,您(程序写作者)或Windows(操作系统)需要知道视频显示上1英寸对应多少像素。使用GetDeviceCaps函数能取得有关如视频显示器和打印机之类输出设备的实际显示大小信息。
视频显示器和打印机是两个不同的设备。但也许最不明显的区别是「分辨率」与设备联系起来的方式。对于打印机,我们经常用「每英寸的点数(dpi)」表示分辨率。例如,大多数激光打印机有300或600dpi的分辨率。然而,视频显示器的分辨率是以水平和垂直的总像素数来表示的,例如,1024×768。大多数人不会告诉您他的打印机在一张纸上水平和垂直打印多少像素或他们的视频显示器上每英寸有多少像素。
在本书中,我用「分辨率」来严格定义每度量单位(一般为英寸)内的像素数。我使用「像素大小」或「像素尺寸」表示设备水平或垂直显示的总像素数。「度量大小」或「度量尺寸」是以英寸或毫米为单位的设备显示区域的大小。(对于打印机页面,它不是整个页面,只是可打印的区域。)像素大小除以度量大小就得到分辨率。
现在Windows使用的大多数视频显示器的屏幕都是宽比高多33%。这就表示纵横比为1.33:1或(一般写法)4:3。历史上,该比例可追溯到Thomas Edison制作电影的年代。它一直作为电影的标准纵横比,直到1953年出现各种型态的宽银幕投影机。电视机屏幕的纵横比也是4:3。
然而,Windows应用程序不应假设视频显示器具有4:3的纵横比。人们进行文字处理时希望视频显示器与一张纸的长和宽类似。最普通的选择是把4:3变为3:4显示,把标准显示翻转一下。
如果设备的水平分辨率与垂直分辨率相等,就称设备具有「正方形像素」。现在,Windows普遍使用的视频显示器都具有正方形像素,但也有例外。(应用程序也不应假设视频显示器总是具有正方形像素。)Windows第一次发表时,标准显示卡卡是IBM Color Graphics Adapter(CGA),它有640×200的像素大小;Enhanced Graphics Adapter(EGA)有640×350的像素大小;Hercules Graphics Card有720×348的像素大小。所有这些显示卡都使用4:3纵横比的显示器,但是水平和垂直像素数的比值都不是4:3。
执行Windows的使用者很容易确定视频显示器的像素大小。在「控制面板」中选择「显示」,并选择「设定」页面标签。在标有「屏幕分辨率」的字段中,可以看到这些像素尺寸之一:
640×480像素
800×600像素
1024×768像素
1280×1024像素
1600×1200像素
所有这些都是4:3。(除了1280×1024像素大小。这不但有些不好,还有些令人反感。所有这些像素尺寸都认为在4:3的显示器上会产生正方形的像素。)
Windows应用程序可以使用SM_CXSCREEN和SM_CYSCREEN参数从GetSystemMetrics得到像素尺寸。从DEVCAPS1程序中您会注意到,程序可以用HORZRES(水平分辨率)和VERTRES参数从GetDeviceCaps中得到同样的值。这里「分辨率」指的是像素大小而不是每度量单位的像素数。
这些是设备大小的简单部分,现在开始复杂的部分。
前两个设备能力,HORZSIZE和VERTSIZE,文件中称为「以毫米计的实际屏幕的宽度」及「以毫米计的实际屏幕的高度」。这些看起来更像直接的定义。例如,给出视频显示卡和显示器的接口特性,Windows如何真正知道显示器的大小呢?如果您有台笔记本电脑(它的视频驱动程序能知道准确的屏幕大小)并且连接了外部显示器,又是哪种情况呢?如果把视频投影机连接到计算机上呢?
在Windows的16位版本中(及在Windows NT中),Windows为HORZSIZE和VERTSIZE使用「标准」的显示大小。然而,从Windows 95开始,HORZSIZE和VERTSIZE值是从HORZRES、VERTRES、LOGPIXELSX和LOGPIXELSY值中衍生出来的。这是它的工作方式。
当您在「控制面板」中使用「显示」程序选择显示的像素大小时,也可以选择系统字体的大小。这个选项的原因是用于640×480显示的字体在提升到1024×768或更大时字太小,而您可能想要更大的系统字体。这些系统字体大小指「显示」程序的「设定」页面卷标中的「小字体」和「大字体」。
在传统的排版中,字体的字母大小由「点」表示。1点大约1/72英寸,在计算机排版中1点正好为1/72英寸。
理论上,字体的点值是从字体中最高的字符顶部到例如j、p、q和y等字母下部的字符底部的距离,其中不包括重音符号。例如,在10点的字体中此距离是10/72英寸。根据TEXTMETRIC结构,字体的点值等于tmHeight字段减去tmInternalLeading字段,如图5-2所示(该图与上一期的图一样)。
在真正的排版中,字体的点值与字体字母的实际大小并不正好相等。字体的设计者做出的实际字符比点值指示的要大一些或小一些。毕竟,字体设计是一种艺术而不是科学。
TEXTMETRIC结构的tmHeight字段指出文字的连续行在屏幕或打印机上间隔的方式。这也可以用点来测量。例如,12点的行距指出文字连续行的基准线应该间隔12/72(或1/6)英寸。不应该为10点字体使用10点行距,因为文字的连续行会碰到一起。
10点字体读起来很舒服。小于10点的字体不益于长时间阅读。
Windows系统字体-不考虑是大字体还是小字体,也不考虑所选择的视频像素大小-固定假设为10点字体和12点行距。这听起来很奇怪,如果字体都是10点,为什么还把它们称为大字体和小字体呢?
解答是:当您在「控制面板」的「显示」程序上选择小字体或大字体时,实际上是选择了一个假定的视频显示分辨率,单位是每英寸的点数 。当选择小字体时,即要Windows假定视频显示分辨率为每英寸96点。当选择大字体时,即要Windows假定视频显示分辨率为每英寸120点。
再看看上面的图5-2。那是小字体,它依据的显示分辨率为每英寸96点。我说过它是10点字体。10点即是10/72英寸,如果乘以96点,每英寸大概就为13像素。这即是tmHeight减去tmInternalLeading的值。行距是12点,或12/72英寸,它乘以96点,每英寸就为16像素。这即是tmHeight的值。
图5-3显示大字体。这是依据每英寸120点的分辨率。同样,它是10点字体,10/72乘以120点,每英寸等于16像素,即是tmHeight减tmInternalLeading的值。12点行距等于20像素,即是tmHeight的值。(像前一章一样,再次强调所显示的是实际的度量大小,因此您可以理解它工作的方式。不要在您的程序中对此写作程序。)
图5-3 大字体和FONTMETRIC字段
在Windows程序中,您可以使用GetDeviceCaps函数取得使用者在「控制面板」的「显示器」程序中选择的以每英寸的点数为单位的假定分辨率。要得到这些值(如果视频显示器不具有正方形像素,在理论上这些值是不同的),可以使用索引LOGPIXELSX和LOGPIXELSY。LOGPIXELS指逻辑像素,它的基本意思是「以每英寸的像素数为单位的非实际分辨率」。
用HORZSIZE和VERTSIZE索引从GetDeviceCaps得到的设备能力,在文件上称为「实际屏幕的宽度,单位毫米」及「实际屏幕的高度,单位毫米」。因为这些值是从HORZRES、VERTRES、LOGPIXELSX和LOGPIXELSY值中衍生出来的,所以它们应该称为「逻辑宽度」和「逻辑高度」。公式是:
常数25.4用于把英寸转变为毫米。
这看起来是种不合逻辑的退步。毕竟,视频显示器是可以用尺以毫米为单位的大小(至少是近似的)衡量的。但是Windows并不关心这个大小。相反,它以使用者选择的显示像素大小和系统字体大小为基础计算以毫米为单位的显示大小。更改显示的像素大小并根据GetDeviceCaps更改度量大小。这有什么意义呢?
这非常有意义。假定有一个17英寸的显示器。实际的显示大小大约是12英寸乘9英寸。假定在最小要求的640×480像素大小下执行Windows。这意味着实际的分辨率是每英寸53点。10点字体(在纸上便于阅读)在屏幕上从A的顶部到q的底部只有7个像素。这样的字体很难看而且不易读。(可问问那些在旧的Color Graphics Adapter上执行Windows的人们。)
现在,把您的计算机接上视频投影机。投影的视频显示器是4英尺宽,3英尺高。同样的640×480像素大小现在是大约每英寸13点的分辨率。在这种条件下试图显示10点的字体是很可笑的。 10点字体在视频显示器上应是可读的,因为它在打印时是肯定可读的。所以10点字体就成为一个重要的参照。当Windows应用程序确保10点屏幕字体为平均大小时,就能够使用8点字体显示较小的文字(仍可读),或用大于10点的字体显示较大的文字。因而,视频分辨率(以每英寸的点数为单位)由10点字体的像素大小来确定是很有意义的。
然而,在Windows NT/XP中,用老的方法定义HORZSIZE和VERTSIZE值。这种方法与Windows的16位版本一致。HORZRES和VERTRES值仍然表示水平和垂直像素的数值,LOGPIXELSX和LOGPIXELSY仍然与在「控制面板」的「显示器」程序中选择的字体有关。在Windows 98中,LOGPIXELSX和LOGPIXELSY的典型值是96和120 dpi,这取决于您选择的是小字体还是大字体。
在Windows NT/XP中的区别是HORZSIZE和VERTSIZE值固定表示标准显示器大小。对于普通的显示卡,取得的HORZSIZE和VERTSIZE值分别是320和240毫米。这些值是相同的,与选择的像素大小无关。因此,这些值与用HORZRES、VERTRES、LOGPIXELSX和LOGPIXELSY索引从GetDeviceCaps中得到的值不同。然而,可以用前面的公式计算在Windows 98下的HORZSIZE和VERTSIZE值。
如果程序需要实际的视频显示大小该怎么办?也许最好的解决方法是用对话框让使用者输入它们。
最后,来自GetDeviceCaps的另三个值与视频大小有关。ASPECTX、ASPECTY和ASPECTXY值是每一个像素的相对宽度、高度和对角线大小,四舍五入到整数。对于正方形像素,ASPECTX和ASPECTY值相同。无论如何,ASPECTXY值应等于ASPECTX与ASPECTY平方和的平方根,就像直角三角形一样。
关于色彩
如果视频显示卡仅显示黑色像素和白色像素,则每个像素只需要内存中的一位。彩色显示器中每个像素需要多个位。位数越多,色彩越多,或者更具体地说,可以同时显示的不同色彩的数目等于2的位数次方。「Full-Color」视频显示器的分辨率是每个像素24位-8位红色、8位绿色以及8位蓝色。红、绿、蓝即「色光三原色」。混合这三种基本颜色可以生成许多其它的颜色,您通过放大镜看显示屏,就可以看出来。「High-Color」显示分辨率是每个像素16位-5位红色、6位绿色以及5位蓝色。绿色多一位是因为人眼对绿色更敏感一些。
显示256种颜色的显示卡每个像素需要8位。然而,这些8位的值一般由定义实际颜色的调色盘组织的。关于这方面,我们在后面会更详细地讨论它们。
最后,显示16种颜色的显示卡每个像素需要4位。这16种颜色一般固定分为暗的或亮的红、黑、蓝、青、紫、黄、两种灰色。这16种颜色要回溯到老式的IBM CGA。
只有在某些怪异的程序中才需要知道视频显示卡上的内存是如何组织的,但是GetDeviceCaps使程序写作者可以知道显示卡的储存组织以及它能够表示的色彩数目,下面的呼叫传回色彩平面的数目:
invoke GetDeviceCaps,hdc,PLANES
mov iPlanes, eax
下面的呼叫传回每个像素的色彩位数:
invoke GetDeviceCaps,hdc, BITSPIXEL
mov iPlanes, eax
大多数彩色图形显示设备使用多个色彩平面或每像素有多个色彩位的设计,但是不能同时一齐使用这两种方式;换句话说,这两个呼叫必有一个传回1。显示卡能够表示的色彩数可以用如下公式来计算:
iColors = 2 ^ (iPlanes * iBitsPixel) ;
这个值与用NUMCOLORS参数得到的色彩数值可能一样,也可能不一样:
invoke GetDeviceCaps,hdc, NUMCOLORS
mov iColors, eax
我提到过,256色的显示卡使用色彩调色盘。在那种情况下,以NUMCOLORS为参数时,GetDeviceCaps传回由Windows保留的色彩数,值为20,剩余的236种颜色可以由Windows程序用调色盘管理器设定。对于High-Color和True-Color显示分辨率,带有NUMCOLORS参数的GetDeviceCaps通常传回-1,这样就无法得到需要的信息,因此应该使用前面所示的带有PLANES和BITSPIXEL值的iColors公式。在大多数GDI函数呼叫中,使用COLORREF值(只是一个32位的无正负号长整数)来表示一种色彩。COLORREF值按照红、绿和蓝色的亮度指定了一种颜色,通常叫做「RGB色彩」 。32位的COLORREF值的设定如图5-4所示。
图5-4 32位COLORREF值
注意最前面是标为0的8个位,并且每种原色都指定为一个8位的值。理论上,COLORREF可以指定二的二十四次方种或一千六百万种色彩。这个无正负号长整数常常称为一个「RGB色彩」。Windows表头文件Windows.inc提供了几种使用RGB色彩表示的值。
Black equ 000000h
Blue equ 0FF0000h
Green equ 00FF00h
Cyan equ 0FFFF00h
Red equ 0000FFh
我们也可以自己动手编写一个将三个颜色参数合成为一个颜色的宏。RGB宏要求三个参数分别代表红、绿和蓝值,然后将它们组合为一个无正负号长整数:
;输入RGB,合成一个32位值放在EAX中
RGB MACRO red, green, blue xor eax, eax
mov ah, blue ; blue
mov al, green ; green
rol eax, 16
mov al, red ; red
ENDM
注意三个参数的顺序是红、绿和蓝。因此,值:RGB (255, 255, 0)是0x0000FFFF,或黄色(红色和绿色的合成)。当所有三个参数设定为0时,色彩为黑色;当所有参数设定为255时,色彩为白色。GetRValue、GetGValue和GetBValue宏从COLORREF值中抽取出原色值。当您在使用传回RGB色彩值的Windows函数时,这些宏有时会很方便。
在16色或256色显示卡上,Windows可以使用「混色」来模拟设备能够显示的颜色之外的色彩。混色利用了由多种色彩的像素组成的像素图案。可以呼叫GetNearestColor来决定与某一色彩最接近的纯色:
invoke GetNearestColor,hdc,crColor
mov crPureColor,eax
设备内容属性
前面已经提到过,Windows使用设备内容来保存控制GDI函数在显示器上如何操作的「属性」。例如,在用TextOut函数显示文字时,程序写作者不必指定文字的色彩和字体,Windows从设备内容取得这个信息。
程序取得一个设备内容的句柄时,Windows用默认值设定所有的属性(在下一节会看到如何取代这种设定)。表5-1列出了Windows 98/XP支持的设备内容属性,程序可以改变或者取得任何一种属性。
表5-1
设备内容属性
默认值
修改该值的函数
取得该值的函数
Mapping Mode MM_TEXT SetMapMode GetMapMode
Window Origin (0, 0) SetWindowOrgEx
OffsetWindowOrgEx
GetWindowOrgEx
Viewport Origin (0, 0) SetViewportOrgEx
OffsetViewportOrgEx
GetViewportOrgEx
Window Extents (1, 1) SetWindowExtEx
SetMapMode
ScaleWindowExtEx
GetWindowExtEx
Viewport Extents (1, 1) SetViewportExtEx
SetMapMode
ScaleViewportExtEx
GetViewportExtEx
Pen BLACK_PEN SelectObject SelectObject
Brush WHITE_BRUSH SelectObject SelectObject
Font SYSTEM_FONT SelectObject SelectObject
Bitmap None SelectObject SelectObject
Current Position (0, 0) MoveToEx
LineTo
PolylineTo
PolyBezierTo
GetCurrentPositionEx
Background Mode OPAQUE SetBkMode GetBkMode
Background Color White SetBkColor GetBkColor
Text Color Black SetTextColor GetTextColor
Drawing Mode R2_COPYPEN SetROP2 GetROP2
Stretching Mode BLACKONWHITE SetStretchBltMode GetStretchBltMode
Polygon Fill Mode ALTERNATE SetPolyFillMode GetPolyFillMode
Intercharacter Spacing 0 SetTextCharacterExtra GetTextCharacterExtra
Brush Origin (0, 0) SetBrushOrgEx GetBrushOrgEx
Clipping Region None SelectObject
SelectClipRgn
IntersectClipRgn
OffsetClipRgn
ExcludeClipRect
SelectClipPath
GetClipBox
保存设备内容
通常,在您呼叫GetDC或BeginPaint时,Windows用默认值建立一个新的设备内容,您对属性所做的一切改变在设备内容用ReleaseDC或EndPaint呼叫释放时,都会丢失。如果您的程序需要使用非内定的设备内容属性,则您必须在每次取得设备内容句柄时初始化设备内容:
.elseif message == WM_PAINT
invoke BeginPaint,hwnd,addr ps
mov hdc,eax
设备内容属性
绘制窗口显示区域
invoke EndPaint,hwnd,addr ps
ret
虽然在通常情况下这种方法已经很令人满意了,但是您还可能想要在释放设备内容之后,仍然保存程序中对设备内容属性所做的改变,以便在下一次呼叫GetDC和BeginPaint时它们仍然能够起作用。为此,可在登录窗口类别时,将CS_OWNDC旗标纳入窗口类别的一部分:
mov wndclass.style,CS_HREDRAW or CS_VREDRAW or CS_OWNDC
现在,依据这个窗口类别所建立的每个窗口都将拥有自己的设备内容,它一直存在,直到窗口被删除。如果使用了CS_OWNDC风格,就只需初始化设备内容一次,可以在处理WM_CREATE消息处理期间完成这一操作:
.if message==WM_CREATE
invoke GetDC,hwnd
mov hdc,eax
初始化设备内容属性
invoke ReleaseDC,hwnd, hdc
这些属性在改变之前一直有效。
CS_OWNDC风格只影响GetDC和BeginPaint获得的设备内容,不影响其它函数(如GetWindowDC)获得的设备内容。以前不提倡使用CS_OWNDC风格,因为它需要内存;现在,在处理大量图形的Windows NT/XP应用程序中,它可以提高性能。即使用了CS_OWNDC,您仍然应该在退出窗口消息处理程序之前释放设备内容。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -