第 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,LPINT lpnTabStopPositions,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定义的形状了 。