Windows编程 第六回 又见设备描述表
-----路过的朋友,若发现错误或有好的建议,欢迎在下面留言,谢谢!-----
设备描述表还真是十分重要,以至于我们在这里还要再学习它。这节内容比较少,相信你很快就应该能看完。Let’go!
获取设备描述表句柄
复习:当你想在一个图形输出设备(诸如屏幕或者打印机)上绘图时,你首先必须获得一个设备描述表(或者DC)的句柄。将句柄传回给程序时,Windows就给了你使用设备的权限。然后你在GDI函数中将这个句柄作为一个参数,向Windows标识你想在其上进行绘图的设备。如果在处理一个消息时取得了设备描述表句柄,应该在退出窗口函数之前释放它(或者删除它)。一旦释放了句柄,它就不再有效了。
Windows提供了几种取得设备描述表句柄的方法,如下。
方法一:
这是获取并释放设备描述表句柄最常用的方法,即在处理WM_PAINT消息时使用BeginPaint和EndPaint函数。这两个函数需要程序的窗口句柄和PAINTSTRUCT结构的变量的地址为参数。Windows程序员通常把这一结构变量命名为ps并且在窗口过程中定义它:
PAINTSTRUCT ps ;
在处理WM_PAINT消息时,窗口过程首先调用BeginPaint。一般在BeginPaint函数的调用中,如果客户区的无效区域的背景还未被擦除,则由Windows来擦除。(它使用已注册的窗口类WNDCLASS结构的hbrBackground成员变量中指定的画刷来擦除背景,上回的注释1我们提过)
BeginPaint调用使整个客户区有效,并且返回一个设备描述表句柄,这一返回值通常被保存在叫做hdc的变量中。该函数也填入ps结构的字段(可以回忆一下上一回的注释1)。
设备描述表句柄在窗口过程中的定义如下:
HDC hdc ;//定义设备描述表句柄变量
然后,程序就可以使用需要设备描述表句柄的TextOut等GDI函数。在窗口的客户区显示文字和图形需要设备描述表句柄,但是从BeginPaint返回的设备描述表句柄不能在客户区域之外绘图。调用EndPaint即可释放设备描述表句柄。
一般地,处理WM_PAINT消息的形式如下:
caseWM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
[other program lines]
EndPaint (hwnd, &ps) ;
return 0 ;
我们再来强调一下吧(别怪我啰嗦):BeginPaint调用使整个客户区有效(阻止WM_PAINT消息一直发送,见附录),填充ps结构的字段,返回的设备描述表句柄。使用这个句柄只能在ps结构中的rcPaint字段规定的区域内绘图。EndPaint调用可释放设备描述表句柄。两个函数必须成对使用,一般只用在处理WM_PAINT消息中。
Windows程序在处理非WM_PAINT消息时还可以用以下方法获得设备描述表句柄,当然以下函数调用不会使客户区中的无效区域变成有效。
方法二:
hdc = GetDC (hwnd) ;
[other program lines]
ReleaseDC (hwnd, hdc) ;
GetDC函数调用后会返回hwnd参数所指定的窗口的客户区所对应的设备描述表句柄。可见GetDC调用与BeginPaint的基本区别是,利用从GetDC返回的句柄可以在整个客户区上绘图。如果hwnd参数设置为NULL,那么函数会返回整个桌面的设备描述表句柄。当不再需要该设备环境时,需要调用ReleaseDC函数释放设备描述表。
方法三:
hdc = GetWindowDC (hwnd) ;
[other program lines]
ReleaseDC (hwnd, hdc) ;
GetWindowDC返回可以在整个窗口(包括客户区部分和标题栏、菜单、滚动条、框架等非客户区部分)绘图的设备描述表句柄,不过此函数很少使用。
方法四:
hdc = CreateDC (lpszDriver, lpszDevice, lpszOutput, lpData) ;
[other program lines]
DeleteDC (hdc) ;
BeginPaint、GetDC和GetWindowDC获得的设备内容都与显示器上的某个特定窗口(即hwnd)相关。CreateDC是取得设备描述表句柄一个更通用的函数,它甚至可以获取非显示器输出设备描述表句柄。当不再需要该设备描述表时只可调用DeleteDC函数删除它。
当然,获得设备描述表的函数还有很多,我们就先简单介绍到这儿,其他类似的函数我们以后遇到再说。
设备描述表的属性——我们上回提到过哦
设备描述表中包含许多确定GDI函数如何在设备上工作的当前“属性”,这些属性允许传递给GDI函数的参数只包含起始坐标或者尺寸信息,而不必包含Windows在设备上显示对象时需要的所有其它信息。例如,上回我们调用TextOut时,我们只需要在函数中给出设备描述表句柄、起始坐标、文字和文字的长度。而不必指定字体、文字颜色、文字后面的背景色彩以及字符间距,因为这些属性都是设备描述表的一部分。当你想改变这些属性之一时,您调用一个可以改变设备描述表中属性的函数(例如把文字颜色设置成红色),以后针对该设备描述表的TextOut调用就可以使用改变后的属性了(例如可以输出红色字体)。
我在此列出如下表,方便大家查阅。
属性 |
默认值 |
相关函数 |
背景色 |
WHITE |
GetBkColor |
SetBkColor |
||
背景模式 |
OPAQUE |
GetBkMode |
SetBkMode |
||
位图 |
NONE |
CreateBitMap |
CreateBitMapIndirect |
||
CreateCompatibleBitmap |
||
SelectObject |
||
画刷 |
WHITE_BRUSH |
CreateBrushIndirect |
CreateDIBPatternBrush |
||
CreateHatchBrush |
||
CreatePatternBrush |
||
CreateSolidBrush |
||
SelectObject |
||
画刷起始位置 |
(0,0) |
GetBrushOrg |
SetBrushOrg |
||
UnrealizeObject |
||
剪裁域 |
DISPLAY SURFACE |
ExcludeClipRect |
IntersetClipRect |
||
OffsetClipRgn |
||
SelectClipPath |
||
SelectObject |
||
SelectClipRgn |
||
颜色调色板 |
DEFAULT_PALETTE |
CreatePalette |
RealizePatte |
||
SelectPalette |
||
绘图方式 |
R2_COPYPEN |
GetROP2 |
SetROP2 |
||
字体 |
SYSTEM_FONT |
CreateFont |
CreateFontIndirect |
||
SelectObject |
||
字符间距 |
0 |
GetTextCharacterExtra |
SetTextCharacterExtra |
||
映射方式 |
MM_TEXT |
GetMapMode |
SetMapMode |
||
画笔 |
BLACK_PEN |
CreatePen |
CreatePenIndirect |
||
SelectObject |
||
多边形填充方式 |
ALTERNATE |
GetPolyFillMode |
SetPolyFileMode |
||
缩放模式 |
BLACKONWHITE |
SetStretchBltMode |
GetStretchBltMode |
||
文本颜色 |
BLACK |
GetTextColor |
SetTextColor |
||
视图范围 |
(1,1) |
GetViewportExtEx |
SetViewportExtEx |
||
ScaleViewportExtEx |
||
视图原点 |
(0,0) |
GetViewportOrgEx |
SetViewportOrgEx |
||
窗口范围 |
(1,1) |
GetWindowExtEx |
SetWindowExtEx |
||
ScaleWindowExtEx |
||
窗口原点 |
(0,0) |
GetWindowOrgEx |
OffsetWindowOrgEx |
||
SetWindowOrgEx |
一般以Get开头的函数作用是获取该属性值,一般以Set开头的函数作用是设置该属性值,若有不太清楚的函数,你就自己动手查吧。
保存设备描述表
通常,在你调用GetDC或BeginPaint时,Windows用默认值建立一个新的设备描述表,你对属性所做的一切改变在设备描述表调用ReleaseDC或EndPaint释放时,都会丢失。如果你的程序需要使用非默认的设备描述表属性,则你必须在每次取得设备描述表句柄时初始化设备内容:
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
[initialize device context attributes]
[paint client area of windows]
EndPaint (hwnd, &ps) ;
return 0 ;
虽然在通常情况下这种方法已经很令人满意了,但是你还可能想要在释放设备描述表之后,仍然保存程序中对设备描述表属性所做的改变,以便在下一次调用GetDC和BeginPaint时它们仍然能够起作用。为此,可在设计窗口类时,将CS_OWNDC标志包含为窗口类的一部分:
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC ;
现在,基于这个窗口类所创建的每个窗口都将拥有自己的设备描述表,它一直存在,直到窗口被删除。如果使用了CS_OWNDC风格,就只需初始化设备描述表一次,可以在处理WM_CREATE消息处理期间完成这一操作:
case WM_CREATE:
hdc = GetDC (hwnd) ;
[initialize device context attributes]
ReleaseDC (hwnd, hdc) ;
这些属性在改变之前一直有效。
CS_OWNDC风格只影响GetDC和BeginPaint获得的设备内容,不影响其它函数获得的设备描述表。使用了CS_OWNDC,你仍然应该在退出窗口过程之前释放设备描述表。
某些情况下,你可能想改变某些设备描述表属性,用改变后的属性进行绘图,然后恢复原来的设备描述表属性。要简化这一过程,可以通过如下调用来保存设备描述表的状态:
int idSaved = SaveDC (hdc) ;
现在,可以改变一些属性,在想要回到调用SaveDC前存在的设备内容时,调用:
RestoreDC (hdc, idSaved) ;
你可以在调用RestoreDC之前调用SaveDC数次。
大多数程序写作者以不同的方式使用SaveDC和RestoreDC。然而,更像汇编语言中的PUSH和POP指令,当你调用SaveDC时,可以不需要保存返回值:
SaveDC (hdc) ;
然后,你可以更改某些属性并再次调用SaveDC。要将设备内容恢复到一个已经保存的状态,调用:
RestoreDC (hdc, -1) ;
这就将设备描述表恢复到最近由SaveDC函数保存的状态中。
附录
小技巧:
在处理WM_PAINT消息时,为了在更新的矩形外绘图,可以在调用BeginPaint之前使用如下函数:
InvalidateRect (hwnd, NULL, TRUE) ;
它使整个显示区域变为无效,并擦除背景。但是,如果最后一个参数等于FALSE,则不擦除背景,原有的东西将保留在原处。
通常这是Windows程序在无论何时收到WM_PAINT消息而不考虑rcPaint结构的情况下简单地重画整个客户区最方便的方法。例如,如果在客户区的显示输出中包括了一个圆,但是只有圆的一部分落到了无效矩形中,它就仅绘制圆在无效矩形中的部分。这对于画整个圆来说是无意义的。注意:使用从BeginPaint返回的设备描述表句柄时,Windows不会绘制rcPaint矩形外的任何部分。在BeginPaint调用之前使用上面那个函数,可使rcPaint结构中的无效矩形为整个客户区,就不愁绘制圆了。
在处理WM_PAINT消息时,必须成对地调用BeginPaint和EndPaint。如果窗口过程不处理WM_PAINT消息,则它必须将WM_PAINT消息传递给Windows中DefWindowProc(默认窗口过程)。DefWindowProc以下列代码处理WM_PAINT消息:
case WM_PAINT:
BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
这两个BeginPaint和EndPaint调用之间中没有任何语句,仅仅使先前无效区域变为有效。但以下方法是错误的:
case WM_PAINT:
return 0 ; // WRONG !!!
Windows将一个WM_PAINT消息放到消息队列中,是因为客户区的一部分无效。如果不调用BeginPaint和EndPaint(或者ValidateRect),则Windows不会使该区域变为有效。若一直保持无效,Windows将会再发送另一个WM_PAINT消息,且一直发送下去。