第 3章 Visual C++图形程序设计
基础
内容提要
? Visual C++是在 Microsoft C的基础上发展而来的,
随着计算机软、硬件技术的快速发展,如今 Visual
C++已成为集编辑、编译、运行、调试于一体功
能强大的集成编程环境。本章以 Visual C++ 6.0为
对象,主要介绍 Visual C++集成编成环境的使用、
图形设备接口和常用图形程序设计、鼠标编程以
及菜单设计等基础,目的是通过对 Visual C++的
学习,掌握 Visual C++图形程序设计的方法,为
计算机图形学原理部分的算法实现提供程序工具
和方法。
3,1 Visual C++ 6.0应用程序开
发方法
? 介绍 Visual C++ 6.0集成开发环境,以一个
简单的实例介绍利用 Visual C++应用程序工
程建立方法和程序设计框架。
3,1,1 Visual C++的集成开发
环境
? 从开始菜单中启动 Visual C++ 6.0,进入开
发集成环境。打开一个项目后,可以看到
Visual C++ 6.0的开发环境由标题栏、工具
栏、工作区窗口、源代码编辑窗口、输出
窗口和状态栏组成,见图 3.1所示。
图 3.1 Visual C++ 6.0集成开发环境
标题栏
菜单栏
工作区
源代码
编辑窗口
状态栏
标题栏用于显示应用程序名和所打开的文件名,标题栏的
颜色可以表明对应窗口是否被激活。菜单栏包括文件、编
辑、显示、插入、工程、编译、工具、窗口和帮助九项主
菜单,包含了从源代码的编辑、界面设计、程序调试和编
译运行在内的所有功能。工具栏列出了常用的菜单命令功
能和对象方法。工具栏的下面是两个窗口,一个是工作区
窗口,用于列出工程中的各种对象,一个是源代码编辑窗
口,用于各个对象的程序设计。输出窗口显示项目建立过
程中所产生的各种信息。屏幕底端是状态栏,它给出当前
操作或所选择命令的提示信息。
3,1,2 应用程序工程的建立方

? Visual C++提供了一种称为 App Wizard的工具, 利
用该工具, 用户可以方便地按照自己的需要创建
符合需要的应用程序框架 。 在这个基础上, 用户
可以进一步将自己编写的程序加入到这个框架中,
实现用户程序的功能 。 下面介绍建立 VcApp应用
程序框架的方法, 其它应用程序的方法都与此类
似 。
? 第一步:启动 Visual C++,选择工程方法
? 从开始菜单中选择 Visual C++,进入 Visual
C++集成环境 。 从文件菜单中选择新建 (New)命令,
3,1,2 应用程序工程的建立方

? 弹出图 3-2对话框 。 切换到工程 ( Projects)
标签, 项目类型选择 MFC AppWizard(exe),
输入工程的名字 ( 如 VcApp), 选择项目放
置的位置, 然后单击, 确定, 按钮 。
图 3-2 Visual C++的 New对话框
3,1,2 应用程序工程的建立方

? 第二步:设置应用程序的特性 。
? 这些设置包括六个问题, 每一个问题都有不同
的选项供选择 。 一个问题选择完后, 通过, 下一
步, ( Next) 选择下一个问题, 直到六个问题选
择完毕 。 还可以通过, 上一步, (Back)返回上一
个问题重新选择 。 下面继续上面的例子, 在单击
,确定, 按钮后, 弹出第一个问题窗口, 如图 3-3
所示 。
? 第一个问题是建立什么类型的应用程序, 有三个
选项:单个文档 ( Single document), 多重文档
( Multiple document ) 和 基 本 对 话 ( Dialog
based) 。
3,1,2 应用程序工程的建立方法
? 单个文档应用程序主窗口中只有一个窗口, 多重
文档可以在主窗口中开多个子窗口, 基本对话主
窗口是一个对话框 。 例中选择单个文档, 单击
,确定,, 进入下一个问题, 如图 3-4所示 。
? 第二个问题是数据库的支持,是否用 ODBC存取
数据库,有四个选项:不包括数据库的支持
( None)、仅包含 ODBC头文件( Header files
only)、指定一个数据库但没有文件支持和指定
一个数据库但需要文件支持。当选择了后两项,
则需要用户选择一个已经建立的数据库。例中不
需要数据库支持,选择第一个选项, 否,,进入
第三个问题,如图 3-5所示
图 3-3 第一个问题:选择应用程序的类型
图 3-4 第二个问题:选择是否要用 ODBC支持
? 第三个问题是对 ActiveX的支持 。 有五个选项:
( 1) 没有对 ActiveX的支持; ( 2) ActiveX容器,
它可以包含链接和嵌入对象 。 容器不能为其它的
ActiveX程序提供支持, 它只能维护嵌入对象;
( 3) 微型服务器 ( Mini-server), 应用程序不能
独立运行, 只能被调用为其它程序建立 ActiveX对
象 。 ( 4) 完整服务器 ( Full-server), 它能够独
立运行, 并能够为其它应用程序建立 ActiveX对象 。
( 5) 容器和服务器, 一个应用程序可以同时是容
器和服务器 。
? 在例子中,选择第一个选项,没有对 ActiveX的支
持,单击, 下一个, ( Next)进入下一个问题。
图 3-5 第三个问题:选择是否对 ActiveX的支持
第四个问题是应用程序的特性和高级选项,如图 3-6所示。
图 3-6 应用程序的特性和高级选项
? 例中全部采用默认选项, 进入下一个问题 。
? 第五个问题是项目的风格, 原文件注释和 MFC库
类型, 如图 3-7所示 。
? 在例子中全部采用默认选项, 进入第六个问题 。
? 第六个问题是确定类名和文件名, 如图 3-8所示 。
? 基于第一个问题到第五个问题的回答,
AppWizard会把将要建立的新类的名称通知用户。
AppWizard将为应用程序建立四个新类,
CVcAppApp是应用程序类,它是 CWinApp的派生
类。 CMainFrame是一个拥有应用程序主窗口的类。
CVcAppDoc和 CVcAppView是该应用程序的文档
和视图类。这些名字用户可以改变。最后单击
,完成, ( Finish),显示所建项目的信息,单击
,确定, 后,项目建立完成。
图 3-7 项目的风格、原文件注释和 MFC库类型
图 3-8 通知 MFC产生的类名称
3,1,3 输入源程序进行程序设计
? 应用程序项目工程建立以后, 就为应用程序的开
发建立了一个框架, 这是不输入任何程序代码,
对该项目程序进行编译和运行, 可以生成一个完
整的窗口程序 。 用户根据项目工程中的不同类,
输入自己设计的程序代码, 完成用户的程序设计 。
? 例如, 从 VcApp Classes中找到 CVcAppView的
OnDraw( ) 函数, 如图 3-9所示 。 双击 OnDraw( )
函数, 这时系统会打开 VcAppView.cpp文件, 而
且光标正置于 OnDraw( ) 函数中, 在其中输入下
列语句:
? pDc->TextOut(30,30,”同学们好, 欢迎使用 VC++编
程 !, );
? 编译并运行该程序, 运行结果如图 3-10所示 。
图 3-9 输入程序源代码
图 3-10 运行结果
3,2 图形设备接口和图形程
序设计
3,2,1 图形设备接口简介
? 在 Windows系统中, 程序都是通过一个叫做图形
设备接口 ( GDI,Graphics Device Interface) 的抽
象接口和硬件打交道, Windows会自动将设备环
境表映射到相应的物理设备, 并且会提供正确的
输入 /输出指令 。
? GDI是 Windows系统核心的三种动态链接库之一,
它管理 Windows系统的所有程序的图形输出。在
Windows系统中,GDI向程序员提供了高层次的绘
图函数,只要掌握这些绘图函数,就可以很方便
地进行图形程序设计。
? 另一个概念是设备描述表 ( DC,Device Context) 。
DC是一个数据结构, 当程序向 GDI设备中绘图时,
需要访问该设备的 DC。 MFC将 GDI的 DC封装在
C++类中, 包括 CDC类和 CDC派生类, 这些类中
的许多成员都是对本地 GDI绘图函数进行简单封
装而形成的内联函数 。
? DC的作用就是提供程序与物理设备或者虚拟设备
之间的联系, 除此之外, DC还要处理绘图属性的
设置, 如文本的颜色等 。 程序员可以通过调用专
门的 GDI函数修改绘图属性, 如 SetTextColor()函
数 。
? CDC类是 GDI封装在 MFC中最大的一个类, 它表
示总的 DC。 表 3.1列出了 CDC中的一些常用绘图
函数 。
表 3.1 CDC类中常用绘图函数
函 数 描 述 使用频率
Arc() 椭圆弧 ****
BitBlt() 把位图从一个 DC拷贝到另一个 DC *
Draw3dRect() 绘制三维矩形 **
DrawDragRect() 绘制用鼠标拖动的矩形 **
DrawEdge() 绘制矩形的边缘 **
DrawIcon() 绘制图标 ***
Ellipse() 绘制椭圆 ****
FillRect() 绘制用给定的画刷颜色填充矩形 ***
FillRgn() 绘制用给定的画刷颜色填充区域 ***
FillSolidRed() 绘制用给定的颜色填充矩形 ***
FloodFill() 用当前的画刷颜色填充区域 ***
FrameRect() 绘制矩形边界 **
FrameRgn() 绘制区域边界 **
GetBKColor() 获取背景颜色 *****
GetCurrentBitmap() 获取所选位图的指针 **
GetCurrentBrush() 获取所选画刷的指针 ***
GetCurrentFont() 获取所选字体的指针 ***
GetCurrentPalette() 获取所选调色板的指针 ***
GetCurrentPen() 获取所选画笔的指针 ***
GetCurrentPosition() 获取画笔的当前位置 ****
GetDeviceCaps() 获取显示设备能力的信息 **
GetMapMode() 获取当前设置映射模式 ***
Getpixel() 获取给定像素的 RGB颜色值 *****
GetPolyFillMode() 获取多边形填充模式 ***
GetTextColor() 获取文本颜色 ****
GetTextExtent() 获取文本的宽度和高度 **
GetTextMetrics() 获取当前文本的信息 **
GetWindow() 获取 DC窗口的指针 **
GrayString() 绘制灰色文本 ***
LineTo() 绘制直线 ******
MoveTo() 设置当前画笔位置 ******
Pie() 绘制饼图 ***
Polygon() 绘制多边形 ***
PolyLine() 绘制一组直线 ***
RealizePalett
e()
将逻辑调色板映射到系统
调色板
**
Rectangle() 绘制矩形 ****
RoundRect
()
绘制圆角矩形 ***
SelectObje
ct()
选择 GDI绘图对象 **
SelectPalet
te()
选择逻辑调色板 **
SelectStoc
kObject()
选择预定义图形对象 **
? 这些函数的语法和使用可以通过 MSDN帮助查询。
3.2.2节主要介绍 Windows中基本图形,包括电、
直线、圆、圆弧、矩形、椭圆、扇形、折线等程
序设计
SetBkColor() 设置背景颜色 ******
SetMapMode() 设置映射模式 ***
SetPixel() 把像素设定为给定的颜色 ******
SetTextColor() 设置文本颜色 ******
StretchBlt() 把位图从一个 DC拷贝到另一个 DC,并根据需要扩展或
压缩位图
*
TextOut() 绘制字符串文本 *****
3,2,2 绘制基本图形
? ( 1) 画点
? SetPixel()函数可以在指定的坐标位置按
指定的颜色画点。函数原型说明如下:
COLORREF CDC:,SetPixel(int X,int Y,COLORREF crColor);
? 其中, ( X,Y) 为点的坐标位置, crColor
参数为点的颜色值 。 如果函数调用成功,
则函数返回像素的颜色值, 否则返回值为 -1。
颜色值通过 RGB(Red,Green,Blue)来设置,
其中三个参数取值 0~255。 例如, 在 VcAPP
项目中, 在 CVcAppView类中的 OnDraw()函
数中加入下列画点语句:
? //绘制一组彩色点
? //绘制一组彩色点
? pDC->TextOut(20,20,"point:");
? pDC->SetPixel(100,20,RGB(255,0,0));
? pDC->SetPixel(110,20,RGB(0,255,0));
? pDC->SetPixel(120,20,RGB(0,0,255));
? pDC->SetPixel(100,20,RGB(255,255,0));
? pDC->SetPixel(100,20,RGB(255,0,255));
? pDC->SetPixel(100,20,RGB(0,255,255));
? pDC->SetPixel(100,20,RGB(0,0,0));
? pDC->SetPixel(100,20,RGB(255,255,255));
? 运行程序,查看运行结果。
( 2)画直线和折线
? 画直线需要 LineTo()和 MoveTo()两个函数的
配合使用 。
? LineTo()函数以当前位置所在的点为直线的
起点, 另指定一个点为直线的终点, 画出
一段直线 。 直线的颜色通过画笔的颜色来
设定, 在后面介绍 。 LineTo()函数原型说明
如下:
BOOL CDC:,LineTo(int nXEnd,int nYEnd);
? 直线的终点位置由 ( nXEnd,nYEnd) 指定 。
如果函数调用成功, 那么该点就成为当前
位置, 并返回 TRUE,否则返回 FALSE。
? MoveTo()函数只是将当前位置移动到指定
位置,它并没有画出直线,其函数说明为:
BOOL CDC:,MoveTo (int X,int Y);
? 示例:在 CVcAppView类中的 OnDraw()函数
中加入下列画点语句:
? //绘制直线
? pDC->TextOut(20,60,"Line:");
? pDC->MoveTo(20,90);
? pDC->LineTo(160,90);
? Polyline() 函数用来画一条折线, 而
PolyPolyline()函数则用来画多条折线, 它们
的函数原型说明如下:
BOOL CDC::Polyline(COUST POINT *lppt,int cPoints);
BOOL CDC::PolyPolyline(COUST POINT * lppt,COUST DWORD *lpdwPolyPoints,
DWORD cCount);
? 在 Polyline()函数中, lppt是指向折线顶点数组的指
针, 而 cPoints是折线顶点数组中的顶点数 。 例如,
绘制一条具有 4个顶点的折线, 程序如下:
? POINT
polylinepoint[4]={{70,240},{20,190},{70,190},{20,
240}};
? pDC->Polyline(polylinepoint,4);
? 在 PolyPolyline( ) 函数中, lppt是指向保存顶点
数组的指针, 而各条折线的顶点数则保存在
lpdwPolyPoints 参数所指向的数组中, 最后 的
cCount参数指定折线的数目 。
? 例如:
? POINT
polypolylinePt[9]={{95,160},{120,185},{120,250},{
145,160},{120,185},
? {90,185},{150,185},{80,210},{160,210}};
? DWORD dwPolyPoints[4]={3,2,2,2}; //分四段折
线, 分别占用 3,2,2,2个顶点
? pDC->PolyPolyline(polypolylinePt,dwPolyPoints,
4);
? 注:由于一条折线至少需要 2个顶点,因此
dwPolyPoints数组中的数不应该小于 2。
( 3)画弧线和曲线
? 通过 Arc()函数画弧线或整个椭圆。椭圆限
定在一个矩形内,称为外接矩形。 Arc()函
数的圆形说明如下:
? BOOL CDC:,Arc(int nLeftRect,int nTopRect,
int nRightRect,int nBottomRect,
? int nXStartArc,int nYStartArc,int nXEndArc,
int nYEndArc);
? 其中, ( nLeftRect,nTopRect) 是外接矩形
的左上角坐标值, ( nRightRect,
nBottomRect) 是外接矩形的右下角坐标值 。
而椭圆中心与点 ( nXStartArc,nYStartArc)
所构成的射线与椭圆的交点成为弧线的起
点, 椭圆中心与点 ( nXEndArc,nYEndArc)
所构成的射线与椭圆的交点成为弧线的终
点 。 椭圆上从始点到终点就形成一条弧线 。
? 在 Windows系统中,弧线从始点到终点的
方向是逆时针方向,但可以通过
SetArcDirection()函数将绘制弧线方向设置
为顺时针方向。
? 示例, 用 Arc()绘制圆, 圆弧和椭圆, 程序如下:
? for (i=0;i<6;i++)
? {
? pDC->Arc(260-5*i,70-
5*i,260+5*I,70+5*i,260+5*i,70,260+5*i,70);
? }
? for (i=3;i<6;i++)
? {
? pDC->Arc(260-10*i,70-10*i,260+10*i,70+10*i,
? (int)260+10*i*cos(60*3.1415926/180),
? (int)70+10*i*sin(60*3.1415926/180),
? (int)260+10*i*cos(60*3.1415926/180),
? (int)70-10*i*sin(60*3.1415926/180));
? pDC->Arc(260-10*i,70-10*i,260+10*i,
70+10*i,
? (int)260-10*i*cos(60*3.1415926/180),
? (int)70-10*i*sin(60*3.1415926/180),
? (int)260-10*i*cos(60*3.1415926/180),
? (int)70+10*i*sin(60*3.1415926/180));
? }
? Bezier曲线是最常见的非规则曲线之一。
Bezier曲线属于三次曲线,需要四个控制顶
点来确定一条 Bezier曲线,其中曲线通过第
一点和最后一点,并且第一条边和最后一
条边是曲线在起点和终点处的切线,从而
确定了曲线的走向。 PolyBezier()函数可以
画出一条或多条 Bezier曲线,其函数原型说
明如下:
? BOOL CDC:,PolyBezier(CONST POINT *
lppt,DWORD cPoints);
? 其中, lppt参数是曲线控制顶点所组成的数组,
cPoints参数表示 lppt数组中的顶点数, 一条 Bezier
曲线需要四个控制顶点 。 如果 lppt数组用于画多条
Bezier曲线, 第二条以后的曲线只需要三个控制顶
点, 因为后面的曲线总是把前一条曲线的终点作
为自己的起点 。
? 示例, 给出四个控制顶点, 画出一条 Bezier曲线和
特征多边形 。
? //绘制 Bezier 曲线
? POINT
polyBezier[4]={{20,310},{60,240},{120,300},{160,
330}};
? pDC->Polyline(polyBezier,4);
? pDC->PolyBezier(polyBezier,4);
( 4)画封闭曲线
? Windows中提供了一组画封闭曲线的函数,
包括绘制矩形, 多边性, 椭圆等, 这些画
封闭曲线的函数不但可以利用画笔来画出
轮廓线, 同时还可以利用画刷来填充这些
封闭曲线所围成的区域 。
? Rectangle()函数用来画矩形,其函数原型说
明如下:
? BOOL CDC:,Rectangle(int nLeftRect,int
nTopRect,
? int nRightRect,int nBottomRect);
? 其中, 参数 nLeftRect和 nTopRect给出了矩
形 左 上 角 的 坐 标, 而 nRightRect 和
nBottomRect则给出矩形的右下角坐标 。
?
? Ellipse()函数的作用则是画椭圆形。在
Ellipse()函数中,椭圆是由其外接矩形来确
定的,外接矩形的中心与椭圆中心重合,
矩形的长与宽和椭圆的长短轴相等。函数
说明如下,BOOL CDC:,Ellipse(int
nLeftRect,int nTopRect,
? int nRightRect,int nBottomRect);
? 其中的参数说明与 Rectangle()函数相同。
? RoundRect()函数用来画圆角矩形,其函数
的原型说明如下:
? BOOL CDC:,RoundRect(int nLeftRect,int
nTopRect,
? int nRightRect,int nBottomRect,
? int nWidth,int nHeight);
? 其中的前四个参数与 Rectangle()函数相同,
nWidth表示圆角的宽度,nHeight表示圆角
的高度。
? Polygon()函数用来画封闭的任意多边形,
其函数原型说明如下:
? BOOL CDC:,Polygon((COUST POINT
*lpPoints,int cCount);
? 其中的参数说明与 Polyline()函数相同。
但两个函数有区别,Polygon()函数会自动
将起点和终点相连形成封闭的多边形,而
Polyline()函数则画出多条折线,只有当最
后一点与起点相同时才画出封闭的多边形。
? 示例, 绘制矩形, 圆角矩形, 椭圆和多边
形, 程序如下:
? //绘制矩形, 圆角矩形, 椭圆和多边形
? pDC->Rectangle(190,270,250,310);
? pDC->RoundRect(265,270,330,310,30,20);
? pDC->Ellipse(260-50,200-30,260+50,200+30);
? POINT
polygonPts[3]={{390,160},{430,220},{350,21
0}};
? pDC->Polygon(polygonPts,3);
3,2,3 画笔与画刷
? ( 1) 画笔
? 当绘制图形时, 线条的属性, 包括颜色,
宽度, 样式等都是由画笔来确定的 。 程序
员可以创建画笔, 定义画笔的属性, 从而
画出多彩的图形 。
? 方法一:直接构造一个 CPen对象, 并将定
义画笔的参数传给它, 例如:
? CPen pen(PS-SOLID,1,RGB(255,0,0));
? 创建一个宽度为一个像素, 实线和红色的
画笔 。
? 方法二:首先声明一个没有初始化的 CPen
类对象, 然后再用 CreatePen()函数定义画笔
的属性 。 例如,
? CPen Pen;
? Pen->CreatePen (PS-SOLID,1,RGB(255,0,0));
? 方法三:先声明一个 CPen类对象和一个描
述画笔结构的 LOGPEN类对象, 并填入画笔
的属性值, 然后调用 CreatePenIndirect()函数
来创建画笔 。 如下所示:
? CPen Pen;
? LOGPEN LogPen;
? LogPen.lopnStyle=PS_SOLID;
? LogPen.lopnWidth=1;
? LogPen.lpenColor=RGB(255,0,0);
? Pen.CreatePenIndirect(&LogPen);
? 如果画笔被成功创建,那么两个函数返回
TRUE,否则返回 FALSE。
画笔包括样式, 宽度和颜色三个属性 。
表 3.2列出了 GDI画笔的样式 。
画笔的宽度用像素个数来确定。 PS_DASH、
PS_DOT,PS_DASHDOT和 PS_DASHDOTDOT参
数要求画笔宽度只能为 1,其它参数可以创建任意
宽度的画笔。画笔的颜色是一个 24位的 RGB颜色,
由 RGB(rColor,gColor,bColor)来定义,三个参数取
值 0~255。
Windows预定义了三个实线,1个像素宽的
画笔,它们是 WHITE_PEN,BLACK_PEN和
NULL_PEN,程序中可以直接使用这些画笔,方法
如下:
CPen Pen;
Pen.CreateStockObject(WHITE_PEN);
示例:在屏幕上绘制三组直线,第一组按不
同线型绘制,第二组按不同宽度绘制,第三组按
不同颜色绘制。程序如下:
? //画笔的样式、宽度和颜色
? int i1;
? int nPenStyle[]=
? {
? PS_SOLID,PS_DASH,PS_DOT,PS_DASHDOT,
PS_DASHDOTDOT,PS_NULL,
? PS_INSIDEFRAME,
? };
? CPen *pNewPen;
? CPen *pOldPen;
? //用不同样式的画笔
? for (i1=0;i1<7;i1++)
? {
? //构造新笔
? pNewPen=new CPen;
? if (pNewPen->CreatePen(nPenStyle[i1],1,RGB(0,0,0)))
? {
? pOldPen=pDC->SelectObject(pNewPen); //选
择新笔,并保存旧笔
? //画直线
? pDC->MoveTo(20,60+i1*20);
? pDC->LineTo(160,60+i1*20);
? //恢复原有的笔
? pDC->SelectObject(pOldPen);
? }
? else
? {
? //出错提示
? AfxMessageBox("CreatePen Erroe!!");
? }
? //删除新笔
? delete pNewPen;
? }
? //用不同的宽度的笔绘图
? for(i1=0;i1<7;i1++)
? {
? //构造新笔
? pNewPen=new CPen;
? if (pNewPen-
>CreatePen(PS_SOLID,i1+1,RGB(0,0,0)))
? {
? pOldPen=pDC->SelectObject(pNewPen);
? //画直线
? pDC->MoveTo(200,60+i1*20);
? pDC->LineTo(340,60+i1*20);
? //恢复原有的笔
? pDC->SelectObject(pOldPen);
? }
? else
? {
? //出错提示
? AfxMessageBox("CreatePen Erroe!!");
? }
? //删除新笔
? delete pNewPen;
? }
? //设置颜色表
? struct tagColor
? {
? int r,g,b;
? } color[7]=
? {
? {255,0,0},{0,255,0},{0,0,255},
? {255,255,0},{255,0,255},{0,255,255},{0,0,0},
? };
? //用不同颜色绘图
? for(i1=0;i1<7;i1++)
? {
? //构造新笔
? pNewPen=new CPen;
? if (pNewPen-
>CreatePen(PS_SOLID,2,RGB(color[i1].r,color[i1].g,color
[i1].b)))
? {
? pOldPen=pDC->SelectObject(pNewPen);
? //画直线
? pDC->MoveTo(380,60+i1*20);
? pDC->LineTo(520,60+i1*20);
? //恢复原有的笔
? pDC->SelectObject(pOldPen);
? }
? else
? {
? //出错提示
? AfxMessageBox("CreatePen Erroe!!");
? }
? //删除新笔
? delete pNewPen;
? }
? //画笔程序结束
? ( 2)画刷
在进行区域填充或绘制封闭图形时,需要用到画刷。
MFC把 GDI画刷封装在 CBrush类中。画刷分三种基本类型:
纯色画刷、阴影画刷和图案画刷。
纯色画刷绘图使用单色来定义,颜色由 RGB()函数来
确定。纯色画刷可以采用直接声明的方法,例如:
CBrush Brush(RGB(255,0,0)); 创建一个红色画刷。
也可以采用分步方法,由 CreateSolidBrush()函数创建。
CBrush Brush;
Brush->Create->CreateSolidBrush(RGB(255,0,0));
Windows预定义了七种画刷,包括,BLACK_BRUSH、
DKGRAY_BRUSH,GRAY_BRUSH,LTGRAY_BRUSH、
HOLLOW_BRUSH,NULL_BRUSH和 WHITE_BRUSH。可
以参照 CPen类的方法,采用 CreateStockObject()来使用预
定义的画刷。
阴影画刷使用预定义的六种阴影样式进行绘图。表
3.3列出了六种阴影样式。
创建阴影画刷的方法与纯色画刷的创建方法相似,
例如创建一个 45度方向的交叉阴影线的画刷,方法如下:
CBrush Brush(HS_DIAGCROSS,RGB(255,0,0));
或者
CBrush Brush;
Brush-
>CreateHatchBrush(HS_DIAGCROSS,RGB(255,0,0));
函数中有两个参数,第一个参数是画刷的阴影样式,
第二个参数是阴影线的颜色。示例:绘制缺省画刷的矩
形,纯色画刷矩形和绘制 100单位的矩形,并且用白色
45度交叉线阴影将其填充,程序如下:
? //画刷程序
? pDC->Rectangle(300,300,400,400); //缺省的画刷,白色
? //纯色画刷
? CBrush *pNewBrush1;
? CBrush *pOldBrush1;
? pNewBrush1=new CBrush;
? if (pNewBrush1->CreateSolidBrush(RGB(255,0,0)))
? {
? //选择新画刷
? pOldBrush1=pDC->SelectObject(pNewBrush1);
? //绘制矩形
? pDC->Rectangle(200,200,300,400);
? //恢复原有画刷
? pDC->SelectObject(pOldBrush1);
? }
? delete pNewBrush1;
? //阴影画刷
? CBrush
Brush(HS_DIAGCROSS,RGB(255,255,255));
? CBrush *pOldBrush;
? pOldBrush=pDC->SelectObject(&Brush);
? pDC->SetBkColor(RGB(192,192,192));
? pDC->Rectangle(0,0,100,100);
? pDC->SelectObject(pOldBrush);
3,2,4 文本显示
Windows可以显示很多数据,包括在窗口中
显示文本信息。由于文本是以图像的形式显示在
窗口中的,因此需要处理设备描述表( DC),另
外还需要对文本字体的处理,包括:文本的显示、
文本的颜色、字符的间距和文本的对齐方式等。
( 1)文本显示
在拥有一个设备描述表以后,就可以调用
TextOut()函数来显示文本行。例如:
pDC->TextOut(20,20,”This is a line of text.”);
TextOut()函数的三个参数分别是输出文本的 X坐标
和 Y坐标以及输出文本串。
( 2)设置文本颜色
在默认情况下,Windows绘制黑色文本。可以通过
SetTextColor()函数改变文本的颜色。例如:
CDC *pDC=GetDC(); //声明一个设备描述表 pDC1
pDC->SetTextColor(RGB(255,0,0)); //设置文本颜色为红色
可以通过 GetTextColor()函数检索到当前文本的颜色,例如:
COLORREF color=pDC->GetTextColor();
SetBkColor()和 GetBkColor()函数用于设置背景颜色和获取当
前的背景颜色。
( 2)设置字符间距
SetTextCharacterExtra()函数用来设置文本字符的间距,
GetTextCharacterExtra()用来获得当前文本字符的间距,函数
说明如下:
pDC-> SetTextCharacterExtra(space);
int space=pDC-> GetTextCharacterExtra();
其中,space表示在文本字符之间使用的额外空间的像素数。
( 3)设置文本的对齐方式
SetTextAlign()函数用于设置显示文本
的对齐方式,函数说明如下:
pDC->SetTextAlign(alignment);
其中,alignment参数取值,TA_LEFT、
TA_CENTER和 TA_RIGHT,分别表示左对
齐、居中方法和右对齐。 Alignment参数取
值,TA_TOP,TA_BOTTOM和
TA_BASELINE分别表示文本在垂直方向的
对齐方式,上对齐、下对齐和字符的基线
对齐。
3.3 鼠标编程
在图形操作系统中,鼠标是最重要的
输入设备之一。 Windows系统为用户提供
了统一的鼠标编程接口,而不必过多了解
其底层的知识。 Windows是基于消息传递、
事件驱动的操作系统,当用户移动鼠标、
按下或释放鼠标键时都会产生鼠标消息。
应用程序可以接收 10种鼠标消息,表 3.3列
出了这些鼠标消息和它们的描述。
3.3.1 鼠标消息处理
MFC把鼠标消息处理函数封装在 CView类中,
它们分别是:
OnMouseMove(UINT nFlags,CPoint point);
OnLButtonDblclk(UINT nFlags,CPoint point);
OnLButtonDown(UINT nFlags,CPoint point);
OnLButtonUp(UINT nFlags,CPoint point);
……
分别对应表 3.3中 10个鼠标消息。在鼠标处理
函数中,point参数代表鼠标热点处的坐标位置,
point.x为横坐标,point.y为纵坐标。默认坐标原
点( 0,0)位于窗口的左上角。由于应用程序要
求自动捕获鼠标事件,因此应当采用 Windows事
件处理函数,而不是成员函数,具体使用方法参
见 3.3.3节示例程序。
nFlags参数中包含了鼠标按钮和键盘
组合使用标志,用来描述鼠标按钮和键盘
上的 Shift键和 Ctrl键的组合状态。 nFlag参数
取值范围:
MK_LBUTTON:鼠标左键被按下;
MK_RBUTTON:鼠标右键被按下;
MK_MBUTTON:鼠标中键被按下;
MK_SHIFT:键盘上的 Shift键被按下;
MK_CONTROL:键盘上的 Ctrl键被按下;
如果想知道某个键是否被按下,可用
对应的位屏蔽值与 nFlags参数作按位逻辑
,与, 运算,所的结果若为非零值,则表
示该按钮被按下,例如:
if (nFlags & LBUTTON)
AfxMessageBox(“LButton is pressed down!”)
Else
AfxMessageBox(“LButton is pressed Up!”);
如何区分两次单击和一次双击,这取决于两
次按下按钮之间的时间间隔,只有当时间间隔小
于一定值时才被认为是 Windows默认的时间为
500ms。可以用 SetDoubleClickTime()函数来重新
设置时间间隔值。
若要使窗口函数能接收到鼠标双击产生的消
息,在注册窗口类时,必须指明该窗口具有
CS_DBLCLKS风格,否则,即使进行了双击操作,
该窗口也只能收到两条, WM_LBUTTONDOWN”
和, WM_LBUTTONUP”消息,例如:
wndclass.style=CS_HREDRAW|CS_VREDRAW|CS_
DBLCLKS;
3.3.2 捕捉鼠标
在交互式图形程序设计中,经常要使
用鼠标的位置拾取、拖动或拖放,这些动
作必须进行鼠标的捕捉。
鼠标捕捉只需要调用
CWnd::SetCapture()函数。用户完成鼠标
捕捉工作后一般是响应一个鼠标按下信息,
要释放鼠标捕捉则是调用
CWnd::ReleaseCapture()函数。释放被捕
捉鼠标的最好时间是在响应鼠标弹起的时
候( WM_LBUTTON)。
3.3.3 鼠标编程综合示例
示例 1:在窗口中以文本的形式给出鼠
标的状态,即当鼠标移动时,给出鼠标的
位置;当鼠标按下鼠标左、右键时显示出
鼠标按键状态。例如,当鼠标左键按下时,
显示, LBUTTON DOWN!”。
第一步:建立一个 myMouse工程文件;
第二步:添加鼠标事件处理函数
鼠标右击视图类(如 CmyMouseView),
选择, add windows message handler…”,
弹出事件处理函数列表窗口,如图 3-11所

图 3-11 Windows事件处理函数列表窗口
从左边事件消息列表中选择, WM_LBUTTONDOWN”,
然后单击, Add and Edit”按钮,即加入鼠标左键按下事件
函数,并要求编辑事件处理程序。
第三步:输入事件处理程序
void CMymouseView::OnLButtonDown(UINT nFlags,
CPoint point)
{
// TODO,Add your message handler code here and/or
call default
//获得 pDC
CDC* pDC=GetDC();
pDC->TextOut(20,40,”LBUTTONDOWM!”); // 输出显示信

CView::OnLButtonDown(nFlags,point);
}
其中,阴影部分是用户输入的程序,其它内
容都是自动生成的内容。
第四步:重复第二步和第三步,分别添加
WM_LBUTTONUP,WM_MOUSEMOVE,
WM_RBUTTONDOWN,WM_RBUTTONUP,
WM_LBUTTONDBCLK,WM_RBUTTONDBCLK鼠
标事件,并输入以下程序:
void CMouseView::OnLButtonUp(UINT nFlags,
CPoint point)
{
// TODO,Add your message handler code here
and/or call default
CDC *pDC=GetDC();
pDC->TextOut(20,40,"LButton UP!");
CView::OnLButtonUp(nFlags,point);
}
? void CMouseView::OnRButtonDown(UINT nFlags,CPoint
point)
? {
? // TODO,Add your message handler code here
and/or call default
? CDC *pDC=GetDC();
? pDC->TextOut(20,60,"RButton Down!");
? CView::OnRButtonDown(nFlags,point);
? }
? void CMouseView::OnRButtonUp(UINT nFlags,CPoint
point)
? {
? // TODO,Add your message handler code here
and/or call default
? CDC *pDC=GetDC();
? pDC->TextOut(20,40,"RButton UP!");
? CView::OnRButtonUp(nFlags,point);
? }
? void CMouseView::OnMouseMove(UINT nFlags,
CPoint point)
? {
? // TODO,Add your message handler code
here and/or call default
? CDC *pDC=GetDC();
? char tbuf[80];
? sprintf(tbuf,"Position:(%3d,%3d)",point.x,poi
nt.y);
? // 输出鼠标当前位置
? pDC->TextOut(20,20,tbuf);
? CView::OnMouseMove(nFlags,point);
? }
? void CMouseView::OnLButtonDblClk(UINT
nFlags,CPoint point)
? {
? // TODO,Add your message handler code here
and/or call default
? CDC *pDC=GetDC();
? pDC->TextOut(20,80,"LButton is double clicked!");
? CView::OnLButtonDblClk(nFlags,point);
? }
? void CMouseView::OnRButtonDblClk(UINT nFlags,CPoint
point)
? {
? // TODO,Add your message handler code here
and/or call default
? CDC *pDC=GetDC();
? pDC->TextOut(20,80,"RButton is double clicked!");
? CView::OnRButtonDblClk(nFlags,point);
? }
第五步:编译程序,并验证执行结果。
示例 2:采用鼠标橡皮筋技术画圆
鼠标橡皮筋技术画圆就是采用圆心和圆周上任一点画
圆技术(简称 C+P方法),首先用鼠标左击选择圆心位置,
然后移动鼠标,圆随鼠标移动而扩大或缩小,当再次单击
鼠标左键时,确定圆周上的一点,从而画出相应的圆。直
线、矩形等基本图形都可以采用橡皮筋技术。
第一步:建立 MouseSpring工程文件;
第二步:向视图类中添加自定义的成员变量
用鼠标右键单击视图类,选择, Add Member
Variable…”,添加下面三个成员变量。
? proctected,
? CPoint m_bO; // 圆心
? CPoint m_bR; //圆上的点
? int m_ist; //圆心与圆周上点的区别,m_ist=0,
表示鼠标左击点为圆心,
? //m_ist=1,表示鼠标左击点为圆周上的点
操作方法如图 3-13所示,分别添加上述三个成员变量。
图 3-13 添加成员变量
第三步:向视图类中添加自定义的成
员函数原型:
? public:
? void DrawCircle(CDC* pDC,CPoint
cenp,CPoint ardp);
? int ComputeRadius(CPoint
cenp,CPoint ardp);
具体操作方法:用鼠标右键单击视图
类,选择, Add Member Function…”,如
图 3-14所示。分别添加上述两个成员函数,
分别用于画圆和计算圆的半径。
图 3-14 添加成员函数
第三步:在视图类 CPP文件的构造函数中初
始化成员变量。
视图类的构造函数名与该视图类的名字相同。
在视图类中选择构造函数,如:
CMouseSpringView(),用鼠标左键双击,输入下
面程序代码:
? CMouseSpringView:,CMouseSpringView()
? {
? //TODO,add construction code here
? m_bO.x=0; m_bO.y=0; //圆心
? m_bR.x=0; m_bR.y=0; //圆上的点
? m_ist=0; //圆心与圆上的点区别
? }
第四步:在视图类的 OnDraw()函数中加入下
列代码,实现视图绘图。
? void CMouseSpringView::OnDraw(CDC* pDC)
? {
? CMouseSpringDoc* pDoc = GetDocument();
? ASSERT_VALID(pDoc);
?
? // TODO,add draw code for native data here
? pDC->SelectStockObject(NULL_BRUSH);
? DrawCircle(pDC,m_bO,m_bR); // 调用自定
义的成员函数画圆
? }
第五步:向视图类中添加两个鼠标消息响应函数,并
输入鼠标处理程序代码。
具体操作方法与鼠标示例 1方法相同。一个是
OnLButtonDown()函数,另一个是 OnMouseMove()函数。
程序如下:
? void CMouseSpringView::OnLButtonDown(UINT nFlags,
CPoint point)
? {
? // TODO,Add your message handler code here
and/or call default
? CDC *pDC=GetDC();
? pDC->SelectStockObject(NULL_BRUSH);
? if (!m_ist) //绘制圆
? {
? m_bO=m_bR=point; //纪录第一次单击鼠标位
置,定圆心
? m_ist++;
? }
? else
? {
? m_bR=point; //记录第二次单击鼠标的位置,
定圆周上的点
? m_ist--; // 为新绘图作准备
? DrawCircle(pDC,m_bO,m_bR); //绘制新圆
? }
? ReleaseDC(pDC); //释放设备环境
? CView::OnLButtonDown(nFlags,point);
? }
? void CMouseSpringView::OnMouseMove(UINT nFlags,
CPoint point)
? {
? // TODO,Add your message handler code here
and/or call default
? CDC *pDC=GetDC();
? int nDrawmode=pDC->SetROP2(R2_NOT); //设置异
或绘图模式,并保存原来绘图模式
? pDC->SelectStockObject(NULL_BRUSH);
? if(m_ist==1)
? {
? CPoint prePnt,curPnt;
? prePnt=m_bR; //获得鼠标所在的前一位置
? curPnt=point;
? //绘制橡皮筋线
? DrawCircle(pDC,m_bO,prePnt); //用异或模式
重复画圆,擦出所画的圆
? DrawCircle(pDC,m_bO,curPnt); //用当前位置作为
圆周上的点画圆
? m_bR=point;
? }
? pDC->SetROP2(nDrawmode); //恢复原绘图模式
? ReleaseDC(pDC); //释放设备环境
? CView::OnMouseMove(nFlags,point);
? }
第六步:添加成员函数的程序代码。
? 分别为两个成员函数 DrawCircle()和
ComputeRadius()添加程序代码,程序如下:
? void CMouseSpringView::DrawCircle(CDC *pDC,
CPoint cenp,CPoint ardp)
? {
? int radius=ComputeRadius(cenp,ardp);
? // 由圆心确定所画圆的外切区域
? CRect rc(cenp.x-radius,cenp.y-
radius,cenp.x+radius,cenp.y+radius);
? pDC->Ellipse(rc); //画出一个整圆
? }
? int
CMouseSpringView::ComputeRadius(CPoin
t cenp,CPoint ardp)
? {
? int dx=cenp.x-ardp.x;
? int dy=cenp.y-ardp.y;
? //sqrt()函数的调用,在头文件中加入
#include "math.h"
? return (int)sqrt(dx*dx+dy*dy);
? }
第七步:编译运行程序,验证运行结果。
3.4 菜单程序设计
在 Windows应用程序设计中,菜单是重
要的用户界面对象和交互手段。 Windows
支持三种类型的菜单,他们分别是菜单栏
(主菜单)、弹出式菜单和上下文菜单
(单击鼠标右键弹出的浮动菜单)。本节
主要介绍如何对菜单进行编辑、如何响应
菜单的消息、如何运用菜单的 UI机制、如
何动态地改变菜单以及如何处理上下文菜
单。
3.4.1 菜单编辑器
菜单编辑器用来创建并编辑菜单资源,是一
个可视化设计工具。对于 MDI应用程序(多文档
应用程序),AppWizard自动生成两个菜单资源:
IDR_MAINFRAME和 IDR_PrjNameTYPE (PrjName
是应用程序工程名 )。在 MDI子窗口打开之前系统
显示 IDR_MAINFRAME菜单,在 MDI子窗口打开之
后系统显示 IDR_ PrjNameTYPE菜单。对于 SDI应
用程序,AppWizard只生成一个菜单资源:
IDR_MAINFRAME。
在一个 SDI文档工程项目中,在工作区窗口
种选择 ResourceView标签,列出工程项目的所有
资源,选择 Menu,双击 IDR_MAINFRAME,弹出
菜单编辑器窗口,如图 3-15所示。
图 3-15 菜单编辑器
( 1)创建菜单和菜单选项
在图 3-15中,可以创建主菜单,也可以创建
菜单选项。可以通过 Tab键(向右移)、
Shift+Tab键(向左移),或鼠标定位,通过 Ins
键在某一菜单前插入新的菜单。另外,用鼠标拖
动菜单方框可以改变菜单项的相对位置。例如,
在查看菜单前插入一个, 绘图, 菜单,包括直线、
圆、矩形和颜色四个菜单项。
步骤 1:定位到, 查看, 菜单,按下 Ins键或
Insert键,插入一个空菜单项;通过 Delete键删除
一个菜单项;
步骤 2:双击空菜单项,弹出菜单项对话框,
并输入菜单信息,如图 3-16所示。
图 3-16 菜单对话框
步骤 3:添加菜单项条目。
双击, 绘图, 菜单下的空菜单项,弹
出菜单项对话框。如图 3-17所示。
图 3-17 菜单项对话框
步骤 4:重复第 3步,完成菜单设计。
在菜单设计中,可以为菜单或菜单项定义助
记符,方法是在响应的字符前加符号 &。菜单项
的 ID号,可以选取已有的 ID号,也可以自定义 ID
号,如果不输入 ID号,则系统自动生成一个 ID号。
另外,在菜单项对话框中还可以为菜单项指定风
格。另外,还可以为菜单项定义快捷键,方法是
在标题后直接输入,转义符 \t表示快捷键左对齐。
(2)弹出菜单的设计
弹出菜单就是主菜单项的子菜单,也称为级联
菜单。
创建级联菜单的方法如下:选择级联菜单
项,在该菜单项属性对话框中选中, 弹出,
( Pop-up)复选项,于是该项便被标记级联菜单
符( ?),且在该项的右侧出现新的菜单项空方框。
添加级联菜单项的方法与上述方法相同。如图 3-
18所示。
图 3-18 级联菜单设计
( 3)上下文菜单
单击鼠标右键将弹出相应的上下文菜单。为
了在应用程序中使用上下文菜单,首先要创建菜
单本身,然后将其与应用程序代码链接。创建上
下文菜单的步骤如下:
步骤 1:创建带空标题的菜单栏。右击 Menu,
选择, Insert”,创建一个空的菜单栏;
步骤 2:输入菜单标题和菜单项,并保存菜
单资源,默认为 IDR_MENI1;
步骤 3:在源文件中添加下列程序代码:
CMenu menu;
// 装载并验证菜单资源;
? VERIFY(menu.LoadMenu(IDR_MENU1));
? CMenu *pPopup=menu.GetSubMenu(0);
? ASSERT(pPopup!=NULL);
? //显示菜单内容
? pPopup-
>TrackPopupMenu(TPM_LEFTALIGN|TPM
_RIGHTBUTTON,
? x,y,AfxGetMainWnd());
在创建上下文菜单资源后,应用程序
代码装载菜单资源并使用函数
TrackPopupMenu()来显示菜单内容。
3.4.2 菜单消息响应
Windows应用程序是通过消息传递机制
运行的。为菜单项添加相应功能函数的方
法步骤:
步骤 1:右击所选菜单项,从弹出的菜
单中选择, 建立类向导 …”,弹出类向导对
话框,如图 3-19所示;
图 3-19 建立类向导对话框
步骤 2:选择工程名( Project:),并在
类名( Class name:)中选择视图类,在
Object IDs:列表中选择菜单项的 ID号,在
Messages:列表中选择 COMMAND;
步骤 3:单击, Add Function…”,添加
成员函数;
步骤 4:选择该成员函数,单击, Edit
Code”输入或编辑程序代码。
3.4.3 综合示例
利用菜单资源编辑器在默认的菜单中
添加, 字体, 菜单项。在程序运行过程中
改变菜单项的显示状态。
步骤 1:创建 Mymenu应用程序工程文
件,选择单文档应程序;
步骤 2:利用 Resource View设计菜单,
如图 3-20所示;
图 3-20 菜单设计示例
添加方法:选择 View\ClassWizard菜单项,弹
出如图 3-19所示的创建类对话框,从中选择工程
名( Projects)和类名( Class name),并从
Object IDs列表框中选择 ID_FONTCOLOR1项,在
Messages列表框中选择 COMMAND,然后,单击
,Add Function”按钮,弹出加入成员函数的对话
框,输入成员函数名 OnFontcolor1,确定后就添
加了 OnFontcolor1消息映射函数。
参照以上方法,添加上表中其他的函数。
步骤 4:在 CMymenuView类中添加成员变量
和成员函数
方法参见鼠标编程中的示例 2,在 CMymenuView类中
添加上述的成员变量和成员函数。
步骤 5:编写 CTestMenuView:,CMymenuView构造函数
? CTestMenuView::CTestMenuView()
? {
? // TODO,add construction code here
? m_FontColor=RGB(0,0,0); //设置默认的颜色
? }
? 步骤 6:编写 CMymenuView::OnDraw()函数。
? void CTestMenuView::OnDraw(CDC* pDC)
? {
? CTestMenuDoc* pDoc = GetDocument();
? ASSERT_VALID(pDoc);
? // TODO,add draw code for native data here
? Redraw(pDC);
? }
步骤 7:分别编写步骤 3添加的 3个函数,程序如下:
? void CMymenuView::OnFontcolor1()
? {
? // TODO,Add your command handler code here
? m_FontColor=RGB(250,0,0);
? CDC * pDC=GetDC();
? Redraw(pDC);
? }
? void CMymenuView::OnFontcolor2()
? {
? // TODO,Add your command handler code here
? m_FontColor=RGB(0,250,0);
? CDC * pDC=GetDC();
? Redraw(pDC);
? }
? void CMymenuView::OnFontcolor3()
? {
? // TODO,Add your command handler code here
? m_FontColor=RGB(0,0,250);
? CDC * pDC=GetDC();
? Redraw(pDC);
? }
? 步骤 8:编写 CTestMenuView::Redraw()函数
? void CMymenuView::Redraw(CDC *pDC)
? {
? //设置文本颜色,显示测试内容
? pDC->SetTextColor(m_FontColor);
? pDC->TextOut(30,30,"菜单测试程序 !");
? }
步骤 9:编译和运行程序,查看程序运行结果。
作业与练习