VC++培训教程草稿(2000年撰写) 张孝祥、袁龙友著 网址:http://www.it315.org
第5章 图形与文本
我们知道,Windows操作系统相对于DOS操作系统一个很大的进步就在于Windows具有丰富的图形界面,使得应用程序与用户之间的交互变得方便、直观。本章主要向读者讲述如何给您的应用程序添加图形及文本,当然,在进行实例之前,有几个重要的概念必须要掌握清楚。
5.1 理解图形设备接口
Windows操作系统提供了一个图形用户界面GUI(Graphics User Interface),图形是组成Windows应用程序的主体,这些图形包括一般的几何图形、位图、光标形状,甚至文本。因此,为了实现Windows应用程序的图形化,Windows操作系统提供了大量的函数来实现绘图的要求,这些函数的集合,就称为图形设备接口GDI(Graphics Device Interface)。
GDI表示的是一个抽象的接口,通过该接口可以实现对图形的颜色、线条的粗细等属性的控制(甚至包括输出文字在内)。Windows图形设备接口提供了一种与设备无关的控制图形输出的方法,它屏蔽了许多硬件设备的差异,应用程序可以通过调用这些函数和硬件打交道,使得用户可以方便的在输出设备上绘图,而不用去考虑到底是哪个厂家生产的硬件。
简单的说,GDI就是应用程序和输出设备之间的中介。一方面,GDI向应用程序提供一个与设备无关的编程环境,另一方面,它又以设备相关的格式和具体的设备打交道。值得注意的是GDI是Microsoft Windows三个重要的动态链接库之一,这三个重要的动态链接库分别为:kernel.dll、User32.dll和GDI32.dll。
5.2 设备描述表
还记得上一章例子的EX04-01是怎么样将“Hello World”输出到窗口的吗?是通过一条语句:pDC->TextOut (0,0,\来实现的。这里,应用程序并没有告诉系统用什么颜色、字体来输出,系统采用的是默认设置。那么这些默认的设置保存在哪儿呢?就是设备描述表。引用设备描述表这一概念是很有效的。那么它到底能带来哪些好处呢?我们知道字体的颜色、样式不会经常改变的,假定用户可能要输出1000个字符串,而它们都使用相同的字体和颜色,如果没有设备描述表,则每输出一个字符串,也就是每调用一次TextOut,就需要把字体和颜色作为参数传递进去,而这样做无非是浪费时间,为了避免这种无用的重复性工作,Windows把这些会重复的参数放到了设备描述表中,这样每次只需要多传入一个设备描述表的指针就可以了。
5.2.1 什么是设备描述表
当Windows要在显示器或其他设备上绘制图形或文本时,它不像DOS系统那样,把图形和文本直接输出到硬件,而是使用一个设备描述表来代替硬件设备的逻辑表示。
设备描述表(Device Context简称为DC),也称设备上下文、设备环境,是一种包含各种绘图属性(如前面所说的字体、颜色)和方法(即各种绘图函数)的数据结构,它定义了设备、画图工具和画图信息,它不仅可以绘制各种图形,而且还可以确定在应用窗口中绘制图形的方式和图形的样式。Windows所有的绘制操作及图形输出都必须通过设备描述表这一虚拟用户工作区来进行。用户在绘图之前,必须获取绘图窗口区域的一个设备环境DC,接着才能进行GDI函数的调用,执行适合于设备环境的命令。
5.2.2 MFC中的设备描述表类
需要注意的是,应用程序不能直接存取设备描述表,只能通过调用有关函数或使用DC的句柄(HDC)来间接的存取DC。设备描述表是一个对象,若要在图形设备上使用,首先
VC++培训教程草稿(2000年撰写) 张孝祥、袁龙友著 网址:http://www.it315.org
必须构造一个DC对象或得到指向DC的指针。MFC库提供了不同类型的设备描述表类,每一个类都封装了代表WindowsDC的句柄(HDC)和函数。
在MFC库中,设备描述表被CDC类封装了起来,而CDC类下面又有4个派生类,这4个派生类各有特点,并可以完成不同的功能。 ? CClientDC 这是一个客户区设备描述表,提供对窗口客户区的图形访问。在窗口中画
图可使用这种DC,但对WM_PAINT消息除外。
? CWindowDC 可以提供在整个窗口(包括客户区和非客户区)中画图的DC。 ? CPaintDC 这是创建响应WM_PAINT消息的DC。应用程序可以使用此CPaintDC更
新Windows显示,通常在MFC应用程序的OnPaint函数中使用。
? CMetaFileDC 这个设备描述表Windows元文件,它包含一系列命令以重新产生图像。
想要创建独立于设备的文件时可使用这种DC。用户可以回放这种文件来创建图像。
以上分别讲述了这四种DC类的特点,下面介绍一下它们之间的区别。 1. CWindowDC类与CPaintDC和CClientDC类的区别:
? 绘图区域不同
用CPaintDC和CClientDC类的对象绘制图形时,绘制区只能是客户区,而不能在非客户区;而CWindowDC可以在非客户区进行绘图。 ? 绘图坐标系不同
在CWindowDC绘图类下,坐标系是建立在整个屏幕之上的,在像素坐标方式下,坐标原点在屏幕的左上角。而CPaintDC和CClientDC类的坐标系是建立在客户区上的,在像素坐标方式下,坐标原点在客户区的左上角。 2. CPaintDC与CClientDC类的区别:
? 绘图机制不同
二者都是在窗口的客户区内绘图。CPaintDC应用在OnPaint函数中,以响应Windows的WM_PAINT消息。而后者应用在非响应消息WM_PAINT的情况下。CPaintDC类响应WM_PAINT消息,自动完成绘制,这对维护图形的完整性有着重要的作用。通俗点说,当用CPaintDC类在OnPaint函数中绘图操作之后,当窗口由于被覆盖或其他什么原因使窗口重画时,系统会自动调用OnPaint函数,那么该函数中的绘图语句同样又被自动调用,又执行了绘图操作,这样就保持了图形的完整性。CClientDC类,可以实时的将图形绘制在屏幕上,需要什么时候画就什么时候画,如果用CPaintDC完成同样的工作,只能发出指令让包含要绘制的这条直线的屏幕部分重画,把这条直线绘制到屏幕上。当然,这个重画区域的其他图形元素同时也会重画。 ? 适用范围不同
CPaintDC只能支持屏幕显示,而CClientDC除了支持屏幕显示,还支持打印。
5.2.3 获取设备描述表
要想在窗口中绘图必须首先获取窗口的设备描述表。没有设备描述表,任何绘图函数都无法工作。Windows不允许直接访问显示设备,而必须通过Windows返回的设备描述表句柄与显示设备进行通讯。用于定义设备描述表句柄的变量是HDC。
之前,我们仅仅在OnDraw中绘制,在这个函数中设备描述表指针(即pDC)作为函数的参数由系统传入。如果系统没有给我们传入这个DC的参数时,我们自己如何来获取对应的DC呢。
1. 使用GetDC函数
VC++培训教程草稿(2000年撰写) 张孝祥、袁龙友著 网址:http://www.it315.org
如果Windows应用程序的绘图操作不是由WM_PAINT消息驱动,就需要调用GetDC函数来获取。GetDC函数有多种形式,最常用的调用形式有两种,一种是Windows API函数形式,如下:
HDC GetDC(
HWND hWnd // handle to window );
该函数只有一个参数,此参数是想要获得的DC所在的窗口的句柄,如果该参数为NULL,那么得到的DC将是整个屏幕。
另一种是CWnd类的成员函数,如下: CDC* GetDC( );
该函数用于获取指定窗口工作区的显示器设备描述表,该函数不带任何参数。如果函数调用成功,则返回标识CWnd客户区的设备环境,否则返回NULL。
一般来说在完成绘图之后,用GetDC函数获取的设备描述表必须通过ReleaseDC函数来释放。同样,对应于常用的两个GetDC函数,也有两种常用的与其匹配的ReleaseDC函数。分别如下:
Windows API函数形式: int ReleaseDC( HWND hWnd, HDC hDC ); CWnd类的成员函数形式:int ReleaseDC( CDC* pDC );
使用哪个ReleaseDC函数和你先前使用的GetDC函数都是一一对应的。 2. 使用BeginPaint函数
Windows应用程序响应WM_PAINT消息进行图形刷新时,会通过调用BeginPaint函数来获取DC,BeginPaint函数也有多种形式,其最常用的调用形式也有两种,一种是Windows API函数形式,如下:
HDC BeginPaint( HWND hwnd,
LPPAINTSTRUCT lpPaint );
该函数有两个参数,一个是需要重绘的窗口的句柄,另一个是指向结构PAINTSTRUCT变量的指针。
另一种常用的形式是CWnd类的成员函数,如下: CDC* BeginPaint( LPPAINTSTRUCT lpPaint );
该函数只有一个参数,就是指向结构PAINTSTRUCT变量的指针。
系统无论调用哪种形式的BeginPaint函数,都要填写PAINTSTRUCT结构以标识需要刷新的无效矩形。只有在响应WM_PAINT消息时才调用BeginPaint函数,但在返回处理消息的结果之前,必须调用EndPaint函数以释放资源。对应于BeginPaint,EndPaint也有和其匹配的两种常用的调用形式,如下:
Windows API函数形式:BOOL EndPaint ( HWND hWnd,CONST PAINTSTRUCT
*lpPaint );
CWnd类的成员函数形式:void EndPaint( LPPAINTSTRUCT lpPaint );
因为应用程序不能保存用函数BeginPaint获取的DC以用于处理另一个消息,在接收到另一个消息的时候,先前获取的DC很可能已经非法了,所以在处理完WM_PAINT消息后,必须将获取到的DC释放,以免造成内存泄漏。 3. 直接构造CDC对象
该方法是用声明一个CDC类或其派生类对象的方式来获取。方法如下:
VC++培训教程草稿(2000年撰写) 张孝祥、袁龙友著 网址:http://www.it315.org
CClientdc dc(CWnd*);
此时,构造的是一个对象。这种方法实际上是间接使用了GetDC成员函数。因为当一个C++类声明一个对象时,系统会自动调用该类的构造函数,而在CClient类的构造函数中就调用了GetDC函数,当这个对象被释放时,又会自动调用该的析构函数,而在析构函数中,则调用了ReleaseDC函数来释放设备描述表。
5.3 Windows的图形设备接口对象
前面提到GDI是一个含有与各种图形操作有关的函数集,而与各种绘图操作有关的内容则更多的都保存在图形设备接口对象(GDI对象)中。一个Windows的GDI对象类型是由一个MFC库类表示的,其中CGdiObject类便是所有图形设备接口对象的一个抽象的基类。然而,设计人员在做开发的过程中很少用到基类CGdiObject类,而是经常用到其派生类,通常一个Windows GDI对象都是由CGdiObject的派生类的C++对象所表示的,CGdiObject的派生类包括CBitmap、CBrush、CFont、CPen、CRgn、CPalette,分别讲述如下:
? CBitmap:位图,一种位矩阵,每一个显示像素都对应于矩阵中的一位或多位。用
户不仅可以利用位图来表示图像,还可以利用它来创建画刷。
? CBrush:画刷,定义了一种位图形式的象素,利用它可以对区域内部填充颜色。 ? CFont:字体,一种具有特定风格和尺寸的所有字符的完整集合。字体通常作为一
种资源存在与磁盘上,并且对一些设备来说具有依赖性。
? CPen;画笔,是一种用来画线及绘制有形边框的工具,可以指定它的颜色及宽度,
并且可以指定它的线型(实线、点线、虚线等)。
? CRgn:区域,是由多边形、椭圆或二者组合形成的一种范围,可以利用它来进行
填充、裁剪以及点中测试。
? CPalette:调色板,是一种颜色映射接口,它允许一个应用程序在不干扰其他应用
程序的情况下,充分利用输出设备的颜色能力。
为了使用某种GDI对象,一般来说需要首先构造一个新的GDI对象,然后将这个新的GDI对象选入一个设备描述表中进行绘制工作,将设备描述表中原来的旧的GDI对象保留,当绘制工作结束,在退出应用程序之前将旧的GDI对象再选回给设备描述表,将先前构造的新的GDI对象删除。
5.4 GDI对象的创建
在本节中,将讲述各种GDI对象的创建方法。我们不需要构造基类CGdiObject的对象,但是,需要构造派生类对象。在GDI对象中有6种库存刷子、3种库存笔和6种库存字体,我们可以直接取出来使用,同时,CBrush类、CPen类和CFont类提供了一系列的函数来创建绘图工具。有些类的构造函数已经有足够的信息可以构造对象,如CBrush和CPen。而有些类使用默认构造函数来构造C++对象之后还要调用一个创建函数,如CFont和CRgn。
5.4.1 自定义画刷(CBrush)
? CBrush类提供用于产生刷子的构造函数:
CBrush( );
CBrush( COLORREF crColor );
CBrush( int nIndex, COLORREF crColor ); CBrush( CBitmap* pBitmap );
VC++培训教程草稿(2000年撰写) 张孝祥、袁龙友著 网址:http://www.it315.org
I. CBrush( COLORREF crColor );
它可以产生某种颜色的实心刷子,下面的代码产生了一个红色的实心刷子。
CBrush br(RGB(255,0,0)); dc.SelectObject(&br);
II. CBrush( int nIndex, COLORREF crColor );
它可以产生某种剖面线的刷子,下面的代码产生了一个红色的剖面线刷子。
CBrush br(HS_FDIAGONAL,RGB(255,0,0)); dc.SelectObject(&br);
III.CBrush( CBitmap* pBitmap );
它可以产生位图刷子。
CBitmap bmp;
bmp.LoadBitmap (IDB_BITMAP1); CBrush br(&bmp); dc.SelectObject(&br);
这段代码首先装入了一幅位图(先在资源面板中添加一个位图资源,其ID指定为IDB_BITMAP1),再根据这幅位图产生了一个位图刷子。 IV.产生空刷子
空刷子是一种特殊的刷子,有两种方法可以产生。 1) LOGBRUSH logbr;
logbr.lbStyle=BS_NULL;
br.CreateBrushIndirect(&logbr); dc.SelectObject(&br);
2)HBRUSH hbr=(HBRUSH)::GetStockObject(NULL_BRUSH);
CBrush *pbr; pbr=CBrush::FromHandle(hbr); dc.SelectObject(pbr);
其中,CBrush::FromHandle是一个静态成员函数,它可以不生成类的实例而直接调用,但前面一定要加上类名,以表示它是哪个类的成员函数。
? CBrush类提供用于创建刷子的成员函数
I. CBrush::CreateSolidBrush
创建一个具有指定颜色的刷子,其用法如下:
BOOL CreateSolidBrush( COLORREF crColor); 参数crColor指定刷子的颜色。 II. CBrush::CreateHatchBrush
创建一个具有指定阴影样式和颜色的刷子,其用法如下:
BOOL CreateHatchBrush ( int nIndex , COLORREF crColor);
参数nIndex指定刷子的阴影模式,详情请参见MSDN。crColor指定刷子颜色。 III. CBrush::CreateBrushIndirect
创建一个拥有LOGBRUSH结构所指定风格、颜色和样式的刷子,其用法如下:
BOOL CreateBrushIndirect (const LOGBRUSH* lpLogBrush);
参数lpLogBrush指向包含刷子信息的LOGBRUSH结构的指针,详情请参见MSDN。