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 所示。这些值是以像素为单位并相对于客户区的左上角。该无效矩形是你应该重画的区域。



/*-----------------------------------------------
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 的应用程序,那么你可能知道我们需要的:这就是那些极好的叫做滚动条的发明的工作。