第二章 输出文本
一、 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了。