Download presentation
Presentation is loading. Please wait.
Published byPhilomena Shona Cunningham Modified 6年之前
1
第7章图形、文本和位图 7.1概述 7.2图形设备接口 7.3图形绘制 7.4字体与文字处理 7.5在对话框及控件中绘图 7.6综合应用
2
7.1概述 设备环境类 设备环境类CDC提供了绘制和打印的全部函数。为了能让用户使用一些特殊的设备环境,CDC还派生了CPaintDC、CClientDC、CWindowDC和CMetaFileDC类。 (1)CPaintDC比较特殊,它的构造函数和析构函数都是针对OnPaint进行的,但用户一旦获得相关的CDC指针,就可以将它当成任何设备环境(包括屏幕、打印机)指针来使用。CPaintDC类的构造函数会自动调用BeginPaint,而它的析构函数则会自动调用EndPaint。 (2)CClientDC只能在窗口的客户区(不包括边框、标题栏、菜单栏以及状态栏)中进行绘图,点(0,0)通常指的是客户区的左上角。而CWindowDC允许在窗口的任意位置中进行绘图,点(0,0)指整个窗口的左上角。CWindowDC和CClientDC构造函数分别调用GetWindowDC和GetDC,但它们的析构函数都是调用ReleaseDC函数。 (3)CMetaFileDC封装了在一个Windows图元文件中绘图的方法。图元文件是一系列与设备无关的图片的集合,由于它对图象的保存比像素更精确,因而往往在要求较高的场合下使用,例如AutoCAD的图像保存等。目前的Windows已使用增强格式(enhanced-format)的32位图元文件来进行操作。
3
7.1概述 7.1.2坐标映射 在讨论坐标映射之前,先来看看下列语句:
pDC->Rectangle(CRect(0,0,200,200)); 它是在某设备环境中绘制出一个高为200个像素,宽也为200个像素的方块。由于默认的映射模式是MM_TEXT,其逻辑坐标(在映射模式下的坐标)和设备坐标(显示设备或打印设备坐标系下的坐标)相等。因此这个方块在1024 x 768的显示器上看起来要比在640 x 480的显示器上显得小一些,而且若将它打印在600dpi精度的激光打印机上,这个方块就会显得更小了。为了能保证打印的结果不受设备的影响,Windows定义了一些映射模式,这些映射模式决定了设备坐标和逻辑坐标之间的关系,如表7.1所示。 表7.1 映射模式 映射模式 含 义 MM_TEXT 每个逻辑单位等于一个设备像素,x向右为正,y向下为正 MM_HIENGLISH 每个逻辑单位为0.001英寸,x向右为正,y向上为正 MM_LOENGLISH 每个逻辑单位为0.01英寸,x向右为正,y向上为正 MM_HIMETRIC 每个逻辑单位为0.01毫米,x向右为正,y向上为正 MM_LOMETRIC 每个逻辑单位为0.1毫米,x向右为正,y向上为正 MM_TWIPS 每个逻辑单位为一个点的1/20(一个点是1/72 英寸),x向右为正,y向上为正 MM_ANISOTROPIC x,y可变比例 MM_ISOTROPIC x,y等比例
4
7.1概述 在映射模式MM_ANISOTROPIC和MM_ISOTROPIC中,常常可以调用CDC:: SetWindowExt(设置窗口大小)和CDC::SetViewportExt(设置视口大小)函数来设置所需要的比例因子。这里的“窗口”和“视口”的概念往往不易理解。所谓“窗口”,可以理解成是一种逻辑坐标下的窗口,而“视口”是实际看到的那个窗口,也就是设备坐标下的窗口。根据“窗口”和“视口”的大小就可以确定x和y的比例因子,它们的关系如下: x比例因子 = 视口x大小 / 窗口x大小 y比例因子 = 视口y大小 / 窗口y大小 [例Ex_Draw] 通过设置窗口和视口大小来改变显示的比例 (1)创建一个默认的单文档应用程序Ex_Draw。
5
7.1概述 (2)在CEx_DrawView::OnDraw函数中添加下列代码:
void CEx_DrawView::OnDraw(CDC* pDC) { CEx_DrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CRect rectClient; GetClientRect(rectClient); // 获得当前窗口的客户区大小 pDC->SetMapMode(MM_ANISOTROPIC); // 设置MM_ANISOTROPIC映射模式 pDC->SetWindowExt(1000,1000); // 设置窗口范围 pDC->SetViewportExt(rectClient.right,-rectClient.bottom); // 设置视口范围 pDC->SetViewportOrg(rectClient.right/2,rectClient.bottom/2);// 设置视口原点 pDC->Ellipse(CRect(-500,-500,500,500)); }
6
7.1概述 (3)编译运行,结果如图7.1所示。 图7.1 改变显示比例
7
7.1概述 7.1.3 CPoint、CSize和CRect
在图形绘制操作中,常常需要使用MFC中的CPoint、CSize和CRect等简单数据类。由于CPoint(点)、CSize(大小)和CRect(矩形)是对Windows的POINT、SIZE和RECT结构的封装,因此它们可以直接使用各自结构的数据成员,如下所示: typedef struct tagPOINT { LONG x; // 点的x坐标 LONG y; // 点的y坐标 } POINT; typedef struct tagSIZE int cx; // 水平大小 int cy; // 垂直大小 } SIZE; typedef struct tagRECT LONG left; // 矩形左上角点的x坐标 LONG top; // 矩形左上角点的y坐标 LONG right; // 矩形右下角点的x坐标 LONG bottom; // 矩形右下角点的y坐标 } RECT;
8
7.1概述 1. CPoint、CSize和CRect类的构造函数 CPoint类带参数的常用构造函数原型如下:
CPoint( int initX, int initY ); CPoint( POINT initPt ); 其中,initX和initY分别用来指定CPoint的成员x和y的值。initPt用来指定一个POINT结构或CPoint对象来初始化CPoint的成员。 CSize类带参数的常用构造函数原型如下: CSize( int initCX, int initCY ); CSize( SIZE initSize ); 其中,initCX和initCY用来分别设置CSize的cx和cy成员。initSize用来指定一个SIZE结构或CSize对象来初始化CSize的成员。 CRect类带参数的常用构造函数原型如下: CRect( int l, int t, int r, int b ); CRect( const RECT& srcRect ); CRect( LPCRECT lpSrcRect ); CRect( POINT point, SIZE size ); CRect( POINT topLeft, POINT bottomRight );
9
7.1概述 其中,l、t、r、b分别用来指定CRect的left、top、right和bottom成员的值。srcRect 和lpSrcRect 分别用一个RECT结构或指针来初始化CRect的成员。point用来指定矩形的左上角位置。size用来指定矩形的长度和宽度。topLeft和bottomRight分别用来指定CRect的左上角和右下角的位置。 2. CRect类的常用操作 由于一个CRect类对象包含用于定义矩形的左上角和右下角点的成员变量,因此在传递LPRECT、LPCRECT或RECT结构作为参数的任何地方,都可以使用CRect对象来代替。 需要说明的是,当构造一个CRect时,要使它符合规范。也就是说,使其left小于right,top小于bottom。例如,但若左上角为(20, 20),而右下角为(10, 10),那么定义的这个矩形就不符合规范。一个不符合规范的矩形,CRect的许多成员函数都会不会有正确的结果。基于此种原因,常常使用CRect::NormalizeRect函数使一个不符合规范的矩形合乎规范。 CRect类的操作函数有很多,这里只介绍矩形的扩大、缩小以及两个矩形的“并”和“交”操作,更多的常用操作如表7.2所示。
10
7.1概述 表7.2 CRect类常用的成员函数 成员函数 功能说明 int Width( ) const; 返回矩形的宽度
int Height( ) const; 返回矩形的高度 CSize Size( ) const; 返回矩形的大小,CSize中的cx和cy成员分别表示矩形的宽度和高度 CPoint& TopLeft( ); 返回矩形左下角的点坐标 CPoint& BottomRight( ); 返回矩形右下角的点坐标 CPoint CenterPoint( ) const; 返回CRect的中点坐标 BOOL IsRectEmpty() const; 如果一个矩形的宽度或高度是0或负值,则称这个矩形为空,返回TRUE BOOL IsRectNull() const; 如果一个矩形的上、左、下和右边的值都等于0,则返回TRUE BOOL PtInRect( POINT point ) const; 如果点point位于矩形中(包括点在矩形的边上),则返回TRUE void SetRect( int x1, int y1, int x2, int y2 ); 将矩形的各边设为指定的值,左上角点为(x1, y1),右下角点为(x2, y2) void SetRectEmpty(); 将矩形的所有坐标设置为零 void NormalizeRect( ); 使矩形符合规范 void OffsetRect( int x, int y ); void OffsetRect( POINT point ); void OffsetRect( SIZE size ); 移动矩形,水平和垂直移动量分别由x、y或point、size的两个成员来指定
11
7.1概述 成员函数InflateRect和DeflateRect用来扩大和缩小一个矩形。由于它们的操作是相互的,也就是说,若指定InflateRect函数的参数为负值,那么操作的结果是缩小矩形,因此下面只给出InflateRect函数的原型: void InflateRect( int x, int y ); void InflateRect( SIZE size ); void InflateRect( LPCRECT lpRect ); void InflateRect( int l, int t, int r, int b ); 其中,x用来指定扩大CRect左、右边的数值。y用来指定扩大CRect上、下边的数值。size中的cx成员指定扩大左、右边的数值,cy指定扩大上、下边的数值。lpRect的各个成员用来指定扩大每一边的数值。l、t、r和b分别用来指定扩大CRect左、上、右和下边的数值。 需要注意的是,由于InflateRect是通过将CRect的边向远离其中心的方向移动来扩大的,因此对于前两个重载函数来说,CRect的总宽度被增加了两倍的x或cx,总高度被增加了两倍的y或cy。
12
7.1概述 成员函数IntersectRect和UnionRect分别用来将两个矩形进行相交和合并,当结果为空时返回FALSE,否则返回TRUE。它们的原型如下: BOOL IntersectRect( LPCRECT lpRect1, LPCRECT lpRect2 ); BOOL UnionRect( LPCRECT lpRect1, LPCRECT lpRect2 ); 其中,lpRect1和lpRect2用来指定操作的两个矩形。例如: CRect rectOne(125, 0, 150, 200); CRect rectTwo( 0, 75, 350, 95); CRect rectInter; rectInter.IntersectRect(rectOne, rectTwo); // 结果为(125, 75, 150, 95) ASSERT(rectInter == CRect(125, 75, 150, 95)); rectInter.UnionRect (rectOne, rectTwo); // 结果为(0, 0, 350, 200) ASSERT(rectInter == CRect(0, 0, 350, 200));
13
7.1概述 颜色和颜色对话框 在MFC中,CDC使用的是RGB颜色空间,即选用红(R)、绿(G)、蓝(B)三种基色分量,通过对这三种基色不同比例的混合,可以得到不同的彩色效果。并且,MFC使用COLORREF数据类型来表示一个32位的RGB颜色,它也可以用下列的十六进制表示: 0x00bbggrr 此形式的rr、gg、bb分别表示红、绿、蓝三个颜色分量的16进制值,最大为0xff。在具体操作RGB颜色时,还可使用下列的宏操作: GetBValue 获得32位RGB颜色值中的蓝色分量 GetGValue 获得32位RGB颜色值中的绿色分量 GetRValue 获得32位RGB颜色值中的红色分量 RGB 将指定的R、G、B分量值转换成一个32位的 RGB颜色值。 MFC的CColorDialog类为应用程序提供了颜色选择通用对话框,如图7.2所示。
14
7.1概述 图7.2 颜色对话框
15
7.1概述 CColorDialog类具有下列的构造函数:
CColorDialog( COLORREF clrInit = 0, DWORD dwFlags = 0, CWnd* pParentWnd = NULL ); 其中,clrInit用来指定选择的默认颜色值,若此值没指定,则为RGB(0,0,0) (黑色)。pParentWnd用来指定对话框的父窗口指针。dwFlags用来表示定制对话框外观和功能的系列标志参数。它可以是下列值之一或”|”组合: CC_ANYCOLOR 在基本颜色单元中列出所有可得到的颜色 CC_FULLOPEN 显示所有的颜色对话框界面。若此标志没有被设定,则用户 单击“规定自定义颜色”按钮才能显示出定制颜色的界面 CC_PREVENTFULLOPEN 禁用“规定自定义颜色”按钮 CC_SHOWHELP 在对话框中显示“帮助”按钮 CC_SOLIDCOLOR 在基本颜色单元中只列出所得到的纯色 当对话框“OK”退出(即DoModal返回 IDOK)时,可调用下列成员获得相应的颜色。 COLORREF GetColor( ) const; // 返回用户选择的颜色。 void SetCurrentColor( COLORREF clr );// 强制使用clr作为当前选择的颜色 static COLORREF * GetSavedCustomColors( ); // 返回用户自己定义颜色
16
7.2图形设备接口 Windows为设备环境提供了各种各样的绘图工具,例如用于画线的“画笔”、填充区域的“画刷”以及用于绘制文本的“字体”。MFC封装了这些工具,并提供相应的类来作为应用程序的图形设备接口GDI,这些类有一个共同的抽象基类CGdiObject,具体如表7.3所示。 表7.3 MFC的GDI类 类 名 说 明 CBitmap “位图”是一种位矩阵,每一个显示象素都对应于其中的一个或多个位。用户可以利用位图来表示图像,也可以利用它来创建画刷。 CBrush “画刷”定义了一种位图形式的象素,利用它可对区域内部填充颜色或样式。 CFont “字体”是一种具有某种风格和尺寸的所有字符的完整集合,它常常被当作资源存于磁盘中,其中有一些还依赖于某种设备。 CPalette “调色板”是一种颜色映射接口,它允许应用程序在不干扰其他应用程序的前提下,可以充分利用输出设备的颜色描绘能力。 CPen “画笔”是一种用来画线及绘制有形边框的工具,用户可以指定它的颜色及宽度,并且可以指定它画实线、点线或虚线等。 CRgn “区域”是由多边形、椭圆或二者组合形成的一种范围,可以利用它来进行填充、裁剪以及鼠标点中测试等。
17
7.2图形设备接口 7.2.1 使用GDI对象 在选择GDI对象进行绘图时,往往遵循着下列的步骤:
(1)在堆栈中定义一个GDI对象(如CPen、CBrush对象),然后用相应的函数(如CreatePen、CreateSolidBrush)创建此GDI对象。但要注意:有些GDI派生类的构造函数允许用户提供足够的信息,从而一步即可完成对象的创建任务,这些类有CPen、CBrush。 (2)将构造的GDI对象选入当前设备环境中,但不要忘记将原来的GDI对象保存起来。 (3)绘图结束后,恢复当前设备环境中原来的GDI对象。 (4)由于GDI对象是在堆栈中创建中,当程序结束后,会自动删除程序创建的GDI对象。 具体操作可像下面的代码过程: void CMyView::OnDraw( CDC* pDC ) { CPen penBlack; // 定义一个画笔变量 penBlack.CreatePen( PS_SOLID, 2, RGB(0,0,0)); // 创建画笔 // 将此画笔选入当前设备环境并保存原来的画笔 CPen* pOldPen = pDC->SelectObject( &penBlack ); // 用此画笔绘图 pDC->MoveTo(...); pDC->LineTo(...); // … 其他绘图函数 pDC->SelectObject( pOldPen ); // 恢复设备环境中原来的画笔 }
18
7.2图形设备接口 除了自定义的GDI对象外,Windows还包含了一些预定义的库存GDI对象。由于它们是Windows系统的一部分,因此用户用不着删除它们。CDC的成员函数SelectStockObject可以把一个库存对象选入当前设备环境中,并返回原先被选中的对 象指针,同时使原先被选中的对象从设备环境中分离出来。如下面的代码: void CEx_SDIView::OnDraw( CDC* pDC ) { CPen newPen( PS_SOLID, 2, RGB(0,0,0) ) ) pDC->SelectObject( &newPen ); pDC->MoveTo(...); pDC->LineTo(...); // … 其他绘图函数 pDC->SelectStockObject( BLACK_PEN ); // newPen被分离出来 }
19
7.2图形设备接口 函数SelectStockObject可选用的库存GDI对象类型可以是下列值之一: BLACK_BRUSH 黑色画刷
DKGRAY_BRUSH 深灰色画刷 GRAY_BRUSH 灰色画刷 HOLLOW_BRUSH 中空画刷 LTGRAY_BRUSH 浅灰色画刷 NULL_BRUSH 空画刷 WHITE_BRUSH 白色画刷 BLACK_PEN 黑色画笔 NULL_PEN 空画笔 WHITE_PEN 白色画笔 DEVICE_DEFAULT_FONT 设备默认字体 SYSTEM_FONT 系统字体
20
7.2图形设备接口 画笔 画笔是Windows应用程序中用来绘制各种直线和曲线的一种图形工具,它可分为修饰画笔和几何画笔两种类型。在这两种类型中,几何画笔的定义最复杂,它不但有修饰画笔的属性,而且还跟画刷的样式、阴影线类型有关,通常用在对绘图有较高要求的场合。而修饰画笔只有简单的几种属性,通常用在简单的直线和曲线等场合。 一个修饰画笔通常具有宽度、风格和颜色三种属性。画笔的宽度用来确定所画的线条宽度,它是用设备单位表示的。默认的画笔宽度是一个像素单位。画笔的颜色确定了所画的线条颜色。画笔的风格确定了所绘图形的线型,它通常有实线、虚线、点线、点划线、双点划线、不可见线和内框线等七种风格。这些风格在Windows中都是以PS_为前缀的预定义的标识,如表7.4所示。 表7.4 修饰画笔的风格 风 格 含 义 图 例 PS_SOLID 实线 PS_DASH 虚线 PS_DOT 点线 PS_DASHDOT 点划线 PS_DASHDOTDOT 双点划线 PS_NULL 不可见线 PS_INSIDEFRAME 内框线
21
7.2图形设备接口 创建一个修饰画笔,可以使用CPen类的CreatePen函数,其原型如下:
BOOL CreatePen( int nPenStyle, int nWidth, COLORREF crColor ); 其中,参数nPenStyle、nWidth、crColor分别用来指定画笔的风格、宽度和颜色。此外,还有一个CreatePenIndirect函数也是用来创建画笔对象,它的作用与CreatePen函数是完全一样的,只是画笔的三个属性不是直接出现在函数参数中,而是通过一个LOGPEN结构间接地给出。 BOOL CreatePenIndirect( LPLOGPEN lpLogPen ); 此函数用由LOGPEN结构指针指定的相关参数创建画笔,LOGPEN结构如下: typedef struct tagLOGPEN { /* lgpn */ UINT lopnStyle; // 画笔风格,同上 POINT lopnWidth; // POINT结构的y不起作用,而用x表示画笔宽度 COLORREF lopnColor; // 画笔颜色 } LOGPEN; 值得注意的是: ● 当修饰画笔的宽度大于1个像素时,画笔的风格只能取PS_NULL、PS_SOLID或 PS_INSIDEFRAME,定义为其他风格不会起作用。 ● 画笔的创建工作也可在画笔的构造函数中进行,它具有下列原型: CPen( int nPenStyle, int nWidth, COLORREF crColor );
22
7.2图形设备接口 7.2.3 画刷 画刷用于指定填充的特性,许多窗口、控件以及其他区域都需要用画刷进行填充绘制,它比画笔的内容更加丰富。
画刷 画刷用于指定填充的特性,许多窗口、控件以及其他区域都需要用画刷进行填充绘制,它比画笔的内容更加丰富。 画刷的属性通常包括填充色、填充图案和填充样式三种。画刷的填充色和画笔颜色一样,都是使用COLORREF颜色类型,画刷的填充图案通常是用户定义的8 x 8位图,而填充样式往往是CDC内部定义的一些特性,它们都是以HS_为前缀的标识,如图7.3所示: HS_BDIAGONAL HS_CROSS HS_DIAGCROSS HS_FDIAGONAL HS_HORIZONTAL HS_VERTICAL 图7.3 画刷的填充样式
23
7.2图形设备接口 CBrush类根据画刷属性提供了相应的创建函数,例如创建填充色画刷和填充样式画刷的函数为CreateSolidBrush和CreateHatchBrush,它们的原型如下: BOOL CreateSolidBrush( COLORREF crColor ); // 创建填充色画刷 BOOL CreateHatchBrush( int nIndex, COLORREF crColor );// 创建填充样式画刷 其中,nIndex用来指定画刷的内部填充样式,而crColor表示画刷的填充色。 与画笔相类似,也有一个LOGBRUSH 逻辑结构用于画刷属性的定义,并通过CBrush的成员函数CreateBrushIndirect来创建,其原型如下: BOOL CreateBrushIndirect( const LOGBRUSH* lpLogBrush ); 其中,LOGBRUSH 逻辑结构如下定义: typedef struct tagLOGBRUSH { // lb UINT lbStyle; // 风格 COLORREF lbColor; // 填充色 LONG lbHatch; // 填充样式 } LOGBRUSH; 另外,还需注意: ● 画刷的创建工作也可在其构造函数中进行,它具有下列原型: CBrush( COLORREF crColor ); CBrush( int nIndex, COLORREF crColor ); CBrush( CBitmap* pBitmap );
24
7.2图形设备接口 ● 画刷也可用位图来指定其填充图案,但该位图应该是8 x 8像素,若位图太大,Windows则只使用其左上角的8 x 8的像素。 ● 画刷仅对绘图函数Chord、Ellipse、FillRect、FrameRect、InvertRect、Pie、 Polygon、PolyPolygon、Rectangle、RoundRect有效。 位图 Windows的位图有两种类型:一种称之为GDI位图,另一种是DIB位图。 GDI位图是由MFC中的CBitmap类来表示的,在CBitmap类的对象中,包含了一种和Windows的GDI模块有关的Windows数据结构,该数据结构是与设备有关的,故此位图又称为DDB位图(device-dependent bitmap,设备相关位图)。当用户的程序取得位图数据信息时,其位图显示方式视显卡而定。由于GDI位图的这种设备依赖性,当位图通过网络传送到另一台PC时,可能就会出现问题。 DIB(device-independent bitmap,设备无关位图)是设备无关位图,它比GDI位图有很多编程优势,例如它自带颜色信息,从而使调色板管理更加容易。且任何运行Windows的机器都可以处理DIB,并通常以后缀为BMP的文件形式被保存在磁盘中或作为资源存在于程序的EXE或DLL文件中。
25
7.2图形设备接口 1. CBitmap类 CBitmap类封装了Windows的GDI位图操作所需的大部分函数。其中,LoadBitmap是位图的初始化函数,其函数原型如下: BOOL LoadBitmap( LPCTSTR lpszResourceName ); BOOL LoadBitmap( UINT nIDResource ); 该函数从应用程序中调入一个位图资源(由nIDResource或lpszResourceName指定)。若用户直接创建一个位图对象,可使用CBitmap类中的CreateBitmap、 CreateBitmapIndirect以及CreateCompatibleBitmap函数,其原型如下。 BOOL CreateBitmap( int nWidth, int nHeight, UINT nPlanes, UINT nBitcount, const void* lpBits ); 该函数用指定的宽度(nWidth)、高度(nHeight)和位模式创建一个位图对象。其中,参数nPlanes表示位图的颜色位面的数目,nBitcount表示每个像素的颜色位个数,lpBits表示包含位值的短整型数组;若此数组为NULL,则位图对象还未初始化。 BOOL CreateBitmapIndirect( LPBITMAP lpBitmap ); 该函数直接用BITMAP结构来创建一个位图对象。 BOOL CreateCompatibleBitmap( CDC* pDC, int nWidth, int nHeight ); 该函数为某设备环境创建一个指定的宽度(nWidth)和高度(nHeight)的位图对象。
26
7.2图形设备接口 2. GDI位图的显示 由于位图不能直接显示在实际设备中,因此对于GDI位图的显示则必须遵循下列步骤:
(1)调用CBitmap类的CreateBitmap、CreateCompatibleBitmap以及 CreateBitmapIndirect函数创建一个适当的位图对象。 (2)调用CDC::CreateCompatibleDC函数创建一个内存设备环境,以便位图在内存中保存下来,并与指定设备(窗口设备)环境相兼容; (3)调用CDC::SelectObject函数将位图对象选入内存设备环境中; (4)调用CDC::BitBlt或CDC::StretchBlt函数将位图复制到实际设备环境中。 (5)使用之后,恢复原来的内存设备环境。 例如,下面的示例过程就是调用一个位图并在视图中显示。 [例Ex_BMP] 在视图中显示位图 (1)创建一个默认的单文档应用程序Ex_BMP。 (2)按快捷键Ctrl+R,弹出“插入资源”对话框,选择Bitmap资源类型。 (3)单击[导入]按钮,出现“导入资源”对话框,将文件类型选择为“所有文件(*.*)”,从外部文件中选定一个位图文件,然后单击[引入]按钮,该位图就被调入应用程序中。保留默认的位图资源标识IDB_BITMAP1。
27
7.2图形设备接口 (4)在CEx_BMPView::OnDraw函数中添加下列代码:
void CEx_BMPView::OnDraw(CDC* pDC) { CEx_BMPDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CBitmap m_bmp; m_bmp.LoadBitmap(IDB_BITMAP1); // 调入位图资源 BITMAP bm; // 定义一个BITMAP结构变量 m_bmp.GetObject(sizeof(BITMAP),&bm); CDC dcMem; // 定义并创建一个内存设备环境 dcMem.CreateCompatibleDC(pDC); CBitmap *pOldbmp = dcMem.SelectObject(&m_bmp); // 将位图选入内存设备环境中 pDC->BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcMem,0,0,SRCCOPY); // 将位图复制到实际的设备环境中 dcMem.SelectObject(pOldbmp); // 恢复原来的内存设备环境 }
28
7.2图形设备接口 (5)编译运行,结果如图7.4所示。 图7.4 Ex_BMP运行结果
29
7.2图形设备接口 通过上述代码过程可以看出:位图的最终显示是通过调用CDC::BitBlt函数来完成的。除此之外,也可以使用CDC::StretchBlt函数。这两个函数的区别在于:StretchBlt函数可以对位图进行缩小或放大,而BitBlt则不能,但BitBlt的显示更新速度较快。它们的原型如下: BOOL BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop ); BOOL StretchBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop ); 其中,参数x、y表示位图目标矩形左上角的x、y逻辑坐标值,nWidth、nHeight表示位图目标矩形的逻辑宽度和高度,pSrcDC表示源设备CDC指针,xSrc、ySrc表示位图源矩形的左上角的x、y逻辑坐标值,dwRop表示显示位图的光栅操作方式。光栅操作方式有很多种,但经常使用的是SRCCOPY,用来直接将位图复制到目标环境中。StretchBlt函数还比BitBlt参数多两个:nSrcWidth、nSrcHeight,它们是用来表示源矩形的逻辑宽度和高度。 事实上,位图显示的最令人注意的地方是先将位图装入内存设备中,然后再显示。根据这一现象,可以将要显示的图形先在内存设备中绘制,然后再显示,可以起到快速显示的作用。若有多个内存设备进行交替使用,则还可实现图形的动态显示,后面会讨论。 事实上,位图显示的最令人注意的地方是先将位图装入内存设备中,然后再显示。根据这一现象,可以将要显示的图形先在内存设备中绘制,然后再显示,可以起到快速显示的作用。若有多个内存设备进行交替使用,则还可实现图形的动态显示,后面会讨论。
30
7.3图形绘制 1. 点 画点是最基本的绘图操作之一,它是通过调用CDC::SetPixel或CDC::SetPixelV函数来实现的。这两个函数都是用来在指定的坐标上设置指定的颜色,只不过SetPixelV函数不需要返回实际像素点的RGB值;正是因为这一点,函数SetPixelV要比SetPixel快得多。 COLORREF SetPixel( int x, int y, COLORREF crColor ); COLORREF SetPixel( POINT point, COLORREF crColor ); BOOL SetPixelV(int x, int y, COLORREF crColor); BOOL SetPixelV( POINT point, COLORREF crColor ); 实际显示像素的颜色未必等同于crColor所指定的颜色值,因为有时受设备限制,不能显示crColor所指定的颜色值,而只能取其近似值。 与上述函数相对应的GetPixel函数是用来获取指定点的颜色。 COLORREF GetPixel( int x, int y ) const; COLORREF GetPixel( POINT point ) const;
31
7.3图形绘制 2. 画线 画线也是特别常用的绘图操作之一。CDC的LineTo和MoveTo函数就是用来实现画线功能的两个函数,通过这两个函数的配合使用,可完成任何直线和折线的绘制操作。 这里,首先值得一提的是在画直线时,总存在一个称为“当前位置”的特殊位置。每次画直线都是以此当前位置为起始点,画线操作结束之后,直线的结束点位置又成为了当前位置。有了当前位置的自动更新,就可避免了每次画线时都要给出两点的坐标。当然,这个当前位置还可用函数CDC::GetCurrentPosition来获得,其原型如下: CPoint GetCurrentPosition( ) const; LineTo函数正是经当前位置所在点为直线起始点,另指定直线终点,画出一段直线的。其原型如下: BOOL LineTo( int x, int y ); BOOL LineTo( POINT point ); 如果当前要画的直线并不与上一条直线的终点相接,那么应该调用MoveTo函数来调整当前位置。此函数不但可以用来更新当前位置,而且还可用来返回更新前的当前位置。其函数原型如下: CPoint MoveTo( int x, int y ); CPoint MoveTo( POINT point );
32
7.3图形绘制 3. 折线 除了LineTo函数可用来画线之外,CDC中还提供了一系列用于画各种折线的函数。它们主要是Polyline、PolyPolyline和PolylineTo。这三个函数中,Polyline和PolyPolyline既不使用当前位置,也不更新当前位置;而PolylineTo总是把当前位置作为起始点,并且在折线画完之后,还把折线终点所在位置设为新的当前位置。 BOOL Polyline( LPPOINT lpPoints, int nCount ); BOOL PolylineTo( const POINT* lpPoints, int nCount ); 这两个函数用来画一系列连续的折线。参数lpPoints是POINT或CPoint的顶点数组;nCount表示数组中顶点的个数,它至少为2。 BOOL PolyPolyline( const POINT* lpPoints, const DWORD* lpPolyPoints, int nCount ); 此函数可用来绘制多条折线。其中lpPoints同前定义,lpPolyPoints表示各条折线所需的顶点数,nCount表示折线的数目。 矩形和多边形 虽然利用前面的直线、折线也可画出矩形和多边形来,但CDC中提供的相关函数使其更胜一筹。
33
7.3图形绘制 1. 矩形和圆角矩形 CDC提供的Rectangle和RoundRect函数分别用于矩形和圆角矩形的绘制,它们的原型如下:
1. 矩形和圆角矩形 CDC提供的Rectangle和RoundRect函数分别用于矩形和圆角矩形的绘制,它们的原型如下: BOOL Rectangle( int x1, int y1, int x2, int y2 ); BOOL Rectangle( LPCRECT lpRect ); BOOL RoundRect( int x1, int y1, int x2, int y2, int x3, int y3 ); BOOL RoundRect( LPCRECT lpRect, POINT point ); 参数lpRect的成员left,top,right,bottom分别表示x1,y1,x2,y2,point的成员x,y分别表示x3,y3;而x1,y1表示矩形的左上角坐标,x2,y2表示矩形的右上角坐标,x3,y3表示绘制圆角的椭圆大小,如图7.5所示。 2. 设置多边形填充模式 多边形填充模式决定了图形填充时寻找填充区域的方法,有两种选择:ALTERNATE和WINDING。ALTERNATE模式是寻找相邻的奇偶边作为填充区域,而WINDING是按顺时针或逆时针进行寻找;一般情况,这两种模式的填充效果是相同的,但对于像五角星这样的图形,填充的结果大不一样,例如下面的代码,其结果如图7.6所示。
34
7.3图形绘制 图7.5 圆角矩形 图7.6 多边形填充模式 x3/2 中心 外接矩形 圆角矩形 (x1,y1) (x2,y2) y3/2
ALTERNATE WINDING
35
7.3图形绘制 ... POINT pt[5]={{247,10},{230,90},{290,35},{210,30},{275,85}}; CBrush brush(HS_FDIAGONAL,RGB(255,0,0)); CBrush* oldbrush=pDC->SelectObject(&brush); pDC->SetPolyFillMode(ALTERNATE); pDC->Polygon(pt,5); for(int i=0;i<5;i++) pt[i].x+=80; pDC->SetPolyFillMode(WINDING); pDC->SelectObject(oldbrush); brush.DeleteObject(); 代码中,SetPolyFillMode是CDC类的一个成员函数,用来设置填充模式,它的参数可以是ALTERNATE和WINDING。 3. 多边形 前面已经介绍过折线的画法,而多边形可以说就是由首尾相接的封闭折线所围成的图形。画多边形的函数Polygon原型如下: BOOL Polygon( LPPOINT lpPoints, int nCount ); 可以看出,Polygon函数的参数形式与Polyline函数是相同的。但也稍有一点小差异。例如,要画一个三角形,使用Polyline函数,顶点数组中就得给出四个顶点(尽管始点和终点重复出现),而用Polygon函数则只需给出三个顶点。 与PolyPolyline可画多条折线一样,使用PolyPolygon函数,一次可画出多个多边形,这两个函数的参数形式和含义也一样。 BOOL PolyPolygon( LPPOINT lpPoints, LPINT lpPolyCounts, int nCount );
36
7.3图形绘制 7.3.3 曲线 同点、直线一样,圆弧也是图形的基本元素。CDC提供的圆弧及曲线成员函数可以方便地生成各种非规则形状的图形。
曲线 同点、直线一样,圆弧也是图形的基本元素。CDC提供的圆弧及曲线成员函数可以方便地生成各种非规则形状的图形。 1. 圆弧和椭圆 通过调用CDC的Arc函数可以画一条椭圆弧线或者整个椭圆。这个椭圆的大小是由其外接矩形(本身并不可见)所决定的。Arc函数的原型如下: BOOL Arc( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ); BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 这里,x1,y1,x2,y2或lpRect用来指定外接矩形的位置和大小,而椭圆中心与点(x3,y3)或ptStart所构成的射线与椭圆的交点就成为椭圆弧线的起始点,椭圆中心与点(x4,y4)或ptEnd所构成的射线与椭圆的交点就成为椭圆弧线的终点。椭圆上弧线始点到终点的部分是要绘制的椭圆弧(如图7.7所示)。 图7.7 弧线 中心 外接矩形 弧线 (x1,y1) (x2,y2) 起点坐标 终点坐标
37
7.3图形绘制 需要说明,要唯一地确定一条椭圆弧线,除了上述参数外,还有一个重要参数,那就是弧线绘制的方向。默认时,这个方向为逆时针,但可以通过调用SetArcDirection函数将绘制方向改设为顺时针方向。 int SetArcDirection( int nArcDirection ); 该函数成功调用时返回以前的绘制方向,nArcDirection可以是AD_CLOCKWISE(顺时针) 或AD_COUNTERCLOCKWISE(逆时针)。此方向对函数Arc、Pie 、ArcTo、Rectangle、Chord、RoundRect、Ellipse有效。 另外,ArcTo也是一个画圆弧的CDC成员函数,它与Arc函数的唯一的区别是:ArcTo函数将圆弧的终点作为新的当前位置,而Arc不会。 BOOL ArcTo( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ); BOOL ArcTo( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 与上述函数相类似, 调用CDC成员函数Ellipse可以用当前画刷绘制一个椭圆区域。 BOOL Ellipse( int x1, int y1, int x2, int y2 ); BOOL Ellipse( LPCRECT lpRect ); 参数x1,y1,x2,y2或lpRect表示椭圆外接矩形的大小的位置。
38
7.3图形绘制 2. 弦形和扇形 CDC函数Chord和Pie是用来绘制弦形(图7.8)和扇形(图7.9),它们具有和Arc一样的参数。
2. 弦形和扇形 CDC函数Chord和Pie是用来绘制弦形(图7.8)和扇形(图7.9),它们具有和Arc一样的参数。 BOOL Chord( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ); BOOL Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); BOOL Pie( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ); BOOL Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 3. Bézier曲线 Bézier曲线是最常见的非规则曲线之一,它的形状不仅便于控制,而且更主要的是它具有几何不变性(即它的形状不随坐标的变换而改变),因此在许多场合往往采用这种曲线。Bézier曲线属于三次曲线,只需给定四个点(第一和第四个点是端点,另两个是控制点),就可唯一确定其形状,如图7.10所示。
39
7.3图形绘制 图7.8 弦形 图7.9 扇形 图7.10 Bézier曲线 中心 外接矩形 弦形 (x1,y1) (x2,y2) 起点坐标
终点坐标 图7.9 扇形 扇形 P1 P2 P3 P4 图7.10 Bézier曲线
40
7.3图形绘制 函数PolyBezier是用来画出一条或多条Bézier曲线的,其函数原型如下:
BOOL PolyBezier( const POINT* lpPoints, int nCount ); 其中lpPoints是曲线端点和控制点所组成的数组,nCount表示lpPoints数组中的点数。如果lpPoints用于画多条Bézier曲线,那么除了第一条曲线要用到四个点之外,后面的曲线只需用三个点,因为后面的曲线总是把前一条曲线的终点作为自己的起始端点。 函数PolyBezier不使用也不更新当前位置。如果需要使用当前位置,那么就应该使用PolyBezierTo函数。 BOOL PolyBezierTo( const POINT* lpPoints, int nCount ); 图形绘制示例 商务中常常要求用图形显示一些销售数字、金融信息、股票价格波动和其它各种形式的数据量。这种显示要求的理由很简单:首先,一个图形显示较一列数字更容易让人明白;其次,图形较数字本身更容易进行对趋势、不规则性和波动的比较;第三,图形显示比字符或数字显示更有感染力。 [例Ex_GDI] 绘制线图 (1)创建一个默认的单文档应用程序Ex_GDI。 (2)在CEx_GDIView::OnDraw函数中添加下列代码:
41
7.3图形绘制 (3)编译运行,结果如图7.11所示。 图7.11 Ex_GDI运行结果
42
7.4字体与文字处理 7.4.1 字体和字体对话框 1. 字体的属性和创建
字体和字体对话框 1. 字体的属性和创建 字体的属性有很多,但其主要属性有字样、风格和尺寸三个。字样是字符书写和显示时表现出的特定模式,例如,对于汉字,通常有宋体、楷体、仿宋、黑体、隶书以及幼圆等多种字样。字体风格主要表现为字体的粗细和是否倾斜等特点。字体尺寸是用来指定字符所占区域的大小,通常用字符高度来描述。字体尺寸可以取毫米或英寸作为单位,但为了直观起见,也常常采用一种称为“点”的单位,一点约折合为1/72英寸。 为了方便用户创建字体,系统定义一种“逻辑字体”,它是应用程序对于理想字体的一种描述方式。在使用逻辑字体绘制文字时,系统会采用一种特定的算法把逻辑字体映射为最匹配的物理字体(实际安装在操作系统中的字体)。逻辑字体的具体属性可由LOGFONT结构来描述,这里仅列最常用到的结构成员。
43
7.4字体与文字处理 typedef struct tagLOGFONT { LONG lfHeight; // 字体的逻辑高度
LONG lfWidth; // 字符的平均逻辑宽度 LONG lfEscapement; // 倾角 LONG lfOrientation; // 书写方向 LONG lfWeight; // 字体的粗细程度 BYTE lfItalic; // 斜体标志 BYTE lfUnderline; // 下划线标志 BYTE lfStrikeOut; // 删除线标志 BYTE lfCharSet; // 字符集,汉字必须为GB2312_CHARSET TCHAR lfFaceName[LF_FACESIZE]; // 字样名称 // … } LOGFONT;
44
7.4字体与文字处理 lfEscapement表示字体的倾斜矢量与设备的x轴之间的夹角(以1/10度为计量单位),该倾斜矢量与文本的书写方向是平行的。lfOrientation表示字符基准线与设备的x轴之间的夹角(以1/10度为计量单位)。lfWeight表示字体的粗细程度,取值范围是从0到1000(字符笔划从细到粗)。例如,400为常规情况,700为粗体。 根据定义的逻辑字体,用户就可以调用CFont类的CreateFontIndirect函数创建文本输出所需要的字体,如下面的代码: LOGFONT lf; // 定义逻辑字体的结构变量 memset(&lf, 0, sizeof(LOGFONT)); // 将lf中的所有成员置0 lf.lfHeight = -13; lf.lfCharSet = GB2312_CHARSET; strcpy((LPSTR)&(lf.lfFaceName), "黑体"); // 用逻辑字体结构创建字体 CFont cf; cf.CreateFontIndirect(&lf); // 在设备环境中使用字体 CFont* oldfont = pDC->SelectObject(&cf); pDC->TextOut(100,100,"Hello"); pDC->SelectObject(oldfont); // 恢复设备环境原来的属性 cf.DeleteObject(); // 删除字体对象
45
7.4字体与文字处理 2. 使用字体对话框 CFontDialog类提供了字体及其文本颜色选择的通用对话框,如图7.12所示。它的构造函数如下: CFontDialog( LPLOGFONT lplfInitial = NULL, DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS, CDC* pdcPrinter = NULL, CWnd* pParentWnd = NULL ); 其中,参数lplfInitial是一个LOGFONT结构指针,用来设置对话框最初的字体特性。dwFlags指定选择字体的标志。pdcPrinter用来表示打印设备环境指针。pParentWnd表示对话框的父窗口指针。 图7.12 字体对话框
46
7.4字体与文字处理 当字体对话框DoModal返回IDOK后,可使用下列的成员函数: 通过字体对话框可以创建一个字体,如下面的代码:
void GetCurrentFont( LPLOGFONT lplf );// 返回用户选择的LOGFONT字体 CString GetFaceName( ) const; // 返回用户选择的字体名称 CString GetStyleName( ) const; // 返回用户选择的字体样式名称 int GetSize( ) const; // 返回用户选择的字体大小 COLORREF GetColor( ) const; // 返回用户选择的文本颜色 int GetWeight( ) const; // 返回用户选择的字体粗细程度 BOOL IsStrikeOut( ) const; // 判断是否有删除线 BOOL IsUnderline( ) const; // 判断是否有下划线 BOOL IsBold( ) const; // 判断是否是粗体 BOOL IsItalic( ) const; // 判断是否是斜体。 通过字体对话框可以创建一个字体,如下面的代码: LOGFONT lf; CFont cf; memset(&lf, 0, sizeof(LOGFONT)); // 将lf中的所有成员置0 CFontDialog dlg(&lf); if (dlg.DoModal()==IDOK) { dlg.GetCurrentFont(&lf); pDC->SetTextColor(dlg.GetColor()); cf.CreateFontIndirect(&lf); ... }
47
7.4字体与文字处理 常用文本输出函数 文本的最终输出不仅依赖于文本的字体,而且还跟文本的颜色、对齐方式等有很大关系。CDC类提供了四个输出文本的成员函数:TextOut、ExtTextOut、TabbedTextOut和DrawText。 对于这四个函数,用户应根据具体情况来选用。例如,如果想要绘制的文本是一个多列的列表形式,那么采用TabbedTextOut函数,启用制表位,可以使绘制出来的文本效果更佳;如果要在一个矩形区域内绘制多行文本,那么采用DrawText函数,会更富于效率;如果文本和图形结合紧密,字符间隔不等,并要求有背景颜色或矩形裁剪特性,那么ExtTextOut函数将是最好的选择。如果没有什么特殊要求,那使用 TextOut函数就显得简练了。下面介绍TextOut、TabbedTextOut和DrawText函数。 virtual BOOL TextOut( int x, int y, LPCTSTR lpszString, int nCount ); BOOL TextOut( int x, int y, const CString& str ); TextOut函数是用当前字体在指定位置 (x,y) 处显示一个文本。参数中lpszString和str指定即将显示的文本, nCount表示文本的字节长度。若输出成功,函数返回TRUE,否则返回FALSE。 virtual CSize TabbedTextOut( int x, int y, LPCTSTR lpszString, int nCount, int nTabPositions, LPINT lpnTabStopPositions, int nTabOrigin ); CSize TabbedTextOut( int x, int y, const CString& str,
48
7.4字体与文字处理 TabbedTextOut也是用当前字体在指定位置处显示一个文本,但它还根据指定的制表位(Tab)设置相应字符位置,函数成功时返回输出文本的大小。参数中,nTabPositions表示lpnTabStopPositions数组的大小,lpnTabStopPositions表示多个递 增的制表位(逻辑坐标)的数组,nTabOrigin表示制表位x方向的起始点(逻辑坐标)。如果nTabPositions为0,且lpnTabStopPositions为NULL,则使用默认的制表位,即一个Tab相当于8个字符。 virtual int DrawText( LPCTSTR lpszString, int nCount, LPRECT lpRect, UINT nFormat ); int DrawText( const CString& str, LPRECT lpRect, UINT nFormat ); DrawText函数是当前字体在指定矩形中对文本进行格式化绘制。参数中,lpRect 用来指定文本绘制时的参考矩形,它本身并不显示;nFormat表示文本的格式,它可以是下列的常用值之一或“|”组合:
49
7.4字体与文字处理 DT_BOTTOM 下对齐文本,该值还必须与DT_SINGLELINE组合 DT_CENTER 水平居中
DT_END_ELLIPSIS 使用省略号取代文本末尾的字符 DT_PATH_ELLIPSIS 使用省略号取代文本中间的字符 DT_EXPANDTABS 使用制表位,缺省的制表长度为8个字符 DT_LEFT 左对齐 DT_MODIFYSTRING 将文本调整为能显示的字串 DT_NOCLIP 不裁剪 DT_NOPREFIX 不支持“&”字符转义 DT_RIGHT 右对齐 DT_SINGLELINE 指定文本的基准线为参考点,单行文本 DT_TABSTOP 设置停止位。nFormat的高位字节是每个制表位的数目 DT_TOP 上对齐 DT_VCENTER 垂直居中 DT_WORDBREAK 自动
50
7.4字体与文字处理 注意,DT_TABSTOP与上述DT_CALCRECT、DT_EXTERNALLEADING、DT_NOCLIP及 DT_NOPREFIX不能组合。 需要说明的是,默认时,上述文本输出函数既不使用也不更新“当前位置”。若要使用和更新“当前位置”,则必须调用SetTextAlign,并将参数nFlags设置为TA_UPDATECP。使用时,最好在文本输出前用MoveTo将当前位置移动至指定位置后,再调用文本输出函数;这样,文本输出函数参数中x,y或矩形的左边才会被忽略。 [例Ex_DrawText] 绘制文本的简单示例 (1)创建一个默认的单文档应用程序Ex_DrawText。 (2)在CEx_DrawTextView::OnDraw中添加下列代码:
51
7.4字体与文字处理 void CEx_DrawTextView::OnDraw(CDC* pDC) {
CEx_DrawTextDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CRect rc(10, 10, 200, 140); pDC->Rectangle( rc ); pDC->DrawText( "单行文本居中", rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE); rc.OffsetRect( 200, 0 ); // 将矩形向右偏移200 int nTab = 40; // 将一个Tab位的值指定为40个逻辑单位 pDC->TabbedTextOut( rc.left, rc.top, "绘制\tTab\t文本\t示例", 1, &nTab, rc.left); // 使用自定义的停止位(Tab) nTab = 80; // 将一个Tab位的值指定为80个逻辑单位 pDC->TabbedTextOut( rc.left, rc.top+20, "绘制\tTab\t文本\t示例", pDC->TabbedTextOut( rc.left, rc.top+40, "绘制\tTab\t文本\t示例", 0, NULL, 0); // 使用默认的停止位 }
52
7.4字体与文字处理 (3)编译运行,结果如图7.13所示。 图7.13 Ex_DrawText运行结果
53
7.4字体与文字处理 文本格式化属性 文本的格式属性通常包括文本颜色、对齐方式、字符间隔以及文本调整等。在绘图设备环境中,默认的文本颜色是黑色,而文本背景色为白色,且默认的背景模式是不透明方式(OPAQUE)。在CDC类中,SetTextColor、SetBkColor和SetBkMode函数就是分别用来设置文本颜色、文本背景色和背景模式,而与之相对应的GetTextColor、GetBkcolor和GetBkMode函数则是分别获取这三项属性的。它们的原型如下: virtual COLORREF SetTextColor( COLORREF crColor ); COLORREF GetTextColor( ) const; virtual COLORREF SetBkColor( COLORREF crColor ); COLORREF GetBkColor( ) const; int SetBkMode( int nBkMode ); int GetBkMode( ) const; 其中,nBkMode用来指定文本背景模式,它可以是OPAQUE或TRANSPARENT (透明)。 文本对齐方式的设置和获取是由CDC函数SetTextAlign和GetTextAlign决定的。它们的原型如下: UINT SetTextAlign( UINT nFlags ); UINT GetTextAlign( ) const;
54
7.4字体与文字处理 上述两个函数中所用到的文本对齐标志如表7.5所示。这些标志可以分为三组:TA_LEFT、TA_CENTER和TA_RIGHT确定水平方向的对齐方式,TA_BASELINE、TA_BOTTOM和TA_TOP确定上下方向的对齐方式,TA_NOUPDATECP和TA_UPDATECP确定当前位置的更新标志。这三组标志中,组与组之间的标志可使用“|”操作符。 表7.5 文本对齐标志 对齐标志 含 义 TA_BASELINE 以字体的基准线作为上下对齐方式 TA_BOTTOM 以文本外框矩形的底边作为上下对齐方式 TA_CENTER 以文本外框矩形的中点作为左右对齐方式 TA_LEFT 以文本外框矩形的左边作为左右对齐方式 TA_NOUPDATECP 不更新当前位置 TA_RIGHT 以文本外框矩形的右边作为左右对齐方式 TA_TOP 以文本外框矩形的顶边作为上下对齐方式 TA_UPDATECP 更新当前位置
55
7.4字体与文字处理 计算字符的几何尺寸 在打印和显示某段文本时,有必要了解字符的高度计算及字符的测量方式,才能更好地控制文本输出效果。在CDC类中,GetTextMetrics(LPTEXTMETRIC lpMetrics)是用来获得指定映射模式下相关设备环境的字符几何尺寸及其它属性的,其TEXTMETRIC结构描述如下(这里仅列出最常用的结构成员): typedef struct tagTEXTMETRIC { // tm int tmHeight; // 字符的高度 (ascent + descent) int tmAscent; // 高于基准线部分的值 int tmDescent; // 低于基准线部分的值 int tmInternalLeading; // 字符内标高 int tmExternalLeading; // 字符外标高 int tmAveCharWidth; // 字体中字符平均宽度 int tmMaxCharWidth; // 字符的最大宽度 // … } TEXTMETRIC;
56
7.4字体与文字处理 通常,字符的总高度是用tmHeight和tmExternalLeading的总和来表示的。但对于字符宽度的测量除了上述参数tmAveCharWidth和tmMaxCharWidth外,还有CDC中的相关成员函数GetCharWidth、GetOutputCharWidth、GetCharABCWidths。 在CDC类中,计算字符串的宽度和高度的函数主要两个:GetTextExtent函数和GetTabbedTextExtent函数。前者适用于字符串没有制表符的情况,而后者适用于含有制表符的字符串。它们的原型如下: CSize GetTextExtent( LPCTSTR lpszString, int nCount ) const; CSize GetTextExtent( const CString& str ) const; CSize GetTabbedTextExtent( LPCTSTR lpszString, int nCount, int nTabPositions, LPINT lpnTabStopPositions ) const; CSize GetTabbedTextExtent( const CString& str, 其中,参数lpszString和str表示要计算的字符串,nCount表示字符串的字节长度, nTabPositions表示lpnTabStopPositions数组的大小,lpnTabStopPositions表示多个递增的制表位(逻辑坐标)的数组。函数返回当前设备环境下的一行字符串的宽度(CSize的cx)和高度(CSize的cy)。
57
7.4字体与文字处理 文档内容显示及其字体改变 这里用示例的形式来说明如何在视图类中通过文本绘图的方法来显示文档的文本内容以及改变显示的字体。 [例Ex_Text] 显示文档内容并改变显示的字体 (1)创建一个单文档应用程序Ex_Text,在创建的第6步将视图的基类选择为 CScrollView。由于视图客户区往往显示不了文档的全部内容,因此需要视图支持滚动操作。 (2)为CEx_TextDoc类添加CStringArray类型的成员变量m_strContents,用来将读取的文档内容保存。 (3)在CEx_TextDoc::Serialize函数中添加读取文档内容的代码: void CEx_TextDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) {…} else CString str; m_strContents.RemoveAll(); while (ar.ReadString(str)) m_strContents.Add(str); }
58
7.4字体与文字处理 (4)为CEx_TextView类添加LOGFONT类型的成员变量m_lfText,用来保存当前所使用的逻辑字体。
(5)在CEx_TextView类构造函数中添加m_lfText的初始化代码: CEx_TextView::CEx_TextView() { memset(&m_lfText, 0, sizeof(LOGFONT)); m_lfText.lfHeight = -12; m_lfText.lfCharSet = GB2312_CHARSET; strcpy(m_lfText.lfFaceName, "宋体"); } (6)用MFC ClassWizard为CEx_TextView类添加WM_LBUTTONDBLCLK(双击鼠标)的消息映射函数,并增加下列代码: void CEx_TextView::OnLButtonDblClk(UINT nFlags, CPoint point) CFontDialog dlg(&m_lfText); if (dlg.DoModal() == IDOK) dlg.GetCurrentFont(&m_lfText); Invalidate(); CScrollView::OnLButtonDblClk(nFlags, point);
59
7.4字体与文字处理 这样,当双击鼠标左键后,就会弹出字体对话框,从中可改变字体的属性,单击[确定]按钮后,执行CEx_TextView::OnDraw中的代码。 (7)在CEx_TextView::OnDraw中添加下列代码: (8)编译运行并测试,打开任意一个文本文件,结果如图7.14所示。 图7.14 Ex_Text运行结果
60
7.5在对话框及控件中绘图 下面来看一个示例,该例是在对话框中和静态文本控件分别绘制一个交叉线填充和十字填充的矩形区域,如图7.15所示。
图7.15 在对话框中绘图 [例Ex_DlgDraw] 在对话框及控件中绘图 (1)创建一个基于对话框应用程序项目Ex_DlgDraw。 (2)将对话框标题设为“在对话框及控件中绘图”,删除[取消]按钮和“TODO:…”静态文本控件,将[确定]按钮标题改为“退出”调整对话框大小,打开对话框网格,参看图7.15,调整对话框大小。
61
7.5在对话框及控件中绘图 (3)在对话框右侧靠上部位添加一个静态文本控件IDC_DRAW,在其“样式”属性中,选中“凹陷”选项。
(4)为CEx_DlgDrawDlg添加下列成员函数DoDrawCtrl: void CEx_DlgDrawDlg::DoDrawCtrl() { CWnd *pWnd = GetDlgItem( IDC_DRAW ); CDC* pDC = pWnd->GetDC(); CRect rcClient; pWnd->GetClientRect( rcClient ); // 获取控件客户区大小 UpdateWindow(); // 告诉对话框,控件已更新过 CBrush brush(HS_CROSS,RGB(0,0,255)); CBrush *oldBrush = pDC->SelectObject( &brush ); pDC->Rectangle( rcClient ); pDC->SelectObject( oldBrush ); }
62
7.5在对话框及控件中绘图 (5)在CEx_DlgDrawDlg::OnPaint函数中添加下列代码: (6)编译运行。
void CEx_DlgDrawDlg::OnPaint() { if (IsIconic()) {… } else CDialog::OnPaint(); UpdateWindow(); // 告诉系统对话框已更新过 CDC* pDC = GetDC(); CRect rcClient; GetClientRect( rcClient ); // 获取对话框客户区大小 CRect rcDraw; rcDraw.SetRect( 10, 10, rcClient.right - 120, rcClient.bottom - 10 ); CBrush brush(HS_FDIAGONAL,RGB(0,255,0)); CBrush *oldBrush = pDC->SelectObject( &brush ); pDC->Rectangle( rcDraw ); pDC->SelectObject( oldBrush ); DoDrawCtrl(); // 调用在控件中绘图的自定义函数 } (6)编译运行。
63
7.6综合应用 下面来举一个应用实例,它是用来实现画线的动态定位操作,如图7.16所示。当鼠标在视图客户区移动时,会出一个大大的光标(由水平线和垂直线组成),光标的右上角的小窗口显示出当前的鼠标位置,当单击鼠标左键时,在屏幕上出现由小十字点标志,此时再移动鼠标,从当前鼠标位置到前面一个点将绘制一条直线,且该直线随鼠标位置移动而变化,就像一根橡皮条一样,且光标右上角的小窗口显示出这条直线的长度和角度,再单击鼠标左键一条直线被绘制出来,…,直到按ESC键结束,一条折线就绘制出来了。为了保证在视图需要更新时,这些线条仍然存在,需要将这些点保存下来,然后再重新绘出。 图7.16 Ex_A7运行结果
64
7.6综合应用 下面按光标生成和实现、动态小窗口的实现和直线动态过程实现共3部分来阐述。 [例Ex_A7] 综合应用 1)光标生成和实现
(2)在Ex_A7View.h文件中的类声明中添加下列成员变量: class CEx_A7View : public CView { public: BOOL m_bCursorFirst; // 光标第一次显示标志 CPoint m_ptPrevPos; // 上一个点的坐标 CPoint m_ptCurPos; // 当前点的坐标 (3)在CEx_A7View::OnDraw中先添加一些清屏代码,并将m_bCursorFirst设为TRUE: void CEx_A7View::OnDraw(CDC* pDC) CEx_A7Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CRect rcClient; GetClientRect( rcClient ); // 获取客户区大小 pDC->FillSolidRect( rcClient, RGB( 0,0,0 ) ); // 将客户区清为黑色 m_bCursorFirst = TRUE; pDC->SetBkMode( TRANSPARENT ); // 设置透明背景模式 }
65
7.6综合应用 (5)为CEx_A7View类添加绘制光标函数的成员函数DrawCursor:
void CEx_A7View::DrawCursor(CDC *pDC, CPoint pt) { CRect rcClip; pDC->GetClipBox( rcClip ); // 当前裁剪区大小 CPen pen( PS_SOLID, 1, RGB( 128, 128, 128 ) ); // 灰色画笔 CPen *oldPen = pDC->SelectObject( &pen ); // 选入画笔 // 设置XOR光栅操作模式 int nOldROP = pDC->SetROP2( R2_XORPEN ); // 绘制水平线 pDC->MoveTo( rcClip.left, pt.y ); pDC->LineTo( rcClip.right, pt.y ); //绘制垂直线 pDC->MoveTo( pt.x, rcClip.top ); pDC->LineTo( pt.x, rcClip.bottom ); // 恢复原来的光栅模式 pDC->SetROP2( nOldROP ); // 恢复原来的画笔 }
66
7.6综合应用 (6)用MFC ClassWizard为CEx_A7View类添加WM_MOUSEMOVE消息映射,并在映射函数中添加下列代码: void CEx_A7View::OnMouseMove(UINT nFlags, CPoint point) { CDC* pDC = this->GetDC(); if ( m_bCursorFirst ) m_bCursorFirst = FALSE; m_ptCurPos = point; DrawCursor( pDC, m_ptCurPos ); } else } CView::OnMouseMove(nFlags, point);
67
7.6综合应用 (6)用MFC ClassWizard为CEx_A7View类添加WM_SETCURSOR的消息映射函数,并增加下列代码,用来关闭在视图客户区的光标: BOOL CEx_A7View::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { SetCursor( NULL ); return TRUE;//CView::OnSetCursor(pWnd, nHitTest, message); } (7)编译运行并测试。看看是否在视图客户区中有一个可以移动的大大的光标。 2)动态小窗口的实现 动态小窗口的实现是通过将内存中相对应的区域填充到原来的小窗口,然后重新绘制小窗口,具体过程如下: 在Ex_A7View.h文件中的类声明中添加下列成员变量: public: BOOL m_bCursorFirst; // 光标第一次显示标志 CPoint m_ptPrevPos; // 上一个点的坐标 CPoint m_ptCurPos; // 当前点的坐标 CDC m_dcMem; // 定义用于内存环境的设备变量 CBitmap *m_oldBmp; // 用于内存的位图指针
68
7.6综合应用 (2)在构造函数CEx_A7View::CEx_A7View中设置m_oldBmp为NULL:
{ m_oldBmp = NULL; } (3)在CEx_A7View::OnDraw中添加并修改成下列代码: (4)在CEx_A7View类添加成员函数DrawInfoWnd,用来绘制小窗口: (5)修改CEx_A7View::OnMouseMove中的代码: (6)编译运行并测试。 3)直线动态过程实现 (1)在Ex_A7View.h文件中的类声明中添加下列成员变量: public: … CBitmap *m_oldBmp; // 用于内存的位图指针 BOOL m_bLineStart; // 用来判断画线是否 CStringArray m_strCmdArray; // 用于保存当前绘图命令
69
7.6综合应用 (2)为CEx_A7View类添加成员函数DrawPointFlag,用来绘制点标志:
void CEx_A7View::DrawPointFlag(CDC *pDC, CPoint pt) { // 选入白色系统画笔 CPen *oldPen = (CPen *)pDC->SelectStockObject( WHITE_PEN ); // 绘制水平线 pDC->MoveTo( pt.x - 4, pt.y ); pDC->LineTo( pt.x + 4, pt.y ); //绘制垂直线 pDC->MoveTo( pt.x, pt.y - 4 ); pDC->LineTo( pt.x, pt.y + 4 ); // 恢复原来的画笔 pDC->SelectObject( oldPen ); } (3)为CEx_A7View类添加成员函数DrawXORLine,用来绘制XOR直线: void CEx_A7View::DrawXORLine(CDC *pDC, CPoint pt1, CPoint pt2) CPen pen( PS_SOLID, 1, RGB( 192, 192, 128 ) ); // 淡黄色画笔 CPen *oldPen = pDC->SelectObject( &pen ); // 选入画笔 int nOldROP = pDC->SetROP2( R2_XORPEN ); pDC->MoveTo( pt1 ); pDC->LineTo( pt2 ); pDC->SetROP2( nOldROP );
70
7.6综合应用 (4)用MFC ClassWizard为CEx_A7View类添加OnInitialUpdate消息(文档创建或打开新文档时,视图会自动调用此函数,一般与文档和视图共同相关的初始化代码添加到此映射函数中)的映射,并添加下列代码: void CEx_A7View::OnInitialUpdate() { CView::OnInitialUpdate(); m_strCmdArray.RemoveAll(); // 清空命令 m_bLineStart = FALSE; } (5)用MFC ClassWizard为CEx_A7View类添加WM_LBUTTONDOWN消息映射,并在映射函数中添加下列代码:
71
7.6综合应用 void CEx_A7View::OnLButtonDown(UINT nFlags, CPoint point) {
m_ptPrevPos = point; if ( !m_bLineStart ) m_bLineStart = TRUE; // 创建并添加命令 CString strCmd; strCmd.Format( "MOVETO,%d,%d", point.x, point.y ); m_strCmdArray.Add( strCmd ); } else strCmd.Format( "LINETO,%d,%d", point.x, point.y ); } DrawCursor( GetDC(), point ); DrawPointFlag( GetDC(), point ); DrawPointFlag( &m_dcMem, point ); CView::OnLButtonDown(nFlags, point);
72
7.6综合应用 (7)用MFC ClassWizard为CEx_A7View类添加WM_KEYDOWN消息映射,并在映射函数中添加下列代码:
void CEx_A7View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { if ( nChar == VK_ESCAPE ) if ( m_bLineStart ) m_bLineStart = FALSE; Invalidate(); // 强制更新视图 } CView::OnKeyDown(nChar, nRepCnt, nFlags); (8)修改CEx_A7View::OnMouseMove中的代码:
73
7.6综合应用 (8)在Ex_A7View.cpp文件前面添加math.h头文件包含:
#include "Ex_A7Doc.h" #include "Ex_A7View.h" #include "math.h" (9)在CEx_A7View::OnDraw中的“这里还要添加代码”中添加下列代码: (10) 编译运行并测试。
74
1. 什么是设备环境(DC)?MFC提供的设备环境类有哪些?有何不同? 2. 为什么需要坐标映射模式?坐标映射模式有哪些?它们有什么不同?
习题 1. 什么是设备环境(DC)?MFC提供的设备环境类有哪些?有何不同? 2. 为什么需要坐标映射模式?坐标映射模式有哪些?它们有什么不同? 3. 什么是GDI?MFC提供哪些GDI类?如何使用它们? 4. 什么是字体?如何构造或定义字体? 5. CDC中文本绘制的函数有哪些?它们有何不同? 6. 文本的格式化属性有哪些?如何设置? 7. 若在一个应用项目的文档窗口中,居中显示出红色、黑体、120点的“您好!”文本,应如何实现? 8. 什么是位图?如何将项目中的位图资源在应用程序中显示出来? 9. 在综合应用程序Ex_A7中,WM_LBUTTONDOWN消息映射函数中有下列代码,其目的是在当前鼠标点处绘制一个点标志图形,为什么需要4句代码?各句代码起什么作用? DrawCursor( GetDC(), point ); DrawPointFlag( GetDC(), point ); DrawPointFlag( &m_dcMem, point ); 10. 在MSDN文档中查找CDC成员函数FillSolidRect和Draw3dRect的作用和用法?若需要绘制一个大小为80 x 20的三维凸起按钮外观,则如何编写代码?
Similar presentations