ps:您可以转载,但请注明出处;你可以修改,但请将修改结果告诉我。 An Introduction to GDIGDI 介绍 To paint the client area of your window, you use Windows' Graphics Device Interface (GDI) functions. Windows provides several GDI functions for writing text strings to the client area of the window. We've already encountered the DrawText function in the last chapter, but the most commonly used text output function is undoubtedly TextOut. This function has the following format: 为了绘制窗口的客户区,你需要使用 Windows 的图形设备接口(GDI)函数。Windows 提供了一些 GDI 函数用于将文本字符串输出到窗口的客户区。在上一章我们已经遇到过 DrawText 函数,但最通用的文本输出函数毫无疑问是 TextOut。该函数格式如下: TextOut (hdc, x, y, psText, iLength) ;TextOut writes a character string to the client area of the window. The psText argument is a pointer to the character string, and iLength is the length of the string in characters. The x and y arguments define the starting position of the character string in the client area. (More details soon on how these work.) The hdc argument is a "handle to a device context," and it is an important part of GDI. Virtually every GDI function requires this handle as the first argument to the function. TextOut 输出字符串到窗口客户区。psText 参数是指向字符串的指针,iLength 是字符串的长度。x 和 y 参数定义了客户区中字符串的开始位置(不就会有很多关于这些如何工作的详细信息)。hdc 参数是“关联设备句柄”,它是 GDI 的一个重要部分。实际上每个 GDI 函数都要求这个句柄作为函数的第一个参数。 The Device Context关联设备 A handle, you'll recall, is simply a number that Windows uses for internal reference to an object. You obtain the handle from Windows and then use the handle in other functions. The device context handle is your window's passport to the GDI functions. With that device context handle you are free to paint your client area and make it as beautiful or as ugly as you like.你可能还记得,句柄只是 Windows 内部引用对象的数字。你从 Windows 获得句柄然后在其它函数中使用它。关联设备句柄是你的窗口到 GDI 函数的通行证。有了关联设备句柄你就可以自由地在客户区绘制并按照你的喜好使它好看或难看。 The device context (also called simply the "DC") is really just a data structure maintained internally by GDI. A device context is associated with a particular display device, such as a video display or a printer. For a video display, a device context is usually associated with a particular window on the display. 关联设备(也简称为“DC”)实际上只是由 GDI 内部维护的一个数据结构。关联设备与特殊的显示设备关联,比如视频显示器或打印机。对视频显示器来说,关联设备通常和显示器上的特殊窗口关联。 Some of the values in the device context are graphics "attributes." These attributes define some particulars of how GDI drawing functions work. With TextOut, for instance, the attributes of the device context determine the color of the text, the color of the text background, how the x-coordinate and y-coordinate in the TextOut function are mapped to the client area of the window, and what font Windows uses when displaying the text. 关联设备中的一些值是图形“属性”。这些属性定义了一些关于 GDI 绘制函数如何工作的细节。例如,对 TextOut,关联设备的属性决定了文本的颜色,文本的背景色,TextOut 函数中 x 和 y 坐标如何映射到窗口的客户区,以及当显示文本时 Windows 用什么字体。 When a program needs to paint, it must first obtain a handle to a device context. When you obtain this handle, Windows fills the internal device context structure with default attribute values. As you'll see in later chapters, you can change these defaults by calling various GDI functions. Other GDI functions let you obtain the current values of these attributes. Then, of course, there are still other GDI functions that let you actually paint the client area of the window. 当程序需要绘制时,它必须首先获得关联设备句柄。当你获得这个句柄时,Windows 用缺省的属性之填充内部的关联设备结构。正如你将在后面章节看到的,你可以通过调用各种 GDI 函数改变这些缺省值。其它 GDI 函数让你获得这些属性的当前值。然后,当然还有其它 GDI 函数让你可以真正在窗口的客户区上绘制。 After a program has finished painting its client area, it should release the device context handle. When a program releases the handle, the handle is no longer valid and must not be used. The program should obtain the handle and release the handle during the processing of a single message. Except for a device context created with a call to CreateDC (a function I won't discuss in this chapter), you should not keep a device context handle around from one message to another.程序完成对客户区的绘制后,它应该释放关联设备句柄。当程序释放该句柄时,该句柄不再有效并不能使用。在处理单个消息期间程序应该获得和释放句柄。除调用 CreateDC(本章我不讨论它)获得关联设备句柄之外,你不能在消息之间保留关联设备句柄。 Windows applications generally use two methods for getting a device context handle in preparation for painting the screen. Windows 应用程序通常使用两种方法获得为绘制屏幕准备的关联设备句柄。 Getting a Device Context Handle: Method One获得关联设备句柄:方法一 You use this method when you process WM_PAINT messages. Two functions are involved: BeginPaint and EndPaint. These two functions require the handle to the window, which is passed to the window procedure as an argument, and the address of a structure variable of type PAINTSTRUCT, which is defined in the WINUSER.H header file. Windows programmers usually name this structure variable ps and define it within the window procedure like so:当你处理 WM_PAINT 消息时使用这种方法。两个函数有关:BeginPaint 和 EndPaint。这两个函数需要两个参数:一个是作为参数传递给窗口处理程序的窗口句柄,一个是在 WINUSER.H 头文件中定义的 PAINTSTRUCT 类型结构变量的地址 。Windows 程序员通常将这个结构变量命名为 ps并在窗口处理程序中定义成这样: PAINTSTRUCT ps ;While processing a WM_PAINT message, the window procedure first calls BeginPaint. The BeginPaint function generally causes the background of the invalid region to be erased in preparation for painting. The function also fills in the fields of the ps structure. The value returned from BeginPaint is the device context handle. This is commonly saved in a variable named hdc. You define this variable in your window procedure like so: 当处理 WM_PAINT 消息时,窗口处理程序首先调用 BeginPaint。BeginPaint 函数通常在准备绘制时导致无效区域的背景被清除。该函数也填充 ps 结构的域。BeginPaint 的返回值是关联设备句柄,它通常保存在变量 hdc 中。你在窗口处理程序中像这样定义改变量: HDC hdc ; The HDC data type is defined as a 32-bit unsigned integer. The program may then use GDI functions, such as TextOut, that require the handle to the device context. A call to EndPaint releases the device context handle. Typically, processing of the WM_PAINT message looks like this: HDC 数据类型定义为 32 位无符号整型。然后程序可能使用要求关联设备句柄的 GDI 函数,比如 TextOut。调用 EndPaint 释放关联设备句柄。典型地处理 WM_PAINT 像这样:case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; [use GDI functions] EndPaint (hwnd, &ps) ; return 0 ; The window procedure must call BeginPaint and EndPaint as a pair while processing the WM_PAINT message. If a window procedure does not process WM_PAINT messages, it must pass the WM_PAINT message to DefWindowProc, which is the default window procedure located in Windows. DefWindowProc processes WM_PAINT messages with the following code: 当处理 WM_PAINT 消息时窗口处理程序必须成对调用 BeginPain 和 EndPaint。如果窗口处理程序不处理 WM_PAINT 消息,那么它必须传递 WM_PAINT 消息给 DefWindowProc——位于 Windows 中的缺省窗口处理函数。DefWindowProc 用下面的代码处理 WM_PAINT 消息:case WM_PAINT: BeginPaint (hwnd, &ps) ; EndPaint (hwnd, &ps) ; return 0 ; The sequence of BeginPaint and EndPaint calls with nothing in between validates the previously invalid region. BeginPaint 和 EndPaint 调用顺序无所谓,仅仅使先前的无效区域有效。 But don't do this: 但是不能这样做:case WM_PAINT: return 0 ; // WRONG !!! Windows places a WM_PAINT message in the message queue because part of the client area is invalid. Unless you call BeginPaint and EndPaint (or ValidateRect), Windows will not validate that area. Instead, Windows will send you another WM_PAINT message, and another, and another, and another…. Windows 放置 WM_PAINT 消息到消息队列是因为客户区域的一部分无效。除非你调用 BeginPaint 和 EndPaint(或 ValidateRect),否则 Windows 不会使该区域有效。然而,Windows 将给你发送另一个 WM_PAINT 消息,一个又一个...。 The Paint Information Structure绘制信息结构 Earlier I mentioned a "paint information structure" that Windows maintains for each window. That's what PAINTSTRUCT is. The structure is defined as follows: 早前我们提过 Windows 为每个窗口维护一个“绘制信息结构”。那就是 PAINTSTRUCT。该结构定义如下:typedef struct tagPAINTSTRUCT{ HDC hdc ; BOOL fErase ; RECT rcPaint ; BOOL fRestore ; BOOL fIncUpdate ; BYTE rgbReserved[32] ;} PAINTSTRUCT ; Windows fills in the fields of this structure when your program calls BeginPaint. Your program can use only the first three fields. The others are used internally by Windows. The hdc field is the handle to the device context. In a redundancy typical of Windows, the value returned from BeginPaint is also this device context handle. In most cases, fErase will be flagged FALSE (0), meaning that Windows has already erased the background of the invalid rectangle. This happens earlier in the BeginPaint function. (If you want to do some customized background erasing in your window procedure, you can process the WM_ERASEBKGND message.) Windows erases the background using the brush specified in the hbrBackground field of the WNDCLASS structure that you use when registering the window class during WinMain initialization. Many Windows programs specify a white brush for the window background. This is indicated when the program sets up the fields of the window class structure with a statement like this: 当程序调用 BeginPaint 时 Windows 填充该结构的域。程序只用前三个域。其它的都是 Windows 内部使用。hdc 域是关联设备句柄。在旧版本的 Windows 中,BeginPaint 的返回值也是这个关联设备句柄。在大多数实例中,fErase 将被标志为 FALSE(0),表明 Windows 已经清除了无效矩形的背景。这很早发生在 BeginPaint 函数中。(如果你想在窗口处理程序中定制背景清除,你可以处理 WM_ERASEBKGND 消息。)Windows 用在 WNDCLASS 结构的 hbrBackground 域中指定的画刷清除背景,该结构在 WinMain 初识化期间注册的窗口类。许多 Windows 程序员为窗口背景制定白色画刷。这指定程序用下面的语句设置窗口类结构的域: wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; However, if your program invalidates a rectangle of the client area by calling InvalidateRect, the last argument of the function specifies whether you want the background erased. If this argument is FALSE (that is, 0), Windows will not erase the background and the fErase field of the PAINTSTRUCT structure will be TRUE (nonzero) after you call BeginPaint. 然而,如果你的程序通过调用 InvalidateRect 使客户区的一个矩形无效,那么该函数的最后一个参数指定你是否希望背景被清除。如果这个参数是 FALSE(也就是 0),那么 Windows 将不清除背景,并且在你调用 BeginPaint 之后 PAINTSTRUCT 结构的 fErase 域将是 TRUE(非零)。 The rcPaint field of the PAINTSTRUCT structure is a structure of type RECT. As you learned in Chapter 3, the RECT structure defines a rectangle with four fields named left, top, right, and bottom. The rcPaint field in the PAINTSTRUCT structure defines the boundaries of the invalid rectangle, as shown in Figure 4-1. The values are in units of pixels relative to the upper left corner of the client area. The invalid rectangle is the area that you should repaint. PAINTSTRUCT 结构的 rcPaint 域是 RECT 类型的结构。正如你在第三章中了解到的,RECT 结构定义了一个矩形,其四个域分别是 left,top,right 和 bottom。PAINTSTRUCT 结构的 rcPaint 域定义了无效矩形的边界,如图 4-1 所示。这些值是以像素为单位并相对于客户区的左上角。该无效矩形是你应该重画的区域。
Figure 4-1. 无效矩形的边界 The rcPaint rectangle in PAINTSTRUCT is not only the invalid rectangle; it is also a "clipping" rectangle. This means that Windows restricts painting to within the clipping rectangle. More precisely, if the invalid region is not rectangular, Windows restricts painting to within that region. To paint outside the update rectangle while processing WM_PAINT messages, you can make this call: PAINTSTRUCT 中的 rcPaint 矩形不仅仅是无效矩形;它也是一个“剪取”的矩形。这意味着 Windows 将绘制限制在剪取矩形之间。更确切地说,如果无效区域不是矩形的,那么 Windows 将绘制限制在该区域之间。当处理 WM_PAINT 消息时,为了绘制更新矩形外面部分,你可以这样调用: InvalidateRect (hwnd, NULL, TRUE) ; before calling BeginPaint. This invalidates the entire client area and causes BeginPaint to erase the background. A FALSE value in the last argument will not erase the background. Whatever was there will stay.在调用 BeginPaint 之前。这使整个客户区无效并引起 BeginPaint 清除背景。最后一个参数中的 FALSE 值将不清除背景。无论那儿是什么都将保留。 It is usually most convenient for a Windows program to simply repaint the entire client area whenever it receives a WM_PAINT message, regardless of the rcPaint structure. For example, if part of the display output in the client area includes a circle but only part of the circle falls within the invalid rectangle, it makes little sense to draw only the invalid part of the circle. Draw the whole circle. When you use the device context handle returned from BeginPaint, Windows will not paint outside the rcPaint rectangle anyway. 对于 Windows 程序来说无论何时接收到 WM_PAINT 消息(更确切地说是 rcPaint 结构)都简单的重画整个客户区通常会更方便。例如,如果客户区的显示输出的一部分包括一个圆,但只有部分圆落在无效矩形中,那么它会使仅绘制圆的无效部分变得毫无意义。绘制整个圆。当你使用 BeginPaint 返回的关联设备句柄时,Windows 不会绘制 rcPaint 矩形外的任何部分。 In the HELLOWIN program in Chapter 2, we didn't care about invalid rectangles when processing the WM_PAINT message. If the area where the text was displayed happened to be within the invalid rectangle, DrawText restored it. If not, then at some point during processing of the DrawText call Windows determined it didn't need to write anything on the display. But this determination takes time. A programmer concerned about performance and speed (and that includes all of us, I hope) will want to use the invalid rectangle during processing of the WM_PAINT message to avoid unnecessary GDI calls. This is particularly important if painting requires accessing disk files such as bitmaps. 在第二章的 HELLOWIN 程序中,当处理 WM_PAINT 消息时我们并不关心无效矩形。如果显示文本的区域碰巧在无效矩形中,那么 DrawText 恢复它。否则,那么在处理 DrawText 调用期间的某个时刻,Windows 决定它不需要在显示器上输出任何东西。但是这个决定需要时间。关注性能和速度的程序员(我希望包括我们全部)将希望在处理 WM_PAINT 消息期间使用无效矩形以避免不必要的 GDI 调用。如果绘制需要访问诸如位图的磁盘文件,那么这是特别重要的。 Getting a Device Context Handle: Method Two获得关联设备句柄:方法二 Although it is best to structure your program so that you can update the entire client area during the WM_PAINT message, you may also find it useful to paint part of the client area while processing messages other than WM_PAINT. Or you may need a device context handle for other purposes, such as obtaining information about the device context. 尽管最好是组织你的程序以便你可以在 WM_PAINT 消息期间更新整个客户区,但是你可能也发现在处理非 WM_PAINT 消息期间绘制部分客户区是有用的。或者你可能需要关联设备句柄用于其他目的,比如获得关联设备的信息。 To get a handle to the device context of the client area of the window, you call GetDC to obtain the handle and ReleaseDC after you're done with it:为获得窗口客户区的关联设备句柄,你调用 GetDC 以获得句柄并在这么做之后调用 ReleaseDC: hdc = GetDC (hwnd) ;[use GDI functions]ReleaseDC (hwnd, hdc) ; Like BeginPaint and EndPaint, the GetDC and ReleaseDC functions should be called in pairs. When you call GetDC while processing a message, you should call ReleaseDC before you exit the window procedure. Do not call GetDC in one message and ReleaseDC in another. 像 BeginPaint 和 EndPaint 一样,GetDC 和 ReleaseDC 函数应该成对调用。当你在处理消息时调用 GetDC,你应该在退出窗口处理程序之前调用 ReleaseDC。不要再一个消息中调用 GetDC 而在另一中调用 ReleaseDC。 Unlike the device context handle returned from BeginPaint, the device context handle returned from GetDC has a clipping rectangle equal to the entire client area. You can paint on any part of the client area, not merely on the invalid rectangle (if indeed there is an invalid rectangle). Unlike BeginPaint, GetDC does not validate any invalid regions. If you need to validate the entire client area, you can call 不像由 BeginPaint 返回的关联设备句柄,由 GetDC 返回的关联设备句柄有一个等于整个客户区的剪裁的矩形。你可以在客户区的任何部分上绘图,不仅仅在无效矩形上(如果的确有一个无效矩形)。不像 BeginPaint,GetDC 不是任何无效区域有效。如果你需要使整个客户区有效,那么你可以调用 ValidateRect (hwnd, NULL) ; Generally, you'll use the GetDC and ReleaseDC calls in response to keyboard messages (such as in a word processing program) or mouse messages (such as in a drawing program). This allows the program to draw on the client area in prompt reaction to the user's keyboard or mouse input without deliberately invalidating part of the client area to generate WM_PAINT messages. However, even if you paint during messages other than WM_PAINT, your program must still accumulate enough information to be able to update the display whenever you do receive a WM_PAINT message. 通常,你将使用 GetDC 和 ReleaseDC 调用响应键盘消息(比如文字处理程序)或鼠标消息(比如绘图程序)。这允许程序在客户区上绘图以迅速响应用户的键盘或鼠标输入而不用故意地使客户区部分无效以产生 WM_PAINT 消息。然而,即使你在非 WM_PAINT 消息期间绘图,你的程序仍然必须积累足够的信息以便能够在你接收到 WM_PAINT 消息的任何时候更新显示。 A function similar to GetDC is GetWindowDC. While GetDC returns a device context handle for writing on the client area of the window, GetWindowDC returns a device context handle that lets you write on the entire window. For example, your program can use the device context handle returned from GetWindowDC to write on the window's title bar. However, your program would also have to process WM_NCPAINT ("nonclient paint") messages as well. 类似于 GetDC 的一个函数是 GetWindowDC。当 GetDC 返回用于在窗口的客户区上输出的关联设备句柄时,GetWindowDC 返回允许你在整个窗口上输出的关联设备句柄。例如,你的程序可以使用由 GetWindowDC 返回的关联设备句柄来在窗口的标题栏上输出。然而,你的程序最好也得处理 WM_NCPAINT(“noclient paint”)消息。 TextOut: The DetailsTextOut:细节 TextOut is the most common GDI function for displaying text. Its syntax is TextOut 是最常用的显示文本的 GDI 函数。它的语法是 TextOut (hdc, x, y, psText, iLength) ;Let's examine this function in more detail. 让我们更详细地考察这个函数。 The first argument is the handle to the device context—either the hdc value returned from GetDC or the hdc value returned from BeginPaint during processing of a WM_PAINT message. 第一个参数是关联设备句柄——由 GetDC 返回的 hdc 值或在处理 WM_PAINT 消息期间由 BeginPaint 返回的 hdc 值。 The attributes of the device context control the characteristics of this displayed text. For instance, one attribute of the device context specifies the text color. The default color (we discover with some degree of comfort) is black. The default device context also defines a text background color, and this is white. When a program writes text to the display, Windows uses this background color to fill in the rectangular space surrounding each character, called the "character box." 关联设备的属性控制了该显示文本的特征。例如,关联设备的属性之一指定了文本颜色。缺省颜色(我们发现某种程度的安慰)是黑色。缺省的关联设备也定义文本背景颜色,并且这是白色。当程序输出文本到显示器时,Windows 用这个背景色填充每个字符周围的矩形空间,叫做“字符框”。 The text background color is not the same background you set when defining the window class. The background in the window class is a brush—which is a pattern that may or may not be a pure color—that Windows uses to erase the client area. It is not part of the device context structure. When defining the window class structure, most Windows applications use WHITE_BRUSH so that the default text background color in the default device context is the same color as the brush Windows uses to erase the background of the client area. 文本背景色和你在定义窗口类时设置的背景不一样。窗口类中的背景是画刷——它是可能是或可能不是纯色的图案——Windows 用它清除客户区。它不是关联设备结构的一部分。当定义窗口类结构时,大多数 Windows 应用程序使用 WHITE_BRUSH 以至缺省关联设备中的缺省文本背景色是和 Windows 用来清除客户区背景的画刷相同的颜色。 The psText argument is a pointer to a character string, and iLength is the number of characters in the string. If psText points to a Unicode character string, then the number of bytes in the string is double the iLength value. The string should not contain any ASCII control characters such as carriage returns, linefeeds, tabs, or backspaces. Windows displays these control characters as boxes or solid blocks. TextOut does not recognize a zero byte (or for Unicode, a zero short integer) as denoting the end of a string. The function uses the iLength argument to determine the string's length. psText 参数是指向字符串的指针,而 iLength 是字符串中字符的数量。如果 psText 指向 Unicode 字符串,那么字符串中字节数是 iLength 值的两倍。字符串不应该包含任何 ASCII 控制字符,比如回车键,换行键,制表符或退格键。Windows 将这些控制字符显示为框或实心块。TextOut 不会把零字节(或者对于 Unicode,是零 short integer)看作是字符串结尾的表示。函数用 iLength 参数确定字符串的长度。 The x and y arguments to TextOut define the starting point of the character string within the client area. The x value is the horizontal position; the y value is the vertical position. The upper left corner of the first character is positioned at the coordinate point (x, y). In the default device context, the origin (that is, the point where x and y both equal 0) is the upper left corner of the client area. If you use zero values for x and y in TextOut, the character string starts flush against the upper left corner of the client area. TextOut 的 x 和 y 参数定义了客户区中字符串的开始点。x 值是水平点;y 值是垂直点。第一个字符的左上角位于坐标点(x,y)。在缺省关联设备中,原点(也就是说,x 和 y 都等于 0 的点)是客户区的左上角。如果你在 TextOut 中将零值用于 x 和 y,那么字符串从客户区的左上角开始输出。 When you read the documentation of a GDI drawing function such as TextOut, you'll find that the coordinates passed to the function are usually documented as "logical coordinates." What this means exactly we'll examine in more detail in Chapter 5. For now, be aware that Windows has a variety of "mapping modes" that govern how the logical coordinates specified in GDI drawing functions are translated to the physical pixel coordinates of the display. The mapping mode is defined in the device context. The default mapping mode is called MM_TEXT (using the identifier defined in the WINGDI.H header file). Under the MM_TEXT mapping mode, logical units are the same as physical units, which are pixels, relative to the upper left corner of the client area. Values of x increase as you move to the right in the client area, and values of y increase as you move down in the client area. (See Figure 4-2.) The MM_TEXT coordinate system is identical to the coordinate system that Windows uses to define the invalid rectangle in the PAINTSTRUCT structure. (Things are not quite as convenient with the other mapping modes, however.) 当你阅读诸如 TextOut 的 GDI 函数的文档时,你将发现传递给函数的坐标通常都注明为“逻辑坐标”。它正确的含义我们将在第 5 章中作更详细地考察。现在,注意 Windows 有各种管理在 GDI 绘图函数中指定的逻辑坐标是如何转换为显示器的物理像素坐标的“映射模式”。映射模式定义在关联设备中。缺省的映射模式叫做 MM_TEXT(用在 WINGDI.H 头文件中定义的标识符)。在 MM_TEXT 映射模式下,逻辑单位和物理单位一样,都是像素,相对于客户区的左上角。当你在客户区上右移时 x 值增加,而当你在客户区上下移时 y 值增加。(参看图 4-2。)MM_TEXT 坐标系统和 Windows 在 PAINTSTRUCT 结构中用来定义无效矩形的坐标系统一样。(然而,用其它映射模式不是十分方便。) Figure 4-2. The x-coordinate and y-coordinate in the MM_TEXT mapping mode. 图 4-2 MM_TEXT 映射模式中 x 坐标和 y 坐标 The device context also defines a clipping region. As you've seen, the default clipping region is the entire client area for a device context handle obtained from GetDC and the invalid region for the device context handle obtained from BeginPaint. When you call TextOut, Windows will not display any part of the character string that lies outside the clipping region. If a character is partly within the clipping region, Windows displays only the portion of the character inside the region. Writing outside the client area of your window isn't easy to do, so don't worry about doing it inadvertently. 关联设备也定义剪裁区域。正如你曾看到的,缺省的剪裁区域是用于由 GetDC 获得的关联设备句柄的整个客户区和用于由 BeginPaint 获得的关联设备句柄的无效区域。当你调用 TextOut 时,Windows 不显示位于剪裁区域外的字符串的任何部分。如果字符串部分在剪裁区域内,那么 Windows 只显示在区域内的字符串部分。输出到你的窗口的客户区外不是容易做到的,因此不用担心不注意地这样做。 The System Font系统字体 The device context also defines the font that Windows uses when you call TextOut to display text. The default is a font called the "system font" or (using the identifier in the WINGDI.H header file) SYSTEM_FONT. The system font is the font that Windows uses by default for text strings in title bars, menus, and dialog boxes. 关联设备也定义了当你调用 TextOut 显示文本时 Windows 使用的字体。缺省的是叫做“系统字体”或(使用在 WINGDI.H 头文件中的标识符)SYSTEM_FONT 的字体。系统字体是 Windows 缺省用于标题栏,菜单和对话框中的文本字符串的字体。 In the early days of Windows, the system font was a fixed-pitch font, which means that all the characters had the same width, much like a typewriter. However, beginning with Windows 3.0, the system font became a variable-pitch font, which means that different characters have different widths. A "W" is wider than an "i", for example. It has been well established by studies in reading that text printed with variable-pitch fonts is more readable than fixed-pitch font texts. It seems to have something to do with the letters being closer together, allowing the eyes and mind to more clearly see entire words rather than individual letters. As you might imagine, the change from fixed-pitch fonts to variable-pitch fonts broke a lot of early Windows code and required that programmers learn some new techniques for working with text. 在早期的 Windows 中,系统字体是等宽字体,这意味着所有的字符有相同的宽度,更像打字机。然而,从 Windows 3.0 开始,系统字体变成可变宽度字体,这意味着不同的字符有不同的宽度。例如,“A”比“i”宽。阅读用可变字体打印的文本比用等宽字体的文本更易读,这已被确定。这似乎和字母靠得更近,允许眼睛和注意力更清楚地看到整个单词而不是单独的字母有关。正如你可能想象的,从等宽字体到可变宽度字体的改变打破了大量早期的 Windows 代码并要求程序员学习一些新的使用文本的技术。 The system font is a "raster font," which means that the characters are defined as blocks of pixels. (In Chapter 17, we'll work with TrueType fonts, which are defined by scaleable outlines.) To a certain extent, the size of the characters in the system font is based on the size of the video display. The system font is designed to allow at least 25 lines of 80-character text to fit on the screen. 系统字体是“光栅字体”,这意味着字符被定义为像素块。(在第 17 章中,我们将使用 TrueType 字体,它是由可缩放轮廓定义的。)为了确定的范围,系统字体中字符的大小基于视频显示器的尺寸。系统字体被设计成允许屏幕上至少适合 25 行,每行 80 个字符。 The Size of a Character字符大小 To display multiple lines of text by using the TextOut function, you need to know the dimensions of characters in the font. You can space successive lines of text based on the height of the characters, and you can space columns of text across the client area based on the average width of the characters. 为了通过使用 TextOut 函数使用多行文本,你必须知道该字体中字符的尺寸。你可以隔开基于字符高度的连续的行,并且你可以隔开基于字符平均宽度的跨过客户区的文本列。 What is the height and average width of characters in the system font? Well, I'm not going to tell you. Or rather, I can't tell you. Or rather, I could tell you, but I might be wrong. The problem is that it all depends on the pixel size of the video display. Windows requires a minimum display size of 640 by 480, but many users prefer 800 by 600 or 1024 by 768. In addition, for these larger display sizes, Windows allows the user to select different sized system fonts. 系统字体中字符的高度和平均宽度是什么呢?噢,我不打算告诉你。或者更确切地说,我不可以告诉你。或者再确切地说,我可以告诉你,但我可能是错的。问题是它们都依赖于视频显示器的像素尺寸。Windows 要求最小的显示尺寸是 640×480,但是许多用户更喜欢 800×600 或 1024×768。另外,对于这些更大的显示尺寸,Windows 允许用户选择不同尺寸的系统字体。 Just as a program can determine information about the sizes (or "metrics") of user interface items by calling the GetSystemMetrics function, a program can determine font sizes by calling GetTextMetrics. GetTextMetrics requires a handle to a device context because it returns information about the font currently selected in the device context. Windows copies the various values of text metrics into a structure of type TEXTMETRIC defined in WINGDI.H. The TEXTMETRIC structure has 20 fields, but we're interested in only the first seven: 正如程序通过 GetSystemMetrics 函数可以确定关于用户界面项目的尺寸(或“metrics”)的信息,程序通过调用 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 ; [other structure fields]}TEXTMETRIC, * PTEXTMETRIC ;The values of these fields are in units that depend on the mapping mode currently selected for the device context. In the default device context, this mapping mode is MM_TEXT, so the dimensions are in units of pixels. 这些域值的单位依赖于当前为关联设备选择的映射模式。在缺省关联设备中,这个映射模式是 MM_TEXT,因此尺寸是以像素为单位。 To use the GetTextMetrics function, you first need to define a structure variable, commonly called tm: 为了使用 GetTextMetrics 函数,你首先需要定义一个结构变量,通常叫做 tm: TEXTMETRIC tm ;When you need to determine the text metrics, you get a handle to a device context and call GetTextMetrics: 当你需要确定文本尺寸时,你获得关联设备句柄并调用 GetTextMetrics: hdc = GetDC (hwnd) ;GetTextMetrics (hdc, &tm) ;ReleaseDC (hwnd, hdc) ; You can then examine the values in the text metric structure and probably save a few of them for future use. 然后你可以检查文本尺寸结构中的值并可能保存它们中的一些以便将来使用。 Text Metrics: The Details文本尺寸:细节 The TEXTMETRIC structure provides various types of information about the font currently selected in the device context. However, the vertical size of a font is defined by only five fields of the structure, four of which are shown in Figure 4-3.TEXTMETRIC 结构提供了关于当前选进关联设备的字体的各种类型的信息。然而,字体的垂直尺寸只由该结构的五个域定义,它们中的四个在图 4-3 中显示。 Figure 4-3. Four values defining vertical character sizes in a font. 图 4-3 定义字体中垂直字符尺寸的四个值 The most important value is tmHeight, which is the sum of tmAscent and tmDescent. These two values represent the maximum vertical extents of characters in the font above and below the baseline. The term "leading" refers to space that a printer inserts between lines of text. In the TEXTMETRIC structure, internal leading is included in tmAscent (and thus in tmHeight) and is often the space in which accent marks appear. The tmInternalLeading field could be set to 0, in which case accented letters are made a little shorter so that the accent marks fit within the ascent of the character. 最重要的值是 tmHeight,它是 tmAscent 和 tmDescent 之和。这两个值指出字体在基线上下间字符的最大垂直范围。术语“leading”指打印机在文本行之间插入的空间。在 TEXTMETRIC 结构中,internal leading 被包括在 tmAscent中(因此也在 tmHeight中),并且常常是重音标志出现的空间。tmInternalLeading 域可以被设为 0,在这种情况下重音字母被创建的更短些以便重音标志适合在字符的 ascent 内。 The TEXTMETRIC structure also includes a field named tmExternalLeading, which is not included in the tmHeight value. This is an amount of space that the designer of the font suggests be added between successive rows of displayed text. You can accept or reject the font designer's suggestion for including external leading when spacing lines of text. In the system fonts that I've encountered recently, tmExternalLeading has been zero, which is why I didn't include it in Figure 4-3. (Despite my vow not to tell you the dimensions of a system font, Figure 4-3 is accurate for the system font that Windows uses by default for a 640 by 480 display.) TEXTMETRIC 结构也包括叫做 tmExternalLeading 的域,它不包括在 tmHeight 值中。这是字体设计者建议在连续的显示文本行之间添加的大量的空间。你可以接受或拒绝字体设计者对于在分隔文本行时包括 external leading 的建议。在我最近曾遇到的系统字体中,tmExternalLeading 是零,这就是我没有在图 4-3 中包括它的原因。(尽管我发誓不告诉你系统字体的尺寸,但是图 4-3 对于 Windows 缺省用于 640×480 显示的系统字体来说是正确的。) The TEXTMETRIC structure contains two fields that describe character widths: the tmAveCharWidth field is a weighted average of lowercase characters, and tmMaxCharWidth is the width of the widest character in the font. For a fixed-pitch font, these values are the same. (For the font illustrated in Figure 4-3, these values are 7 and 14, respectively.) TEXTMETRIC 结构包含两个描述字符宽度的域:tmAveCharWidth 域是小写字符平均宽度,而 tmMaxCharWidth 是字体中最宽字符的宽度。对等宽字体来说,这两个值是一样的。(对图 4-3 中图解的字体来说,这两个值分别是 7 和 14。) The sample programs in this chapter will require another character width—the average width of uppercase letters. You can calculate this fairly accurately as 150% of tmAveCharWidth. 本章中的示例程序将需要另一个字符宽度——大写字母的平均宽度。你可以非常精确地把它看作是 tmAveCharWidth 的 150%。 It's important to realize that the dimensions of a system font are dependent on the pixel size of the video display on which Windows runs and, in some cases, on the system font size the user has selected. Windows provides a device-independent graphics interface, but you have to help. Don't write your Windows programs so that they guess at character dimensions. Don't hard-code any values. Use the GetTextMetrics function to obtain this information. 意识到系统字体的尺寸依赖于 Windows 运行的视频显示器的像素尺寸,并且在某些情况下,依赖于用户选择的系统字体尺寸是重要的。Windows 提供设备无关的图形界面,但是你不得不帮助它。不要编写你的 Windows 程序以至它们猜测字符尺寸。不要硬编码任何值。用 GetTextMetrics 函数获得该信息。 Formatting Text格式化文本 Because the dimensions of the system font do not change during a Windows session, you need to call GetTextMetrics only once when your program executes. A good place to make this call is while processing the WM_CREATE message in the window procedure. The WM_CREATE message is the first message the window procedure receives. Windows calls your window procedure with a WM_CREATE message when you call CreateWindow in WinMain. Suppose you're writing a Windows program that displays several lines of text running down the client area. You'll want to obtain values for the character width and height. Within the window procedure you can define two variables to save the average character width (cxChar) and the total character height (cyChar): 因为在 Windows 会话期间系统字体的尺寸没有改变,所以你只需要在程序执行时调用一次 GetTextMetrics。进行该调用的一个好地方是在窗口处理函数中处理 WM_CREATE 消息期间。WM_CREATE 消息是窗口处理函数接收到的第一个消息。当你在 WinMain 中调用 CreateWindow 时 Windows 用 WM_CREATE 消息调用你的窗口处理函数。假设你正在编写显示几个在客户区中文本行的 Windows 程序。你将想获得字符的宽度和高度值。在窗口处理函数内你可以定义两个变量以保存字符平均宽度(cxChar)和整个字符高度(cyChar): static int cxChar, cyChar ;The prefix c added to the variables names stands for "count," and in this case means a count of (or number of) pixels. In combination with x or y, the prefix refers to a width or height. These variables are defined as static because they must be valid when the window procedure processes other messages, such as WM_PAINT. Or you can define the variables globally outside of any function. 添加到变量名的前缀 c 代表“count”,并且在这里意味着像素的计数(或数量)。与 x 或 y 结合,该前缀指的是宽度或高度。这些变量之所以被定义为 static,是因为它们在窗口处理函数处理其它诸如 WM_PAINT 消息期间必须是有效的。或者你可以定义任何函数外面的全局变量。 Here's the WM_CREATE code to obtain the width and height of characters in the system font: 这里是获得系统字体中字符的宽度和高度的 WM_CREATE 代码:case WM_CREATE: hdc = GetDC (hwnd) ; GetTextMetrics (hdc, &tm) ; cxChar = tm.tmAveCharWidth ; cyChar = tm.tmHeight + tm.tmExternalLeading ; ReleaseDC (hwnd, hdc) ; return 0 ; Notice that I've included the tmExternalLeading field in the calculation of cyChar. Even though this field is 0 in the system fonts I've seen lately, it should be included if it's ever nonzero because it makes for more readable line spacing. Each successive line of text is displayed cyChar pixels further down the window. 注意我将 tmExternalLeading 域包括在 cyChar 的计算中。即使该域在我们最近看到的系统字体中是 0,即使它是非零值也应该被包括,因为这使行间隔更易读。每个连续的文本行沿着窗口向下每隔 cyChar 像素显示。 You'll often find it necessary to display formatted numbers as well as simple character strings. As I discussed in Chapter 2, you can't use the traditional tool for this job (the beloved printf function), but you can use sprintf and the Windows version of sprintf, wsprintf. These functions work just like printf except that they put the formatted string into a character string. You can then use TextOut to write the string to the display. Very conveniently, the value returned from sprintf and wsprintf is the length of the string. You can pass that value to TextOut as the iLength argument. This code shows a typical wsprintf and TextOut combination: 你将常发现有必要显示格式化数字和简单的字符串。正如我在第 2 章中讨论的,你不可以将传统的工具用于这项工作(心爱的 printf 函数),但是你可以用 sprintf 和 Windows 版本的 sprintf,wsprintf。这些函数就像 printf 一样工作,除了它们将格式化字符串放进字符串中之外。你可以用 TextOut 将字符串输出到显示器。非常方便地,由 sprintf 和 wsprintf 返回的值是字符串的长度。你可以将该值传递给 TextOut 作为 iLength 参数。这段代码显示了典型的 wsprintf 和 TextOut 组合: int iLength ;TCHAR szBuffer [40] ;[ other program lines ]iLength = wsprintf (szBuffer, TEXT ("The sum of %i and %i is %i"), iA, iB, iA + iB) ;TextOut (hdc, x, y, szBuffer, iLength) ;For something as simple as this, you could dispense with the iLength definition and combine the two statements into one: 对于一些和这一样简单的情况,你可以省去 iLength 定义并将两个语句合为一个: TextOut (hdc, x, y, szBuffer,wsprintf (szBuffer, TEXT ("The sum of %i and %i is %i"),iA, iB, iA + iB)) ; It ain't pretty, but it works. 它不好看,但是它可以工作。 Putting It All Together把它放到一起 Now we seem to have everything we need to write a simple program that displays multiple lines of text on the screen. We know how to get a handle to a device context during the WM_PAINT message, how to use the TextOut function, and how to space text based on the size of a single character. The only thing left for us to do is to display something interesting. 现在我们似乎有我们编写一个简单的在屏幕上显示多行文本的程序所需要的一切。我们知道在 WM_PAINT 消息期间如何获得关联设备句柄,如何使用 TextOut 函数,还有如何分隔基于单一字符的尺寸的文本。留给我们唯一要做的事是显示一些有趣的东西。 In the previous chapter, we took a little peek at the interesting information available from the Windows GetSystemMetrics function. The function returns information about the size of various graphical items in Windows, such as icons, cursors, title bars, and scroll bars. These sizes vary with the display adapter and driver. GetSystemMetrics is an important function for achieving device-independent graphical output in your program. 在前一章中,我们大概看到来自 Windows GetSystemMetrics 函数的有趣的可用的信息。该函数返回关于 Windows 中诸如图标,指针,标题栏和滚动条的各种图形项目的尺寸的信息。这些尺寸随显示卡和驱动程序变化。GetSystemMetrics 是一个用于在你的程序中完成设备无关图形输出的重要函数。 The function requires a single argument called an "index." The index is one of 75 integer identifiers defined in the Windows header files. (The number of identifiers has increased with each release of Windows; the programmer's documentation in Windows 1.0 listed only 26 of them.) GetSystemMetrics returns an integer, usually the size of the item specified in the argument. 这个函数需要叫做“index”的单一参数。这个索引是在 Windows 头文件中定义的 75 个整型标识符之一。(这些标识符的数量随每个 Windows 版本有所增加;Windows 1.0 中的程序员文档只列出了它们中的 26 个。)GetSystemMetrics 返回一个整数,通常是参数中指定的项目的尺寸。 Let's write a program that displays some of the information available from the GetSystemMetrics calls in a simple one-line-per-item format. Working with this information is easier if we create a header file that defines an array of structures containing both the Windows header-file identifiers for the GetSystemMetrics index and the text we want to display for each value returned from the call. This header file is called SYSMETS.H and is shown in Figure 4-4. 让我们编写一个以简单的每个项目一行的格式显示一些来自 GetSystemMetrics 的可用的信息的程序。如果我们创建一个定义一些包括用于 GetSystemMetrics 索引的 Windows 头文件标识符和我们想为每个由该调用返回的值显示的文本的结构的头文件,那么使用这些信息是容易的,该头文件叫做 SYSMETS.H 并在图 4-4 中显示。 Figure 4-4. The SYSMETS.H file. 图 4-4 SYSMETS.H 文件SYSMETS.H/*-----------------------------------------------
SYSMETS.H -- System metrics display structure
-----------------------------------------------*/
#define NUMLINES ((int) (sizeof sysmetrics / sizeof sysmetrics [0]))
struct
{
int iIndex ;
TCHAR * szLabel ;
TCHAR * szDesc ;
}
sysmetrics [] =
{
SM_CXSCREEN, TEXT ("SM_CXSCREEN"),
TEXT ("Screen width in pixels"),
SM_CYSCREEN, TEXT ("SM_CYSCREEN"),
TEXT ("Screen height in pixels"),
SM_CXVSCROLL, TEXT ("SM_CXVSCROLL"),
TEXT ("Vertical scroll width"),
SM_CYHSCROLL, TEXT ("SM_CYHSCROLL"),
TEXT ("Horizontal scroll height"),
SM_CYCAPTION, TEXT ("SM_CYCAPTION"),
TEXT ("Caption bar height"),
SM_CXBORDER, TEXT ("SM_CXBORDER"),
TEXT ("Window border width"),
SM_CYBORDER, TEXT ("SM_CYBORDER"),
TEXT ("Window border height"),
SM_CXFIXEDFRAME, TEXT ("SM_CXFIXEDFRAME"),
TEXT ("Dialog window frame width"),
SM_CYFIXEDFRAME, TEXT ("SM_CYFIXEDFRAME"),
TEXT ("Dialog window frame height"),
SM_CYVTHUMB, TEXT ("SM_CYVTHUMB"),
TEXT ("Vertical scroll thumb height"),
SM_CXHTHUMB, TEXT ("SM_CXHTHUMB"),
TEXT ("Horizontal scroll thumb width"),
SM_CXICON, TEXT ("SM_CXICON"),
TEXT ("Icon width"),
SM_CYICON, TEXT ("SM_CYICON"),
TEXT ("Icon height"),
SM_CXCURSOR, TEXT ("SM_CXCURSOR"),
TEXT ("Cursor width"),
SM_CYCURSOR, TEXT ("SM_CYCURSOR"),
TEXT ("Cursor height"),
SM_CYMENU, TEXT ("SM_CYMENU"),
TEXT ("Menu bar height"),
SM_CXFULLSCREEN, TEXT ("SM_CXFULLSCREEN"),
TEXT ("Full screen client area width"),
SM_CYFULLSCREEN, TEXT ("SM_CYFULLSCREEN"),
TEXT ("Full screen client area height"),
SM_CYKANJIWINDOW, TEXT ("SM_CYKANJIWINDOW"),
TEXT ("Kanji window height"),
SM_MOUSEPRESENT, TEXT ("SM_MOUSEPRESENT"),
TEXT ("Mouse present flag"),
SM_CYVSCROLL, TEXT ("SM_CYVSCROLL"),
TEXT ("Vertical scroll arrow height"),
SM_CXHSCROLL, TEXT ("SM_CXHSCROLL"),
TEXT ("Horizontal scroll arrow width"),
SM_DEBUG, TEXT ("SM_DEBUG"),
TEXT ("Debug version flag"),
SM_SWAPBUTTON, TEXT ("SM_SWAPBUTTON"),
TEXT ("Mouse buttons swapped flag"),
SM_CXMIN, TEXT ("SM_CXMIN"),
TEXT ("Minimum window width"),
SM_CYMIN, TEXT ("SM_CYMIN"),
TEXT ("Minimum window height"),
SM_CXSIZE, TEXT ("SM_CXSIZE"),
TEXT ("Min/Max/Close button width"),
SM_CYSIZE, TEXT ("SM_CYSIZE"),
TEXT ("Min/Max/Close button height"),
SM_CXSIZEFRAME, TEXT ("SM_CXSIZEFRAME"),
TEXT ("Window sizing frame width"),
SM_CYSIZEFRAME, TEXT ("SM_CYSIZEFRAME"),
TEXT ("Window sizing frame height"),
SM_CXMINTRACK, TEXT ("SM_CXMINTRACK"),
TEXT ("Minimum window tracking width"),
SM_CYMINTRACK, TEXT ("SM_CYMINTRACK"),
TEXT ("Minimum window tracking height"),
SM_CXDOUBLECLK, TEXT ("SM_CXDOUBLECLK"),
TEXT ("Double click x tolerance"),
SM_CYDOUBLECLK, TEXT ("SM_CYDOUBLECLK"),
TEXT ("Double click y tolerance"),
SM_CXICONSPACING, TEXT ("SM_CXICONSPACING"),
TEXT ("Horizontal icon spacing"),
SM_CYICONSPACING, TEXT ("SM_CYICONSPACING"),
TEXT ("Vertical icon spacing"),
SM_MENUDROPALIGNMENT, TEXT ("SM_MENUDROPALIGNMENT"),
TEXT ("Left or right menu drop"),
SM_PENWINDOWS, TEXT ("SM_PENWINDOWS"),
TEXT ("Pen extensions installed"),
SM_DBCSENABLED, TEXT ("SM_DBCSENABLED"),
TEXT ("Double-Byte Char Set enabled"),
SM_CMOUSEBUTTONS, TEXT ("SM_CMOUSEBUTTONS"),
TEXT ("Number of mouse buttons"),
SM_SECURE, TEXT ("SM_SECURE"),
TEXT ("Security present flag"),
SM_CXEDGE, TEXT ("SM_CXEDGE"),
TEXT ("3-D border width"),
SM_CYEDGE, TEXT ("SM_CYEDGE"),
TEXT ("3-D border height"),
SM_CXMINSPACING, TEXT ("SM_CXMINSPACING"),
TEXT ("Minimized window spacing width"),
SM_CYMINSPACING, TEXT ("SM_CYMINSPACING"),
TEXT ("Minimized window spacing height"),
SM_CXSMICON, TEXT ("SM_CXSMICON"),
TEXT ("Small icon width"),
SM_CYSMICON, TEXT ("SM_CYSMICON"),
TEXT ("Small icon height"),
SM_CYSMCAPTION, TEXT ("SM_CYSMCAPTION"),
TEXT ("Small caption height"),
SM_CXSMSIZE, TEXT ("SM_CXSMSIZE"),
TEXT ("Small caption button width"),
SM_CYSMSIZE, TEXT ("SM_CYSMSIZE"),
TEXT ("Small caption button height"),
SM_CXMENUSIZE, TEXT ("SM_CXMENUSIZE"),
TEXT ("Menu bar button width"),
SM_CYMENUSIZE, TEXT ("SM_CYMENUSIZE"),
TEXT ("Menu bar button height"),
SM_ARRANGE, TEXT ("SM_ARRANGE"),
TEXT ("How minimized windows arranged"),
SM_CXMINIMIZED, TEXT ("SM_CXMINIMIZED"),
TEXT ("Minimized window width"),
SM_CYMINIMIZED, TEXT ("SM_CYMINIMIZED"),
TEXT ("Minimized window height"),
SM_CXMAXTRACK, TEXT ("SM_CXMAXTRACK"),
TEXT ("Maximum draggable width"),
SM_CYMAXTRACK, TEXT ("SM_CYMAXTRACK"),
TEXT ("Maximum draggable height"),
SM_CXMAXIMIZED, TEXT ("SM_CXMAXIMIZED"),
TEXT ("Width of maximized window"),
SM_CYMAXIMIZED, TEXT ("SM_CYMAXIMIZED"),
TEXT ("Height of maximized window"),
SM_NETWORK, TEXT ("SM_NETWORK"),
TEXT ("Network present flag"),
SM_CLEANBOOT, TEXT ("SM_CLEANBOOT"),
TEXT ("How system was booted"),
SM_CXDRAG, TEXT ("SM_CXDRAG"),
TEXT ("Avoid drag x tolerance"),
SM_CYDRAG, TEXT ("SM_CYDRAG"),
TEXT ("Avoid drag y tolerance"),
SM_SHOWSOUNDS, TEXT ("SM_SHOWSOUNDS"),
TEXT ("Present sounds visually"),
SM_CXMENUCHECK, TEXT ("SM_CXMENUCHECK"),
TEXT ("Menu check-mark width"),
SM_CYMENUCHECK, TEXT ("SM_CYMENUCHECK"),
TEXT ("Menu check-mark height"),
SM_SLOWMACHINE, TEXT ("SM_SLOWMACHINE"),
TEXT ("Slow processor flag"),
SM_MIDEASTENABLED, TEXT ("SM_MIDEASTENABLED"),
TEXT ("Hebrew and Arabic enabled flag"),
SM_MOUSEWHEELPRESENT, TEXT ("SM_MOUSEWHEELPRESENT"),
TEXT ("Mouse wheel present flag"),
SM_XVIRTUALSCREEN, TEXT ("SM_XVIRTUALSCREEN"),
TEXT ("Virtual screen x origin"),
SM_YVIRTUALSCREEN, TEXT ("SM_YVIRTUALSCREEN"),
TEXT ("Virtual screen y origin"),
SM_CXVIRTUALSCREEN, TEXT ("SM_CXVIRTUALSCREEN"),
TEXT ("Virtual screen width"),
SM_CYVIRTUALSCREEN, TEXT ("SM_CYVIRTUALSCREEN"),
TEXT ("Virtual screen height"),
SM_CMONITORS, TEXT ("SM_CMONITORS"),
TEXT ("Number of monitors"),
SM_SAMEDISPLAYFORMAT, TEXT ("SM_SAMEDISPLAYFORMAT"),
TEXT ("Same color format flag")
} ;
The program that displays this information is called SYSMETS1. The SYSMETS1.C source code file is shown in Figure 4-5. Most of the code should look familiar by now. The code in WinMain is virtually identical to that in HELLOWIN, and much of the code in WndProc has already been discussed.
显示这些信息的程序叫做 SYSMETS1。SYSMETS1.C 源代码文件在图 4-5中显示。到目前大部分这段代码应该看起来熟悉。WinMain 中的代码实际上和 HELLOWIN 中的一样,并且许多 WndProc 中的代码已经被讨论过。
Figure 4-5. SYSMETS1.C.
图 4-5 SYSMETS1.C
SYSMETS1.C
/*----------------------------------------------------
SYSMETS1.C -- System Metrics Display Program No. 1
(c) Charles Petzold, 1998
----------------------------------------------------*/
#include <windows.h>
#include "sysmets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("SysMets1") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 1"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxChar, cxCaps, cyChar ;
HDC hdc ;
int i ;
PAINTSTRUCT ps ;
TCHAR szBuffer [10] ;
TEXTMETRIC tm ;
switch (message)
{
case WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
for (i = 0 ; i < NUMLINES ; i++)
{
TextOut (hdc, 0, cyChar * i,
sysmetrics[i].szLabel,
lstrlen (sysmetrics[i].szLabel)) ;
TextOut (hdc, 22 * cxCaps, cyChar * i,
sysmetrics[i].szDesc,
lstrlen (sysmetrics[i].szDesc)) ;
SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
TextOut (hdc, 22 * cxCaps + 40 * cxChar, cyChar * i, szBuffer,
wsprintf (szBuffer, TEXT ("%5d"),
GetSystemMetrics (sysmetrics[i].iIndex))) ;
SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
}
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
Figure 4-6 shows SYSMETS1 running on a standard VGA. As you can see from the first two lines in the program's client area, the screen width is 640 pixels and the screen height is 480 pixels. These two values, as well as many of the other values shown by the program, may be different for different types of video displays.
图 4-6 显示了运行于标准 VGA 上的 SYSMETS1。正如你可以从程序的客户区中前两行看到的,屏幕宽度是 640 像素,而屏幕高度是 480 像素。这两个值,和程序显示的许多其它值一样,可能对于不同类型的视频显示器来说是不同的。
Figure 4-6. The SYSMETS1 display.
图 4-6 SYSMETS1 显示
The SYSMETS1.C Window ProcedureSYSMETS1.C 窗口处理函数 The WndProc window procedure in the SYSMETS1.C program processes three messages: WM_CREATE, WM_PAINT, and WM_DESTROY. The WM_DESTROY message is processed in the same way as the HELLOWIN program in Chapter 3. 在 SYSMETS1.C 程序中 WndProc 窗口处理函数处理三个消息:WM_CREATE,WM_PAINT 和 WM_DESTROY。WM_DESTROY 消息和第 3 章中 HELLOWIN 程序一样处理。 The WM_CREATE message is the first message the window procedure receives. Windows generates the message when the CreateWindow function creates the window. During the WM_CREATE message, SYSMETS1 obtains a device context for the window by calling GetDC and gets the text metrics for the default system font by calling GetTextMetrics. SYSMETS1 saves the average character width in cxChar and the total height of the characters (including external leading) in cyChar. WM_CREATE 消息是窗口处理函数接收到的第一个消息。当 CreateWindow 函数创建窗口时 Windows 产生该消息。在 WM_CREATE 消息期间,SYSMETS1 通过调用 GetDC 获得窗口的关联设备句柄,并通过调用 GetTextMetrics 获得缺省系统字体的文本尺寸。SYSMETS1 将平均字符宽度保存在 cxChar 中并将字符高度(包括 external leading)保存在 cyChar 中。 SYSMETS1 also saves an average width of uppercase letters in the static variable cxCaps. For a fixed-pitch font, cxCaps would equal cxChar. For a variable-width font, cxCaps is set to 150 percent of cxChar. The low bit of the tmPitchAndFamily field in the TEXTMETRIC structure is 1 for a variable-width font and 0 for a fixed-pitch font. SYSMETS1 uses this bit to calculate cxCaps from cxChar: SYSMETS1 也将大写字母的平均宽度保存在静态变量 cxCaps 中。对于等宽字体,cxCaps 将等于 cxChar。对于可变宽度字体,cxCaps 被设为 cxChar 的 150%。TEXTMETRIC 结构中的 tmPitchAndFamily 域的低位对于可变宽度字体来说是 1,对于等宽字体来说是 0。SYSMETS1 用这一位从 cxChar 计算 cxCaps: cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;SYSMETS1 does all window painting during the WM_PAINT message. As normal, the window procedure first obtains a handle to the device context by calling BeginPaint. A for statement loops through all the lines of the sysmetrics structure defined in SYSMETS.H. The three columns of text are displayed with three TextOut function calls. In each case, the third argument to TextOut (that is, the y starting position) is set to 在 WM_PAINT 消息期间 SYSMETS1 做所有的窗口绘制。通常,窗口处理函数首先通过调用 BeginPaint 获得关联设备的句柄。for 语句循环经过 SYSMETS.H 中定义的 sysmetrics 结构的所有行。三列文本用三个 TextOut 函数调用显示。在每个情况下,TextOut 的第三个参数(也就是说,y 开始位置)位置被设置为 cyChar * iThis argument indicates the pixel position of the top of the character string relative to the top of the client area. 这个参数指出相对于客户区顶端的字符串顶端的像素位置。 The first TextOut statement displays the uppercase identifiers in the first of the three columns. The second argument to TextOut is 0 to begin the text at the left edge of the client area. The text is obtained from the szLabel field of the sysmetrics structure. I use the Windows function lstrlen to calculate the length of the string, which is required as the last argument to TextOut. 第一个 TextOut 参数显示大写字母在三列中的第一列。TextOut 的第二个参数是 0,以从客户区的左边开始输出文本。文本是从 sysmetrics 结构的 szLabel 域获得。我用 Windows 函数 lstrlen 计算字符串的长度,它是 TextOut 要求的最后一个参数。 The second TextOut statement displays the description of the system metrics value. These descriptions are stored in the szDesc field of the sysmetrics structure. In this case, the second argument to TextOut is set to 第二个 TextOut 语句显示系统尺寸值的描述。这些描述存储在 sysmetrics 结构的 szDesc 域中。在这里,TextOut 的第二个参数被设置为 22 * cxCapsThe longest uppercase identifier displayed in the first column is 20 characters, so the second column must begin at least 20 × cxCaps to the right of the beginning of the first column of text. I use 22 to add a little extra space between the columns. The third TextOut statement displays the numeric values obtained from the GetSystemMetrics function. The variable-width font makes formatting a column of right-justified numbers a little tricky. Fortunately, in all variable-width fonts used today, the digits from 0 through 9 all have the same width. Otherwise, displaying columns of numbers would be monstrous. However, the width of the digits is greater than the width of a space. Numbers can be one or more digits wide, so different numbers can begin at different horizontal positions. 在第一列中显示的最长的大写字母标识符是 20 个字符,因此第二列必须从第一列文本开始处往右至少 20 × cxCaps 开始。我用 22 在列之间添加少量额外空间。第三个 TextOut 语句显示从 GetSystemMetrics 函数获得的数字值。可变宽度的字体使得格式化一列右对齐的数字有点麻烦。幸运的是,在所有今天使用的可变宽度字体中,从 0 到 9 的数字 都有相同的宽度。否则,数字列的显示将是怪异的。然而,数字的宽度比空格的宽度大。数字可以是一个或多个阿拉伯数字的宽度,因此不同的数字可以在不同的水平位置开始。 Wouldn't it be easier if we could display a column of right-justified numbers by specifying the horizontal pixel position where the number ends rather than begins? This is what the SetTextAlign function lets us do (among other things). After SYSMETS1 calls 如果我们通过指定数字结尾而非开头的水平像素位置可以显示右对齐的数字列,那么它将不容易吗?这就是 SetTextAlign 函数让我们做的(在其它事之间)。在 SYSMETS1 之后调用 SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;Windows will interpret the coordinates passed to subsequent TextOut functions as specifying the top-right corner of the text string rather than the top-left corner. The TextOut function to display the column of numbers has its second argument set toWindows 将把传递给连续的 TextOut 函数的坐标看作是指定文本字符串的右上角而非左上角。显示数字列的 TextOut 函数将它的第二个参数设置为 22 * cxCaps + 40 * cxCharThe 40 × cxChar value accommodates the width of the second column and the width of the third column. Following the TextOut function, another call to SetTextAlign sets things back to normal for the next time through the loop. 40 × cxChar 值容纳第二列的宽度和第三列的宽度。在 TextOut 函数之后,另一个 SetTextAlign 调用为下一次经过循环恢复一切。 Not Enough Room不足够的空间 One nasty little problem exists with the SYSMETS1 program: Unless you have a gigantic, big-screen, high-resolution video adapter, you can't see many of the lines in the system metrics lists. If you make the window narrower, you can't even see the values. 一个小问题存在于 SYSMETS1 程序:除非你有巨大的屏幕,高分辨率的视频卡,否则你不可以看到在系统尺寸清单中列出的许多行。如果你使窗口更窄,那么你甚至不可以看到这些值。 SYSMETS1 is not aware of this problem. Otherwise we might have included a message box that said, "Sorry!" It's not aware of the problem because the program doesn't even know how large its client area is. It begins displaying the text at the top of the window and relies on Windows to clip everything that drifts beyond the bottom of the client area. SYSMETS1 不关心这个问题。否则我们可能包括一个消息框说“对不起”!这不是关心的问题,因为程序甚至不知道它的客户区是多大。它在窗口的顶端开始显示文本并且依赖于 Windows 裁减超出客户区底端的部分。 Clearly, this is not desirable. Our first job in solving this problem is to determine how much of the program's output can actually fit within the client area. 显然地,这不是令人想要得。解决这个问题的我们第一个工作是确定程序的输出有多少实际上可以在客户区中容纳。 The Size of the Client Area客户区的尺寸 If you experiment with existing Windows applications, you'll find that window sizes can vary widely. If a window is maximized, the client area occupies nearly the entire video display. The dimensions of a maximized client area are, in fact, available from the GetSystemMetrics call by using arguments of SM_CXFULLSCREEN and SM_CYFULLSCREEN (assuming that the window has only a title bar and no menu). The minimum size of a window can be quite small—sometimes almost nonexistent—virtually eliminating the client area. 如果你有现有的 Windows 应用程序的经验,那么你将发现窗口尺寸可以变得很宽。如果窗口是最大化的,那么客户区几乎占据整个视频显示器。实际上,最大化客户区的尺寸通过用 SM_CXFULLSCREEN 和 SM_CYFULLSCREEN 参数从 GetSystemMetrics 调用可以获得(假设窗口只有标题栏而没有菜单)。窗口最小化的尺寸可以十分小——有时几乎不存在——实际上排除了客户区。 In the last chapter, we used the GetClientRect function for determining the dimensions of the client area. There's nothing really wrong with this function, but it's a bit inefficient to call it every time you need to use this information. A much better method for determining the size of a window's client is to process the WM_SIZE message within your window procedure. Windows sends a WM_SIZE message to a window procedure whenever the size of the window changes. The lParam variable passed to the window procedure contains the width of the client area in the low word and the height in the high word. To save these dimensions, you'll want to define two static variables in your window procedure: 在上一章中,我们将 GetClientRect 函数用于确定客户区的尺寸。用这个函数没有真正的错误,但是每次你需要使用这个信息都调用它是有点效率低的。一个更好的确定窗口的客户区的尺寸的方法是在你的窗口处理函数内处理 WM_SIZE 消息。只要窗口的尺寸改变 Windows 就发送 WM_SIZE 消息给窗口处理函数。传递给窗口处理函数的 lParam 变量包含客户区宽度在低位字,高度在高位字。为了保存这些尺寸,你将想在你的窗口处理函数中定义两个静态变量: static int cxClient, cyClient ;Like cxChar and cyChar, these variables are defined as static because they are set while processing one message and used while processing another message. You handle the WM_SIZE method like so: 像 cxChar 和 cyChar 一样,这些变量之所以被定义为静态变量,是因为它们在处理某个消息时设置而在处理另一个消息时使用。你处理 WM_SIZE 的方法像这样:case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; You'll see code like this in virtually every Windows program. LOWORD and HIWORD are macros that are defined in the Windows header file WINDEF.H. If you're curious, the definitions of these macros look like this: 你将在实际上每个 Windows 程序中看到这样的代码。LOWORD 和 HIWORD 是在 Windows 头文件 WINDEF.H 中定义的宏。如果你很好奇,那么这些宏的定义看起来像这样: #define LOWORD(l) ((WORD)(l))#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))The two macros return WORD values—that is, 16-bit unsigned short integers that range from 0 through 0xFFFF. Typically you'll store these values in 32-bit signed integers. That doesn't involve any conversion problems and makes the values easier to use in any calculations you may later need. 这两个宏都返回 WORD 值——也就是说,范围从 0 到 0xFFFF 的 16 位无符号短整型。典型地你将这些值存储在 32 位符号整型中。那不包括任何转换问题并且使这些值更容易用在你后面可能需要的任何计算中。 In many Windows programs, a WM_SIZE message will eventually be followed by a WM_PAINT message. How do we know this? Because when we define the window class we specify the class style as 在许多 Windows 程序中,WM_SIZE 消息最终将跟在 WM_PAINT 消息后面。我们如何知道它的呢?因为在我们定义窗口类时,我们指定类风格为 CS_HREDRAW | CS_VREDRAWThis class style tells Windows to force a repaint if either the horizontal or vertical size changes. 这个类风格告诉 Windows 如果水平或垂直尺寸改变的话强制重画。 You can calculate the number of full lines of text displayable within the client area with the formula: 你可以用公式 cyClient / cyCharThis can be 0 if the height of the client area is too small to display a full character. Similarly, the approximate number of lowercase characters you can display horizontally within the client area is equal to 计算可以在客户区内显示的所有文本行的数量。同样地,你可以在客户区内垂直显示的小写字符的大概数量等于 cxClient / cxCharIf you determine cxChar and cyChar during the WM_CREATE message, don't worry about dividing by 0 in these calculations. Your window procedure receives a WM_CREATE message when WinMain calls CreateWindow. The first WM_SIZE message comes a little later, when WinMain calls ShowWindow, at which point cxChar and cyChar have already been assigned positive nonzero values. 如果你在 WM_CREATE 消息期间确定 cxChar 和 cyChar,那么不必担心在这些计算中被 0 除。当 WinMain 调用 CreateWindow 时,你的窗口处理函数接收 WM_CREATE 消息。第一个 WM_SIZE 消息来得有点晚,当 WinMain 调用 ShowWindow 时,在这个时候点 cxChar 和 cyChar 已经被赋值为正的非零值。 Knowing the size of the window's client area is the first step in providing a way for the user to move the text within the client area if the client area is not large enough to hold everything. If you're familiar with other Windows-based applications that have similar requirements, you probably know what we need: this is a job for those wonderful inventions known as scroll bars. 知道窗口的客户区的尺寸是如果客户区不足够大容纳所有的东西给用户提供移动客户区内文本的第一步。如果你熟悉其它有类似要求的基于 Windows 的应用程序,那么你可能知道我们需要的:这就是那些极好的叫做滚动条的发明的工作。