设备描述表是Windows应用程序、设备驱动程序和输出设备之间的桥梁,它与一个特定的设备相关联。例如,对显示器来说,设备描述表通常指显示器上的某个窗口。设备描述表描述了所选定的绘图工具、字体、字体颜色、工具在设备上绘制(或者说是映射)的方式及设备上可使用的输出区域等属性。WindowsGDI函数实际上是在设备描述表里显示正文及绘图的。 当程序显示文字或绘图时,首先必须获得一个设备描述表句柄,完成输出文字或绘图之后,还必须及时释放该句柄,否则会大大减少Windows的存储单元。释放之后的句柄就不会再有效了。一般说来,在处理某条消息时,获取和释放设备描述表句柄必须成对出现,而且不同设备描述表的获取与释放的方法也不同。而这只是所使用的函数不同而已。
二、显示缓冲区
Windows环境是基于图形操作的,图形设备接口(GDI)是一个在Windows应用程序中执行与设备无关的函数库。这些函数在不同的输出设备上产生图形及文字输出。
显示缓冲区是一种”设备缓冲区”,特别用于窗口用户区的输出。设备缓冲区定义设备,绘图工具及有关设备的完整信息。显示缓冲区只定义与窗口用户区有关的内容,包括输出设备、当前绘图工具、颜色,以及其它一些GDI输出函数产生输出所需的信息。在窗口中绘图,需要使用窗口的句柄,根据窗口句柄,可以得到窗口用户区的显示缓冲区句柄,所有GDI输出函数都需要一个显示缓冲区句柄,没有它就无法完成输出。
可以根据输出的需要选择获得显示缓冲区句柄的方法。画和写操作可以存在于应用程序中的任何地方(包括WinMain函数中),倡大多数应用程序把它们放在窗口函数中。每当对窗口的操作可能影响用户区内容时,Windows发送WM_PAINT消息给窗口函数。应用程序通常在响应WM_PAINT消息时,完成画和写。Windows发送WM_PAINT消息给窗口函数,并由它刷新用户区,因为只有应用程序才知道用户区的内容。
通常用BeginPaint函数来响应WM_PAINT消息。如果要在没有WM_PAINT消息的时刻画用户区,必须使用GetDC函数得到显示缓冲区的句柄。
当应用程序需要得到窗口的显示缓冲区时,Windows把它暂时借给应用程序。显示缓冲区是一种共享资源,一个应用程序占有它之后,其它应用程序就无法得到它。因此,应用程序在利用显示缓冲区画完窗口内容之后,就必须使用ReleaseDC函数释放它。同理,要求用EndPaint函数释放由BeginPaint函数获得的显示缓冲区。
显示缓冲区中有缺省的画笔、画刷和缺省字模。
(一)GetDC函数
在处理非WM_PAINT消息时,应用程序获取窗口用户区设备描述表句柄使用GetDC函数,它常常用来对用户的某些动作提供反馈。例如,当用户移动鼠标光标穿越窗口时,在屏幕上画一条线。GetDC函数返回一个显示缓冲区句柄,它可以用于任何GDI输出函数。
请看下面这个程序段:
case WM_PAINT:
hDC=GetDC(hWnd);
TextOut(hDC,10,10,”你好,欢迎来到VC之路”,20);
ReleaseDC(hWnd,hDC);
Break;
在这里,我们用设备环境句柄hDC定义了一个设备描述表句柄hDC,然后利用函数GetDC取得hWnd参数所标识窗口的显示缓冲区的显示描述表赋给hDC,再通过TextOut函数在窗口用户区(10,10)位置显示包含20个字符(计入空隔)的字符串,使用完之后,及时用函数RealeaseDC释放这个显示描述句柄hDC。
使用GetDC函数获得显示缓冲区句柄,在窗口函数中处理WM_PAINT消息。当窗口函数接收到影响用户区内容的WM_PAINT消息时,用户区中先前已画的内容可能被擦掉。这是因为在处理WM_PAINT消息的过程中,Windows发送WM_ERASEBKGND消息给窗口函数,如果把WM_ERASEBKGND消息交给DefWindowProc函数处理,DefWindowProc函数使用这种窗口类的背景色填满受影响的区域,并擦掉原先已经画的内容。
(二)WM_PAINT消息
在前面我们已经认识了WM_PAINT,让我们再来看一下。由于Windows是一个多任务环境,某个应用程序的窗口上面可能被对话框或窗口覆盖,当撤消这些对话框或窗口时,这个应用程序窗口中就有一个”空洞”,这个”空洞”就是一块无效的用户区域。为重新显示无效用户区域,Windows发送WM_PAINT消息实现。要求Windows发送WM_PAINT的情况有:改变窗口大小,覆盖用户区的菜单或对话框关闭,使用UpdateWindow和ScrollWindow函数等。
Windows发送WM_PAINT消息时,把它放到应用程序队列的最后,使得其它的输入能够先于WM_PAINT消息被处理。GetMessage函数也得到队列中WM_PAINT消息之后的其它消息,即只有有没有其它消息的情况下,才从队列中取出WM_PAINT消息进行处理。这样做是为了让应用程序首先完成影响窗口显示结果的其它操作,不致因为频繁地执行输出操作而引起显示器的闪烁。Windows把WM_PAINT消息放在队列最后就是这个原因。
Windows并非WM_PAINT消息的唯一来源,使用InvalidateRect或InvalidateRgn函数也可以产生绘图窗口的WM_PAINT消息。这两个函数把用户区全部或部分标记成无效用户区而要求重新显示。下面的函数调用是把整个用户区标记成无效:
InvalidateRect(hWnd,NULL,TRUE);
这个例子把hWnd句柄参数指定的窗口用户区标记成无效。作为钜形结构的NULL参数指定整个用户区。TRUE参数表示擦除背景。
InvalidateRect和InvalidateRgn函数并不实际产生WM_PAINT消息。当用户区无效时,Windows就发送一个WM_PAINT消息,如果用户区的其它部分也被标记成无效,Windows就不再发送另一条WM_PAINT消息,而是把已有的无效区域合并,以便根据同一条WM_PAINT消息处理所有这些区域。
如果想改变重新显示的用户区,可以调用ValidateRect和ValidateRgn函数使相应的用户区有效,这两个函数取消原有的无效区,并在没有无效区的情况下取消队列中的WM_PAINT消息。
如果不想等待应用程序队列中的WM_PAINT消息,使用UpdateWindow函数直接发送WM_PAINT消息给指定窗口的窗口函数。如果还存在无效的用户区,UpdateWindow函数从队列中取出WM_PAINT消息,并把它直接发送给指定窗口的窗口函数。UpdateWindow函数通常用在窗口需要立即更新它的用户区,如在窗口刚被创建时使用。
用BeginPaint函数获得输出文字和画点的显示缓冲区句柄:
PAINTSTRUCT ps;
…
case WM_PAINT:
hdc=BeginPaint(hWnd,&ps);
/* 输出操作 */
EndPaint(hWnd,&ps);
Break;
结构PAINTSTRUCT函数原型如下:
typedef struct tagPAINTSTRUCT{
HANDLE hDC; //设备描述表句柄
BOOL rErase; //确定背景是否已被重画
RECT rePaint; //给出无效钜形的边界
BOOL fRestore; /内部使用的保留字段
BOOL fIncUpdate; //保留字段
BYTE rgbReserved; //保留字段
}PAINTSTRUCT;
无效区域是一个钜形区域,它是一个RECT结构,其在window.h定义为
typedef struct tagRECT{
int left; //钜形左上角的X坐标
int top; //钜形左上角的Y坐标
int right; //钜形右下角的X坐标
int bottow; //钜形右下角的Y坐标
}RECT
例中BeginPaint函数返回一个显示缓冲区句柄,它可用于其它的GDI输出函数中。
EndPaint函数通知Windows所有输出操作均已处理完毕,并释放显示缓冲区。