windows程序设计第四章 输出文字

绘制和更新

WM_PAINT消息

Windows通过发送WM_PAINT消息通知窗口消息处理程序,窗口的部分显示区域需要绘制。

在发生下面几种事件之一时,窗口消息处理程序会接收到一个WM_PAINT消息:

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

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

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

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

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

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

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

显示工具提示消息。

有效矩形和无效矩形

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

只有在显示区域的某一部分失效时,窗口才会接受WM_PAINT消息。

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

窗口消息处理程序可以通过呼叫InvalidateRect使显示区域内的矩形无效。通过呼叫GetUpdateRect,可以在任何时候取得无效矩形坐标。

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

GDI 简介

hdc参数是「设备内容句柄」,它是GDI的重要部分。实际上,每个GDI函数都需要将这个句柄作为函数的第一个参数。

设备内容

设备内容句柄是GDI函数的窗口「通行证」。

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

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

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

绘图信息结构

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程序使用白色画刷。

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

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

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

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

InvalidateRect (hwnd, NULL, TRUE) ;

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

通常这是Windows程序在无论何时收到WM_PAINT消息而不考虑rcPaint结构的情况下简单地重画整个显示区域最方便的方法。

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

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

hdc= GetDC (hwnd) ;

//使用GDI函数

ReleaseDC (hwnd, hdc) ;

不要在一个消息中呼叫GetDC却在另一个消息呼叫ReleaseDC

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

ValidateRect (hwnd, NULL) ;

TextOut:细节

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

以下将详细地讨论这个函数。

第一个参数是设备内容句柄,它既可以是GetDC的传回值,也可以是在处理WM_PAINT消息时BeginPaint的传回值。

设备内容的属性控制了被显示的字符串的特征。例如,设备内容中有一个属性指定文字颜色,内定颜色为黑色;内定设备内容还定义了白色的背景。在程序向显示器输出文字时,Windows使用这个背景色来填入字符周围的矩形空间(称为「字符框」)。

该文字背景色与定义窗口类别时设置的背景并不相同,窗口类别中的背景是一个画刷,它是一种纯色或者非纯色组成的画刷,Windows用它来擦除显示区域,它不是设备内容结构的一部分。

psText参数是指向字符串的指针,iLength是字符串中字符的个数。如果psText指向Unicode字符串,则字符串中的字节数就是iLength值的两倍。字符串中不能包含任何ASCII控制字符(如回车、换行、制表或退格),Windows会将这些控制字符显示为实心块。Text0ut不识别作为字符串结束标志的内容为零的字节(对于Unicode,是一个短整数型态的0),而需要由nLength参数指明长度。

TextOut中的xy定义显示区域内字符串的开始位置,x是水平位置,y是垂直位置。字符串中第一个字符的左上角位于坐标点(x,y)。在内定的设备内容中,原点(xy均为0的点)是显示区域的左上角。如果在TextOut中将xy设为0,则将从显示区域左上角开始输出字符串。

Windows有许多「坐标映像方式」,它们用来控制GDI函数指定的逻辑坐标转换为显示器的实际像素坐标的方式。映像方式在设备内容中定义,内定映像方式是MM_TEXT(使用WINGDI.H中定义的标识符)。在MM_TEXT映像方式下,逻辑单位与实际单位相同,都是像素;x的值从左向右递增,y的值从上向下递增(参看图4-2)。MM_TEXT坐标系与WindowsPAINTSTRUCT结构中定义无效矩形时使用的坐标系相同,这为我们带来了很多方便(但是,其它映像方式并非如此)。

系统字体

系统字体是Windows用来在标题栏、菜单和对话框中显示字符串的内定字体。系统字体的字符大小取决于视讯显示器的大小。系统字体设计为至少能在显示器上显示2580列文字。

字符大小

程序可以呼叫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 ;


 

TEXTMETRIC结构还包括一个不包含在tmHeight值中的字段tmExternalLeading。它是字体设计者建议加在横向字符之间的空间大小。在安排文字行之间的空隙时,您可以接受设计者建议的值,也可以拒绝它。在系统字体中tmExternalLeading可以为0

TEXTMETRICS结构包含有描述字符宽度的两个字段,即tmAveCharWidth(小写字母加权平均宽度)和tmMaxCharWidth(字体中最宽字符的宽度)。对于定宽字体,这两个值是相等的。

本章的范例程序还需要另一种字符宽度,即大写字母的平均宽度,这可以用tmAveCharWidth乘以150%大致计算出来。

必须认识到,系统字体的大小取决于Windows所执行的视讯显示器的分辨率,在某些情况下,取决于使用者选取的系统字体的大小。

对于可变宽度字体,TEXTMETRIC结构中的tmPitchAndFamily字段的低位为1,对于固定宽度字体,该值为0。使用这个位从小写字母加权平均宽度cxChar计算大写字母的平均宽度cxCaps:

cxCaps = (tm.tmPitchAndFamily & 1 ? 3 :2) * cxChar / 2 ;

显示区域的大小

最大化了的显示区域的尺寸可以通过以SM_CXFULLSCREEN和SM_CYFULLSCREEN为参数呼叫GetSystemMetrics来获得。

我们使用GetClientRect函数来取得显示区域的大小。使用这个函数没有什么不好,但是在您每次要使用信息时就去呼叫它一遍是没有效率的。确定窗口显示区域大小的更好方法是在窗口消息处理程序中处理WM_SIZE消息。在窗口大小改变时,Windows给窗口消息处理程序发送一个WM_SIZE消息。传给窗口消息处理程序的lParam参数的低字组中包含显示区域的宽度,高字组中包含显示区域的高度。

case WM_SIZE:

cxClient = LOWORD (lParam) ;

cyClient = HIWORD (lParam) ;

LOWORD和HIWORD宏在Windows表头文件WINDEF.H中定义。这些宏的定义看起来像这样:

#define LOWORD(l) ((WORD)(l))

#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))

用如下公式计算可以在显示区域内显示的文字的总行数:

cyClient / cyChar

滚动条

很容易在应用程序中包含水平或者垂直的滚动条,程序写作者只需要在CreateWindow的第三个参数中包括窗口样式(WS)标识符WS_VSCROLL(垂直卷动)和/或WS_HSCROLL(水平卷动)即可。显示区域不包含卷动列所占据的空间。对于特定的显示驱动程序和显示分辨率,垂直卷动列的宽度和水平卷动列的高度是恒定的。如果需要这些值,可以使用GetSystemMetrics呼叫来取得。

Windows负责处理对滚动条的所有鼠标操作,但是,窗口滚动条没有自动的键盘接口。如果想用光标键来完成卷动功能,则必须提供这方面的程序代码。

滚动条的范围和位置

内定情况下,滚动条的范围是从0(顶部或左部)至100(底部或右部),但将范围改变为更方便于程序的数值也是很容易的:

SetScrollRange (hwnd, iBar, iMin, iMax,bRedraw) ;

参数iBar为SB_VERT或者SB_HORZ,iMin和iMax分别是范围的最小值和最大值。如果想要 Windows根据新范围重画滚动条,则设置bRedraw为TRUE(如果在呼叫SetScrollRange后,呼叫了影响滚动条位置的其它函数,则应该将bRedraw设定为FALSE以避免过多的重画)。

您可以使用SetScrollPos在滚动条范围内设置新的卷动方块位置:

SetScrollPos (hwnd, iBar, iPos, bRedraw) ;

参数iPos是新位置,它必须在iMin至iMax的范围内。Windows提供了类似的函数(GetScrollRange和GetScrollPos)来取得滚动条的目前范围和位置。

在程序内使用滚动条时,程序写作者与Windows共同负责维护滚动条以及更新卷动方块的位置。下面是Windows对滚动条的处理:

处理所有滚动条鼠标事件

当使用者在滚动条内单击鼠标时,提供一种「反相显示」的闪烁

当使用者在滚动条内拖动卷动方块时,移动卷动方块

为包含滚动条窗口的窗口消息处理程序发送滚动条消息

以下是程序写作者应该完成的工作:

初始化滚动条的范围和位置

处理窗口消息处理程序的滚动条消息

更新滚动条内卷动方块的位置

更改显示区域的内容以响应对滚动条的更改

滚动条消息

在用鼠标单击滚动条或者拖动卷动方块时,Windows给窗口消息处理程序发送WM_VSCROLL(供上下移动)和WM_HSCROLL(供左右移动)消息。在滚动条上的每个鼠标动作都至少产生两个消息,一条在按下鼠标按钮时产生,一条在释放按钮时产生。

和所有的消息一样,WM_VSCROLL和WM_HSCROLL也带有wParam和lParam消息参数。对于来自作为窗口的一部分而建立的滚动条消息,您可以忽略lParam;它只用于作为子窗口而建立的滚动条(通常在对话框内)。

wParam消息参数被分为一个低字组和一个高字组。wParam的低字组是一个数值,它指出了鼠标对滚动条进行的操作。这个数值被看作一个「通知码」。通知码的值由以SB(代表「scroll bar(滚动条)」)开头的标识符定义。以下是在WINUSER.H中定义的通知码:

#define SB_LINEUP 0 
#define SB_LINELEFT 0 
#define SB_LINEDOWN 1 
#define SB_LINERIGHT 1 
#define SB_PAGEUP 2 
#define SB_PAGELEFT 2 
#define SB_PAGEDOWN 3 
#define SB_PAGERIGHT 3 
#define SB_THUMBPOSITION 4 
#define SB_THUMBTRACK 5 
#define SB_TOP 6 
#define SB_LEFT 6 
#define SB_BOTTOM 7 
#define SB_RIGHT 7 
#define SB_ENDSCROLL 8

 

包含LEFT和RIGHT的标识符用于水平滚动条,包含UP、DOWN、TOP和BOTTOM的标识符用于垂直滚动条。

如果在滚动条的各个部位按住鼠标键,程序就能收到多个滚动条消息。当释放鼠标键后,程序会收到一个带有SB_ENDSCROLL通知码的消息。一般可以忽略这个消息,Windows不会去改变卷动方块的位置,而您可以在程序中呼叫SetScrollPos来改变卷动方块的位置。

当把鼠标的光标放在卷动方块上并按住鼠标键时,您就可以移动卷动方块。这样就产生了带有SB_THUMBTRACK和SB_THUMBPOSITION通知码的滚动条消息。在wParam的低字组是SB_THUMBTRACK时,wParam的高字组是使用者在拖动卷动方块时的目前位置。该位置位于卷动列范围的最小值和最大值之间。在wParam的低字组是SB_THUMBPOSITION时,wParam的高字组是使用者释放鼠标键后卷动方块的最终位置。对于其它的卷动列操作,wParam的高字组应该被忽略。

为了给使用者提供回馈,Windows在您用鼠标拖动卷动方块时移动它,同时您的程序会收到SB_THUMBTRACK消息。然而,如果不通过呼叫SetScrollPos来处理SB_THUMBTRACK或SB_THUMBPOSITION消息,在使用者释放鼠标键后,卷动方块会迅速跳回原来的位置。

程序能够处理SB_THUMBTRACK或SB_THUMBPOSITION消息,但一般不同时处理两者。如果处理SB_THUMBTRACK消息,在使用者拖动卷动方块时您需要移动显示区域的内容。而如果处理SB_THUMBPOSITION消息,则只需在使用者停止拖动卷动方块时移动显示区域的内容。处理SB_THUMBTRACK消息更好一些(但更困难),对于某些型态的数据,您的程序可能很难跟上产生的消息。

WINUSER.H表头文件还包括SB_TOP、SB_BOTTOM、SB_LEFT和SB_RIGHT通知码,指出滚动条已经被移到了它的最小或最大位置。然而,对于作为应用程序窗口一部分而建立的滚动条来说,永远不会接收到这些通知码。

绘图程序的组织

在处理完滚动条消息后,SYSMETS2不更新显示区域,相反,它呼叫InvalidateRect使显示区域失效。这导致Windows将一个WM_PAINT消息放入消息队列中。

然而,Windows将WM_PAINT消息当成低优先级消息,如果系统有许多其它的动作正在发生,那么也许会让您等待一会儿工夫。

如果您希望立即更新无效区域,可以在呼叫InvalidateRect之后呼叫UpdateWindow:

UpdateWindow(hwnd) ;

如果显示区域的任一部分无效,则UpdateWindow将导致Windows用WM_PAINT消息呼叫窗口消息处理程序(如果整个显示区域有效,则不呼叫窗口消息处理程序)。这一WM_PAINT消息不进入消息队列,直接由Windows呼叫窗口消息处理程序。窗口消息处理程序完成更新后立即退出,Windows将控制传回给程序中UpdateWindow呼叫之后的叙述。

//Sysmets.h
#define NUMLINES ((int) (sizeof sysmetrics / sizeof sysmetrics [0]))
struct
{
	int Index ;
	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")
} ;

//Sysmets.cpp

#include 
#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 ("SysMets2") ;
	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) ;
	//Defoe.Tu [email protected] Windows 程序设计 PDF 美化版
	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. 2"),
		WS_OVERLAPPEDWINDOW | WS_VSCROLL,
		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, cyClient, iVscrollPos ;
	HDC hdc ;
	int i, y ;
	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) ;
		SetScrollRange (hwnd, SB_VERT, 0, NUMLINES - 1, FALSE) ;
		SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
		return 0 ;
	case WM_SIZE:
		cyClient = HIWORD (lParam) ;
		return 0 ;
	case WM_VSCROLL:
		switch (LOWORD (wParam))
		{
		case SB_LINEUP:
			iVscrollPos -= 1 ;
			break ;
		case SB_LINEDOWN:
			iVscrollPos += 1 ;
			break ;
		case SB_PAGEUP:
			iVscrollPos -= cyClient / cyChar ;
			break ;
		case SB_PAGEDOWN:
			iVscrollPos += cyClient / cyChar ;
			break ;
		case SB_THUMBPOSITION:
			iVscrollPos = HIWORD (wParam) ;
			break ;
		default :
			//Defoe.Tu [email protected] Windows 程序设计 PDF 美化版
			break ;
		}
		iVscrollPos = max (0, min (iVscrollPos, NUMLINES - 1)) ;
		if (iVscrollPos != GetScrollPos (hwnd, SB_VERT))
		{
			SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
			InvalidateRect (hwnd, NULL, TRUE) ;
		}
		return 0 ;
	case WM_PAINT:
		hdc = BeginPaint (hwnd, &ps) ;
		for (i = 0 ; i < NUMLINES ; i++)
		{
			y = cyChar * (i - iVscrollPos) ;
			TextOut (hdc, 0, y,
				sysmetrics[i].szLabel,
				lstrlen (sysmetrics[i].szLabel)) ;
			TextOut (hdc, 22 * cxCaps, y,
				sysmetrics[i].szDesc,
				lstrlen (sysmetrics[i].szDesc)) ;
			SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
			TextOut (hdc, 22 * cxCaps + 40 * cxChar, y, szBuffer,
				wsprintf (szBuffer, TEXT ("%5d"),
				GetSystemMetrics (sysmetrics[i].Index))) ;
			SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
		}
		EndPaint (hwnd, &ps) ;
		return 0 ;
	case WM_DESTROY:
		PostQuitMessage (0) ;
		return 0 ;
	}
	return DefWindowProc (hwnd, message, wParam, lParam) ;
}


 

建立更好的滚动

滚动条文件(在/Platform SDK/User InterfaceServices/Controls/Scroll Bars中)指出SetScrollRange、SetScrollPos、GetScrollRange和GetScrollPos函数是「过时的」,但这并不完全正确。这些函数在Windows 1.0中就出现了,在Win32 API中升级以处理32位参数。它们仍然具有良好的功能。

Win32 API介绍的两个滚动条函数称作SetScrollInfo和GetScrollInfo。这些函数可以完成以前函数的全部功能,并增加了两个新特性。

第一个功能涉及卷动方块的大小,卷动方块大小与在窗口中显示的文件大小成比例。显示的大小称作「页面大小」。可以使用SetScrollInfo来设置页面大小。

GetScrollInfo函数增加了第二个重要的功能,或者说它改进了目前API的不足。假设您要使用65,536或更大单位的范围,这在16位Windows中是不可能的。当然在Win32中,函数被定义为可接受32位参数,因此是没有问题的。(记住如果使用这样大的范围,卷动方块的实际物理位置数仍然由卷动列的像素大小限制)。然而,当使用SB_THUMBTRACK或SB_THUMBPOSITION通知码得到WM_VSCROLL或WM_HSCROLL消息时,只提供了16位数据来指出卷动方块的目前位置。通过GetScrollInfo函数可以取得真实的32位值。

SetScrollInfo和GetScrollInfo函数的语法是

SetScrollInfo (hwnd, iBar, &si, bRedraw);

GetScrollInfo (hwnd, iBar, &si) ;

像在其它滚动条函数中那样,iBar参数是SB_VERT或SB_HORZ,它还可以是用于滚动条控制的SB_CTL。SetScrollInfo的最后一个参数可以是TRUE或FALSE,指出了是否要Windows重新绘制计算了新信息后的滚动条。

两个函数的第三个参数是SCROLLINFO结构,定义为:

typedef struct tagSCROLLINFO 
{ 
UINT cbSize ;// set to sizeof (SCROLLINFO) 
UINT fMask ; // values to set or get 
int nMin ; // minimum range value 
int nMax ; // maximum range value 
UINT nPage ; // page size 
int nPos ; // current position 
int nTrackPos ;// current tracking position 
} SCROLLINFO, * PSCROLLINFO ;

 

在程序中,可以定义如下的SCROLLINFO结构型态:

SCROLLINFO si ;

在呼叫SetScrollInfo或GetScrollInfo之前,必须将cbSize字段设定为结构的大小:

si.cbSize = sizeof (si) ;

或 si.cbSize = sizeof (SCROLLINFO) ;

新滚动条函数的一个好的功能是当使用与滚动条范围一样大的页面时,它已经为您做掉了一大堆杂事。可以像下面的程序代码一样使用SCROLLINFO结构和SetScrollInfo:

si.cbSize = sizeof (SCROLLINFO) ;

si.cbMask = SIF_RANGE | SIF_PAGE ;

si.nMin = 0 ;

si.nMax = NUMLINES - 1 ;

si.nPage = cyClient / cyChar ;

SetScrollInfo (hwnd, SB_VERT, &si, TRUE);

这样做之后,Windows会把最大的滚动条位置限制为si.nMax - si.nPage +1而不是si.nMax。像前面那样做出假设:NUMLINES等于75 (所以si.nMax等于74),si.nPage等于50。这意味着最大的滚动条位置限制为74 - 50 + 1,即25。这正是我们想要的。

当页面大小与滚动条范围一样大时,会发生什么情况呢?在这个例子中,就是nPage等于75或更大的情况。Windows通常隐藏滚动条,因为它并不需要。如果不想隐藏滚动条,可在呼叫SetScrollInfo时使用SIF_DISABLENOSCROLL,Windows只是让那个滚动条不能被使用,而不隐藏它。

//Sysmets2.cpp
#include 
#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 ("SysMets3") ;
	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 ("Program requires Windows NT!"),
			szAppName, MB_ICONERROR) ;
		return 0 ;
	}
	hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 3"),
		WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
		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, cxClient, cyClient, iMaxWidth ;
	HDC hdc ;
	int i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd ;
	PAINTSTRUCT ps ;
	SCROLLINFO si ;
	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) ;
		// Save the width of the three columns
		iMaxWidth = 40 * cxChar + 22 * cxCaps ;
		return 0 ;
	case WM_SIZE:
		cxClient = LOWORD (lParam) ;
		cyClient = HIWORD (lParam) ;
		// Set vertical scroll bar range and page size
		si.cbSize = sizeof (si) ;
		si.fMask = SIF_RANGE | SIF_PAGE ;
		si.nMin = 0 ;
		si.nMax = NUMLINES - 1 ;
		si.nPage = cyClient / cyChar ;
		SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
		// Set horizontal scroll bar range and page size
		si.cbSize = sizeof (si) ;
		si.fMask = SIF_RANGE | SIF_PAGE ;
		si.nMin = 0 ;
		si.nMax = 2 + iMaxWidth / cxChar ;
		si.nPage = cxClient / cxChar ;
		SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ;
		return 0 ;
	case WM_VSCROLL:
		// Get all the vertical scroll bar information
		si.cbSize = sizeof (si) ;
		si.fMask = SIF_ALL ;
		GetScrollInfo (hwnd, SB_VERT, &si) ;
		// Save the position for comparison later on
		iVertPos = si.nPos ;
		switch (LOWORD (wParam))
		{
		case SB_TOP:
			si.nPos = si.nMin ;
			break ;
		case SB_BOTTOM:
			si.nPos = si.nMax ;
			break ;
		case SB_LINEUP:
			si.nPos -= 1 ;
			break ;
		case SB_LINEDOWN:
			si.nPos += 1 ;
			break ;
		case SB_PAGEUP:
			si.nPos -= si.nPage ;
			break ;
		case SB_PAGEDOWN:
			si.nPos += si.nPage ;
			break ;
		case SB_THUMBTRACK:
			si.nPos = si.nTrackPos ;
			break ;
		default:
			break ;
		}
		// Set the position and then retrieve it. Due to adjustments
		// by Windows it may not be the same as the value set.
		si.fMask = SIF_POS ;
		SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
		GetScrollInfo (hwnd, SB_VERT, &si) ;
		// If the position has changed, scroll the window and update it
		if (si.nPos != iVertPos)
		{
			ScrollWindow (hwnd, 0, cyChar * (iVertPos - si.nPos),
				NULL, NULL) ;
			UpdateWindow (hwnd) ;
		}
		return 0 ;
	case WM_HSCROLL:
		// Get all the vertical scroll bar information
		si.cbSize = sizeof (si) ;
		si.fMask = SIF_ALL ;
		// Save the position for comparison later on
		GetScrollInfo (hwnd, SB_HORZ, &si) ;
		iHorzPos = si.nPos ;
		switch (LOWORD (wParam))
		{
		case SB_LINELEFT:
			si.nPos -= 1 ;
			break ;
		case SB_LINERIGHT:
			si.nPos += 1 ;
			break ;
		case SB_PAGELEFT:
			si.nPos -= si.nPage ;
			break ;
		case SB_PAGERIGHT:
			si.nPos += si.nPage ;
			break ;
		case SB_THUMBPOSITION:
			si.nPos = si.nTrackPos ;
			break ;
		default :
			break ;
		}
		// Set the position and then retrieve it. Due to adjustments
		// by Windows it may not be the same as the value set.
		si.fMask = SIF_POS ;
		SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ;
		GetScrollInfo (hwnd, SB_HORZ, &si) ;
		// If the position has changed, scroll the window
		if (si.nPos != iHorzPos)
		{
			ScrollWindow (hwnd, cxChar * (iHorzPos - si.nPos), 0,
				NULL, NULL) ;
		}
		return 0 ;
	case WM_PAINT :
		hdc = BeginPaint (hwnd, &ps) ;
		// Get vertical scroll bar position
		si.cbSize = sizeof (si) ;
		si.fMask = SIF_POS ;
		GetScrollInfo (hwnd, SB_VERT, &si) ;
		iVertPos = si.nPos ;
		// Get horizontal scroll bar position
		GetScrollInfo (hwnd, SB_HORZ, &si) ;
		iHorzPos = si.nPos ;
		// Find painting limits
		iPaintBeg = max (0, iVertPos + ps.rcPaint.top / cyChar) ;
		iPaintEnd = min ( NUMLINES - 1,
			iVertPos + ps.rcPaint.bottom / cyChar) ;
		for (i = iPaintBeg ; i <= iPaintEnd ; i++)
		{
			x = cxChar * (1 - iHorzPos) ;
			y = cyChar * (i - iVertPos) ;
			TextOut (hdc, x, y,
				sysmetrics[i].szLabel,
				lstrlen (sysmetrics[i].szLabel)) ;
			TextOut (hdc, x + 22 * cxCaps, y,
				sysmetrics[i].szDesc,
				lstrlen (sysmetrics[i].szDesc)) ;
			SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
			TextOut (hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer,
				wsprintf (szBuffer, TEXT ("%5d"),
				GetSystemMetrics (sysmetrics[i].Index))) ;
			SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
		}
		EndPaint (hwnd, &ps) ;
		return 0 ;
	case WM_DESTROY :
		PostQuitMessage (0) ;
		return 0 ;
	}
	return DefWindowProc (hwnd, message, wParam, lParam) ;
}


 

ScrollWindow函数在窗口的显示区域中卷动信息而不是重画它。虽然该函数很复杂(在新版本的Windows中已被更复杂的ScrollWindowEx所替代),SYSMETS3仍以相当简单的方式使用它。函数的第二个参数给出了水平卷动显示区域的数值,第三个参数是垂直卷动显示区域的数值,单位都是像素。
ScrollWindow的最后两个参数设定为NULL,这指出了要卷动整个显示区域。Windows自动把显示区域中未被卷动操作覆盖的矩形设为无效。这会产生WM_PAINT消息。再也不需要InvalidateRect了。注意ScrollWindow
不是GDI函数,因为它不需设备内容句柄。它是少数几个非GDI的Windows函数之一,它可以改变窗口的显示区域外观。

你可能感兴趣的:(windows编程学习笔记)