上一页 下一页 返回

8.2   图形设备接口(GDI)

图形设备接口(GDI)是一个可执行程序,它接受Windows应用程序的绘图请求(表现为GDI函数调用),并将它们传给相应的设备驱动程序,完成特定于硬件的输出,象打印机输出和屏幕输出。

GDI负责Windows的所有图形输出,包括屏幕上输出像素、在打印机上输出硬拷贝以及绘制Windows用户界面。

8.2.1 三种图形输出类型

应用程序可以使用GDI创建三种类型的图形输出:矢量输出、光栅图形输出和文本输出。

矢量图形输出

矢量图形输出指的是创建线条和填充图形,包括点、直线、曲线、多边形、扇形和矩形的绘制。

光栅输出

光栅图形的输出是指光栅图形函数对以位图形式存储的数据进行操作,它包括各种位图和图标的输出。在屏幕上表现为对若干行和列的像素的操作,在打印机上则是若干行和列的点阵的输出。

光栅图形输出的优点是速度很快,它是直接从内存到显存的拷贝操作。缺点是需要额外的内存空间。Windows在绘制界面时使用了大量的光栅输出。

文本输出

与DOS字符方式下的输出不同,Windows是按图形方式输出的。这样,在输出文本时,必须以逻辑坐标为单位计算文本的输出位置,而不是象DOS下以文本行为单位输出文本。这比DOS下的文本输出要难一些。

但是,按图形方式输出文本也给文本输出带来很大的灵活性。用户可以通过调用各种GDI函数,制造出各种文本输出效果,包括加粗、斜体、设置颜色等。

Windows还提供了一种TrueType(写真字体)。TrueType字体用一组直线和曲线命令及一些参数来描述字体的轮廓。Windows可以通过参数来调整直线的长度和曲线的形状,从而实现对字体的自由缩放。

8.2.2 MFC中与GDI有关的类

为了支持GDI绘图,MFC提供了两种重要的类:设备上下文类,用于设置绘图属性和绘制图形;绘图对象类,封装了各种GDI绘图对象,包括画笔、刷子、字体、位图、调色板和区域。

设备上下文类

设备上下文类包括CDC和它的派生类CClientDC、CPaintDC、CWindowDC、CMetaFileDC。

CDC是设备上下文类的基类,除了一般的窗口显示外,还用于基于桌面的全屏幕绘制和非屏幕显示的打印机输出。CDC类封装了所有图形输出函数,包括矢量、光栅和文本输出。

CClientDC(客户区设备上下文)用于客户区的输出,它在构造函数中封装了GetDC(),在析构函数中封装了ReleaseDC()函数。一般在响应非窗口重画消息(如键盘输入时绘制文本、鼠标绘图)绘图时要用到它。用法是:

CClientDC dc(this);//this一般指向本窗口或当前活动视图

dc.TextOut(10,10,str,str.GetLength());

//利用dc输出文本,如果是在CScrollView中使用,还要注意调

//用OnPrepareDC(&dc)调整设备上下文的坐标。

CPaintDC用于响应窗口重绘消息(WM_PAINT)是的绘图输出。CPaintDC在构造函数中调用BeginPaint()取得设备上下文,在析构函数中调用EndPaint()释放设备上下文。EndPaint()除了释放设备上下文外,还负责从消息队列中清除WM_PAINT消息。因此,在处理窗口重画时,必须使用CPaintDC,否则WM_PAINT消息无法从消息队列中清除,将引起不断的窗口重画。CPaintDC也只能用在WM_PAINT消息处理之中。

CWindowDC用于窗口客户区和非客户区(包括窗口边框、标题栏、控制按钮等)的绘制。除非要自己绘制窗口边框和按钮(如一些CD播放程序等),否则一般不用它。

CMetaFileDC专门用于图元文件的绘制。图元文件记录一组GDI命令,可以通过这一组GDI命令重建图形输出。使用CMetaFileDC时,所有的图形输出命令会自动记录到一个与CMetaFileDC相关的图元文件中。

图形对象类

图形对象类包括CGdiObject、画笔、刷子、字体、位图、调色板、区域等。CGdiObject是图形对象类的基类,但该类不能直接为应用程序所使用。要使用GDI对象,必须使用它的派生类:画笔、刷子、字体、位图、区域等等。

使用图形对象要注意两点:

1.同其他MFC对象一样,GDI对象的创建也要分为两步:第一步,是定义一个GDI绘图对象类的实例;第二步调用该对象的创建方法真正创建对象。

2.创建对象:使用该对象,首先要调用CDC::SelectObject()将它选入到设备上下文中,同时保存原来的设置到一个GDI对象指针比如说pOldObject中。在使用完后,再用SelectObject(pOldObject)恢复原来的设置。但是,如果该设备上下文是用户自己创建的,则不必恢复原来设置,因为框架会在该设备上下文生存期结束时删除该设备上下文,同时也就删除了原来存放于该设备上下文中的绘图对象设置。

下面介绍各种对象的用法:

画笔(CPen):封装GDI画笔,可被选中设备上下文中当前所用得笔。画笔用于绘制对象的边框以及直线和曲线。缺省画笔画一条与一个像素等宽的黑色实线。

要使用画笔,首先要定义一个画笔:

CPen pen;

然后创建画笔。创建画笔有两种方法:

一是使用CPen::CreatePen(int nPenStyle,int nWidth,DWORD crColor)进行初始化。第一个参数是笔的风格。nPenStyle可选值有:

PS_SOLID 实线

PS_DOT 虚线

PS_INSIDEFRAME 在一个封闭形状的框架内画线,若设定的颜色不能在调色板种找到且线宽大于1,Windows会使用一种混色。

PS_NULL 空的画笔,什么也不画

第二个参数是线的宽度,按逻辑单位。若线宽设为0,则不管是什么映射模式下,线宽始终为一个像素。第三个参数是线的颜色,可以选16种VGA颜色中的一种。颜色的设置用一个RGB宏来指定。

RGB宏形式如下

COLORREF RGB(cRed,cGreen,cBlue)

cRed、cGreen、cBlue分别代表颜色的RGB三个分量,它们的取值在0-255之间。可以使用RGB组合成各种色彩。但是,这种表示法并不是很直观,因此我们把常见的RGB组合定义成新的宏并放在一个colors.h中,如清单8.1。

清单8.1 常见色彩定义

/*COLORS.H -常见色彩定义 */

#ifndef _COLORS_H

#define _COLORS_H

//Main Colors

#define WHITE RGB(255,255,255)

#define BLACK RGB(0,0,0)

#define DK_GRAY RGB(128,128,128)

#define LT_GRAY RGB(192,192,192)

//dark colors

#define DK_RED RGB(128,0,0)

#define DK_GREEN RGB(0,128,0)

#define DK_BLUE RGB(0,0,128)

#define DK_PURPLE RGB(128,0,128)

#define DK_YELLOW RGB(128,128,0)

#define DK_CYAN RGB(0,128,128)

//bright colors

#define BR_RED RGB(255,0,0)

#define BR_GREEN RGB(0,255,0)

#define BR_BLUE RGB(0,0,255)

#define BR_PURPLE RGB(255,0,255)

#define BR_YELLOW RGB(255,255,0)

#define BR_CYAN RGB(0,255,255)

#endif

这样,要指定一个淡黄色的宽度为逻辑单位1的实心笔,可以调用:

pen.CreatePen(PS_SOLID,1,BR_YELLOW);

创建笔的另一个方法是使用库存对象。SelectStockObject可从以下库存笔中选择一个:

BLACK_PEN 黑笔

NULL_PEN 空笔(不画线或边框)

WHITE_PEN 白笔

注:库存对象是由操作系统维护的用于绘制屏幕的常用对象,包括库存画笔、刷子、字体等。

刷子(CBrush):封装GDI刷子,可用作设备上下文中当前刷子。刷子用来填充一个封闭图形对象(如矩形、椭圆)的内部区域。缺省的刷子将封闭图形的内部填充成全白色。我们以前所创建的窗口内部都是白色就是窗口使用缺省刷子填充的结果。

可以用以下几种方法创建刷子:

(1)CreateSolidBrush(DWORD crColor)创建一个实心刷子,用一种颜色填充一个内部区域。

(2)CreateHatchBrush(int nIndex,DWORD crColor);创建一个带阴影的刷子,nIndex代表一种影线模式:

图8-2刷子的各种影线效果

(3)用CreatePatternBrush(CBitmap* pBitmap)

用一个位图作刷子,一般采用8X8的位图,因为刷子可以看作8X8的小位图。当Windows桌面背景采用图案(如weave)填充时,使用的就是这种位图刷子。

(4)同样可以使用SelectStockObject()从库存刷子中选取一个:

BLACK_BRUSH 黑色刷子

WHITE_BRUSH 白色刷子

DKGRAY_BRUSH 暗灰刷子

GRAY_BRUSH 灰色刷子

LTGRAY_BRUSH 淡灰色刷子

NULL_BRUSH 空刷子,内部不填充

字体(CFont):封装了GDI字体对象,用户可以建立一种GDI字体,并使用CFont的成员函数来访问它。关于CFont类,我们在前面已经作了一些介绍,这里不再赘述,读者可以参见前一章内容。

位图(CBitmap):封装一GDI位图,它提供成员函数装载和操作位图。

调色板(CPalette):封装GDI调色板,它保存着系统可用的色彩信息,是应用程序和彩色输出设备上下文的接口。

区域CRgn类:封装GDI区域。区域是窗口内的一块多边形或椭圆形的区域。CRgn用于设备环境(通常是窗口)内的区域操作。CRgn通常与CDC的有关剪裁(clipping)的成员函数配合使用。

有关位图和调色板的使用在第十一章“多媒体编程”还要再作详细阐述。

 

8.2.3常见的绘图任务

输出文本

字体大小计算:通过调用GetTextMetrics()返回当前使用字体的尺寸描述,如前面文 本编辑所演示的那样。

字体颜色设置:

设置前景色:CDC::SetTextColor(int nColor);

设置背景色:CDC::SetBkColor(int nColor);

例如:

dc.SetTextColor(WHITE);

dc.SetBkColor(DK_BLUE);

dc.TextOut(10,10,“White Text on blue background”,30);

文字输出:

除了我们前面介绍的文本输出函数TextOut()之外,还有其他几个函数可用于文本输 出:

TabbedTextOut:象TextOut一样显示正文,但用指定的制表间隔扩充制表键Tab。 在前面的文本编辑器中,当输入一个Tab时,TextOut在屏幕上输出一个黑色方块。

ExtTextOut:在指定的矩形中显示正文。可以用该函数删去超出矩形的正文,用正 文背景填充矩形,调整字符间隔。

DrawText:在指定矩形种显示正文,可以用这个函数扩展制表键Tab。在格式化矩 形时调整正文左对齐、右对齐或居中;还可以在一个词中断开以适应矩形边界。

画点

SetPixel在指定坐标处按指定色彩画一点。

画线

MoveToEx将直线起点移动到指定坐标处,LineTo从起点开始画直线到终点处。使 用的线型由当前所用画笔指定。

画弧

Arc(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4);

 

 T8_3.tif (43298 bytes)

图8-3 弧线的坐标定位

封闭图形

矩形Rectangle

圆角矩形RoundRect()

Ellipse在一个矩形内画椭圆

Chord弦形图

Pie画饼形图

Polygon生成封闭的多边形

PolyPolygon画完整的一组多边形

 

其它常用的绘图函数还有:

FillRect:用指定颜色填充矩形且不画边线

Draw3dRect:这是一个非常实用的函数,用于绘制各种3D边框。它的函数原型如下:

void Draw3dRect( LPCRECT lpRect, COLORREF clrTopLeft, COLORREF clrBottomRight );

void Draw3dRect( int x, int y, int cx, int cy, COLORREF clrTopLeft, COLORREF clrBottomRight );

通过设置上下边框的颜色clrTopLeft和clrBottomRight,可以绘制出凸出或 凹陷等各种效果的3D边框。

T8_4.tif (91724 bytes)

图8-4 Draw3dRect绘制3D边框

DrawFocusRect:用点线画一个矩形框,内部不填充,边线是用于屏幕上当前色的相反色画出来的,故第二次画时,会擦除原来所画的线。

ExtFloodFill:用给定的颜色,利用当前刷子填充表面被一个边线包围的区域,用户可以有选择地填充一个由指定颜色组成的区域。

FloodFill:用给定的颜色,利用当前所选的刷子填充显示的底面被一个边线所包围的区域,如多边形区域的填充。

FrameRect:绘制矩形边框时内部不填充。

InvertRect:在某一矩形区域内反显现有颜色。