您的位置:首页 > 其它

第四章 输出文字(绘制和更新,GDI 简介)

2011-04-22 01:58 309 查看
在前一章,您看到了一个简单的Windows 98程序,它在窗口中央,或者更准确地说,在显示区域中央显示一行文字。正如我们学到的,显示区域是整个应用程序窗口中未被标题列、窗口边框,以及可选的菜单列、工具列、状态列和滚动条占据的部分。简而言之,显示区域是窗口中可以由程序任意书写和传递视觉信息的部分。

对于程序的显示区域,您几乎可以为所欲为,只不过您不能假定窗口大小是某一特定尺寸,或者在程序执行时其大小会保持不变。如果您不熟悉图形窗口环境的程序设计,这些限制可能会使您感到惊讶:不能再假设屏幕上的一行文字一定有80个字符了。您的程序必须与其它Windows程序共享视讯显示器。Windows使用者控制程序窗口在屏幕上显示的方式。尽管可以建立固定大小的窗口(这对于计算器之类的应用是合理的),但在大多数情况下,使用者应该能够改变应用程序窗口的大小。您的程序必须能够接受指定给它的大小,并且合理地利用这一空间。

这有两种可能的情况。一种可能是,程序只有仅能显示「hello」的显示区域;还有另一种可能,即程序在一个大屏幕、高分辨率的系统上执行,其显示区域大得足以显示两整页文字。灵活地处理这两种极端是Windows程序设计的要点之一。

这一章,我们将讲述程序在显示区域显示信息的方式,但比上一章说明的显示方式更加复杂。当程序在显示区域显示文字或图形时,它经常要「绘制」它的显示区域。本章着重讲述绘制的方法。

尽管Windows为显示图形提供了强大的图形设备接口(GDI)函数,但在这一章中,我只介绍简单文字行的显示。我也将忽略Windows能够使用的不同字体外形及字体大小,仅使用Windows的内定系统字体。这看起来似乎是一种限制,其实不然,本章涉及和解决的问题适用于所有Windows程序设计。在混合显示文字和图形时,Windows内定字体的字符大小通常决定了图形的尺寸。

本章表面上是讨论绘图的方法,实际上是讨论与设备无关的程序设计基础。Windows程序只能对显示区域大小甚至字符的大小做很少的假定,相反地,必须使用Windows提供的功能来取得关于程序执行环境的信息。

绘制和更新

在文字模式环境下,程序可以在显示器的任意部分输出,程序输出到屏幕上的内容会停留在原处,不会神秘地消失。因此,程序可以丢掉重新生成屏幕显示时所需的信息。

在Windows中,只能在窗口的显示区域绘制文字和图形,而且不能确保在显示区域内显示的内容会一直保留到程序下一次有意地改写它时还保留在那里。例如,使用者可能会在屏幕上移动另一个程序的窗口,这样就可能覆盖您的应用程序窗口的一部分。Windows不会保存您的窗口中被其它程序覆盖的区域,当程序移开后,Windows会要求您的程序更新显示区域的这个部分。

Windows是一个消息驱动系统。它通过把消息投入应用程序消息队列中或者把消息发送给合适的窗口消息处理程序,将发生的各种事件通知给应用程序。Windows通过发送WM_PAINT消息通知窗口消息处理程序,窗口的部分显示区域需要绘制。

WM_PAINT消息

大多数Windows程序在WinMain中进入消息循环之前的初始化期间都要呼叫函数UpdateWindow。Windows利用这个机会给窗口消息处理程序发送第一个WM_PAINT消息。这个消息通知窗口消息处理程序:必须绘制显示区域。此后,窗口消息处理程序应在任何时刻都准备好处理其它WM_PAINT消息,必要的话,甚至重新绘制窗口的整个显示区域。在发生下面几种事件之一时,窗口消息处理程序会接收到一个WM_PAINT消息:

在使用者移动窗口或显示窗口时,窗口中先前被隐藏的区域重新可见。

使用者改变窗口的大小(如果窗口类别样式有着CS_HREDRAW和CS_VREDRAW位旗标的设定)。

程序使用ScrollWindow或ScrollDC函数滚动显示区域的一部分。

程序使用InvalidateRect或InvalidateRgn函数刻意产生WM_PAINT消息。

在某些情况下,显示区域的一部分被临时覆盖,Windows试图保存一个显示区域,并在以后恢复它,但这不一定能成功。在以下情况下,Windows可能发送WM_PAINT消息:

Windows擦除覆盖了部分窗口的对话框或消息框。

菜单下拉出来,然后被释放。

显示工具提示消息。

在某些情况下,Windows总是保存它所覆盖的显示区域,然后恢复它。这些情况是:

鼠标光标穿越显示区域。

图标拖过显示区域。

处理WM_PAINT消息要求程序写作者改变自己向显示器输出的思维方式。程序应该组织成可以保留绘制显示区域需要的所有信息,并且仅当「响应要求」-即Windows给窗口消息处理程序发送WM_PAINT消息时才进行绘制。如果程序在其它时间需要更新其显示区域,它可以强制Windows产生一个WM_PAINT消息。这看来似乎是在屏幕上显示内容的一种舍近求远的方法。但您的程序结构可以从中受益。

有效矩形和无效矩形

尽管窗口消息处理程序一旦接收到WM_PAINT消息之后,就准备更新整个显示区域,但它经常只需要更新一个较小的区域(最常见的是显示区域中的矩形区域)。显然,当对话框覆盖了部分显示区域时,情况即是如此。在擦除对话框之后,需要重画的只是先前被对话框遮住的矩形区域。

这个区域称为「无效区域」或「更新区域」。正是显示区域内无效区域的存在,才会让Windows将一个WM_PAINT消息放在应用程序的消息队列中。只有在显示区域的某一部分失效时,窗口才会接受WM_PAINT消息。

Windows内部为每个窗口保存一个「绘图信息结构」,这个结构包含了包围无效区域的最小矩形的坐标以及其它信息,这个矩形就叫做「无效矩形」,有时也称为「无效区域」。如果在窗口消息处理程序处理WM_PAINT消息之前显示区域中的另一个区域变为无效,则Windows计算出一个包围两个区域的新的无效区域(以及一个新的无效矩形),并将这种变化后的信息放在绘制信息结构中。Windows不会将多个WM_PAINT消息都放在消息队列中。

窗口消息处理程序可以通过呼叫InvalidateRect使显示区域内的矩形无效。如果消息队列中已经包含一个WM_PAINT消息,Windows将计算出新的无效矩形。否则,它将一个新的WM_PAINT消息放入消息队列中。在接收到WM_PAINT消息时,窗口消息处理程序可以取得无效矩形的坐标(我们马上就会看到这一点)。通过呼叫GetUpdateRect,可以在任何时候取得这些坐标。

在处理WM_PAINT消息处理期间,窗口消息处理程序在呼叫了BeginPaint之后,整个显示区域即变为有效。程序也可以通过呼叫ValidateRect函数使显示区域内的任意矩形区域变为有效。如果这呼叫具有令整个无效区域变为有效的效果,则目前队列中的任何WM_PAINT消息都将被删除。

GDI 简介

要在窗口的显示区域绘图,可以使用Windows的图形设备接口(GDI)函数。Windows提供了几个GDI函数,用于将字符串输出到窗口的显示区域内。我们已经在上一章看过DrawText函数,但是目前使用最为普遍的文字输出函数是TextOut。该函数的格式如下:

TextOut (hdc, x, y, psText, iLength) ;


TextOut向窗口的显示区域写入字符串。psText参数是指向字符串的指针,iLength是字符串的长度。x和y参数定义了字符串在显示区域的开始位置(不久会讲述关于它们的详细情况)。hdc参数是「设备内容句柄」,它是GDI的重要部分。实际上,每个GDI函数都需要将这个句柄作为函数的第一个参数。

设备内容

读者可能还记得,句柄只不过是一个数值,Windows以它在内部使用对象。程序写作者从Windows取得句柄,然后在其它函数中使用该句柄。设备内容句柄是GDI函数的窗口「通行证」,有了这种设备内容句柄,程序写作者就能自如地在显示区域上绘图,使图形如自己所愿地变得好看或者难看。

设备内容(简称为「DC」)实际上是GDI内部保存的数据结构。设备内容与特定的显示设备(如视讯显示器或打印机)相关。对于视讯显示器,设备内容总是与显示器上的特定窗口相关。

设备内容中的有些值是图形「属性」,这些属性定义了GDI绘图函数工作的细节。例如,对于TextOut,设备内容的属性确定了文字的颜色、文字的背景色、x坐标和y坐标映像到窗口的显示区域的方式,以及显示文字时Windows使用的字体。

当程序需要绘图时,它必须先取得设备内容句柄。在取得了该句柄后,Windows用内定的属性值填入内部设备内容结构。在后面的章节中您会看到,可以通过呼叫不同的GDI函数改变这些默认值。利用其它的GDI函数可以取得这些属性的目前值。当然,还有其它的GDI函数能够在窗口的显示区域真正地绘图。

当程序在显示区域绘图完毕后,它必须释放设备内容句柄。句柄被程序释放后就不再有效,且不能再被使用。程序必须在处理单个消息处理期间取得和释放句柄。除了呼叫CreateDC(函数,在本章暂不讲述)建立的设备内容之外,程序不能在两个消息之间保存其它设备内容句柄。

Windows应用程序一般使用两种方法来取得设备内容句柄,以备在屏幕上绘图。

取得设备内容句柄:方法一

在处理WM_PAINT消息时,使用这种方法。它涉及BeginPaint和EndPaint两个函数,这两个函数需要窗口句柄(作为参数传给窗口消息处理程序)和PAINTSTRUCT结构的变量(在WINUSER.H表头文件中定义)的地址为参数。Windows程序写作者通常把这一结构变量命名为ps并且在窗口消息处理程序中定义它:

PAINTSTRUCT ps ;


在处理WM_PAINT消息时,窗口消息处理程序首先呼叫BeginPaint。BeginPaint函数一般在准备绘制时导致无效区域的背景被擦除。该函数也填入ps结构的字段。BeginPaint传回的值是设备内容句柄,这一传回值通常被保存在叫做hdc的变量中。它在窗口消息处理程序中的定义如下:

HDC hdc ;


HDC数据型态定义为32位的无正负号整数。然后,程序就可以使用需要设备内容句柄的TextOut等GDI函数。呼叫EndPaint即可释放设备内容句柄。

一般地,处理WM_PAINT消息的形式如下:

caseWM_PAINT:

hdc = BeginPaint (hwnd, &ps) ;

使用GDI函数

EndPaint (hwnd, &ps) ;

return 0 ;


在处理WM_PAINT消息时,必须成对地呼叫BeginPaint和EndPaint。如果窗口消息处理程序不处理WM_PAINT消息,则它必须将WM_PAINT消息传递给Windows中DefWindowProc(内定窗口消息处理程序)。DefWindowProc以下列代码处理WM_PAINT消息:

case WM_PAINT:

BeginPaint (hwnd, &ps) ;

EndPaint (hwnd, &ps) ;

return 0 ;


这两个BeginPaint和EndPaint呼叫之间中没有任何叙述,仅仅使先前无效区域变为有效。但以下方法是错误的:

case WM_PAINT:

return 0 ;   // WRONG !!!


Windows将一个WM_PAINT消息放到消息队列中,是因为显示区域的一部分无效。如果不呼叫BeginPaint和EndPaint(或者ValidateRect),则Windows不会使该区域变为有效。相反,Windows将发送另一个WM_PAINT消息,且一直发送下去。

绘图信息结构

前面提到过,Windows为每个窗口保存一个「绘图信息结构」,这就是PAINTSTRUCT,定义如下:

typedef struct tagPAINTSTRUCT

{

HDC           hdc ;

BOOL      fErase ;

RECT      rcPaint ;

BOOL          fRestore ;

BOOL          fIncUpdate ;

BYTE          rgbReserved[32] ;

} PAINTSTRUCT ;


在程序呼叫BeginPaint时,Windows会适当填入该结构的各个字段值。使用者程序只使用前三个字段,其它字段由Windows内部使用。hdc字段是设备内容句柄。在旧版本的Windows中,BeginPaint的传回值也曾是这个设备内容句柄。在大多数情况下, fErase被标志为FALSE(0),这意味着Windows已经擦除了无效矩形的背景。这最早在BeginPaint函数中发生(如果要在窗口消息处理程序中自己定义一些背景擦除行为,可以自行处理WM_ERASEBKGND消息)。Windows使用WNDCLASS结构的hbrBackground字段指定的画刷来擦除背景,这个WNDCLASS结构是程序在WinMain初始化期间登录窗口类别时使用的。许多Windows程序使用白色画刷。以下叙述设定窗口类别结构字段值:

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;


不过,如果程序通过呼叫Windows函数InvalidateRect使显示区域中的矩形失效,则该函数的最后一个参数会指定是否擦除背景。如果这个参数为FALSE(即0),则Windows将不会擦除背景,并且在呼叫完BeginPaint后PAINTSTRUCT结构的fErase字段将为TRUE(非零)。

PAINTSTRUCT结构的rcPaint字段是RECT型态的结构。您已经在第三章中看到,RECT结构定义了一个矩形,其四个字段为left、top、right和bottom。PAINTSTRUCT结构的rcPaint字段定义了无效矩形的边界,如图4-1所示。这些值均以图素为单位,并相对于显示区域的左上角。无效矩形是应该重画的区域。

图4-1 无效矩形的边界
PAINTSTRUCT中的rcPaint矩形不仅是无效矩形,它还是一个「剪取」矩形。这意味着Windows将绘图操作限制在剪取矩形内(更确切地说,如果无效矩形区域不为矩形,则Windows将绘图操作限制在这个区域内)。

在处理WM_PAINT消息时,为了在更新的矩形外绘图,可以使用如下呼叫:

InvalidateRect (hwnd, NULL, TRUE) ;


该呼叫在BeginPaint呼叫之前进行,它使整个显示区域变为无效,并擦除背景。但是,如果最后一个参数等于FALSE,则不擦除背景,原有的东西将保留在原处。

通常这是Windows程序在无论何时收到WM_PAINT消息而不考虑rcPaint结构的情况下简单地重画整个显示区域最方便的方法。例如,如果在显示区域的显示输出中包括了一个圆,但是只有圆的一部分落到了无效矩形中,它就使仅绘制圆的无效部分变得没有意义。这需要画整个圆。在您使用从BeginPaint传回的设备内容句柄时,Windows不会绘制rcPaint矩形外的任何部分。

第三章的HELLOWIN程序中,我们并不关心处理WM_PAINT消息时的无效矩形。如果文字显示区域恰巧在无效矩形内,则由DrawText恢复之。否则,在处理DrawText呼叫的某个时刻,Windows会确定它无须向显示器上输出。不过,这一决定需要时间。关心程序性能和速度的程序写作者希望在处理WM_PAINT期间使用无效矩形范围,以避免不必要的GDI呼叫。如果绘制时需要存取例如位图这样的磁盘文件,则这就显得尤其重要。

取得设备内容句柄:方法二

虽然最好是在处理WM_PAINT消息处理期间更新整个显示区域,但是您也会发现在处理非WM_PAINT消息处理期间绘制显示区域的某个部分也是非常有用的。或者您需要将设备内容句柄用于其它目的,如取得设备内容的信息。

要得到窗口显示区域的设备内容句柄,可以呼叫GetDC来取得句柄,在使用完后呼叫ReleaseDC:

hdc = GetDC (hwnd) ;

使用GDI函数

ReleaseDC (hwnd, hdc) ;


与BeginPaint和EndPaint一样,GetDC和ReleaseDC函数必须成对地使用。如果在处理某消息时呼叫GetDC,则必须在退出窗口消息处理程序之前呼叫ReleaseDC。不要在一个消息中呼叫GetDC却在另一个消息呼叫ReleaseDC。

与从BeginPaint传回设备内容句柄不同,GetDC传回的设备内容句柄具有一个剪取矩形,它等于整个显示区域。可以在显示区域的某一部分绘图,而不只是在无效矩形上绘图(如果确实存在无效矩形)。与BeginPaint不同,GetDC不会使任何无效区域变为有效。如果需要使整个显示区域有效,可以呼叫

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将文字大小的不同值复制到在WINGDI.H中定义的TEXTMETRIC型态的结构中。TEXTMETRIC结构有20个字段,我们只使用前七个:

typedef struct tagTEXTMETRIC

{

LONG tmHeight ;

LONG tmAscent ;

LONG tmDescent ;

LONG tmInternalLeading ;

LONG tmExternalLeading ;

LONG tmAveCharWidth ;

LONG tmMaxCharWidth ;

其它结构字段

}

TEXTMETRIC, * PTEXTMETRIC ;


这些字段值的单位取决于选定的设备内容映像方式。在内定设备内容下,映像方式是MM_TEXT,因此值的大小是以图素为单位。

要使用GetTextMetrics函数,需要先定义一个结构变量(通常称为tm):

TEXTMETRIC tm ;


在需要确定文字大小时,先取得设备内容句柄,再呼叫GetTextMetrics:

hdc = GetDC (hwnd) ;

GetTextMetrics (hdc, &tm) ;

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函数取得这一信息。

格式化文字

Windows启动后,系统字体的大小就不会发生改变,所以在程序执行过程中,程序写作者只需要呼叫一次GetTexMetrics。最好是在窗口消息处理程序中处理WM_CREATE消息时进行此呼叫,WM_CREATE消息是窗口消息处理程序接收的第一个消息。在WinMain中呼叫CreateWindow时,Windows会以一个WM_CREATE消息呼叫窗口消息处理程序。

假设要编写一个Windows程序,在显示区域显示几行文字,这需要先取得字符宽度和高度。您可以在窗口消息处理程序内定义两个变量来保存平均字符宽度(cxChar)和总的字符高度(cyChar):

static int cxChar, cyChar ;


变量名的前缀c代表「count」,在这里指图素数,与x和y结合,分别指宽和高。这些变量定义为static静态变量,因为它们在窗口消息处理程序中处理其它消息(如WM_PAINT)时也应该是有效的。如果变量在函数外面定义,则不需要定义为static。

下面是取得系统字体的字符宽度和高度的WM_CREATE程序代码:

case WM_CREATE:

hdc = GetDC (hwnd) ;

GetTextMetrics (hdc, &tm) ;

cxChar = tm.tmAveCharWidth ;

cyChar = tm.tmHeight + tm.tmExternalLeading ;

ReleaseDC (hwnd, hdc) ;

return 0 ;


注意我在计算cyChar时包括了tmExternalLeading字段,虽然该字段在系统字体中为0,但是因为它使得文字的可读性更好,所以还是应该把它包括进去。沿着窗口向下每隔cyChar图素就会显示一行文字。

您会发现常常需要显示格式化的数字跟简单的字符串。我在第二章讲到过,您不能使惯用的工具(可爱的printf函数)来完成这项工作,但是可以使用sprintf和Windows版的sprintf-wsprintf。这些函数与printf相似,只是把格式化字符串放到字符串中。然后,可以用TextOut将字符串输出到显示器上。非常方便的是,从sprintf和wsprintf传回的值就是字符串的长度。您可以将这个值传递给TextOut作为iLength参数。下面的程序代码显示了wsprintf与TextOut的典型组合:

int  iLength ;

TCHAR szBuffer [40] ;

其它行程序

iLength = wsprintf (szBuffer, TEXT ("The sum of %i and %i is %i"),

iA, iB, iA + iB) ;

TextOut (hdc, x, y, szBuffer, iLength) ;


对于这样简单的情况,可以将nLength的定义值与TextOut放在同一条叙述中,从而无需定义iLength:

TextOut (hdc, x, y, szBuffer,

wsprintf (szBuffer, TEXT ("The sum of %i and %i is %i"),

iA, iB, iA + iB)) ;


虽然这样子写起来不好看,但是功能与前者是一样的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: