380 likes | 610 Views
第 8 章 绘图、字体和位图. 8.1 概述 8.2 简单图形的绘制 8.3 字体与文字处理 8.4 位图、图标与光标. 8.1 概述. 8.1.1 设备环境类 (1) CPaintDC CPaintDC 类的构造函数会自动调用 BeginPaint ,而它的析构函数则会自动调用 EndPaint 。 (2) CClientDC 和 CWindowDC CWindowDC 和 CClientDC 构造函数分别调用 GetWindowDC 和 GetDC ,但它们的析构函数 都是调用 ReleaseDC 函数。
E N D
第8章 绘图、字体和位图 8.1 概述 8.2 简单图形的绘制 8.3 字体与文字处理 8.4 位图、图标与光标
8.1 概述 8.1.1设备环境类 (1)CPaintDC CPaintDC类的构造函数会自动调用BeginPaint,而它的析构函数则会自动调用EndPaint。 (2)CClientDC 和CWindowDC CWindowDC和CClientDC构造函数分别调用GetWindowDC和GetDC,但它们的析构函数 都是调用ReleaseDC函数。 (3)CMetaFileDC CMetaFileDC封装了在一个Windows图元文件中绘图的方法。 8.1.1坐标映射 为了能保证打印的结果不受设备的影响,Windows定义了一些映射模式,这些映射模式决 定了设备坐标和逻辑坐标之间的关系。它们是: MM_TEXT 每个逻辑单位等于一个设备像素,x向右为正,y向下为正 MM_HIENGLISH 每个逻辑单位为0.001英寸,x向右为正,y向上为正 MM_HIMETRIC 每个逻辑单位为0.01毫米,x向右为正,y向上为正 MM_ANISOTROPIC x,y可变比例 MM_ISOTROPIC x,y等比例 MM_LOENGLISH 每个逻辑单位为0.01英寸,x向右为正,y向上为正 MM_LOMETRIC 每个逻辑单位为0.1毫米,x向右为正,y向上为正 MM_TWIPS 每个逻辑单位为一个点的1/20(一个点是1/72 英寸), x向右为正,y向上为正。
8.1 概述 在MM_ISOTROPIC映射模式下,纵横比总是1:1,换句话说,无论比例因子如何变化,圆总是圆的;但在MM_ANISOTROPIC映射模式下,x和y的比例因子可以独立地变化,即圆可以被拉扁成椭圆形状。 将一个椭圆绘制在视窗中央,且当视图的大小发生改变时,椭圆的形状也会随之改变: void CMyView::OnDraw(CDC* pDC) { 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)); } 8.1.3图形设备接口 1.使用GDI对象 在选择GDI对象进行绘图时,往往遵循着下列的步骤: (1)在堆栈中定义一个GDI对象(如CPen、CBrush对象),然后用相应的函数(如CreatePen、 CreateSolidBrush)创建此GDI对象。
8.1 概述 (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 ); // 恢复设备环境中原来的画笔 } 2.库存的GDI对象 Windows包含了一些库存的可以利用的GDI对象。CDC的成员函数SelectStockObject可以把一个库存对象选入当前设备环境中,并返回原先被选中的对象指针,同时使原先被选中的对象从设备环境中分离出来。如下面的代码:
8.1 概述 void CMyView::OnDraw( CDC* pDC ) { CPen newPen( PS_SOLID, 2, RGB(0,0,0) ) ) pDC->SelectObject( &newPen ); pDC->MoveTo(...); pDC->LineTo(...); pDC->SelectStockObject( BLACK_PEN ); // newPen被分离出来 } 函数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 系统字体
8.1 概述 8.1.4颜色和颜色对话框 在MFC中,CDC使用的是RGB颜色空间。其中,COLORREF是用来表示RGB颜色的一个32位的数据类型,它可以用下列的十六进制表示一个RGB值: 0x00bbggrr 在具体操作RGB颜色时,还可使用下列的宏操作: GetBValue 获得32位RGB颜色值中的蓝色分量 GetGValue 获得32位RGB颜色值中的绿色分量 GetRValue 获得32位RGB颜色值中的红色分量 RGB 将指定的R、G、B分量值转换成一个32位的RGB颜色值。 MFC的CColorDialog类为我们应用程序提供了颜色选择通用对话框,它具有下列的构造函数: CColorDialog( COLORREF clrInit = 0, DWORD dwFlags = 0, CWnd* pParentWnd = NULL ); 我们可以在CColorDialog提供的颜色列表中选择一种颜色或定制一种颜色。当该对话框“OK”退出(即DoModal返回 IDOK)时,还可以调用下列成员获得相应的颜色。 COLORREF GetColor( ) const; // 返回用户选择的颜色。 void SetCurrentColor( COLORREF clr ); // 强制使用clr作为当前选择的颜色 static COLORREF * GetSavedCustomColors( );// 返回用户自己定义颜色 例如,下面的代码片断: CColorDialog dlg; if (dlg. DoModal() != IDOK) return; COLORREF myColor = dlg.GeColor();
8.1 概述 8.1.5 简单数据类CPoint、CSize和CRect 1.CPoint、CSize和CRect类的构造函数 CPoint类带参数的常用构造函数原型如下: CPoint( int initX, int initY ); CPoint( POINT initPt ); CSize类带参数的常用构造函数原型如下: CSize( int initCX, int initCY ); CSize( SIZE initSize ); 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 ); 2.CPoint、CSize和CRect的基本运算符操作 (1) “+”操作 若向CPoint对象加上一个CSize对象,则返回CPoint对象。 若向CRect对象加上一个CPoint对象或CSize对象,则返回CRect对象。 若向CRect对象加上一个CSize对象,则将一个RECT(或CRect)值偏移(移动)CSize大小,
8.1 概述 (2) “-”操作 若从CPoint对象减去一个CSize对象,则返回一个CPoint对象。 若从CPoint对象减去一个CPoint对象,则返回一个CSize对象。 若从CRect对象减去一个CPoint对象或CSize对象,则返回一个CRect对象。 3.CRect类的常用操作 由于一个CRect类对象包含用于定义矩形的左上角和右下角点的成员变量,因此在传递 LPRECT、LPCRECT或RECT结构作为参数的任何地方,都可以使用CRect对象来代替。 成员函数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 ); 成员函数IntersectRect和UnionRect分别用来将两个矩形进行相交和合并,当结果为空时 返回FALSE,否则返回TRUE。它们的原型如下: BOOL IntersectRect( LPCRECT lpRect1, LPCRECT lpRect2 ); BOOL UnionRect( LPCRECT lpRect1, LPCRECT lpRect2 );
8.1 概述 其中,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));
8.2 简单图形的绘制 8.2.1创建画笔 创建一个修饰画笔,可以使用CPen类的CreatePen函数,其原型如下: BOOL CreatePen( int nPenStyle, int nWidth, COLORREF crColor ); 其中,参数nPenStyle、nWidth、crColor分别用来指定画笔的风格、宽度和颜色。 BOOL CreatePenIndirect( LPLOGPEN lpLogPen ); 此函数用由LOGPEN结构指针指定的相关参数创建画笔,LOGPEN结构如下: typedef struct tagLOGPEN { /* lgpn */ UINT lopnStyle; // 画笔风格,同上 POINT lopnWidth;// POINT结构的y不起作用,而用x表示画笔宽度 COLORREF lopnColor; // 画笔颜色 } LOGPEN; 值得注意的是: n当修饰画笔的宽度大于1个像素时,画笔的风格只能取PS_NULL、PS_SOLID或 PS_INSIDEFRAME,定义为其他风格不会起作用。 n画笔的创建工作也可在画笔的构造函数中进行,它具有下列原型: CPen( int nPenStyle, int nWidth, COLORREF crColor );
8.2 简单图形的绘制 8.2.2创建画刷 画刷用于指定填充的特性,许多窗口、控件以及其他区域都需要用画刷进行填充绘制, 它比画笔的内容更加丰富。 画刷的属性通常包括填充色、填充图案和填充样式三种。 CBrush类根据画刷属性提供了相应的创建函数,例如创建填充色画刷和填充样式画刷的函数为CreateSolidBrush和CreateHatchBrush,它们的原型如下: BOOL CreateSolidBrush( COLORREF crColor ); // 创建填充色画刷 BOOL CreateHatchBrush( int nIndex, COLORREF crColor ); // 创建填充样式画刷 与画笔相类似,也有一个LOGBRUSH 逻辑结构用于画刷属性的定义,并通过CBrush的成员函数CreateBrushIndirect来创建,其原型如下: BOOL CreateBrushIndirect( const LOGBRUSH* lpLogBrush ); 其中,LOGBRUSH 逻辑结构如下定义: typedef struct tagLOGBRUSH { // lb UINT lbStyle; // 风格 COLORREF lbColor; // 填充色 LONG lbHatch; // 填充样式 } LOGBRUSH;
8.2 简单图形的绘制 另外,还需注意: n画刷的创建工作也可在其构造函数中进行,它具有下列原型: CBrush( COLORREF crColor ); CBrush( int nIndex, COLORREF crColor ); CBrush( CBitmap* pBitmap ); n画刷也可用位图来指定其填充图案,但该位图应该是8×8像素,若位图太大,Windows 则只使用其左上角的8行8列的像素。 n画刷仅对绘图函数Chord、Ellipse、FillRect、FrameRect、InvertRect、Pie、Polygon、 PolyPolygon、Rectangle、RoundRect有效。 [例Ex_GDI] 绘制简单图形,其结果如右图所示。 创建的单文档应用程序为Ex_GDI,将代码添加 在CEx_GDIView::OnDraw中,则有: void CEx_GDIView::OnDraw(CDC* pDC) { CEx_GDIDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); int data[20] = {19,21,32,40,41,39,42,35, 33,23,21,20,24,11,9,19,22,32,40,42}; CRect rc; GetClientRect(rc);// 获得客户区的大小
8.2 简单图形的绘制 rc.DeflateRect(50,50); // 将矩形大小沿x和y方向各减小50 int gridXnums = 10, gridYnums = 8; int dx = rc.Width()/gridXnums; int dy = rc.Height()/gridYnums; CRect gridRect(rc.left,rc.top,rc.left+dx*gridXnums,rc.top+dy*gridYnums); // 调整矩形大小 CPen gridPen(0,0,RGB(0,100,200)); CPen* oldPen = pDC->SelectObject(&gridPen); for (int i=0; i<=gridXnums; i++) // 绘制垂直线 { pDC->MoveTo(gridRect.left+i*dx,gridRect.bottom); pDC->LineTo(gridRect.left+i*dx,gridRect.top); } for (int j=0; j<=gridYnums; j++) // 绘制水平线 { pDC->MoveTo(gridRect.left,gridRect.top+j*dy); pDC->LineTo(gridRect.right,gridRect.top+j*dy); } pDC->SelectObject(oldPen); // 恢复原来画笔 gridPen.Detach(); // 将画笔对象与其构造的内容分离,以便能再次构造画笔 gridPen.CreatePen(0,0,RGB(0,0,200)); // 重新创建画笔
8.2 简单图形的绘制 pDC->SelectObject(&gridPen); CBrush gridBrush(RGB(255,0,0)); // 创建画刷 CBrush* oldBrush = pDC->SelectObject(&gridBrush); POINT ptRect[4] = {{-3,-3},{-3,3},{3,3},{3,-3}}, ptDraw[4]; int deta; POINT pt[256]; int nCount = 20; deta = gridRect.Width()/nCount; for (i=0; i<nCount; i++) { pt[i].x = gridRect.left+i*deta; pt[i].y = gridRect.bottom-(int)(data[i]/60.0*gridRect.Height()); for (j=0; j<4; j++) { ptDraw[j].x = ptRect[j].x+pt[i].x; ptDraw[j].y = ptRect[j].y+pt[i].y; } pDC->Polygon(ptDraw,4); // 绘制小方块 } pDC->Polyline(pt,nCount); // 绘制折线
8.2 简单图形的绘制 // 恢复原来绘图属性 pDC->SelectObject(oldPen); pDC->SelectObject(oldBrush); } 需要说明的是: n大多数的绘图函数一般都是添加在用户视图中的OnDraw函数内,这时因为OnDraw是CView类的一个虚成员函数,每当视窗需要被重新绘制时,系统都要调用OnDraw函数。当与OnDraw类似的还有OnPaint函数。 n若对同一个GDI对象重新构造,则必须调用Detach函数把该对象从GDI中分离出来。 n在画直线时,总存在一个称为“当前位置”的特殊位置。有了“当前位置”的自动更新,就可避免了每次画线时都要给出两点的坐标。当然,这个当前位置还可用函数 CDC::GetCurrentPosition来获得,其原型如下: CPoint GetCurrentPosition( ) const;
8.3 字体与文字处理 8.3.1字体和字体对话框 1.字体的属性和创建 字体的属性有很多,但其主要属性主要有字样、风格和尺寸三个。 逻辑字体的具体属性可由LOGFONT结构来描述。 typedef struct tagLOGFONT { LONG lfHeight; // 字体的逻辑高度 LONG lfWidth; // 字符的平均逻辑宽度 LONG lfEscapement; // 倾角 LONG lfOrientation; // 书写方向 LONG lfWeight; // 字体的粗细程度 BYTE lfItalic; // 斜体标志 BYTE lfUnderline; // 下划线标志 BYTE lfStrikeOut; // 删除线标志 BYTE lfCharSet; // 字符集,汉字必须为GB2312_CHARSET BYTE lfOutPrecision; // 字符输出精度 BYTE lfClipPrecision; // 裁剪精度 BYTE lfQuality; // 逻辑字体与物理字体的相似程度
8.3 字体与文字处理 BYTE lfPitchAndFamily; // 字符的间隔和字体的类型 TCHAR lfFaceName[LF_FACESIZE]; // 字样名称 } LOGFONT; 在结构成员中,lfHeight表示字符的逻辑高度,这里的高度是字符的纯高度 。 若对于MM_TEXT映射模式,当用指定的点的大小来确定字符高度时,我们可以使用下列的公式: lfHeight = -MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72); 根据定义的逻辑字体,用户就可以调用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");
8.3 字体与文字处理 pDC->SelectObject(oldfont); // 恢复设备环境原来的属性 cf.DeleteObject(); // 删除字体对象 2.使用字体对话框 CFontDialog类为我们提供了字体及其文本颜色选择的通用对话框。它的构造函数如下: CFontDialog( LPLOGFONT lplfInitial = NULL, DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS, CDC* pdcPrinter = NULL, CWnd* pParentWnd = NULL ); 当字体对话框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; // 判断是否是斜体
8.3 字体与文字处理 例如下列代码是通过字体对话框来创建一个字体的: 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); ... } 8.3.2常用文本输出函数 CDC为用户提供了四个输出文本的函数:TextOut、ExtTextOut、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) 处显示一个文本。 virtual CSize TabbedTextOut( int x, int y, LPCTSTR lpszString, int nCount, int nTabPositions, LPINTlpnTabStopPositions, int nTabOrigin ); CSize TabbedTextOut( int x, int y, const CString& str, int nTabPositions, LPINT lpnTabStopPositions, int nTabOrigin );
8.3 字体与文字处理 TabbedTextOut也是用当前字体在指定位置处显示一个文本,但它还根据指定的制表位(Tab)设置相应字符位置,函数成功时返回输出文本的大小。 需要说明的是,默认时,上述文本输出函数既不使用也不更新“当前位置”。若要使用和更新“当前位置”,则必须调用SetTextAlign,并将参数nFlags设置为TA_UPDATECP。 8.3.3 本格式化属性 文本的格式属性通常包括文本颜色、对齐方式、字符间隔以及文本调整等。 在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; 文本对齐方式的设置和获取是由CDC函数SetTextAlign和GetTextAlign决定的。它们的原型如下: UINT SetTextAlign( UINT nFlags ); UINT GetTextAlign( ) const;
8.3 字体与文字处理 上述两个函数中所用到的文本对齐标志如下表所示。 8.3.4 计算字符的几何尺寸 在CDC类中,GetTextMetrics(LPTEXTMETRIC lpMetrics)是用来获得指定映射模式下相关设备环境的字符几何尺寸及其它属性的,其TEXTMETRIC结构描述如下: typedef struct tagTEXTMETRIC { int tmHeight; // 字符的高度 (ascent + descent) int tmAscent; // 高于基准线部分的值 int tmDescent; // 低于基准线部分的值 int tmInternalLeading; // 字符内标高 int tmExternalLeading; // 字符外标高
8.3 字体与文字处理 int tmAveCharWidth; // 字体中字符平均宽度 int tmMaxCharWidth; // 字符的最大宽度 int tmWeight; // 字体的粗细 BYTE tmItalic; // 非0表示斜体 BYTE tmUnderlined; // 非0表示加下划线 BYTE tmStruckOut; // 非0表示带有删除线 BYTE tmFirstChar; // 字体的第一个字符 BYTE tmLastChar; // 字体的最后一个字符 BYTE tmDefaultChar; // 指定不在字体中的字符的替换字符 BYTE tmBreakChar; // 用于定义换行字符 BYTE tmPitchAndFamily; // 字符的间隔和字体的类型 BYTE tmCharSet; // 字符集 int tmOverhang; // 一些特殊字体的额外宽度 int tmDigitizedAspectX; // 字体的水平比例 int tmDigitizedAspectY; // 字体的垂直比例 } TEXTMETRIC; 通常,字符的总高度是用tmHeight和tmExternalLeading的总和来表示的。
8.3 字体与文字处理 在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, int nTabPositions, LPINT lpnTabStopPositions ) const; 8.3.5 文档内容显示及其字体改变 [例Ex_Text]在视图类中通过文本绘图的方法显示出打开文档的文本内容以及显示字体的改变。 (1)用MFC AppWziard创建一个单文档应用程序Ex_Text,在创建的第六步将视图的基类选 择为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();
8.3 字体与文字处理 while (ar.ReadString(str)) { m_strContents.Add(str); } } } (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((LPSTR)&(m_lfText.lfFaceName), "宋体"); } (6)用ClassWizard为CEx_TextView类添加WM_LBUTTONDBLCLK(双击鼠标)的消息映射 函数,并增加下列代码:
8.3 字体与文字处理 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); } 这样,当双击鼠标左键后,就会弹出字体对话框,从中可改变字体的属性,单击[确定]按钮后,执行CEx_TextView::OnDraw中的代码。 (7) 在CEx_TextView::OnDraw中添加下列代码: void CEx_TextView::OnDraw(CDC* pDC) { CEx_TextDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // 创建字体 CFont cf; cf.CreateFontIndirect(&m_lfText); CFont* oldFont = pDC->SelectObject(&cf);
8.3 字体与文字处理 // 计算每行高度 TEXTMETRIC tm; pDC->GetTextMetrics(&tm); int lineHeight = tm.tmHeight + tm.tmExternalLeading; int y = 0; int tab = tm.tmAveCharWidth * 4; // 为一个TAB设置4个字符 // 输出并计算行的最大长度 int lineMaxWidth = 0; CString str; CSize lineSize(0,0); for (int i=0; i<pDoc->m_strContents.GetSize(); i++) { str = pDoc->m_strContents.GetAt(i); pDC->TabbedTextOut(0, y, str, 1, &tab, 0); str = str + "A"; // 多计算一个字符宽度 lineSize = pDC->GetTabbedTextExtent(str, 1, &tab); if ( lineMaxWidth < lineSize.cx ) lineMaxWidth = lineSize.cx; y += lineHeight; }
8.3 字体与文字处理 pDC->SelectObject(oldFont); int nLines = pDoc->m_strContents.GetSize() + 1; // 多算一行 CSize sizeTotal; sizeTotal.cx = lineMaxWidth; sizeTotal.cy = lineHeight * nLines; SetScrollSizes(MM_TEXT, sizeTotal); // 设置滚动逻辑窗口的大小 } (8) 编译运行并测试,打开任意一个文本文件,结果如下图5所示。
8.4 位图、图标与光标 8.4.1 使用图形编辑器 利用Visual C++ 6.0提供的图形编辑器,可以完成下列操作: n绘制新的位图、图标和光标。 n选用或定制显示设备 n设置光标“热点” n使用256色绘制图标和光标 下面仅介绍显示设备的选用和定制以及光标“热点”的设置。 1.选用和定制显示设备 用户在创建新图标或光标的时候,图形编辑器首先创建的是一个适合于VGA环境中的图像。默认情况下,图形编辑器所支持的显示设备如下表所示。 2. 设置光标热点
8.4 位图、图标与光标 Windows系统借助光标“热点”来确定光标实际的位置。默认时,光标热点是图像左上角(0,0)的点,当然,这个热点位置可能重新指定,具体步骤如下: (1) 打开光标资源。 (2) 在图形编辑器的控制条上,单击Hot Spot按钮。 (3) 在光标图像上单击要指定的像素点,此时会看到在控制条上自动显示所点中的像 素点的坐标。 (4) 重复2、3两步,直到指定的热点位置满意为止。 8.4.1位图 Windows的位图有两种类型:一种称之为GDI位图,另一种是DIB位图。 1.CBitmap类 CBitmap类封装了Windows的GDI位图操作所需的大部分函数,这主要包括位图的初始化函数。例如: BOOL LoadBitmap( LPCTSTR lpszResourceName ); BOOL LoadBitmap( UINT nIDResource ); BOOL LoadOEMBitmap( UINT nIDBitmap ); 若用户直接创建一个位图对象,可使用CBitmap类中的CreateBitmap、 CreateBitmapIndirect以及CreateCompatibleBitmap函数,其原型如下。 BOOL CreateBitmap( int nWidth, int nHeight, UINT nPlanes, UINT nBitcount, const void* lpBits );
8.4 位图、图标与光标 BOOL CreateBitmapIndirect( LPBITMAP lpBitmap ); 该函数直接用BITMAP结构来创建一个位图对象。 BOOL CreateCompatibleBitmap( CDC* pDC, int nWidth, int nHeight ); 该函数为某设备环境创建一个指定的宽度(nWidth)和高度(nHeight)的位图对象。 2.GDI位图的显示 由于位图不能直接显示在实际设备中,因此对于GDI位图的显示则必须遵循下列步骤: (1)调用CBitmap类的CreateBitmap、CreateCompatibleBitmap以及 CreateBitmapIndirect函数创建一个适当的位图对象。 (2)调用CDC::CreateCompatibleDC函数创建一个内存设备环境,以便位图在内 存中保存下来,并与指定设备(窗口设备)环境相兼容; (3)调用CDC::SelectObject函数将位图对象选入内存设备环境中; (4)调用CDC::BitBlt或CDC::StretchBlt函数将位图复制到实际设备环境中。 (5)使用之后,恢复原来的内存设备环境。 [例Ex_BMP] 显示BMP位图文件。 创建一个单文档应用程序Ex_BMP,从外部文件中调入一张位图作为应用程 序的位图资源(IDB_BITMAP1),则下面的代码是将其显示在视图的客户区内: void CEx_BMPView::OnDraw(CDC* pDC) { CEx_BMPDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);
8.4 位图、图标与光标 CBitmap m_bmp; m_bmp.LoadBitmap(IDB_BITMAP1); // 调入位图资源 BITMAP bm; 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); // 恢复原来的内存设备环境 } 通过上述代码,用户可以看出:位图的最终显示是通过调用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 );
8.4 位图、图标与光标 8.4.3图标 一个应用程序允许有两个尺寸不一的图标来标识自己的身份:一种是普通图标,也称为大图标,它是32 x 32的位图。另一种是小图标,它是大小为16 x 16的位图。 1.图标的调入和清除 在MFC中,使用CWinApp::LoadIcon函数可将一个图标资源调入并返回一个图标句柄。函数原型如下: HICON LoadIcon( LPCTSTR lpszResourceName ) const; HICON LoadIcon( UINT nIDResource ) const; 其中,lpszResourceName和nIDResource分别表示图标资源的字符串名和标识。 如果不想创建新的图标资源,也可使用系统中预定义好的标准图标,这时需调用 CWinApp::LoadStandardIcon 函数,其原型如下: HICON LoadStandardIcon( LPCTSTR lpszIconName ) const; 其中,lpszIconName可以是下列值之一: IDI_APPLICATION 默认的应用程序图标 IDI_HAND 手形图标(用于严重警告) IDI_QUESTION 问号图标(用于提示消息) IDI_EXCLAMATION 警告消息图标(惊叹号) IDI_ASTERISK 消息图标 全局函数DestroyIcon可以用来删除一个图标,并释放为图标分配的内存,其原型如下 BOOL DestroyIcon( HICON hIcon );
8.4 位图、图标与光标 2.图标的绘制 函数CDC::DrawIcon用来将一个图标绘制在指定的位置处,其原型如下: BOOL DrawIcon( int x, int y, HICON hIcon ); BOOL DrawIcon( POINT point, HICON hIcon ); 其中,(x, y)和point用来指定图标绘制的位置,而hIcon用来指定要绘制的图标句柄。 3.应用程序图标的改变 在用MFC AppWizard创建的应用程序中,图标资源IDR_MAINFRAME用来表示应用程序窗口的图标。实际上,程序中还可使用GetClassLong和SetClassLong函数重新指定应用程序窗口的图标,函数原型如下: DWORD SetClassLong( HWND hWnd, int nIndex, LONG dwNewLong); DWORD GetClassLong( HWND hWnd, int nIndex); nIndex用来指定与WNDCLASSEX结构相关的索引,它可以是下列值之一: GCL_HBRBACKGROUND 窗口类的背景画刷句柄 GCL_HCURSOR 窗口类的的光标句柄 GCL_HICON 窗口类的的图标句柄 GCL_MENUNAME 窗口类的的菜单资源名称 [例Ex_Icon]图标按一定的序列显示, 模拟动画效果。 (1)用MFC AppWziard创建一个单文档应用程序Ex_Icon。
8.4 位图、图标与光标 (2) 创建六个图标,大小为16×16,ID号分别为默认的IDI_ICON1~ IDI_ICON4。用图形编 辑器绘制图标,结果如下图所示。 (3)为CMainFrame类添加一个成员函数ChangeIcon,用来切换应用程序的图标。该函数的 代码如下: void CMainFrame::ChangeIcon(UINT nIconID) { HICON hIconNew = AfxGetApp()->LoadIcon(nIconID); HICON hIconOld = (HICON)GetClassLong(m_hWnd, GCL_HICON); if (hIconNew != hIconOld) {
8.4 位图、图标与光标 DestroyIcon(hIconOld); SetClassLong(m_hWnd, GCL_HICON, (long)hIconNew); RedrawWindow(); // 重绘窗口 } } (4)在CMainFrame::OnCreate函数的最后添加计时器设置代码: int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; ... SetTimer(1, 500, NULL); return 0; } (5)用ClassWizard为CMainFrame类添加WM_TIMER的消息映射函数,并增加下列代码: void CMainFrame::OnTimer(UINT nIDEvent) { static int icons[] = { IDI_ICON1, IDI_ICON2, IDI_ICON3, IDI_ICON4}; static int index = 0;
8.4 位图、图标与光标 ChangeIcon(icons[index]); index++; if (index>3) index = 0; CFrameWnd::OnTimer(nIDEvent); } (6)用ClassWizard为CMainFrame类添加WM_DESTROY的消息映射函数,并增加下列代码: void CMainFrame::OnDestroy() { CFrameWnd::OnDestroy(); KillTimer(1); } (7)编译并运行。可以看到任务栏上的按钮以及应用程序的标题栏上四个图标循环显示的 动态效果,显示速度为每秒两帧。 8.4.1光标 1.使用系统光标 Windows预定义了一些经常使用的标准光标,这些光标均可以使用函数CWinApp:: LoadStandardCursor加载到程序中,其函数原型如下: HCURSOR LoadStandardCursor( LPCTSTR lpszCursorName ) const; 其中,lpszCursorName用来指定一个标准光标名,它可以是下列宏定义: IDC_ARROW 标准箭头光标
8.4 位图、图标与光标 IDC_IBEAM 标准文本输入光标 IDC_WAIT 漏斗型计时等待光标 IDC_CROSS 十字形光标 IDC_UPARROW 垂直箭头光标 IDC_SIZEALL 四向箭头光标 IDC_SIZENWSE 左上至右下的双向箭头光标 IDC_SIZENESW 左下至右上的双向箭头光标 IDC_SIZEWE 左右双向箭头光标 IDC_SIZENS 上下双向箭头光标 例如,加载一个垂直箭头光标IDC_UPARROW的代码如下: HCURSOR hCursor; hCursor = AfxGetApp()->LoadStandardCursor(IDC_UPARROW); 2.使用光标资源 用编辑器创建或从外部调入的光标资源,可通过函数CWinApp::LoadCursor 进行加载,其原型如下: HCURSOR LoadCursor( LPCTSTR lpszResourceName ) const; HCURSOR LoadCursor( UINT nIDResource ) const; 其中,lpszResourceName和nIDResource分别用来指定光标资源的名称或ID号。 例如,当光标资源为IDC_CURSOR1时,则可使用下列代码: HCURSOR hCursor;
8.4 位图、图标与光标 hCursor = AfxGetApp()->LoadCursor(IDC_CURSOR1); 需要说明的是,也可直接用全局函数LoadCursorFromFile加载一个外部光标文件,例如 HCURSOR hCursor; hCursor = LoadCursorFromFile(“c:\\windows\\cursors\\globe.ani”); 3.更改程序中的光标 更改应用程序中的光标除了可以使用GetClassLong和SetClassLong函数外,最简单的方法是用ClassWizard映射WM_SETCURSOR消息。CWnd为此消息的映射函数定义这样的原型: afx_msg BOOL OnSetCursor( CWnd* pWnd, UINT nHitTest, UINT message ); [例Ex_Cursor]鼠标移动到标题栏时,改变光标。加入下列代码: BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { BOOL bRes = CFrameWnd::OnSetCursor(pWnd, nHitTest, message); if (nHitTest == HTCAPTION ) { HCURSOR hCursor; hCursor = AfxGetApp()->LoadCursor(IDC_CURSOR1); SetCursor(hCursor); bRes = TRUE; } return bRes; } 这样,当鼠标移动到标题栏时,光标就变成了IDC_CURSOR1定义的形状了。