第二章 输出文本
一、 WM_PAINT消息: 在应用程序进入WinMain函数后调用UpdateWindow函数,Windows利用这个机会就给窗体过程发送一个WM_PAINT消息,要求绘制无效区域。 在如下情况下窗体过程会收到WM_PAINT消息: 1、在用户移动窗口或显示窗口时,窗口中先前被隐藏的区域重新可见时
2、用户改变窗口大小时(但是必须具有CS_HREDRAW和CS_VREDRAW设置)
3、程序使用ScrollWindow或者ScrollDc函数滚动客户区一部分时
4、程序使用InvalidateRect或者InvalidateRect函数显式产生WM_PAINT消息。
如下情况下Windows可能发出WM_PAINT消息: 1、Windows擦除覆盖了部分窗口的对话框或者消息框 2、菜单下拉出来然后释放 3、显示工具提示
某些情况下Windows总是保存他所覆盖显示区域,然后自动恢复: 1、鼠标光标穿越客户区 2、图标拖过客户区
注意窗口过程接收WM_PAINT消息的唯一条件是:客户区的某一部分失效。
现对窗体过程对WM_PAINT消息的处理如下说明: 1、当窗口过程处理WM_PAINT之前另一个区域变无效,Windows就会去计算一个包围在两个区域的新无效区域,并将这些变化写入绘制信息结构中。Windows是不会,请注意,不会把多个WM_PAINT消息放入消息队列的。
2、Windows默认情况下对WM_PAINT消息的处理,也就是DefWindowProc对该消息的响应如下:
Case WM_PAINT:
BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);
Return 0;
二、设备描述表 1、绘图信息结构 typedef struct tagPAINTSTRUCT { // ps HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[32]; } PAINTSTRUCT; 对这个结构说明如下:
程序员所用到一般是前三个字段,其他的由Windows处理。 hdb设备描述表句柄。 fErase一般来说是FALSE(0),意味着Windows已经擦除了无效矩形背景。
如果使用InvalidRect(hwnd,NULL,FALSE)函数后,窗口过程处理完WM_PAINT消息后就将fErase字段置1。 第三个参数是Rect类型: typedef struct _RECT { LONG left; LONG top; LONG right; LONG bottom; } RECT; 表示了无效区域的位置。
2、获取设备描述表的句柄 方法1: 处理WM_PAINT的时候使用这个方法: PAINTSTRUCT ps; HDC hdc; case WM_PAINT: hdc=BeginPaint(hwnd,&ps); EndPaint(hwnd,&ps); return 0; 注意这种方法错误: case WM_PAINT: return 0; 千万不要这样!!Windows将一个WM_Paint消息放入消息队列就是因为客户区一部分无效。如果不调用BeginPaint与EndPaint或者ValidateRect那么这个区域不会变成有效,然后Windows就会一直发WM_Paint消息。 另外,Windows通过一个InvalidateRect让整个客户区内的矩形无效,当收到WM_PAINT消息的时候可以通过GetUpdateRect在获得无效矩形的坐标。 在处理WM_PAINT消息期间,窗口过程调用了BeginPaint以后,(BeginPaint函数一般在准备绘制时导致无效区域的背景被当前的画刷擦除,该函数也填入ps结构的域)整个客户区变成有效,程序可以通过调用ValidateRect函数使客户区内的任意矩形区域变为有效。如果调用这条命令以后使得整个客户区有效,那么当前消息队列中的所有的WM_PAINT消息就被删除。
方法二: 一般调用GetDC和ReleaseDC对键盘消息或者鼠标消息作出反应。 hdc=GetDC(hwnd); ReleaseDC(hwnd,hdc); 注意:必须在退出窗口过程之前调用ReleaseDC,不要在一个消息中调用GetDC,却在另一个消息中去调用ReleaseDC
与GetDC相似的还有GetwindowDC,这个函数返回整个窗口的设备描述句柄。
两种方式的不同:
GetDC返回的设备描述表句柄具有一个剪取区域,等于整个客户区,GetDC也不会使无效区域变成有效。 而BeginPaint开始的时候就会恢复无效区域。 如下调用 InvalidateRect(hwnd,NULL,TRUE) 可以使得整个客户区变为无效,并擦除背景,这是一种简单的重画客户区的方法。 而ValidateRect(hwnd,NULL)则相反。
三、TextOut细节问题: TextOut(hdc,x,y,psText,iLength) psText:指向要显示的字符的指针 iLength:所显示字符串的长度 与wsprintf如下综合使用: int iLength; TCHAR szBuffer[40]; iLength=wsprintf(szBuffer,TEXT(“The sum of %i and %i is %i”),iA,IB,IA+IB); TextOut(hdc,x,y,szBuffer,iLength);
四、字体的问题 程序调用一个GetSystemMetrics函数来确定用户界面的大小 如下调用 TEXTMERIC tm; hdc=GetDC(hwnd); GetTextMetrics(hdc,&tm); ReleaseDC(hwnd,hdc);
关于TEXTMETRIC结构如下: typedef struct tagTEXTMETRIC { // tm LONG tmHeight; LONG tmAscent; LONG tmDescent; LONG tmInternalLeading; LONG tmExternalLeading; LONG tmAveCharWidth; LONG tmMaxCharWidth; LONG tmWeight; LONG tmOverhang; LONG tmDigitizedAspectX; LONG tmDigitizedAspectY; BCHAR tmFirstChar; BCHAR tmLastChar; BCHAR tmDefaultChar; BCHAR tmBreakChar; BYTE tmItalic; BYTE tmUnderlined; BYTE tmStruckOut; BYTE tmPitchAndFamily; BYTE tmCharSet; } TEXTMETRIC;
2、格式化文本 首先使用两个变量存放平均的字符高度宽度: static int cxChar,cyChar; case WM_CREATE: hdc=GetDC(hwnd); GetTextMerics(hdc,&tm); cxChar=tm.tmAveCharWidth; cyChar=tm.tmHeight+tm.tmExternalLeading; ReleaseDC(hwnd,hdc); return 0;
五、滚动条 1、客户区域的大小: 客户区与是否最大化可以通过 使用SM_CXFULLSCREEN和SM_CYFULLSCREEN为参数调用GetSystemMetrics获得。 另外可以通过GetClientRect函数来确定客户区的大小。这个函数应该在WM_SIZE消息发生的时候去调用。传给窗口过程的lParam参数的低位字中包含客户区的宽度,高位中包含客户区的高度。 使用如下两个变量保存: static int cxClient,cyClient; 实际使用如下: WM_SIZE: cxClient=LOWORD(lParam); cyClient=HIWORD(lParam); return 0;
客户区文本的总行数:cyClient/cyChar 客户区水平方向可显示的小写字母数:cxClient/cxChar
另外大多数情况下,一个WM_SIZE消息后面总是跟着WM_PAINT消息,这是因为我们制定窗体风格:CS_HREDRAW|CS_VREDRAW 这类风格告诉Windows当水平或者垂直大小发生变化,强制刷新客户区。
2、滚动条生成: 滚动条的生成可以在CreateWindow中的窗体风格中标示: WS_VSCROLL或者WS_HSCROLL
3、滚动条范围和位置: 默认情况下范围0~100 如下函数改变范围: SetScrollRange(hwnd,iBar,iMin,iMax,bRedraw); bRedraw:如果要windows根据新范围重画滚定条,则设置bRedraw为TRUE,如果在调用SetScrollRange以后调用了影响滚动条位置的其他函数,应该将bRedraw设置为FALSE,避免多次重画。 iBar:数值是SB_VERT或者SB_HORZ
设置滚动条位置: SetScrollPos(hwnd,iBar,iPos,bRedraw); 另外Windows提供了GetScrollRange和GetScrollPos来获得滚动条的当前范围和当前的位置。
请注意这个函数GetScrollPos,该函数在用户拖动滚动条或者滚动块的时候并不改变,必须使用SetScrollPos函数才能改变其数值,这就是为什么拖动滚动块,如果不调用SetScrollPos来处理SB_THUMBTRACK或者SB_THUMPOSITION消息,在用户释放鼠标键后,滚动框会迅速跳回原来位置的原因。
4、滚动条消息 下面是windows对滚动条的处理: 处理所有滚动条鼠标时间 当用户在滚动条内单击鼠标时,提供一种“反向闪烁” 当用户在滚动条内拖动滚动框,移动滚动框 为包含滚动条窗口的窗口过程发送滚动条消息
以下是程序员要处理的: 初始化滚动条的范围和位置 处理窗口过程响应滚动条消息 更新滚动条内滚动框的位置 更改客户区的内容以响应对滚动条更改
windows给窗口过程发送的是WM_VSCROLL(上下移动), WM_HSCROLL(供左右移动)消息。 滚动条的消息都是一对的,在鼠标按下和释放的时候。 与其他消息一样WM_VSCROLL和WM_HSCROLL也有wParam和lParam消息参数。对于来自作为窗口的一部分而创建的滚动条消息,可以忽略lParam,他只用于作为子窗口而创建的滚动条。 而wParam消息参数则被分为一个低字位和一个高字位。其中低字位是一个数值,指出了鼠标对滚动条进行的操作(见P91),高字位是用户在拖动滚动框时的当前位置。
鼠标拖动滚动框移动它的时候,应用程序将收到SB_THUMBTRACK消息。然而,如果不通过调用SetScrollPos来处理SB_THUMBTRACK或SB_THUMBPOSITION消息,用户释放释放鼠标键后,滚动框会迅速跳会原来的位置。
如果处理SB_THUMBTRACK消息,用户拖动滚动框时您需要移动客户区的内容。
如果处理SB_THUMBPOSITION消息,只要在用户停止滚动框时移动客户区的内容。
而WINUSER.H头文件还包括的SB_TOP、SB_BOTTOM、SB_LEFT和SB_RIGHT通知码之处滚动条已经移到了它的最大或者最小位置。但是作为应用程序窗口一部分而创建的滚动条来说,永远不会接受到这些通知码。
5、Win32API下的滚动条函数
Win32API提供了如下两个滚动条函数:
SetScrollInfo和GetScrollInfo函数
SetScrollInfo(hwnd, iBar, &si, bRedraw);
GetScrollInfo(hwnd, iBar, &si);
IBar: SB_VERT或SB_HORZ或者是滚动条控制的SB_CTL
SCROLLINFOR结构定义如下:
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;
si.cbSize = sizeof(si);
si.cbSize = sizeof (SCROLLINFO);是一致的
fMask字段以SIZ前缀开头的一个或者多个标志,并可以采用C的位操作OR函数|组合这些标志。
SIFmask——nMin和nMax字段设置为滚动条所需的范围。
SIFPOS——nPos位置所需的位置
SIF_PAGE——获取页面的大小(行数)
fMask标志 |
含义 |
SIFmask |
nMin和nMax字段设置为滚动条所需的范围。 |
SIFPOS |
nPos位置所需的位置 |
SIF_PAGE |
获取页面的大小(行数) |
SIF_TRACKPOS |
nTrackPos字段指出当前32位滚动条位置 |
SIFALL |
以上所有参数均要设置 |
SIF_DISABLENOSCROLL |
在程序试图隐藏滚动条时,禁用滚动条 |
6、Win32两个API函数设置滚动条的范围:
假设窗体大小50行,而NUMLINES等于75
那么滚动范围是从:0——50到25——75
因此可以通过如下参数设定:
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等于25(si.nMax等于74),
si.nPage等于50。于是最大的位置限制为74-50+1=25正是我们所需要的。
当页面大小与滚动条范围一样大的时候,也就是nPage大于等于75的时候,Windows通常就会隐藏滚动条,但是可以通过SIF_DISABLENOSCROLL标志来禁用滚动条而不是隐藏它。
6、ScrollWindow函数:
BOOL ScrollWindow(
HWND hWnd, // handle to window
int XAmount, // horizontal scrolling
int YAmount, // vertical scrolling
CONST RECT *lpRect, // client area
CONST RECT *lpClipRect // clipping rectangle
);
第二个参数指出了水平滚动客户区的数值。
第三个参数指出了垂直滚动客户区的数值,单位都是像素。
最后两个参数设置为NULL,指出了要滚动整个客户区。Windows自动把客户区中未被滚动操作覆盖的矩形设为无效。这样会产生WM_PAINT消息。再也不需要InvalidateRect了。

|