2008-5-13
文字输出
一、绘制和更新:
想了解文字的输出我们先了解一下绘制和更新原理。Windows是一个消息驱动系统,它通过把消息投入应用程序消息队列中或者把消息发送给合适的窗口消息处理程序,将发生的各种事件通知给应用程序。Windows通过发送WM_PAINT消息通知消息处理程序,窗口的部分显示区域需要绘制。
1、 WM_PAINT消息:
大多数windows程序在winMain中进入消息循环之前的初始化期间都要调用函数UpdateWindow。Winodws利用这个机会给窗口消息处理程序发送第一个WM_PAINT消息。这个消息通知窗口消息处理程序:必须绘制显示区域。此后,窗口消息处理程序就在任何时刻都准备好处理其它WM_PAINT消息,必要的话,甚至重新绘制窗口的整个显示区域。
在如下情况时会重绘窗口(窗口消息处理程序会接收一个WM_PAINT消息):
a) 在使用者移动窗口或显示窗口时,窗口中先前被隐藏的区域重新可见。
b) 使用者改变窗口的大小
c) 程序使用ScrollWindows或ScrollDC函数滚动显示区域的一部分。
d) 程序使用InvalidateRect或InvalidateRgn函数刻意产生WM_PAINT消息。
在如下情况下,显示域的一部分被临时覆盖,windows试图保存一个显示区域,并在以后恢复它,但这不一定能成功(局部重绘不一定成功)
a) Windows擦除覆盖了部分窗口的对话框或消息框
b) 菜单下拉后,然后被释放。
e) 显示工具提示消息。
在某些情况下,Windows总是保存它所覆盖的显示区域,然后恢复它。(局部重绘)
a) 鼠标光标穿越显示区域。
b) 图标拖过显示区域。
程序应该组织成可以保留绘制显示区域需要的所有信息,并且仅当“响应要求”,即windows给窗口消息处理程序员发送WM_PAINT消息时才进行绘制。如果程序在其它时间更新其显示区域,它可以强制windows产生一个WM_PAINT消息。
2、 有效区域和无效区域:
1、尽管窗口消息处理程序一旦接收到WM_PAINT消息之后,就准备更新整个 显示区域,但它经常只需要更新一个较小的区域,这个区域称为无效区域或更新区域,正是显示区域内无效区域的存在,才会让windows将一个WM_PAINT消息放在应用程序的消息队列中。只有在显示区域中的某一部分失效时窗口才会接受WM_PAINT消息。
2、Windows内部为每个窗口保存一个“绘图信息结构”,这个结构包含了包围无效区域的最小矩形的坐标以及其它信息,这个矩形就叫做无效区域。如果在窗口消息处理程序处理WM_PAINT的消息之前显示区域中的另一个区域变为无效,则windows计算出一个包围两个区域的新的无效区域并将这种变化后的信息放在绘制信息结构中。Windows不会将多个WM_PAINT消息都放在消息队列中。
3、窗口消息处理程序可以通过调用InvalidateRect使显示区域内的矩形无效。如果消息队列中已经包含一个WM_PAINT消息,Windows将计算出新的无效矩形。否则,它将一个新的MW_PAINT消息放入消息队列中。在接收到WM_APINT消息时,窗口消息处理程序可以取得无效矩形的坐标。通过调用GetUpdateRect,可以在任何时候取得这些坐标。
4、在处理WM_PAINT消息处理期间,窗口消息处理程序在调用了BeginPaint之后,整个显示区域即变为有效。程序也可以通过调用ValidateRect函数显示区域内的任意矩形区域变以有效。如果这调用具有令整个无效区域变为有效的效果,则目前队列中的任何WM_PAINT消息都将被删除。
二、GDI简介
要在窗口的显示区域绘图,可以使用windows的图形设备接口(GDI)函数。
1、设备内容:
设备内容简称DC,实际上是GDI内部保存的数据结构。设备内容与特定的显示设备相关。设备内容中有些值是图形“属性”,这些属性定义了GDI绘图函数的工作细节,当程序需要绘图时,它必须先取得设备内容句柄,在取得该句柄后windows用内定的属性值填入内部设备内容结构。当程序在显示区域绘图完毕后,它必须释放设备内容句柄。
Windows就用程序一般使用两种方法来取得设备内容句柄。
3、 取得设备内容句柄:
方法一:
在WM_PAINT中有一对成对出现的BeginPaint(hwnd,&ps);EndPaint(hwnd,&ps);
BeginPaint返回原是一个设备内容句柄,在EndPaint中把其释放。
在这里我们还要介绍一下PAINTSTRUCT这个在BeginPaint和EndPaint都用到的结构体,在windows中为每个窗口保存一个“绘图信息结构”,这就是PAINTSTRUCT,定义如下:
typedef struct tagPAINTSTRUCT { // ps
HDC hdc;
BOOL fErase;//被标志为FALSE意味windows已经擦除了无效矩形背景
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT;
如果程序通过调用windows函数InvalidateRect使显示区域中的矩形失效,则该函数的最后一个参数会指定是否擦除背景,如果为false那么windows将不会擦除背景,并且在调用完BeginPaint后PAINTSTRUCT结构的fErase字段为TRUE;PINTSTRUCT中的rcPaint矩形不仅是无效矩形,它还是一个剪取矩形。这意味着windows将绘图操作限制在剪取矩形内。在处理WM_PAINT消息时,为了在更新的矩形外绘图可以用InvalidateRect (hwnd,NULL,TRUE);
方法二:
要得到窗口显示区域的设备内容句柄,可以调用GetDC来取得句柄,在使用完后调用ReleaseDC
hdc=GetDC(hwnd);
ReleaseDC(hwnd,hdc);
和BeignPaint,EndPaint一样,GetDC和ReleaseDC函数必须地使用。如果在处理某消息时调用GetDC,则必须在退出窗口消息处理程序之前调用ReleaseDC,不要在一个消息中调用GetDC却在另一个消息调用ReleaseDC。与BeginPaint传回设备内容句柄不同,GetDC传回的设备内容句柄具有一个剪取矩形,它等于整个显示区域。GetDC不会使任何无效区域变为有效。如果需要使整个显示区域有效可以调用:
ValidateRect(hwnd,NULL);
与GetDC相似的函数是GetWindowDC。GetDC传回用于写入窗口显示区域的设备内容句柄,而GetWindowDC传回写入整个 窗口的设备内容句柄。GetWindowDC(NULL)是得到全屏幕的DC。
4、 字符大小及系统可视组件大小信息:
程序可以调用GetSystemMetrics函数来得到各类视觉组件大小的信息,他是在程序中完成与设备无关图形输出的重要函数。
程序可以调用GetTextMetrics传回设备内容中目前选取的字体信息;
在调用GetTextMetrics时我们会遇到TEXTMETRIC这个结构体!这了结构体主要是处理文件大小等信息等。这个结构体有20个字段,但是我们只要使用七个:
在使用时我们先声明一个TEXTMETRIC tm;的变量在用GetTextMetrics(hdc,&tm)来得到字体的类型,差保存在tm中。
600×480显示分辨率中使用的系统字体
typedef struct tagTEXTMETRIC { // tm
LONG tmHeight; //tmAscent+tmDescent
LONG tmAscent; //基线以上的区域(不完全)
LONG tmDescent; //基线以下的区域(不完全)
LONG tmInternalLeading; //这是行与行的间距也是重读或音调区
LONG tmExternalLeading; //也是行与行之间你可以接受设计者建议的值可拒绝 LONG tmAveCharWidth; //小写字母平均宽度,大写字母×150%
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;
系统字体的大小取决于windows所执行的视讯显示器的分辨率,在某些情况下,取决于使用者选取的系统字体的大小。
Static int cxChar,cyChar;
case WM_CREATE:
hdc=GetDC(hwnd);
GetTextMetrics(hdc,&tm);
cxChar=tm.tmAveCharWidth;
cyChar=tm.tmHeight+tm.ExternalLeading;
ReleaseDC(hwnd,hdc);
Return 0;
对于可变宽度字体,其字的宽度为tmAveCharWidth×1.5,对于可变宽度字体,TEXTMETRIC结构中的tmPitchAndFamily字段的低位为1,对于固定宽度字体,该值为0可用:(tm.tmpitchAndFamily & 1?3:2)*tm.AveCharWidth/2;
三、滚动条:
1、显示区域的大小:
得到窗口当前尺寸的大小这里有三种常用方法:
1) 可以通过GetSystemMetrics(SM_CXFULLSCREEN)返全屏X的大小
2) 可以用GetClientRect函数来取得到显示区域大小。
3) 最好的办法是处理WM_SIZE消息。在窗口大小改变时,windows给窗口消息处理程序发送一个WM_SIZE消息传给窗口消息的处理程序的lParam参数的低字组中包含显示区域的宽度,高字组中包含显示区域的高度。代码如下:
Static int cxClient,cyClient;
Case WM_SIZE:
cxClient=LOWORD(lParam);
cyClient=HIWORD(lParam);
return 0;
对LOWORD和HIWORD宏的解释如下:
#define LOWORD(l) ((WORD)(l))
#define HIWORD(l) ((WORD)(((DWORD(l)>>16)&0xFFFF))
这两个宏传回WORD值(16位的无正负号整数,范围从0到0xFFFF)。一般将这些值保存在32位有符号整数中,这就不会牵到任何转换问题,并使得这些值在以后需要的任何计算中易于使用。其实就是把宽度和高度用一个32位的lParam整数保存了他,他的前16位是高度后16位是宽度,通过宏把其值又提取到两32位的(cxClient,cyClient)整数中。
得到屏幕的长和宽后我们就可以用字的长宽来求得一行有多少字一列有多少字!有几行:(cyClient/cyChar)有几列(cxClient/cxChar);
2、 滚动条:
1) 先让窗口有显示条吧:在creeateWindow的第三个参数加入WS_HSCROLL|WS_VSCROLL就可以显示出滚动条了,如果要真正的有滚动效果的话还要设置滚动条的函数:setScrollRange(hwnd,iBar,iMin,iMax,bRedraw);如果想要windows根据新范围重画滚动条,则设置bRedraw为true如果在调用setScrollRange后,调用了影响滚动条位置的其它函数,刚应该将bRedraw设定为false以避免过多的重画。setScrollPos(hwnd,iBar,iPos,bRedraw);iPos必须在iMin到iMax的范围内,windows提供了类似的函数(GetScrollRange和GetScrollPos)来取得滚动条的目前范围和位置。
2) Windows对滚动条的处理:
a)、处理所有滚动条鼠标事件;
b)、当使用者在滚动条内单击鼠标时,提供一种“反相显示”的闪烁;
c)、当使用者在滚动条拖动卷动方块时,移动卷动方块;
d)、当包含滚动条窗口的窗口消息处理程序发送滚动条消息;
3) 程序员应该完成的工作:
a)、初始化滚动条的范围和位置setScrollRange
b)、处理窗口消息处理程序的滚动条消息
c)、更新滚动条内卷动方块的位置
d)、更改显示区域的内容以响应对滚动条的更改
3、滚动条消息:
在用鼠标单击滚动条或者拖动卷支方块时,windows给窗口消息处理程序发送WM_VSCROLL(上下移动)WM_HSCROLL(供左右移动)消息。在滚动条上的每个鼠标动作都至少产生两个消息,一条在按下鼠标按钮时产生,一条在释放按钮时产生。
和所有的消息一样,WM_VSCROLL和WM_HSCROLL也带有wParam和lParam消息参数,对于来息作为窗口的一部分而建立的滚动条消息,您可以忽略lParam;它只用于作为子窗口而建立的滚动条(通常在对话框内)。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
用于滚动条消息的wParam值的标识符
对于SB_THUMBTRACK和SB_THUMBPOSITION消息在wParam的低字组是SB_THUMBPOSITION时wParam的高字组是使用者释放鼠标键扣卷动方块的最终位置。对于其他的卷动列操作,wParam的高字组应该被害人忽略。如果SetScrollPos没用处理以上两个消息的话卷动方块会迅速跳回原来位置。
程序能够处理以上两个消息但是不是同时处理两者,如果处理SB_THUMBTRACK消息,在使用者拖动卷动方块时您需要移动显示区域的内容。而如果处理SB_THUMBPOSITION消息只需在使用者停止手动卷动方块时移动显示区域的内容,其他的top,bottom,left,right指出滚动条已经被移动到了它的最小最大位置,对于应用程序窗口一部分而建立的滚动条来说,永远不会接收到这些通知码。
4、加滚动条的步骤:
第一步:在CreateWindows调用WS_OVERLAPPEDWINDOW|WS_VSCROLL(这是 垂直的滚动条)
第二步:在WM_CREATE消息时增加对ScrollRange和ScrollPos的设置
SetScrollRange(hwnd,SB_VERT,0,100,FALSE);
SetScrollPos(hwnd,SB_VERT,iVscrollPos,TRUE);
第三步:对WM_VSCROLL时行设置:对其WM_LINEUP,WM_PAGEUP……进行设 置相应消息处理,注意的是这些消息是在WM_VSCROLL的wparam的底 字段。与当前位置比较不同让显示区域无效。
第四步:WM_PAINT的显示处理,在WS_VSCROLL上只要处理垂直的就行了!
四、滚动条信息函数
Win32 API介绍的两个滚动条函数称作SetScrollInfo和GetScrollInfo。这些函数可 以完成以前函数的全部功能,并增加了两个新特性。
第一个功能:可以改变卷动块的大小,卷动方块大小与在窗口中显示的文件大小成比例。
我们一般可以使用SetScrollInfo来设置页面大小从而设置了卷动方块的大 小。
第二个功能:在SB_THUMBTRACK或SB_THUMBPOSITION通知码得到的消息放在 一个wParam中滚动的位置是用16位保存的在但是通过GetScrollInfo函 数可以取得真实的32位值。
1、 SetScrollInfo和GetScrollInfo函数的语法:
int SetScrollInfo(
HWND hwnd, // handle to window with scroll bar
int fnBar, // scroll bar flag
LPSCROLLINFO lpsi, // pointer to structure with scroll parameters
BOOL fRedraw // 是否重新绘制计算后的滚动条
);
BOOL GetScrollInfo(
HWND hwnd, // handle to window with scroll bar
int fnBar, // scroll bar flag
LPSCROLLINFO lpsi // pointer to structure for scroll parameters
);
typedef struct tagSCROLLINFO { // si
UINT cbSize; //指定结构体的大小
UINT fMask; //用什么类型的比例化流动条
int nMin;
int nMax; //滚动范围
UINT nPage; //
int nPos;
int nTrackPos;
} SCROLLINFO;
typedef SCROLLINFO FAR *LPSCROLLINFO;
在调用SetScrollInfo或GetScrollInfo之前,必须将cbSize字段定为结构的大小
如:SCROLLINFO si;
Si.cbSize=sizeof(si);或si.cbSize=sizeof(SCROLLINFO);
看试多此一举其实这个字段使将来的windows版本可以扩充结构并添加新的功能,并且仍然与以前编译的版本兼容。
2、滚动条的范围设置:
一般情况下我们会设置最小的(nMin)为0,最大的为NUMLINES-1,当滚动条位置是0时,第一行信息显示在显示区域的顶部;当滚动条的位置是NUMLINES-1时,最后一行显示区域的顶部,并且看不见其它行。这样的显示很不爽,不过这都是system32的约定,但是我们也可以改变他,当处理WM_CREATE消息时不设置滚动条范围,而是等到收到WM_SIZE消息后再做些工作,
iVscrollMax=max(0,NUMLINES-syClient/cyChar);
SetScrollRange(hwnd,SB_VERT,0,iVscrollMax,TRUE);
其实也不要这么烦,这个函数的一个好的功能就是当使用与滚动条范围一样大的页面时,可以把cbMask=SIF_RANGE|SIF_PAGE在把nMin=0,nMax=NUMLINES-1;就行了。
3、使用过程:
1)先用setScrollInfo来设置SCROLLINFO的值,用getScrollInfo来得到SCROLLINFO的结构体,
2)再用ScrollWindows来刷新scroll的区域。在这里windows自动把显示区域中未被卷动的操作矩形区设为无效,这会产生WM_PAINT再也不需要InvalidateRect了。