16 有效矩形和无效矩形
窗口过程一旦接受到WM_PAINT消息之后,就准备更新整个客户区,但往往只需更新一个较小的区域。这个区域就称为“无效区域”。正是客户区内存在无效区域,才提示Windows将一个WM_PAINT消息放入消息队列。
Windows内部为每个窗口保存一个“绘图信息结构”,这个结构包含了包围无效区域的最小矩形的坐标以及其他信息,这个矩形就叫做“无效矩形”。
如果在窗口过程处理WM_PAINT消息之前,客户区又有一个区域变为无效,那么Windows计算出一个包围两个无效区域的新的无效矩形,并将这个变化后的信息放在绘制信息结构中。
一个消息队列在一个时刻只能有一个WM_PAINT消息在队列中。
窗口过程可以调用InvalidateRect使客户区变为无效。如果消息队列包含一个WM_PAINT消息,那么Windows将计算出新的无效矩形;否则,就在消息队列中添加一个WM_PAINT消息。
在处理WM_PAINT消息期间,窗口过程在调用了BeginPaint之后,整个客户区就会变得有效。
程序也可以显式调用ValidateRect函数使客户区内的任意矩形区域变得有效。如果这条调用使整个客户区都有效,那么将在当前消息队列中删除WM_PAINT消息。
17 设备描述表
要在窗口的客户区绘图,可以使用Windows的图形设备接口GDI函数。
设备描述表DC是GDI内部保存的数据结构。
设备描述表与特定的显示设备有关。
设备描述表中的有些值是图形化的“属性”,如指出颜色、背景色、坐标映射方式等。
当程序要绘图时,必须先获取设备描述表句柄。在获取了该句柄之后,Windows用默认的属性值填充设备描述表结构的内部各域。
当程序在客户区绘图完毕后,必须释放设备描述表句柄。句柄被释放后不再有效,也不再使用。程序必须在处理单个消息期间获取和释放句柄。
18 获取设备描述表句柄的方法之一
在使用WM_PAINT消息时,使用这种方法。它涉及到BeginPaint和EndPaint两个函数。
在处理WM_PAINT消息时,窗口过程首先调用BeginPaint。BeginPaint函数一般在准备绘制时导致无效区域的背景被擦除。BeginPaint返回的值是设备描述表句柄,这一返回值通常被保持在叫做hdc的变量中。
HDC hdc;
HDC数据类型定义为32位的无符号数。
然后,程序就可以使用需要设备描述表句柄的GDI函数了。
调用EndPaint即可释放设备描述表句柄。
一般地,处理WM_PAINT消息的形式如下:
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
使用GDI函数
EndPaint(hwnd, &ps);
return 0;
处理WM_PAINT消息时,必须成对地调用BeginPaint和EndPaint。
19 绘图信息结构
Windows为每一个窗口保存一个绘图信息结构。这就是PAINTSTRUCT,定义如下:
typedef struct tagPAINTSTRUCT {
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT
在程序调用BeginPaint时,Windows填充该结构的各个字段。用户程序只需要使用前三个字段。
hdc是设备描述表句柄。
fErase通常被标识为FLASE,这意味着Windows已经擦除了无效矩形的背景。
如果程序通过调用Windows函数InvalidateRect使客户区中的矩形失效,那么该函数的最后一个参数会指定fErase的值。如果指定0,那么在稍后的PAINTSTRUCT里面的fErase会被设置为TRUE。
rcPaint是RECT结构,定义了无效矩形的边界。RECT结构中的left、top、right、bottom以像素点为单位。此时,Windows将绘图操作限制在此RECT结构定义的矩形范围内,如果要在无效矩形外绘图,应该在调用BeginPaint之前,使用如下调用:
InvalidateRect(hwnd, NULL, TRUE);
它将使整个客户区无效,并擦除背景。
20获取设备描述表句柄的方法之二
要得到窗口客户区的设备描述表句柄,可以调用GetDC来获取句柄。在使用完后调用ReleaseDC;
hdc = GetDC(hwnd);
使用GDI函数
ReleaseDC(hwnd, hdc);
GetDC和ReleaseDC函数必须成对地使用。
GetDC返回的设备描述表句柄具有一个剪取矩形,等于整个客户区。
GetDC不会使任何无效区域变为有效,要是整个客户区有效,需要调用:
ValidateRect(hwnd, NULL);
一般可以调用GetDC和ReleaseDC来对键盘消息、鼠标消息作出反应。
21 TextOut细节
TextOut是用于显示文本的最常用的GDI函数。语法是:
TextOut(hdc, x, y, psText, iLength);
第一个参数:设备描述表句柄,既可以是GetDC的返回值,也可以是BeginPaint的返回值。
第二个参数:定义客户区内字符串的开始位置的水平坐标。
第三个参数:定义客户区内字符串的开始位置的垂直坐标。
第四个参数:指向要输出的字符串的指针。
第五个参数:字符串中字符的个数。如果psText中的字符是Unicode的,那么串中的字节数就是iLength值的两倍。
设备描述表还定义了一个剪取区域。
对于从GetDC获取的设备描述表句柄,默认的剪取区是整个客户区。
对于从BeginPaint获取的设备描述表句柄,默认的剪取区是无效区域。
Windows不会在剪取区域之外的任何位置显示字符串。
22 字符大小
要用TextOut显示多行文本,就必须确定字体的字符大小,可以根据字符的高度来定位字符的后续行,以及根据字符的宽度来定位字符的后续列。
系统字体的字符高度和平均宽度取决于视频显示器的像素大小。
程序可以调用GetSystemMetrics函数来确定关于用户界面构件大小的信息。
程序可以调用GetTextMetrics函数来确定字体大小。
metric是度量的意思。
TEXTMETRIC的结构:
typedef struct tagTEXTMETRIC
{
LONG tmHeight;
LONG tmAscent;
LONG tmDescent;
LONG tmInternalLeading;
LONG tmExternalLeading;
LONG tmAveCharWidth;
LONG tmMaxCharWidth;
其他域
} TEXTMETRIC, *PTEXTMETRIC;
要使用GetTextMetrics函数,需要先定义一个通常被称为tm的结构变量:
TEXTMETRIC tm;
在需要确定文本尺寸时,先要获取设备描述表句柄,再调用GetTextMetrics:
hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm);
操作;
ReleaseDC(hwnd,hdc);
23 文本尺寸
字体的纵向大小由5个值确定:
① tmHeight,等于tmAscent加上tmDescent。这两个值表示了基线上下字符的最大纵向高度。
② tmAscent,基线以上的高度
③ tmDescent,基线以下的高度
④ tmInternalLeading,重音号和字符之间的距离,如ü中的u和两点的距离。
⑤ tmExternalLeading,一般用于多行文本间行距的调整。
字符的横向大小由2个值确定:
① tmAveCharWidth,小写字母加权平均宽度。
② tmMaxCharWidth,字体中最宽字符的宽度。
对于等宽字体,tmAveCharWidth和tmMaxCharWidth这两个值相等。
大写字母的平均宽度比较复杂,如果:
① 字体是等宽字体,那么大写字母的平均宽度等于tmAveCharWidth。
② 字体是变宽字体,那么大写字母的平均宽度等于tmAveCharWidth*1.5。
判断字体是否是变宽字体,可以通过TEXTMETRIC结构中的tmPitchAndFamily域的低位判断,如果低位是1,那么是变宽字体,如果是0,那么是等宽字体。
大写字母宽度 = (tm.tmPitchAndFamily & 1 ? 3 : 2) / 2 * 小写字母宽度
24 格式化文本
在一次Windows对话期间,系统字体的大小不会改变,因此在程序运行过程中,只需要调用一次GetTextMetric。最好是在窗口过程中处理WM_CREATE消息时进行此调用。
假设要编写一个Windows程序,在客户区显示多行文本,这需要先获取字符宽度和高度。可以在窗口过程内定义两个变量来保存字符宽度和总的字符高度。
case WM_CREATE:
hdc = BeginPaint(hwnd, &pt);
GetTextMetric(hdc, &tm);
cxChar = tm.tmAveCharWidth; 小写字母宽度
cyChar = tm.Height + tm.tmExternalLeading; 字母高度
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) / 2 * tm.tmAveCharWidth; 大写字母宽度
EndPaint(hwnd, &pt);
return 0;
25 客户区的大小
窗口最大化之后的客户区大小,可以通过以SM_CXFULLSCREEN和SM_CYFULLSCREEN为参数调用GetSystemMetric来获得。
要确定客户区的大小,最好的方法是在窗口过程处理WM_SIZE消息。在窗口大小改变时,就会产生WM_SIZE消息。传给窗口过程的lParam参数的低位字中包含客户区的宽度x,高位字中包含客户区的高度y。要保存这些尺寸,可以定义两个int型变量来保存。
static int cxClient,cyClient;
然后在WM_SIZE消息处理中:
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
用cyClient/cyChar可以得到客户区可以显示的文本总行数。
26 滚动条的范围和位置
每个滚动条都有一个相关的范围和位置。这是一对整数。当滚动框在滚动条的顶部(左部)时,滚动框的位置是范围的最小值;在滚动条的底部(右部)时,滚动框的位置是范围的最大值。
在默认情况下,滚动条的范围是0~100,但将范围改变为更方便于程序的数值也是很容易的:
SetScrollRange(hwnd, iBar, iMin, iMax, bRedraw);
其中hwnd为该窗口的句柄。
iBar为SB_VERT或SB_HORZ。
iMin和iMax为范围。
bRedraw,如果要Windows根据新范围重绘滚动条,则设置为TRUE。
滚动框的位置不是连续的,而是离散的整数值。
可以使用SetScrollPos在滚动条范围内设置新的滚动框位置:
SetScrollPos(hwnd, iBar, iPos, bRedraw);
参数iPos是新位置,必须在iMin至iMax的范围内。
Windows提供了类似的函数GetScrollRange和GetScrollPos来获取滚动条的当前范围和位置。
27 滚动条消息
在用鼠标单击滚动条或者拖动滚动框时,Windows都给窗口过程发生WM_VSCROLL或WM_HSCROLL消息。在滚动条上的每个鼠标动作都至少产生两个消息,一个在按下鼠标键时产生,一个在释放鼠标键时产生。
WM_VSCROLL和WM_HSCROLL也带有wParam和lParam消息参数。
lParam只用于作为子窗口而创建的滚动条(通常在对话框内)。
wParam消息参数被分为一个低位字和一个高位字。
低位字是一个数值,指出了鼠标对滚动条进行的操作。这个数值被看作一个“通知码”。通知码以SB开头。
#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
当把鼠标的光标放在滚动框上并按住鼠标键时,就产生SB_THUMBPOSITION和SB_THUMBTRACK消息。
当wParam的低位字是SB_THUMBTRACK时,wParam的高位字是用户在拖动滚动框时的当前位置。
当wParam的低位字是SB_THUMBPOSITION时,wParam的高位字是用户释放鼠标后滚动框的最终位置。
28 滚动条信息函数
滚动条文档指出SetScrollPos、SetScrollRange、GetScrollPos、GetScrollRange函数是过时的。
在Win32 API中,升级了2个滚动条函数,称作SetScrollInfo和GetScrollInfo。这些函数完成上述4个函数的全部功能,并增加了2个新特性。
第一个功能设计滚动框的大小。滚动框的大小称作页面大小。算法是:
滚动框大小 / 滚动长度 ≈ 页面大小 / 范围 ≈ 显示的文档数量 / 文档的总大小
可以使用SetScrollInfo来设置页面大小。
第二个功能是GetScrollInfo函数,它可以获取32位的范围值。
SetScrollInfo和GetScrollInfo函数的语法是:
SetScrollInfo(hwnd, iBar, &si, bRedraw);
GetScrollInfo(hwnd, iBar, &si);
iBar参数是SB_VERT或SB_HORZ。
bRedraw可以是TRUE或FALSE,指出了是否要Windows重新绘制计算了新信息后的滚动条。
两个函数的第三个参数是SCROLLINFO结构,定义为:
typedef struct tagSCROLLINFO
{
UINT cbSize;
UINT fMask;
int nMin;
int nMax;
UINT nPage;
int nPos;
int nTrackPos;
} SCROLLINFO
在程序中,可以定义如下的SCROLLINFO结构类型:
SCROLLINFO si;
在调用SetScrollInfo或GetScrollInfo函数之前,必须将cbSize自动设置为结构的大小:
si.cbSize = sizeof(si);或si.cbSize = sizeof(SCROLLINFO);
fMask:把fMask字段设置为以SIF前缀开头的一个或多个标识,来获取或设置里面的结构中域的值。
① SIF_RANGE:用于获取或设置滚动条的范围
② SIF_POS:用于获取或设置滚动框的位置
③ SIF_PAGE:用于获取或设置滚动条的页面大小
④ SIF_TRACKPOS:用于获取或设置滚动框移动时的位置