VC++ 第5章 图形绘制

更新时间:2024-03-15 06:21:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第5章 图形绘制

在Visual C++6.0中,掌握图形程序设计方法是非常重要的。因为图形在任何一个可视化工程项目中都是不可缺少的。CDC(设备环境)类封装了图形绘制所需要的各种操作。本章我们将通过实际例子和较详细的阐述,来了解和掌握如何使用设备环境类

(CDC)及图形设备接口(GDI)进行图形绘制。

5.1 设备环境与设备环境类(CDC)

5.1.1 设备环境

设备环境也称设备上下文(Device Context,简称DC),是计算机物理设备的代表,也是图形设备接口的主要组成部分。

由于Windows是一个与设备无关的操作系统,即Windows不允许直接访问硬件,如果用户想将文本和图形绘制到显示器或其它设备中去,必须通过“设备环境”这个抽象层与硬件进行通信,设备上下文对象的作用就是实现Windows的设备无关性,任何向屏幕上进行输出的功能都要间接地通过它来完成。

设备上下文是Windows的一种数据结构,它包含了有关如显示器或打印机等设备的绘图属性信息。所有绘画都是通过设备上下文对象来实现的,该对象封装了Windows的画线、图形和文本的API函数。设备上下文允许在Windows下独立于设备的绘画。设备上下文不仅能够被用来在屏幕上绘画,它也可以将绘画输出到打印机和图元文件中。

5.1.2 设备环境类

设备环境类CDC直接继承于CObject类,该类定义了一类设备对象。CDC对象提供了非常多的成员函数,与设备环境的显示器、打印机等一起工作。例如,如果要在显示器等设备上绘制图形,我们可以用MFC提供的设备环境类CDC类,因为CDC类中包含了绘图所需要的所有成员函数。同时。MFC还提供了以下几个CDC的派生类: 1、CPaintDC类

此类比较特殊,它的构造函数和析构函数都是针对OnPaint进行的。用户一旦获得相关的CDC指针,就可以将它当做任何设备环境(包括屏幕、打印机)指针来使用,CPaintDC类的构造函数会自动调用BeginPaint,而它的析构函数则会自动调用EndPaint。 2、CClientDC和CWindowDC类

CClientDC只能在窗口的客户区(不包括边框、工具条、标题栏、滚动条、菜单栏以及状态栏)进行绘图,点(0,0)通常指的是客户区的左上角。其构造函数调用GetDC,析构函数调用ReleaseDC函数。

CWindowDC允许在窗口的任意位置中进行绘图,点(0,0)指整个窗口的左上角。 其构造函数调用GetWindowDC,析构函数调用ReleaseDC函数。 3、CMetaFileDC类

封装了在一个Windows图元文件中绘图的方法。图元文件是一系列与设备无关的图片的集合,由于它对图像的保存比像素更精确,因而往往在要求较高的场合下使用,例如:AutoCAD的图像保存等。目前的Windows已使用增强格式(enhanced-format)的32

1

位图元文件来进行操作。设备环境类CDC及其派生类如图5.1.1所示。

CDC

CClientDC CPaintDC CWindowDC

图5.1.1 CDC与其子类继承图

5.2 绘图程序

5.2.1 CDC基类

CDC类是其它DC类的基类,CDC类封装了使用设备环境的各种图形设备接口(GDI)函数,它用于定义一个设备环境对象,并提供了在显示器、打印机和窗口的客户区域上画图的方法。

1、在视图类的OnDraw()函数中绘图

在视图类的OnDraw函数中绘图时,直接使用OnDraw()函数中的CDC形参指针pDC,调用它的函数进行绘图:

例5.1 绘制矩形与文本

(1)创建一个单文档应用程序,名为: 显示绘图

(2)在视图类的实现文件“显示绘图

View.cpp”的OnDraw函数中加如下代码: void CMyView::OnDraw(CDC* pDC) { CMyDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc); 图5.2.1 绘制矩形和文本 //选择画刷,浅灰色画刷,见表5.2

pDC->SelectStockObject(LTGRAY_BRUSH);

CRect rect(10,10,200,70);//定义一个矩形 pDC->Rectangle(rect);//绘制一个矩形 pDC->TextOut(20,80,\我们会编制图形程序了 !\//绘制文本 pDC->TextOut(20,100,”努力学好Visual C++ !”); }

3)编译运行,结果如图5.2.1所示。

说明:SelectStockObject( ) 该函数检索预定义的备用笔、刷子、字体或者调色板的句柄。 参数:LTGRAY_BRUSH:亮灰色画笔;BLACK_BRUSH黑色画笔; NULL_BRUSH:空画笔(相当于HOLLOW_BRUSH); WHITE_BRUSH:白色画笔;BLACK_PEN:黑色钢笔;......

2、在视图类的一般函数中绘图

2

例5.2 在鼠标放下函数WM_LBUTTONDOWN函数中绘图 (1)创建一个单文档应用程序,名为:一般函数绘图

(2)利用MFC ClassWizard将鼠标放下WM_LBUTTONDOWN消息映射到视图类CMyView中,并添加代码:

void CMyView::OnLButtonDown(UINT nFlags, CPoint point) { //定义一个矩形

CRect rect(0,0,100,100);

//GetDC检索整个屏幕的设备上下文环境

CDC *pDC=GetDC();

pDC->Rectangle(rect);//绘制矩形 ReleaseDC(pDC);//释放pDC空间

CView::OnLButtonDown(nFlags, point); }

(3)编译运行,鼠标点击窗口,出现 图5.2.2 在OnLButtonDown函数中绘图 图5.2.2所示的矩形。

5.2.2 CPaintDC类

CPaintDC对象代表了一个窗口的绘图画面,主要用来绘图。它可以用来处理来自Windows的WM_PAINT消息。改变窗口大小或移动覆盖在窗上的窗口或对话框时,Windows会发送WM_PAINT消息以通知客户区的变动,而WM_PAINT消息的处理是在OnPaint()消息处理函数中进行的。

例5.3 在WM_PAINT消息函数中绘图

(1)创建一个单文档的应用程序,名为:绘图

(2)利用MFC ClassWizard将WM_PAINT消息映射到视图类(View)中,并添加代码: void CMyView::OnPaint() { //CPaintDC dc(this);

//调用MoveTo函数,定点到点(10,10) dc.MoveTo(10,10);

//调用LineTo函数,画线到点(100,100) dc.LineTo(100,100); //画线

//调用画椭圆函数Ellipse画椭圆

dc.Ellipse(120,120,160,160); }

(3)编译运行,结果见5.2.3所示 画直线图5.2.3 运行结果

5.2.3 CClientDC类

CClientDC对象用来自动处理对描述窗口的客户程序区域的设备环境进行调用和释放。在CClientDC对象创建时自动调用GetDC(),在撤销时将自动调用ReleaseDC()。

3

例5.4 在客户区画一条直线

(1)创建一个单文档的应用程序,名为:画直线 (2)利用MFC ClassWizard将鼠标放下WM_LBUTTONDOWN消息映射到视图类CMyView中,并添加代码:

void CMyView::OnLButtonDown(UINT nFlags, CPoint point) { CClientDC dc(this);

CRect rect; //定义一个矩形。 // 取得客户区矩形区域 GetClientRect(&rect);

//调用MoveTo,定点到客户区的左上角

dc.MoveTo(rect.left,rect.top);

//调用LineTo函数,画线到客户区的右下角

dc.LineTo(rect.right,rect.bottom); 图5.2.4 绘制一条直线 }

(3)编译运行,鼠标左键点击窗口任一点,便在窗口上画一条直线,如图5.2.4所示。

5.3 图形设备接口(GDI)对象

GDI(Graphic Device Interface)对象与设备环境对象的关系,类似于笔和纸的关系。也就是说,GDI提供了用于在DC上画图的绘图工具。MFC的GDI中包含了各种绘图类并提供各类的绘图函数即定义了若干种对于Windows的绘图工具的对象,该类的继承关系如图5.3.1所示;GDI中包含的各种绘图类有一个共同的抽象基类CGdiObject,具体如表5.1所示:

CGdiObject CBitmap CBrush CFont CPalette CPen CRgn

图5.3.1 MFC 的GDI类继承图

1、使用GDI对象

选择GDI对象进行绘图时,一般要遵循下列步骤:

(1)定义一个GDI对象(如CPen、CBrush对象),然后用相应的函数(如CreatePen、CreateSolidBrush)创建此GDI对象。但要注意;有些GDI派生类的构造函数允许用户提供足够的信息,从而一步即可完成对象的创建任务,这些类有CPen、CBrush。

(2)将构造的GDI对象选入当前设备坏境中,但不要忘记将原来的GDI对象保存起来。 (3)绘图结束后,恢复当前设备环境中原来的GDI对象。 2、库存的GDI对象

除了自定义的GDI对象外,Windows还包含了一些预定义的库存GDI对象。由于它们是Windows系统的一部分,因此用户用不着删除它们。CDC的成员函数

4

SelectStockObject可以把一个库存对象选入当前设备环境中,并返回原先被选中的对象指针,同时使原先被选中的对象从设备环境中分离出来。

函数SelectStockObject可选用的库存GDI对象类型可以是表5.2所示值之一。

表5.1 MFC的GDI类

类 名 说 明 CBitmap “位图”是一种位矩阵,每一个显示像素都对应于其中的一个或多个位,用户可以

利用位图来表示图象,也可以利用它来创建画刷

CBrush “画刷”定义了一种位图形式的像素,利用它可对区域内部填充颜色或样式 CFont “字体”是一种具有某种风格和尺寸的所有字符的完整集合,它常常

被当做资源存于磁盘中,其中有一些还依赖于某种设备

CPalette “调色板”是一种颜色映射接口,它允许应用程序在不干扰其它应用

程序的前提下,充分利用输出设备的颜色描绘能力

CPen “画笔”是一种用于画线及绘制有形边框的工具,用户可以指定它的

颜色及宽度,并且可以指定它实线、点线或虚线等

CRgn “区域”是由多边形、椭圆或二者组合形成的一种范围,可以利用它

来进行填充、裁剪以及鼠标点中测试等

表5.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 系统字体

5.3.1 画笔CPen类

CPen类封装了一个Windows GDI画笔,并且提供了用于操作CPen对象的若干方法。CPen类用来决定画线的风格和颜色。在使用画笔之前,首先必须构造画笔对象,有2种方法:

(1)构造和初始化对象都在带参数的构造函数中一步完成,如:生成颜色为绿色,宽度为2个像素的实心画笔:CPen newPen(PS_SOLID,2,RGB(0,255,0));

5

Typedef struct tagSIZE

{ int cx; //水平大小 (表示矩形的宽度) int cy; //垂直大小 (表示矩形的高度) }SIZE;

typedef struct tagRECT

{ LONG left; //矩形左上角点的x坐标 LONG top; //矩形左上角点的y坐标 LONG right; //矩形右下角点的x坐标 LONG bottom; //矩形右下角点的y坐标 }RECT;

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);

参 数:

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),那么定义的这个矩形就

11

不符合规范。一个不符合规范的矩形,CRect的许多成员函数都不会有正确的结果。因此,常常使用CRect::NormalizeRect函数使一个不符合规范的矩形合乎规范。 CRect类的操作函数有很多,这里只介绍矩形的扩大、缩小以及2个矩形的“并”和“交”操作,更多的常用的成员函数如表5.6所示:

表5.6 CRect类常用的成员函数

成员函数 功能说明 int Width()const; 返回矩形的宽度 int Height()const; 返回矩形的高度 CSize Size()const; 返回矩形左下角的点坐标 CPoint &BottomRight 返回矩形右下角的点坐标 CPoint CenterPoing()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(); 将矩形的所有坐标设置为0

void NormalizeRect(); 使矩形符合规范如:左上角为(10,10),右下角为(20,20)

是规范的。而左上角为(20,20),右下角为(10,10)是不 规范的。

void OffsetRect(int x,int y); 移动矩形,水平和垂直移动量分别由x、y或point、size的2个

成员来指定

void OffsetRect(POINT point); void OffsetRect(SIZE size);

成员函数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);

说 明:

1、x 用于指定扩大CRect左、右边的数值 2、y 用于指定扩大CRect上、下边的数值

3、size中的 cx 成员指定扩大左、右边的数值,cy成员指定扩大上、下边的数值 4、lpRect的各个成员用于指定扩大每一边的数值

12

5、l,t,r,b分别用于指定扩大CRect左、上、右、下边的数值

由于InflateRect是通过将CRect的边向远离其中心的方向移动来扩大的,因此对于前2个重载函数来说,CRect的总宽度被增加了2倍的x或cx,总高度被增加了2倍的y或cy。

成员函数IntersectRect和UnionRect分别用于将2个矩形进行相交和合并,当结果为空时返回FALSE,否则返回TRUE。他们的原型如下: BOOL IntersectRect(LPCRECT lpRect1,LPCRECT lpRect2); BOOL UnionRect(LPCRECT lpRect1,LPCRECT lpRect2); 参 数:lpRect和lpRect2用于指定操作的2个矩形,例如:

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));

例5.8 改变窗口大小时,窗口会以三种不同的颜色显示窗口背景。 1)建一个单文档SDI应用程序,名为:显示背景颜色

2)在视图的头文件View.h的public下定义矩形窗口对象:CRect r; 3)在视图的执行文件View.cpp前面加颜色预定义: ……….

#endif

#define RED RGB(255,0,0) //红色 #define GREEN RGB(0,255,0) //绿色 #define BLUE RGB(0,0,255) //兰色 #define BLACK RGB(0,0,0) //黑色值

///////////////////////////////////////////////////////////////////////////// // CMyView

IMPLEMENT_DYNCREATE(CMyView, CView) BEGIN_MESSAGE_MAP(CMyView, CView) ……….

4)在OnDraw()函数添加如下代码: void CMyView::OnDraw(CDC* pDC) { CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);

GetClientRect(r); //获取窗口大小

if(r.right>750&&r.right<1000||r.bottom>750&&r.bottom<1000) pDC->FillSolidRect(r,RED); //充填窗口背景红色 else if(r.right>500&&r.right<750||r.bottom>500&&r.bottom<750)

13

pDC->FillSolidRect(r,GREEN); //充填窗口背景绿色 else

if(r.right<500||r.bottom<500) pDC->FillSolidRect(r,BLUE); //充填窗口背景蓝色) else

pDC->FillSolidRect(r,BLACK);//充填窗口背景黑色 }

5)运行后出现的窗口是红色,最小是蓝色,中间是绿色。

5.6 颜色和颜色对话框

一个彩色像素的显示需要颜色空间支持,MFC中的CDC类使用的是RGB颜色空间,即选用红(R)、绿(G)、蓝(B)3种基色分量,通过对这3种基色不同比例的混合,可以得到不同彩色效果。在MFC中使用COLORREF数据类型来表示一个32位的RGB颜色,它可以用16进制表示: OxOObbggrr。其中:rr,gg,bb分别表示红、绿、蓝各种颜色分量的16进制值,最大为Oxff。在具体操作RGB颜色时,还可使用下列宏操作:

GetBValue 获得32位RGB颜色值中的蓝色分量 GetGValue 获得32位RGB颜色值中的绿色分量 GetRValue 获得32位RGB颜色值中的红色分量

RGB 将指定的R,G,B分量值转换成一个32位的RGB颜色值

MFC的CColorDialog类为应用程序提供了“颜色选择通用”对话框,如图5.6.1所示。 CColorDialog类具有下列的构造函数: CColorDialog(COLORREF clrInit=0,

DWORD dwFlags=0,

CWnd *pParentWnd=NULL);

参 数:

clrInit 用于指定选择的默认颜色值,若此值没有指定,

则为RGB(0,0,0) (黑色)

pParentWnd 用于指定对话框的父窗口指针

dwFlags 用于表示定制对话框外观和功能的系列标志

参数,它可以是下列值之一或“|”组合: CC_ANYCOLOR 在基本颜色单元中列出所有可得

到的颜色

CC_FULLOOEN 显示所有的颜色对话框界面。 图5.6.1“颜色选择通用”对话框(调色板)

若此标志没有被设定,则用

户单击“规定自定义颜色”按钮才能显示出定制颜色界面

CC_PREVENTFULLOPEN禁用“规定自定义颜色”按钮 CC_SHOWHELP 在对话框中显示“帮助”按钮

CC_SOLIDCOLOR 在基本颜色单元中只列出所得到的纯色。

当对话框“OK”退出(即DoModal返回IDOK)时,可调用下列成员获得相应的颜色: COLORREF GetColor()const; //返回用户选择的颜色

14

void SetCurrentColor(COLORREF clr); //强制使用clr作为当前选择的颜色 static COLORREF *GetSavedCustomColors(); //返回用户自己定义的颜色

例5.9 使用颜色对话框,可在窗口中随意画图、更改颜色及画笔宽度、填充颜色等。 如何进行颜色选取,MFC为我们封装了一个名为CColorDialog的类,这个类将使我们轻松地完成颜色选取,弹出颜色对话框,“自定义颜色”等。 (1)建一个单文档SDI应用程序,名为:工具条绘图

(2)在查看的下面建菜单:ID为:ID_COLORS_MENU ,标题为:画笔(&H) (3)加工具条及工具条按钮,样式如图5.6.2所示:

图5.6.2 工具条按钮布置的样式

ResourceView?右键击Toolbar?Insert?Toolbar?New,将此工具条本身的ID改为:IDR_MYTOOLBAR,右边工具条上出现一个按钮,双击这个按钮,将其ID改为:ID_BUTTONRED,之后用画刷将其涂成红色。再逐个双击后面出现的空白按钮,将它们逐个的ID改为:

ID_BUTTONGREEN(用画刷涂成绿色) ID_BUTTONBLUE(用画刷涂成蓝色) ID_BUTTONDASH(细斜杠) ID_BUTTONSOLID(粗斜杠) ID_BUTTONRECT(方框填充) ID_BUTTONFILL(画刷图形)

ID_BUTTON_PENCOLORS(画笔图形) ID_BUTTON_PENWILD(画笔宽度) (4)加鼠标映射(分别映射到视图View里) WM_ LBUTTONDOWN 鼠标按下

WM_MOUSEMOVE 鼠标移动 WM_LBUTTONUP 鼠标抬起

(5)自定义按钮的消息映射函数:用于选择其中的任何一项画图:

在视图头文件(View.h)消息映射区里加:afx_msg void OnPenChioce(UINT nID); (6)在视图执行文件(View.cpp)消息映射开始处加:

//位图按钮消息映射函数

ON_COMMAND_RANGE(ID_BUTTONRED,ID_BUTTON_PENWILD,OnPenChioce) (7)在视图Wiew.h头文件的public:下加成员变量和函数: CPoint StartPt,EndPt; //用于记录画笔的起始位置和终点位置 CPen Pen; //画笔对象 COLORREF m_colors; //颜色对象

CRect Rect; //矩形对象即窗口大小 int PenStarte; //画笔状态

DWORD PenStyle; //用于存储画笔风格

15

int MyPenWild; //用于设置画笔的宽度

void FillMyRect(); //充填颜色

void MyColorsDlg(); //用以完成颜色选取对话框的实现

void WildDlg(); //用于完成选择画笔宽度对话框的初始化 (8)在视图View.cpp的开头部分加: #endif

#define RED RGB(255,0,0) #define GREEN RGB(0,255,0) #define BLUE RGB(0,0,255) 在下面的构造函数里这样加: CMyView::CMyView()

:PenStarte(0) //此处的“:”表示其后面列表称为成员初始化列表。画笔状态初始为0 { MyPenWild=1; //用于设置画笔的宽度,初始宽度为1

PenStyle=PS_SOLID; //用于存储画笔风格,初始风格为实线,见表5.3 }

(9)在MainFrm.h的public:里加:

CToolBar *m_pColorToolbar; //定义颜色工具条类指针 (10)在MainFrm.cpp里的构造函数里这样加:

CMainFrame::CMainFrame():m_pColorToolbar(0)

{ // TODO: add member initialization code here }

说 明

“:”表示其后面列表称为成员初始化列表。这里表示:颜色工具条类指针对象初始状态为0

(11)为该项目增加一对话框: Insert?Resource(或Ctrl+R)?Dialog?New 1)将对话框本身的ID改为:IDD_DIALOG_WILD 2)拖过来Edit Box改ID为:ID_EDIT_WILD

注意:属性General里置好Visible和Tab Stop

3)拖来一个Spin旋转按钮控件放在EDIT右边;改ID为:IDC_SPIN_WILD 同样置好Visible和Tab Stop,在Styles下:Orientation(控件放置方向)栏置Vertical(垂直),在Alignment(控件在伙伴窗口的位置安排)栏置好Right(右边),右边置好Auto buddy(自动选择前一个窗口作为伙伴窗口)和Set buddy integer(使控件设置伙伴窗口,数值可十进制或十六进制)及Arrow keys(当按下向上和向下方向键时,也能增加或减少) (12)给对话框加类

View?ClassWizard?AddClass(或你双击对话框,问你要加新类吗,回答:“是”就行了)。在出现的对话框的Name栏写:CPENWILDDLG,基类:CDialog;下面保证是IDD_DIALOG_WILD。

为对话框类上的控件加成员变量:

IDC_EDIT_WILD int m_penwild //整型 IDC_EDIT_WILD CEdit m_cpenwild //编辑类型

IDC_SPIN_WILD CSpinButtonCtrl m_cwildspin //旋转按钮

16

注:可在对话框类PENWILDDLG.cpp的DDX下面加:DDV_MinMaxInt(pDX,m_penwild,1,100);

在视图头文件View.h里加:#include “PENWILDDLG.h”

(13)加映射消息及代码:

1)将菜单项消息映射ID_COLORS_MENU映射在CMainFrame主框架中并加代码: void CMainFrame::OnColorsMenu()

{ if(0==m_pColorToolbar)

{ m_pColorToolbar = new CToolBar; //为颜色工具条开辟一片单元

m_pColorToolbar->Create(this); //建一个Windows工具条并将其连接到此CTollBar中 m_pColorToolbar->LoadToolBar(IDR_MYTOOLBAR);//加载由资源编辑器创建的工具

//条资源,我们建好的这个工具条

m_pColorToolbar->EnableDocking(CBRS_ALIGN_ANY); DockControlBar(m_pColorToolbar); } }

说 明:

EnableDocking(CBRS_ALIGN_ANY):可使控制条停靠在父窗口的任何一边

参 数:指定框架窗口的哪一边可用做控制条的停靠点,可以是下列值中一个或多个:

CBRS_ALIGN_TOP允许停靠在客户区的顶部 CBRS_ALIGN_BOTTOM允许停靠在客户区的底部 CBRS_ALIGN_LEFT允许停靠在客户区的左边 CBRS_ALIGN_RIGHT允许停靠在客户区的右边 CBRS_ALIGN_ANY允许停靠在客户区的任何一边

CBRS_ALIGN_MULTI允许一个小框架窗口中存在多个浮动控制条

2)在主框架为ID_COLORS_MENU加映射消息UPDATE_COMMAND_UI(命令属性 ) void CMainFrame::OnUpdateColorsMenu(CCmdUI* pCmdUI) { if (0 == m_pColorToolbar) pCmdUI->SetCheck(FALSE); else if(m_pColorToolbar->IsWindowVisible() == TRUE) pCmdUI->SetCheck(TRUE); else }

pCmdUI->SetCheck(FALSE);

说 明:

1、SetCheck:设置命令用户界面元素为恰当选择状态,可用于工具条按钮(不确定状态)和菜单项。 参 数:0:即FALSE设置为不选择; 1:即TRUE设置为选择; 2:设置为不确定 2、IsWindowVisible:获得给定窗口的可视状态。 函数原型:BOOL IsWindowVisible(HWND hWnd); 参 数; hWnd:被测试窗口的句柄。

3、CCmdUI:仅用于ON_UPDATE_COMMAND(不是命令)处理函数中,一般用来设置特性等用。

17

如:pCmdUI->Enable(false); 是屏蔽菜单

3)为鼠标按下消息(视图类中View)加代码:

void CMyView::OnLButtonDown(UINT nFlags, CPoint point) { CRect r;

if(PenStarte==0)

{ StartPt.x=point.x; //起始位置的x坐标 StartPt.y=point.y; //起始位置的y坐标 }

else if(PenStarte==1) { Rect.left=point.x; //窗口左x坐标

Rect.top=point.y; //窗口顶y坐标 }

else{}

CView::OnLButtonDown(nFlags, point); }

4)为鼠标抬起消息(视图类中View)加代码:

void CMyView::OnLButtonUp(UINT nFlags, CPoint point) { if(PenStarte==0) //画笔状态为0 StartPt.x=-1; //起始位置x else if(PenStarte==1) //画笔状态为1

{

Rect.right=point.x; Rect.bottom=point.y; CDC *pDC=GetDC();

CPen RectPen(PS_DASH,1,m_colors);

pDC->SelectObject(&RectPen); //选择一个GDI绘图对象 pDC->MoveTo(Rect.left,Rect.top); pDC->LineTo(Rect.right,Rect.top); pDC->LineTo(Rect.right,Rect.bottom); pDC->LineTo(Rect.left,Rect.bottom); pDC->LineTo(Rect.left,Rect.top);

ReleaseDC(pDC); PenStarte=0;//画笔状态置0 }

CView::OnLButtonUp(nFlags, point); }

说 明:

1、GetDC:检索一指定窗口的客户区域或整个屏幕的显示设备上下文的句柄;以后可以在GDI函 数中用该句柄绘图。函数原型为: GetDC(HWND hWnd);

18

参 数:hWnd为检索的窗口的句柄,若为NULL,则检索整个屏幕的设备上下文环境。 2、CPen RectPen(PS_DASH,1,m_colors):定义画笔对象;虚线、宽度为 1、含有颜色值。 函数原型为:CPen(int nPenStyle,int nWidth,COLORREF crColor) 参 数:nPenSyle指定画笔风格;

nWidth指定画笔的宽度; crColor包含画笔的RGB颜色值;

3、SelectObject:将一个对象选入设备环境中,新选对象竟替代同一类型的先前对象,函数原型为: CPen *SelectObjiect(CPen *pPen)

参 数:pPen指向一个要选择的CPen对象的指针

4、MoveTo:将当前位置移动到x和y参数(或point参数MoveTo(POINT point))指定的点,函数原型为:MoveTo(int x,int y)

参 数:x新位置的逻辑x坐标;

y新位置的逻辑y坐标;

5、LineTo:从当前位置到由x和y(或point)指定的端点(但不包括此端点)画线,函数原型为: BOOL LineTo(int x,int y)

参 数:x 线段端点的逻辑x坐标;

y 线段端点的逻辑y坐标;

6、ReleaseDC:释放设备上下文环境(DC)供其他应用程序使用。函数的效果与设备上下文环境类型有关。它只释放公用的和设备上下文环境,对于类或私有的则无效, 函数原型为: int ReleaseDC(HWND hWnd, HDC hdc); 参 数:

hWnd:指向要释放的设备上下文环境所在的窗口的句柄。 hdc:指向要释放的设备上下文环境的句柄。

5)为鼠标移动消息(视图类中View)加代码:

void CMyView::OnMouseMove(UINT nFlags, CPoint point) { CClientDC dc(this); dc.SelectObject(&Pen);

EndPt.x=point.x; //终点x坐标 EndPt.y=point.y; //终点y坐标

if(StartPt.x>=0) //起始x坐标大于0

{ dc.MoveTo(StartPt.x,StartPt.y);//从起点画线 }

dc.LineTo(EndPt);//到指定的端点画线 StartPt.x=EndPt.x;//端点x再当起点x StartPt.y=EndPt.y; //端点y再当起点y }

CView::OnMouseMove(nFlags, point);

说 明:

CClientDC dc(this):构造CClientDC对象,则设备环境的映射区域限于客户区域,不能在客户区域外绘图。原点(0,0)在客户区左上角。this 是一个指向CMyView对象的指针。如果创建

19

CWindowDC对象,则设备环境的映射区域为整个窗口(包括标题栏、状态栏、窗口边框等)。原点(0,0)在整个窗口的左上角。 注 意:

1、视图窗口没有非客户区域 2、视图窗口覆盖在框架窗口之上

6)完成自定义充填函数(View)加代码(全用手写): void CMyView::FillMyRect() { CDC *pDC=GetDC(); CBrush Brush; //画刷

Brush.CreateSolidBrush(m_colors); //用指定实颜色初始化画刷。参数:画刷颜色 pDC->FillRect(&Rect,&Brush); }

ReleaseDC(pDC);// 释放设备上下文环境(DC)供其他应用程序使用

说 明:

FillRect(&Rect,&Brush):用给定画刷填充指定矩形,包括左边和上部边界,但不填充右边和底 部边界,函数原型为:void FillRect(LPCRECT lpRect,CBrush *pBrush) 参 数:

lpRect指向一个RECT结构,该结构中包含要填充矩形的逻辑坐标可为其传递一个CRect对象。 pBrush标识用于填充矩形的画刷。

7)完成自定义颜色选取对话框的实现(View)函数代码 (全用手写) : void CMyView::MyColorsDlg()

{ CColorDialog dlg; //颜色对话框类对象 if(IDOK==dlg.DoModal()) { m_colors=dlg.GetColor();

Pen.DeleteObject();//删除GDI对象,对象使用的所有系统资源都会被释放 Pen.CreatePen(PenStyle,1,m_colors); } }

说 明:

1、GetColor:在调用DoModal之后调用,用以获取用户选中颜色的信息。返回值:一个COLORREF值,其中包括颜色对话框中选中的颜色的RGB信息。

2、CreatePen(PenStyle,1,m_colors):用指定风格、宽度和颜色初始化一支画笔,函数原型为: BOOL CreatePen(int nPenStyle,int nWidth,COLORREF crColor);

8)完成自定义选择画笔宽度对话框的初始化(View)函数代码(全用手写): void CMyView::WildDlg() { CPENWILDDLG Dlg; if(IDOK==Dlg.DoModal()) MyPenWild=Dlg.m_penwild; }

9)完成自定义按钮的消息映射(View)函数加代码(全用手写):

20

void CMyView::OnPenChioce(UINT nID) { switch(nID)

{ case ID_BUTTONRED:m_colors=RED;break;

case ID_BUTTONGREEN:m_colors=GREEN;break; case ID_BUTTONBLUE:m_colors=BLUE;break; case ID_BUTTONDASH:PenStyle=PS_DASH;break; case ID_BUTTONSOLID:PenStyle=PS_SOLID;break; case ID_BUTTONRECT:PenStarte=1;break; case ID_BUTTONFILL:FillMyRect();break;

case ID_BUTTON_PENCOLORS:MyColorsDlg();break; case ID_BUTTON_PENWILD:WildDlg();break; }

Pen.DeleteObject();

Pen.CreatePen(PenStyle,MyPenWild,m_colors); }

(14)编译运行,如图5.6.3和5.6.4 所示。

图5.6.3画线和填充色、弹出调色板设置颜色

图5.6.4 画笔宽度为20绘制的粗线

21

5.7 多种图形的绘制

在实际工作中,经常要遇到绘制各种不同图形的情况,无论绘制什么图形,通常都需要创建画笔和画刷,然后调用CDC类中的绘图函数。这些绘图函数包括画点、线、矩形、多边形、圆弧、椭圆、扇形、曲线等。下面我们只通过3个实例,了解它们的绘制方法。

例5.10 绘制2个不同填充形式的五角星 (1)建一个单文档的应用程序,名为:五角星

(2)在视图类的实现文件(View.cpp)Draw函数中加下列代码: void CMyView::OnDraw(CDC* pDC) {

CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);

POINT pt[5]={{47,10},{30,90},{90,35},{10,30},{75,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;//x+80第二个五星的点 pDC->SetPolyFillMode(WINDING);

pDC->Polygon(pt,5);

pDC->SelectObject(oldbrush); brush.DeleteObject(); }

(3)编译运行,见图5.7.1所示。 图5.7.1 绘制五角星及填充色

说 明:

SetPolyFillMode(ALTERNATE):是CDC类的一个成员函数,用于设置填充模式,它的参数可以是ALTERNATE和WINDING。对於ALTERNATE方式,可以设想从一个无穷大的封闭区域内部的点画线,只有假想的线穿过了奇数条边界线时,才填入封闭区域。这就是填入了星的角而中心没被填入的原因。五角星的例子使得WINDING方式看起来比实际上更简单一些。在绘制单个的多边形时, 大多数情况下,WINDING方式会填入所有封闭的区域。但是也有例外。在WINDING方式下要确定一个封闭区域是否被填入,仍旧可以设想从那个无穷大的区域画线。如果假想的线穿过了奇数条边界线,区域就被填入,这和ALTERNATE方式一样。如果假想的线穿过了偶数条边界线,则区域可能被填入也可能不被填入。如果一个方向的边界线数不相等,就填入区域。

例5.11 绘制一条由点连起的曲线 1、建一个单文档的应用程序,绘曲线

2、在Viwe.cpp的OnDraw(CDC *pDC)函数里加如下代码: void CMyView::OnDraw(CDC* pDC)

22

{ CMyDoc* 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); //是取得客户区矩形区域

//下面是通过朝它的中心移动边,以缩小CRect。函数原型是void DeflateRect(int x,int y)。 //参数x指定要向左或向右移动CRect边的数量,y指向要向上或向下移动CRect边的数量。

rc.DeflateRect(50,50);

int gridXnums=10; //定义网格变量x是10 int gridYnums=8; //定义网格变量y是8 int dx=rc.Width()/gridXnums;//宽度/x int dy=rc.Height()/gridYnums;//高度/y

//下面这条语句是矩形构造函数CRect ::CRect(int l,int t,int r,int b)

//参数:l指定CRect的左边位置,t指定CRect的上边位置,r指定CRect的右边位置, // b指定CRect的底边位置。这里是:左边位置,上边位置,右边位置(左边位置+宽度* //网格变量10),底边位置(上边位置+高度*网格变量8),实际是画笔行走范围。

CRect gridRect(rc.left,rc.top,rc.left+dx *gridXnums,rc.top+dy *gridYnums);

//下面这条语句是定义画笔对象CPen::Cpen(int nPenStyle,int nWidth,COLORREF crColor) //参数:nPenStyle指定画笔风格; nWidth指定画笔宽度;crColor包含画笔的RGB颜色值

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); //x方向开始画 pDC->LineTo(gridRect.left+i *dx,gridRect.top); //到端点 }

for(int j=0;j<=gridYnums;j++)

{ pDC->MoveTo(gridRect.left,gridRect.top+j *dy); //y方向开始画 pDC->LineTo(gridRect.right,gridRect.top+j *dy); //到端点 }

pDC->SelectObject(oldPen); gridPen.Detach();

gridPen.CreatePen(0,0,RGB(0,0,200)); 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;

23

for(i=0;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);

pDC->SelectObject(oldPen); pDC->SelectObject(oldBrush); }

(3)编译运行,见图5.7.2所示。

图5.7.2 运行结果

例5.12 课程成绩分布直方图

表示一个班级某门课程的成绩分布,用一个直方图来反映<60、60~69、70~79、80~89以及>90的5个分数段的人数,它需要绘制5个矩形,相邻矩形的填充样式还要有所区别,并且还需要显示各分数段的人数。 1、建一个单文档的应用程序名为:直方图

2、为视图类添加一个自定义的void类型成员函数(ClassView->右键单击CMyView->…): DrawScore(CDC *pDC, float *fScore, int nNum) 并加如下代码: void CMyView::DrawScore(CDC *pDC, float *fScore, int nNum) //pDC是CDC类指针,fScore是成绩数组指针,nNum是学生人数 { int nScoreNum[]={0,0,0,0,0}; //各成绩段的人数的初始值 //下面是统计各分数段的人数

24

for(int i=0;i

{ int nSeg=(int)(fScore[i]/10); //取数的\位上的值 if(nSeg<6)nSeg=5; //<60分

if(nSeg==10) nSeg=9; //如为100分,即算为>90分数段

nScoreNum[nSeg-5]++; //各分数段计算

}

int nSegNum=sizeof(nScoreNum)/sizeof(int); //计算有多少个分数段 int nNumMax=nScoreNum[0];

for(i=1;i

}

CRect rc; //矩形对象

GetClientRect(rc); //获得当前窗口的客户区大小 rc.DeflateRect(40,40); //缩小矩形大小 int nSegWidth=rc.Width()/nSegNum; //计算每段的宽度

int nSegHeight=rc.Height()/nNumMax; //计算每段的单位高度 COLORREF crSeg=RGB(0,0,192); //定义一个颜色变量 CBrush brush1(HS_FDIAGONAL,crSeg); //画刷样式308页4 CBrush brush2(HS_BDIAGONAL,crSeg); // ...... 1

CPen pen(PS_INSIDEFRAME,2,crSeg); //画笔样式内框线307最后 CBrush *oldBrush=pDC->SelectObject(&brush1);//将brush1选入设备环境 CPen *oldPen=pDC->SelectObject(&pen);//将pen选入设备环境 CRect rcSeg(rc);

rcSeg.right=rcSeg.left+nSegWidth; //左加宽赋给右 CString strSeg[]={\ CRect rcStr;

for(i=0;iSelectObject(&brush2); else pDC->SelectObject(&brush1);

rcSeg.top=rcSeg.bottom-nScoreNum[i] *nSegHeight-2;//计算每段矩形的高度 pDC->Rectangle(rcSeg); if(nScoreNum[i]>0) { CString str; str.Format(\人\ pDC->DrawText(str,rcSeg,DT_CENTER|DT_VCENTER|DT_SINGLELINE); }

rcStr=rcSeg;

25

rcStr.top=rcStr.bottom+2; rcStr.bottom+=20;

pDC->DrawText(strSeg[i],rcStr,DT_CENTER|DT_VCENTER| DT_SINGLELINE); rcSeg.OffsetRect(nSegWidth,0); //右移矩形 }

pDC->SelectObject(oldBrush);//恢复原来的画刷属性 pDC->SelectObject(oldPen); //恢复原来的画笔属性 }

3、在OnDraw(CDC *pDC)函数中添加下列代码: void CMyView::OnDraw(CDC* pDC) { CMyDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

float fScore[]={66,82,79,74,86,82,67,60,45,44,77,98,65, 90,66,76,66,62,83,84,97,43,67,57,60,60,71,74,60,72, 81,69,79,91,69,71,81};

DrawScore(pDC,fScore,sizeof(fScore)/sizeof(float)); }

(4)编译运行,结果见图5.7.3所示。

5.8 字体CFont类

CFont对象封装了一种Windows GDI

字体,并且提供了用于操作CFont对象的 若干方法。CFont类用来决定绘图文本的 字体,必须先创建字体,然后将其选进要

进行文本输出的DC,就可以利用文本输

出函数显示该字体形式的文本内容了。 图5.7.3 课程成绩分布直方图 创建字体由2步完成:

(1)定义一个CFont类的对象,应用程序框架会调用构造函数,如:CFont myFont; (2)调用CFont类,创建字体函数,从而将CFont对象与Windows的某种字体相关联。

例5.13 字体显示

(1)创建一个单文档应用程序,名为:字体显示

(2)在视图类的实现文件(View.cpp)的OnDraw函数中,添加下列代码: void CMyView::OnDraw(CDC* pDC) { CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);

pDC->SetBkColor(RGB(240,240,250));//设置背景颜色 pDC->SetTextColor(RGB(255,0,0));//设置文本颜色 int ny=260; int ndl=40;

26

for(int i=32;i>=20;i-=4) { FontOut(pDC,ny,i,ndl);

ndl +=300; } }

(3)添加自定义函数FontOut()

点击ClassView打开项目工作区->用右键单击CMyView ->选择Add Member Function添加一个类型为void名为:FontOut(CDC *pDC,int &nHeight,int nPoints,int dline)的自定义函数

(4)在FontOut(CDC *pDC,int &nHeight,int nPoints,int dline)函数中加代码: void CMyView::FontOut(CDC *pDC, int &nHeight, int nPoints, int dline) { TEXTMETRIC textM;//定义文本结构TEXTMETRIC变量 CFont font;//定义CFont类对象 CString str; //创建字体

font.CreateFont(-nPoints,0,dline,0,400,FALSE,

FALSE,0,ANSI_CHARSET, OUT_DEFAULT_PRECIS,

CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,

DEFAULT_PITCH|

FF_SWISS,\宋体\

//将创建的字体选入内存DC,同时暂时挤出并保

//存原字体

CFont *poldfont=(CFont *)pDC-> 图5.8.1 字体显示程序运行结果

SelectObject(&font);

pDC->GetTextMetrics(&textM); //获取文本信息 str.Format(\这是%d点阵宋体字\ pDC->TextOut(20,nHeight,str);

nHeight -=textM.tmHeight + textM.tmExternalLeading; pDC->SelectObject(poldfont); //恢复内存DC中原有的字体

}

(5)编译运行,结果见图5.8.1所示。

1、字体的属性和创建

字体的属性有很多,但其主要属性有3个:字样、风格、尺寸。

字样:是字符书写和显示时表现出的特定模式,如:汉字的宋体、楷体、仿宋、黑体、

隶书、幼圆等多种字样。

风格:主要表现为字体粗细和是否倾斜等特点。

尺寸:是用于指定字符所占区域的大小,通常用字符高度来描述,可以取mm或英寸作为

27

单位,但为了直观起见,也常常采用一种称为“点”的单位,一点约折合为1/72英寸。

为了方便用户创建字体,系统定义一种“逻辑字体”,它是应用程序对于理想字体的一种描述方式。在使用逻辑字体绘制文字时,系统会采用一种特定的算法把逻辑字体映射为最匹配的物理字体(实际安装在操作系统中的字体)。逻辑字体的具体属性可由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 nOutPrecision,//字体输出的结果和要求的匹配程度 BYTE nClipPrecision,//如何剪裁落于剪裁区之外的字符 BYTE nOuality, //字体属性匹配的精确度 BYTE nPitchAndFamily,//字体间距和字体族 TCHAR lfFaceName[LF_FACESIZE],//字样名称 //……. }LOGFONT; 说 明:

在结构成员中:lfHeight表示字符的逻辑高度,这里的高度是字符的纯高度,当此值>0时,系统将此值映射为实际字体单元格的高度;当 =0时,系统将使用默认的值;当 <0时,系统将此值映射为实际的字体高度。

lfEscapement表示字体的倾斜矢量与设备的x轴之间的夹角(以1/10度为计量单位),该倾斜矢量与文本的书写方向是平行的。

lfOrientation表示字符基准线与设备的x轴之间的夹角(以1/10度为计量单位)。

lfWeight表示字体的粗细程度,取值范围是从0---1000(字符笔画从细到粗)。例如:400为常规情况,700为粗体。

根据定义的逻辑字体,用户可以调用CFont类的CreateFontIndirect函数创建文本输出所需要的字体。

下例中的逻辑字体创建是利用CreateFont()函数实现的,函数原型如下:

BOOL CreateFont (

int nHeight, //字体的逻辑高度,0代表默认值 int nWidth, //字体的平均逻辑宽度,0代表默认值 int nEscapement, //倾斜角度 int nOrientation, //字体朝向角度 int fnWeight, //字体的权重

28

DWORD fdwItalic, //是否为斜体 DWORD fdwUnderline, //是否有下划线 DWORD fdwStrikeOut, //字体是否凸出

DWORD fdwCharSet, //字体所属字符集,默认为DEFAULT_GHARSET DWORD fdwOutputPrecision, //输出精度,通常为OUT_DEFAULT_PRECIS DWORD fdwClipPrecision, //显示精度

DWORD fdwQuality, //输出质量,通常为DEFAULT_QUALITY DWORD fdwPitchAndFamily, //选择的字符集 LPCTSTR lpszFace //字符体名称的字符串 );

例5.14 设计不同大小字体并输出

1、建一个单文档的应用程序,名为:创建字体

2、在视图类View.cpp的OnDraw函数中加如下代码: void CMyView::OnDraw(CDC* pDC) { CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); int cHeight;

UINT position=0;

for(UINT x=0;x<6;x++) { CFont font;

cHeight=16+x*8;

font.CreateFont(-cHeight, //字体高度,小于0转换为设备单位,绝对值与可用字体的字

//符高度匹配,大于0转换设备单位匹配可用字体的单元高度,等于0使用一种合理的默认大小

0, //字符平均宽度取默认值 0, //文本行角度为0,水平 0, //字符角度为0,正立 FW_NORMAL, //正常字体 FALSE, //不倾斜 FALSE, //不加下划线 FALSE, //不加删除线 ANSI_CHARSET, //字符集 OUT_DEFAULT_PRECIS, //输出精度 CLIP_DEFAULT_PRECIS, //显示精度 DEFAULT_QUALITY, //输出质量 DEFAULT_PITCH|FF_MODERN, //选择的字符集

//字符体名称的字符串,Times New Roman一种优秀的电脑字型

\

CFont *oldFont=pDC->SelectObject (&font); position+=cHeight;

29

}

pDC->TextOut(20,position,\时代先锋\pDC->SelectObject(oldFont);

}

(3)编译运行,结果见图5.8.2所示。

图5.8.2 设计不同大小字体 图5.8.3 字体对话框

2、使用字体对话框

CFontDialog类为我们提供了字体及其文本颜色选择的通用字体对话框,如图5.8.3所示。它的构造函数如下:

CFontDialog(LPLOGFONT lplfInitial=NULL,DWORD dwFlags=CF_EFFECTS|

CF_SCREENFONTS,CDC *pdcPrinter=NULL,CWnd *pParentWndNULL);

参 数:

lplfInitial:是一个LOGFONT结构指针,用于设置对话框最初的字体特性;dwFlags: 指定选择字

体的标志;

pdcPrinter:用于表示打印设备环境指针;

pParentWnd:用于表示对窗口的父窗口指针。当字体对话框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(); //判断是否是斜体

例5.15 显示文档内容并使用字体对话框改变显示的字体大小

30

(1)用MFC AppWizard创建单文档应用程序,名为:设置字体。在创建的第6步将视图的基类选择为:CScrollView。由于视图客户区往往显示不了文档的全部内容,因此需要视图支持滚动操作。

(2)在文档类CMyDoc.h文件的public下,添加CStringArray m_strContents; 用来将读取的文档内容保存。

(3)在CMyDoc::Serialize函数中添加读取文档内容的代码: void CMyDoc::Serialize(CArchive& ar) { if (ar.IsStoring())

{ // TODO: add storing code here } else

{ CString str; m_strContents.RemoveAll(); while(ar.ReadString(str)) { m_strContents.Add(str); } } }

(4)为视图类CMyView添加成员变量(public下):

LOGFONT m_lfText; //用来保存当前所使用的逻辑字体。

(5)在视图类的实现文件CMyView.cpp的构造函数中添加m_lfText的初始化代码: CMyView::CMyView()

{ memset(&m_lfText,0,sizeof(LOGFONT)); m_lfText.lfHeight=-12;

m_lfText.lfCharSet=GB2312_CHARSET; strcpy(m_lfText.lfFaceName,\宋体\}

(6)用MFC ClassWizard为视图类(CMyView)添加WM_LBUTTONDBLCLK(双击鼠标)的消息映射函数,并增加代码:

void CMyView::OnLButtonDblClk(UINT nFlags, CPoint point) { CFontDialog dlg(&m_lfText); if(dlg.DoModal()==IDOK)

{ dlg.GetCurrentFont(&m_lfText); Invalidate(); }

CScrollView::OnLButtonDblClk(nFlags, point); }

至此,当双击鼠标左键后,就会弹出“字体”对话框,从中可改变字体的属性。单击“确定”按钮后,程序就执行CMyView::OnDraw函数中的代码: (7)在视图类执行文件CMyView.cpp的OnDraw函数中加下列代码: void CMyView::OnDraw(CDC* pDC)

31

{

CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);

//创建字体

CFont cf;

cf.CreateFontIndirect(&m_lfText); CFont *oldFont = pDC->SelectObject(&cf);

//计算每行的高度

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;im_strContents.GetSize();i++) { str=pDoc->m_strContents.GetAt(i); pDC->TabbedTextOut(0,y,str,1,&tab,0); str=str + \ //多计算一个字符宽度 lineSize=pDC->GetTabbedTextExtent(str,1,&tab); if(lineMaxWidth

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所示。 3、常用文本输出函数

文本的最终输出不仅依赖于文本的字体,而且还和文本的颜色,对齐方式等有很大关系。CDC类提供了4个输出文本的成员函数,它们是:

TabbedTextOut:绘制的文本是一个多列的列表形式,启用制表位,可以使绘制出来的文

本效果更佳。

32

DrawText: 在一个矩形区域绘制多行文本。

ExtTextOut:文本和图形结合紧密,字符间隔不等,并要求有背景颜色或矩形剪裁特性。 TextOut:一般没什么特殊要求,采用此函数显得简单。

图5.8.4 字体设置程序运行结果

它们的函数原型如下:

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,int nTabPositions, LPINT lpnTabStopPositions,int nTabOrigin);

TabbedTextOut函数也是用当前字体在指定位置显示一个文本,但它还根据指定的指标位(Tab)设置相应字符位置,函数成功时返回输出文本的大小。

参 数:

nTabPositions表示lpnTabStopPositions数组的大小

lpnTabStopPositions表示多个递增的制表位(逻辑坐标)的数组

nTabOrigin表示制表位x方向的起始点(逻辑坐标),如果nTabPositions为0, lpnTabStopPosition为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用于指定文本绘制时的参考矩形,它本身并不显示

33

nFormat表示文本的格式,它可以是下列的常用值之一或”|”组合

DT_BOTTON 下对齐文本,该值还必须与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 自动换行

注 意:DT_TABSTOP与上述DT_CALCRECT、DT_EXTERNALLEADING、DT_NOCLIP及 DT_NOPREFIX不能组合。 说 明:

默认时,上述文本输出函数即不使用,也不更新“当前位置”。若要使用和更新“当前位置”, 则必须调用SetTextAlign,并将参数nFlags设置为TA_UPDATECP。 使用时,最好在文本输出前用MoveTo将当前位置移动至指定位置后,再调用文本输出函数。这样,文本输出函数参数中x、y或矩形的左边才会被忽略。

例5.16 绘制文本的简单示例

(1) 建一个单文档的应用程序:绘制文本

(2) 在视图类View.cpp的OnDraw函数中添加下列代码: void CMyView::OnDraw(CDC *pDC)

{ CRect rc(10,10,200,140); //左,顶,右,底成员的值

pDC->Rectangle(rc); //矩形和圆角矩形的绘制,310页说明 //水平居中,垂直居中,指定文本的基准线为参考点

pDC->DrawText(\单行文本居中\ rc.OffsetRect(200,0);//将矩形向右偏移200 pDC->Rectangle(rc);

int nTab=40; //将一个Tab位的值指定为40个逻辑单位 //使用自定义的停止位(Tab)

pDC->TabbedTextOut(rc.left,rc.top,\绘制\\tTab\\t文本\\t示例\ nTab=80; //将一个Tab位的值指定为80个逻辑单位 //使用自定义的停止位(Tab)

pDC->TabbedTextOut(rc.left,rc.top+20,\绘制\\tTab\\t文本\\t示例\

34

//使用默认的停止位

pDC->TabbedTextOut(rc.left,rc.top+40,\绘制\\tTab\\t文本\\t示例 \

}

(3) 编译运行,结果见图5.8.5所示。 4、文本格式属性

文本的格式属性通常包括文本颜 色、对齐方式、字符间隔以及文本调 整等。在绘图设备环境中,默认的文 本颜色是黑色,而文本背景色为白色, 且默认的背景模式是不透明方式

(OPAQUE).在CDC类中,SetTextColor、 图5.8.5 绘制文本程序运行结果

SetBkColor和SetBkMode函数是分别用

于设置文本颜色、文本背景色和背景模式,而与之相对应的GetTextColor、GetBkcolor和GetBkMode函数则是分别获取这3项属性的。它们的原型如下: 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;

这两个函数中用到的文本对齐标志如表5.7所示。

表5.7 文本对齐标志

对齐标志 含义 TA_BASELINE 以字体的基准线作为上、下对齐方式 TA_BOTTOM 以文本外框矩形的底边作为上、下对齐方式 TA_CENTER 以文本外框矩形的中点作为左、右对齐方式 TA_LEFT 以文本外框矩形的左边作为左、右对齐方式 TA_NOUPDATECP 不更新当前位置

TA_RIGHT 以文本外框矩形的右边作为左、右对齐方式 TA_TOP 以文本外框矩形的顶边作为上、下对齐方式 TA_UPDATECP 更新当前位置

如上文本对齐标志可分为3组:

35

TA_LEFT、TA_CENTER和TA_RIGHT确定水平方向的对齐方式。

TA_BASELINE、TA_BOTTON和TA_TOP确定上、下方向的对齐方式。 TA_NOUPDATECP和TA_UPDATECP确定当前位置的更新标志。 这3组标志中,组与组之间的标志可使用“|”操作符。 5、 计算字符的几何尺寸:

在打印和显示某段文本时,有必要了解字符的高度计算及字符的测量方式,才能更好地控制文本输出效果。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;

通常,字符的总的高度是用tmHeight和tmExternalLeading的总和来表示的。但对于字符宽度的测量除了上述参数tmAveCharWidth和tmMaxCharWidth外,还有CDC中的相关成员函数GetCharWidth、GetOutputCharWidth、GetCharABCWidths。 在CDC类中,计算字符串的宽度和高度的函数主要有2个:

GetTextExtent函数和GetTabbedTextExtent函数。前者适用于字符串没有制表符的情况,而后者适用于含有制表符的字符串。它们的原型如下:

CSize GetTextExtent(LPCTSTR lpszString,int nCount)const; CSize GetTextExtent(const CString &str)const;

CSize GetTabbedTextExtent(LPCTSTR lpszString,int nCount, int nTabPositions,LPINT lpnTabStopPosition)const; CSize GetTabbed TextExtent(const CString &str,

int nTabPositions,LPINT lpnTabStopPositions)const;

参 数:

lpszString和str表示要计算的字符串 nCount表示字符串的字节长度

nTabPositions表示lpnTabStopPositions数组的大小

lpnTabStopPositions表示多个递增的制表位(逻辑坐标)的数组。函数返回当前设备环境下的一行字

符串的宽度(CSize的cx)和高度(CSize的cy)。

5.9 位图、图标与光标

36

基于Windows的应用程序是离不开图形图象的,这些图象最为常见的则是Windows位图,位图实际上是一些和显示像素相对应的位阵列。它可以用于保存、加载和显示。图标、光标也是一种位图,但它们有各自的特点。例如,同一个图标或光标对应于不同的显示设备时,可以包含不同的图象,对于光标而言,还有“热点”的特性。下面介绍如何用图形编辑器创建和编辑图标和光标,并着重讨论它们在程序中的控制方法。

5.9.1 使用图形编辑器

图形编辑器可以创建和编辑任何位图格式的图象资源,除工具栏外,它还用于位图、图标和光标。它的功能很多,如提供一套完整的绘图工具来绘制256色的图形,进行位图的移动和复制以及含有若干个编辑工具等。下面讨论如何创建新的图标和光标、选用或定制显示设备和设置光标“热点”等。

1、 创建一个新的图标和光标

(1)建一个单文档的应用程序名为:光标与图标

(2)insert ->Resource->选择Cursor(光标)资源类型->单击New->系统为程序添加一个新的光标,同时在开发环境右侧出现图形编辑器。如图5.9.1所示是添加光标后的图形编辑器。以同样的方法再添加一个图标(Icon),ID为IDI_ICON1。

新设备按钮 New Device Image

图5.9.1 添加图标后的图形编辑器

在创建新图标和光标时,图形编辑器首先创建的是一个适合于VGA环境中的图象。开始时它以屏幕色(透明方式)来填充。对于创建的新光标,其“热点”被初始化为左上角的点,坐标为(0,0)。默认情况下,图形编辑器所支持的显示设备如表5.8所示。

表5.8 图形编辑器所支持的显示设备

设 备 颜色数目 宽 度 高 度

单显模式(Monochrome) 2 32 32 小设备模式(Small) 16 16 16 标准模式(Standard) 16 32 32 大模式(Large) 256 48 48

37

由于同一个光标或图标在不同的显示环境中包含有不同的图象,因此,在创建光标 或图标前必须事先指定目标显示设备。这样,在打开所创建的图形资源时,与当前设备最相吻合的图象被打开。 2、 选用和定制显示设备

点开Icon(图标)双击IDI_ICON1,在图形编辑器工作窗口的控制条上,将鼠标放在右侧的“新设备按钮”上,出现New Device Image(Ins)字样,单击此按钮-->系统弹出“图象设备选择”对话框,如图5.9.2所示。单击Custom(定制)按钮->在弹出的对话框中定制显示设备,如图5.9.3所示,这里就可指定新设备的大小和颜色了。

图5.9.2 “图象设备选择”对话框 图5.9.3 定制显示设备

3、 设置光标热点

Windows系统借助光标”热点”来确定光标实际位置。在图形编辑器的控制条上或光标属性对话框中都可以看到当前的光标“热点”位置,如图5.9.4所示是添加光标后的图形编辑器。

控制条 设置热点按钮

图5.9.4 添加光标后的图形编辑器

默认时,光标热点是图象左上角(0,0)的点,当然,这个热点位置是可以重新指定的。将鼠标放在控制条最后的“设置热点按钮”上,出现“Set Hotspot”字样,单击它一下,之后把光标移到下面的图形上,随便点一下(像素点),控制条上的光标位置便随之改变,显示出所点中的像素点的坐标。

回过来,再点一下“设置热点按钮”,再把光标移到下面的图形上点一下,控制条的光标位置又改变了,不断这样,便不断改变。

38

5.9.2 位图

Windows的位图有两种类型:一种是GDI位图,另一种是DIB位图。GDI位图是由MFC中的CBitmap类来表示的,是与设备有关的位图。DIB是与设备无关的位图,它自带颜色,并任何运行Windows的机器都可以处理DIB,比GDI优越得多。 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); 该函数从应用程序中调如一个位图资源(由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)的位图对象。由于位图不能直接显示在实际设备中,因此对于GDI位图的显示则必须遵循下列步骤: (1)调用CBitmap类的CreateBitmap、CreateCompatibleBitmap以及CreateBitmapIndirect函数创建一个适当的位图对象。

(2)调用CDC::CreateCompatibleDC函数创建一个内存环境,以便位图在内存中保存下来,并与指定设备(窗口设备)环境相兼容。

(3)调用CDC::SelectObject函数将位图对象选入内存设备环境中。

(4)调用CDC::BitBlt或CDC::StretchBlt函数将位图复制到实际设备环境中。 (5)使用之后,恢复原来的内存设备环境。

例5.17:显示BMP位图文件,调用一个位图并在视图中显示 1、建一个SDI单文档应用程序:显示位图

2、开始?搜索?找到一个.bmp文件?import到程序里?ID为 IDB_BITMAP1

39

Insert?Resource?Bitmap?Import?*.*? *.bmp文件,将.bmp文件插到程序中。 3、在 OnDraw(CDC *pDC)函数里加以下代码: void CMyView::OnDraw(CDC* pDC) {CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CBitmap m_bmp;//位图类对象

//加载位图资源,就是我们拷贝到本帐号的图形

m_bmp.LoadBitmap(IDB_BITMAP1); BITMAP bm; //位图结构体变量

m_bmp.GetObject(sizeof(BITMAP),&bm); CDC dcMem;

//创建一个与pDC指定的只支持光栅操作的设

//备兼容的内存设备环境 图5.9.5 在视图中显示位图

dcMem.CreateCompatibleDC(pDC);

CBitmap *pOldbmp=dcMem.SelectObject(&m_bmp);

pDC->BitBlt(60,60,bm.bmWidth,bm.bmHeight,&dcMem,0,0,SRCCOPY); dcMem.SelectObject(pOldbmp); }

4、运行后,在窗口上就出现你拷过来的位图(这里是通过位图的ID显示位图),如图5.9.5。

通过上述代码可以看出:位图的最终显示是通过调用CDC::BitBlt函数来完成的。 除此之外,也可以使用CDC::StretchBlt函数。这2个函数的区别在于:StretchBlt函数可以对位图进行缩小或放大,而BitBlt则不能,但BitBlt的显示更新速度较快。 它们的原型如下: BOOL BitBit(int x,int y,int nWidth,

int nHeight,CDC *pSrcDC,int xSrc, int ySrc,DWORD dwRop);

BOOL StretchBlt(i nt 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函数还比BitBit函数多2个:nSrcWidth、nSrcHeight参数,它们是用于表示源 矩形的逻辑宽度和高度。dwRop的光栅操作方式见MFC类库大全322页。

5.9.3 图标

图标应用很广,在Winddows中,一个应用程序允许有2种尺寸的图标来标明自己;一种是普通图标,也称为大图标,它是32*32的位图,另一种是小图标,它是大小为16*16

40

的位图。在桌面上,应用程序总是用大图标作为自身的图形标识,而一旦启动后,其窗口的左上角和任务栏的程序按钮上就显示出该应用程序的小图标,图标还用于反映某些程序状态等。

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);

参 数:hIcon用于指定要删除的图标句柄。

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);

参 数:hWnd用于指定窗口类句柄

dwNewLong用于指定新的32位值

nIndex用于指定与WNDCLASSEX结构相关的索引,它可以是下列值之一: GCL_HBRBACKGROUND 窗口类的背景画刷句柄 GCL_HCURSOR 窗口类的光标句柄 GCL_HICON 窗口类的图标句柄

41

GCL_MENUNAME 窗口类的菜单资源名称

4、图标实例

例5.18 改变应用程序图标,呈现动态效果 (1)建一个单文档的应用程序,名为:改变图标

(2)添加6个图标资源:Insert->Resource->选中Icon->New,这样插入6个图标资源。通过图象设备选择对话框,Device:选择Small(16*16)作为图标的设备类型,图标资源ID号分别为默认的IDI_ICON1~IDI_ICON6。(在图形编辑器工作窗口的控制条上,单击“新设备

按钮”,出现图象设备选择对话框New Icon Image,选中Small(16*16)双击,见图5.9.6->OK出现5.9.7图形资源)。

图5.9.6 选中Small[16x16] 图5.9.7 Small[16x16] 图形资源

注 意:也可在“搜索”里找到6个*.ico的图形,Import到程序中,在右边图形编辑器工作窗口的控制条上Device: Standard(32*32)处下拉,置16*16 256 color,见图5.9.8所示。

(3)用图形编辑器将这6个图标资源,绘制不同的图形(如果有Import具体图形,此步不做)。

新设备按钮

图5.9.8 Import的图形

(4)为主框架CMainFrame添加一个成员函数void ChangeIcon(UINT nIconID)(在项目 工作区ClassView用右键点重CMainFrame类),用于切换应用程序的图标。并加下列代码: void CMainFrame::ChangeIcon(UINT nIconID)

{ HICON hIconNew=AfxGetApp()->LoadIcon(nIconID); HICON hIconOld=(HICON)GetClassLong(m_hWnd,GCL_HICON); if(hIconNew!=hIconOld) {

42

DestroyIcon(hIconOld);

SetClassLong(m_hWnd,GCL_HICON,(long)hIconNew);

RedrawWindow();//重绘窗口 } }

说 明:

1、HICON图标句柄; AfxGetApp()取应用程序实例指针;LoadIcon图标资源调入 2、GetClassLong:该函数返回与指定窗口相关的WNDCLASSEX结构的指定32位值。 函数原型:DWORD GetClassLong(HWND hWnd,int nlndex); 参 数:

hWnd:窗口句柄间接给出的窗口所属的类;

nlndex:指定与WNDCLASSEX结构相关的索引,它可以是下面值之一:

GCSW原子:获得一个唯一标识窗口类的原子值,该值与RegisterClassEx函数的返回值相同。 GCL_CBWNDEXTRA:获得与类中的每个窗口相关的额外窗口中内存空间的字节大小,进入

该存储空间的方法请参看GetWindwoLong。

GCL_HBRBACKGROUND:获得与类有关的背景刷子的句柄。 GCL_HCURSOR:获得与类有关的光标的句柄。 GCL_HICON:获得与类有关的图标的句柄。 GCL_HICONSM:获得与类有关的小图标的句柄。 GCL_HMOUDLE:获得注册该类的模块的句柄。

GCL_MENUNAME:获得菜单名字符串的地址,该字符串标识了与类有关的菜单资源。 GCL_STYLE:获得窗口类的风格位。

GCL_WNDRPOC:获得与类有关的窗口过程的地址。

返回值:如果函数成功,返回值是所需的32位值;如果函数失败,返回值为0。若想获得更多错误信息,请调用GetLastError函数。

3、WNDCLASSEX结构包含了Window类信息。它被RegisterClassEx和ClassInfoEx函数所使用 typedef struct _WNDCLASSEX

{ UINT cbSize;cbSize这个结构定义了大小,它的单位是字节,这个数就是sizeof(WNDCLASSEX)

在调用GetClassInforEx功能之前来确定这个数。

UINT style; style指定了类类型。这个数可以是类类型的任意组合

WNDPROC lpfnWndProc; 指向窗口程序。你必须使用CallWindowProc功能来调用窗口过程 int cbClsExtra; 指定了额外的字节,来分配给窗口类结构。系统初始化为0

int cbWndExtra; 指定了额外分配的字节数,来适应窗口实例。系统初始化为0。如果一个

应用程序使用WNDCLASSEX来注册一个对话框,那么它必须被设置为 DLGWINDOWEXTRA

HINSTANCE hInstance; 用来处理包含了窗口进程类的实例

HICON hIcon; 处理类按钮。这个成员必须是一个按钮资源的句柄。如果这个成员是NULL,

那么系统提供一个默认的按钮

HCURSOR hCursor; 处理光标类指针。这个成员必须是一个指向光标类的句柄。如果这个

成员是空,必须有一个应用程序来设定在进入该应用程序时候的光标形状

43

HBRUSH hbrBackground; 处理背景刷类。这个成员可以是一个物理的背景刷的句柄,或者

它是一个颜色值。颜色值一定是标准系统颜色中的一种(数值1必须被加到所 选择的颜色中)。如果得到了一个颜色值,你必须把它转化为HBRUSH类型的 一种:如:COLOR_MENU等19种。

LPCTSTR lpszMenuName; 指向一个不会被中断的字符组,它描述了类菜单的资源名,也

是资源文件的名字。

LPCTSTR lpszClassName; 指向一个不会被中断的字符组,它描述了菜单类的名字。类的名

字可以是任意的注册类名等,或者是任何一个预先定义的类名字

HICON hIconSm; 处理和窗口类相关联的一个小图标。如果这个成员是空,系统会搜索其

它的ICON。

} WNDCLASSEX, *PWNDCLASSEX;

(5)在CMainFrame::OnCreate函数最后添加计时器设置代码:

SetTimer(1,500,NULL); (6)用MFC ClassWizard为CMainFrame类添加WM_TIMER的消息影射函数,并增加

下列代码:

void CMainFrame::OnTimer(UINT nIDEvent) { static int icons[]={ IDI_ICON1,IDI_ICON2,IDI_ICON3,IDI_ICON4, IDI_ICON5,

IDI_ICON6 };

static int index=0; ChangeIcon(icons[index]); index++;

if(index>5)

index=0;

CFrameWnd::OnTimer(nIDEvent);

循环显示的动态效果 }

注 意:OnTimer函数的参数nIDEvent用于 表示发送WM_TIMER消息的计时器的标识值。

(7)用MFC ClassWizard为

CMainFrame类添加WM_DESTROY 的消息影射函数,并增加下列代码:

void CMainFrame::OnDestroy() { CFrameWnd::OnDestroy(); 图5.9.8 循环显示的动态效果 KillTimer(1); }

说 明:

代码中,KillTimer函数是CWnd类成员函数,用于停止WM_TIMER消息的传送,其函数参数 值用指定要停用的计时器标识值。

(8)编译运行,见应用程序左上角标题栏上6个图标循环显示的动态效果。显示速度 为每秒2桢。见图5.9.8所示。

44

例5.19 更换应用程序图标

(1)建一个单文档的应用程序:更换图标 (2)插入一个.ico图标图形:

Insert->Resource->选中Icon->Import->找到一个.ico的图形插入程序中。 (3)删除IDR_MAINFRAME,将刚才插入的图形ID改为IDR_MAINFRAME (4)编译运行,见标题栏上的图标变了。

例5.20 在客户区显示一个图标

(1)利用MFC AppWizard[exe]向导创建一个单文档应用程序:图标显示 (2)在程序资源编辑器中添加一个图标资源,其ID为IDI_MYICON

(3)在应用程序类CMyApp实现文件CMyApp.cpp中的InitInstance函数中添加代码: BOOL CMyApp::InitInstance() { … … …

HICON myicon=AfxGetApp()->LoadIcon(IDI_MYICON);//加载自定义图标 m_pMainWnd->SetIcon(myicon,TRUE);//设置32x32图标 m_pMainWnd->SetIcon(myicon,FALSE);//设置16x16图标 return TRUE; }

(4)在视图类CMyView实现文件CMyView.cpp中的OnDraw函数中添加代码: void CMyView::OnDraw(CDC* pDC)

{ CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); HICON myicon;

myicon = AfxGetApp()->LoadIcon(IDI_MYICON);//加载自定义图标 pDC->DrawIcon(100,100,myicon);//显示图标 DestroyIcon(myicon);//释放图标资源 }

5.9.4 光标

光标(Cursor)实际上是一个小的位图,是一种特殊的、可移动的32*32点阵图形。它在屏幕上的位置由顶点设备控制,例如鼠标、画笔等。鼠标移动时,Windows系统相应地移动光标。Windows支持两种类型的光标:标准光标和定制光标。标准光标是指系统定义的光标,用户移动窗口或者图标,或改变窗口的尺寸时,Windows都会显示标准光标。定制光标用于应用程序特定的用途。 1、使用系统光标

Windows预定义了一些经常使用的标准光标,这些光标均可以使用函数CWinApp::LoadStandardCursor加载到程序中,其函数原型如下: HCURSOR LoadStandardCursor(LPCTSTR lpszCursorName)const;

说 明:LPCTSTR:指向字符串常量的32位指针

45

参 数:lpszCursorName指定一个标准光标名,它可以是表5.9宏定义即Windows提供的标准光标: 表5.9 Windows提供的标准光标

标准光标 说 明 IDC_ARROW 标准箭头光标 IDC_IBEAM 标准文本输入光标 IDC_WAIT 漏斗型计时等待光标 IDC_CROSS 十字形光标 IDC_UPARROW 垂直箭头光标 IDC_SIZEALL 四向箭头光标

IDC_SIZENWSE 左上至右下的双向箭头光标 IDC_SIZENESW 左下至右上的双向箭头光标 IDC_SIZEWE 左右双箭头光标 IDC_SIZENS 上下双向箭头光标

2、使用光标资源

用编辑器创建或从外部调如入的光标资源,可通过函数:CWinApp::LoadCursor进行加载,函数原形如下:

HCURSOR LoadCursor(LPCTSTR lpszResourceName)const; HCURSOR LoadCursor(UINT nIDResource)const;

参 数:lpszResourceName和nIDResource分别用于指定光标资源的名称或ID号。

例如:加载一个垂直箭头光标IDC_UPARROW的代码如下:

HCURSOR hCursor;

hCursor=AfxGetApp()->LoadStandardCursor(IDC_UPARROW); 3、更改程序中的光标

更改应用程序中的光标,除了可以使用GetClassLong和SetClassLong函数外,最简单的方法是用ClassWizard映射WM_SETCURSOR消息,该消息是当光标移动到一个窗口内并且还没有扑捉到鼠标时产生的。CWnd为此消息的映射函数定义这样的原型: afx_msg BOOL OnSetCursor(CWnd *pWnd,UINT nHitTest,UINT message);

参 数:

pWnd 表示拥有光标的窗口指针;

nHitTest用于表示光标所处的位置,例如:当为HTCLIENT时表示光标在处理窗口的客户区中,而

为HTCAPTION时表示光标在处理窗口的标题栏处,等等。

message表示鼠标消息。

在OnSetCursor函数调用SetCursor来设置相应的光标,并将OnSetCursor函数返回TRUE时,就可改变当前的光标了。 4、光标实例

例5.21 在客户区内,显示一个带有两个垂直方向箭头的光标

(1)利用MFC AppWizard[exe]向导创建一个单文档应用程序,名为:箭头光标

(2)利用ClassWizard为视图类添加消息WM_SETCURSOR消息处理函数,并添代码:

46

BOOL CMyView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { HCURSOR m_cursor;

//加载上下双向箭头光标,见表5.9

m_cursor=AfxGetApp()->LoadStandardCursor(IDC_SIZENS); SetCursor(m_cursor);//设置光标

return TRUE;

//return CView::OnSetCursor(pWnd, nHitTest, message); }

(3)编译运行。

注 意:在OnSetCursor函数中,需要在最后使用return TRUE,而不是使用OnSetCursor自己的return语句,不然不能设置自己的光标。

例5.22 刚开始时,是箭头光标,当光标处在菜单栏并点击菜单项时,箭头光标就变成其它形状了。

1)建一个单文档的应用程序:光标形状

2)添加一个光标资源:Insert?Resource?打开Cursor?双击最上条IDC_NODROP?见加进一个光标资源,缺省ID号为IDC_NODROP?将这个ID号改为IDC_CURSOR1 3)用ClassWizard将WM_SETCURSOR消息映射在主框架CMainFrame类里,并加代码: BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { BOOL bRes=CFrameWnd::OnSetCursor(pWnd,nHitTest,message); if(nHitTest==HTCAPTION)// HTCAPTION表示光标在窗口的标题栏处 { HCURSOR hCursor; hCursor=AfxGetApp()->LoadCursor(IDC_CURSOR1); SetCursor(hCursor); bRes=true; }

return bRes; }

4)运行后,当鼠标放在“文档窗口”、“菜单栏”、“工具栏”鼠标形状不变,放在“标题拦”鼠标的形状就变了。

下面的例子是根据当前鼠标所在的位置来确定单文档应用程序光标的类型,当鼠标处在客户区时,为一个自定义光标;当鼠标处在菜单栏时,为一个箭头光标,此时点击菜单项时,光标消失,抬起后,恢复箭头光标;回到客户区,又为自定义光标;当鼠标处在标题栏时,光标变成动画光标。

例5.23 改变应用程序光标,

1)利用MFC AppWizard(exe)建一个单文档应用程序,名为:光标更改 2)Insert->Resource ->选中Cursor->New,ID为IDC_CURSOR1 3)将鼠标放在图形编辑器工作窗口的控制条上的“新设备按钮”上,出现:“New Device Image(Ins)”单击它,出现”New Cursor Image”对话框,再单击Custom”定制”按钮。

47

4)在弹出的” 定制图像Custom Image ”对话框中,保留默认的大小和颜色数,单击OK回到:”New Cursor Image”对话框。

5)选择”32*32,16 colors”设备类型,单击OK。

6)在图形编辑器的”Device:32*32,16 colors”组合框中,下拉,选择:“Monochrome[32*32]”。再把鼠标放在主窗口的菜单栏,找到Image菜单打开,选择最后的”Delete Device Image”命令,删除”Monochrome[32*32]”设备类型。如果不这样做,加载后的光标不会采用”32*32,16 colors”设备类型。

7)保留默认ID号IDC_CURSOR1,用图形编辑器绘制光标图形,画的光标如图5.9.9 所示。指定光标热点位置为(15,15)即光标中心位置,(点一下图形编辑器工作窗口的控制

条上的“设置热点按钮”,再在下面的方块图上点一下,就能看到图形编辑器控制条上的Hot spot热 点位置,这样循环点几次就能找到(15,15)的位置)。

8)为CMainFrame类添加一个类型为光标句柄的成员变量:HCURSOR m_hCursor; 9)用MFC ClassWizard为主框架CMainFrame类添加WM_SETCURSOR的消息映射函 数,并加下列代码:

BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { BOOL bRes=CFrameWnd::OnSetCursor(pWnd,nHitTest,message); if(nHitTest==HTCAPTION)

{ m_hCursor=LoadCursorFromFile(\ SetCursor(m_hCursor); bRes=TRUE; }

else

if(nHitTest==HTCLIENT)

{ m_hCursor=AfxGetApp()->LoadCursor(IDC_CURSOR1); SetCursor(m_hCursor); bRes=TRUE; }

return bRes;

return CFrameWnd::OnSetCursor(pWnd, nHitTest, message); }

图5.9.9 自画的图形光标

48

10)编译运行,当光标在客户区和工具拦时,是自画光标IDC_CURSOR1,移到菜单拦时,是箭头,移到标题栏时,光标变成动画光标。

需要说明的是,Visual C++6.0中还提供BeginWaitCursor和EndWaitCursor函数来启动和终止动画沙漏光标。

章 后 实 训

实训1:在实际工作中,对图象进行局部放大,以便更加清楚、细致的观察图象的某个部位,是经常遇到的。在Visual C++6.0中,巧妙、灵活的运用CDC类的StretchBlt函数以及有效的完成对鼠标的消息映射等,就能实现这一目的;这里所实现的对图象进行局部放大的操作是:(1)移动MOUSE鼠标,放大显示图象的不同部位。(2)左击鼠标放大倍率。(3)右击鼠标减少倍率。实现效果见图1和图2所示。

图1:原图象 图2:局部放大后的图象

1、利用MFC AppWizard(exe)建一个单文档的应用程序,名为:图像局部放大 2、在视图类(View.h)的public中添加以下数据成员:

CSize m_sizeDest; // CSize类是对Windows结构SIZE的封装,表示一个矩形的长度和宽度 CSize m_sizeSource;

CBitmap *m_pBitmap; // 定义位图类指针对象 CDC *m_pdcMem; // 定义CDC类指针对象 int oldx,oldy,s,d;

bool recover; // 布尔类型变量,即:真或假 long mana;//长整形

3、在资源中加入你自己喜欢的位图,ID为:IDB_BITMAP1,步骤为:

Insert->Resource->点中Bitmap->Import->找到一个你喜欢的位图图形,导入项目中。 4、在视图类(View.cpp)的构造函数中,初始化数据成员。 CMyView::CMyView()

{ m_pdcMem = new CDC; //CDC类

m_pBitmap= new CBitmap; //位图类

recover = true; //赋给布尔类型变量为真

49

s=30; d=45;

mana=SRCCOPY; // 实现多媒体设备的播放和多媒体资源文件的记录 }

5、在视图类的View.cpp的析构函数中,对用完后的数据成员进行释放 CMyView::~CMyView()

{ delete m_pdcMem; //释放为CDC类开辟的一片单元

delete m_pBitmap; //释放为位图类开辟的一片单元 }

6、将主框架类MainFrm.h中的保护模式protected:下的CStatusBar m_wndStatusBar; 剪切到公共模式public:下。

在视图类执行文件(View.cpp)的前面,加入:#include “MainFrm.h” 7、在视图类(View.cpp)的显示函数OnDraw()中加入如下代码: void CMyView::OnDraw(CDC* pDC) { CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); static bool load; if(!load)

{ BITMAP bm; load = !load;

m_pBitmap->LoadBitmap(IDB_BITMAP1);

m_pdcMem->CreateCompatibleDC(pDC);

m_pdcMem->SelectObject(m_pBitmap); m_pBitmap->GetObject(sizeof(bm),&bm); m_sizeSource.cx=bm.bmWidth; m_sizeSource.cy=bm.bmHeight; m_sizeDest=m_sizeSource;

pDC->StretchBlt(0,0,m_sizeSource.cx,m_sizeSource.cy,

m_pdcMem,0,0,m_sizeSource.cx,m_sizeSource.cy,mana); } else { }

pDC->StretchBlt(0,0,m_sizeSource.cx,m_sizeSource.cy, m_pdcMem,0,0,m_sizeSource.cx,m_sizeSource.cy,mana); }

说 明

(1)m_pBitmap->LoadBitmap(IDB_BITMAP1); 位图类成员函数,从程序的可执行文件中调入指定

的位图资源,并连接位图到对象,初始化对象,参数是:位图资源ID号。

(2)m_pdcMem->CreateCompatibleDC(pDC); CDC类成员函数,创建一个内存设备环境,以便

位图在内存中保存下来,并与指定设备(窗口设备)环境相兼容,参数是:设备环境指针。

50

本文来源:https://www.bwwdw.com/article/yy18.html

Top