输出文本
见钱眼开 于2005-3-28
客户区是整个应用程序窗口中未被标题栏、窗口边框以及可选的菜单栏、工具栏、状态栏和滚动条占据的部分,是窗口中可以由程序任意书写或传递可视信息的部分。
窗口客户区时刻准备重新绘制。在窗口客户区的某一部分失效后,Windows通过发送WM_PAINT消息通知窗口过程刷新“无效区域”。
Windows内部为每一个窗口保存了一个“绘图信息结构”,包含了“无效区域”的最小矩形坐标及其他信息。窗口过程处理WM_PAINT消息之前可通过该结构来返回“无效区域”矩形范围。Windows不会将多个WM_PAINT消息同时放入消息队列。因此,如果窗口过程处理WM_PAINT消息之前客户区另一个区域变为“无效区域”,Windows将计算出包围两个区域的无效区域,并将变化后的信息放入绘图信息结构。
InvalidateRect使客户区内的某个矩形区域无效。GetUpdateRect可随时返回“无效区域”信息。调用BeginPaint使整个客户区有效,调用ValidateRect使客户区内任意矩形区域有效。如果客户区内不存在“无效区域”,消息队列中的WM_PAINT消息将被删除。
要在窗口客户区绘图,必须调用Windows提供的图形设备接口(GDI)函数。每个GDI函数的首个参数必定是一个HDC类型的句柄。它就是设备描述表(Device Context,简称DC)句柄。调用GDI函数之前必须先获取设备描述表句柄,使用之后必须释放设备描述表句柄。
除了调用CreateDC返回的设备描述表句柄,程序必须在处理单个消息期间获取和释放句柄。
有两种方法返回设备描述表句柄:
1. 调用BeginPaint函数。一般在处理WM_PAINT消息时使用这种方法。BeginPaint函数使窗口客户区有效。在此期间一般会传送WM_ERASEBKGND消息给窗口过程擦除无效矩形区域背景(如果调用InvalidateRect使客户区矩形无效,可设置参数决定是否擦除背景)。通过返回的PAINTSTRUCT结构fErase标志可以查看处理结果,如果值为FALSE(0),表示无效矩形背景已被擦除;如果值为TRUE,表示未擦除。PAINTSTRUCT结构首个成员代表返回的设备描述表句柄,rcPaint成员代表无效区域的矩形范围。最后别忘了必须调用EndPaint。
2. 调用GetDC。使用完毕后必须调用ReleaseDC。另外要注意的是,这两个函数应该在同一个消息处理中分别调用。与BeginPaint不同,GetDC返回的设备描述表句柄代表整个客户区,而非仅是无效矩形区域。还有,GetDC不会使任何无效区域变为有效。要使无效区域有效,可以调用ValidateRect。与GetDC相似的函数是GetWindowDC。不过,GetDC返回用于绘制窗口客户区的设备描述表句柄,GetWindowDC返回用于绘制整个窗口的设备描述表句柄。
TextOut是用于显示文本的最常用的GDI函数。该函数的首个参数就是设备描述表句柄。设备描述表的属性控制显示文本的特征,缺省定义文本字体为黑色,文本背景为白色。设备描述表的文本背景颜色和定义窗口类时设置的背景颜色是独立的两个部分。窗口类背景颜色在擦除客户区时使用,文本背景颜色在客户区调用GDI函数绘制新内容时使用。一般默认将两者设置相同颜色。x和y定义客户区内字符串的开始坐标。在默认设备描述表中,原点(0,0)坐标设为客户区的左上角。其实我们在GDI函数中使用的坐标都是“逻辑坐标”,它是显示器物理象素坐标的一种映射。Windows中有多种坐标“映射方式”。映射方式在设备描述表中定义,默认映射方式是MM_TEXT。在这种映射方式下,逻辑单位和物理单位相同。
调用TextOut显示文本时默认采用的字体是系统字体,它是一种“点阵字体”。早期系统字体采用等宽字体,现都采用变宽字体。
GetTextMetrics函数返回设备描述表中设定的字体信息,返回信息保存在一个TEXTMETRIC结构中。这里我们只使用该结构的前7个参数:
typedef struct tagTEXTMETRIC {
LONG tmHeight;
LONG tmAscent;
LONG tmDescent;
LONG tmInternalLeading;
LONG tmExternalLeading;
LONG tmAveCharWidth;
LONG tmMaxCharWidth;
…
} TEXTMETRIC, *PTEXTMETRIC;
tmHeight等于tmAscent和tmDescent之和。tmAscent和tmDescent分别表示基线上、下的最大纵向高度。间距(Leading)表示两行文本间预留的空间。内部间距(tmInternalLeading)包含在tmAscent中,它是重音符号经常出现的地方。如果内部间距设置为0,加重音的字符会缩短高度,以容纳重音符号。外部间距(tmExternalLeading)是文本行之间的空间大小,不包含在tmAscent中。tmAveCharWidth表示小写字母加权平均宽度。tmMaxCharWidth表示字体中最宽字符的宽度。
一般我们在调用CreateWindow之后的WM_CREATE消息处理中即可调用GetTextMetrics函数。
GetSystemMetrics函数返回各种系统信息(图形元素的长宽)和系统配置设定。 该函数只有一个参数,叫做“索引”。通过Windows定义的75个整数标识符,GetSystemMetrics返回一个整数,这个整数通常是函数参数中指定图形元素的大小。函数原型如下:
int GetSystemMetrics(int nIndex);
TextOut调用之前调用SetTextAlign可指定文本排列方式。
对于变宽字体,TEXTMETRIC结构中的tmPitchAndFimaly参数最低位为1,对于等宽字体,该位为0。大写字母宽度差不多是小写字母的1.5倍。因此容纳一个字符的基本宽度可以这样计算:
cxCaps = (tm.tmPitchAndFamily&1 ? 3:2)*cxChar/2;
尽管我们可以调用GetClientRect来返回客户区大小,但确定客户区大小更好的方法是在窗口过程中处理WM_SIZE消息。该消息的lParam参数低位字包含客户区宽度,高字位包含客户区高度。一般WM_SIZE消息之后必然有一个WM_PAINT消息。窗口大小改变,必然导致客户区刷新。
如果当前客户区无法容纳下即将绘制的全部内容,那么我们就需要使用“滚动条”。
滚动条分“水平滚动条”和“垂直滚动条”两种。要在应用程序窗口中包含滚动条,在CreateWindow函数的窗口风格参数中包含WS_VSCROLL(垂直滚动)和WS_HSCROLL(水平滚动)即可。客户区不包含滚动条所占据的空间。
每个滚动条均有一个相关的“范围”(这是一对整数,分别代表最小值和最大值)和“位置”(它是滚动框在此范围内的位置)。滚动条范围默认是从0(顶部或左部)~100(底部或右部),改变范围也很容易,调用SetScrollRange即可。函数原型如下:
BOOL SetScrollRange(
HWND hWnd,
int iBar,
int iMinPos,
int iMaxPos,
BOOL
bRedraw
);
参数iBar为SB_VERT或SB_HORZ,iMin和iMax分别指范围最小值和最大值。
bRedraw表示范围设定后是否重新绘制滚动条。举例如下:
SetScrollRange(hwnd,SB_VERT,0,NUMLINES – 1,FALSE); //根据文本行数决定滚动条 //范围
如果调用SetScrollRange后将调用改变滚动条位置的函数,则
bRedraw应该设置为FALSE。防止多次刷新。
调用SetScrollPos函数可以在滚动条范围内设置新的滚动条位置。函数原型如下:
int SetScrollPos(
HWND hWnd,
int iBar,
int iPos,
BOOL
bRedraw
);
参数iPos表示新位置。举例如下:
SetScrollPos (hwnd,SB_VERT,0,TRUE); //设定滚动条初始位置
滚动框的位置总是离散的整数值。Windows会负责处理所有滚动条鼠标事件以及发送滚动条消息。我们通常负责初始化滚动条范围和位置、处理滚动条消息、更新滚动条位置以及更新客户区以响应滚动条的改变。
在用鼠标单击滚动条或拖动滚动框时,Windows给窗口过程发送WM_VSCROLL(上下移动)或WM_HSCROLL(左右移动)消息。滚动条上的每个鼠标动作至少产生两个消息,一个在按下鼠标按键时产生,一个在释放键时产生。如果滚动条作为窗口的一部分,忽略lParam;lParam参数只用于作为子窗口的滚动条。wParam参数低16位标识鼠标对于滚动条的动作,而高16位只在发生鼠标拖动滚动框动作时,用于标识滚动框位置,其他鼠标动作情形下被忽略。
单击滚动条两端箭头产生SB_LINEUP或SB_LINELEFT或SB_LINEDOWN或SB_LINERIGHT消息;单击滚动条本身产生SB_PAGEUP或SB_PAGELEFT或SB_PAGEDOWN或SB_PAGERIGHT消息,拖动滚动框产生WM_THUMBTRACK消息;而鼠标动作结束时除拖动滚动框产生WM_THUMBPOSITION消息外,其他鼠标动作结束时都产生一个WM_ENDSCROLL消息。
处理拖动滚动框动作,一般无需同时处理WM_THUMBTRACK和WM_THUMBPOSITION消息。至于选择处理哪个消息,视情况而定。
作为应用程序窗口一部分的滚动条,永远不会接收到包含SB_TOP、SB_BOTTOM、SB_LEFT、SB_RIGHT等鼠标动作的通知消息。
响应滚动条鼠标动作代码举例:
case SB_LINEUP:
iVscrollPos -= 1;
case SB_LINEDOWN:
iVscrollPos += 1;
case SB_THUMBPOSITION:
iVscrollPos = HIWORD(wParam);
…
SetScrollPos(hwnd,SB_VERT,iVscrollPos,TRUE);
InvalidateRect(hwnd,NULL,TRUE); //调用UpdateWindow函数客户区刷新更快
现在我们处理滚动条消息时更常用SetScrollInfo函数,主要有两大优点:
1. 滚动框和显示文档大小成比例。算法为:
2. 滚动条范围可设置为32位值,通过GetScrollInfo函数可获取32位的滚动框位置。
SetScrollInfo和GetScrollInfo函数都包含一个SCROLLINFO结构类型的参数,该结构原型如下:
typedef struct tagSCROLLINFO {
UINT cbSize;
UINT fMask;
int nMin;
int nMax;
UINT nPage;
int nPos;
int nTrackPos;
} SCROLLINFO, *LPSCROLLINFO;
fMask参数包含以SIF前缀开头的一个或多个标志,可通过位或运算(|)组合这些标志。使用SIF_RANGE标志时,通过nMin和nMax参数设置或返回滚动条范围;使用SIF_POS标志时,通过nPos参数设置或返回滚动条位置;使用SIF_PAGE标志时,通过nPage参数设置或返回当前页面大小;使用SIF_TRACKPOS标志,拖动滚动框时通过nTrackPos参数返回当前滚动框位置;使用SIF_DISABLENOSCROLL标志,禁用滚动条。举例如下:
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);
上面SetScrollInfo函数调用实现了下列代码的效果:
iVscrollMax = max(0,NUMLINES – cyClient/cyChar);
SetScrollRange (hwnd,SB_VERT,0,iVscrollMax,TRUE);