事实上,图形设备接口(Graphics Device Interface,GDI)是指这样的一个可执行程序,它处理来自Windows应用程序的图形函数调用,然后把这些调用传递给合适的设备驱动程序,由设备驱动程序来执行与硬件相关的函数并产生最后的输出结果。GDI可以看作是一个应用程序与输出设备之间的中介,一方面,GDI向应用程序提供了一个设备无关的编程环境,另一方面,它又以设备相关的格式和具体的设备打交道。
经常同图形设备接口相提并论的另一个概念是设备上下文(Device Context,DC)。设备上下文是一种Windows数据结构,它包括了与一个设备(如显示器或打印机)的绘制属性相关的信息。所有的绘制操作通过一个设备上下文对象进行,该对象封装了实现绘制线条、形状和文本的Windows API函数。设备上下文可以用来向屏幕、打印机和图元文件输出结果。
在Windows应用程序中,我们通常在绘制之前调用BeginPaint函数,然后在设备上下文中进行一系列的绘制操作,最后调用EndPaint函数结束绘制。MFC类CPaintDC封装了这一过程。在构造CPaintDC对象的同时,其构造函数自动调用BeginPaint函数;在消毁CPaintDC对象的同时,其析构函数自动调用EndPaint函数。因此前面所讲述的过程可以对应于下面的三个步骤:构造一个CDC对象,进行绘制操作,消毁该CDC对象。在基于文档/视结构的应用程序框架中,这个过程被进一步的简化。回忆前几章中讲述的内容,我们一般在视类的OnDraw成员函数中处理有关重绘的操作。通过OnPrepareDC成员函数,框架自动的向OnDraw成员函数传递一个类型为CPaintDC的设备上下文对象。我们只需简单的通过该对象进行绘制,而不需要关心这一对象的构造和消毁。这一过程由框架自动的完成,而且,隐藏在背后的设备上下文在对OnDraw的调用返回时由框架进行释放。
除了上面的CPaintDC类外,MFC还提供了其它的一些封装不同设备上下文的类。如CClientDC类,它所封装的设备上下文仅代表了一个窗口的客户区。在CClientDC的构造函数中调用的不是BeginPaint函数,而是GetDC函数;相应的,ReleaseDC函数在类CClientDC的析构函数被自动调用。与此对应的还有另一个类CWindowDC,它所封装的设备上下文代表的是整个窗口,不仅包括其客户区,也同时包括窗口的边框及其它非客户区对象。
所有的设备上下文类中比较特殊的是类CMetaFileDC,通过CMetaFileDC对象所进行的绘制操作不是对一个实在的设备来进行的,这些操作都被记录到一个Windows图元文件中。不象自动传递给OnDraw成员函数的CPaintDC对象,如果要在这种情况下使用CMetaFileDC对象的话,我们必须自己调用OnPrepareDC成员函数。
所有的这些设备上下文类都以类CDC作为其基类。
一般情况下,很多绘制操作都是在应用程序的视类的OnDraw成员函数中进行的,前面说到过,当视类窗口收到消息WM_PAINT时,该消息对应的处理函数OnPaint被调用,该处理函数构造一个CPaintDC对象,并将指向该对象的指针传递给OnDraw成员函数。这里我们考虑这样一种情况,如果我们正在编写的是一个通过鼠标在屏幕上绘图的应用程序。这时我很显然需要为鼠标的移动消息添加消息处理函数,而且,我们希望用户在移动鼠标的过程中立即就可以看到所绘制的内容,而不是等到窗口收到WM_PAINT消息(即发生重绘事件)才调用成员函数OnDraw绘制窗口。在这种情况下,我们更倾向于直接在鼠标消息处理函数中进行绘制,这时,就需要创建一个设备上下文对象,然后通过该对象调用一系列的绘制方法。
Windows本身是一个图形界面的操作系统,进行Windows程序设计随时都会同设备上下文打交道,甚至在本书前面的章节中的一些示例程序中我们也已经用到了设备上下文,只不过在当时我们回避了与设备上下文有关的很多复杂东西。本章的目的之一就是系统的讨论这些前面已经用到但没有加以阐述的概念和技巧,并补充一些尚未涉及的内容,这些内容包括:
使用设备上下文进行绘制
绘图对象
直线与曲线
填充形状
字体和文本
颜色
坐标空间及变换
这些概念往往是交织起来的,哪怕是一个很简单的绘制操作,往往都需要用到不只一个绘图对象,因此我们很难将它们人为的分割开来进行讲述。在本章上,各节的标题只代表了本节的侧重点,而对于某一个概念的叙述或使用,则有可能分散在不只一个小节中。事实上,一个应用程序是一个整体,它常常需要很多个部件共同协调工作才可以正常工作。因此,出现这种情况是很自然的。
在MFC应用程序中,绘制操作通常涉及三类对象,一类是输出对象,亦即设备上下文对象,包括CDC及其派生类;一类是绘制工具对象,亦即前面所说的图形对象,如果CFont、CBrush和CPen等;另一类属于Windows编程中需要用到的的基本数据类型,如CPoint、CSize和CRect等。
不同的设备上下文类封装了不同类型的设备上下文类对象,如表1所示。
除了设备上下文以外,在Windows中进行绘制通常还需要各种绘制工具,如用来绘制线条的笔、用来填充一个图形内部的刷子以及用来绘制文本的字体等。这些工具称作图形对象,它们由Windows系统提供,MFC提供的图形对象类对它们进行了封装,表2给出了这些图形对象以及与它们等价的Windows图形设备句柄类型。
表 1 MFC中的设备上下文类
设备上下文类 |
描述 |
CDC |
所有设备上下文类的基类。可用来直接访问整个显示器或如打印机之类的非显示设备上下文。 |
CPaintDC |
在窗口的OnPaint成员函数中使用的一种显示上下文。在其构造过程中自动调用BeginPaint,在其析构过程中自动调用EndPaint。 |
CClientDC |
代表窗口的客户区的显示上下文。通常在需要直接在窗口客户区进行绘制时使用。 |
CWindowDC |
代表整个窗口的显示上下文,包括客户区和非客户区。 |
CMetaFileDC |
代表Windows图元文件的设备上下文。一个Windows图元文件包括一系列的图形设备接口命令,可以通过重放这些命令来创建图形。向CMetaFileDC对象进行的各种绘制操作可以被记录到一个图元文件中。 |
表 2 MFC中的Windows GDI对象类
图形对象类 |
等价的Windows图形设备句柄 |
描述 |
CBrush |
HBRUSH |
用来填充正在绘制的对象的内部 |
CPen |
HPEN |
用来绘制对象的边线 |
CFont |
HFONT |
用来绘制文本 |
CBitmap |
HBITMAP |
用来提供操作位图的接口 |
CPalette |
HPALETTE |
用作应用程序和色彩输出设备(如显示器)之间的接口 |
1.1 几个与图形绘制有关的简单数据类型
在讲述设备上下文和图形对象之前,我们来介绍几个常用的数据结构类。
(1) CPoint类
CPoint类封装了一个点的坐标。它事实上是从POINT结构派生而来的。结构POINT在Win32 SDK中定义。因此,CPoint也继承了POINT结构的数据成员x和y。CPoint对象可以用在任何使用POINT结构的场合。CPoint对象还可以和另一种简单数据类型CSize或SIZE结构相互进行转换。
CPoint类具有多种形式的构造函数:
CPoint( );
CPoint( int initX, int initY );
CPoint( POINT initPt );
CPoint( SIZE initSize );
CPoint( DWORD dwPoint );
当使用DWORD类型的值来构造CPoint对象时,其低位字将被赋值给CPoint对象的成员x,高位字将被赋值给成员y。
CPoint的成员函数Offset可以设置点的偏移量,同时,在类中定义的一些运算符,如?、?、??和??等大大的简化了对点坐标的各种运算和比较。
(2) CSize类
如果要表示距离以及相对位置,可以使用CSize对象。MFC类CSize事实上是从SIZE派生而来的,因此,CSize继承了SIZE结构的数据成员cx和cy。构造一个CSize对象与用对应的方法构造CPoint对象非常相似,因此我们不需讲述。同样,我们可以使用一个DWORD值来构造CSize对象,这时,其低位字被赋值给CSize对象的成员cx ,高位字被赋值给成员cy。在类Size中定义了六个运算符:?、?、??、??、??和??。
(3) CRect类
CRect类是编程时经常使用的几个简单数据结构之一,它从RECT结构派生,因此,CRect类继承了RECT结构的数据成员left、top、right和bottom。它们是CRect的公有成员。
一个CRect对象可以传递给任何以RECT结构或LPCRECT和LPRECTW指针为参数的函数。
注意:
在指定一个CRect对象时,一般情况下我们需要使它的左边界的坐标小于右边界的坐标和上边界的坐标小于下边界的坐标。我们称满足该条件的矩形为常态矩形。很多函数要求传递给它的CRect对象表示一个常态矩形,否则这些函数将有可能返回一个错误的结果。我们可以通过调用成员函数NormalizeRect来将一个非常态矩形转换为一个常态矩形。在程序中出现非常态矩形并不一定的程序员的疏忽大意。这里举一个例子,如果当前显示上下文的映射模式为MM_LOENGLISH,将一个表示常态矩形的CRect对象传递给成员函数CDC::DPtoLP,将得到一个非常态矩形,该矩形的高度将成为一个负值。这是因为在MM_LOENGLISH映射模式中,纵坐标的方向是向上的。
相比我们在前面所讲述的CPoint类和CSize类来说,类CRect要庞大得多。表列出了在类CRect中定义的成员函数。
表 3 类CRect的成员函数
成员函数 |
描述 |
Width |
计算矩形的宽度 |
Height |
计算矩形的高度 |
Size |
计算矩形的大小 |
续表3
成员函数 |
描述 |
TopLeft |
返回矩形的左上角 |
BottomRight |
返回矩形的右下角 |
CenterPoint |
返回矩形的中点 |
IsRectEmpty |
判断矩形是否为空。空的矩形的宽和高都为0 |
IsRectNull |
判断矩形的top、bottom、left和right成员变量是否全都为0 |
PtInRect |
判断指定点是否的矩形内 |
SetRect |
设置矩形的大小 |
SetRectEmpty |
将矩形设置为空(所有坐标均为0) |
CopyRect |
从源矩形中拷贝维度到矩形中 |
EqualRect |
判断两个矩形是否相等 |
InflateRect |
扩大矩形的宽和高 |
DeflateRect |
减小矩形的宽和高 |
NormalizeRect |
使用矩形的宽和高标准化 |