VisualC实用教程第3版课件

上传人:博****1 文档编号:592175497 上传时间:2024-09-19 格式:PPT 页数:782 大小:4.01MB
返回 下载 相关 举报
VisualC实用教程第3版课件_第1页
第1页 / 共782页
VisualC实用教程第3版课件_第2页
第2页 / 共782页
VisualC实用教程第3版课件_第3页
第3页 / 共782页
VisualC实用教程第3版课件_第4页
第4页 / 共782页
VisualC实用教程第3版课件_第5页
第5页 / 共782页
点击查看更多>>
资源描述

《VisualC实用教程第3版课件》由会员分享,可在线阅读,更多相关《VisualC实用教程第3版课件(782页珍藏版)》请在金锄头文库上搜索。

1、第第1章章 基本基本C+语言语言C+是在20世纪80年代初期由贝尔实验室设计的一种在C语言的基础上增加了对面向对象程序设计支持的语言,它是目前应用最为广泛的编程语言。本章先来说明C+程序结构,然后详细讨论数据类型、运算符与表达式、基本语句、函数和预处理、构造类型、指针和引用等内容。需要说明的是,在学习本章之前最好先做实验1。1.1 C+程序结构程序结构同其他程序设计语言一样,C+也有自己的程序结构。1.1.1 几个几个C+程序程序下面先来介绍几个比较简单的C+程序。例例Ex_Simple1 一个简单的一个简单的C+程序程序#include stdafx.h/*C+程序的基本结构*/#inclu

2、devoidmain()doubler,area;/声明变量coutr;/从键盘上输入变量r的值area=3.14159*r*r;/计算面积cout圆的面积为:arean;/输出面积1.1.1 几个几个C+程序程序例例Ex_Simple2 在屏幕上输出一个由星号形成的三角形在屏幕上输出一个由星号形成的三角形/输出星号的三角形阵列#includevoidDoDraw(intnum);/声明一个全局函数voidmain()intnum=5;/定义并初始化变量DoDraw(num);/函数的调用voidDoDraw(intnum)/函数的定义for(inti=0;inum;i+)/循环语句for(i

3、ntj=0;j=i;j+)cout*;coutn;主函数main和被调用的函数DoDraw。DoDraw函数是在屏幕上输出星号的三角形阵列,这个阵列的行数以及每行星号的个数由num决定。结果如下:1.1.1 几个几个C+程序程序例例Ex_Simple3 用类的概念重写例用类的概念重写例Ex_Draw#includeclassCDrawArray/定义一个类public:voidDoDraw(intnum);/声明类的公有成员函数;voidCDrawArray:DoDraw(intnum)/成员函数的实现for(inti=0;inum;i+)for(intj=0;j=i;j+)cout*; co

4、utn;voidmain()intnum=5;CDrawArraymyDraw;/定义类的一个对象myDraw.DoDraw(num);/调用此对象的成员函数虽然本程序的作用和例Ex_Simple2是一样的,但它引用了类的概念,是一个面向对象的C+程序。程序中class后的名称是要定义的类名,该类仅声明了一个公共类型的成员函数DoDraw。调用时,先定义该类的对象,然后像myDraw.DoDraw(num)语句那样调用。1.1.2 C+程序的基本组成程序的基本组成从上面的几个例子可以看出,一个C+程序往往由预处理命令、语句、函数、变量和对象、输入与输出以及注释等几个基本部分组成的。(1)预处理

5、命令。(2)语句。(3)函数。(4)变量和对象。(5)输入与输出。(6)注释。1.1.3 C+程序的书写风格程序的书写风格1. 标识符命名标识符命名标识符是用来标识变量名、函数名、数组名、类名、对象名等的有效字符序列。下面几个原则是命名时所必须注意的。(1)合法性。C+规定标识符由大小写字母、数字字符(09)和下划线组成,且第一个字符必须为字母或下划线。任何标识符中都不能有空格、标点符号及其他字符,(2)有效性。(3)易读性。1.1.3 C+程序的书写风格程序的书写风格2. 缩进和注释缩进和注释缩进每个“”花括号占一行,并与使用花括号的语句对齐。花括号内的语句采用缩进书写格式,缩进量为四个字符

6、(一个默认的制表符)。注释要注意的是:(1)注释应在编程的过程中同时进行,不要指望程序开发完成后再补写注释。(2)必要的注释内容应包含:在源文件头部进行必要的源程序的总体注释:版权说明、版本号、生成日期、作者、内容、功能、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。在函数的头部进行必要的函数注释:函数的目的/功能、输入参数、输出参数、返回值、调用关系(函数、表)等。其他的少量注释。如全局变量的功能、取值范围等。千万不要陈述那些一目了然的内容,否则会使注释的效果适得其反。1.2.1 基本数据类型基本数据类型C+基本数据类型有char(字符型)、int(整型)、float(

7、单精度实型)和double(双精度实型),“实型”又可称为“浮点型”。在ANSIC+中,基本数据类型还有wchar_t(双字节字符型)和bool(布尔型,值为false或true,而false用0表示,true用1表示)。需要说明的是:(1)无符号(unsigned)和有符号(signed)的区别在于数值最高位的含义。对于signed类型来说,最高位是符号位,其余各位表示数值大小;而unsigned类型的各个位都用来表示数值大小;因此相同基本数据类型的signed和unsigned的数值范围是不同。例如,无符号字符型值的范围为0255,而有符号字符型值的范围为-128-127。(2)char、

8、short、int和long可统称为整型。默认时,char、short、int和long本身是有符号(signed)的。1.2.1 基本数据类型基本数据类型基本数据类型数据类型构 造 类 型字符型单字符型 char宽字符型 wchar_t整型 int实 型单精度 float双精度 double逻辑型 bool空值型 void派生类型复合类型指针 type *引用 type &数组 type结构体 struct共用体 union枚举 enum类 class图1.1C+的数据类型1.2.1 基本数据类型基本数据类型表表1.1 C+的基本数据类型的基本数据类型1.2.2 常量常量根据程序中数据的可变

9、性,数据可以分为常量和变量两大类。在程序运行过程中,其值不能被改变的量称为“常量”。常量,又称为“直接量”,可分为不同的类型,如1、20、0、-6为整型常量,1.2、-3.5为实型常量,a、b为字符常量。常量一般从其字面形式即可判别。下面介绍各种不同数据类型常量的表示方法。1.2 .2常量常量1. 整型常量整型常量整型常量可以用十进制、八进制和十六进制来表示。(1)十进制整型常量。即十进制整数,如34、128等。(2)八进制整型常量。以数字0开头的数是八进制整数,它由0至7的数字组成。如045,即(45)8,表示八进制数45,等于十进制数37;-023表示八进制数-23,等于十进制数-19。(

10、3)十六进制整型常量。以0x或0X开头的数是十六进制整数,它由0至9、A至F或a至f组成。例如0x7B,即(7B)16,等于十进制的123,-0X1a等于十进制的-26。需要注意的是:整型常量中的长整型(long)要以L或小写字母l作为结尾,如3276878L,496l等。整型常量中的无符号型(unsigned)要以U或u作为结尾,如2100U,6u,100ul(表示unsignedlong)等。1.2.2 常量常量实型常量实型常量实型常量即实数,它有十进制数或指数两种表示形式。(1)十进制数形式。十进制数形式是由整数部分和小数部分组成的(注意必须有小数点)。例如0.12、.12、1.2、12

11、.0、12.、0.0都是十进制数形式。(2)指数形式。指数形式采用科学表示法,它能表示出很大或很小的实数。例如1.2e9或1.2E9都代表1.2x109,注意字母E(或e)前必须有数字,且E(或e)后面的指数必须是整数。若实型常量是以F(或f)结尾的,则表示单精度实型(float),以L(或小写字母l)结尾的,表示长双精度实型(longdouble)。若一个实型常量没有任何说明,表示双精度实型(double)。1.2 .2常量常量字符常量字符常量字符常量是用单引号括起来的一个字符。如A、g、%、等都是字符常量。注意B和b是两个不同的字符常量。C+还可以用一个“”开头的字符序列来表示特殊形式的字

12、符常量。表1.2列出了常用的转义序列符。表表1.2 C+中常用转义序列符中常用转义序列符1.2.2 常量常量字符串常量字符串常量C+语言中除了允许使用字符常量外,还允许使用字符串常量。字符串常量是一对双引号括起来的字符序列。例如:“Hello,World!n”“C+语言”“abcdef”等等都是字符串常量。字符串常量中还可以包含空格、转义序列符或其他字符。由于双引号是字符串的分界符,因此如果需要在字符串中出现双引号则必须用“”表示。例如:“Pleasepress“F1”tohelp!”这个字符串被解释为:Pleasepress“F1”tohelp!字符串常量应尽量在同一行书写,若一行写不下,可

13、用来连接,例如:“ABCDEFGHIGK.”1.2 常量常量符号常量符号常量在C+中,也可以用一个标识符来代替一个常量。例如:例例Ex_PI1 用用#define定义符号常量定义符号常量#include#definePI3.14159voidmain()doubler=100.0,area;area=PI*r*r;cout圆的面积是:arean;程序中用#define命令行定义PI,使其代替常量3.141593,此后凡是在程序中出现的PI都代表3.14159,可以和常量一样进行运算。在C+中,除了用#define定义符号常量外,C+还常常用const定义的变量来代替#define定义的符号常量

14、。1.2.2 常量常量例例Ex_PI2 用用const定义的变量代替符号常量定义的变量代替符号常量#includeconstdoublePI=3.14159;voidmain()doubler=100.0,area;area=PI*r*r;cout圆的面积是:arean;与例Ex_PI1相比,程序代码几乎一样,不同的地方是第二行中的const和例Ex_PI1中的#define不一样。1.2.3 变量变量变量是指在程序执行中其值可以改变的量。变量有3个基本要素:C+合法的变量名、变量类型和变量的数值。1. 变量的定义变量的定义一般格式语句进行定义的(凡格式中出现的尖括号凡格式中出现的尖括号“”,

15、表示,表示括号中的内容是必需指定,若为方括号括号中的内容是必需指定,若为方括号“”,则括号中的内容是可选的,本书,则括号中的内容是可选的,本书作作此约定此约定):;变量名和数据类型是告诉编译器要为其分配多少内存空间,以及变量中要存取的是什么类型的数据。例如:intnNum1;intnNum2;intnNum3;double x;这样,nNum1、nNum2、nNum3分别占用4个字节的存储空间,其存取的数据类型是int型,称之为“整型变量”,而x则占用8个字节的存储空间,存取的数据类型是double型,称之为“双精度实型变量”。有时,为使代码简洁,还可以将同类型的变量定义在一行语句中,不过同类

16、型的变量名要用逗号(,)分隔。1.2.3 变量变量2. 变量的初始化变量的初始化程序中常需要对一些变量预先设置初值,即将初值存储在变量名所代表的内存空间,这一过程称为初始化。在C+中,变量初始化是在定义变量时同时赋初值。例如:intnNum1=3;/指定nNum1为整型变量,初值为3doublex=1.28;/指定x为双精度实变量,初值为1.28charc=G;/指定c为字符变量,初值为G也可以在多个变量的定义语句中单独对某个变量初始化,如:intnNum1,nNum2=3,nNum3;变量的初始化还有另外一种形式,例如:intnX(1),nY(3);表示nX和nY是整型变量,它们的初值分别为

17、1和3。1.2.4 基本输入、输出基本输入、输出1. 输出流输出流(cout)通过cout可以输出一个整数、实数、字符及字符串,cout中的插入符“”可以连续写多个,每个后面可以跟一个要输出的常量、变量、转义序列符、对象以及表达式等。 例例Ex_CoutEndl cout的输出算子的输出算子endl#includevoidmain()coutABCDt1234tendl;执行该程序,结果如下:ABCD1234程序中“t”是制表符(见表1.2),它将后面的1234在水平的下一个制表位置输出。endl是C+中控制输出流的一个操作算子(预定义的对象),它的作用和n等价,都是结束当前行,并将屏幕输出的

18、光标移至下一行。1.2.4 基本输入、输出基本输入、输出实际上,为了更好地调整输出格式,有时还可以使用下面的输出函数。(1)width()函数。width()函数有两种格式:int width();int width(int);第一种格式用来获取当前输出数据时的宽度,另一种格式是用来设置当前输出数据时的宽度。(2)precision()函数。与width()相似,precision()也有两种格式:int precision();int precision(int);(3)fill()函数。fill()函数也有下列两种格式,这两种格式分别用来获取和设置当前宽度内的填充字符,第二种格式函数还将返

19、回设置前的填充字符。char fill();char fill(char);1.2.4 基本输入、输出基本输入、输出下面通过一个例子说明上述格式输出函数的用法。例例Ex_CoutFrm cout的格式输出的格式输出#includevoidmain()intnNum=1234;doublefNum=12.3456789;cout1234567890endl;cout.width(10);coutnNumn;cout.width(10);coutfNumendl;coutcout.precision(4)endl;coutfNumendl;cout.fill(#);cout.width(10);c

20、outfNum.其中,提取符“”可以连续写多个,每个后面跟一个表达式,该表达式通常是获得输入值的变量或对象。例如:intnNum1,nNum2,nNum3;cinnNum1nNum2nNum3;要求用户从键盘上输入三个整数。输入时,必须在3个数值之间加上一些空格来分隔,空格的个数不限,最后用回车键结束输入(书中出现的书中出现的“ ”表示输入一表示输入一个回个回车键,特此约定车键,特此约定);或者在每个数值之后按回车键。例如,上述输入语句执行时,用户可以输入:129 20或129201.2.4 基本输入、输出基本输入、输出3.格式算子格式算子格式算子oct、dec和hex能分别将输入或输出的数值

21、转换成oct、dec和和hex八进制、十进制及十六进制,例如:例例Ex_Algorism 格式算子的使用格式算子的使用#includevoidmain()intnNum;couthexnNum;coutOcttoctnNumendl;coutDectdecnNumendl;coutHexthexnNumendl;程序执行时,结果如下:1.3.1 算术运算符算术运算符算术运算符包括双目的加减乘除四则运算符、求余运算符以及单目的正负运算符。C+中没有幂运算符,幂运算符是通过函数来实现的。算术运算符如下所示:+(正号运算符,如+4,+1.23等)-(负号运算符,如-4,-1.23等)*(乘法运算符,

22、如6*8,1.4*3.56等)/(除法运算符,如6/8,1.4/3.56等)%(模运算符或求余运算符,如40%11等)+(加法运算符,如6+8,1.4+3.56等)-(减法运算符,如6-8,1.4-3.56等)1.3.1 算术运算符算术运算符(5)溢出处理在C+中,当某数除以0或当其它溢出时,编译系统将报告错误并终止程序运行。但对整数溢出,系统却不认为是一个错误,这在编程时需要特别小心。例如:例例Ex_OverFlow 一个整型溢出的例子一个整型溢出的例子#includevoidmain()shortnTotal,nNum1,nNum2;nNum1=nNum2=1000;nTotal=nNum

23、1*nNum2;coutnTotaln;程序运行的结果是169601.3.2 赋值运算符赋值运算符1. 复合赋值复合赋值在C+语言中,规定了10种复合赋值运算符:+=,-=,*=,/=,%=,=,|=,=,=它们都是在赋值符“=”之前加上其它运算符而构成的,其中的算术复合赋值运算符的含义如表1.3所示。表表1.3 复合赋值运算符复合赋值运算符1.3.2 赋值运算符赋值运算符1. 复合赋值复合赋值复合运算符的优先级和赋值符的优先级一样,在C+的所有运算符中只高于逗号运算符,而且复合赋值运算符的结合性也是从右至左的,所以在组成复杂的表达式时要特别小心。例如:a*=b-4/c+d;等效于a=a*(b

24、-4/c+d);而不等效不等效于a=a*b-4/c+d;1.3.2 赋值运算符赋值运算符2.多重赋值多重赋值所谓多重赋值是指在一个赋值表达式中出现两个或更多的赋值符(“=”),例如:nNum1=nNum2=nNum3=100;由于赋值符的结合性是从右至左的,因此上述的赋值是这样的过程:首先对赋值表达式nNum3=100求值,即将100赋值给nNum3,同时该赋值表达式取得值100;然后将该值赋给nNum2,这是第二个赋值表达式,该赋值表达式也取得值100;最后将100赋给nNum1。由于赋值是一个表达式,所以它几乎可以出现在程序的任何地方,例如:a=7+(b=8)(赋值表达式值为15,a值为1

25、5,b值为8)a=(c=7)+(b=8)(赋值表达式值为15,a值为15,c值为7,b值为8)1.3.3 数据类型转换数据类型转换在进行运算时,往往要遇到混合数据类型的运算问题。例如一个整型数和一个实数相加就是一个混合数据类型的运算。C+采用两种方法对数据类型进行转换,一种是“自动转换”,另一种是“强制转换”。1.3.3 数据类型转换数据类型转换自动转换自动转换自动转换是将数据类型从低到高的顺序进行转换,如图1.2所示。例如:10+a+2*1.25-5.0/4L的运算次序如下:(1)进行2*1.25的运算,将2和1.25都转换成double型,结果为double型的2.5。(2)进行5.0/4

26、L的运算,将长整型4L和5.0都转换成double型,结果值为1.25。(3)进行10+a的运算,先将a转换成整数97,运算结果为107。(4)整数107和2.5相加,先将整数107转换成double型,结果为double型,值为109.5。(5)进行109.5-1.25的运算,结果为double型的108.25。图图1.2 类型转换的顺序类型转换的顺序1.3.3 数据类型转换数据类型转换2. 强制转换强制转换强制转换是在程序中通过指定数据类型来改变图1.2所示的类型转换顺序,将一个变量从其定义的类型改变为另一种新的类型。强制类型有下列两种格式:()()这里的“类型名”是任何合法的C+数据类型

27、,例如float、int等。通过类型的强制转换可以将“表达式”转换成适当的类型,例如:doublef=3.56; intnNum;nNum=(int)f;或者nNum=int(f);都是将使nNum的值变为3。1.3.4 关系运算符关系运算符关系运算是逻辑运算中比较简单的一种。所谓“关系运算”实际上是比较两个操作数是否符合给定的条件。若符合条件,则关系表达式的值为“真”,否则为“假”。在C+编译系统中,往往将“真”表示为“true”或1,将“假”表示为“false”或0。而任何不为0的数被认为是“真”,0被认为是“假”。由于关系运算需要两个操作数,所以关系运算符都是双目运算符。C+提供了下列6

28、种关系运算符:(小于),(大于),=(大于等于),=(相等于),!=(不等于)其中,前4种的优先级相同且高于后面的两种。例如:a=bc等效于a=(bc)但关系运算符的优先级低于算术运算符(其他可参见表1.4)。例如:a=bc等效于a=(b3&2|83&2|83”,结果为“真”,这里用1表示。(4)处理“83”,结果为“假”,这里用0表示。这样表达式变成“1&2|0”(5)进行“1&2”的运算,结果为1(“真”),因为2是不等于0的数。(6)最后结果为1(“真”)。1.3.6 位运算符位运算符位运算符是对操作数按其在计算机内表示的二制数逐位地进行逻辑运算或移位运算,参与运算的操作数只能是整型常量

29、或变量。C+语言提供了六种位运算符:(按位求反,单目运算符)(右移,双目运算符)&(按位与,双目运算符)(按位异或,双目运算符)|(按位或,双目运算符)“按位求反”是将一个二进制数的每一位求反,即0变成1,1变成0。“按位与”是将两个操作数对应的每个二进制位分别进行逻辑与操作。“按位或”是将两个操作数对应的每个二进制位分别进行逻辑或操作。“按位异或”是将两个操作数对应的每个二进制位分别进行异或操作。“左移”是将左操作数的二进制值向左移动指定的位数,它具有下列格式:操作数移位的位数1.3.7 三目运算符三目运算符C+中惟一的三目运算符是条件运算符,其格式如下:?:“条件表达式”是C+中可以产生“

30、真”和“假”结果的任何表达式,如果条件表达式的结果为“真”,则执行表达式1,否则执行表达式2。例如:nNum=(ab)?10:8;注意,只有在表达式2后面才能出现分号结束符,“表达式1”和“表达式2”中都不能有分号。1.3.8 自增和自减运算符自增和自减运算符单目运算符自增(+)和自减(-)为整型变量加1或减1提供一种非常有效的方法。+和-既可放在变量的左边也可以出现在变量的右边,分别称为前缀运算符和后缀运算符。例如:i+;或+i;(等效于i=i+1;或i+=1;)i-;或-i;(等效于i=i-1;或i-=1;)这是要特别注意:若前缀运算符和后缀运算符仅用于某个变量的增1和减1,则这两都是等价

31、的,但如果将这两个运算符和其他的运算符组合在一起,在求值次序上就会产生根本的不同:如果用前缀运算符对一个变量增1(减1),在将该变量增1(减1)后,用新的值在表达式中进行其他的运算。如果用后缀运算符对一个变量增1(减1),用该变量的原值在表达式进行其他的运算后,再将该变量增1(减1)。例如:a=5;b=+a-1;/相当于a=a+1;b=a1;和a=5;b=a+-1;/相当于b=a1;a=a+1;1.3.8 自增和自减运算符自增和自减运算符虽然它们中的a值的结果都是6,但b的结果却不一样,前者为5,后者为4。在自增和自减混合运算时,一要注意次序,二要注意变量是表示相应的存储空间这个特性。例如:b

32、=a+*-a*a+;/相当于a=a1;b=a*a*a;a=a+1;a=a+1;/若a初值为5,该句执行后,则b=64,a=6;这比较好理解,若a的初值为5,当有:b=-a*-a*-a;则不同的编译器有不同的处理方式,TurboC+或BorlandC+认为其相当于:a=a1;a=a1;a=a1;b=a*a*a;显然执行该语句后的结果为a=2,b=8。而VisualC+先计算-a*-a,即其相当于:a=a1;a=a1;b=a*a;此时a=3,b=9;然后计算9*-a,即其相当于:a=a1;b=9*a;结果a=2,b=18。无论是何种处理方式,可千万不要认为是b=4*3*2,那是完全错误的,因为变量

33、a是表示一个相应的存储空间,在同一运行周期中,其存储的数值不应有两种可能。再比如,若a的初值为5,当有:b=a+*-a*-a;/执行该语句后,TC+或BC+:a=4,b=27。VC+:a=4,b=48。1.3.9 逗号运算符逗号运算符逗号运算符是优先级最低的运算符,它可以使多个表达式放在一行上,从而大大简化了程序。在计算时,C+将从左至右逐个计算每个表达式,最终整个表达式的结果是最后计算的那个表达式的类型和值。例如:j=(i=12,i+8);式中,i=12,i+8是含逗号运算符的表达式,计算次序是先计算表达式i=12,然后再计算i+8,整个表达式的值是最后一个表达式的值,即i+8的值20,从而

34、j的结果是20。再如:d=(a=1,b=a+2;c=b+3);d的结果为6。1.3.10sizeof运算符sizeof的目的是返回操作数所占的内存空间大小(字节数),它具有下列两种格式:sizeof()sizeof()例如:sizeof(“Hello”)/计算字符串常量“Hello”所占内存的字节大小,结果为6sizeof(int)/计算整型int所占内存的字节数需要说明的是,由于同一类型的操作数在不同的计算机中占用的存储字节数可能不同,因此sizeof的结果有可能不一样。例如sizeof(int)的值可能是4,也可能是2。1.3.11 优先级、结合性和运算次序优先级、结合性和运算次序1.4.

35、1 表达式语句、空语句和复合语句表达式语句、空语句和复合语句例例Ex_Block 块语句的变量使用范围。块语句的变量使用范围。#include voidmain()inti=5,j=6;coutijendl;/输出的结果是5和6inti=2,j=3,k=4;coutijkendl;/输出结果是2、3和4coutijendl;/输出的结果仍然是5和6,但不能使用k,如coutk;将发生错误。1.4.2 选择结构语句选择结构语句1. 条件语句条件语句条件语句if具有下列一般形式:if()else这里的if、else是C+的关键字。注意,if后的一对圆括号不能省。当“表达式”为“真”(true)或不

36、为0时,将执行语句1。当“表达式”为“假”(false或0)时,语句2被执行。其中,else可省略,即变成这样的简单的if语句:if()当“表达式”为“真”(true)或不为0时,语句被执行。1.4.2 选择结构语句选择结构语句例例Ex_Compare 输入两个整数,比较两者的大小输入两个整数,比较两者的大小#includevoidmain()intnNum1,nNum2;coutnNum1nNum2;if(nNum1!=nNum2)if(nNum1nNum2)coutnNum1nNum2endl;elsecoutnNum1nNum2endl;elsecoutnNum1=nNum2nNum2。

37、当然,表达式的类型也可以是任意的数值类型(包括整型、实型、字符型等)。例如:if(3)coutThisisanumber3;执行结果是输出Thisisanumber3;因为3是一个不为0的数,条件总为“真”。(2)适当添加花括号(“”)来增加程序的可读性。例如:上面例Ex_Compare中的条件语句还可写成下列形式,其结果是一样的。if(nNum1!=nNum2)if(nNum1nNum2)coutnNum1nNum2endl;elsecoutnNum1nNum2endl;elsecoutnNum1=nNum2nNum2)coutnNum1nNum2;/此句才是if后面的有效语句coutend

38、l;/此句无论if表达式是否为真都会执行(4)条件语句中的语句1和语句2也可是if条件语句,这就形成了if语句的嵌套。例如程序中if(nNum1!=nNum2)后面的语句也是一个if条件语句。(5)else总是和其前面最近的if配套的,例如程序中的第一个else是属于第二个if,而第二个else是属于第一个if的。1.4.2 选择结构语句选择结构语句开关语句开关语句当程序有多个条件判断时,若使用if语句则可能使嵌套太多,降低了程序的可读性。开关语句switch能很好地解决这种问题,它具有下列形式:switch()case:语句1case:语句2.case:语句ndefault:语句n+1其中s

39、witch、case、default都是关键字,当表达式的值与case中某个表达式的值相等时,就执行该case中“:”号后面的所有语句。若case中所有表达式的值都不等于表达式的值,则执行default:后面的语句,若default省略,则跳出switch结构。需要注意的是:switch后面的表达式可以是整型、字符型或枚举型的表达式,而case后面的常量表达式的类型必须与其匹配。1.4.2 选择结构语句选择结构语句例例Ex_Switch 根据成绩的等级输出相应的分数段根据成绩的等级输出相应的分数段#includevoidmain()charchGrade;coutchGrade;switch(

40、chGrade)caseA:casea:cout90-100endl;break;caseB:caseb:cout80-89endl;break;caseC:casec:cout70-79endl;caseD:cased:cout60-69endl;caseE:casee:cout60endl;default:couterror!endl;1.4.2 选择结构语句选择结构语句例例Ex_Switch 根据成绩的等级输出相应的分数段根据成绩的等级输出相应的分数段运行时,当用户输入A,则输出:但当用户输入d时,则结果如下:实际上,这不是想要的结果,而应该只输出60-69。1.4.3 循环结构语句循环

41、结构语句1. while循环语句循环语句while循环语句可实现“当型”循环,它具有下列形式:while() while是关键字,是此循环的循环体,它可以是一条语句,也可以是多条语句。当为多条语句时,一定要用花括号(“”)括起来,使之成为复合语句,如果不加花括号,则while的范围只到while后面第一条语句。当表达式为非0(“真”)时便开始执行while循环体中的语句,然后反复执行,每次执行都会判断表达式是否为非0,若等于0(“假”),则终止循环。1.4.3 循环结构语句循环结构语句例例Ex_SumWhile 求整数求整数1到到50的和的和#includevoidmain()intnNum=

42、1,nTotal=0;while(nNum=50)nTotal+=nNum;nNum+;coutThesum,from1to50,is:nTotaln;运行结果为:1.4.3 循环结构语句循环结构语句2.do.while循环语句循环语句do.while循环语句可实现“直到型”循环,它具有下列形式:dowhile()其中do和while都是C+关键字,是此循环的循环体,它可以是一条语句,也可以是复合语句。当语句执行到while时,将判断表达式是否为非0值,若是,则继续执行循环体,直到下一次表达式等于0为止。例如Ex_SumWhile用do.while循环语句可改写成:例例Ex_SumDoWhil

43、e 求整数求整数1到到50的和的和#includevoidmain()int nNum=1,nTotal=0;donTotal+=nNum;nNum+;while(nNum=50);coutThesum,from1to50,is:nTotaln;1.4.3 循环结构语句循环结构语句3. for循环语句循环语句表达式1表达式2循环体表达式3truefalse图1.3for循环流程图for循环语句既可实现“当型”循环,又可实现“直到型”循环,它具有下列形式: for (表达式1;表达式2;表达式3) 其中for是关键字,是此循环的循环体,它可以是一条语句,也可以是复合语句。一般情况下,表达式1用作

44、循环变量的初始化,表达式2是循环体的判断条件,当等于非0(true)时,开始执行循环体,然后计算表达式3,再判断表达式2的值是否为非0,若是,再执行循环体,再计算表达式3,如此反复,直到表达式2等于0(false)为止。其流程如图1.3所示。图1.3for循环流程图1.4.3 循环结构语句循环结构语句例如,Ex_SumWhile用for循环语句可改写成:例例Ex_SumFor求整数求整数1到到50的和的和#includevoidmain()intnTotal=0;for(intnNum=1;nNum=50;nNum+)nTotal+=nNum;coutThesum,from1to50,is:n

45、Totalendl;1.4.4 break和和continue语句语句在C+程序中,若需要跳出循环结构或提前结束本次循环,就需要使用break和continue语句,其格式如下:break;continue;break语句用于强制结束switch结构(如例Ex_Switch)或从一个循环体跳出,即提前终止循环。continue是用于那些依靠条件判断而进行循环的循环语句。例例Ex_Continue 把把1100之间的不能被之间的不能被7整除的数输出整除的数输出#includevoidmain()for(intnNum=1;nNum=100;nNum+)if(nNum%7=0)continue;c

46、outnNum;coutn;1.5.1 函数的定义和调用函数的定义和调用1.函数的定义函数的定义在C+程序中,定义一个函数的格式如下:()函数体可以看出,一个函数的定义是由函数名、函数类型、形式参数表和函数体四个部分组成的。函数类型决定了函数所需要的返回值类型,它可以是函数或数组之外的任何有效的C+数据类型,包括构造的数据类型、指针等。如果不需要函数有返回值,只要定义函数的类型为void即可。1.5.1 函数的定义和调用函数的定义和调用2. 函数的声明函数的声明声明一个函数可按下列格式进行:();其中,形参的变量名可以省略。但要注意,函数声明的内容应和函数的定义应相同。例如对于sum()函数的

47、声明如下:intsum(intx,inty);和intsum(int,int);是等价的。但末尾的分号“;”不要忘记。需要说明的是,函数的声明又可称为对函数的原型进行说明。1.5.1 函数的定义和调用函数的定义和调用3. 函数的调用函数的调用函数调用的一般形式为:();所谓“实际参数”(简称“实参”),它与“形参”相对应,是实际调用函数时所给定的常量、变量或表达式,且必须有确定的值。例如:inta5=7,9,6,3,4;sum(a0,6);或sum(a0*a1,a2+a3);等都是合法的调用。需要注意的是:实参与形参的个数应相等,类型应一致,且按顺序对应,一一传递数据。C+中,调用一个函数的方

48、式可以有很多,例如:sum(3,4);/Aintc=2*sum(4,5);/Bc=sum(c,sum(c,4);/C)其中,A是将函数作为一个语句,不使用返回值,只要求函数完成一定的操作;B把函数作为表达式的一部分,将返回值参与运算,结果c=18;C是将函数作为函数的实参,等价于“c=sum(18,sum(18,4);”,执行sum(18,4)后,等价于“c = sum(18,22) ;”,最后结果为,最后结果为c = 40。1.5.2 函数的参数传递函数的参数传递例例Ex_SwapValue 交换函数两个参数的值。交换函数两个参数的值。#includevoidswap(floatx,floa

49、ty)floattemp;temp=x;x=y;y=temp;coutx=x,y=yn;voidmain()floata=20,b=40;couta=a,b=bn;swap(a,b);couta=a,b=ba;if(a0)a=-a;intb;/b的作用域起始处/b的作用域终止处/a的作用域终止处代码中,声明的局部变量a和b处在不同的块中。其中变量a是在fun函数的函数体块中,因此在函数体这个范围内,该变量是可见的。而b是在if语句块中声明的,故它的作用域是从声明处开始到if语句结束处终止。1.5.3 作用域和存储类型作用域和存储类型1. 作用域作用域(2)函数原型作用域。例如:doublema

50、x(doublex,doubley);和doublemax(double,double);是等价的。不过,从程序的可读性考虑,在声明函数原型时,为每一个形参指定有意义的标识符,并且和函数定义时的参数名相同,是一个非常好的习惯。(3)函数作用域。(4)文件作用域。1.5.3 作用域和存储类型作用域和存储类型2. 变量的存储类型变量的存储类型这些存储类型的声明是按下列格式进行的:;(1)自动类型(auto)。一般说来,用自动存储类型声明的变量都是限制在某个程序范围内使用的,即为局部变量。从系统角度来说,自动存储类型变量是采用动态分配方式来分配内存空间的。因此,当程序执行到超出该变量的作用域时,就释

51、放它所占用的内存空间,其值也随之消失了。在C+语言中,声明一个自动存储类型的变量是在变量类型前面加上关键字auto,例如:autointi;若自动存储类型的变量是在函数内或语句块中声明的,则可省略关键字auto,例如:inti;(2)静态类型(static)。静态类型变量也是一种局部变量。它和自动存储类型的变量的最大不同之处在于:静态类型变量在内存中是以固定地址存放的,它采用静态分配方式来分配内存空间的。在这种方式下,只要程序还在继续执行,静态类型变量的值就一直有效,不会随它所在的函数或语句块的结束而消失。1.5.3 作用域和存储类型作用域和存储类型在C+语言中,声明一个静态类型的变量是在变量

52、类型前面加上关键字static。例如:例例Ex_Static 使用静态类型的局部变量使用静态类型的局部变量#includevoidcount()inti=0;staticintj=0; /静态类型cout”i=”i”,j=”j”n”;i+;j+;voidmain()count();count();运行结果为:1.5.4 带默认形参值的函数带默认形参值的函数在C+中,允许在函数的声明或定义时给一个或多个参数指定默认值。这样在调用时,可以不给出参数,而按指定的默认值进行工作。例如:voiddelay(intloops=1000);/函数声明voiddelay(intloops)/函数定义if(lo

53、ops=0)return;for(inti=0;iloops;i+);/空循环,起延时作用这样,当调用delay();/和delay(1000)等效时,程序都会自动将loops当作成1000的值来进行处理。当然,也可重新指定相应的参数值,例如:delay(2000);1.5.4 带默认形参值的函数带默认形参值的函数在设置函数的默认参数值时要注意:(1)当函数既有声明又有定义时,不能在函数定义中指定默认参数。(2)当一个函数中有多个默认参数时,则形参分布中,默认参数应从右到左逐次定义。在函数调用时,系统按从左到右的顺序将实参与形参结合,当实参的数目不足时,系统将按同样的顺序用声明或定义中的默认值

54、来补齐所缺少的参数。例如:例例Ex_Default 一个设置多个默认参数的函数示例一个设置多个默认参数的函数示例#includevoiddisplay(inta,intb=2,intc=3)/在函数的定义中设置默认参数couta=a,b=b,c=c0时时1.5.5 函数的递归调用函数的递归调用#includelongfactorial(intn);voidmain()coutfactorial(4)endl;/结果为24longfactorial(intn)longresult=0;if(n=0)result=1;elseresult=n*factorial(n-1);returnresult

55、;1.5.5 函数的递归调用函数的递归调用例例Ex_Factorial 编程编程n的阶乘的阶乘n!上述过程可用图1.5来表示,从中可以看出:递归函数实际上是同名函数的多级调用。但要注意,递归函数中必须要有结束递归过程的条件,即函数不再进行自身调用,否则递归会无限制地进行下去。factorial(4)result = 4*factorial(3);result = 3*factorial(2);result = 2*factorial(1);result = 1*factorial(0);表示调用“初始化”,执行被调函数表示调用“后处理”,返回主调函数result = 1;result=1*1=

56、1;result=2*1=2;result=3*2=6;result=4*6=24;12345678910图1.5factorial(4)递归函数执行过程1.5.6 内联函数内联函数内联函数的定义方法是在函数定义时,在函数的类型前增加关键字inline。例如:例例Ex_Inline 用内联函数实现求两个实数的最大值用内联函数实现求两个实数的最大值#includeinlinefloatfmax(floatx,floaty)returnxy?x:y;voidmain()floata,b;coutab;cout最大的数为:fmax(a,b)n;使用内联函数时,还需要注意的是:(1)内联函数也要遵循定

57、义在前,调用在后的原则。形参与实参之间的关系与一般函数相同。(2)在C+中,需要定义成的内联函数不能含有循环、switch和复杂嵌套的if语句。(3)递归函数是不能被用来做内联函数的。(3)编译器是否将用户定义成的内联函数作为真正的内联函数处理,由编译器自行决定。1.5.7 函数的重载函数的重载函数重载是指C+允许多个同名的函数存在,但同名的各个函数的形参必须有区别:形参的个数不同,或者形参的个数相同,但参数类型有所不同。例如:例例Ex_OverLoad 编程求两个或三个操作数之和编程求两个或三个操作数之和#includeintsum(intx,inty);intsum(intx,inty,i

58、ntz);doublesum(doublex,doubley);doublesum(doublex,doubley,doublez);voidmain()coutsum(2,5)endl;/结果为7coutsum(2,5,7)endl;/结果为14coutsum(1.2,5.0,7.5)endl;/结果为13.7intsum(intx,inty)returnx+y;intsum(intx,inty,intz)returnx+y+z;doublesum(doublex,doubley)returnx+y;doublesum(doublex,doubley,doublez)returnx+y+z;

59、1.5.7 函数的重载函数的重载从上面的例子可以看出:由于使用函数的重载,因而不仅方便函数名的记忆,而且更主要的是完善了同一个函数的代码功能,给调用带来了许多方便。程序中各种型式的sum函数都称为sum的“重载函数”。需要说明的是:(1)重载函数必须具有不同的参数个数或不同的参数类型,若只有返回值的类型不同是不行的。(2)当函数的重载带有默认参数时,应该注意避免二义性。例如:intfun(inta,intb=0);intfun(inta);是错误的。因为如果有函数调用fun(2)时,编译器无法准确地确定应调用哪个函数。1.5.8 预处理预处理1. 不带参数的宏定义不带参数的宏定义宏定义就是用一

60、个指定的标识符来代替一个字符串。宏定义有两种形式:不带参数的宏定义和带参数的宏定义。宏定义是通过宏定义命令#define来实现。在以前的程序中,曾用#define定义一个符标常量,如:#definePI3.141593其中,#define是宏定义命令,PI称为宏名。在程序编译时,编译器首先将程序中的PI用3.141593来替换,然后再进行代码编译,故称为“编译预处理”。需要注意的是:(1)#define、PI和3.141593之间一定要有空格,且一般将宏名定义成大写,以与普通标识符相区别。(2)宏后面的内容实际上是“字符串”,编译器本身不对其进行任何语法检查,仅仅是用来在程序中作与宏名的简单替

61、换。例如,若有:#definePI3.141ABC593它是一个合法的宏定义。(3)宏被定义后,使用下列命令后可重新定义:#undef宏名(4)一个定义过的宏名可以用来定义其它新的宏,但要注意其中的括号,例如:#defineWIDTH80#defineLENGTH(WIDTH+10)宏LENGTH等价于:#defineLENGTH(80+10)但其中的括号不能省略,因为当var=LENGTH*20;若宏LENGTH定义中有括号,则预处理后变成:var=(80+10)*20;若宏LENGTH定义中没有括号,则预处理后变成:var=80+10*20;显然,两者的结果是不一样的。1.5.8 预处理预

62、处理2. 带参数的宏定义带参数的宏定义带参数的宏义命令的一般格式为:#define(参数名表)字符串在字符串中包括了括号内的参数,称为形参,以后在程序中这些形参将被实参替换。例如例如:#define MAX(a,b)(a)(b)?(a):(b)其中(a,b)是宏MAX的参数表,如果在程序出现下列语句:x=MAX(3,9);则预处理后变成:x=(3)(9)?(3):(9);/结果为9很显然,带参数的宏相当于一个函数的功能,但却比函数简捷。需要注意的是:定义有参宏时,宏名与左圆括号之间不能留有空格。否则,编译器将空格以后的所有字符均作为替代字符串,而将该宏视为无参数的宏定义。1.5.8 预处理预处

63、理3. 文件包含命令文件包含命令所谓“文件包含”是指将另一个源文件的内容合并到源程序中。C+语言提供了#include命令用来实现文件包含的操作,它有下列两种格式:#include#include“文件名”在使用#include命令需要注意的是,一条#include命令只能包含一个文件,若想包含多个文件须用多条文件包含命令。例如:#include#include.1.5.8 预处理预处理4. 条件编译命令条件编译命令一般情况下,源程序中所有的语句都参加编译,但有时也希望根据一定的条件去编译源文件的不同部分,这就是“条件编译”。条件编译使得同一源程序在不同的编译条件下得到不同的目标代码。C+提供

64、的条件编译命令有几种常用的形式,现分别介绍如下:第一种形式#ifdef#else#endif其中,#ifdef、#else和#endif都是关键字,是由若干条预处理命令或语句组成的。这种形式的含义是:如果标识符已被#define命令定义过,则编译,否则编译。1.5.8 预处理预处理例例Ex_UseIfdef 使用使用#ifdef条件编译命令条件编译命令#include#defineLIvoidmain()#ifdefLIcoutHello,LI!n;#elsecoutHello,everyone!n;#endif运行结果:(2)第二种形式#ifndef#else#endif这与前一种形式的区别

65、仅在于,如果标识符没有被#define命令定义过,则编译,否则就编译。1.5.8 预处理预处理例例Ex_UseIfdef 使用使用#ifdef条件编译命令条件编译命令(3)第三种形式#if#elif.#else#endif其中,#if、#elif、#else和#endif是关键字。它的含义是,如果为“真”就编译,否则如果为“真”就编译,.,如果各表达式都不为“真”就编译。1.6 构造类型构造类型1.6.1 数组数组数组是相同类型的元素的有序集合,每个元素在数组中的位置可用统一的数组名和下标来惟一确定。1. 数组的定义数组的定义定义一个数组可按下列格式进行:.后面的.用于确定数组的维数和大小。如

66、:inta10;它表示数组名为a,一维数组,此数组有10个元素,每个元素的类型都是整型。又如:floatb23;charc456;其中,b是实型的二维数组,它有2x3个元素,每个元素的类型都是实型。c是字符型的三维数组,它有4x5x6个元素,每个元素的类型都是字符型。一般地,表示某维大小的常量表达式中不能包含变量,但可以包括常量和符号常量,其值必须是一个确定的整型数值,且数值大于1。例如:inta4-23*6;constintSIZE=18;int bSIZE;都是合法的数组定义。1.6.1 数组数组2. 数组元素的引用数组元素的引用数组定义后,就可以引用数组中的元素,引用时按下列格式:.例如

67、a0、b5等,这里的0和5是数组的下标,a和b是定义过的数组名。需要注意的是:(1)C+数组的下标总是从0开始的,也就是说,a0是数组a的第一个元素;但下标一定要小于数组定义时的大小。也就是说,长度为n的数组,其下标范围为0(n-1)。例如,inta5;它的数组元素下标应从0到4,而没有a5这个数组元素。(2)下标可以是整型常量或整型表达式,且引用的元素下标个数应与数组定义的维数一致。例如:intd234;是三维数组,d023,d012都是合法的元素,但d12或d13等都是不合法的数组元素的引用。(3)数组定义后,系统会根据数组的大小开辟相应的内存,并依照下标的高低依次存放数组中的各个元素。例

68、如一维数组a5的存放次序是:a0,a1,a2,a3,a4。(4)由于每一个数组元素都可以看成是一个与数组类型相同的变量,因此在程序中对某个或所有数组元素进行赋值或其他处理时,它的操作与变量类似。1.6.1 数组数组3. 一维数组的初始化和赋值一维数组的初始化和赋值数组中的元素既可以在数组定义的同时赋初值,即初始化,也可以在定义后赋值。例如:inta5=1,2,3,4,5;是将整数1,2,3,4,5分别赋于数组a的各个元素,注意要将这些初值用花括号“”括起来。它与下列的赋值语句的结果是相同的:a0=1; a1=2; a2=3; a3=4; a4=5;当然,也可以给其中的一部分元素赋值。例如:in

69、tb5=1,2;是将数组b的元素b0,b1分别赋予1,2的值。需要说明的是,在对数组进行初始化中,若没有明确列举元素值的元素,则其值均为0。有时,在对全部数组元素赋初值时,可以不指定数组的长度;例如intc=1,2,3,4,5;系统将据数值的个数自动定义c数组的长度,这里是5。1.6.1 数组数组4. 二维数组的初始化和赋值二维数组的初始化和赋值二维数组可以看成一个具有行和列的数据表,例如intb34;它在内存空间的存放次序是:b00,b01,b02,b03,/第0行b10,b11,b12,b13,/第1行b20,b21,b22,b23。/第2行可见,在数组b34中,3表示行数,4表示列数。因

70、此,在进行二维数组进行初始化时可以采用以“行”为单位来进行。例如:intd34=1,2,3,4,5,6,7,8,9,10,11,12;其中,最外面的一对花括号里面的1,2,3,4是对第0行元素进行初始化,5,6,7,8是对第1行元素进行初始化,9,10,11,12是对第2行元素进行初始化。每对花括号里的数据个数均不能大于列数。它等价于intd34=1,2,3,4,5,6,7,8,9,10,11,12;/依次对元素进行初始化1.6.1 数组数组5. 排序排序数组作为一种常见的数据结构,在许多方面都得到了应用。“排序”就是一个常用的应用实例。例如:例例Ex_ArraySort 把把5个整型数按从小

71、到大的次序排列个整型数按从小到大的次序排列#include#defineN5voidmain()intaN=20,40,-50,7,13;intmin,k;for(inti=0;iN-1;i+)/外循环min=ai;k=i;for(intj=i+1;jaj)min=aj;k=j;ak=ai; ai=min;for(i=0;iN;i+)coutait;coutendl;结果如下:1.6.1 数组数组5. 排序排序上述排序是用“选择法”进行的。所谓“选择法”,其过程是这样的:首先从N个数中找出最小值,放在第一个元素位置上,再从剩下的N-1个数中找出最小值,放在第二个元素位置上,这样不断重复下去,直

72、到剩下最后一个数。当然,也可以用“冒泡法”(或称“起泡法”)进行排序,其过程是:首先将N个数中相邻两个数进行比较,若当前的数比下一个数大,则相互交换,再与下一个相邻的数逐一比较,直到最大的数“沉”到第N个位置为止,再将剩下的N-1个数,从头开始进行相邻两个数的比较,直到最大的数“沉”到第N-1个位置为止,这样不断重复下去,直到剩下最后一个数。程序如下:1.6.1 数组数组例例Ex_ArrayBubbleSort 用用“冒泡法冒泡法”把把5个整型数按从小到大的次序排列个整型数按从小到大的次序排列#include#defineN5voidmain()intaN=20,40,-50,7,13;for

73、(inti=1;iN;i+)for(intj=0;jaj+1)inttemp;temp=aj;aj=aj+1;aj+1=temp;for(i=0;iN;i+)coutait;coutendl;1.6.1 数组数组6. 字符数组字符数组字符数组中的字符串是以空字符“0”作为结束符,它虽不是字符串的内容,但却占据了一个存储空间。例如:charch=“Hello!”;/第一种形式或charch=“Hello!”;/第二种形式或charch=H,e,l,l,o,!,0;/第三种形式都是使得ch0为H,ch1为e,ch2为l,ch3为l,ch4为o,ch5为!,ch6为0。但第二种形式是最简捷的。cha

74、rch9=“Hello!”;charch6=“Hello!”;charstr3=“How”,“are”,“you”;这时,数组元素str00表示一个字符,值为H;但str0却表示一个字符串“How”,因为str0是一个一维字符数组。1.6.2 传递数组参数传递数组参数数组也可作为函数的形参和实参,若数组元素作为函数的实参,则其用法与变量相同。当数组名作为函数的实参和形参时,传递的是数组的地址。例如:例例Ex_StrChange 改变字符串中的内容改变字符串中的内容#include#includevoidchange(charch5);voidmain()charname5=Ding;coutn

75、ameendl;change(name);/调用时,只需指定数组名coutnameendl;voidchange(charch5)strcpy(ch,Zhen);1.6.2 传递数组参数传递数组参数例例Ex_StrChange 改变字符串中的内容改变字符串中的内容代码中,strcpy是一个处理字符串的C+库函数,它是将字符串“Zhen”复制到ch字符数组中。使用时要添加包含文件string.h。程序运行结果为:(1)实参数组与形参数组类型应一致,如不一致,结果将出错。(2)形参数组也可以不指定大小,在定义数组时数组名后面跟一个空的方括号,为了在被调用函数中处理数组元素的需要,可以另设一个参数,

76、传递数组元素的个数。例如:intsum(intarray,intn);1.6.3 结构体结构体一个结构体是由多种类型的数据(变量)组成的整体。组成结构的各个分量称为结构体的数据成员(简称为“成员”,或称为“成员变量”)。结构体是C+提供的构造复杂数据类型的手段之一。1. 定义结构体定义结构体结构体定义的格式为:struct结构体名;.;结构变量名表;1.6.3 结构体结构体结构体定义是以关键字struct开始的,结构体名应是一个有效的标识符,若该结构体变量以后不再定义,结构体名也可不指定。结构体中的每个成员都必须通过“成员定义”来确定成员名及其类型。例如:structPERSON int ag

77、e;/年龄charsex;/性别floatweight;/体重 charname25;/姓名family_member;其中,PERSON是结构体名,该结构有4个成员变量。family_member是跟随结构体一起定义的结构体变量。当然,也可以在结构体定义后再定义结构体变量。例如:structPERSONsister;/struct关键字可以省略PERSONbrother;PERSONyou,me,he;或PERSONpersons10;/定义一个结构体数组都是合法的结构体变量的定义。1.6.3 结构体结构体需要注意:(1)在定义结构体时,不要忘记最后一个花括号的结尾后面的分号“;”。(2)结

78、构体的成员变量类型既可以是基本数据类型,也可以是其他合法的类型。相同类型的成员变量也可用一行语句来定义,但定义的变量之间要用逗号隔开。例如:structSTUDENTPERSONone;/用已定义的结构体类型声明成员floateng,phy,math,poli;/英语、物理、数学和政治的成绩;1.6.3 结构体结构体2. 结构体变量的初始化结构体变量的初始化结构体变量的初始化的一般形式是在变量后面加上:=;例如:structPOINTintx;inty;spot=20,40;/依次使spot中的x为20,y为40或POINTpt2=100;/只是使pt2中的x为100都是合法的初始化形式。若当

79、结构体中的本身又是一个结构体变量时,其初始化可用“”来增加其可读性。例如:structRECTPOINTptLeftTop;intnWidth;intnHeight;rc=10,20,40,50;1.6.3 结构体结构体3. 结构体变量的引用结构体变量的引用(1)当一个结构体变量定义之后,就可引用这个变量。使用时,遵循下列规则:只能引用结构体变量中的成员变量,并使用下列格式:.例如:coutspot.xspot.y;“.”是成员运算符,它的优先级很高,仅次于域运算符,因而可以把spot.x和spot.y作为一个整体来看待,它可以像普通变量那样进行赋值或进行其他各种运算。(2)若成员本身又是一个

80、结构体变量,引用时需要用多个成员运算符一级一级地找到最低一级的成员。例如:structRECTPOINTptLeftTop;POINTptRightDown;rc=10,20,40,50;则有:coutrc.ptLeftTop.xrc.ptLeftTop.y;1.6.3 结构体结构体3. 结构体变量的引用结构体变量的引用(3)结构类型相同的变量之间可以直接赋值,这种赋值等效于各个成员的依次赋值。例如:structPERSONintage;/年龄char name25;/姓名;PERSONone=30,LiMing;PERSONanother=one;coutanother.namen;/输出L

81、iMing其中,another=one等效于:another.age=one.age;strcpy(another.name,one.name);1.6.4 传递结构体参数传递结构体参数结构体也可以作为函数的形参和实参。当以按值传递方式时,整个结构体都将被复制到形参中去。例如:例例Ex_StructValue 将结构体的值作为参数传给函数将结构体的值作为参数传给函数#includestructPERSONintage;/年龄floatweight;/体重char name25; /姓名;voidprint(PERSONone)coutone.nametone.agetone.weightn;P

82、ERSONall4=20,60,Zhang,28,50,Fang,33,78,Ding,19,65,Chen;voidmain()for(inti=0;i4;i+)print(alli);1.6.4 传递结构体参数传递结构体参数例例Ex_StructValue 将结构体的值作为参数传给函数将结构体的值作为参数传给函数运行结果为:print函数的参数是PERSON结构变量,main函数调用了4次print函数,实参为PERSON结构数组的元素。1.6.4 传递结构体参数传递结构体参数事实上,结构体还可以作为一个函数的返回值。例如:例例Ex_StructReturn 将结构体的值作为参数传给函数将

83、结构体的值作为参数传给函数#includestructPERSONintage;/年龄floatweight;/体重char name25; /姓名;voidprint(PERSONone)coutone.nameone.ageone.weightn;PERSONgetperson()PERSONtemp;couttemp.nametemp.agetemp.weight;returntemp;voidmain()PERSONone=getperson();print(one);#includestructPERSONintage;/年龄floatweight;/体重charname25;/姓名

84、;PERSONtemp;voidprint(PERSONone)coutone.nameone.ageone.weightn;voidgetperson()/PERSONtemp;couttemp.nametemp.agetemp.weight;/returntemp;voidmain()PERSONone;getperson();one=temp;print(one);1.6.4 传递结构体参数传递结构体参数例例Ex_StructReturn 将结构体的值作为参数传给函数将结构体的值作为参数传给函数运行结果为:由于getperson函数返回一个结构体的值,因此需要先将用户输入的数据保存到临时

85、结构体变量temp中,然后返回temp的值。1.6.5 共用体共用体定义一个共用体可用下列格式:union;.;共用体变量名表;/注意最后的分号不要忘记。例如:unionNumericType intiValue;/整型变量,4个字节长long lValue;/长整型变量,4个字节长 float fValue;/实型,8个字节长;这时,系统为NumericType开辟了8个字节的内存空间,因为成员fValue是实型,所占空间最大。1.6.6 枚举类型枚举类型枚举也是一种构造类型,它是一系列的有标识名的整型常量整型常量的集合,其主要功能是增加程序代码的可读性。它的格式如下:enum枚举变量;/注

86、意最后的分号不要忘记。enum是关键字,枚举常量表中的枚举常量名之间要用逗号分隔,例如:enumDaysSun,Mon,Tue,Wed,Thu,Fri,Sattoday;其中Days是定义的一个枚举类型名,它有七个枚举常量(又称枚举值、枚举元素)。默认时,系统为每一个枚举常量都对应一个整数,并从0开始,逐个增1,也就是说枚举常量Sun等于0,Mon等于1,Tue等于2等等。这些默认的值也可重新指定,例如:enumColorsBlack,Blue,Green=4,Cyan,Red=8,Yellow,White;则各枚举常量对应的整数依次为0,1,4,5,8,9,10。上述定义中,today是定义

87、的枚举类型Days的变量,也可以用下列格式来定义。例如:enumDaystoday,yesterday;或Daystomorrow;/省略enum关键字1.6.6 枚举类型枚举类型枚举变量最终的值只能等于该枚举类型中的某个枚举常量,而不能用一个整型数值直接赋值。例如:today=Mon;/合法,值为1tomorrow=today;/合法,值为1inti=today;/合法,值为1yesterday=3;/不合法,不能直接赋值需要注意的是,不要在定义枚举类型的同时,再对枚举常量、枚举变量及枚举类型名重新定义。例如下列的定义是不合法的:int tomorrow;intYellow;int Sun;

88、1.6.7 用用typedef定义类型定义类型使用typedef可以将已有的数据类型名用新的类型名(别名)来代替,它具有下列格式:typedef;例如:typedeffloatFLOAT;typedefcharCH1010;这里,FLOAT表示float类型,CH10表示具有10个元素的字符数组类型。这样在以后的代码中,就可以使用这些类型名定义新的变量,如:FLOATx,y;CH10a,b;/a和b都是具有10个元素的字符数组。它们等价于floatx,y;charx10,y10;typedef几乎可以对所有的数据类型进行定义,但却不能用来定义变量;而且与struct、union、enum等相比

89、,它不能构造出新的数据类型。下面的一些示例可帮助理解:typedefunsignedlongULONG;ULONG ul;/等价于unsignedlongul;typedefstructmystructtagMYSTRUCT;MYSTRUCTms;/等价于structmystructtagms;1.7.1 指针和指针变量指针和指针变量inti=5;int*p=&i;C+中定义一个指针变量可按下列格式:*,*,.;int *pInt1,*pInt2;/pInt1,pInt2是指向整型变量的指针float*pFloat; /pFloat是一个指向实型变量的指针char*pChar;/pChar是一

90、个指向字符型变量的指针,它通常用来处理字符串需要说明的是,绝大多数情况下,都可以将指针变量简称为绝大多数情况下,都可以将指针变量简称为“指针指针”。1.7.2 &和和*运算符运算符 C+中有两个专门用于指针的运算符:&(取地址运算符)、*(取值运算符)运算符“&”只能对变量操作,作用是取该变量的地址。运算符“*”用是取指针或地址所指内存单元中存储的内容。例如:inta=3;/整型变量,初值为3int*p=&a; /指向整型变量的指针,其值等于a的地址intb=*p;/将指针所指的地址中的内容赋值给b,值为3。上述赋值是在指针变量定义时进行的;当然,也可以在程序中进行赋值。例如:inta=3;/

91、整型变量,初值为3int*pi;/指向整型变量的指针pi=p;/将指针p的地址赋给指针pi,使得它们都是指向a的指针,/它等价于pi=&a;注意在pi前没有*。1.7.3 指针运算指针运算除了前面的赋值运算外,指针还有算术运算和关系运算。1. 指针的算术运算指针的算术运算在实际应用中,指针的算术运算主要是对指针加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如,若有:int*ptr;指针变量ptr加上整数n后,即ptr=ptr+n。编译器会把指针ptr的值加上sizeof(int)*n,在32位机器中,sizeof(int)等于4。由于地址是以字节为单位的,故

92、ptr所指向的存储单元向高地址方向移动了sizeof(int)*n字节。这里的int是指针变量ptr的数据类型,若定义成float型,则ptr=ptr+n是使ptr向高地址方向移动了sizeof(float)*n字节。因此,=+n它是使指针变量所指向的存储单元向高地址方向移动了sizeof(指针变量类型)*n个字节。类似的:=-n它是使指针变量所指向的存储单元向低地址方向移动了sizeof(指针变量类型)*n个字节。当n为1时,指针变量的上述加减运算就是指针变量的自增(+)、自减(-)运算。1.7.3 指针运算指针运算2. 指针的关系运算指针的关系运算两个指针变量的关系运算是根据两个指针变量值

93、的大小来进行比较。在实际应用中,通常是比较两个指针反映地址的前后关系或判断指针变量的值是否为0。例如:例例Ex_PointerOp 将字符数组将字符数组a中的中的n个字符按相反顺序存放个字符按相反顺序存放#includevoidmain()chara=Chinese;char*p1=a,*p2=a,temp;while(*p2!=0)p2+;p2-;/将p2指向a的最后一个元素while(p1p2)temp=*p1;*p1=*p2;*p2=temp;/交换内容p1+;p2-;coutaendl;/输出结果程序中,先将指针p1和p2分别指向同一个字符数组a,然后将p2指向字符数组a的最后一个元素

94、,p1从数组a的首地址向后移动,p2从数组a的末地址向前移动,当p1的地址在p2之前时,交换地址的内容,即交换字符数组a的元素内容,从而实现数组a中的字符按相反顺序存放。结果如下:1.7.4 指针和数组指针和数组数组中所有元素都是依次存储在内存单元中的,每个元素都有相应的地址。C+又规定数组名代表数组中下标为0的元素的地址,即数组的首地址。注意:数组名表示的首地址是一个地址(指针)常量。例如,当有下列的数组定义时:int a5;则a所表示的地址就是元素a0的地址,a是一个地址(指针)常量,a+是不合法的是不合法的。需要说明的是,下标运算符具有下列含义:ai=*(a+i)这是因为a是一个地址(指

95、针),a+i表示ai的地址值,它等价于&ai,因而ai=*(a+i)。在指针操作中,若定义了下列指针:int *pi;则pi=a;/等价于pi=&a0;通过指针能引用数组元素。例如:*(pi+1)=1;和a1=1;是等价的。由于指针变量和数组的数组名在本质上是一样,都是反映地址值。因此指向数组的指针变量实际上也可像数组变量那样使用下标,而数组变量又可像指针变量那样使用指针。例如:pii与*(pi+i)及ai是等价的,*(a+i)与*(pi+i)是等价的。1.7.4 指针和数组指针和数组例例Ex_SumUsePointer 用指针运算来计算数组元素的和用指针运算来计算数组元素的和#include

96、voidmain()inta6=1,2,3,4,5,6;int*p=a;/用数组名a给指针初始化intsum=0;for(inti=0;i6;i+)sum+=*p;p+;coutsumn;运行结果为21。1.7.4 指针和数组指针和数组用指针运算时,要注意分析。例如:例例Ex_ArrayAndPointer 分析下列程序的输出结果分析下列程序的输出结果#includevoidmain()inta=5,8,7,6,2,7,3;inty,*p=&a1;y=(*-p)+;coutyn;程序中,最难理解的语句是“y=(*-p)+;”,由于取值运算符“*”和前缀自减运算符“-”处于相同的优先级,但它们的

97、结合方向是自右至左,因此先运算-p,也就是a0的地址,(*-p)是元素a0的值,为5;再运算“y=(*-p)+;”,它相当于“y=(*-p);(*-p)=(*-p)+1;”,故最终结果为5。1.7.4 指针和数组指针和数组例例Ex_MultiArrayAndPointer 分析下列程序的输出结果分析下列程序的输出结果#includevoidmain()inta33=1,2,3,4,5,6,7,8,9;inty=0;for(inti=0;i3;i+)for(intj=0;j3;j+)y+=(*(a+i)j;coutyn;程序中,“y+=(*(a+i)j;”是理解本程序的关键。事实上,*(a+i)

98、就是ai,因而(*(a+i)j就是aij。这里的“y+=(*(a+i)j;”语句就是求数组a中各个元素之和,结果是45。1.7.5 指针和结构体指针和结构体指针也可指向结构体类型变量,例如:例例Ex_StructPointer 指针在结构体中的应用指针在结构体中的应用#include#includestructPERSONintage;/年龄charsex;/性别floatweight;/体重charname25; /姓名;voidmain()structPERSONone;structPERSON*p;/指向PERSON类型的指针变量p=&one;p-age=32;p-sex=M;p-wei

99、ght=(float)80.2;strcpy(p-name,LiMing);cout姓名:(*p).nameendl;cout姓别:(*p).sexendl;cout年龄:(*p).ageendl;cout体重(Kg):(*p).weight”称为指向运算符,它的左边必须是一个指针变量,它等效于指针变量所指向的结构体类型变量,如p-name和(*p).name是等价的,都是引用结构PERSON类型变量one中的成员name,由于成员运算符“.”优先于“*”运算符,所以(*p).name中的*p两侧括号不能省,否则*p.name与*(p.name)等价,但这里的*(p.name)是错误的。若将结

100、构体变量看成一个整体,那么指向结构体变量数组的指针操作和指向数组的指针操作是一样的。例如若有:PERSONmany10,*pp;pp=many;/等价于pp=&many0;则pp+i与many+i是等价的,(pp+i)-name与manyi.name是等价的,等等。1.7.6 多级指针多级指针如果指针变量中存储的是另一个指针变量的地址,或指向指针类型的指针变量,称为多级指针。例例Ex_MultiPointer 使用多级指针使用多级指针#includevoidmain()intnum=4;int*pnum=#int*ppnum=&pnum;cout*ppnumendl;*ppnum=8;

101、coutnum=num=*ppnumendl;运行结果是:1.7.6 多级指针多级指针程序中,ppnum是一个多级(二级)指针变量,它是指向pnum指针变量的指针,而pnum是指向num整型变量的指针,注意它们之间的层次关系:(1)*ppnum与pnum等价,*pnum与num等价。(2)&num与pnum等价,&pnum与ppnum等价。(3)*ppnum与num等价,&(&num)与ppnum等价。1.7.7 指针和函数指针和函数指针既可以作为函数的形参和实参,又可以作为返回值,应用非常广泛。1. 指针作为函数的参数指针作为函数的参数函数的参数可以是C+语言中任意合法变量,自然,也可以是一

102、个指针。如果函数的某个参数是指针,对这一个函数的调用就是按地址传递的函数调用,简称传址调用。由于函数形参指针和实参指针指向同一个地址,因此形参内容的改变必将影响实参。在实际应用中,函数可以通过指针类型的参数带回一个或多个值。1.7.7 指针和函数指针和函数例例Ex_SwapUsePointer 指针作为函数参数的调用方式指针作为函数参数的调用方式#includevoidswap(int*x,int*y);voidmain()inta=7,b=11;swap(&a,&b);couta=a,b=bn;voidswap(int*x,int*y)inttemp;temp=*x;*x=*y;*y=tem

103、p;coutx=*x,y=*yn;结果是:1.7.7 指针和函数指针和函数传递指针的函数调用实现过程如下:(1)函数声明中指明指针参数,即示例中的“voidswap(int*x,int*y);”;(2)函数调用的实参中指明变量的地址,即示例中的“swap(&a,&b);”;(3)函数定义中对形参进行间接访问。对*x和*y的操作,实际上就是访问函数的实参变量a和b,通过局部变量temp的过渡,使变量a和b的值被修改。1.7.7 指针和函数指针和函数2. 返回指针的函数返回指针的函数函数可以返回一个指针,该指针指向一个已定义的任一类型的数据。定义返回指针的函数格式如下:*()它与一般函数定义基本相

104、同,只不过在函数名前面增加了一个“*”号,用来指明函数返回的是一个指针,该指针所指向的数据类型由函数类型决定。例如:1.7.7 指针和函数指针和函数例例Ex_PointerRreturn 返回指针的函数:用来将一个字符串逆序输出返回指针的函数:用来将一个字符串逆序输出#includechar*flip(char*str)char*p1,*p2,ch;p1=p2=str;while(*p2!=0)p2+;p2-;while(p1p2)ch=*p2; *p2=*p1;*p1=ch;/交换字符p1+;p2-;returnstr;voidmain()charstr=ABCDEFGH;coutflip(

105、str)n;运行结果为:1.7.7 指针和函数指针和函数3. 指向函数的指针指向函数的指针同变量相似,每一个函数都有地址。指向函数地址的指针称为“函数指针”。函数指针指向内存空间中的某个函数,通过函数指针可以调用相应的函数。函数指针的定义如下:(*)();例如:int(*func)(chara,charb);就是定义的一个函数指针。int为函数的返回类型,*表示后面的func是一个指针变量名。该函数具有两个字符型参数a和b。需要说明的是,由于()的优先级大于*,所以下面是返回指针的函数定义而不是函数指针定义:int*func(chara,charb);一旦定义了函数指针变量,就可以给它赋值。由

106、于函数名表示该函数的入口地址,因此可以将函数名赋给指向函数的指针变量。但一般来说,赋给函数指针变量的函数的返回值类型与参数个数、顺序要和函数指针变量相同。1.7.7 指针和函数指针和函数例如intfn1(chara,charb);int*fn2(chara,charb);intfn3(intn);int(*fp1)(charx,chary);int(*fp2)(intx);fp1=fn1;/正确,fn1函数与指针fp1指向的函数一致fp1=fn2;/错误,fn2函数的返回值类型与指针fp1指向的函数不一致fp2=fn3;/正确,fn3函数与指针fp2指向的函数一致fp2=fp1;/错误,两个指

107、针指向的函数不一致fp2=fn3(5);/错误,函数赋给函数指针时,不能加括号函数指针变量赋值后,就可以使用指针来调用函数了。调用函数的格式如下:(*)();或();例如:(*fp2)(5);或fp2(5);1.7.7 指针和函数指针和函数例例Ex_FuncPointer1 函数指针的使用函数指针的使用#includedoubleadd(doublex,doubley)return(x+y);doublemul(doublex,doubley)return(x*y);voidmain()double(*func)(double,double);/定义一个函数指针变量doublea,b;char

108、op;coutabop;if(op=+)func=add;/将函数名赋给指针elsefunc=mul;coutaopb=func(a,b)endl;/函数调用结果如下:1.7.7 指针和函数指针和函数函数指针变量可用作函数的参数。例如:例例Ex_FuncPointer2 函数指针变量可用作函数的参数函数指针变量可用作函数的参数#includedoubleadd(doublex,doubley)return(x+y);doublemul(doublex,doubley)return(x*y);voidop(double(*func)(double,double),doublex,doubley)

109、coutx=x,y=y,result=func(x,y)n;voidmain()cout使用加法函数:;op(add,3,7);cout使用乘法函数:;op(mul,3,7);运行结果为:1.7.7 指针和函数指针和函数例例Ex_FuncPointer2 函数指针变量可用作函数的参数函数指针变量可用作函数的参数代码中,op函数的第一个参数为函数指针,该指针指向的函数有两个double参数并返回double类型值。定义的add和mul函数也是有两个double参数并返回double类型值,因此它们可以作为实参赋给函数指针func。1.7.7 指针和函数指针和函数与一般变量指针数组一样,函数指针也

110、可构成指针数组。例如:例例Ex_FuncPointerArray 函数指针数组的使用函数指针数组的使用#includevoidadd(doublex,doubley)coutx+y=x+yn;voidsub(doublex,doubley)coutx-y=x-yn;voidmul(doublex,doubley)coutx*y=x*yn;voiddiv(doublex,doubley)coutx/y=x/yn;void(*func4)(double,double)=add,sub,mul,div;/函数指针数组定义和初始化voidmain()doublex=3,y=7;charop;docou

111、t+-相加n-相减n*-相乘n/-相除nop;switch(op)case+:func0(x,y);break;case-:func1(x,y);break;case*:func2(x,y);break;case/:func3(x,y);break;case0:return;while(1);1.7.8带参数的主函数带参数的主函数main()到目前为止,我们所接触到的main()函数都是不带参数的。但在实际应用中,程序有时需要从命令行输入参数。例如:c:copyfile1file2这是一个常用的DOS命令。当它运行时,操作系统将命令行参数以字符串的形式传递给main()。为了能使程序处理这些参

112、数,需要main()带有参数,其最常用的格式是:数据类型main(intargc,char*argv)其中,第一个int型参数用来存放命令行参数的个数,实际上argc所存放的数值比命令行参数的个数多1,即将命令字(或称为可执行文件名,如copy)也计算在内。第二个参数argv是一个一维的指针数组,用来存放命令行中各个参数和命令字的字符串,且规定:argv0存放命令字argv1存放命令行中第一个参数argv2存放命令行中第二个参数argv3存放命令行中第三个参数这里,argc的值和argv各元素的值都是系统自动赋值的。1.7.8带参数的主函数带参数的主函数main()例例Ex_Main 处理命令

113、行参数处理命令行参数#includevoidmain(intargc,char*argv)cout这个程序的程序名是:argv0n;if(argc=1)cout没有参数!;elseintnCount=1;while(nCountargc)cout第nCount个参数是:argvnCountEx_MainabcdEF运行结果为:1.7.9 new和和delete在C+中,使用运算符new和delete能有效地、直接地进行动态内存的分配和释放。运算符new返回指定类型的一个指针,如果分配失败(如没有足够的内存空间)时则返回0。例如:double*p;p=newdouble;*p=30.4;/将值存

114、在在开辟的单元中系统自动根据double类型的空间大小开辟一个内存单元,并将地址放在指针p中。当然,也可在开辟内存单元时,对单元里的值进行初始化。例如上述代码可写成:double*p;p=newdouble(30.4);运算符delete操作是释放new请求到的内存。例如:deletep;它的作用是将p指针的内存单元释放,指针变量p仍然有效,它可以重新指向另一个内存单元。1.7.9 new和和delete需要注意的是:(1)new和delete须配对使用。也就是说,用new为指针分配内存,当使用结束之后,一定要用delete来释放已分配的内存空间。(2)运算符delete必须用于先前new分配

115、的有效指针。如果使用了未定义的其它任何类型的指针,就会带来严重问题,如系统崩溃等。(3)new可以为数组分配内存,但当释放时,也可告诉delete数组有多少个元素。例如:int *p;p=newint10;/分配整型数组的内存,数组中有10元素if(!p)cout”内存分配失败!”;exit(1);/中断程序执行for(inti=0;i10;i+)pi=i;/给数组赋值.delete10p;/告诉delete数组有多少个元素,或deletep;1.7.10 引用引用C+中提供了一个与指针密切相关的特殊数据类型“引用”。引用是一个变量的别名,定义引用类型变量,实质上是给一个已定义的变量起一个别名

116、,系统不会为引用类型变量分配内存空间,只是使引用类型变量与其相关联的变量使用同一个内存空间。定义引用类型变量的一般格式为:&=或&()其中,变量名必须是一个已定义过的变量。例如:inta=3;int&ra=a;这样,ra就是一个引用,它是变量a的别名。所有对这个引用ra的操作,实质上就是对被引用对象a的操作。例如:ra=ra+2;实质上是a加2,a的结果为5。但是如果给引用赋一个新值,结果会怎样的呢?1.7.10 引用引用例例Ex_Reference 给引用重新赋值给引用重新赋值#includevoidmain()inta;int&ra=a;a=5;couta=an;coutra=ran;co

117、uta的地址是:&an;coutra的地址是:&ran;intb=8;ra=b;couta=an;coutb=bn;coutra=ran;couta的地址是:&an;coutb的地址是:&bn;coutra的地址是:&ran;1.7.10 引用引用运行结果:程序中,引用ra被重新赋值为变量b。但从运行结果可以看出,ra与a的地址仍然相同,只不过它们的值都等于b的值。从这个例子可以看出,引用与指针的最大区别是:指针是一个变量,可以把它再赋值成指向别处的地址,而引用一旦初始化后,其地址不会再改变。当然,在使用引用时,还需要注意的是:(1)定义引用类型变量时,必须将其初始化。而且引用变量类型必须与为

118、它初始化的变量类型相同。例如:floatfVal;int&rfVal=fVal;/错误:类型不同(2)当引用类型变量的初始化值是常数的,则必须将该引用定义成const类型。例如:constint&ref=2;/const类型的引用1.7.10 引用引用(3)不能引用一个数组,这是因为数组是某个数据类型元素的集合,数组名表示该元素集合空间的起始地址,它自己不是一个真正的数据类型。例如:inta10;int&ra=a;/错误:不能建立数组的引用(4)可以引用一个结构体。例如structPERSON int age;/年龄 charname25;/姓名;PERSONone;PERSON&rone=o

119、ne;(5)引用本身不是一种数据类型,所以没有引用的引用,也没有引用的指针。例如:inta;int&ra=a;int&rra=ra;/正确,变量a的另一个引用int&*p=&ra;/错误:企图定义一个引用的指针1.7.11函数的引用传递函数的引用传递如果以引用作为参数,则既可以实现指针所带来的功能,而且简便自然。一个函数能使用引用传递的方式是在函数定义时将形参前加上引用运算符“&”。例如:例例Ex_SwapUseReference 引用作为函数参数的调用方式引用作为函数参数的调用方式#includevoidswap(int&x,int&y);voidmain()inta(7),b(11);sw

120、ap(a,b);cout“a=”a“,b=”bn;voidswap(int&x,int&y)inttemp;temp=x;x=y;y=temp;coutx=x,y=yn;结果是:#includevoidswap(int&x,int&y);voidmain()inta(7),b=11,*p=&a;swap(a,b);couta=a,b=bn;voidswap(int&x,int&y)inttemp,*p1=&x;temp=x;x=y;y=temp;coutx=x,y=yn;1.7.11函数的引用传递函数的引用传递例例Ex_RefReturn 返回引用的函数的使用返回引用的函数的使用#includ

121、edoublearea;double&CalArea(doubler)area=3.141593*r*r;returnarea;voidmain()doublec=CalArea(5.0);double&d=CalArea(10.0);coutcn;coutddata用来指向结点p的数据域,(*p).next或p-next用来指向结点p的下一个结点。1.7.12简单链表简单链表2. 链表的输出链表的输出由于链表中的各个结点是由指针链接在一起的,因此只要知道链表的头指针(即head),那么就可以定义一个指针p,先指向第一个结点,输出p所指向的结点数据,然后根据结点p找到下一个结点,再输出,直到链

122、表的最后一个结点(指针为空)。程序如下:voidOutputList(NODE*head)NODE*current=head;while(current!=NULL)coutdatanext;coutdata=bData;p=*head;if(p=NULL)/若链表是空,符合情况(1)*head=b;/将b作为第一个结点b-next=NULL;elseif(p-data=aData)/若a是第一个结点,符合情况(2)b-next=p;*head=b;elsewhile(p-data!=aData&p-next!=NULL)/查找结点aa=p;p=p-next;if(p-data=aData)/

123、有结点a,符合情况(3)a-next=b;b-next=p;else/没有结点a,符合情况(4)p-next=b;b-next=NULL;1.7.12简单链表简单链表4.链表的删除链表的删除(1)如果要在链表中删除结点a,并释放被删除的结点所占的存储空间,则需要考虑下列几种情况:(2)若要删除的结点a是第一个结点,则把head指向a的下一个结点。如图1.8(a)所示。(3)若要删除的结点a存在于链表中,但不是第一个结点,则应使a的上一个结点ak的指针域指向a的下一个结点ak+1。如图1.8(b)所示。(4)空表或要删除的结点a不存在,则不作任何改变。图1.8 链表的删除( a )headaa1

124、.( b )aak+1.ak1.7.12简单链表简单链表4.链表的删除链表的删除程序如下:voidDeleteList(NODE*head,intaData)/设aData是结点a中的数据NODE*p,*a;p=*head;if(p=NULL)return;/若是空表,符合情况(3)if(p-data=aData)/若a是第一个结点,符合情况(1)*head=p-next;deletep;elsewhile(p-data!=aData&p-next!=NULL)/查找结点aa=p;p=p-next;if(p-data=aData)/有结点a,,符合情况(2)a-next=p-next;dele

125、tep;1.7.12简单链表简单链表以上的OutputList、InsertList和DeleteList是操作链表的三个函数,使用它们可形成完整的程序:例例Ex_SimpleList 使用简单链表使用简单链表#includestructNODEintdata;NODE*next;NODE*head=NULL;int data6=25,41,17,98,5,67;voidInsertList(NODE*head,intaData,intbData);voidDeleteList(NODE*head,intaData);voidOutputList(NODE*head);voidmain()fo

126、r(inti=0;i6;i+)InsertList(&head,data0,datai);OutputList(head);DeleteList(&head,98);DeleteList(&head,41);OutputList(head);1.7.12简单链表简单链表例例Ex_SimpleList 使用简单链表使用简单链表结果如下:代码中,若将在指定结点之前插入结点的函数InsertList改为在链表最后添加结点,设函数名为AppendList,则这样的函数应如何实现?第第2章章C+面向对象程序设计面向对象程序设计在传统的结构化程序设计方法中,数据和处理数据的程序是分离的。当对某段程序进行修

127、改或删除时,整个程序中所有与其相关的部分都要进行相应的修改,从而程序代码的维护比较困难。为了避免这种情况的发生,C+引用了面向对象的设计方法,它是将数据及处理数据的相应函数“封装”到一个“类”中,类的实例称为“对象”。在一个对象内,只有属于该对象的函数才可以存取该对象的数据。这样,其他函数就不会无意中破坏它的内容,从而达到保护和隐藏数据的效果。2.1.1 类的定义类的定义类的定义一般地分为声明部分和实现部分。声明部分是用来声明该类中的成员,包含数据成员(或称“成员变量”)的声明和成员函数的声明。成员函数是用来对数据成员进行操作的,又称为“方法”。实现部分是用来对成员函数的定义。概括说来,声明部

128、分将告诉使用者“干什么”,而实现部分是告诉使用者“怎么干”。C+中定义类的一般格式如下:classprivate:public:;其中,class是定义类的关键字,class的后面是用户定义的类名,通常用大写的C字母开始的标识符作为类名,C用来表示类(Class),以与对象、函数及其他数据类型相区别。类中的数据和函数是类的成员,分别称为数据成员和成员函数。2.1.1 类的定义类的定义定义类时还应注意:(1)类中的数据成员的数据类型可以是任意的,但不允许对所定义的数据成员进行初始化,例如类CMeter中,下面的定义是错误的:classCMeter.private:intm_nPos=10;/错误

129、.;(2)在“public:”或“private:”后面定义的所有成员都是公有或私有的,直到下一个“public:”或“private:”出现为止。若成员前面没有类似“public:”或“private:”,则所定义的成员是private(私有),这是类的默认设置。(3)关键字public和private可以在类中出现多次,且前后的顺序没有关系;但最好先声明公有成员,后声明私有成员,因为public成员是用户最关心的。(4)除了public和private外,关键字protected(保护)也可修饰成员的类型,它与private两者基本相似,但在类的继承时有所不同(后面还会讲到)。(5)数据成

130、员的类型可以是任意的,包含整型、浮点型、字符型、数组、指针和引用等,也可以是另一个类的对象。(6)尽量将类单独存放在一个文件中或将类的声明放在.h文件中而将成员函数的实现放在与.h文件同名的.cpp文件中。以后将会看到,VisualC+6.0为用户创建的应用程序框架中都是将各个类以.h和同名的.cpp文件组织的。2.1.2 对象的定义对象的定义一个类定义后,就可以定义该类的对象,如下面的格式:其中,类名是用户已定义过的类的标识符,对象名可以有一个或多个,多个时要用逗号分隔。被定义的对象既可以是一个普通对象,也可以是一个数组对象或指针对象。例如:CMetermyMeter,*Meter,Mete

131、rs2;这时,myMeter是类CMeter的一个普通对象,Meter和Meters分别是该类的一个指针对象和对象数组。一个对象的成员就是该对象的类所定义的数据成员(成员变量)和成员函数。访问对象的成员变量和成员函数和访问变量和函数的方法是一样的,只不过要在成员前面加上对象名和成员运算符“.”,其表示方式如下:.()2.1.3 构造函数和析构函数构造函数和析构函数1. 构造函数构造函数前面已提及,在类的定义中是不能对数据成员进行初始化的。为了能给数据成员自动设置某些初始值,这时就要使用类的特殊成员函数构造函数。构造函数的最大特点是在对象建立时它会被自动执行,因此用于变量、对象的初始化代码一般放

132、在构造函数中。C+规定:构造函数必须与相应的类同名,它可以带参数,也可以不带参数,与一般的成员函数定义相同,也可以重载。例如:classCMeterpublic:CMeter(intnPos)/带参数的构造函数m_nPos=nPos;.这样若有:CMeteroMeter(10),oTick(20);则会自动调用构造函数CMeter(intnPos),从而使得对象oMeter中的私有成员m_nPos的值为10;使得对象oTick中的私有成员m_nPos的值为20。2.1.3 构造函数和析构函数构造函数和析构函数2. 析构函数析构函数与构造函数相对应的是析构函数。析构函数是另一个特殊的C+成员函数

133、,它只是在类名称前面加上一个“”符号。每一个类只有一个析构函数,没有任何参数,也不返回任何值。例如:classCMeterpublic:.CMeter()/析构函数.析构函数只有在下列两种情况下才会被自动调用:(1)当对象定义在一个函数体中,该函数调用结束后,析构函数被自动调用。(2)用new为对象分配动态内存,当使用delete释放对象时,析构函数被自动调用。2.1.3 构造函数和析构函数构造函数和析构函数3. 默认构造函数和析构函数默认构造函数和析构函数系统自动生成的默认构造函数和析构函数如下所示:CMeter()/默认构造函数的形式CMeter()/默认析构函数的形式需要说明的是,在用户

134、定义一个对象时,编译器会自动根据对象定义的格式选择相应的构造函数。例如:CMeterm1,m2;由于m1和m2不带任何参数,当类没有用户定义的构造函数时,则编译器就会使用默认构造函数对m1和m2进行初始化。用默认构造函数对对象进行初始化时,则将对象的所有数据成员都初始化为零或空。2.1.3 构造函数和析构函数构造函数和析构函数4. 构造函数的重载构造函数的重载构造函数可以被重载,C+会根据对象定义中的参数选择合适的构造函数。例如:例例Ex_ConOverLoad 构造函数的重载构造函数的重载#includeclassCDatepublic:CDate();CDate(intday);CDate

135、(intmonth,intday);CDate(intyear,intmonth,intday);/其他公共成员private:intnYear,nMonth,nDay;CDate:CDate()nMonth=7;nDay=30;nYear=2002;coutnYear-nMonth-nDayendl;CDate:CDate(intday)nMonth=7;nDay=day;nYear=2002;coutnYear-nMonth-nDayendl;CDate:CDate(intmonth,intday)nMonth=month;nDay=day;nYear=2002;coutnYear-nMo

136、nth-nDayendl;CDate:CDate(intyear,intmonth,intday)nYear=year;nMonth=month;nDay=day;coutnYear-nMonth-nDayendl;voidmain()CDateday1;CDateday2(28);CDateday3(8,1);CDateday4(2003,3,10);运行结果为:2.1.3 构造函数和析构函数构造函数和析构函数例例Ex_ConDefault 带默认参数的构造函数带默认参数的构造函数#includeclassCDatepublic:CDate(intyear=2002,intmonth=7,i

137、ntday=30)nYear=year;nMonth=month;nDay=day;coutnYear-nMonth-nDayendl;/其他公共成员private:intnYear,nMonth,nDay;voidmain()CDateday1;CDateday2(2002,8);运行结果为:2.1.3 构造函数和析构函数构造函数和析构函数5. 拷贝构造函数拷贝构造函数拷贝构造函数是一种特殊的成员函数,它的功能是用一个已知的对象来初始化一个被创建的同类的对象。拷贝构造函数的函数名与构造函数一样,也是它所属类的类名。不过与一般的构造函数有一点不同,即它只有一个参数,且参数是同类的一个对象的引用

138、。定义一个拷贝构造函数的一般形式如下::(const&)其中,const是一个类型修饰符,被它修饰的对象是一个不能被更新的常量。例如:2.1.3 构造函数和析构函数构造函数和析构函数例例Ex_ConCopy 拷贝构造函数的使用拷贝构造函数的使用#includeclassCDatepublic:CDate(intyear=2002,intmonth=7,intday=30)cout调用构造函数endl;nYear=year;nMonth=month;nDay=day;coutnYear-nMonth-nDayendl;CDate(constCDate&ymd)/定义的拷贝构造函数cout调用拷贝

139、构造函数endl;coutymd.nYear-ymd.nMonth-ymd.nDayendl;/其他公共成员private:intnYear,nMonth,nDay;voidmain()CDateday1(2002,8);CDateday2(day1);2.1.3 构造函数和析构函数构造函数和析构函数例例Ex_ConCopy 拷贝构造函数的使用拷贝构造函数的使用运行结果为:2.1.3 构造函数和析构函数构造函数和析构函数5. 拷贝构造函数拷贝构造函数实际上,如果类中没有声明拷贝构造函数,则编译器自动生成一个默认的拷贝构造函数。例如:例例Ex_ConCopyDefault 默认拷贝构造函数的使用

140、默认拷贝构造函数的使用#includeclassCDatepublic:CDate(intyear=2002,intmonth=7,intday=30)cout调用构造函数endl;nYear=year;nMonth=month;nDay=day;voidoutput()coutnYear-nMonth-nDayendl;private:intnYear,nMonth,nDay;voidmain()CDateday1(2002,8);CDateday2(day1);/调用默认的拷贝函数day1.output();day2.output();2.1.3 构造函数和析构函数构造函数和析构函数例例E

141、x_ConCopyDefault 默认拷贝构造函数的使用默认拷贝构造函数的使用运行结果为:2.1.4 对象成员初始化对象成员初始化前面所遇到的都是单独的一个类,但在实际应用中往往需要多个类,这时就可能把一个已定义类的对象作为另一个类的成员。为了能对这些对象成员进行初始化,C+允许采用这样的构造函数定义格式::(形参表):对象1(参数表),对象2(参数表),对象n(参数表)其中,对象1、对象2、对象n就是该类使用的其他类的对象,冒号“:”后面的列表称为成员初始化列表。下面来看一个示例:2.1.4 对象成员初始化对象成员初始化例例Ex_InitMultObject 对象成员的初始化对象成员的初始化

142、#includeclassCPointpublic:CPoint(intx,inty)nPosX=x;nPosY=y;voidShowPos()cout当前位置:x=nPosXnPosYendl;private:,y=intnPosX,nPosY;classCSizepublic:CSize(intl,intw)nLength=l;nWidth=w;voidShowSize()cout当前大小:l=nLength,w=nWidthendl;private:intnLength,nWidth;classCRectpublic:CRect(intleft,inttop,intright,intbo

143、ttom):size(right-left,bottom-top),ptCenter(left+right)/2,(top+bottom)/2)voidShow()ptCenter.ShowPos();size.ShowSize();private:CPointptCenter;CSizesize;voidmain()CRectrc(10,100,80,250);rc.Show();运行结果为:代码中,声明类CRect的构造函数时,将成员CPoint类对象ptCenter和CSize类对象size按CRect构造函数的形参进行初始化。2.1.5 静态成员静态成员静态成员的提出是为了解决数据共享

144、的问题。1. 静态数据成员静态数据成员它是这样定义的:(1)使用关键字static声明静态数据成员。(2)对静态数据成员进行初始化。由于静态数据成员要实际在分配空间,因此不能在类声明中进行初始化。静态数据成员初始化在类的外部进行,且与一般数据成员初始化不同,它的格式如下::=例如:2.1.5 静态成员静态成员例例Ex_StaticData 静态数据成员的使用静态数据成员的使用#includeclassCSumpublic:CSum(inta=0,intb=0)nSum+=a+b;intGetSum()returnnSum;voidSetSum(intsum)nSum=sum;private:s

145、taticintnSum;/声明静态数据成员;intCSum:nSum=0;/静态数据成员的初始化voidmain()CSumone(10,2),two;coutsum=one.GetSum()endl;one.SetSum(5);coutsum=one.GetSum()endl;coutsum=two.GetSum()endl;运行结果为:2.1.5 静态成员静态成员2. 静态成员函数静态成员函数例例Ex_StaticFunc 静态成员函数的使用静态成员函数的使用#includeclassCSumpublic:CSum(inta=0,intb=0)nSum+=a+b;intGetSum()r

146、eturnnSum;voidSetSum(intsum)nSum=sum;staticvoidShowData(CSumone);/声明静态成员函数private:staticintnSum;voidCSum:ShowData(CSumone)/静态成员函数的实现cout直接使用静态成员endl;coutsum=nSumendl;cout使用同类的对象endl;coutsum=one.GetSum()endl;intCSum:nSum=0;voidmain()CSumone(10,2);CSum:ShowData(one);/直接访问one.SetSum(8);one.ShowData(one

147、);/通过对象访问运行结果为:2.1.6 友元友元例例Ex_FriendFunc 友元函数的使用友元函数的使用#includeclassCPointpublic:CPoint()m_x=m_y=0;CPoint(unsignedx,unsignedy)m_x=x;m_y=y;voidPrint()coutPoint(m_x,m_y)endl;friendCPointInflate(CPoint&pt,intnOffset);/声明一个友元函数private:unsignedm_x,m_y;CPointInflate(CPoint&pt,intnOffset)/友元函数的定义 CPointptT

148、emp=pt; ptTemp.m_x+=nOffset;/直接改变私有数据成员m_x和m_yptTemp.m_y+=nOffset;returnptTemp;voidmain()CPointpt(10,20);pt.Print(); pt=Inflate(pt,3);/调用友元函数pt.Print();运行结果为:2.1.7 常类型常类型常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。因此,定义或说明常类型时必须进行初始化。1. 常对象常对象常对象是指对象常量,定义格式如下:const定义常对象时,修饰符const可以放在类名后面,也可以放在类名前面。例如:

149、classCOnepublic:COne(inta,intb)x=a;y=b;private:intx,y;constCOnea(3,4);COneconstb(5,6);其中,a和b都是COne对象常量,初始化后就不能再被更新。2.1.7 常类型常类型2. 常指针和常引用常指针和常引用常指针也是使用关键字const来修饰的。但需要说明的是,const的位置不同,其含意也不同,它有三种形式。第一种形式是将const放在指针变量的类型之前,表示声明一个指向常量的指针。此时,在程序中不能通过指针来改变它所指向的数据值,但可以改变指针本身的值。例如:inta=1,b=2;constint*p1=&a

150、;/声明指向int型常的指针p1,指针地址为a的地址*p1=2;/错误,不能更改指针所指向的数据值p1=&b;/正确,指向常量的指针本身的值是可以改变的第二种形式是将const放在指针定义语句的指针名前,表示指针本身是一个常量,称为指针常量或常指针。因此,不能改变这种指针变量的值,但可以改变指变量所指向的数据值。例如:inta=1,b=2;int*constp1=&a; /声明指向int型常的指针p1,指针地址为a的地址int*constp2;/错误,在声明指针常量时,必须初始化*p1=2;/正确,指针所指向的数据值可以改变p1=&b;/错误,指针常量本身的值是不可改变的第三种形式是将cons

151、t在上述两个地方都加,表示声明一个指向常量的指针常量,指针本身的值不可改变,而且它所指向的数据的值也不能通过指针改变。例如:inta=1,b=2;constint*constpp=&a;*pp=2;/错误pp=&b;/错误2.1.7 常类型常类型例Ex_ConstPara常参数的函数传递#includeclassCOnepublic:voidprint(constint*p,intn)/使用常参数cout*p;for(inti=1;in;i+)cout,*(p+i);coutendl;voidmain()intarray6=1,2,3,4,5,6;COneone;one.print(array

152、,6);运行结果为:2.1.7 常类型常类型3. 常成员函数常成员函数使用const关键字进行声明的成员函数,称为“常成员函数”。只有常成员函数才有资格操作常量或常对象,没有使用const关键字说明的成员函数不能用来操作常对象。常成员函数说明格式如下:()const;其中,const是加在函数说明后面的类型修饰符,它是函数类型的一个组成部分,因此,在函数实现部分也要带const关键字。例如:2.1.7 常类型常类型例例Ex_ConstFunc 常成员函数的使用常成员函数的使用#includeclassCOnepublic:COne(inta,intb)x=a;y=b;voidprint();v

153、oidprint()const;/声明常成员函数private:intx,y;voidCOne:print()coutx,yendl;voidCOne:print()constcout使用常成员函数:x,yendl;voidmain()COneone(5,4);one.print();constCOnetwo(20,52);two.print();运行结果为:程序中,类COne声明了两个重载成员函数,一个带const,一个不带。语句“one.print();”调用成员函数“voidprint();”,而“two.print();”调用常成员函数“voidprint()const;”。2.1.7

154、 常类型常类型4. 常数据成员常数据成员类型修饰符const不仅可以说明成员函数,也可以说明数据成员。由于const类型对象必须被初始化,并且不能更新,此,在类中声明了const数据成员时,只能通过成员初始化列表的方式来生成构造函数对数据成员初始化。例如:例Ex_ConstData常数据成员的使用#includeclassCOnepublic:COne(inta):x(a),r(x)/常数据成员的初始化voidprint();constint&r;/引用类型的常数据成员private:constintx;/常数据成员staticconstinty;/静态常数据成员;constintCOne:y

155、=10;/静态数据成员的初始化voidCOne:print()coutx=x,y=y,r=rendl;voidmain()COneone(100);one.print();该程序的运行结果为:2.1.8 this指针指针this指针是一个仅能被类的非静态成员函数所能访问的特殊指针。当一个对象调用成员函数时,编译器先将对象的地址赋给this指针,然后调用成员函数。例如,当下列成员函数调用时:one.copy(two);它实际上被解释成:copy(&one,two);只不过,&one参数为隐藏了。需要说明的是,通过*this可以判断是哪个对象来调用该成员函数或重新指定对象。例如:2.1.8 thi

156、s指针指针例例Ex_This this指针的使用指针的使用#includeclassCOnepublic:COne()x=y=0;COne(inta,intb)x=a;y=b;voidcopy(COne&a);/对象引用作函数参数voidprint()coutx,yM这样的表达式中,其中pa是一个指向A类对象的指针。2.1.9 类的作用域和对象的生存期类的作用域和对象的生存期对象的生存期是指对象从被创建开始到被释放为止的时间。按生存期的不同,对象可分为如下三种:(1)局部对象:当对象被定义时调用构造函数,该对象被创建,当程序退出定义该对象所在的函数体或程序块时,调用析构函数,释放该对象。(2)

157、静态对象:当程序第一次执行所定义的静态对象时,该对象被创建,当程序结束时,该对象被释放。(3)全局对象:当程序开始时,调用构造函数创建该对象,当程序结束时调用析构函数释放该对象。2.2 继承和派生类继承和派生类2.2.1. 单继承单继承从一个基类定义一个派生类可按下列格式:class:;其中,继承方式有3种:public(公有)、private(私有)及protected(保护),若继承方式没有指定,则被指定为默认的public方式。继承方式决定了派生类的继承基类属性的使用权限,下面分别说明。2.2.1. 单继承单继承1. 公有继承公有继承(public)公有继承的特点是基类的公有成员和保护成

158、员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的。例如:classCStick:publicCMeterint m_nStickNum;/声明一个私有数据成员public:voidDispStick();/声明一个公有成员函数;/注意分号不能省略voidCStick:DispStick()m_nStickNum=GetPos();/调用基类CMeter的成员函数coutm_nStickNum;这时,从基类CMeter派生的CStick类除具有CMeter所有公有成员和保护成员外,还有自身的私有数据成员m_nStickNum和公有成员函数DispStick()。这个完整的

159、示例如下:2.2.1. 单继承单继承例例Ex_ClassPublicDerived 派生类的公有继承示例派生类的公有继承示例#includeclassCMeterpublic:CMeter(intnPos=10)m_nPos=nPos;CMeter()voidStepIt()m_nPos+;intGetPos()returnm_nPos;protected:voidSetPos(intnPos)m_nPos=nPos;private:intm_nPos;classCStick:publicCMeter/从CMeter派生,公有继承intm_nStickNum;/声明一个私有数据成员public

160、:voidDispStick();/声明一个公有成员函数voidSetStick(intnPos)SetPos(nPos);/类中调用基类的保护成员;voidCStick:DispStick()m_nStickNum=GetPos();/调用基类CMeter的成员函数coutm_nStickNum;voidmain()CMeteroMeter(20);CStickoStick;coutCMeter:oMeter.GetPos(),CStick:oStick.GetPos()endl;oMeter.StepIt();coutCMeter:oMeter.GetPos(),CStick:oStick

161、.GetPos()endl;oStick.StepIt();outCMeter:oMeter.GetPos(),CStick:oStick.GetPos()endl;oStick.DispStick();oStick.StepIt();oStick.DispStick();运行结果为:2.2.1. 单继承单继承2. 私有继承私有继承(private)私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。例如:例例Ex_ClassPrivateDerived 派生类的私有继承示例派生类的私有继承示例#includeclassCMeterpublic:C

162、Meter(intnPos=10) m_nPos=nPos;CMeter()voidStepIt()m_nPos+;intGetPos()returnm_nPos;protected:voidSetPos(intnPos)m_nPos=nPos;private:intm_nPos;classCStick:privateCMeter/从CMeter派生,私有继承int m_nStickNum;/声明一个私有数据成员public:voidDispStick();/声明一个公有成员函数voidSetStick(intnPos)SetPos(nPos);/调用基类的保护成员intGetStick()r

163、eturnGetPos();/调用基类的公有成员;voidCStick:DispStick()m_nStickNum=GetPos();/调用基类CMeter的成员函数coutm_nStickNum;voidmain()CMeteroMeter(20);CStickoStick;coutCMeter:oMeter.GetPos(),CStick:oStick.GetStick()endl;oMeter.StepIt();coutCMeter:oMeter.GetPos(),CStick:oStick.GetStick()endl;oStick.DispStick();运行结果为:2.2.1.

164、单继承单继承3. 保护继承保护继承(protected)保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。表2.1列出三种不同的继承方式的基类特性和派生类特性。表表2.1 不同继承方式的基类特性和派生类特性不同继承方式的基类特性和派生类特性2.2.2 派生类的构造函数和析构函数派生类的构造函数和析构函数例例Ex_ClassDerived 派生类的构造函数和析构函数的示例派生类的构造函数和析构函数的示例#include#includeclassCAnimalpublic:CAnimal(char*pName=n

165、oname);CAnimal();voidsetName(char*pName)strncpy(name,pName,sizeof(name);char*getName(void)returnname;private:charname20;CAnimal:CAnimal(char*pName)setName(pName);cout调用CAnimal的构造函数!endl;CAnimal:CAnimal()cout调用CAnimal的析构函数!endl;classCCat:publicCAnimalpublic:CCat()cout调用CCat的构造函数!endl;CCat()cout调用CCat

166、的析造函数!endl;voidDispName()cout猫的名字是:getName()endl;voidmain()CCatcat;cat.DispName();cat.setName(Snoopy);cat.DispName();运行结果为:2.2.2 派生类的构造函数和析构函数派生类的构造函数和析构函数例如,在Ex_ClassPublicDerived示例中,CStick的构造函数可这样定义:classCStick:publicCMeter.public:CStick():CMeter(30).;此时再重新运行程序,结果就会变为:2.2.3 多继承多继承多继承下派生类的定义是按下面的格式

167、:class:,.;其中的继承方式还是前面的3种:public、private和protected。例如:classA.classB.classC:publicA,privateB.由于派生类C继承了基类A和B,具有多继承性,因此派生类C的成员包含了基类A中成员和B中成员以及该类本身的成员。2.2.4虚基类虚基类一般说来,在派生类中对基类成员的访问应该是惟一的,但是,由于多继承情况下,可能造成对基类中某成员的访问出现了不惟一的情况,这种情况称为基类成员调用的二义性。例如:例例Ex_Conflict 基类成员调用的二义性基类成员调用的二义性#includeclassApublic:intx;A(

168、inta=0)x=a;classB1:publicApublic:inty1;B1(inta=0,intb=0):A(b)y1=a;classB2:publicApublic:inty2;B2(inta=0,intb=0):A(b)y2=a;classC:publicB1,publicB2public:intz;C(inta,intb,intd,inte,intm):B1(a,b),B2(d,e)z=m;voidprint()coutx=xendl;/编译出错的地方couty1=y1,y2=y2endl;coutz=zendl;voidmain()Cc1(100,200,300,400,500

169、);c1.print();2.2.4虚基类虚基类程序中,派生类B1和B2都从基类A继承,这时在派生类中就有两个基类A的拷贝。当编译器编译到“cout”x=“xendl;”语句时,因无法确定成员x是从类B1中继承来的,还是从类B2继承来,产生了二义性,从而出现编译错误。解决这个问题的方法之一是使用域作用运算符“:”来消除二义性,例如若将print()函数实现代码变为:voidprint()coutB1:x=B1:xendl;coutB2:x=B2:xendl;couty1=y1,y2=y2endl;coutz=zendl;重新运行,结果为:2.3.1 虚函数虚函数先来看一个虚函数应用实例。例例E

170、x_VirtualFunc 虚函数的使用虚函数的使用#includeclassCShapepublic:virtualfloatarea()/将area定义成虚函数return0.0;classCTriangle:publicCShapepublic:CTriangle(floath,floatw)H=h;W=w;floatarea()return(float)(H*W*0.5);private:floatH,W;classCCircle:publicCShapepublic:CCircle(floatr)R=r;floatarea()return(float)(3.14159265*R*R)

171、;private:floatR;voidmain()CShape*s2;s0=newCTriangle(3,4);coutarea()endl;s1=newCCircle(5);coutarea()area();”实际上是调用CTriangle类的area成员函数,结果是6;同样可以分析s1-area()的结果。需要说明的是:(1)虚函数在重新定义时参数的个数和类型必须和基类中的虚函数完全匹配,这一点和函数重载完全不同。(2)只有通过基类指针才能实现虚函数的多态性,若虚函数的调用是普通方式来进行的,则不能实现其多态性。(3)如果不使用new来创建相应的派生类对象指针,也可使用通过&运算符来获取

172、对象的地址。(4)虚函数必须是类的一个成员函数,不能是友元函数,也不能是静态的成员函数。(5)可把析构函数定义为虚函数,但不能将构造函数定义为虚函数。声明纯虚函数的一般格式为:virtual()=0;2.3.2 纯虚函数和抽象类纯虚函数和抽象类例例Ex_PureVirtualFunc 纯虚函数和抽象类的使用纯虚函数和抽象类的使用#includeclassCShapepublic:virtualfloatarea()=0;/将area定义成纯虚函数;classCTriangle:publicCShapepublic:CTriangle(floath,floatw)H=h;W=w;floatare

173、a()/在派生类定义纯虚函数的具体实现代码return(float)(H*W*0.5);private:floatH,W;classCCircle:publicCShapepublic:CCircle(floatr)R=r;floatarea()/在派生类定义纯虚函数的具体实现代码return(float)(3.14159265*R*R);private:floatR;voidmain()CShape*pShape;CTriangletri(3,4);couttri.area()endl;pShape=&tri;coutarea()endl;CCirclecir(5);coutcir.area

174、()endl;pShape=○coutarea()endl;运行结果为:2.4.1 运算符重载的语法运算符重载的语法定义一个运算符重载函数与定义普通函数相类似,只不过函数名必须以operator开头,其一般形式如下::operator() /函数体由于运算符重载函数的函数是以特殊的关键字开始的,编译器很容易与其他的函数名区分开来。先来看一个实例,这个例子是定义一个复数类CComplex,然后重载“+”运算符,使这个运算符能直接完成复数的加运算。2.4.1 运算符重载的语法运算符重载的语法例例Ex_Complex 运算符的简单重载运算符的简单重载#includeclassCComplex

175、public:CComplex(doubler=0,doublei=0)realPart=r;imagePart=i;voidprint()cout该复数实部=realPart,虚部=imagePartendl;CComplexoperator+(CComplex&c);/重载运算符+CComplexoperator+(doubler);/重载运算符+private:doublerealPart;/复数的实部doubleimagePart;/复数的虚部;CComplexCComplex:operator+(CComplex&c) /参数是CComplex引用对象CComplextemp;tem

176、p.realPart=realPart+c.realPart;temp.imagePart=imagePart+c.imagePart;returntemp;CComplexCComplex:operator+(doubler)/参数是double类型数据CComplextemp;temp.realPart=realPart+r;temp.imagePart=imagePart;returntemp;voidmain()CComplexc1(12,20),c2(50,70),c;c=c1+c2;c.print();c=c1+20;c.print();运行结果为:2.4.2 友元重载友元重载实现

177、运算符重载的方法有两种:用类的成员函数来实现和通过类的友元函数来实现。这里来用友元函数实现重载的方法。友元重载方法既可用于单目运算符,也可以用于双目运算符。它们的一般格式如下:friendoperator()/单目运算符重载 /函数体friendoperator() /双目运算符重载 /函数体重载函数来说,它有两个形参,这两个形参中必须有一个是类的对象。需要说明的是,有些运算符是不能重载为友元函数的,它们是:=、()、和-。2.4.2 友元重载友元重载例例Ex_ComplexFriend 运算符的友元重载运算符的友元重载#includeclassCComplexpublic:CComplex(

178、doubler=0,doublei=0)realPart=r;imagePart=i;voidprint()cout该复数实部=realPart,虚部=imagePartendl;CComplexoperator+(CComplex&c);/重载运算符+CComplexoperator+(doubler);/重载运算符+friendCComplexoperator+(doubler,CComplex&c);/友元重载运算符+friendCComplexoperator-(CComplex&c);/友元重载单目运算符-voidoperator+=(CComplex&c);private:doub

179、lerealPart;/复数的实部doubleimagePart;/复数的虚部;CComplexCComplex:operator+(CComplex&c)CComplextemp;temp.realPart=realPart+c.realPart;temp.imagePart=imagePart+c.imagePart;returntemp;CComplexCComplex:operator+(doubler)CComplextemp;temp.realPart=realPart+r;temp.imagePart=imagePart;returntemp;CComplexoperator+(

180、doubler,CComplex&c)CComplextemp;temp.realPart=r+c.realPart;temp.imagePart=c.imagePart;returntemp;CComplexoperator-(CComplex&c)returnCComplex(-c.realPart,-c.imagePart);voidCComplex:operator+=(CComplex&c)realPart+=c.realPart;imagePart+=c.imagePart;voidmain()CComplexc1(12,20),c2(30,70),c;c=c1+c2;c.prin

181、t();c=c1+20;c.print();c=20+c2;c.print();c2+=c1;c2.print();c1=-c1;c1.print();运行结果为:2.4.3 转换函数转换函数类型转换是将一种类型的值映射为另一种类型的值。C+的类型转换包含自动隐含和强制转换的两种方法。转换函数是实现强制转换的手段之一,它是类中定义的一个非静态成员函数,其一般格式为;classpublic: operator ();其中,是要转换后的一种数据类型,它可以是基本的数据类型,也可以是导出的的数据类型。operator和一起构成了转换函数名。这个转换函数的作用是将“class”声明的类对象转换成指定的

182、数据类型。下面举一例来说明,这个例子是将金额的小写形式(数字)转换成金额的大写形式(汉字)。2.4.3 转换函数转换函数例例Ex_Money 转换函数的使用转换函数的使用#include#includetypedefchar*string;classCMoneypublic:CMoney(doublea=0.0) amount=a;operatorstring();private:doubleamount;CMoney:operatorstring()stringbasestr15=分,角,元,拾,佰,仟,万,拾,佰,仟,亿,拾,佰,仟,万;stringdatastr10=零,壹,贰,叁,肆,

183、伍,陆,柒,捌,玖;staticcharstrResult80;doubletemp,base=1.0;intn=0;temp=amount*100.0;strcpy(strResult,金额为:);if(temp=10.0)/计算位数base=base*10.0;temp=temp/10.0;n+;if(n=15)strcpy(strResult,金额超过范围!);elsetemp=amount*100.0;for(intm=n;m=0;m-)intd=(int)(temp/base);temp=temp-base*(double)d;base=base/10.0;strcat(strRes

184、ult,datastrd);strcat(strResult,basestrm);returnstrResult;voidmain()CMoneymoney(1234123456789.123);cout(string)moneyendl;运行结果是:程序中,转换的类型是用typedef定义的string类型。调用该转换函数是直接采用强制转换方式,如程序中的(string)money或者是string(money)。还需要说明的是:转换函数只能是成员函数,不能是友元函数,转换函数可以被派生类继承,也可以被说明为虚函数,在一个类中可以定义多个转换函数。2.2.4 赋值运算符的重载赋值运算符的重载

185、我们知道,相同类型的对象之间可以直接相互赋值,但不是所有的同类型对象都可以这么操作的。当对象的成员中有数组或动态的数据类型时,就不能直接相互赋值,否则在程序的编译或执行过程中出现编译或运行错误。例如:classCdemopublic:CDemo(char*s)ps=newcharstrlen(s)+1;strcpy(ps,s);CDemo()if(ps)deleteps;voidprint()coutpsendl;private:char*ps;voidmain()CDemod1(Key),d2(Mouse);d1=d2;程序运行到“d1=d2”时发生运行错误。因此,必须重载“=”运算符,它与

186、其他运算符的重载相同,具体代码如下:2.2.4 赋值运算符的重载赋值运算符的重载例例Ex_Evaluate 赋值运算符的重载赋值运算符的重载#include#includeclassCdemopublic:CDemo(char*s)ps=newcharstrlen(s)+1;strcpy(ps,s);CDemo()if(ps)deleteps;voidprint()coutpsendl;CDemo&operator=(CDemo&a)/赋值运算符重载if(ps)deleteps;if(a.ps)ps=newcharstrlen(a.ps)+1;strcpy(ps,a.ps);elseps=0;

187、return*this;private:char*ps;voidmain()CDemod1(Key),d2(Mouse);d1=d2;d1.print();运行结果为:需要说明的是:(1)赋值运算符重载函数operator=()的返回类型是CDemo&,注意它返回是类的引用而不是对象。这是因为,C+要求赋值表达式左边的表达式是左值,它能进行诸如下列的运算:intx,y=5;(x=y)+;/结果x为6由于引用的实质就是对象的地址,所以通过引用当然可以改变对象的值。而如果返回的类型是类的对象,则被认为是一个常量,不能出现在等号左边作为左值(也就是说不能被改变)。(3)赋值运算符不能重载为友元函数,

188、只能是一个非静态成员函数。(4)赋值运算符是惟一的一个不能被继承的运算符函数。2.4.5 自增和自减运算符的重载自增和自减运算符的重载自增“+”和自减“-”运算符是单目运算符,它们又有前缀和后缀运算符两种。为了区分这两种运算符,在重载时将后缀运算符视为双目运算符。即obj+或obj-被看作为:obj+0或obj-0下面举例说明重载“+”和“-”运算符的应用。例例Ex_IncrementAndDecrement “+”和和“”运算符的重载运算符的重载#includeclassCCounterpublic:CCounter()unCount=0;CCounteroperator+();CCount

189、eroperator+(int);voidprint() coutunCountendl;private:unsignedunCount;CCounterCCounter:operator+()/前缀+运算符 unCount+; return*this;CCounterCCounter:operator+(int)/后缀+运算符 CCountertemp; temp.unCount=unCount+; returntemp;voidmain() CCounterdemo1; for(inti=0;i8;i+)demo1+; demo1.print(); CCounterdemo2; for(i

190、=0;i8;i+)+demo2; demo2.print();运行结果为:2.5 输入输出流库输入输出流库在C+中,没有专门的内部输入输出语句。但为了方便用户灵活实现输入输出基本功能,C+提供了两套输入输出方法:一套是与C语言相兼容的输入输出函数,如printf()和scanf()等;另一套是使用功能强大的输入输出流库ios。2.5.1 概述概述所谓“流”,它是C+的一个核心概念,数据从一个位置到另一个位置的流动抽象为“流”。C+针对流的特点,构造了功能强大的输入输出流库,它具有面向对象的特性,其继承结构如图2.1所示。iosistreamostreamiostreamstreambuf图图2

191、.1 C+的输入输出流库的输入输出流库2.5.2 流的格式控制和错误处理流的格式控制和错误处理下面分别来讨论它们的使用方法以及流的错误处理。1. 使用格式控制成员函数使用格式控制成员函数在ios类中控制输入输出的成员函数有:int ios:width();/返回当前的宽度设置int ios:width(int);/设置宽度并返回上一次的设置int ios:precision();/返回当前的精度设置int ios:precision(int);/设置精度并返回上一次的设置char ios:fill();/返回当前空位填充的字符char ios:fill(char);/设置空位填充的字符并返回上

192、一次的设置long ios:setf(long);/设置状态标志并返回上一次的状态标志long ios:unsetf(long);/消除状态标志并返回上一次的状态标志long ios:flags();/返回当前的状态标志long ios:flags(long);/设置状态标志并返回上一次的状态标志2.5.2 流的格式控制和错误处理流的格式控制和错误处理1. 使用格式控制成员函数使用格式控制成员函数ios:skipws跳过输入中的空白符ios:left输出数据按输出域左对齐ios:right输出数据按输出域右对齐(默认)ios:internal数据的符号左对齐,数据本身右对齐,符号和数据之间为填

193、充字符ios:dec转换为十进制形式ios:oct转换为八进制形式ios:hex转换为十六进制形式ios:showbase输出的数值前面带有基数符号(0或0x)ios:showpoint显示浮点数的小数点和后面的0ios:uppercase 用大写字母(A到F)输出十六进制数ios:showpos显示正数前面的“+”号ios:scientific按科学记数法显示浮点数ios:fixed用定点格式显示浮点数ios:unitbuf输入操作完成后立即刷新缓冲区ios:stdio每次输入操作完成后刷新stdout和stderr2.5.2 流的格式控制和错误处理流的格式控制和错误处理例例Ex_Forma

194、tFunc 使用格式控制成员函数使用格式控制成员函数#includevoidmain()intnNum=12345;doubledNum=12345.6789;char*str=This,is,aTest!;cout.setf(ios:oct|ios:showbase|ios:showpos);/设置标志:八进制,显示基和正号coutnNumtdNumendl;cout.setf(ios:hex|ios:scientific|ios:uppercase);/设置十六进制,科学记数法和大写标 志coutnNumtdNumendl;cout.fill(*);/设置填充符号为*for(inti=0;

195、i3;i+)cout.width(12);coutstri;coutendl;cout.setf(ios:left);/设置标志:左对齐for(i=0;i3;i+)cout.width(12);coutstri;coutendl;运行结果为:2.5.2 流的格式控制和错误处理流的格式控制和错误处理2. 使用格式算子使用格式算子前面介绍的使用成员函数进行格式控制的方法中,每次都要使用一条语句,这样操作起来比较繁琐。为此,C+提供了一些格式算子来简化上述操作。格式算子是一个对象,可以直接用插入符或提取符来操作。C+提供的预定义格式算子如表2.2所示。表表2.2 C+预定义的格式算子预定义的格式算子

196、2.5.2 流的格式控制和错误处理流的格式控制和错误处理例例Ex_Formator 使用格式算子使用格式算子#include#includevoidmain()intnNum=12345;doubledNum=12345.6789;char*str=This,is,aTest!;coutsetiosflags(ios:oct|ios:showbase|ios:showpos); /设置标志:八进制,显示 基和正号coutnNumtdNumendl;coutsetiosflags(ios:hex|ios:scientific|ios:uppercase);/设置十六进制,科学记数法和大写标志co

197、utnNumtdNumendl;coutsetfill(*);/设置填充符号为*for(inti=0;i3;i+)coutsetw(12)stri;coutendl;coutsetiosflags(ios:left);/设置标志:左对齐for(i=0;i3;i+)coutsetw(12)stri;coutendl;2.5.2 流的格式控制和错误处理流的格式控制和错误处理3. 流的错误处理流的错误处理在ios类中,定义了一个公有枚举成员io_state来记录各种错误的性质:enumio_stategoodbit=0x00, /正常eofbit=0x01, /已达到文件尾failbit=0x02,

198、 /操作失败badbit=0x04 /非法操作;在ios类中又定义了检测上述流状态的下列成员函数:intios:rdstate();/返回当前的流状态,它等于io_state中的枚举值int ios:bad();/如果badbit位被置1,返回非0void ios:clear(int);/清除错误状态int ios:eof();/返回非0表示提取操作已到文件尾int ios:fail();/如果failbit位被置1,返回非0int ios:good();/操作正常时,返回非02.5.2 流的格式控制和错误处理流的格式控制和错误处理例例Ex_ManipError 检测流的错误检测流的错误#in

199、cludevoidmain()inti,s;charbuf80;couti;s=cin.rdstate();cout流状态为:hexsendl;while(s)cin.clear();cin.getline(buf,80);couti;s=cin.rdstate();运行结果如下:2.5.3 使用输入输出成员函数使用输入输出成员函数1. 输入操作的成员函数输入操作的成员函数数据的输入/输出可以分为三大类:字符类、字符串和数据。(1)使用get和getline函数用于输入字符或字符串的成员函数get原型如下:int get();istream& get( char& rch );istream&

200、 get( char* pch, int nCount, char delim = n );第一种形式是从输入流中提取一个字符,并转换成整型数值。第二种形式是从输入流中提取字符到rch中。第三种形式是从输入流中提取一个字符串并由pch返回,nCount用来指定提取字符的最多个数,delim用来指定结束字符,默认时是n。函数getline原型如下:istream& getline( char* pch, int nCount, char delim = n );它是用来从输入流中提取一个输入行,并把提取的字符串由pch返回,nCount和delim的含义同上。这些函数可以从输入流中提取任何字符,

201、包括空格等。例如:2.5.3 使用输入输出成员函数使用输入输出成员函数例例Ex_GetAndGetLine get和和getline的使用的使用#includevoidmain()chars180,s280,s380;cout请键入一个字符:;coutcin.get()endl;cin.get();/提取换行符cout请输入一行字符串:;for(inti=0;i80;i+)cin.get(s1i);if(s1i=n)s1i=0;break;/退出for循环couts1endl;cout请输入一行字符串:;cin.get(s2,80);couts2endl;cin.get();/提取换行符cou

202、t请输入一行字符串:;cin.getline(s3,80);couts3endl;运行结果为:2.5.3 使用输入输出成员函数使用输入输出成员函数1. 输入操作的成员函数输入操作的成员函数(2)使用read函数read函数不仅可以读取字符或字符串(称为文本流),而且可以读取字节流。其原型如下:istream& read( char* pch, int nCount );istream& read( unsigned char* puch, int nCount );istream& read( signed char* psch, int nCount );read函数的这几种形式都是从输入流

203、中读取由nCount指定数目的字节并将它们放在由pch或puch或psch指定的数组中。例如:2.5.3 使用输入输出成员函数使用输入输出成员函数例例Ex_Read read函数的使用函数的使用#includevoidmain()chardata80;cout请输入:endl;cin.read(data,80);datacin.gcount()=0;coutendldataendl;运行结果为:其中,Z表示用户按下“Ctrl+z”键,“Z+回车键”表示数据输入提前结束。gcount是istream类的另一个成员函数,用来返回上一次提取的字符个数。从这个例子可以看出,当用read函数读取数据时,

204、不会因为换行符而结束读取,因此它可以读取多个行的字符串,这在许多场合下是很有用处的。2.5.3 使用输入输出成员函数使用输入输出成员函数2. 输出操作的成员函数输出操作的成员函数ostream类中用于输出单个字符或字节的成员函数是put和write,它们的原型如下:ostream& put( char ch );ostream& write( const char* pch, int nCount );ostream& write( const unsigned char* puch, int nCount );ostream& write( const signed char* psch,

205、int nCount );例如:chardata80;cout请输入:endl;cin.read(data,80);cout.write(data,80);cout”和“”运算符,以便用户利用标准的输入输出流来输入输出自己定义的数据类型(包括类),实现对象的输入输出。重载这两个运算符时,虽然可使用别的方法,但最好将重载声明为类的友元函数,以便能访问类中的私有成员。下面来看一个示例。例例Ex_ExtractAndInsert 提取和插入运算符的重载提取和插入运算符的重载#includeclassCStudentpublic:friendostream&operator(istream&is,CS

206、tudent&stu);private:charstrName10;/姓名charstrID10;/学号floatfScore3;/三门成绩;ostream&operator(ostream&os,CStudent&stu)osendl学生信息如下:endl;os姓名:stu.strNameendl;os学号:stu.strIDendl;os成绩:stu.fScore0,tstu.fScore1,tstu.fScore2(istream&is,CStudent&stu)cout请输入学生信息endl;coutstu.strName;coutstu.strID;coutstu.fScore0st

207、u.fScore1stu.fScore2;returnis;voidmain()CStudentone;cinone;cout”;而向一个文件写入数据,可以使用put、write函数以及插入符“”。下面举例来说明文件的操作过程和方法。例例Ex_File 将文件内容保存在另一文件中,并将内容显示在屏幕上将文件内容保存在另一文件中,并将内容显示在屏幕上#include#include/文件操作必须的头文件voidmain()fstreamfile1;/定义一个fstream类的对象用于读file1.open(Ex_DataFile.txt,ios:in);if(!file1)coutEx_Data

208、File.txt不能打开!n;return;fstreamfile2;/定义一个fstream类的对象用于写file2.open(Ex_DataFileBak.txt,ios:out|ios:trunc);if(!file2)coutEx_DataFileBak.txt不能创建!n;file1.close();return;charch;while(!file1.eof()file1.read(&ch,1);coutch;file2.write(&ch,1);file2.close();/不要忘记文件使用结束后要及时关闭file1.close();上述程序中,eof是ios类的成员函数,当达到

209、文件的末尾时,它将返回“真”。2.5.7 随机文件操作随机文件操作在以随机的方式读写文件时,同样必须首先打开文件,且随机方式和顺序方式打开文件所用的函数也完全相同,但随机方式的文件流的打打开模式必须同时有开模式必须同时有ios:in|ios:out。在文件打开时,文件指针指向文件的第一个字符(字节)。当然,用户可根据具体的读写操使用C+提供的seekg和seekp函数将文件指针移动到指定的位置。它们的原型如下:istream& seekg( long pos );istream& seekg( long off, ios:seek_dir dir );ostream& seekp( long

210、pos );ostream& seekp( long off, ios:seek_dir dir );其中,pos用来指定文件指针的绝对位置。而off用来指定文件指针的相对偏移时,文件指针的最后位置还依靠dir的值。dir值可以是:ios:beg从文件流的头部开始ios:cur从当前的文件指针位置开始ios:end从文件流的尾部开始2.5.7 随机文件操作随机文件操作例例Ex_FileSeek 使用使用seekp指定文件指针的位置指定文件指针的位置#include#include#include#includeclassCStudentpublic:CStudent(char*name,cha

211、r*id,floatscore=0);voidprint();friendostream&operator(istream&is,CStudent&stu);private:charstrName10;/姓名charstrID10;/学号floatfScore;/成绩;CStudent:CStudent(char*name,char*id,floatscore)strncpy(strName,name,10);strncpy(strID,id,10);fScore=score;voidCStudent:print()coutendl学生信息如下:endl;cout姓名:strNameendl;

212、cout学号:strIDendl;cout成绩:fScoreendl;ostream&operator(istream&is,CStudent&stu)charname10;charid10;is.read(name,10);is.read(id,10);is.read(char*)&stu.fScore,4);strncpy(stu.strName,name,10);strncpy(stu.strID,id,10);returnis;voidmain()CStudentstu1(MaWenTao,99001,88);CStudentstu2(LiMing,99002,92);CStudent

213、stu3(WangFang,99003,89);CStudentstu4(YangYang,99004,90);CStudentstu5(DingNing,99005,80);fstreamfile1;file1.open(student.dat,ios:out|ios:in|ios:binary);file1stu1stu2stu3stu4*one;one-print();file1.seekp(size*1);file1*one;one-print();file1.seekp(size*2,ios:cur);file1*one;one-print();file1.close();delet

214、eone;2.5.7 随机文件操作随机文件操作例例Ex_FileSeek 使用使用seekp指定文件指针的位置指定文件指针的位置运行结果如下:程序中,先将五个学生记录保存到文件中,然后移动文件指针,读取相应的记录,最后将数据输出到屏幕上。需要说明的是,由于文件流file1既可以读(ios:in)也可以写(ios:out),因此用seekg代替程序中的seekp,其结果也是一样的。第第3章章 MFC基本应用程序的建立基本应用程序的建立3.1 Windows编程基础编程基础编制一个功能强大和易操作的Windows应用程序所需要的代码肯定会比一般的C+程序要多得多,但并不是所有的代码都需要自己从头开

215、始编写,因为VisualC+不仅提供了常用的Windows应用程序的基本框架,而且可以在框架程序中直接调用Win32API(ApplicationProgrammingInterface,应用程序接口)函数。这样,用户仅需要在相应的框架位置中添加自己的代码或修改部分代码就可实现Windows应用程序的许多功能。3.1.1 简单的简单的Windows应用程序应用程序先来看一个最简单的Windows应用程序Ex_HelloMsg。例例Ex_HelloMsg 一个简单的一个简单的Windows应用程序应用程序#includeintWINAPIWinMain(HINSTANCEhInstance,HI

216、NSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow)MessageBox(NULL,你好,我的VisualC+世界!,问候,0);return0;在VisualC+6.0运行上述程序需要进行以下步骤:(1)选择“文件”“新建”菜单命令,显示出“新建”对话框。在“工程”标签页面的列表框中,选中Win32Application项。(2)在工程编辑框中键入Win32应用程序项目名称Ex_HelloMsg。在“位置”编辑框中直接键入文件夹名称,或单击浏览按钮选择一个已有的文件夹。(3)单击确定按钮继续。一个询问项目类型的Win32应用程序向导将被显示,选中A

217、nemptyproject项。单击完成按钮,系统将显示该应用程序向导的创建信息,单击确定按钮系统将自动创建此应用程序。(4)再次选择“文件”“新建”菜单命令,显示出“新建”对话框。在“文件”标签页面左边的列表框中选择C+SourceFile项,在右边的文件框中键入Ex_HelloMsg.cpp,单击确定按钮。3.1.1 简单的简单的Windows应用程序应用程序例例Ex_HelloMsg 一个简单的一个简单的Windows应用程序应用程序(5)输入上面的代码,运行程序,结果如图3.1所示。从上面的程序可以看出:C+控制台应用程序以main函数作为进入程序的初始入口点,但在Windows应用程序

218、中,main主函数被WinMain函数取代。WinMain函数的原型如下:int WINAPI WinMain (HINSTANCE hInstance, /当前实例句柄HINSTANCE hPrevInstance, /前一实例句柄LPSTR lpCmdLine, /指向命令行参数的指针int nCmdShow) /窗口的显示状态图3.1Ex_HelloMsg运行结果3.1.1 简单的简单的Windows应用程序应用程序所谓句柄是一个标识Windows资源(如菜单、图标、窗口等)和设备等对象的数据指针类型。通常,一个句柄变量可用来对系统中某些资源的间接引用。每一个C+Windows应用程序都

219、需要Windows.h头文件,它还包含了其他的一些Windows头文件。这些头文件定义了Windows的所有数据类型、函数调用、数据结构和符号常量。程序中,MessageBox是一个Win32API函数,用来弹出一个消息对话框。该函数第一个参数用来指定父窗口句柄,即对话框所在的窗口句柄。第二、三个参数分别用来指定显示的消息内容和对话框窗口的标题,最后一个参数用来指定在对话框中显示的按钮。3.1.1 简单的简单的Windows应用程序应用程序例例Ex_HelloWin 一个完整的一个完整的Windows应用程序应用程序#includeLRESULTCALLBACKWndProc(HWND,UIN

220、T,WPARAM,LPARAM); /窗口过程intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow)HWNDhwnd;/窗口句柄MSGmsg;/消息WNDCLASSwndclass;/窗口类wndclass.style=CS_HREDRAW|CS_VREDRAW;wndclass.lpfnWndProc=WndProc;wndclass.cbClsExtra=0;wndclass.cbWndExtra=0;wndclass.hInstance=hInstance;wndclas

221、s.hIcon=LoadIcon(NULL,IDI_APPLICATION);wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);wndclass.hbrBackground =(HBRUSH)GetStockObject(WHITE_BRUSH);wndclass.lpszMenuName =NULL;wndclass.lpszClassName =HelloWin;/窗口类名if(!RegisterClass(&wndclass)/注册窗口MessageBox(NULL,窗口注册失败!,HelloWin,0);return0;hwnd=CreateWin

222、dow(HelloWin,/窗口类名我的窗口,/窗口标题WS_OVERLAPPEDWINDOW, /窗口样式CW_USEDEFAULT,/窗口最初的x位置CW_USEDEFAULT,/窗口最初的y位置CW_USEDEFAULT,/窗口最初的x大小CW_USEDEFAULT,/窗口最初的y大小NULL,/父窗口句柄NULL,/窗口菜单句柄hInstance,/应用程序实例句柄NULL);/创建窗口的参数ShowWindow(hwnd,nCmdShow);/显示窗口UpdateWindow(hwnd);/更新窗口,包括窗口的客户区/进入消息循环:当从应用程序消息队列中检取的消息是WM_QUIT时,

223、则退出循环。while(GetMessage(&msg,NULL,0,0)TranslateMessage(&msg);/转换某些键盘消息DispatchMessage(&msg);/将消息发送给窗口过程,这里是WndProcreturnmsg.wParam;LRESULTCALLBACKWndProc(HWNDhwnd,UINTmessage,WPARAMwParam,LPARAMlParam)switch(message)caseWM_CREATE:/窗口创建产生的消息return0;caseWM_LBUTTONDOWN:MessageBox(NULL,你好,我的VisualC+世界!,问

224、候,0);return0;caseWM_DESTROY:/当窗口关闭时产生的消息PostQuitMessage(0);return0;returnDefWindowProc(hwnd,message,wParam,lParam);/执行默认的消息处理3.1.1 简单的简单的Windows应用程序应用程序例例Ex_HelloWin 一个完整的一个完整的Windows应用程序应用程序程序运行后,单击鼠标左键,就会弹出一个对话框,结果如图3.2所示。图3.2Ex_HelloWin运行结果3.1.1 简单的简单的Windows应用程序应用程序窗口过程函数WndProc用来接收和处理各种不同的消息,而主

225、函数WinMain通常要完成以下几步工作:(1)调用API函数RegisterClass注册应用程序的窗口类。(2)调用相关API函数创建和显示窗口,并进行其它必要的初始化处理。其中,函数CreateWindow用来创建已注册窗口类的窗口。Windows每一个窗口都有一些基本属性,如窗口标题、窗口位置和大小、应用程序图标、鼠标指针、菜单和背景颜色等。窗口类就是充当这些属性的模板。(3)创建和启动应用程序的消息循环。Windows应用程序接受各种不同的消息,包括键盘消息、鼠标以及窗口产生的各种消息。Windows系统首先将消息放入消息队列中,应用程序的消息循环就是从应用程序的消息队列中检取消息,

226、并将消息发送相应的窗口过程函数中作进一步处理。API函数GetMessage和DispatchMessage就是起到这样的作用。(4)如果接收到WM_QUIT消息,则退出应用程序。3.1.1 简单的简单的Windows应用程序应用程序初始化应用程序开始执行初始化并创建应用程序窗口进入消息循环并获取一条消息消息是否为WM_QUIT?终止程序是应用程序中是否处理了此消息否处理消息转送消息给窗口进行默认处理是否图3.3Windows应用程序的基本流程3.1.2 Windows编程特点编程特点一个完整的Windows应用程序除了WinMain函数外,还包含用于处理用户动作和窗口消息的窗口函数。这不同于

227、一个C+的控制台应用程序,可以将整个程序包含在main函数中。事实上,它们的区别还远不止这些,不久还会发现一个Windows应用程序还常常具有这样的一些特性:消息驱动机制图形设备接口(GDI)基于资源的程序设计动态链接库进程和线程3.1.2 Windows编程特点编程特点1. 消息驱动机制消息驱动机制在Windows操作环境中,无论是系统产生的动作或是用户运行应用程序产生的动作,都称为事件(Events)产生的消息(Message)。例如,在Windows桌面(传统风格)上,双击应用程序的快捷图标,系统就会执行该应用程序。在Windows的应用程序中,也是通过接收消息、分发消息、处理消息来和用

228、户进行交互的。这种消息驱动的机制是Windows编程的最大特点。需要注意的是,许多Windows消息都经过了严格的定义,并且适用于所有的应用程序。例如,当用户按下鼠标的左键时系统就会发送WM_LBUTTONDOWN消息,而当用户敲了一个字符键时系统就会发送WM_CHAR消息,若用户进行菜单选择或工具按钮单击等操作时,系统又会相应地发送WM_COMMAND消息给相应的窗口等等。3.1.2 Windows编程特点编程特点2. 图形设备接口图形设备接口(GDI)在传统的DOS环境中,想要在打印机上打印一幅图形是一件非常复杂的事情,因为用户必须根据打印机类型和指令规则向打印机输送数据。而Windows

229、则提供了一个抽象的接口,称为图形设备接口(GraphicalDeviceInterface,简称GDI),使得用户直接利用系统的GDI函数就能方便实现输入或输出,而不必关心与系统相连的外部设备的类型。3. 基于资源的程序设计基于资源的程序设计Windows应用程序常常包含众多图形元素,例如光标、菜单、工具栏、位图、对话框等,在Windows环境下,每一个这样的元素都作为一种可以装入应用程序的资源来存放。这些资源就像C+程序中的常量一样,可以被编辑、修改,也可以被其他应用程序所共享。VisualC+6.0中就提供这样的编辑器,可“所见即所得”地对这些不同类型的资源进行设计、编辑等。3.1.2 W

230、indows编程特点编程特点4. 动态链接库动态链接库动态链接库提供了一些特定结构的函数,能被应用程序在运行过程中装入和连接,且多个程序可以共享同一个动态链接库,这样就可以大大节省内存和磁盘空间。从编程角度来说,动态链接库可以提高程序模块的灵活性,因为它本身是可以单独设计、编译和调试的。Windows提供了应用程序可利用的丰富的函数调用,大多数用于实现其用户界面和在显示器上显示的文本和图形,都是通过动态链接库来实现的。这些动态链接库是一些具有.DLL扩展名或者有时是.EXE扩展名的文件。在Windows操作系统中,最主要的DLL有KERNEL32.DLL、GDI32.DLL和USER32.DL

231、L三个模块。其中,KERNEL32用来处理存储器低层功能、任务和资源管理等Windows核心服务;GDI32用来提供图形设备接口,管理用户界面和图形绘制,包括Windows元文件、位图、设备描述表和字体等;而USER32负责窗口的管理,包括消息、菜单、光标、计时器以及其它与控制窗口显示相关的一些功能。3.1.2 Windows编程特点编程特点5 . 进程和线程进程和线程在32位Windows多任务操作系统中,采用了进程和线程的管理模式。进程是装入内存中正在执行的应用程序。进程包括私有的虚拟地址空间、代码、数据及其它操作系统资源,如文件、管道以及对该进程可见的同步对象等。进程包括了一个或多个在进

232、程上下文内运行的线程。线程是操作系统分配CPU时间的基本实体。线程可以执行应用程序代码的任何部分,包括当前正在被其它线程执行的那些部分。同一进程的所有线程共享同样的虚拟地址空间、全局变量和操作系统资源。在一个应用程序中,可以包括一个或多个进程,每个进程由一个或多个线程构成。3.1.3 Windows基本数据类型基本数据类型表3.1列出了一些在Windows编程中常用的基本数据类型。表表3.1 Windows常用的基本数据类型常用的基本数据类型3.1.3 Windows基本数据类型基本数据类型表3.2列出了常用的预定义句柄,它们的类型均为void*,即一个32位指针。表表3. 2 Windows

233、常用的句柄类型常用的句柄类型3.2 编制一个编制一个MFC应用程序应用程序前面的Ex_HelloMsg和Ex_HelloWin都是基于WindowsAPI的C+应用程序。显然,随着应用程序的复杂性,C+应用程序代码也必然越复杂。为了帮助用户处理那些经常使用又复杂繁琐的各种Windows操作,VisualC+设计了一套基础类库(MicrosoftFoundationClassLibrary,简称MFC)。MFC把Windows编程规范中的大多数内容封装成为各种类,使程序员从繁杂的编程中解脱出来,提高了编程和代码效率。3.2.1 设计一个设计一个MFC程序程序在理解MFC机制之前,先来看一个MFC

234、应用程序。例例Ex_HelloMFC 一个一个MFC应用程序应用程序#include/MFC头文件classCHelloApp:publicCWinApp/声明应用程序类public:virtualBOOLInitInstance();CHelloApptheApp;/建立应用程序类的实例classCMainFrame:publicCFrameWnd/声明主窗口类public:CMainFrame()/创建主窗口Create(NULL,我的窗口,WS_OVERLAPPEDWINDOW,CRect(0,0,400,300);protected:afx_msgvoidOnLButtonDown(U

235、INTnFlags,CPointpoint);DECLARE_MESSAGE_MAP();/消息映射入口BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd)ON_WM_LBUTTONDOWN()/单击鼠标左键消息的映射宏END_MESSAGE_MAP()/定义消息映射函数voidCMainFrame:OnLButtonDown(UINTnFlags,CPointpoint) MessageBox(你好,我的VisualC+世界!,问候,0);CFrameWnd:OnLButtonDown(nFlags,point);BOOLCHelloApp:InitInstance

236、()/每当应用程序首次执行时都要调用的初始化函数m_pMainWnd=newCMainFrame();m_pMainWnd-ShowWindow(m_nCmdShow);m_pMainWnd-UpdateWindow();returnTRUE;3.2.1 设计一个设计一个MFC程序程序在VisualC+6.0运行上述MFC程序需要进行以下步骤:(1)选择“文件”“新建”菜单命令,显示出“新建”对话框。在“工程”标签页面的列表框中,选中Win32Application项,创建一个Ex_HelloMFC空应用程序项目。(2)再次选择“文件”“新建”菜单命令,显示出“新建”对话框。在文件标签页面左边

237、的列表框中选择C+SourceFile项,在右边的文件框中键入Ex_HelloMFC.cpp,单击确定按钮。(3)输入上面的代码。选择“工程”“设置”菜单命令,在出现的对话框中选择“General”标签。然后在“MicrosoftFoundationClasses”组合框中,选择“UseMFCinaSharedDLL”,如图3.4所示。单击确定按钮。(4)程序运行后,单击鼠标左键,就会弹出一个对话框,结果同Ex_HelloWin。3.2.1 设计一个设计一个MFC程序程序图3.4设置工程属性3.2.2 理解程序代码理解程序代码按照MFC的消息映射机制,映射一个消息的过程是由三个部分组成的:(1

238、)在处理消息的类中,使用消息宏DECLARE_MESSAGE_MAP声明对消息映射的支持,并在该宏之前声明消息处理函数。例如前面示例中的:protected:afx_msgvoidOnLButtonDown(UINTnFlags,CPointpoint);DECLARE_MESSAGE_MAP()(2)使用BEGIN_MESSAGE_MAP和END_MESSAGE_MAP宏在类声明之后的地方定义该类支持的消息映射入口点,所有消息映射宏都添加在这里,当然不同的消息MFC都会有不同的消息映射宏。例如:BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd)END_MESSAG

239、E_MAP()其中,BEGIN_MESSAGE_MAP带有两个参数,第一个参数用来指定需要支持消息映射的用户派生类,第二个参数指定该类的基类。(3)定义消息处理函数。例如:voidCMainFrame:OnLButtonDown(UINTnFlags,CPointpoint) MessageBox(你好,我的VisualC+世界!,问候,0);CFrameWnd:OnLButtonDown(nFlags,point);3.3 使用使用MFC AppWizard3.3.1应用程序框架类型应用程序框架类型这些类型基本满足了各个层次的用户的需要,但一般地,用户更关心的是MFCAppWizard(ex

240、e)应用程序框架,因为它包含用户最常用、最基本的三种应用程序类型:单文档、多文档和基于对话框的应用程序。表表3.3 MFC AppWizard创建的应用程序类型创建的应用程序类型3.3.2 创建一个单文档应用程序创建一个单文档应用程序用MFCAppWizard(MFC应用程序向导)可以方便地创建一个通用的Windows单文档应用程序,其步骤如下。1. 开始开始选择“文件”“新建”菜单,在弹出的“新建”对话框中,可以看到工程标签页面中,显示出一系列的应用程序项目类型;选择MFCAppWizard(exe)的项目类型(该类型用于创建可执行的Windows应用程序),将项目工作文件夹定位在“D:Vi

241、sualC+6.0程序”,并在工程编辑框中输入项目名Ex_SDIHello,结果如图3.5所示。图3.5MFCAppWizard的“新建”对话框3.3.2 创建一个单文档应用程序创建一个单文档应用程序2. 第一步第一步单击确定按钮,出现如图3.6所示的对话框,进行下列选择:(1)从应用程序类型单个文档(SDI)、多重文档(MDI)和基本对话框(基于对话框的应用程序)中选择“单个文档”。(2)决定应用程序中是否需要MFC的文档视图(“文档/查看体系结构支持”)结构的支持。若不选定此项,则程序中的磁盘文件的打开、保存以及文档和视图的相互作用等功能需要用户来实现,且将跳过Step2Step5,直接弹

242、出“Step6”对话框。一般情况下,应选中此项。(3)选择资源所使用的语言,这里是“中文中国”。3.第二步第二步单击下一个按钮,出现如图3.7所示的对话框,让用户选择程序中是否加入数据库的支持(有关数据库的内容将在以后的章节中介绍)。图3.6MFCAppWizard的“Step1”对话框图3.7MFCAppWizard的“Step2”对话框3.3.2 创建一个单文档应用程序创建一个单文档应用程序4.第三步第三步单击下一个按钮进入下一步,出现如图3.8所示的对话框。允许用户在程序中加入复合文档、自动化、ActiveX控件的支持。5. 第四步第四步单击下一个按钮进入下一步,出现如图3.9所示的对话

243、框,对话框的前几项依次确定对浮动工具条、打印与预览以及通信等特性的支持。图3.8MFCAppWizard的“Step3”对话框图3.9MFCAppWizard的“Step4”对话框3.3.2 创建一个单文档应用程序创建一个单文档应用程序6. 第五步第五步保留以上默认值,单击下一个按钮进入下一步。弹出如图3.10所示的对话框,这里出现三个方面的选项,供用户来选择:(1)应用程序的主窗口是MFC标准风格还是窗口左边有切分窗口的浏览器风格;(2)在源文件中是否加入注释用来引导用户编写程序代码;(3)使用动态链接库还是静态链接库。图3.10MFCAppWizard的“Step5”对话框3.3.2 创建

244、一个单文档应用程序创建一个单文档应用程序7. 第六步第六步保留默认选项,单击下一步按钮进行下一步,出现如图3.11所示的对话框。在这里,用户可以对MFCAppWizard提供的默认类名、基类名、各个源文件名进行修改。单击完成按钮出现一个信息对话框,显示出用户在前面几个步骤中作出的选择内容,单击确定按钮系统开始创建,并又回到了VisualC+6.0的主界面。8.编译并运行编译并运行到这里为止,用户虽然没有编写任何程序代码,但MFCAppWizard已经根据用户的选择内容自动生成基本的应用程序框架。单击编译工具栏上的运行工具按钮“”或按快捷键Ctrl+F5,系统开始编连并运行生成的单文档应用程序可

245、执行文件Ex_SDIHello.exe,运行结果如图3.12所示。菜单栏工具栏状态栏标题栏文档窗口图3.11MFCAppWizard的“Step6”对话框图3.12Ex_SDIHello运行结果3.3.3 MFC应用程序项目组织应用程序项目组织1. 项目的文件组织项目的文件组织在VisualC+6.0中,项目中所有的源文件都是采用文件夹的方式进行管理的,它将项目名作为文件夹名,在此文件夹下包含源程序代码文件(.cpp,.h)、项目文件(.dsp)以及项目工作区文件(.dsw)等。表3.4列出了这些文件类型的的含义。表表3.4 Visual C+ 6.0文件类型的含义文件类型的含义3.3.3 M

246、FC应用程序项目组织应用程序项目组织除了上述文件外,还有相应的Debug(调试)或Release(发行)、Res(资源)等子文件夹。例如上述创建的单文档应用程序项目Ex_SDIHello,其各文件的组织如图3.13所示。当然,不同类型的项目的文件类型及数目会有所不同。Ex_SDIHello文件夹Debug或Release文件夹Res文件夹类文件.cpp,.h资源文件Ex_SDIHello.rc项目及项目工作区文件其他文件编连过程产生的文件运行文件Ex_SDIHello.exe图标文件Ex_SDIHello.ico等工具栏资源Toolbar.bmp其他资源Ex_SDIHello.rc2等图3.1

247、3Ex_SDIHello项目的文件组织3.3.3 MFC应用程序项目组织应用程序项目组织2. MFC类结构类结构在开发环境中,VisualC+6.0是通过左边的项目工作区窗口来对项目进行各种管理。项目工作区窗口包含3个页面,它们分别是ClassView页、ResourceView页和FileView页,通过单击项目区窗口底部的页面标签进行切换。将VisualC+6.0项目工作区窗口切换到ClassView页面,可以看到MFC为单文档应用程序项目Ex_SDIHello自动创建了类CAboutDlg、CEx_SDIHelloApp、CEx_SDIHelloDoc、CEx_SDIHelloView和

248、CMainFrame。这些MFC类之间的继承和派生关系如图3.14所示。图3.14 MFC类的基本层次结构CCmdTargetCWndCDocumentCFrameWndCObjectCWinTreadCWinAppCViewCDialog及控件CMDIChildWndCMDIFrameWndCMiniFrameWnd3.4 使用类向导使用类向导MFC类向导(ClassWizard)是VisualC+6.0中又一个非常有用的工具。它能自动为一个项目添加一个类、进行消息和数据映射、创建OLEAutomation(自动化)属性和方法以及进行ActiveX事件处理等。3.4.1 类向导概述类向导概述

249、打开MFC类向导可以使用下列几种方法:(1)选择“查看”“建立类向导”菜单或直接使用Ctrl+W快捷键。(2)在源代码文件的文档编辑窗口中,右击鼠标,从弹出的快捷菜单中选择“建立类向导”命令。当MFC类向导打开后,就会弹出如图3.15所示的MFCClassWizard对话框。图3.15MFCClassWizard对话框3.4.1 类向导概述类向导概述可以看到ClassWizard对话框包含了5个标签页面,它们各自含义如下:MessageMaps(消息映射):用来添加、删除和编程处理消息的成员函数。MemberVariables(成员变量):添加或删除与控件相关联的成员变量(或称数据成员),以便

250、与控件进行数据交换。这些控件所在的类一般是从CDialog、CPropertyPage、CRecordView或CDaoRecordView中派生的类。Automation(自动化):为支持Automation的类(如ActiveX控件类)添加属性和方法。ActiveXEvents(ActiveX事件):为ActiveX控件类添加触发事件的支持。ClassInfo(类信息):有关项目中类的其他信息。一般来说,MFCClassWizard对话框最前两项是用户最关心的,也是最经常使用的,因为几乎所有的代码编写都要利用这两个标签项。由于MemberVariables功能以后还会详细讨论,因此这里仅讨

251、论MessageMaps(消息映射)与类的添加和删除方法。3.4.2 消息和消息映射消息和消息映射1. 消息分类消息分类Windows应用程序中的消息主要有三种类型。(1)窗口消息(Windowsmessage)。这类消息主要是指由WM_开头的消息(WM_COMMAND除外),一般由窗口类和视图类对象来处理。窗口消息往往带有参数,以标志处理消息的方法。(2)控件的通知消息(Controlnotifications)。当控件的状态发生改变(例如用户在控件中进行输入)时,控件就会向其父窗口发送WM_COMMAND通知消息。应用程序框架处理控件消息的方法和窗口消息相同,但按钮的BN_CLICKED通

252、知消息除外,它的处理方法与命令消息相同。(3)命令消息(Commandmessage)。命令消息主要包括由用户交互对象(菜单、工具条的按钮、快捷键等)发送的WM_COMMAND通知消息。命令消息的处理方式与其他两种消息不同,它能够被多种对象接收、处理,这些对象包括文档类、文档模板类、应用程序本身以及窗口和视类等;而窗口消息和控件的通知消息是由窗口对象接收并处理的,这里的窗口对象是指从CWnd中派生的类的对象,它包括CFrameWnd、CMDIFrameWnd、CMDIChildWnd、CView、CDialog以及从这些类派生的对象等。3.4.2 消息和消息映射消息和消息映射2. ClassW

253、izard映射消息的一般方法映射消息的一般方法在MFC中,绝大多数消息都可由MFC的ClassWizard来映射。将ClassWizard对话框切换到MessageMaps页面(参看图3.15),可以看到它有许多选项,如项目组合框、类组合框等。各项功能说明如表3.5所示。表表3.5 ClassWizard对话框的对话框的Message Maps页面功能页面功能3.4.2 消息和消息映射消息和消息映射2. ClassWizard映射消息的一般方法映射消息的一般方法例如,若向CEx_SDIHelloView中添加WM_LBUTTOMDOWN的消息映射,则可按下列步骤进行:(1)按Ctrl+W快捷键

254、打开MFCClassWizard对话框。(2)在Classname组合框中,将类名选定为CEx_SDIHelloView。此时,ObjectIDs和Messages列表内容会相应的改变。(3)在ObjectIDs列表框中选定CEx_SDIHelloView,而在Messages列表中选定WM_LBUTTOMDOWN消息。(4)双击Messages列表中的WM_LBUTTOMDOWN消息或单击AddFunction按钮,都会在CEx_SDIHelloView类中添加该消息的映射函数OnLButtonDown,同时在Memberfuncions列表中显示这一消息映射函数和被映射的消息,结果如图3.

255、16所示。(5)单击EditCode按钮后,ClassWizard对话框退出,并转向文档窗口,定位到OnLButtonDown函数源代码处。(6)添加下列代码:voidCEx_SDIHelloView:OnLButtonDown(UINTnFlags,CPointpoint)MessageBox(你好,我的VisualC+世界!,问候,0);CView:OnLButtonDown(nFlags,point);(7)这样就完成了一个消息映射过程。程序运行后,在窗口客户区单击鼠标左键,就会弹出一个消息对话框。3.4.2 消息和消息映射消息和消息映射图3.16映射WM_LBUTTONDOWN消息3.

256、4.2 消息和消息映射消息和消息映射3. 消息映射代码消息映射代码查看CEx_SDIHelloView程序代码,可以发现:ClassWizard为WM_LBUTTOMDOWN的消息映射作了以下三个方面内容的安排:(1)在头文件Ex_SDIHelloView.h中声明消息处理函数OnLButtonDown:protected:/AFX_MSG(CEx_SDIHelloView)afx_msg void OnLButtonDown(UINT nFlags, CPoint point);/AFX_MSGDECLARE_MESSAGE_MAP()代码中的/AFX_MSG(CEx_SDIHelloVie

257、w)和/AFX_MSG之间的部分是ClassWizard定义的专门用作消息映射函数声明的标记。表示该程序块中的消息映射声明是由ClassWizard来自动管理的,用户一般不需要去更改。需要说明的是,凡/和/之间的程序代码块均由ClassWizard自动管理。3.4.2 消息和消息映射消息和消息映射3. 消息映射代码消息映射代码(2)在Ex_SDIHelloView.cpp源文件前面的消息映射入口处,添加了相应的映射宏:BEGIN_MESSAGE_MAP(CEx_SDIHelloView,CView)/消息映射开始/AFX_MSG_MAP(CEx_SDIHelloView)ON_WM_LBUTT

258、ONDOWN()/AFX_MSG_MAPEND_MESSAGE_MAP()/消息映射结束(3)在Ex_SDIHelloView.cpp文件中写入一个空的消息处理函数的模板,以便用户填入具体代码,如下面的框架:voidCEx_SDIHelloView:OnLButtonDown(UINTnFlags,CPointpoint)/TODO:Addyourmessagehandlercodehereand/orcalldefaultCView:OnLButtonDown(nFlags,point);事实上,根据ClassWizard产生的上述消息映射过程,用户可以自己手动添加一些MFCClassWiz

259、ard不支持的消息映射函数,以完成特定的功能。例如,Ex_HelloMFC示例就是按照上述过程添加消息映射的。3.4.2 消息和消息映射消息和消息映射4. 键盘消息键盘消息当用户按下一个键或组合键时,Windows将WM_KEYDOWN或WM_SYSKEYDOWN放入具有输入焦点的应用程序窗口的消息队列中。当键被释放时,Windows则把WM_KEYUP或WM_SYSKEYUP消息放入消息队列中。对于字符键来说,还会在这两个消息之间产生WM_CHAR消息。MFCClassWizard能自动添加了当前类的WM_KEYDOWN和WM_KEYUP击键消息处理函数的调用,它们具有下列函数原型:afx_

260、msg void OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags );afx_msg void OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags );afx_msg是MFC用于定义消息函数的标志,参数nChar表示虚拟键代码,nRepCnt表示当用户按住一个键时的重复计数,nFlags表示击键消息标志。所谓虚拟键代码,是指与设备无关的键盘编码。在VisualC+中,最常用的虚拟键代码已被定义在Winuser.h中,例如:VK_SHIFT表示SHIFT键,VK_F1表示功能键F1等。同击键消息一样,MF

261、C中的ClassWizard也提供相应的字符消息处理框架,并自动添加了当前类的WM_CHAR消息处理函数调用,它具有下列函数原型:afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags );参数nChar表示键的ASCII码,nRepCnt表示当用户按住一个键时的重复计数,nFlags表示字符消息标志。3.4.2 消息和消息映射消息和消息映射5. 鼠标消息鼠标消息当用户对鼠标进行操作时,像键盘一样也会产生对应的消息。通常,Windows只将键盘消息发送给具有输入焦点的窗口,但鼠标消息不受这种限制。只要鼠标移过窗口的客户区时,就会向该

262、窗口发送WM_MOUSEMOVE(移动鼠标)消息。这里的客户区是指窗口中用于输出文档的区域。由于MFC头文件中定义的与鼠标按钮相关的标识使用了LBUTTON(左)、MBUTTON(中)和RBUTTON(右),因而当在窗口的客户区中按下或释放一个鼠标键时,还会产生如表3.6所示的消息。表表3.6 客户区鼠标消息客户区鼠标消息3.4.2 消息和消息映射消息和消息映射5. 鼠标消息鼠标消息对于所有这些消息来说,ClassWizard都会将映射成类似afx_msgvoidOnXXXX的消息处理函数,如前面WM_LBUTTONDOWN的消息函数OnLButtonDown,它们具有函数原型:afx_msg

263、 void OnXXXX( UINT nFlags, CPoint point );其中,point表示鼠标光标在屏幕的(x,y)坐标;nFlags表示鼠标按钮和键盘组合情况,它可以是下列值的组合(MK前缀表示“鼠标键”):MK_CONTROL键盘上的Ctrl键被按下MK_LBUTTON鼠标左按钮被按下MK_MBUTTON鼠标中按钮被按下MK_RBUTTON鼠标右按钮被按下MK_SHIFT 键盘上的Shift键被按下若想知道某个键被按下,可用对应的标识与nFlags进行逻辑“与”(&)运算,所得结果若为TRUE(非0)时,则表示该键被按下。例如,若收到了WM_LBUTTONDOWN消息,且值n

264、Flags&MK_CONTROL是TRUE时,则表明按下鼠标左键的同时也按下Ctrl键。3.4.2 消息和消息映射消息和消息映射6. 计时器消息计时器消息应用程序是通过CWnd的SetTimer函数来设置并启动计时器的,这个函数的原型如下: UINTSetTimer(UINTnIDEvent,UINTnElapse, void (CALLBACKEXPORT*lpfnTimer)(HWND,UINT,UINT,DWORD);参数nIDEvent用来指定该计时器的标识值(不能为0),当应用程序需要多个计时器时可多次调用该函数,但每一个计时器的标识值应是唯一的,各不相同。nElapse表示计时器的

265、时间间隔(单位为毫秒),lpfnTimer是一个回调函数的指针,该函数由应用程序来定义,用来处理计时器WM_TIMER消息。一般情况下该参数为NULL,此时WM_TIMER消息被放入到应用程序消息队列中供CWnd对象处理。SetTimer函数成功调用后返回新计时器的标识值。当应用程序不再使用计时器时,可调用CWnd:KillTimer函数来停止WM_TIMER消息的传送,其函数原型如下: BOOL KillTimer( int nIDEvent );其中nIDEvent和用户调用SetTimer函数设置的计时器标识值是一致的。对于WM_TIMER消息,ClassWizard会将其映射成具有下列

266、原型的消息处理函数: afx_msgvoidOnTimer(UINTnIDEvent);通过nIDEvent可判断出WM_TIMER是哪个计时器传送的。3.4.2 消息和消息映射消息和消息映射7. 其他窗口消息其他窗口消息在系统中,除了用户输入产生的消息外,还有许多系统根据应用程序的状态和运行过程产生的消息,有时也需要用户进行处理。(1)WM_CREATE消息。该消息是在窗口对象创建后,Windows向视图发送的第一个消息;如果用户有什么工作需要在初始化时处理,就可在该消息处理函数中加入所需代码。但是,由于WM_CREATE消息发送时,窗口对象还未完成,窗口还不可见,因此在该消息处理函数OnC

267、reate内,不能调用那些依赖于窗口处于完成激活状态的Windows函数,如窗口的绘图函数等。(2)WM_CLOSE或WM_DESTROY消息。当用户从系统菜单中关闭窗口或者父窗口被关闭时,Windows都会发送WM_CLOSE消息;而WM_DESTROY消息是在窗口从屏幕消失后发送的,因此它紧随WM_CLOSE之后。(3)WM_PAINT消息。当窗口的大小发生变化、窗口内容发生变化、窗口间的层叠关系发生变化或调用函数UpdateWindow或RedrawWindow时,系统都将产生WM_PAINT消息,表示要重新绘制窗口的内容。该消息处理函数的原型是; afx_msg void OnPain

268、t();用ClassWizard映射该消息的目的是执行自己的图形绘制代码。3.4.3 类的添加和删除类的添加和删除1. 类的添加类的添加给项目添加一个类有很多方法,例如选择“工程”“添加工程”“Files”菜单命令,可将外部源文件所定义的类添加到项目中。但是如果使用MFC的ClassWizard,就可以从大多数MFC类中派生一个类,并且创建的类代码自动包含MFC所必需的消息映射等机制。用MFCClassWizard给项目添加一个类通常是按下列步骤进行的:(1)按快捷键Ctrl+W启动MFCClassWizard对话框。单击AddClass按钮,从弹出的下拉菜单中选择New命令,弹出如图3.17

269、所示的NewClass对话框。(2)对话框中,Name是用来输入用户定义的类名,注意要以“C”字母打头,以保持与MFC标识符命名规则一致;FileName是该类的源代码文件名,单击Change按钮可改变源文件名称及其在磁盘中的位置;Baseclass用来指定该类的基类;DialogID是当选择CDialog作为基类时指定对话框的资源ID号。最下面的Automation是用来设置对自动化的支持。(3)单击OK按钮,一个新类就会自动添加到项目中。3.4.3 类的添加和删除类的添加和删除图3.17NewClass对话框图1. 类的添加类的添加3.4.3 类的添加和删除类的添加和删除2. 类的删除类的

270、删除当添加的类需要删除时,则需要按下列步骤进行:将VisualC+6.0打开的所有文档窗口关闭。将项目工作区窗口切换到FileView页面,展开SourceFiles和HeaderFiles结点,分别选定要删除类的对应.h和.cpp文件,按下Delete键,删除这两个文件。选择“文件”“关闭工作区”菜单命令,关闭项目。从实际的文件夹中删除对应的.h和.cpp文件与.clw文件。这样,当调入项目文件后,按Ctrl+W快捷键就会弹出一个对话框,询问是否重新建立ClassWizard数据文件,单击是按钮,出现如图3.18所示的SelectSourceFiles对话框。单击右下的AddAll按钮即可将

271、.h和.cpp所包含的类删除。3.4.3 类的添加和删除类的添加和删除2. 类的删除类的删除3.18SelectSourceFiles对话框第第4章章 对话框和常用控件对话框和常用控件对话框是Windows应用程序中最重要的用户界面元素之一,是与用户交互的重要手段。在程序运行过程中,对话框可用于捕捉用户的输入信息或数据。对话框是一个特殊类型的窗口,任何对窗口进行的操作(如移动、最大化、最小化等)也可在对话框中实施。一般来说,在对话框中通过各种控件(如按钮、编辑框、列表框、组合框等)来和用户进行交互,控件是在系统内部定义的用于和用户交互的基本单元。4.1 对话框的使用对话框的使用在VisualC

272、+6.0应用程序中,使用一个对话框的一般过程是:添加对话框资源;设置对话框的属性;添加和布局控件;创建对话框类;添加对话框代码;在程序中调用对话框。4.1.1 资源与资源标识资源与资源标识1. 资源的分类资源的分类先用MFCAppWizard创建一个单文档应用程序Ex_SDI,然后项目工作区窗口切换到“ResourceView”页面,展开所有的节点,如图4.1所示。图4.1 Ex_SDI资源视图资源类别资源标识符1. 资源的分类资源的分类可以看出,一个单文档应用程序所使用的资源可分为下列几类:(1)快捷键列表(Accelerator)。一系列组合键的集合,被应用程序用来引发一个动作。该列表一般

273、与菜单命令相关联,用来代替鼠标操作。(2)对话框(Dialog)。含有按钮、列表框、编辑框等各种控件的窗口。(3)图标(Icon)。代表应用程序显示在Windows桌面上的位图,它同时有32x32像素和16x16像素两种规格。(4)菜单(Menu)。用户通过菜单可以完成应用程序的大部分操作。(5)字串表(StringTable)。应用程序使用的全局字符串或其他标识符。(6)工具栏按钮(Toolbar)。工具栏外观是以一系列具有相同尺寸的位图组成的,它通常与一些菜单命令相对应,用以提高用户的工作效率。(7)版本信息(Version)。包含应用程序的版本、用户注册码等相关信息。除了上述常用资源类别

274、外,VisualC+6.0应用程序中还可有鼠标指针、HTML等,也可以自己添加新的资源类别。4.1.1 资源与资源标识资源与资源标识2. ID标识符标识符一般地,要遵循下列规则:在标识符名称中允许使用字母az、AZ、09以及下划线。标识符名称不区分大小写字母,如new_idd与New_Idd是相同的标识符。不能以数字开头,如8BIT是不合法的标识符名。除了上述规则外,出于习惯,VisualC+还提供了一些常用的定义标识符名称的前缀供用户使用、参考,见表4.1。4.1.2 添加对话框资源添加对话框资源在一个MFC应用程序中添加对话框资源,通常按下列步骤进行:(1)选择“插入”“资源”菜单,或按快

275、捷键Ctrl+R打开“插入资源”对话框,在对话框中可以看到资源列表中存在Dialog项,若单击Dialog项左边的“+”号,将展开对话框资源的不同类型选项,如图4.2所示,表4.2列出各种类型的对话框资源的不同用途。图4.2 “插入资源”对话框4.1.2 添加对话框资源添加对话框资源表表4.2 对话框资源类型对话框资源类型其中,新建按钮是用来创建一个由“资源类型”列表中指定类型的新资源,定制按钮是用来创建“资源类型”列表中没有的新类型的资源,导入按钮是用于将外部已有的位图、图标、光标或其他定制的资源添加到当前应用程序中。4.1.2 添加对话框资源添加对话框资源(2)对展开的不同类型的对话框资源

276、不作任何选择,选中“Dialog”,单击新建按钮,系统就会自动为当前应用程序添加了一个对话框资源,并出现如图4.3所示的界面。对话框模板控件工具栏布局工具栏默认标识符图4.3添加对话框资源后的开发环境4.1.3 设置对话框属性设置对话框属性在对话框模板处右击鼠标,从弹出的快捷菜单中选择“属性”菜单项,出现如图4.4所示的对话框属性窗口。图4.4对话框属性窗口4.1.3 设置对话框属性设置对话框属性可以看出,对话框的属性有General(一般)、Styles(风格)、MoreStyles(更多风格)、ExtendedStyles(扩展风格)、MoreExtendedStyles(更多扩展风格)等

277、部分,这里仅介绍最常用的General属性,如表4.3所示。表表4.3 对话框的对话框的General属性属性4.1.3 设置对话框属性设置对话框属性将添加的对话框的属性进行以下3点修改,结果如图4.5所示:将对话框标识符改成IDD_DIALOG_FIRST;将对话框标题改为“我的第一个对话框”;单击字体按钮,通过弹出的字体对话框将对话框内的文本设置成“宋体,9”,以使自己的对话框和Windows中的对话框保持外观上的一致。图4.5对话框属性修改后的界面4.1.4 添加和布局控件添加和布局控件一旦对话框资源被打开或被创建,就会出现对话框编辑器,通过它可以在对话框中进行控件的添加和布局等操作。1

278、. 控件的添加控件的添加对话框编辑器最初打开时,控件工具栏是随之出现的,利用此工具栏中的各个按钮可以顺利完成控件的添加。图4.6说明了各个按钮所对应的控件类型。控件的选择静态文本组框复选框组合框水平滚动条旋转按钮滑动条列表视图标签复合编辑月历用户定制控件静态图片编辑框按钮单选框列表框垂直滚动条进展条热键树形视图动画日期选择IP地址扩展组合框图4.6控件工具栏和各按钮含义4.1.4 添加和布局控件添加和布局控件在对话框中添加一个控件的方法有下列几种:在控件工具栏中单击某控件,此时的鼠标箭头在对话框内变成“十”字形状;在对话框指定位置单击鼠标左键,则此控件被添加到对话框的相应位置,再拖动刚添加控件

279、的选择框可改变其大小和位置。在控件工具栏中单击某控件,此时的鼠标箭头对话框内变成“十”字形状;在指定位置处单击鼠标左键不放,拖动鼠标至满意位置,释放鼠标键。用鼠标左键点中控件工具栏中的某控件,并按住鼠标左键不放;在移动鼠标到对话框的指定位置的过程中,用户会看到一个虚线框,下面带有该控件的标记;释放鼠标左键,新添加的控件立即出现在对话框中。4.1.4 添加和布局控件添加和布局控件2. 控件的选取控件的选取控件的删除、复制和布局操作一般都要先选取控件,若选取单个控件,则可以下列方法:用鼠标直接选取。首先保证在控件工具栏中的选择按钮()是被选中的,然后移动鼠标指针至指定的控件上,单击鼠标左键即可。用

280、助记符来选取。如果控件的标题中带有下划线的字符,这个字符就是助记符,选择时直接按下该助记符键或“Alt+助记符”组合键即可。用Tab键选取。在对话框编辑器中,系统会根据控件的添加次序自动设置相应的Tab键次序。利用Tab键,用户可在对话框内的控件中进行选择。每按一次Tab键依次选取对话框中的下一个控件,若按住Shift键,再单击Tab键则选取上一个控件。对于多个控件的选取,可采用下列方法:先在对话框内按住鼠标左键不放,拖出一个大的虚框,然后释放鼠标,则被该虚框所包围的控件都将被选取。先按住Shift键不放,然后用鼠标选取控件,直到所需要的多个控件选取之后再释放Shift键。若在选取时,对已选取

281、的控件再选取一下,则取消该控件选取。4.1.4 添加和布局控件添加和布局控件需要注意的是:(1)一旦单个控件被选取后,其四周由选择框包围着,选择框上还有几个(通常是八个)蓝色实心小方块,拖动它可改变控件的大小,如图4.7(a)所示。(2)多个控件被选取后,其中只有一个控件的选择框有几个蓝色实心小方块,这个控件称为主要控件,而其他控件的选择框的小方块是空心的。如图4.7(b)所示。图4.7 单个控件和多个控件的选择框(a)(b)4.1.4 添加和布局控件添加和布局控件3. 控件的删除、复制和布局控件的删除、复制和布局当单个控件或多个控件被选取后,按方向键或用鼠标拖动控件的选择框可移动控件。若在鼠

282、标拖动过程中还按住Ctrl键则复制控件。若按Del键可将选取的控件删除。当然还有其他一些编辑操作,但这些操作方法和一般的文档编辑器基本相同,这里不再重复。对于控件的布局,对话框编辑器中提供了控件布局工具栏,如图4.8所示,它可以自动地排列对话框内的控件,并能改变控件的大小。图4.8控件布局工具栏3. 控件的删除、复制和布局控件的删除、复制和布局与布局工具相对应的菜单命令在“编排”菜单下,而且大部分命令均有相应的快捷键,如图4.9所示。表4.4还列出菜单命令及其相应的功能与快捷键描述。“编排”菜单不是在VisualC+6.0开发环境一开始就出现的,而是随着对话框编辑器的打开而显示的。图4.9“编

283、排”菜单命令项表表4.4 “编排编排”菜单命令的快捷键及功能描述菜单命令的快捷键及功能描述4.1.4 添加和布局控件添加和布局控件4. 测试对话框测试对话框“编排”菜单下的Test命令或布局工具栏上的测试按钮是用来模拟所编辑的对话框的运行情况,帮助用户检验对话框是否符合用户的设计要求以及控件功能是否有效等。5. 操作示例操作示例下面来向对话框添加一个静态文本控件。一个静态文本控件就是一个文本标签,如图4.10所示。右击添加的控件,从弹出的快捷菜单中选择“属性”,出现如图4.11所示的属性对话框。图4.10添加的静态文本控件图4.11静态文本控件的属性对话框4.1.5 创建对话框类创建对话框类在

284、对话框资源模板的空白区域(没有其他元素或控件)内双击鼠标,将弹出如图4.12所示的对话框,询问是否为对话框资源创建一个新类。单击OK按钮,将弹出如图4.13所示的NewClass对话框。在Name框中输入类名CFirstDlg。Baseclass和DialogID内容是由系统自动设置的,一般无需修改。从Baseclass框的内容可以看出,用户对话框类是从基类CDialog派生而来的。单击OK按钮,一个基于对话框资源模板的对话框类CFirstDlg就创建好了。此时,出现MFCClassWizard(MFC类向导)对话框。图4.12“AddingaClass”对话框图4.13“NewClass”对

285、话框4.1.6 添加对话框代码添加对话框代码在MFCClassWizard对话框,查看“Classname”列表中是否选择了CFirstDlg,若不是,则在IDs列表中选择CFirstDlg。在Messages框中找到并选定WM_INITDIALOG消息,如图4.14。图4.14“MFCClassWizard”对话框4.1.6 添加对话框代码添加对话框代码在Memberfunctions列表框中选择刚添加的OnInitDialog函数,单击EditCode按钮(或直接在函数名双击鼠标),将自动出现该函数代码编辑窗口,在此函数中添加下列代码:BOOLCFirstDlg:OnInitDialog(

286、)CDialog:OnInitDialog();/TODO:AddextrainitializationhereCStatic*pWnd=(CStatic*)GetDlgItem(IDC_STATIC_1);pWnd-SetWindowText(这是我的第一个对话框!);returnTRUE;/returnTRUEunlessyousetthefocustoacontrol/EXCEPTION:OCXPropertyPagesshouldreturnFALSE代码中,CStatic是静态文本控件的MFC类,SetWindowText是CWnd的一个成员函数,用来设置窗口的文本内容,由于控件类是

287、CWnd的子类(派生类),因此可以使用基类的SetWindowText来改变静态文本控件显示的内容。GetDlgItem也是CWnd类的一个成员函数,用来获得对话框中控件(参数是控件的ID标识符,这里是IDC_STATIC_1)的窗口指针。4.1.7 在程序中调用对话框在程序中调用对话框在项目工作区窗口中选择ResourceView页面,双击资源“Menu”项中的IDR_MAINFRAME,则菜单编辑器窗口出现在主界面的右边,相应的Ex_SDI项目的菜单资源被显示出来,在菜单的最右一项,VisualC+为用户留出了一个空位置,用来输入新的菜单项,如图4.15所示。菜单的空位置图4.15Ex_S

288、DI菜单资源4.1.7 在程序中调用对话框在程序中调用对话框(2)在菜单的空位置上双击鼠标左键,则出现它的属性对话框,在标题框中输入“测试(&T)”,结果如图4.16所示,其中符号&用来其后面的字符作为该菜单项的助记符,这样当按住“Alt”键不放,再敲击该助记符键时,对应的菜单项就会被选中,或在菜单打开时,直接按相应的助记符键,对应的菜单项也会被选中。图4.16Ex_SDI菜单资源子菜单的空位置保存可见按钮4.1.7 在程序中调用对话框在程序中调用对话框(3)单击菜单属性对话框中的“保存可见”(KeepVisible)按钮,使此属性对话框一直可见。单击“测试”菜单项下方的空位置,在属性对话框中

289、,输入标题“对话框(&D)”,在ID框输入该菜单项的资源标识:ID_TEST_DLG,结果如图4.17所示,单击属性对话框右上角的关闭按钮。(4)单击“测试”菜单项并按住鼠标左键不放,移动鼠标,将“测试”菜单项移到“查看”和“帮助”菜单项之间,然后释放鼠标。结果如图4.18所示。图4.17修改菜单项属性图4.18菜单项“对话框”拖放后的位置4.1.7 在程序中调用对话框在程序中调用对话框(5)按Ctrl+W快捷键,弹出MFCClassWizard对话框。在MessageMaps页面中,从Classname列表中选择CMainFrame,在IDs列表中选择ID_TEST_DLG,然后在Messa

290、ges框中选择COMMAND消息。(6)单击AddFunction按钮或双击COMMAND消息,出现AddMemberFunction对话框,输入成员函数的名称。系统默认的函数名为OnTestDlg,如图4.19所示。该函数是对菜单项ID_TEST_DLG的映射,也就是说,当在应用程序运行时,用户选择“测试”“对话框”,则该函数OnTestDlg被调用,执行函数中的代码。图4.19添加成员函数4.1.7 在程序中调用对话框在程序中调用对话框(7)单击OK按钮,在ClassWizard的Memberfunctions列表中将列出新增加的成员函数。选择此函数,单击EditCode按钮(或直接双击函

291、数名),在此成员函数中添加下列代码:voidCMainFrame:OnTestDlg()CFirstDlgdlg;/定义对话框类对象dlg.DoModal();/显示对话框代码中,DoModal是CDialog基类成员函数,用来将对话框按模式方式来显示。(8)在OnTestDlg函数的实现文件MainFrm.cpp的前面添加CFirstDlg类的包含语句,即:#includeEx_SDI.h#includeMainFrm.h#includeFirstDlg.h4.1.7 在程序中调用对话框在程序中调用对话框(9)编译并运行。在应用程序菜单上,选择“测试”“对话框”菜单项,将出现如图4.20的对

292、话框,这个对话框就是刚才添加的对话框。图4.20 对话框的显示4.1.8 使用无模式对话框使用无模式对话框对话框有两种类型,一种是模式对话框,另一是无模式对话框。1. 模式对话框模式对话框所谓“模式对话框”是指当对话框被弹出,用户必须在对话框中作出相应的操作,在退出对话框之前,对话框所在应用程序的其它操作不能继续执行。一般情况下,模式对话框会有OK(确定)和Cancel(取消)按钮。单出OK按钮,系统认定用户在对话框中的选择或输入有效,对话框退出;单击Cancel按钮,对话框中的选择或输入无效,对话框退出,程序恢复原有状态。模式对话框的应用范围较广,上面示例中的对话框和平常所见到的大多数对话框

293、都是模式对话框。2. 无模式对话框无模式对话框所谓“无模式对话框”是指当对话框被弹出后,一直保留在屏幕上,用户可继续在对话框所在的应用程序中进行其它操作;当需要使用对话框时,只需象激活一般窗口一样单击对话框所在的区域即可激活。4.1.8 使用无模式对话框使用无模式对话框下面在单文档应用程序Ex_SDI中创建并使用无模式对话框,其步骤如下:(1)在该项目中添加一个对话框资源,将其标识符设为IDD_DIALOG_SECOND,标题设为“无模式对话框”,对话框字体和大小设为“宋体,9号”。(2)为IDD_DIALOG_SECOND对话框资源创建一个对话框类CSecondDlg。(3)按Ctrl+W快

294、捷键打开MFCClassWizard,从“Classname”列表中选择CSecondDlg,在IDs列表中选择IDOK,它是对话框中OK按钮的标识符,然后在Messages框中选择BN_CLICKED(单击按钮)消息,单击AddFunction按钮或双击BN_CLICKED消息,出现“AddMemberFunction”对话框以输入成员函数的名称,保留系统默认的函数名为OnOK,如图4.21所示。该函数是对OK按钮单击消息的映射,即当用户单击此对话框中的OK按钮时,OnOK函数被执行。(4)修改CSecondDlg:OnOK函数中的代码。voidCSecondDlg:OnOK()Destro

295、yWindow();/终止对话框显示deletethis;/删除对话框,释放内存空间代码中,DestroyWindow是对话框基类CWnd的一个成员函数,用来终止窗口。图4.21映射IDOK消息4.1.8 使用无模式对话框使用无模式对话框(5)用MFCClassWizard来添加Cancel按钮(标识符为IDCANCEL)的BN_CLICKED消息映射,并修改其映射函数CSecondDlg:OnCancel的代码。voidCSecondDlg:OnCancel()DestroyWindow();deletethis;(6)将项目工作区切换到ClassView(类视图)页面,展开CMainFra

296、me类的所有成员,双击OnTestDlg就会在文档窗口中自动定位到该函数的实现代码处,将其修改成下列代码:voidCMainFrame:OnTestDlg()CSecondDlg*pDlg=newCSecondDlg;/使用new来为对话框分配内存空间pDlg-Create(IDD_DIALOG_SECOND);/创建对话框pDlg-ShowWindow(SW_NORMAL);/显示对话框代码中,Create函数可以用来以一个对话框资源来创建对话框,ShowWindow是CWnd一个成员函数,用来显示对话框,SW_NORMAL用来指定将窗口显示成一般常用的状态。4.1.8 使用无模式对话框使用

297、无模式对话框(7)在文件MainFrm.cpp的前面添加CSecondDlg类的包含语句,即:#includeFirstDlg.h#includeSecondDlg.h(8)编译并运行。在应用程序菜单上,多次选择“测试”“对话框”菜单项,将会在同一个位置中出现多个对话框,拖动这些对话框到适当位置,如图4.22所示。图4.22无模式对话框显示的结4.2 控件的创建和使用方法控件的创建和使用方法控件是在系统内部定义的能够完成特定功能的控制程序单元。在应用程序中使用控件不仅简化了编程,还能完成常用的各种功能。为了更好地发挥控件作用,用户还必须理解和掌握控件的属性、消息以及创建和使用的方法。4.2.1

298、 控件的创建方法控件的创建方法控件的创建方式有以下两种:一种是在对话框模板中用编辑器指定控件,也就是说,将控件的父窗口指定为对话框,这样做的好处是明显的,因为当应用程序启动该对话框时,Windows系统就会为对话框创建控件;而当对话框消失时,控件也随之清除。另一种方式是将控件看作是任一窗口的子窗口,并通过调用相应的Create函数来创建。4.2.1 控件的创建方法控件的创建方法例例Ex_DlgCtrls 使用控件的编程方式来创建一个按钮使用控件的编程方式来创建一个按钮(1)选择“文件”“新建”菜单,在弹出的新建对话框的项目类型列表中选择MFCAppWizard(exe)类型,在工程框中输入项目

299、名Ex_DlgCtrls,结果如图4.23所示。图4.23MFCAppWizard的“新建”对话框4.2.1 控件的创建方法控件的创建方法(2)单击确定按钮,在出现的“Step1”对话框中选择“基本对话”应用程序类型,单击完成按钮,创建一个默认的基于对话框的应用程序项目。(3)将项目工作区切换到ClassView页面,展开CEx_DlgCtrlsDlg类,右击CEx_DlgCtrlsDlg类名,弹出如图4.24所示的快捷菜单。图4.24弹出的快捷菜单4.2.1 控件的创建方法控件的创建方法4)从快捷菜单中选择AddMemberVariable(添加成员变量),在出现的对话框中定义一个CButt

300、on类对象m_btnWnd,通常以“m_”来作为变量的开头,表示“成员”(member)的意思。如图4.25所示,单击OK按钮。图4.25添加成员变量4.2.1 控件的创建方法控件的创建方法(5)在项目工作区窗口的ClassView页面中,双击OnInitDialog函数名,在该函数中添加下列代码:BOOLCEx_DlgCtrlsDlg:OnInitDialog()CDialog:OnInitDialog();m_btnWnd.Create(你好,WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,CRect(20,20,120,60),this,201);/创建CFont*f

301、ont=this-GetFont();/获取对话框的字体m_btnWnd.SetFont(font);/设置控件字体returnTRUE;/returnTRUEunlessyousetthefocustoacontrol由于OnInitDialog函数在对话框初始化时被调用,因此将对话框中的一些初始化代码都添加在此函数中。代码中,Create用来创建一个按钮控件,该函数第一个参数用来指定该按钮的标题,第二个参数用来指定控件的风格,第三个参数用来指定它在父窗口中的位置和大小,第四个参数用来指定父窗口指针,最后一个参数是指定该控件的标识值。4.2.1 控件的创建方法控件的创建方法(6)编译并运行,

302、结果如图4.26所示。图4.26 控件创建的结果4.2.2 控件的消息及消息映射控件的消息及消息映射当控件的状态发生改变时,控件就会向其父窗口发送消息,这个消息称为“通知消息”。对于每个消息,系统都会用一个MSG结构来记录,MSG具有下列结构:typedefstructtagMSG/msgHWNDhwnd;/接收到消息的窗口句柄UINT message;/消息WPARAMwParam;/消息的附加信息,它的含义取决于messageLPARAMlParam;/消息的附加信息,它的含义取决于messageDWORDtime;/消息传送时的时间POINTpt;/消息传送时,光标所在的屏幕坐标MSG;

303、对于一般控件来说,其通知消息通常是一条WM_COMMAND消息,这条消息的wParam参数的低位字中含有控件标识符,wParam参数的高位字则为通知代码,lParam参数则是指向控件的句柄。而对于有些控件,其通知消息通常是一条WM_NOTIFY消息,这条消息的wParam参数是发送通知消息的控件的标识符,而lParam参数则是指向一个结构指针。4.2.2 控件的消息及消息映射控件的消息及消息映射1. 映射控件消息映射控件消息在MFC中,映射一个控件消息是非常简便的。例如下面的步骤是用来映射按钮命令消息。 打开Ex_DlgCtrls应用程序项目。将项目工作区窗口切换到ResourseView页面

304、,双击Dialog资源下的标识IDD_EX_DLGCTRLS_DIALOG,打开该对话框资源模板。删除“TODO:在这里设置对话控制。”控件,添加一个按钮控件,保留其默认属性。如图4.27所示。图4.27添加一个按钮4.2.2 控件的消息及消息映射控件的消息及消息映射(4)按快捷键Ctrl+W,打开MFCClassWizard对话框,查看“Classname”列表中是否选择了CEx_DlgCtrlsDlg,在IDs列表中选择IDC_BUTTON1,这是添加按钮后,系统自动为此按钮设置的默认标识符,然后在Messages框中选择BN_CLICKED消息。(5)单击AddFunction按钮或双击

305、BN_CLICKED消息,出现“AddMemberFunction”对话框,在这里可以输入成员函数的名称,系统默认的函数名为OnButton1。如图4.28所示。图4.28添加按钮消息映射函数4.2.2 控件的消息及消息映射控件的消息及消息映射(6)单击OK按钮,在MFCClassWizard的“Memberfunctions”列表中将列出新增加的成员函数。选择此函数,单击EditCode按钮(或直接在函数名双击鼠标),开发环境的文档窗口中将自动打开该函数所在的源代码文件,并定位到该函数的实现代码处。在此成员函数中添加下列代码:voidCEx_DlgCtrlsDlg:OnButton1()Me

306、ssageBox(你按下了Button1按钮!);(7)编译并运行,当单击Button1按钮时,就会执行OnButton1函数,弹出一个消息对话框。这就是按钮BN_CLICKED消息的映射过程,其他控件的消息也可以类似操作。4.2.2 控件的消息及消息映射控件的消息及消息映射2. 映射控件通用消息映射控件通用消息上述过程是映射一个控件的某一个消息,事实上也可通过WM_COMMAND消息的映射来处理一个或多个控件的通用消息,如下面的步骤:打开MFCClassWizard对话框,在“Classname”列表中是否选择了CEx_DlgCtrlsDlg,在IDs列表中选择CEx_DlgCtrlsDlg

307、,在Messages框中找到并双击OnCommand,这样OnCommand消息函数就添加好了,如图4.29所示。图4.29添加OnCommand函数重载4.2.2 控件的消息及消息映射控件的消息及消息映射(2)在OnCommand函数中添加下列代码:BOOLCEx_DlgCtrlsDlg:OnCommand(WPARAMwParam,LPARAMlParam)WORDnCode=HIWORD(wParam);/控件的通知消息WORDnID=LOWORD(wParam);/控件的ID值if(nID=201)&(nCode=BN_CLICKED)MessageBox(你按下了你好按钮!);if(

308、nID=IDC_BUTTON1)&(nCode=BN_CLICKED)MessageBox(这是在OnCommand处理的结果!);returnCDialog:OnCommand(wParam,lParam);注意:第一条if语句中,201是前面用Create创建按钮时指定的标识值。(3)编译并运行。当单击如前图4.27所示的Button1按钮时,就会弹出一个消息对话框。4.2.3控件的数据交换(DDX)和数据校验(DDV)使用MFCClassWizard可以很容易地为一个控件定义关联变量并可设置其数据范围。例如,下面的步骤是为CEx_DlgCtrlsDlg类的按钮控件IDC_BUTTON1添

309、加并使用其关联变量m_RelBtn。(1)打开MFCClassWizard,并切换到MemberVariables页面,如图4.30所示。图4.30“MemberVariables”页面4.2.3 控件的数据交换控件的数据交换(DDX)和数据校验和数据校验(DDV)(2)选定Classname为CEx_DlgCtrlsDlg,然后在ControlIDs列表中,选定所要关联的控件ID标识符IDC_BUTTON1,双击鼠标左键或单击AddVariable按钮,弹出AddMemberVariable对话框,在对话框设置变量的名称、类别和数据类型,如图4.31所示。图4.31“AddMemberVar

310、iable”对话框4.2.3 控件的数据交换控件的数据交换(DDX)和数据校验和数据校验(DDV)(3)在Membervariablename框中填好与控件相关联的成员变量m_RelBtn,单击OK按钮,又回到MFCClassWizard对话框的MemberVariables页面中,在ControlIDs列表中出现刚才添加的控件关联变量(或直接称之为“控件变量”)。(4)单击确定按钮后,打开CEx_DlgCtrlsDlg类源文件,可以发现MFCClassWizard对上述操作作了以下三方面的修改。在Ex_DlgCtrlsDlg.h文件中,添加控件关联变量的声明,代码如下面的加粗部分:/Dial

311、ogData/AFX_DATA(CEx_DlgCtrlsDlg)enumIDD=IDD_EX_DLGCTRLS_DIALOG;/枚举类型CButtonm_RelBtn;CString m_strEdit;/AFX_DATA4.2.3 控件的数据交换控件的数据交换(DDX)和数据校验和数据校验(DDV)在Ex_DlgCtrlsDlg.cpp文件中的CEx_DlgCtrlsDlg构造函数实现代码处,添加了控件变量的一些初始代码:CEx_DlgCtrlsDlg:CEx_DlgCtrlsDlg(CWnd*pParent/*=NULL*/):CDialog(CEx_DlgCtrlsDlg:IDD,pPa

312、rent)/AFX_DATA_INIT(CEx_DlgCtrlsDlg)m_strEdit = _T();/AFX_DATA_INIT4.2.3 控件的数据交换控件的数据交换(DDX)和数据校验和数据校验(DDV)在Ex_DlgCtrlsDlg.cpp文件中的DoDataExchange函数体内,添加了控件的DDX/DDV代码,它们都是一些以DDV_或DDX_开头的函数调用。voidCEx_DlgCtrlsDlg:DoDataExchange(CDataExchange*pDX)CDialog:DoDataExchange(pDX);/调用此函数作为DDX的开始/AFX_DATA_MAP(CE

313、x_DlgCtrlsDlg)DDX_Control(pDX, IDC_BUTTON1, m_RelBtn);DDX_Text(pDX, IDC_EDIT1, m_strEdit);/将IDC_EDIT1与m_strEdit进行数据交换DDV_MaxChars(pDX, m_strEdit, 20);/校验m_strEdit的最大字符个数不超过20/AFX_DATA_MAP4.2.3控件的数据交换(DDX)和数据校验(DDV)(5)当为一个控件定义一个关联的数据变量后,就可以使用CWnd:UpdateData函数实现控件数据的输入和读取。例如,将CEx_DlgCtrlsDlg:OnButton1

314、修改成下列代码:voidCEx_DlgCtrlsDlg:OnButton1()UpdateData();/默认参数值是TRUEm_RelBtn.SetWindowText(m_strEdit);代码中,UpdateData函数只有一个为TRUE或FALSE的参数。当调用UpdateData(FALSE)时,数据由控件相关联的成员变量向控件传输,当调用UpdateData(TRUE)或不带参数的UpdateData时,数据从控件向相关联的成员变量复制。4.2.3控件的数据交换(DDX)和数据校验(DDV)(6)运行该程序,当在编辑框中输入“Hello”后,单击Button1按钮,OnButton

315、1函数中的UpdateData将编辑框内容保存到m_strEdit变量中,从而执行下一条语句后按钮的名称就变成了编辑框控件中的内容“Hello”,其结果如图4.33所示图4.33 使用控件的数据成员变量4.3 常用控件常用控件根据控件的特性和功能,一般可将其分为三类:Windows公共控件、ActiveX控件以及MFC新增的一些控件等。表4.5列出了本书所用到的常用控件类。表表4.5 常用控件类常用控件类4.3.1 静态控件和按钮静态控件和按钮1. 静态控件静态控件一个静态控件是用来显示一个字符串、框、矩形、图标、位图或增强的图元文件。它可以被用来作为标签、框或用来分隔其它的控件。一个静态控件

316、一般不接收用户输入,也不产生通知消息。在对话框编辑器的控件工具栏中,属于静态控件的有:静态文本()、组框()和静态图片()三种。其中,静态图片控件的一般属性对话框如图4.34所示。表4.6列出了其一般属性的各个项的含义。在属性对话框中,用户可以选择图片“类型”、“图像”两个组合框中的有关选项内容,并可将应用程序资源中的图标、位图等内容显示在该静态图片控件中。另外,用户还可设置其风格来改变控件的外观以及图像在控件的位置等。1. 静态控件静态控件图4.34静态图片控件的General属性对话框表表4.6 静态图片控件的静态图片控件的General和和Style属性属性4.3.1 静态控件和按钮静态

317、控件和按钮2. 按钮按钮在Windows中所用的按钮是用来实现一种开与关的输入,常见的按钮有3种类型:按键按钮、单选按钮、复选框按钮,如图4.37所示。图4.37按钮的不同类型4.3.1 静态控件和按钮静态控件和按钮2. 按钮按钮(1)按键按钮。按键按钮通常可以立即产生某个动作,执行某个命令,因此也常被称为命令按钮。按键按钮有两种风格:标准按键按钮和默认按键按钮。(2)单选按钮。单选按钮的外形是在文本前有一个圆圈,当它被选中时,单选按钮中就标上一个黑点,它可分为一般和自动两种类型。在自动类型中,用户若选中同组按钮中的某个单选按钮,则其余的单选按钮的选中状态就会清除,保证了多个选项始终只有一个被

318、选中。(3)复选框。复选框的外形是在文本前有一个空心方框,当它被选中时,复选框中就加上一个“”标记,通常复选框只有选中和未选中两种状态,若复选框前面有一个灰色是“”,则这样的复选框是三态复选框,如图4.37的Check2,它表示复选框的选择状态是“不确定”。设定成三态复选框的方法是在复选框属性对话框的Style页面中选中“(状态)Tri-state”项。4.3.1 静态控件和按钮静态控件和按钮3. 按钮的消息按钮的消息在按钮映射的消息中,常见的只有两个:BN_CLICKED(单击按钮)和BN_DOUBLE-CLICKED(双击按钮)。4. 按钮选中操作按钮选中操作最常用的按钮操作是设置或获取一

319、个按钮或多个按钮的选中状态。CButton类的成员函数SetCheck和GetCheck分别用来设置或获取指定按钮的选中状态,其原型如下:void SetCheck( int nCheck );int GetCheck( ) const;其中,nCheck和GetCheck函数返回的值可以是:0表示不选中,1表示选中,2表示不确定(仅用于三态按钮)。而对于同组多个单选按钮的选中状态的设置或获取,需要使用通用窗口类CWnd的成员函数CheckRadioButton和GetCheckedRadioButton,它们的原型如下:void CheckRadioButton( int nIDFirstB

320、utton, int nIDLastButton, int nIDCheckButton );int GetCheckedRadioButton( int nIDFirstButton, int nIDLastButton );其中,nIDFirstButton和nIDLastButton分别指定同组单选按钮的第一个和最后一个按钮ID值,nIDCheckButton用来指定要设置选中状态的按钮ID值,函数GetCheckedRadioButton返回被选中的按钮ID值。4.3.1 静态控件和按钮静态控件和按钮5. 示例:制作问卷调查示例:制作问卷调查问卷调查是日常生活中经常遇到的调查方式。例如

321、,图4.38就是一个问卷调查对话框,它针对“上网”话题提出了三个问题,每个问题都有四个选项,除最后一个问题外,其余都是单项选择。图4.38上网问卷调查对话框4.3.1 静态控件和按钮静态控件和按钮例例Ex_Research 制作问卷调查制作问卷调查1)创建并设计对话框创建并设计对话框(1)创建一个默认的对话框应用程序Ex_Research。(2)VisualC+会自动打开对话框编辑器并显示对话框资源模板。单击对话框编辑器工具栏上的切换网格按钮,显示对话框网格,将对话框标题改为“上网问卷调查”。(3)调整对话框的大小,删除对话框中间的“TODO:在这里设置对话控制。”静态文本控件,将确定和取消按

322、钮移至对话框的下方,并向对话框中添加组框(Group)控件,然后调整其大小和位置。(4)右击添加的组框控件,从弹出的快捷菜单中选择“属性”菜单,出现该控件的属性对话框,在属性对话框窗口中可以看到它的ID为默认的IDC_STATIC。将其Caption属性内容由“Static”改成“你的年龄”。在组框控件的Styles属性中,“水平对齐”属性用来指定文本在顶部的左边(Left)、居中(Center)还是右边(Right)。默认(Default)选项表示左对齐。4.3.1 例例Ex_Research1)创建并设计对话框创建并设计对话框(5)在组框内添加4个单选按钮,默认的ID依次为IDC_RADI

323、O1、IDC_RADIO2、IDC_RADIO3和IDC_RADIO4。在其属性对话框中将ID属性内容分别改成IDC_AGE_L18、IDC_AGE_18T27、IDC_AGE_28T38和IDC_AGE_M38,然后将其“标题”Caption属性内容分别改成“38”,最后调整位置,结果如图4.39所示。图4.39 添加的组框和单选按钮4.3.1 例例Ex_Research1)创建并设计对话框创建并设计对话框(6)接下来添加一个静态文本,标题设为“你使用的接入方式:”,然后在其下再添加4个单选按钮,标题分别是“FTTL或ADSL”、“单位LAN”、“拨号56K”和“其他”,并将相应的ID属性依

324、次改成:IDC_CM_FTTL、IDC_CM_LAN、IDC_CM_56K和IDC_CM_OTHER。用对话框编辑器工具栏的按钮命令调整控件左右之间的间距,结果如图4.40所示。图4.40再添加单选框图4.3.1 例例Ex_Research1)创建并设计对话框创建并设计对话框(7)在对话框的下方,再添加一个组框控件,其标题为“你上网主要是”。然后添加四个复选框,其标题分别为“收发邮件”、“浏览资料”、“聊天游戏”和“其他”,ID分别为IDC_DO_POP、IDC_DO_READ、IDC_DO_GAME和IDC_DO_OTHER。结果如图4.41所示。图4.41三个问题全部添加后的对话框4.3.

325、1 例例Ex_Research1)创建并设计对话框创建并设计对话框(8)单击工具栏上的测试对话框按钮。对话框测试后,可以发现:顺序添加的这8个单选按钮全部变成一组,也就是说,在这组中只有一个单选按钮被选中,这不符合我们的本意。解决这个问题的最好的办法是将每一组中的第一个单选按钮的Group(组)属性选中。(9)分别将这二个问题中的第一个单选按钮的Group(组)属性均选中。如图4.42所示是对第二个问题设置的结果。图4.42选中“Group”属性4.3.1 例例Ex_Research1)创建并设计对话框创建并设计对话框(10)单击对话框编辑器工具栏上的切换辅助线按钮,然后将对话框中的控件调整到

326、辅助线以内,并适当对其他控件进行调整。这样,整个问卷调查的对话框就设计好了,单击工具栏上的测试对话框按钮进行测试。4.3.1 例例Ex_Research(2) 完善代码完善代码(1)将项目工作区切换到ClassView(类视图)页面,展开CEx_ResearchDlg类的所有成员,双击OnInitDialog函数名就会在文档窗口中自动定位到该函数的实现代码处,在此函数添加下列初始化代码:BOOLCEx_ResearchDlg:OnInitDialog()CDialog:OnInitDialog();CheckRadioButton(IDC_AGE_L18,IDC_AGE_M38,IDC_AGE

327、_18T27);CheckRadioButton(IDC_CM_FTTL,IDC_CM_OTHER,IDC_CM_FTTL);CButton*pBtn=(CButton*)GetDlgItem(IDC_DO_POP);pBtn-SetCheck(1);/使“收发邮件”复选框选中returnTRUE;/returnTRUEunlessyousetthefocustoacontrol代码中,GetDlgItem是CWnd类的一个成员函数,用来获得对话框中控件(参数是控件的ID标识符)的窗口指针。4.3.1 例例Ex_Research(2) 完善代码完善代码(2)用MFCClassWizard在CE

328、x_ResearchDlg类中添加IDOK按钮的BN_CLICKED消息映射,并添加下列代码,使得用按确定按钮获取用户所作的选择内容。voidCEx_ResearchDlg:OnOK()CStringstr,strCtrl;/定义两个字符串变量,CString是操作字符串的MFC类/获取第一个问题的用户选择str=你的年龄:;UINTnID=GetCheckedRadioButton(IDC_AGE_L18,IDC_AGE_M38);GetDlgItemText(nID,strCtrl);/获取指定控件的标题文本str=str+strCtrl;/获取第二个问题的用户选择str=str+n你使用

329、的接入方式:;nID=GetCheckedRadioButton(IDC_CM_FTTL,IDC_CM_OTHER);GetDlgItemText(nID,strCtrl);/获取指定控件的标题文本str=str+strCtrl;/获取第三个问题的用户选择str=str+n你上网主要是:n;UINTnCheckIDs4=IDC_DO_POP,IDC_DO_READ,IDC_DO_GAME,IDC_DO_OTHER;CButton*pBtn;for(inti=0;iGetCheck()pBtn-GetWindowText(strCtrl);str=str+strCtrl;str=str+;Mes

330、sageBox(str);CDialog:OnOK();代码中,GetDlgItemText是CWnd类成员函数,用来获得对话框(或其他窗口)中的指定控件的窗口文本。在单选按钮和复选框中,控件的窗口文本就是它们的标题属性内容。该函数有两个参数,第一个参数用来指定控件的标识,第二个参数是返回的窗口文本。后面的函数GetWindowText的作用与GetDlgItemText相同,也是获取窗口的文本内容。不过,GetWindowText使用更加广泛,但要注意这两个函数在使用上的不同。4.3.1 例例Ex_Research(2) 完善代码完善代码(3)编译并运行,出现“上网问卷调查”对话框,当回答问

331、题后,按确定按钮,出现如图4.43所示的消息对话框,显示用户选择的内容。图4.43显示用户选择的内容4.3.2 编辑框和旋转按钮控件编辑框和旋转按钮控件1. 编辑框编辑框编辑框是一个让用户从键盘输入和编辑文本的矩形窗口,用户可以通过它,很方便地输入各种文本、数字或者口令,也可使用它来编辑和修改简单的文本内容。当编辑框被激活且具有输入焦点时,就会出现一个闪动的插入符(又可称为文本光标),表明当前插入点的位置。(1)编辑框的属性和通知消息。用对话框编辑器可以方便地设置编辑框的属性和风格,如图4.44所示。表4.7还列出其中各项的含义。当编辑框的文本修改或者被滚动时,会向其父窗口发送一些消息,如表4

332、.8所示。图4.44编辑框的属性对话框表表4.7 编辑框的编辑框的Style属性属性表表4.8 编辑框的通知消息编辑框的通知消息4.3.2 编辑框和旋转按钮控件编辑框和旋转按钮控件1. 编辑框编辑框(2)编辑框的基本操作。由于编辑框的形式多样,用途各异,因此下面针对编辑框的不同用途,分别介绍一些常用操作,以实现一些基本功能。口令设置。口令设置在编辑框中不同于一般的文本编辑框,用户输入的每个字符都被一个特殊的字符代替显示,这个特殊的字符称为口令字符。默认的口令字符是“*”,应用程序可以用成员函数CEdit:SetPasswordChar来定义自己的口令字符,其函数原型如下:void SetPas

333、swordChar( TCHAR ch );其中,参数ch表示设定的口令字符;当ch=0时,编辑框内将显示实际字符。选择文本。当在编辑框中编辑文本时,往往需要选定文本作为整体进行各种编辑操作。用户可以用鼠标或键盘来选择文本。用鼠标来选择文本的操作方法是:在要选择的文本的一端按下鼠标左键并拖动鼠标,到另一端释放鼠标键。用键盘来选择文本的方法是:在按光标方向移动键的同时,按住Shift键。设置编辑框的页面边距。设置编辑框的页面边距可以使文本在编辑框显示更具满意效果,这在多行编辑框中尤为重要,应用程序可通过调用成员函数CEdit:SetMargins来实现,这个函数的原型如下:void SetMar

334、gins( UINT nLeft, UINT nRight );其中,参数nLeft和nRight分别用来指定左、右边距的像素大小。4.3.2 编辑框和旋转按钮控件编辑框和旋转按钮控件剪帖板操作。编辑框通过CEdit类的Copy、Paste和Cut成员函数来实现文本的复制、粘贴、剪切的操作,并还自动支持键盘快捷操作,其对应的快捷健分别为Ctrl+C、Ctrl+V和Ctrl+X。若应用程序调用CEdit:Undo函数时,则还可撤消当前的操作,再调用一次该函数,则恢复刚才的操作。例如下面的代码:if(m_Edit.CanUndo()m_Edit.Undo();获取多行编辑框文本。获取多行编辑框控件

335、的文本可以有两种方法:一种是使用DDX/DDV,当将编辑框控件所关联的变量类型选定为CString(字符串类)后,则不管多行编辑框的文本有多少都可用此变量来保存,从而能简单地解决多行文本的读取。但这种方法不能单独获得多行编辑框中的某一行文本。另一种方法是使用编辑框CEdit类的相关成员函数来获取文本。例如,下面的代码将显示编辑框中第二行的文本内容:charstr100;if(m_Edit.GetLineCount()=2)/判断多行编辑框的文本是否有两行以上intnChars;nChars=m_Edit.LineLength(m_Edit.LineIndex(1);/获取第二行文本的字符个数/

336、0表示第一行,1表示第二行,依次类推。LineIndex用于将文本行转换成/能被LineLength识别的索引m_Edit.GetLine(1,str,nChars);/获取第二行文本strnChars=0;MessageBox(str);代码中,由于调用GetLine获得某行文本内容时,并不能自动在文本后添加文本的结束符0,因此需要首先获得某行文本的字符数,然后设置文本的结束符。4.3.2 编辑框和旋转按钮控件编辑框和旋转按钮控件2. 旋转按钮控件旋转按钮控件“旋转按钮控件”(也称为上下控件)是一对箭头按钮,用户点击它们来增加或减小某个值,比如一个滚动位置或显示在相应控件中的一个数字。如图4

337、.45所示。图4.45 旋转按钮控件及其伙伴窗口 旋转按钮 伙伴窗口4.3.2 编辑框和旋转按钮控件编辑框和旋转按钮控件2. 旋转按钮控件旋转按钮控件(1)旋转按钮控件常用的风格。旋转按钮控件有许多风格,它们都可以通过旋转按钮控件属性对话框进行设置,如图4.46所示,其中各项的含义见表4.9。图4.46旋转按钮控件属性对话框表表4.9 旋转按钮控件的旋转按钮控件的Style属性属性4.3.2 编辑框和旋转按钮控件编辑框和旋转按钮控件2. 旋转按钮控件旋转按钮控件(2)旋转按钮控件的基本操作。MFC的CSpinButtonCtrl类提供了旋转按钮控件的各种操作函数,使用它们可以进行基数、范围、位

338、置设置和获取等基本操作。成员函数SetBase是用来设置其基数的,这个基数值决定了伙伴窗口显示的数字是十进制还是十六进制。如果成功则返回先前的基数值,如果给出的是一个无效的基数则返回一个非零值。函数的原型如下:int SetBase( int nBase ); 其中参数nBase表示控件的新的基数,如10表示十进制,16表示十六进制等。与此函数相对应的成员函数GetBase是获取旋转按钮控件的基数。成员函数SetPos和SetRange分别用来设置旋转按钮控件的当前位置和范围,它们的函数原型如下:int SetPos( int nPos );void SetRange( int nLower,

339、 int nUpper );其中,参数nPos表示控件的新位置,它必须在控件的上限和下限指定的范围之内。nLower和nUpper表示控件的上限和下限。任何一个界限值都不能大于0x7fff或小于-0x7fff。与这两个函数相对应的成员函数GetPos和GetRange分别用来获取旋转按钮控件的当前位置和范围。4.3.2 编辑框和旋转按钮控件编辑框和旋转按钮控件2. 旋转按钮控件旋转按钮控件(3)旋转按钮控件的通知消息。旋转按钮控件的通知消息只有一个:UDN_DELTAPOS,它是在当控件的当前数值将要改变时向其父窗口发送的。3. 示例:用对话框输入学生成绩示例:用对话框输入学生成绩在一个简单的

340、学生成绩结构中,常常有学生的姓名、学号以及三门成绩等内容。为了能够输入这些数据,需要设计一个对话框,如图4.47所示。图4.47学生成绩输入对话框4.3.2 编辑框和旋转按钮控件编辑框和旋转按钮控件例例Ex_Input 用对话框输入学生成绩用对话框输入学生成绩1) 创建并设计对话框创建并设计对话框(1)用MFCAppWizard(exe)创建一个默认的单文档应用程序Ex_Input。(2)添加一个新的对话框资源,通过其属性对话框,将ID标识符改为IDD_INPUT,标题为“学生成绩输入”,将对话框字体改为“宋体,9号”。将OK和Cancel按钮标题改为“确定”和“取消”。(3)显示对话框网格,

341、调整对话框的大小,然后将确定和取消按钮移至对话框的下方。(4)向对话框添加如表4.10所示的控件,调整控件的位置,结果如图4.48所示。表表4.10 学生成绩输入对话框添加的控件学生成绩输入对话框添加的控件图4.48设计的学生成绩输入对话框4.3.2 例例Ex_Input2) 完善完善CInputDlg类代码类代码(1)在MFCClassWizard的MemberVariables页面中,确定Classname中是否已选择了CInputDlg,选中所需的控件ID标识符,双击鼠标或单击AddVariables按钮。依次为表4.11控件增加成员变量。表表4.11 控件变量控件变量4.3.2 例例E

342、x_Input2) 完善完善CInputDlg类代码类代码(2)用MFCClassWizard为CInputDlg添加WM_INITDIALOG消息映射,添加下列代码:BOOLCInputDlg:OnInitDialog()CDialog:OnInitDialog();m_spinScore1.SetRange(0,100);/设置旋转按钮控件范围m_spinScore2.SetRange(0,100);m_spinScore3.SetRange(0,100);returnTRUE;/returnTRUEunlessyousetthefocustoacontrol4.3.2 例例Ex_Inpu

343、t2) 完善完善CInputDlg类代码类代码(3)用MFCClassWizard为CInputDlg增加IDC_SPIN_S1控件的UDN_DELTAPOS消息映射,并添加下列代码:voidCInputDlg:OnDeltaposSpinS1(NMHDR*pNMHDR,LRESULT*pResult)NM_UPDOWN*pNMUpDown=(NM_UPDOWN*)pNMHDR;UpdateData(TRUE);/将控件的内容保存到变量中m_fScore1+=(float)pNMUpDown-iDelta*0.5f;if(m_fScore1100.0)m_fScore1=100.0f;Upda

344、teData(FALSE);/将变量的内容显示在控件中*pResult=0;代码中,NM_UPDOWN结构用于反映旋转控件的当前位置(由成员iPos指定)和增量大小(由成员iDelta指定)。4.3.2 例例Ex_Input 3) 调用对话框调用对话框(1)打开Ex_Input单文档应用程序的菜单资源,添加顶层菜单项“测试(&T)”,在其下添加一个菜单项“学生成绩输入(&I)”,ID为ID_TEST_INPUT。(2)用MFCClassWizard为CMainFrame类添加菜单项ID_TEST_INPUT的COMMAND消息映射,取默认的映射函数名,并添加下列代码:voidCMainFram

345、e:OnTestInput()CInputDlgdlg;if(IDOK=dlg.DoModal()/获取对话框数据CStringstr;str.Format(%s,%s,%4.1f,%4.1f,%4.1f,dlg.m_strName,dlg.m_strNO,dlg.m_fScore1,dlg.m_fScore2,dlg.m_fScore3);AfxMessageBox(str);代码中,if语句是判断用户是否单击对话框的确定按钮。Format是CString类的一个经常使用的成员函数,它通过格式操作使任意类型的数据转换成一个字符串。该函数的第一个参数是带格式的字符串,其中的“%s”就是一个格式

346、符,每一个格式符依次对应于该函数的后面参数表中的参数项。4.3.2 例例Ex_Input3) 调用对话框调用对话框(3)在文件MainFrm.cpp的前面添加CInputDlg类的头文件包含:#includeMainFrm.h#includeInputDlg.h(4)编译并运行,在应用程序的菜单上,选择“测试”“学生成绩输入”菜单项,将弹出如前图4.47所示的对话框。单击成绩1的旋转按钮控件将以0.5增量来改变它的伙伴窗口的数值。而成绩2和成绩3的旋转按钮控件由于设置了Setbuddyinteger属性,因此它按默认增量1自动改变伙伴窗口的数值。4.3.3 列表框列表框1. 列表框的风格和消息

347、列表框的风格和消息按性质来分,列表框有单选、多选、扩展多选以及非选四种类型,如图4.50所示。图4.50不同类型的列表框1. 列表框的风格和消息列表框的风格和消息列表框还有一系列其它风格,用来定义列表框的外观及操作方式,这些风格可在如图4.51所示的列表框属性对话框中设置。表4.12列出Style各项的含义。图4.51列表框的属性对话框1. 列表框的风格和消息列表框的风格和消息表表4.12 列表框的列表框的Style属性属性1. 列表框的风格和消息列表框的风格和消息当列表框中发生了某个动作,如用户双击选择了列表框中某一项时,列表框就会向其父窗口发送一条通知消息。常用的通知消息如表4.13所示。

348、表表4.13 列表框的通知消息列表框的通知消息4.3.3 列表框列表框2. 列表框的基本操作列表框的基本操作需要注意的是:列表框的项除了用字符串来标识外,还常常通过索引来确定。索引表明项目在列表框中排列的位置,它是以0为基数的,即列表框中第一项的索引是0,第二项的索引是1,依次类推。(1)添加列表项。列表框创建时是一个空的列表,需要用户添加或插入一些列表项。CListBox类成员函数AddString和InsertString分别用来添加列表项,其函数原型如下:int AddString( LPCTSTR lpszItem );int InsertString( int nIndex, LPC

349、TSTR lpszItem );其中,列表项的字符串文本由参数lpszItem来指定。虽然两个函数成功调用时都将返回列表项在列表框的索引,错误时返回LB_ERR,空间不够时,返回LB_ERRSPACE。但InsertString函数不会对列表项进行排序,不管列表框控件是否具有sort属性,只是将列表项插在指定索引的列表项之前,若nIndex等于-1,则列表项添加在列表框末尾。而AddString函数当列表框控件具有sort属性时会自动将添加的列表项进行排序。函数原型中,LPCTSTR类型用来表示一个常值字符指针,这里可以将其理解成是一个常值字符串类型。2. 列表框的基本操作列表框的基本操作(2

350、)删除列表项。CListBox类成员函数DeleteString和ResetContent分别用来删除指定的列表项和清除列表框所有项目。它们的函数原型如下:int DeleteString( UINT nIndex ); /nIndex指定要删除的列表项的索引void ResetContent( );(3)查找列表项。为了保证列表项不会重复地添加在列表框中,有时还需要对列表项进行查找。CListBox类成员函数FindString和FindStringExact分别用来在列表框中查找所匹配的列表项。其中,FindStringExact的查找精度最高。int FindString( int nS

351、tartAfter, LPCTSTR lpszItem ) const;int FindStringExact( int nIndexStart, LPCTSTR lpszFind ) const;其中,lpszFind和lpszItem指定要查找的列表项文本,nStartAfter和nIndexStart指定查找的开始位置,若为-1,则从头至尾查找。查到后,这两个函数都将返回所匹配列表项的索引,否则返回LB_ERR。2. 列表框的基本操作列表框的基本操作(4)列表框的单项选择。当选中列表框中某个列表项,用户可以使用CListBox:GetCurSel来获取这个结果,与该函数相对应的CList

352、Box:SetCurSel函数是用来设定某个列表项呈选中状态(高亮显示)。int GetCurSel( ) const;/返回当前选择项的索引int SetCurSel( int nSelect );其中,nSelect指定要设置的列表项索引,错误时这两个函数都将返回LB_ERR。若要获取某个列表项的字符串,可使用下列函数:int GetText( int nIndex, LPTSTR lpszBuffer ) const;void GetText( int nIndex, CString& rString ) const;其中,nIndex指定列表项索引,lpszBuffer和rString

353、是用来存放列表项文本。2. 列表框的基本操作列表框的基本操作(5)列表框的多项选择。当在列表框的Style属性对话框中选中多选(Multiple)或扩展多选(Extended)类型后,就可以在列表框中进行多项选择。要想获得选中的多个选项,需要用MFCClassWizard映射列表框控件的LBN_SELCHANGE消息,并添加类似下面的一些代码:voidCListBoxDlg:OnSelchangeList1()intnCount=m_list.GetSelCount();/获取用户选中的项数if(nCount=LB_ERR)return;int*buffer=newintnCount;/开辟缓

354、冲区m_list.GetSelItems(nCount,buffer);/将各个选项的索引号内容存放在缓冲区中CStringallStr=NULL,str;for(inti=0;iEnableWindow(FALSE);/使删除按钮灰显returnTRUE;/returnTRUEunlessyousetthefocustoacontrol例例Ex_City(5)打开MFCClassWizard,切换到MesssageMaps页面,为按钮IDC_BUTTON_ADD添加BN_CLICKED的消息映射,并增加下列代码:voidCCityDlg:OnButtonAdd()if(!IsValidate

355、()return;intnIndex=m_ListBox.FindStringExact(-1,m_strCity);if(nIndex!=LB_ERR) MessageBox(该城市已添加!);return;nIndex=m_ListBox.AddString(m_strCity);m_ListBox.SetItemData(nIndex,m_dwZipCode);(6)用MFCClassWizard为按钮IDC_BUTTON_DEL添加BN_CLICKED的消息映射,并增加下列代码:voidCCityDlg:OnButtonDel()intnIndex=m_ListBox.GetCurSe

356、l();if(nIndex!=LB_ERR)m_ListBox.DeleteString(nIndex);elseGetDlgItem(IDC_BUTTON_DEL)-EnableWindow(FALSE);例例Ex_City(7)用MFCClassWizard为列表框IDC_LIST1添加LBN_SELCHANGE(当前选择项发生改变)的消息映射,并增加下列代码。这样,当单击列表框的城市名时,将会在编辑框中显示出城市名和邮政编码。voidCCityDlg:OnSelchangeList1()intnIndex=m_ListBox.GetCurSel();if(nIndex!=LB_ERR)m

357、_ListBox.GetText(nIndex,m_strCity);m_dwZipCode=m_ListBox.GetItemData(nIndex);UpdateData(FALSE);/使用当前列表项所关联的内容显示在控件上GetDlgItem(IDC_BUTTON_DEL)-EnableWindow(TRUE);例例Ex_City3.调用对话框调用对话框(1)打开Ex_City单文档应用程序的菜单资源,添加顶层菜单项“测试(&T)”,在其下添加一个菜单项“城市邮政编码(&C)”,ID为ID_TEST_CITY。(2)用MFCClassWizard为CMainFrame类添加菜单项ID_

358、TEST_CITY的COMMAND消息映射,取默认的映射函数名,并添加下列代码:voidCMainFrame:OnTestCity()CCityDlgdlg;dlg.DoModal();(3)在文件MainFrm.cpp的前面添加CCityDlg类的头文件包含:#includeMainFrm.h#includeCityDlg.h(4)编译运行后,在应用程序的菜单上,选择“测试”“城市邮政编码”菜单项,将弹出如前图4.52所示的对话框。4.3.4 组合框组合框作为用户输入的接口,前面的列表框和编辑框各有其优点。例如,列表框中可列出用户所需的各种可能的选项,这样一来,用户不需要记住这些项,只需进行

359、选择操作即可,但用户却不能输入列表框中列表项之外的内容。虽然编辑框能够允许用户输入内容,但却没有列表框的选择操作。于是很自然地产生这样的想法:把常用的项列在列表框中以供选择,而同时提供编辑框,允许用户输入列表框中所没有的新项。组合框正是这样的一种控件,它结合列表框和编辑框的特点,取二者之长,从而完成较为复杂的输入功能。4.3.4 组合框组合框1. 组合框的风格类型和消息组合框的风格类型和消息按照组合框的主要风格特征,可把组合框分为3类:简单组合框、下拉式组合框、下拉式列表框。简单组合框和下拉式组合框都包含有列表框和编辑框,但是简单组合框中的列表框不需要下拉,是直接显示出来的,而当用户单击下拉式

360、组合框中的下拉按钮时,下拉的列表框才被显示出来。下拉式列表框虽然具有下拉式的列表,却没有文字编辑功能。如图4.54所示。图4.54 组合框的类型4.3.4 组合框组合框组合框还有其他一些风格,这些风格可在如图4.55所示的组合框的属性对话框中设置。其各项含义见表4.16。图4.55组合框的属性对话框表表4.16 组合框的组合框的Style属性属性4.3.4 组合框组合框在组合框的通知消息中,有的是列表框发出的,有的是编辑框发出的,如表4.17所示。表表4.17 组合框的通知消息组合框的通知消息4.3.4 组合框组合框2. 组合框常见操作组合框常见操作组合框的操作大致分为两类,一类是对组合框中的

361、列表框进行操作,另一类是对组合框中的编辑框进行操作。这些操作都可以调用CComboBox成员函数来实现,见表4.18。表表4.18 CComboBox类常用成员函数类常用成员函数4.3.4 组合框组合框3. 示例:城市邮政编码和区号示例:城市邮政编码和区号前面的示例中,只是简单的涉及到城市名和邮政编码的对应关系。实际上,城市名还和区号一一对应,为此本例需要设计这样的对话框,如图4.56所示。单击添加按钮将城市名、邮政编码和区号添加到组合框中,在添加前同样需要进行重复性的判断。选择组合框中的城市名,将在编辑框中显示出邮政编码和区号,单击修改按钮,将以城市名作为组合框的查找关键字,找到后修改其邮政

362、编码和区号内容。图4.56城市邮政编码和区号例例Ex_CityCode 创建并使用城市邮政编码和区号创建并使用城市邮政编码和区号对话框对话框1. 添加并设计对话框添加并设计对话框(1)用MFCAppWizard(exe)创建一个默认的单文档应用程序Ex_CityCode。(2)向应用程序中添加一个对话框资源IDD_CITYZONE,标题定为“城市邮政编码和区号”,字体设为“宋体,9号”,创建此对话框类为CCityZoneDlg。(3)删除原来的Cancel按钮,将OK按钮标题改为“退出”。(4)打开对话框网格,参看图4.56的控件布局,为对话框添加如表4.19所示的一些控件。表表4.19 城市

363、邮政编码和区号对话框添加的控件城市邮政编码和区号对话框添加的控件例例Ex_CityCode2. 完善完善CCityZoneDlg类代码类代码(1)打开MFCClassWizard的MemberVariables页面,看看Classname是否是CCityZoneDlg,然后选中所需的控件ID标识符,双击鼠标或单击AddVariables按钮。依次为下列控件增加成员变量。如表4.20所示。(2)将项目工作区切换到ClassView页面,右击CCityZoneDlg类名,从弹出的快捷菜单中选择“AddMemberFunction”,弹出AddMemberFunction对话框,在FunctionT

364、ype(函数类型)框中输入BOOL,在FunctionDeclaration(函数声明)框中输入IsValidate,单击OK按钮。表表4.20 控件变量控件变量例例Ex_CityCode2. 完善完善CCityZoneDlg类代码类代码(3)在CCityZoneDlg:IsValidate函数输入下列代码:BOOLCCityZoneDlg:IsValidate()UpdateData(); m_strCity.TrimLeft();if(m_strCity.IsEmpty()MessageBox(城市名输入无效!); returnFALSE;m_strZip.TrimLeft();if(m_

365、strZip.IsEmpty()MessageBox(邮政编码输入无效!);returnFALSE;m_strZone.TrimLeft();if(m_strZone.IsEmpty()MessageBox(区号输入无效!);returnFALSE;returnTRUE;例例Ex_CityCode2. 完善完善CCityZoneDlg类代码类代码(4)打开MFCClassWizard,切换到MesssageMaps页面,为按钮IDC_BUTTON_ADD添加BN_CLICKED的消息映射,并增加下列代码:voidCCityZoneDlg:OnButtonAdd()if(!IsValidate(

366、)return;intnIndex=m_ComboBox.FindStringExact(-1,m_strCity);if(nIndex!=CB_ERR)MessageBox(该城市已添加!);return;CStringstrData;strData.Format(“%s,%s”,m_strZip,m_strZone);/将邮政编码和区号合并为 一个字符串m_ComboBox.SetItemDataPtr(nIndex,newCString(strData);例例Ex_CityCode2. 完善完善CCityZoneDlg类代码类代码(5)用MFCClassWizard为按钮IDC_BUTT

367、ON_CHANGE添加BN_CLICKED的消息映射,并增加下列代码:voidCCityZoneDlg:OnButtonChange()if(!IsValidate()return;intnIndex=m_ComboBox.FindStringExact(-1,m_strCity);if(nIndex!=CB_ERR)delete(CString*)m_ComboBox.GetItemDataPtr(nIndex);CStringstrData;strData.Format(%s,%s,m_strZip,m_strZone);m_ComboBox.SetItemDataPtr(nIndex,n

368、ewCString(strData);例例Ex_CityCode2. 完善完善CCityZoneDlg类代码类代码(7)用MFCClassWizard为组合框IDC_COMBO1添加CBN_SELCHANGE(当前选择项发生改变)的消息映射,并增加下列代码:voidCCityZoneDlg:OnSelchangeCombo1()intnIndex=m_ComboBox.GetCurSel();if(nIndex!=CB_ERR)m_ComboBox.GetLBText(nIndex,m_strCity);CStringstrData=*(CString*)m_ComboBox.GetItemD

369、ataPtr(nIndex);/分解字符串intn=strData.Find(,);m_strZip=strData.Left(n);/前面的n个字符m_strZone=strData.Mid(n+1);/从中间第n+1字符到未尾的字符串UpdateData(FALSE);例例Ex_CityCode2. 完善完善CCityZoneDlg类代码类代码(7)用MFCClassWizard为对话框添加WM_DESTROY的消息映射,并增加下列代码:voidCCityZoneDlg:OnDestroy()/此消息是当对话框关闭时发送的for(intnIndex=m_ComboBox.GetCount(

370、)-1;nIndex=0;nIndex-)/删除所有与列表项相关联的CString数据,并释放内存delete(CString*)m_ComboBox.GetItemDataPtr(nIndex);CDialog:OnDestroy();例例Ex_CityCode3. 调用对话框调用对话框(1)打开Ex_CityCode单文档应用程序的菜单资源,添加顶层菜单项“测试(&T)”,在其下添加一个菜单项“城市邮政编码和区号(&Z)”,ID为ID_TEST_CITYZONE。(2)用MFCClassWizard为CMainFrame类添加菜单项ID_TEST_CITYZONE的COMMAND消息映射,

371、取默认的映射函数名,并添加下列代码:voidCMainFrame:OnTestCityzone()CCityZoneDlgdlg;dlg.DoModal();(3)在文件MainFrm.cpp的前面添加CCityZoneDlg类的头文件包含:#includeMainFrm.h#includeCityZoneDlg.h(4)编译运行并测试。4.3.5 进展条、滚动条和滑动条进展条、滚动条和滑动条1. 进展条进展条进展条是一个如图4.58所示的控件。除了能表示一个过程进展情况外,使用进展条还可表明温度、水平面或类似的测量值。(1)进展条的风格。打开进展条的属性对话框,如图4.59所示,可以看到它的

372、风格属性并不是很多。其中,边框(Border)用来指定进展条是否有边框,垂直(Vertical)用来指定进展是水平还是垂直的,若选中,则为垂直的。平滑(Smooth)表示平滑地填充进展条,若不选中则表示将用块来填充,就像图4.58那样。图4.58进展条图4.59进展条Style属性对话框4.3.5 进展条、滚动条和滑动条进展条、滚动条和滑动条1. 进展条进展条(2)进展条的基本操作。进展条的基本操作有:设置其范围、当前位置、设置增量等。这些操作都是通过CProgressCtrl类的相关成员函数来实现的。int SetPos( int nPos );int GetPos();这两个函数分别用来设

373、置和获取进展条的当前位置。需要说明的是,这个当前位置是指在SetRange中的上限和下限范围之间的位置。void SetRange( short nLower, short nUpper );void SetRange32(int nLower, int nUpper );void GetRange( int & nLower, int& nUpper );它们分别用来设置和获取进展条范围的上限和下限值。一旦设置后,还会重画此进展条来反映新的范围。成员函数SetRange32为进展条设置32位的范围。参数nLower和nUpper分别表示范围的下限(默认值为0)和上限(默认值为100)。int

374、 SetStep( int nStep );该函数用来设置进展条的步长并返回原来的步长,默认步长为10。int StepIt();该函数将当前位置向前移动一个步长并重画进展条以反映新的位置。函数返回进展条上一次的位置。4.3.5 进展条、滚动条和滑动条进展条、滚动条和滑动条2. 滚动条滚动条滚动条是一个独立的窗口,虽然它具有直接的输入焦点,但却不能自动地滚动窗口内容,因此,它的使用受到一定的限制。根据滚动条的走向,可分为垂直滚动条和水平滚动条两种类型。这两种类型滚动条的组成部分都是一样的,两端都有两个箭头按钮,中间有一个可沿滚动条方向移动的滚动块。如图4.60所示。图4.60 滚动条外观滚动箭

375、头按钮 滚动条 滚动块4.3.5 进展条、滚动条和滑动条进展条、滚动条和滑动条2. 滚动条滚动条(1)滚动条的基本操作。滚动条的基本操作一般包括设置和获取滚动条的范围及滚动块的相应位置。由于滚动条控件的默认滚动范围是0到0,因此在使用滚动条之前必须设定其滚动范围。在MFC的CScrollBar类中,函数SetScrollRange是用来设置滚动条的滚动范围,其原型为:SetScrollRange( int nMinPos, int nMaxPos, BOOL bRedraw = TRUE );其中,nMinPos和nMaxPos表示滚动位置的最小值和最大值。bRedraw为重画标志,当为TRU

376、E时,滚动条被重画。在CScrollBar类中,设置滚动块位置操作是由SetScrollPos函数来完成的,其原型如下:int SetScrollPos( int nPos, BOOL bRedraw = TRUE );其中,nPos表示滚动块的新位置,它必须是在滚动范围之内。与SetScrollRange和SetScrollPos相对应的两个函数是分别用来获取滚动条的当前范围以及当前滚动位置:void GetScrollRange( LPINT lpMinPos, LPINT lpMaxPos ) ;int GetScrollPos();其中,LPINT是整型指针类型,lpMinPos和lp

377、MaxPos分别用来返回滚动块最小和最大滚动位置。4.3.5 进展条、滚动条和滑动条进展条、滚动条和滑动条2. 滚动条滚动条(2)WM_HSCROLL或WM_VSCROLL消息。当用户对滚动条进行操作时,滚动条就会向父窗口发送WM_HSCROLL或WM_VSCROLL消息(分别对应于水平滚动条和垂直滚动条)。这些消息是通过MFCClassWizard在其对话框(滚动条的父窗口)中进行映射的,并产生相应的消息映射函数OnHScroll和OnVScroll,这两个函数具有下列原型:afx_msg void OnHScroll( UINT nSBCode, UINT nPos, CScrollBar

378、* pScrollBar );afx_msg void OnVScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar );其中,nPos表示滚动块的当前位置,pScrollBar表示由滚动条控件的指针,nSBCode表示滚动条的通知消息。图4.61表示当鼠标单击滚动条的不同部位时,所产生的不同通知消息。2. 滚动条滚动条 SB_LINELEFT SB_LINERIGHT SB_PAGELEFT SB_PAGERIGHT图4.61 滚动条通知代码与位置的关系SB_LINEUPSB_PAGEUPSB_THUMBTRACK和SB_THUMBPO

379、SITIONSB_PAGEDOWNSB_LINEDOWN2. 滚动条滚动条表4.21列出了各通知消息的含义。表表4.21 滚动条的通知消息滚动条的通知消息4.3.5 进展条、滚动条和滑动条进展条、滚动条和滑动条3. 滑动条滑动条滑动条控件是由滑动块和可选的刻度线组成的。当用户用鼠标或方向键移动滑动块时,该控件发送通知消息来表明这些改变。滑动条是按照应用程序中指定的增量来移动。例如,如果用户指定此滑动条的范围为5,则滑动块只能有6个位置:在滑动条控件最左边的一个位置和另外5个在此范围内每隔一个增量的位置。通常,这些位置都是由相应的刻度线来标识。如图4.62所示。图4.62 带刻度线的滑动条滑动块

380、刻度线4.3.5 进展条、滚动条和滑动条进展条、滚动条和滑动条3. 滑动条滑动条(1)滑动条的风格和消息。滑动条控件有许多风格,它们都可以通过滑动条控件的属性对话框进行设置,如图4.63所示。表4.22列出该属性对话框的各项含义。图4.63滑动条属性对话框表表4.22 滑动条控件的滑动条控件的Style属性属性3. 滑动条滑动条(2)滑动条的基本操作。MFC的CSliderCtrl类提供了滑动条控件的各种操作函数,这其中包括范围、位置设置和获取等。成员函数SetPos和SetRange分别用来设置滑动条的位置和范围,其原型如下:void SetPos( int nPos );void SetR

381、ange( int nMin, int nMax, BOOL bRedraw = FALSE ); 其中,参数nPos表示新的滑动条位置。bMin和nMax表示滑动条的最小和最大位置,bRedraw表示重画标志,为TRUE时,滑动条被重画。与这两个函数相对应的成员函数GetPos和GetRange是分别用来获取滑动条的位置和范围的。3. 滑动条滑动条成员函数SetTic用来设置滑动条控件中的一个刻度线的位置。函数成功调用后返回非零值;否则返回0。函数原型如下:BOOL SetTic( int nTic );其中,参数nTic表示刻度线的位置。成员函数SetTicFreq用来设置显示在滑动条中的

382、刻度线的疏密程度。其函数原型如下:void SetTicFreq( int nFreq );其中,参数nFreq表示刻度线的疏密程度。例如,如果参数被设置为2,则在滑动条的范围中每两个增量显示一个刻度线。要使这个函数有效,必须在属性对话框中选中Autoticks项。成员函数ClearTics用来从滑动条控件中删除当前的刻度线。其函数原型如下:void ClearTics( BOOL bRedraw = FALSE );其中,参数bRedraw表示重画标志。若该参数为TRUE,则在选择被清除后重画滑动条。成员函数SetSelection用来设置一个滑动条控件中当前选择的开始和结束位置。其函数原型

383、如下:void SetSelection( int nMin, int nMax );其中,参数nMin、nMax表示滑动条的开始和结束位置。4.3.5 进展条、滚动条和滑动条进展条、滚动条和滑动条4. 示例:调整对话框背景颜色示例:调整对话框背景颜色设置对话框背景颜色有许多方法,这里采用最简单的也是最直接的方法,即通过映射WM_CTLCOLOR(当子窗口将要绘制时发送的消息,以便能使用指定的颜色绘制控件)来达到改变背景颜色的目的。本例通过滚动条和两个滑动条来调整VisualC+所使用的RGB颜色的三个分量:R(红色)、G(绿色)和B(蓝色),如图4.64所示。图4.64 调整对话框背景颜色4

384、. 示例:调整对话框背景颜色示例:调整对话框背景颜色例例Ex_Color 调整对话框背景颜色调整对话框背景颜色1) 添加并设计对话框添加并设计对话框(1)用MFCAppWizard(exe)创建一个默认的单文档应用程序Ex_Color。(2)向应用程序中添加一个对话框资源IDD_COLOR,标题定为“调整对话框背景颜色”,字体设为“宋体,9号”,创建此对话框类为CBkColorDlg。(3)删除原来的Cancel按钮,将OK按钮的标题改为“退出”。(4)打开对话框网格,参看图4.64的控件布局,为对话框添加如表4.23所示的一些控件。表表4.23 对话框添加的控件对话框添加的控件例例Ex_Co

385、lor2) 完善完善CBkColorDlg类代码类代码(1)打开ClassWizard的MemberVariables页面,看看Classname是否是CBkColorDlg,选中所需的控件ID标识符,双击鼠标。依次为下列控件增加成员变量。如表4.24所示。表表4.24 控件变量控件变量例例Ex_Color2) 完善完善CBkColorDlg类代码类代码(2)为CBkColorDlg类添加两个成员变量,一个是int型m_nRedValue,用来设置颜色RGB中的红色分量,另一个是画刷CBrush类对象m_Brush,用来设置对话框背景所需要的画刷。(3)用MFCClassWizard为CBkC

386、olorDlg类添加WM_INITDIALOG消息映射,并添加下列初始化代码:BOOLCBkColorDlg:OnInitDialog()CDialog:OnInitDialog();m_scrollRed.SetScrollRange(0,255);m_sliderBlue.SetRange(0,255);m_sliderGreen.SetRange(0,255);m_nBlue=m_nGreen=m_nRedValue=192;UpdateData(FALSE);m_scrollRed.SetScrollPos(m_nRedValue);returnTRUE;/returnTRUEunle

387、ssyousetthefocustoacontrol例例Ex_Color2) 完善完善CBkColorDlg类代码类代码(4)用MFCClassWizard为CBkColorDlg类添加WM_HSCROLL消息映射,并添加下列代码:voidCBkColorDlg:OnHScroll(UINTnSBCode,UINTnPos,CScrollBar*pScrollBar)intnID=pScrollBar-GetDlgCtrlID();/获取对话框中控件ID值if(nID=IDC_SCROLLBAR_RED)/滚动条产生的水平滚动消息switch(nSBCode)caseSB_LINELEFT:m

388、_nRedValue-;/单击滚动条左边箭头break;caseSB_LINERIGHT:m_nRedValue+;/单击滚动条右边箭头break;caseSB_PAGELEFT:m_nRedValue-=10; break;caseSB_PAGERIGHT:m_nRedValue+=10; break;caseSB_THUMBTRACK: m_nRedValue=nPos;break;if(m_nRedValue255)m_nRedValue=255;m_scrollRed.SetScrollPos(m_nRedValue);Invalidate();/使对话框无效,强迫系统重绘对话框CDi

389、alog:OnHScroll(nSBCode,nPos,pScrollBar);例例Ex_Color2) 完善完善CBkColorDlg类代码类代码(5)用MFCClassWizard为CBkColorDlg类添加WM_CTLCOLOR消息映射,并添加下列代码:HBRUSHCBkColorDlg:OnCtlColor(CDC*pDC,CWnd*pWnd,UINTnCtlColor)UpdateData(TRUE);COLORREFcolor=RGB(m_nRedValue,m_nGreen,m_nBlue);m_Brush.Detach();/使画刷和对象分离m_Brush.CreateSol

390、idBrush(color);/创建颜色画刷pDC-SetBkColor(color);/设置背景颜色return(HBRUSH)m_Brush;/返回画刷句柄,以便系统使此画刷绘制对话框代码中,COLORREF是用来表示RGB颜色的一个32位的数据类型,它是VisualC+中一种专门用来定义颜色的数据类型。(画刷的详细用法以后还会讨论)例例Ex_Color3. 调用对话框调用对话框(1)打开Ex_Color单文档应用程序的菜单资源,添加顶层菜单项“测试(&T)”,在其下添加一个菜单项“调整对话框背景颜色(&O)”,ID为ID_TEST_COLOR。(2)用MFCClassWizard为CMa

391、inFrame类添加菜单项ID_TEST_COLOR的COMMAND消息映射,取默认的映射函数名,并添加下列代码:voidCMainFrame:OnTestColor()CBkColorDlgdlg;dlg.DoModal();(3)在文件MainFrm.cpp的前面添加CBkColorDlg类的头文件包含:#includeMainFrm.h#includeBkColorDlg.h(4)编译运行并测试。4.4 通用对话框和消息对话框通用对话框和消息对话框4.4.1 通用对话框通用对话框Windows提供了一组标准用户界面对话框,它们都有相应的MFC库中的类来支持。用户或许早已熟悉了全部或大部分

392、的这些对话框,因为众多的Windows的应用程序早已使用过它们,其中包括VisualC+。所有这些通用对话框类都是从一个公共的基类CCommonDialog派生而来。表4.25列出了这些通用对话框。表表4.25 MFC的通用对话框的通用对话框4.4.1 通用对话框通用对话框通用文件对话框类CFileDialog的构造函数的原型如下:CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVE

393、RWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL );参数中,当bOpenFileDialog为TRUE时表示文件打开对话框,为FALSE时表示文件保存对话框。lpszDefExt用来指定文件扩展名。若用户在文件名编辑框中没有键入扩展名,则系统在文件名后自动添加lpszDefExt指定的扩展名。lpszFileName用来在文件名编辑框中指定开始出现的文件名,若为NULL时,则不出现。dwFlags用来指定对话框的界面标志,当为OFN_HIDEREADONLY时表示隐藏对话框中的“只读”复选框,当为OFN_OVER

394、-WRITEPROMPT时表示文件保存时,若有指定的文件有重名,则出现提示对话框。pParentWnd用来指定对话框的父窗口指针。lpszFilter参数用来确定出现在文件列表框中的文件类型。它由一对或多对字符串组成,每对字符串中第一个字符串表示过滤器名称,第二个字符串表示文件扩展名,若指定多个扩展名则用“;”分隔,字符串最后用两个“|”结尾。注意:字符串应好写在一行,若一行写不下则用“”连接。4.4.2 消息对话框消息对话框消息对话框是最简单的一类对话框,它只是用来显示信息的。在VisualC+6.0的MFC类库中就提供相应的函数实现这样的功能,使用时,直接在程序中调用它们即可。它们的函数原

395、型如下: int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );int MessageBox( LPCTSTR lpszText, LPCTSTR lpszCaption = NULL, UINT nType = MB_OK );这两个函数都是用来创建和显示消息对话框的,它们和前面示例Ex_HelloMsg使用到的Win32API函数MessageBox是不同的,前者是MFC类库中的函数。AfxMessageBox是全程函数,可以用在任何地方。而MessageBox只能控件、对话框、窗口等一些窗口

396、类中使用。4.4.2 消息对话框消息对话框表表4.26 消息对话框常用图标类型消息对话框常用图标类型表表4.27 消息对话框常用按钮类型消息对话框常用按钮类型4.4.2 消息对话框消息对话框在使用消息对话框时,图标类型和按钮类型的标识可使用“|”来组合,例如下面的代码产生如图4.66所示的结果。intnChoice=MessageBox(你喜欢VisualC+吗?,提问,MB_OKCANCEL|MB_ICONQUESTION);if(nChoice=IDYES)/.图4.66 消息对话框第5章菜单、工具栏和状态栏文档应用程序的框架窗口常常包含菜单、工具栏、状态栏、图标和光标等内容,它们是Win

397、dows应用程序中不可缺少的界面元素,其风格和外观有时直接影响着用户对软件的评价。许多优秀的软件(如MicrosoftOffice)为增加对用户的吸引力,不惜资源将它们做得多姿多彩,甚至达到真三维的效果。本章将从它们最简单的用法开始入手,逐步深入直到对其进行编程控制。5.1 菜单菜单为了使Windows程序更容易操作,菜单的显示都遵循下列一些规则:若单击某菜单项后,将弹出一个对话框,那么在该菜单项文本后有“”。若某项菜单有子菜单,那么在该菜单项文本后有“”。若菜单项需要助记符,则用括号将带下划线的字母括起来。助记符与Alt构成一个组合键,当按住“Alt”键不放,再敲击该字母时,对应的菜单项就会

398、被选中。若某项菜单需要快捷键的支持,则一般将其列在相应菜单项文本之后。所谓“快捷键”是一个组合键,如Ctrl+N,使用时是先按下“Ctrl”健不放,然后再按“N”键。任何时候按下快捷键,相应的菜单命令都会被执行。5.1 菜单菜单图5.1是一个菜单样例,注意它们的规则含义。需要说明的是,在常见的菜单系统中,最上面的一层水平排列的菜单称为“顶层菜单”,每一个顶层菜单项可以是一个简单的菜单命令,也可以是下拉(Popup)菜单,在下拉菜单中的每一个菜单项也可是菜单命令或下拉菜单,这样一级一级下去可以构造出复杂的菜单系统。顶层菜单弹出菜单图5.1菜单样例5.1.1 用编辑器设计菜单用编辑器设计菜单1.

399、编辑菜单编辑菜单在顶层菜单的最后一项,VisualC+为用户留出了一个空位置,用来输入新的顶层菜单项。在菜单的空位置上双击鼠标左键,出现菜单项的属性对话框,在标题框中输入“测试(&T)”,结果如图5.2所示,其中符号&用来将其后面的字符作为该菜单项的助记符,这样当按住“Alt”键不放,再敲击该助记符键时,对应的菜单项就会被选中,或在菜单打开时,直接按相应的助记符键,对应的菜单项也会被选中。子菜单的空位置保存可见按钮顶层菜单的空位置图5.2Ex_SDI菜单资源5.1.1 用编辑器设计菜单用编辑器设计菜单1. 编辑菜单编辑菜单单击“测试”菜单项下方的空位置,在菜单项属性对话框中,输入标题“切换菜单

400、(&D)”,在ID框输入该菜单项的资源标识符:ID_TEST_CHANGE,结果如图5.3所示。图5.3修改菜单项属性关闭菜单项属性对话框,将新添加的菜单项拖放到“查看”和“帮助”菜单项之间,结果如图5.4所示。需要说明的是,菜单项位置改变后,其属性并没改变。图5.4菜单项“测试”拖放后的位置5.1.1 用编辑器设计菜单用编辑器设计菜单2. 菜单命令的消息映射菜单命令的消息映射(1)选择“查看”“建立类向导”菜单命令或按Ctrl+W快捷键,则出现MFCClassWizard对话框,并自动切换到MessageMaps页面。从“Classname”列表中选择CMainFrame,在IDs列表中选择

401、ID_TEST_CHANGE,然后在Messages框中选择COMMAND消息。如图5.5所示。图5.5菜单命令消息的映射2. 菜单命令的消息映射菜单命令的消息映射(2)单击AddFunction按钮或双击COMMAND消息,出现“AddMemberFunction”对话框以输入成员函数的名称。系统默认的函数名为OnTestChange,如图5.6所示。该函数是对菜单项ID_TEST_CHANGE的映射,也就是说,当应用程序运行后,用户选择“测试”“对话框”菜单时,该函数OnTestDlg被调用,执行函数中的代码。(3)单击OK按钮,在ClassWizard的“Memberfunctions”

402、列表中将列出新增加的成员函数。选择此函数,单击EditCode按钮(或直接在函数名双击鼠标),在此成员函数中添加下列代码:voidCMainFrame:OnTestChange()/TODO:AddyourcommandhandlercodehereAfxMessageBox(现在就切换吗?);图5.6添加映射函数2. 菜单命令的消息映射菜单命令的消息映射(4)编译并运行。在应用程序的顶层菜单上,单击“测试”菜单项,然后将鼠标移动到弹出的子菜单项“切换菜单”上,则结果如图5.7所示,此时状态栏上显示该菜单项的提示信息,该信息就是在前图5.3的菜单项属性对话框“提示”框中设置的内容。单击“切换菜

403、单”,则弹出一个消息对话框,显示内容“现在就切换吗?”。图5.7Ex_SDI运行后的菜单5.1.2 使用键盘快捷键使用键盘快捷键(1)展开项目工作区窗口中Accelerator的资源项,双击IDR_MAINFRAME,出现如图5.8的加速键资源列表。图5.8Ex_SDI的加速键资源 下端的空行5.1.2 使用键盘快捷键使用键盘快捷键(2)建立一个新的加速键时,只要双击加速键列表的最下端的空行,弹出如图5.9所示的“AccelProperties”(加速键属性)对话框,其中可设置的属性如表5.2所示图5.9加速键属性对话框表表5.2 加速键加速键General属性对话框的各项含义属性对话框的各项

404、含义5.1.2 使用键盘快捷键使用键盘快捷键(3)在加速键属性对话框中,先选择在Ex_SDI应用程序菜单资源添加的“切换菜单”菜单项ID_TEST_CHANGE作为要联用的加速键的ID标识符,然后单击下一键按钮,并按下Ctrl+1作为此加速键的键值。需要说明的是,为了使其他用户能查看并使用该加速键,还需在相应的菜单项文本后面添加加速键内容。例如,可将ID_TEST_CHANGE菜单项的标题改成“切换菜单(&C)tCtrl+1”,其中“t”是将后面的“Ctrl+1”定位到一个水平制表位。(4)编译运行并测试。当程序运行后,按Ctrl+1键将执行相应的菜单命令。5.1.3 菜单的编程控制菜单的编程

405、控制1.创建菜单创建菜单CMenu类的CreateMenu和CreatePopupMenu分别用来创建一个菜单或子菜单框架,它们的原型如下:BOOL CreateMenu( );/ 产生一个空菜单BOOL CreatePopupMenu( );/ 产生一个空的弹出式子菜单2. 装入菜单装入菜单将菜单从资源装入应用程序中,需调用CMenu成员函数LoadMenu,或者用SetMenu对应用程序菜单进行重新设置。BOOL LoadMenu( LPCTSTR lpszResourceName );BOOL LoadMenu( UINT nIDResource );其中,lpszResourceNam

406、e为菜单资源名称,nIDResource为菜单资源ID标识符。5.1.3 菜单的编程控制菜单的编程控制3. 添加菜单项添加菜单项当菜单创建后,用户可以调用AppendMenu或InsertMenu函数来添加一些菜单项。但每次添加时,AppendMenu是将菜单项添加在菜单的末尾处,而InsertMenu在菜单的指定位置处插入菜单项,并将后面的菜单项依次下移。BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0,LPCTSTR lpszNewItem = NULL );BOOL AppendMenu( UINT nFlags, UINT nIDNew

407、Item, const CBitmap* pBmp );BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );其中,nIDNewItem表示新菜单项的资源ID标识符,lpszNewItem表示新菜单项的内容,pBmp用于菜单项的位图指针,nPosition表示新菜单项要插入的菜单项位置。nF

408、lags表示要增加的新菜单项的状态信息,它的值影响其他参数的含义,如表5.3所示。表表5.3 nFlags的值及其对其他参数的影响的值及其对其他参数的影响5.1.3 菜单的编程控制菜单的编程控制4. 删除菜单项删除菜单项调用DeleteMenu函数可将指定的菜单项删除,函数DeleteMenu的原型如下: BOOL DeleteMenu( UINT nPosition, UINT nFlags );其中,参数nPosition表示要删除的菜单项位置,它由nFlags进行说明。若当nFlags为MF_BYCOMMAND时,nPosition表示菜单项的ID标识符,而当nFlags为MF_BYPO

409、SITION时,nPosition表示菜单项的位置(第一个菜单项位置为0)。5. 获取菜单项获取菜单项下面的3个CMenu成员函数分别获得菜单的项数、菜单项的ID标识符以及弹出式子菜单的句柄。 UINT GetMenuItemCount( ) const;该函数用来获得菜单的菜单项数,调用失败后返回-1。 UINT GetMenuItemID( int nPos ) const;该函数用来获得由nPos指定菜单项位置(以0为基数)的菜单项的标识号,若nPos是SEPARATOR(分隔符),则返回-1。 CMenu* GetSubMenu( int nPos ) const;该函数用来获得指定菜

410、单的弹出式菜单的菜单句柄。该弹出式菜单位置由参数nPos指定,开始的位置为0。若菜单不存在,则创建一个临时的菜单指针。5.1.3 菜单的编程控制菜单的编程控制例例Ex_Menu 用程序添加并处理一个菜单项用程序添加并处理一个菜单项(1)创建一个默认的单文档应用程序Ex_Menu。(2)(2)选择“查看”菜单“ResourceSymbols”命令,弹出如图5.10所示的“资源符(3)号”对话框,它能对应用程序中的资源标识符进行管理。由于程序中要添加的菜(4)单项需要一个标识值,最好用一个标识符来代替这个值,这是一个好的习惯。因(5)此这里通过“资源符号”对话框来创建一个新的标识符。图5.10“资

411、源符号”对话框 例例Ex_Menu(3)单击新建按钮,弹出如图5.11所示的“NewSymbol”(新标识符)对话框。在名字(Name)框中输入一个新的标识符ID_NEW_MENUITEM。在值(Value)框中,输入该ID的值,系统要求用户定义的ID值应大于15(0X000F)而小于61440(0XF000)。保留默认的ID值101,单击确定按钮。图5.11新标识符对话框例例Ex_Menu(4)关闭“资源符号”对话框,在CMainFrame:OnCreate函数中添加下列代码,该函数在框架窗口创建时自动调用。intCMainFrame:OnCreate(LPCREATESTRUCTlpCre

412、ateStruct).CMenu*pSysMenu=GetMenu();/获得程序菜单指针CMenu*pSubMenu=pSysMenu-GetSubMenu(1);/获得第二个子菜单的指针CStringStrMenuItem(新的菜单项);pSubMenu-AppendMenu(MF_SEPARATOR);/增加一水平分隔线pSubMenu-AppendMenu(MF_STRING,ID_NEW_MENUITEM,StrMenuItem);/在子菜单中增加一菜单项m_bAutoMenuEnable=FALSE;/关闭系统自动更新菜单状态pSysMenu-EnableMenuItem(ID_N

413、EW_MENUITEM,MF_BYCOMMAND|MF_ENABLED);/激活菜单项DrawMenuBar();/更新菜单return0;例例Ex_Menu(5)此时编译运行后,结果如图5.12所示。但此时选择“新的菜单项”命令不会有反应。图5.12程序添加的菜单项例例Ex_Menu(6)用MFCClassWizard在CMainFrame添加OnCommand消息函数的重载,并添加下列代码:BOOLCMainFrame:OnCommand(WPARAMwParam,LPARAMlParam)/wParam的低字节表示菜单、控件、加速键的命令IDif(LOWORD(wParam)=ID_NE

414、W_MENUITEM)MessageBox(你选中了新的菜单项);returnCFrameWnd:OnCommand(wParam,lParam);(7)编译运行并测试。这时当选择菜单“编辑”“新的菜单项”命令后,就会弹一个对话框,显示“你选中了新的菜单项”消息。5.1.4 使用快捷菜单使用快捷菜单快捷菜单是一种浮动的弹出式菜单,它是一种新的用户界面设计风格。当用户按下鼠标右键时,就会相应地弹出一个浮动菜单,其中提供了几个与当前选择内容相关的选项。用资源编辑器和MFC库的CMenu:TrackPopupMenu函数可以很容易地创建这样的菜单,CMenu:TrackPopupMenu函数原型如下

415、:BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );该函数用来显示一个浮动的弹出式菜单,其位置由各参数决定。其中,nFlags表示菜单在屏幕显示的位置以及鼠标按钮标志,如表5.4所示。表表5.4 nFlags的值及其对其他参数的影响的值及其对其他参数的影响5.1.4 使用快捷菜单使用快捷菜单例例Ex_ContextMenu 使用快捷菜单使用快捷菜单(1)创建一个默认的单文档应用程序Ex_ContextMenu。用MFCClassWizard在CEx_ContextMenuVie

416、w类添加WM_CONTEXTMENU消息映射,并在映射函数中添加下列代码:voidCEx_ContextMenuView:OnContextMenu(CWnd*pWnd,CPointpoint)CMainFrame*pFrame=(CMainFrame*)AfxGetApp()-m_pMainWnd;/获得主窗口指针CMenu*pSysMenu=pFrame-GetMenu();/获得程序窗口菜单指针intnCount=pSysMenu-GetMenuItemCount();/获得顶层菜单个数intnSubMenuPos=-1;for(inti=0;iGetMenuString(i,str,M

417、F_BYPOSITION);if(str.Left(4)=文件)nSubMenuPos=i;break;if(nSubMenuPosGetSubMenu(nSubMenuPos)-TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this);例Ex_ContextMenu(3)在Ex_ContextMenuView.cpp文件的前面添加CMainFrame类的文件包含:#includeEx_ContextMenuView.h#includeMainFrm.h(4)运行并测试。当用户在应用程序窗口的客户区中右击鼠标,会弹出如

418、图5.13的快捷菜单。图5.13快捷菜单 客户区5.2 工具栏工具栏5.2.1 使用工具栏编辑器使用工具栏编辑器选择菜单“文件”“打开工作区”,将前面的单文档应用程序Ex_SDI调入或重新创建。在项目工作区窗口中选择ResourceView页面,双击“Toolbar”项中的IDR_MAINFRAME,则工具栏编辑器出现在主界面的右边,如图5.14所示。图5.14工具栏编辑器窗口空按钮图形工具箱颜色工具箱5.2.1 使用工具栏编辑器使用工具栏编辑器下面就工具栏按钮的一般操作进行说明。(1)创建一个新的工具栏按钮。(2)移动一个按钮。(3)删除一个按钮。(4)在工具栏中插入空格。在工具栏中插入空格

419、有以下几种情况:如果按扭前没有任何空格,拖动该按钮向右移动并当覆盖相邻按钮的一半以上时,释放鼠标键,则此按钮前出现空格。如果按钮前有空格而按钮后没有空格,拖动该按钮向左移动并当按钮的左边界接触到前面按钮时,释放鼠标键,则此按钮后将出现空格。如果按钮前后均有空格,拖动该按钮向右移动并当接触到相邻按钮时,则此按钮前的空格保留,按钮后的空格消失。相反,拖动该按钮向左移动并当接触到前一个相邻按钮时,则此按钮前面的空格消失,后面的空格保留。5.2.1 使用工具栏编辑器使用工具栏编辑器工具栏按钮的一般操作进行说明(5)工具栏按钮属性的设置。双击某按钮弹出其属性对话框,如图5.15所示。属性对话框中的各项说

420、明见表5.5。图5.15工具栏按钮属性对话框表表5.5 工具栏按钮属性对话框的各项含义工具栏按钮属性对话框的各项含义5.2.1 使用工具栏编辑器使用工具栏编辑器工具栏按钮的一般操作进行说明(6)工具栏和菜单相结合。工具栏和菜单相结合是指当选择工具按钮或菜单命令时,操作结果是一样的。使它们结合的具体方法是在工具按钮的属性对话框中将按钮的ID标识符设置为相关联的菜单项ID。需要说明的是,对于单独工具按钮命令消息的映射方法跟菜单命令是一样的。5.2.2 多个工具栏的使用多个工具栏的使用例例Ex_SDI 使用多个工具栏使用多个工具栏1) 添加并更改应用程序菜单添加并更改应用程序菜单(1)创建一个默认的

421、单文档应用程序Ex_SDI。(2)按快捷键Ctrl+R,弹出“插入资源”对话框,在资源类型中选定“Menu”,如图5.16。图5.16“插入资源”对话框例例Ex_SDI1) 添加并更改应用程序菜单添加并更改应用程序菜单(3)单击新建按钮,系统就会为应用程序添加一个新的菜单资源,并自动赋给它一个默认的标识符名称(第一次为IDR_MENU1,以后依次为IDR_MENU2、IDR_MENU3、.),同时自动打开这个新的菜单资源,如图5.17所示。 菜单空位置 菜单默认ID图5.17添加菜单资源后的开发环境例例Ex_SDI1) 添加并更改应用程序菜单添加并更改应用程序菜单(4)在Menu资源的ID_M

422、ENU1上右击鼠标,从弹出的快捷菜单中选择“Properties”命令,出现如图5.18所示的菜单属性对话框,在这里可以重新指定菜单资源ID,设置菜单资源的语言和条件,这个条件用来决定菜单资源包含到哪个环境中,例如当指定条件为_DEBUG,则菜单资源只存在于Debug编译环境中。图5.18菜单属性对话框例例Ex_SDI1) 添加并更改应用程序菜单添加并更改应用程序菜单(5)为菜单ID_MENU1添加一个顶层弹出菜单项“测试(&T)”,并在该菜单下添加一个子菜单项“返回(&R)”,ID设为ID_TEST_RETURN,如图5.19所示。需要再次强调的是,符号&用来指定后面的字符是一个助记符。(6

423、)打开Ex_SDI程序菜单资源IDR_MAINFRAME,在“查看”菜单的最后添加一个子菜单项“显示测试菜单(&M)”,ID设为ID_VIEW_TEST。图5.19设计新的菜单资源例例Ex_SDI1) 添加并更改应用程序菜单添加并更改应用程序菜单(7)为CMainFrame类添加一个CMenu类型的成员变量m_NewMenu。如下面的代码:classCMainFrame:publicCFrameWnd/Attributespublic:CMenum_NewMenu;(8)按快捷键Ctrl+W打开MFCClassWizard对话框,切换到MessageMaps页面,从“Classname”列表中

424、选择CMainFrame,分别为菜单项ID_VIEW_TEST和ID_TEST_RETURN添加COMMAND消息映射,使用默认的消息映射函数名,并添中下列代码:例例Ex_SDI(8)添中下列代码:voidCMainFrame:OnViewTest()m_NewMenu.Detach();/使菜单对象和菜单句柄分离m_NewMenu.LoadMenu(IDR_MENU1);SetMenu(NULL);/清除应用程序菜单SetMenu(&m_NewMenu);/设置应用程序菜单voidCMainFrame:OnTestReturn()m_NewMenu.Detach();m_NewMenu.Lo

425、adMenu(IDR_MAINFRAME);SetMenu(NULL);SetMenu(&m_NewMenu);代码中,LoadMenu和Detach都是CMenu类成员函数,LoadMenu用来装载菜单资源,而Detach是使菜单对象与菜单句柄分离。在调用LoadMenu后,菜单对象m_NewMenu就拥有一个菜单句柄,当再次调用LoadMenu时,由于菜单对象的句柄已经创建,因而会发生运行时错误,但当菜单对象与菜单句柄分离后,就可以再次创建菜单了。SetMenu是CWnd类的一个成员函数,用来设置应用程序的菜单。(9)第一次编译运行并测试。例例Ex_SDI2. 添加并设计工具栏按钮添加并设

426、计工具栏按钮(1)在项目工作区的ResourceView页面中,展开Toolbar(工具栏)资源,双击双击“Toolbar”项中的IDR_MAINFRAME,显示工具栏编辑器。(2)利用工具栏编辑器设计两个工具按钮,其位置和内容如图5.20所示。(3)双击刚才设计的第一个工具按钮,弹出该工具按钮的属性对话框,将该工具按钮的ID号设为ID_TEST_RETURN,在提示框内键入“返回应用程序主菜单n返回主菜单”。图5.20设计的两个工具栏按钮例例Ex_SDI2. 添加并设计工具栏按钮添加并设计工具栏按钮(4)双击刚才设计的第二个工具按钮,弹出该工具按钮的属性对话框,将该工具按钮的ID号设为ID_

427、VEW_TEST,在提示框内键入“显示测试菜单n显示测试菜单”。(5)第二次编译运行并测试。当程序运行后,将鼠标移至刚才设计的第一个工具按钮处,这时在状态栏上显示出“返回应用程序主菜单”信息,若稍等片刻后,还会弹出提示小窗口,显示出“返回主菜单”字样,如图5.21所示。单击新添加的这两个按钮,会执行相应的菜单命令。图5.21工具按钮提示例例Ex_SDI3. 添加工具栏添加工具栏(1)在项目工作区切的ResourceView页面中,展开Toolbar(工具栏)资源,用鼠标单击IDR_MAINFRAME不松开,然后按下Ctrl键,移动鼠标将IDR_MAINFRAME拖到Toolbar资源名称上,这

428、样就复制了工具栏默认资源IDR_MAINFRAME,复制后的资源标识系统自动设为IDR_MAINFRAME1。(2)右击工具栏资源IDR_MAINFRAME1,从弹出的快捷菜单中选择Properties命令,如图5.22所示,将ID设为IDR_TOOLBAR1。(3)双击IDR_TOOLBAR1,打开工具栏资源,按图5.23删除不要的工具按钮。图5.22工具栏属性对话框图5.23删除不要的工具按钮(4)在CMainFrame类中添加一个成员变量m_wndTestBar,变量类型为CToolBar。CToolBar类封装了工具栏的操作。例例Ex_SDI3. 添加工具栏添加工具栏(5)在CMain

429、Frame:OnCreate函数中添加下面的工具栏创建代码:intCMainFrame:OnCreate(LPCREATESTRUCTlpCreateStruct)if(CFrameWnd:OnCreate(lpCreateStruct)=-1)return-1;intnRes=m_wndTestBar.CreateEx(this,TBSTYLE_FLAT,WS_CHILD|WS_VISIBLE|CBRS_TOP|CBRS_GRIPPER|CBRS_TOOLTIPS|CBRS_FLYBY|CBRS_SIZE_DYNAMIC,CRect(0,0,0,0),AFX_IDW_TOOLBAR+10);

430、if(!nRes|!m_wndTestBar.LoadToolBar(IDR_TOOLBAR1)TRACE0(Failedtocreatetoolbarn);return-1;/failtocreatem_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);m_wndTestBar.EnableDocking(CBRS_ALIGN_ANY);EnableDocking(CBRS_ALIGN_ANY);DockControlBar(&m_wndToolBar);DockControlBar(&m_wndTestBar);return0;例例Ex_SDI4. 完善程序

431、代码完善程序代码(1)事实上这不是本例要的结果。还需调用CFrameWnd类的成员函数ShowControlBar来使程序一开始运行时隐藏工具栏IDR_TOOLBAR1。(2)在CMainFrame:OnCreate函数中添加下列代码:intCMainFrame:OnCreate(LPCREATESTRUCTlpCreateStruct)ShowControlBar(&m_wndTestBar,FALSE,FALSE);/关闭测试工具栏return0;代码中,ShowControlBa函数有三个参数,第一个参数用来指定要操作的工具栏或状态栏指针,第二个参数是一个布尔型,当为TRUE时表示显示,

432、否则表示隐藏,第三个参数用来表示是否延迟显示或隐藏,当为FALSE时表示立即显示或隐藏。例例Ex_SDI4. 完善程序代码完善程序代码(3)在CMainFrame:OnViewTest和CMainFrame:OnTestReturn函数中添加下列代码:voidCMainFrame:OnViewTest()ShowControlBar(&m_wndTestBar,TRUE,FALSE);/显示测试工具栏ShowControlBar(&m_wndToolBar,FALSE,FALSE);/关闭主工具栏voidCMainFrame:OnTestReturn()ShowControlBar(&m_wn

433、dTestBar,FALSE,FALSE);/关闭测试工具栏ShowControlBar(&m_wndToolBar,TRUE,FALSE);/显示主工具栏例例Ex_SDI4. 完善程序代码完善程序代码(4)编译运行并测试,结果如图5.25所示,左边是一开始运行的结果,右边是单击工具按钮运行的结果。图5.25Ex_SDI最后运行结果5.3 状态栏状态栏状态栏是一条水平长条,位于应用程序的主窗口的底部。它可以分割成几个窗格,用来显示多组信息。状态栏的定义状态栏的定义用MFCAppWizard创建的单文档或多文档应用程序框架中,有一个静态的indicators数组,它是在MainFrm.cpp文件

434、中定义的,被MFC用作状态栏的定义。图5.26列出了indicators数组元素与标准状态栏窗格的关系。图5.26indicators数组的定义5.3.2 状态栏的常用操作状态栏的常用操作VisualC+6.0中可以方便地对状态栏进行操作,如增减窗格、在状态栏中显示文本、改变状态栏的风格和大小等,并且MFC的CStatusBar类封装了状态栏的大部分操作。1. 增加和减少窗格增加和减少窗格状态栏中的窗格可以分为信息行窗格和指示器窗格两类。若在状态栏中增加一个信息行窗格,则只需在indicators数组中的适当位置中增加一个ID_SEPARATOR标识即可;若在状态栏中增加一个用户指示器窗格,则

435、在indicators数组中的适当位置增加一个在字符串表中定义过的资源ID,其字符串的长度表示用户指示器窗格的大小。若状态栏减少一个窗格,其操作与增加相类似,只需减少indicators数组元素即可。5.3.2 状态栏的常用操作状态栏的常用操作2. 在状态栏上显示文本在状态栏上显示文本有3种办法可以在状态栏窗格显示文本信息:(1)调用CWnd:SetWindowText更新信息行窗格(或窗格0)中的文本。由于状态栏也是一种窗口,故在使用时可直接调用。若状态栏变量为m_wndStatusBar,则m_wndStatusBar.SetWindowText(“消息”)语句将在信息行窗格(或窗格0)内

436、显示“消息”字样。(2)手动处理状态栏的ON_UPDATE_COMMAND_UI更新消息,并在处理函数中调用CCmdUI:SetText函数。(3)调用CStatusBar:SetPaneText函数更新任何窗格(包括信息行窗格)中的文本。此函数原型描述如下:BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE );其中,lpszNewText表示要显示的字符串。nIndex是表示设置的窗格索引(第一个窗格的索引为0)。若bUpdate为TRUE,则系统自动更新显示的结果。值得注意的是,在使用第2种方法时,

437、应按一定的步骤进行。例如下面的示例过程是在状态栏的最右边两个窗格中显示出当前鼠标在窗口客户区的位置。5.3.2 状态栏的常用操作状态栏的常用操作例例Ex_SDIMouse 将鼠标在窗口客户区的位置显示在状态栏上将鼠标在窗口客户区的位置显示在状态栏上(1)创建一个默认的单文档应用程序Ex_SDIMouse。(2)将项目工作区切换到ClassView页面,展开CMainFrame所有项,双击构造函数CMainFrame,在文档窗口中出现该函数的定义,在它的前面就是状态栏数组的定义。(3)将状态栏indicators数组的定义改为下列代码:staticUINTindicators=ID_SEPARA

438、TOR,ID_SEPARATOR,;例例Ex_SDIMouse(4)由于鼠标移动消息WM_MOUSEMOVE在CMainFrame类映射后不起作用,因此只能映射到CEx_SDIMouseView类中。但是,这样一来,就需要更多的代码,因为状态栏对象m_wndStatusBar是在CMainFrame类定义的成员变量,因而需要在CEx_SDIMouseView类中添加访问CMainFrame类的代码。CEx_SDIMouseView:OnMouseMove函数代码如下:voidCEx_SDIMouseView:OnMouseMove(UINTnFlags,CPointpoint)CStrings

439、tr;CMainFrame*pFrame=(CMainFrame*)AfxGetApp()-m_pMainWnd;/获得主窗口指针CStatusBar*pStatus=&pFrame-m_wndStatusBar;/获得主窗口中的状态栏指针if(pStatus)str.Format(X=%d,Y=%d,point.x,point.y);/格式化文本pStatus-SetPaneText(1,str);/更新第二个窗格的文本CView:OnMouseMove(nFlags,point);例例Ex_SDIMouse(5)将MainFrm.h文件中的受保护变量m_wndStatusBar变成公共变量

440、。(6)在Ex_SDIMouseView.cpp文件的开始处增加下列语句:#includeEx_SDIMouseView.h#includeMainFrm.h(7)编译并运行,结果如图5.27所示。图5.27鼠标的位置显示在状态栏上5.3.2 状态栏的常用操作状态栏的常用操作3. 改变状态栏的风格改变状态栏的风格在MFC的CStatusBar类中,有两个成员函数可以改变状态栏风格,它们是:void SetPaneInfo( int nIndex, UINT nID, UINT nStyle, int cxWidth );void SetPaneStyle( int nIndex, UINT n

441、Style ); 其中,参数nIndex表示要设置的状态栏窗格的索引,nID用来为状态栏窗格指定新的ID,cxWidth表示窗格的像素宽度,nStyle表示窗格的风格类型,用来指定窗格的外观,例如SBPS_POPOUT表示窗格是凸起来的,具体见表5.6。表表5.6 状态栏窗格的风格类型状态栏窗格的风格类型5.4 交互对象的动态更新交互对象的动态更新例例Ex_Update 交互对象的动态更新交互对象的动态更新(1)创建一个默认的单文档应用程序Ex_Update。(2)将项目工作区窗口切换到ResourceView页面,展开Toolbar资源节点。(3)选中Toolbar资源IDR_MAINFRA

442、ME,然后按下Ctrl键不放,移动鼠标将IDR_MAIN-FRAME拖到Toolbar资源名称上,这样就复制了工具栏默认资源IDR_MAINFRAME,复制后的资源标识系统自动设为IDR_MAINFRAME1。(4)右击IDR_MAINFRAME1,从弹出的快捷菜单中选择Properties命令,在弹出的属性对话框中将ID改为IDR_NEWBAR。(5)将IDR_NEWBAR工具按钮删除几个以与IDR_MAINFRAME有所区别。(6)打开MainFrm.h文件,在CMainFrame类中声明一个CToolBar类变量m_wndNewBar,一个BOOL型的成员变量m_bNewBar。prot

443、ected:/controlbarembeddedmembersCStatusBarm_wndStatusBar;CToolBarm_wndToolBar;CToolBarm_wndNewBar;BOOLm_bNewBar;例例Ex_Update(7)在CMainFrame:OnCreate中添加下列代码:intCMainFrame:OnCreate(LPCREATESTRUCTlpCreateStruct)if(CFrameWnd:OnCreate(lpCreateStruct)=-1)return-1;if(!m_wndNewBar.CreateEx(this,TBSTYLE_FLAT,W

444、S_CHILD|WS_VISIBLE|CBRS_TOP|CBRS_GRIPPER|CBRS_TOOLTIPS|CBRS_FLYBY|CBRS_SIZE_DYNAMIC,CRect(0,0,0,0),AFX_IDW_TOOLBAR+10)|!m_wndNewBar.LoadToolBar(IDR_NEWBAR)TRACE0(Failedtocreatenewbarn);return-1;/failtocreatem_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);m_wndNewBar.EnableDocking(CBRS_ALIGN_ANY);EnableDo

445、cking(CBRS_ALIGN_ANY);DockControlBar(&m_wndToolBar);DockControlBar(&m_wndNewBar);return0;例例Ex_Update(8)打开菜单资源IDR_MAINFRAME,在“查看”菜单下添加一个菜单项“新工具栏(&N)”,ID标识符设定为ID_VIEW_NEWBAR。(9)用MFCClassWizard在CMainFrame类中添加菜单ID_VIEW_NEWBAR的COMMAND和UPDATE_COMMAND_UI两个消息映射,并在映射函数中添加下列代码:voidCMainFrame:OnViewNewbar()m_b

446、NewBar=!m_bNewBar;ShowControlBar(&m_wndNewBar,m_bNewBar,FALSE);/显示或隐藏工具栏voidCMainFrame:OnUpdateViewNewbar(CCmdUI*pCmdUI)m_bNewBar=m_wndNewBar.IsWindowVisible();pCmdUI-SetCheck(m_bNewBar);例例Ex_Update代码中,OnUpdateViewNewbar是ID_VIEW_NEWBAR的更新命令消息的消息映射函数。该函数只有一个参数,它是指向CCmdUI对象的指针。CCmdUI类仅用于ON_UPDATE_COMM

447、AND_UI消息映射函数,它的成员函数将对菜单项、工具按钮等用户交互对象起作用,具体如表5.7所示。表表5.7 CCmdUI 类的成员函数对用户交互对象的作用类的成员函数对用户交互对象的作用第第6 章框架窗口、文档和视图章框架窗口、文档和视图尽管窗口、文档和视图是MFC的基础,但可能也是最不易理解的部分,因为其概念性比传统编程所需的WindowsAPI函数更强一些6.1 框架窗口框架窗口框架窗口可分为两类:一类是应用程序主窗口,另一类是文档窗口。6.1.1 主窗口和文档窗口主窗口和文档窗口主框架窗口是应用程序直接放置在桌面(DeskTop)上的那个窗口,每个应用程序只能有一个主框架窗口,主框架

448、窗口的标题栏上往往显示应用程序的名称。6.1.1 主窗口和文档窗口主窗口和文档窗口文档窗口对于单文档应用程序来说,它和主框架窗口是一致的,即主框架窗口就是文档窗口;而对于多文档应用程序,文档窗口是主框架窗口的子窗口,如图6.1所示。 文档窗口 主框架窗口6.1.2 窗口风格的设置窗口风格的设置窗口风格既可以通过MFCAppWizard来设置,也可以在主窗口或文档窗口类的PreCreateWindow函数中修改CREATESTRUCT结构,或是可以调用CWnd类的成员函数ModifyStyle和ModifyStyleEx来更改。1. 窗口风格窗口风格窗口风格通常有一般(以WS_为前缀)和扩展(以

449、WS_EX_为前缀)两种形式。这两种形式的窗口风格可在函数CWnd:Create或CWnd:CreateEx参数中指定,其中CreateEx函数可同时支持以上两种风格,而CWnd:Create只能指定窗口的一般风格。需要说明的是,对于控件和对话框这样的窗口来说,它们的窗口风格可直接通过其属性对话框来设置。常见的一般窗口风格如表6.1所示。1. 窗口风格窗口风格表表6.1 窗口的一般风格窗口的一般风格6.1.2 窗口风格的设置窗口风格的设置2. 用用MFC AppWizard设置设置MFCAppWizard有一个高级按扭(在创建单文档或多文档应用程序过程的第四步中),允许用户指定有关SDI和MD

450、I框架窗口的属性,图6.2表示了AdvancedOptions对话框的WindowStyles页面,其中的选项含义见表6.2。但在该对话框中,用户只能设定少数几种窗口风格。图6.2 高级选项对话框表表6.2 高级选项对话框窗口风格的各项含义高级选项对话框窗口风格的各项含义6.1.2 窗口风格的设置窗口风格的设置3. 修改修改CREATESTRUCT结构结构当窗口创建之前,系统自动调用PreCreateWindow虚函数。在用MFCAppWizard创建文档应用程序结构时,MFC已为主窗口或文档窗口类自动重载了该虚函数。用户可以在此函数中通过修改CREATESTRUCT结构来设置窗口的绝大多数风

451、格。例如,在单文档应用程序中,框架窗口默认的风格是WS_OVERLAPPEDWINDOW和FWS_ADDTOTITLE的组合,更改其风格可如下列的代码:BOOLCMainFrame:PreCreateWindow(CREATESTRUCT&cs)/新窗口不带有最大化按钮cs.style&=WS_MAXIMIZEBOX;/将窗口的大小设为1/3屏幕并居中cs.cy=:GetSystemMetrics(SM_CYSCREEN)/3;cs.cx=:GetSystemMetrics(SM_CXSCREEN)/3;cs.y=(cs.cy*3)-cs.cy)/2;cs.x=(cs.cx*3)-cs.cx)

452、/2;returnCFrameWnd:PreCreateWindow(cs);代码中,前面有“:”作用域符号的函数是指全局函数,一般都是一些API函数。“cs.style&=WS_MAXIMIZEBOX;”中的“”是按位取“反”运算符,它将WS_MAXIMIZEBOX的值按位取反后,再和cs.style值按位“与”,其结果是将cs.style值中的WS_MAXIMIZEBOX标志位清零。6.1.2 窗口风格的设置窗口风格的设置4. 使用使用ModifyStyle和和ModifyStyleExCWnd类中的成员函数ModifyStyle和ModifyStyleEx也可用来更改窗口的风格,其中Mo

453、difyStyleEx还可更改窗口的扩展风格。这两个函数具有相同的参数,其含义如下。 BOOL ModifyXXXX( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 );其中,参数dwRemove用来指定需要删除的风格,dwAdd用来指定需要增加的风格,nFlags表示SetWindowPos的标志,0(默认)表示更改风格的同时不调用SetWindowPos函数。由于框架窗口在创建时不能直接设定其扩展风格,因此只能通过调用ModifyStyle函数来进行。例如用MFCClassWizard为一个多文档应用程序Ex_MDI的子文档窗口类CChildFr

454、ame添加OnCreateClient消息处理,并增加下列代码:BOOLCChildFrame:OnCreateClient(LPCREATESTRUCTlpcs,CCreateContext*pContext)ModifyStyle(0,WS_VSCROLL,0);returnCMDIChildWnd:OnCreateClient(lpcs,pContext);这样,当窗口创建客户区时就会调用虚函数OnCreateClient。运行结果如图6.3所示。 添加的滚动条图6.3为文档子窗口添加垂直滚动条6.1.2 窗口风格的设置窗口风格的设置6.1.3 窗口状态的改变窗口状态的改变1. 用用Sh

455、owWindow改变窗口的显示状态改变窗口的显示状态当应用程序运行时,Windows会自动调用应用程序框架内部的WinMain函数,并自动查找该应用程序类的全局变量theApp,然后自动调用用户应用程序类的虚函数InitInstance,该函数会进一步调用相应的函数来完成主窗口的构造和显示工作,如下面的代码(以单文档应用程序项目Ex_SDI为例):BOOLCEx_SDIApp:InitInstance()m_pMainWnd-ShowWindow(SW_SHOW);/显示窗口m_pMainWnd-UpdateWindow();/更新窗口returnTRUE;代码中,m_pMainWnd是主框架

456、窗口指针变量,ShowWindow是CWnd类的成员函数,用来按指定的参数显示窗口,该参数的值如表6.3所示。1. 用用ShowWindow改变窗口的显示状态改变窗口的显示状态表表6.3 ShowWindow函数的参数值函数的参数值6.1.3 窗口状态的改变窗口状态的改变1. 用用ShowWindow改变窗口的显示状态改变窗口的显示状态通过指定ShowWindow函数的参数值可以改变改变窗口显示状态。例如下面的代码是将窗口的初始状态设置为“最小化”:BOOLCEx_SDIApp:InitInstance().m_pMainWnd-ShowWindow(SW_SHOWMINIMIZED);m_p

457、MainWnd-UpdateWindow();returnTRUE;6.1.3 窗口状态的改变窗口状态的改变2. 用用SetWindowPos或或MoveWindow改变窗口的大小和位置改变窗口的大小和位置CWnd中的SetWindowPos是一个非常有用的函数;它不仅可以改变窗口的大小、位置,而且还可以改变所有窗口在堆栈排列的次序(Z次序),这个次序是根据它们在屏幕出现的先后来确定的。其中,参数pWndInsertAfter表示窗口对象指针,它可以下列预定义窗口对象的地址:wndBottom将窗口放置在Z次序中的底层wndTop将窗口放置在Z次序中的顶层wndTopMost设置最顶窗口wnd

458、NoTopMost将窗口放置在所有最顶层的后面,若此窗口不是最顶窗口,则此标志无效2. 用用SetWindowPos或或MoveWindow改变窗口的大小和位置改变窗口的大小和位置x和y表示窗口新的左上角坐标,cx和cy分别表示表示窗口新的宽度和高度,nFlags表示窗口新的大小和位置方式,如表6.4所示。表表6.4 常用常用nFlags值及其含义值及其含义2. 用用SetWindowPos或或MoveWindow改变窗口的大小和位置改变窗口的大小和位置void MoveWindow(int x, inty, intnWidth,int nHeight,BOOLbRepaint = TRUE)

459、;void MoveWindow(LPCRECTlpRect,BOOL bRepaint= TRUE);其中,参数x和y表示窗口新的左上角坐标,nWidth和nHeight表示窗口新的宽度和高度,bRepaint用于指定窗口是否重绘,lpRect表示窗口新的大小和位置。/ 使用使用SetWindowPos函数的示例函数的示例m_pMainWnd-SetWindowPos(NULL,100,100,0,0,SWP_NOSIZE|SWP_NOZORDER);/ 使用使用MoveWindow函数的示例函数的示例CRectrcWindow;m_pMainWnd-GetWindowRect(rcWind

460、ow);m_pMainWnd-MoveWindow(100,100,rcWindow.Width(),rcWindow.Height(),TRUE);当然,改变窗口的大小和位置的CWnd成员函数还不止以上两个。例如CenterWindow函数是使窗口居于父窗口中央,就像下面的代码:CenterWindow(CWnd:GetDesktopWindow();/将窗口置于屏幕中央AfxGetMainWnd()-CenterWindow();/将主框架窗口居中6.2 文档模板文档模板用MFCAppWizard创建的单文档(SDI)或多文档(MDI)应用程序均包含应用程序类、文档类、视图类和框架窗口类,

461、这些类是通过文档模板来有机地联系在一起。6.2.1 文档模板类文档模板类文档应用程序框架结构是在程序运行一开始构造的,在单文档应用程序(设项目名为Ex_SDI)的应用程序类InitInstance函数中,可以看到这样的代码:BOOLCEx_SDIApp:InitInstance()CSingleDocTemplate*pDocTemplate;pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,/ 资源资源IDRUNTIME_CLASS(CEx_SDIDoc), / 文档类文档类RUNTIME_CLASS(CMainFrame), / 主框架

462、窗口类主框架窗口类RUNTIME_CLASS(CEx_SDIView);/ 视图类视图类AddDocTemplate(pDocTemplate);returnTRUE;6.2.2 文档模板字串资源文档模板字串资源表表6.5 文档模板字符串的含义文档模板字符串的含义6.2.2 文档模板字串资源文档模板字串资源实际上,文档模板字串资源内容既可直接通过字串资源编辑器进行修改,也可以在文档应用程序创建向导的第四步中,通过“AdvancedOptions”对话框中的“DocumentTemplateStrings”页面来指定,如图6.4所示。图6.4 Advanced Options对话框5036214

463、6.2.3 使用多个文档类型使用多个文档类型例例Ex_MDIDemo 使用多个文档类型使用多个文档类型(1)用MFCAppWizard创建一个默认的多文档应用程序项目Ex_MDIDemo。(2)打开项目工作区窗口中StringTable的资源项,双击该项下的StringTable,打开字符串表资源,如图6.5所示。图6.5Ex_MDIDemo字符串表资源例Ex_MDIDemo(3)双击IDR_MAINFRAME列表项,弹出字符串属性对话框,将其标题修改为“多个文档类型实例”,结果如图6.6所示。(4)双击IDR_EX_MDITYPE列表项,在字符串属性对话框中,将其内容修改为:nPicture

464、nMDIDemo图片n图片文件(*.bmp)n.bmpnExMDIDemo.DocumentnEx_MDIDocument(5)拖动字符串表编辑器右边的滚动块,直到出现最后一个字符串项,双击最后的空行,在字符串属性对话框中将ID设为IDR_OTHERTYPE,标题内容设为:nTxtnMDIDemo文本n文本文件(*.txt,*.cpp,*.h)n.txt;*.cpp;*.hnExMDIDemo.DocumentnEx_MDIDocument结果如图6.7所示。图6.6修改IDR_MAINFRAME字符串标题 图6.7添加新的字符串项例例Ex_MDIDemo(6)按快捷键Ctrl+W,打开MFC

465、ClassWizard,单击AddClass按钮,从弹出的菜单中选择New,出现“NewClass”对话框,在Name框中输入类名COtherDoc,在Baseclass组合框中选择基类CDocument,结果如图6.8所示。图6.8添加新的文档类COtherDoc(7)单击OK按钮,新的文档类COtherDoc就添加到Ex_MDIDemo项目中。类似的,再添加一个新的视图类COtherView,基类为CView。单击确定按钮,关闭MFCClassWizard对话框。例Ex_MDIDemo(8)修改CEx_MDIDemoApp:InitInstance函数代码,如下所示:BOOLCEx_MDI

466、DemoApp:InitInstance()CMultiDocTemplate*pDocTemplate;pDocTemplate=newCMultiDocTemplate(IDR_EX_MDITYPE,RUNTIME_CLASS(CEx_MDIDemoDoc),RUNTIME_CLASS(CChildFrame),RUNTIME_CLASS(CEx_MDIDemoView);AddDocTemplate(pDocTemplate);pDocTemplate=newCMultiDocTemplate(IDR_OTHERTYPE,/指定新的资源RUNTIME_CLASS(COtherDoc),/

467、指定新的文档类RUNTIME_CLASS(CChildFrame),RUNTIME_CLASS(COtherView);/指定新的视图类AddDocTemplate(pDocTemplate);returnTRUE;例Ex_MDIDemo(9)在类CEx_MDIDemoApp源代码文件Ex_MDIDemo.cpp的开始处,添加包含前面创建的两个派生类的头文件:#includeEx_MDIDemoView.h#includeOtherDoc.h#includeOtherView.h(10)编译运行并测试。在程序运行的一开始弹出文档类型的“新建”对话框,如图6.9所示。选择“MDIDemo图片”,

468、单击确定后,出现CEx_MDIDemo主框架窗口界面,同时出现标题为“Picture1”的文档窗口。选择“文件”“新建”菜单,又会出现如图6.9所示的“新建”对话框,选择“MDIDemo文本”,单击确定后,出现标题为“Txt1”的文档窗口。结果如图6.10所示。选择“文件”“打开”菜单,出现如图6.11所示的文件打开对话框。例Ex_MDIDemo图6.10多类型文档窗口显示图6.11文件打开对话框中的文件类型图6.9文档类型新建对话框6.3 文档序列化文档序列化用户处理的数据往往需要存盘作永久备份。将文档类中的数据成员变量的值保存在磁盘文件中,或者将存储的文档文件中的数据读取到相应的成员变量中

469、。这个过程称为序列化(Serialize)。6.3.1 文档序列化过程文档序列化过程在使用MFC程序结构进行文件序列化操作之前,先来看看对文档不同操作后的具体程序运行过程。1. 创建空文档创建空文档应用程序类的InitInstance函数在调用了AddDocTemplate函数之后,会通过CWinApp:ProcessShellCommand间接调用CWinApp的另一个非常有用的成员函数OnFileNew,并依次完成下列工作:(1)构造文档对象,但并不从磁盘中读数据。(2)构造主框架类CMainFrame的对象,并创建该主框架窗口,但不显示。(3)构造视图对象,并创建视图窗口,也不显示。(4

470、)通过内部机制,使文档、主框架和视图“对象”之间“真正”建立联系。注意与AddDocTemplate函数的区别,AddDocTemplate函数建立的是“类”之间的联系。(5)调用文档对象的CDocument:OnNewDocument虚函数,并调用CDocument:DeleteContents虚函数来清除文档对象的内容。(6)调用视图对象的CView:OnInitialUpdate虚函数对视图进行初始化操作。(7)调用框架对象的CFrameWnd:ActiveFrame虚函数,以便显示出带有菜单、工具栏、状态栏以及视图窗口的主框架窗口。6.3.1 文档序列化过程文档序列化过程2. 打开文档

471、打开文档当MFCAppWizard创建应用程序时,它会自动将“文件(File)”菜单中的“打开(Open)”命令(ID号为ID_FILE_OPEN)映射到CWinApp的OnFileOpen成员函数。这一结果可以从应用类(.cpp)的消息入口处得到验证:BEGIN_MESSAGE_MAP(CEx_SDIApp,CWinApp)ON_COMMAND(ID_FILE_NEW,CWinApp:OnFileNew)ON_COMMAND(ID_FILE_OPEN, CWinApp:OnFileOpen)/StandardprintsetupcommandON_COMMAND(ID_FILE_PRINT_

472、SETUP,CWinApp:OnFilePrintSetup)END_MESSAGE_MAP()OnFileOpen函数还会进一步完成下列工作:弹出通用文件“打开”对话框,供用户选择一个文档。(1)文档指定后,调用文档对象的CDocument:OnOpenDocument虚函数。该函数将打开文档,并调用DeleteContents清除文档对象的内容,然后创建一个CArchive对象用于数据的读取,接着又自动调用Serialize函数。(2)调用视图对象的CView:OnInitialUpdate虚函数。(3)除了使用“文件(File)”“打开(Open)”菜单项外,用户也可以选择最近使用过的文

473、件列表来打开相应的文档。在应用程序的运行过程中,系统会记录下4个(默认)最近使用过的文件,并将文件名保存在Windows的注册表中。当每次启动应用程序时,应用程序都会最近使用过的文件名称显示在“文件(File)”菜单中。6.3.1 文档序列化过程文档序列化过程3. 保存文档保存文档当MFCAppWizard创建应用程序时,它会自动将“文件(File)”菜单中的“保存(Save)”命令与文档类CDocument的OnFileSave函数在内部关联起来,但用户在程序框架中看不到相应的代码。OnFileSave函数还会进一步完成下列工作:(1)弹出通用文件“保存”对话框,让用户提供一个文件名。(2)

474、调用文档对象的CDocument:OnSaveDocument虚函数,接着又自动调用Serialize函数,将CArchive对象的内容保存在文档中。6.3.1 文档序列化过程文档序列化过程4. 关闭文档关闭文档当用户试图关闭文档(或退出应用程序)时,应用程序会根据用户对文档的修改与否来进一步完成下列任务:(1)若文档内容已被修改,则弹出一个消息对话框,询问用户是否需要将文档保存。当用户选择“是”,则应用程序执行OnFileSave过程。(2)调用CDocument:OnCloseDocument虚函数,关闭所有与该文档相关联的文档窗口及相应的视图,调用文档类CDocument的DeleteC

475、ontents清除文档数据。6.3.2 文档序列化操作文档序列化操作从上述的单文档序列化过程可以看出:打开和保存文档时,系统都会自动调用Serialize函数。事实上,MFCAppWizard在创建文档应用程序框架时已在文档类中重载了Serialize函数,通过在该函数中添加代码可达到实现数据序列化的目的。例如,在Ex_SDI单文档应用程序的文档类中有这样的默认代码:voidCEx_SDIDoc:Serialize(CArchive&ar)if(ar.IsStoring()/当文档数据需要存盘时/TODO:addstoringcodehereelse/当文档数据需要读取时/TODO:addlo

476、adingcodehere代码中,Serialize函数的参数ar是一个CArchive类引用变量。通过判断ar.IsStoring的结果是“真”还是“假”就可决定向文档写或读数据。6.3.2 文档序列化操作文档序列化操作CArchive(归档)类提供对文件数据进行缓存,它同时还保存一个内部标记,用来标识文档是存入(写盘)还是载入(读盘)。每次只能有一个活动的存档与ar相连。通过CArchive类可以简化文件操作,它提供“”运算符,用于向文件写入简单的数据类型以及从文件中读取它们,表6.6列出了CArchive所支持的的常用数据类型。表表6.6 ar中可以使用中可以使用运算符的数据类型运算符的

477、数据类型6.3.2 文档序列化操作文档序列化操作除了“”运算符外,CArchive类还提供成员函数ReadString和WriteString用来从一个文件对象中读写一行文本,它们的原型如下: Bool ReadString(CString& rString ); LPTSTR ReadString( LPTSTR lpsz, UINT nMax ); void WriteString( LPCTSTR lpsz );其中,lpsz用来指定读或写的文本内容,nMax用来指定可以读出的最大字符个数。需要说明的是,当向一个文件写一行字符串时,字符0和n都不会写到文件中,在使用时要特别注意。下面举一

478、个简单的示例来说明Serialize函数和CArchive类的文档序列化操作方法。6.3.2 文档序列化操作文档序列化操作例例Ex_SDIArchive 一个简单的文档序列化示例一个简单的文档序列化示例(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_SDIArchive。(2)打开StringTable资源,将文档模板字串资源IDR_MAINFRAME内容修改为:文档序列化操作nnn自定义文件(*.my)n.mynExSDIArchive.DocumentnEx_SDIDocument(3)为CEx_SDIArchiveDoc类添加下列成员变量:public:charm_ch

479、Archive100;/读写数据时使用CStringm_strArchive;/读写数据时使用BOOLm_bIsMyDoc;/用于判断文档(4)在CEx_SDIArchiveDoc类构造函数中添加下列代码:CEx_SDIArchiveDoc:CEx_SDIArchiveDoc()m_bIsMyDoc=FALSE;例Ex_SDIArchive(5)在CEx_SDIArchiveDoc:OnNewDocument函数中添加下列代码:BOOLCEx_SDIArchiveDoc:OnNewDocument()if(!CDocument:OnNewDocument()returnFALSE;strcpy

480、(m_chArchive,&这是一个用于测试文档的内容!);m_strArchive=这是一行文本!;m_bIsMyDoc=TRUE;returnTRUE;例Ex_SDIArchive(6)在CEx_SDIArchiveDoc:Serialize函数中添加下列代码:voidCEx_SDIArchiveDoc:Serialize(CArchive&ar)if(ar.IsStoring()if(m_bIsMyDoc)/是自己的文档for(inti=0;isizeof(m_chArchive);i+)arm_chArchive0;/读取文档首字符if(m_chArchive0=&) /是自己的文档f

481、or(inti=1;im_chArchivei;ar.ReadString(m_strArchive);CStringstr;str.Format(%s%s,m_chArchive,m_strArchive);AfxMessageBox(str);m_bIsMyDoc=TRUE;else/不是自己的文档m_bIsMyDoc=FALSE;AfxMessageBox(打开的文档无效!);例Ex_SDIArchive(7)将文档模板字串资源IDR_MAINFRAME内容修改如下:文档序列化操作nnn自定义文件(*.my)n.mynExSDIArchive.DocumentnEx_SDIDocumen

482、t(8)编译运行并测试。程序运行后,选择“文件”“另存为”菜单,指定一个文档名1.my,然后选择“文件”“新建”菜单,再打开该文档,结果就会弹出对话框,显示该文档的内容,如图6.12所示。图6.12显示文档内容6.3.3 使用简单数组集合类使用简单数组集合类简单数组集合类是一个大小动态可变的数组,数组中的元素可用下标运算符“”来访问(从0开始),设置或获取元素数据。若要设置超过数组当前个数的元素的值,可以指定是否使数组自动扩展。当数组不需扩展时,访问数组集合类的速度与访问标准C+中的数组的速度同样快。以下的基本操作对所有的简单数组集合类都适用。1. 简单数组集合类的构造及元素的添加简单数组集合

483、类的构造及元素的添加对简单数组集合类构造的方法都是一样的,均是使用各自的构造函数,它们的原型如下:CByteArray CByteArray( ); CDWordArray CDWordArray( ); CObArray CObArray( ); CPtrArray CPtrArray( ); CStringArray CStringArray( ); CUIntArray CUIntArray( ); CWordArray CWordArray( ); 下面的代码说明了简单数组集合类的两种构造方法:CObArrayarray;/使用默认的内存块大小CObArray*pArray=newCO

484、bArray;/使用堆内存中的默认的内存块大小6.3.3 使用简单数组集合类使用简单数组集合类1. 简单数组集合类的构造及元素的添加简单数组集合类的构造及元素的添加它们的原型如下:void SetSize( int nNewSize, int nGrowBy = -1 );int GetSize( ) const;向简单数组集合类添加一个元素,可使用成员函数Add和Append,原型如下:int Add( CObject* newElement );int Append( const CObArray& src );其中,Add函数是向数组的末尾添加一个新元素,且数组自动增1。如果调用的函数S

485、etSize的参数nGrowBy的值大于1,那么扩展内存将被分配。此函数返回被添加的元素序号,元素序号就是数组下标。参数newElement表示要添加的相应类型的数据元素。而Append函数是向数组的末尾添加由src指定的另一个数组的内容。函数返回加入的第一个元素的序号。6.3.3 使用简单数组集合类使用简单数组集合类2. 访问简单数组集合类的元素访问简单数组集合类的元素在MFC中,一个简单数组集合类元素的访问既可以使用GetAt函数,也可使用“”操作符,例如:/CObArray:operator示例CObArrayarray;CAge*pa;/CAge是一个用户类array.Add(newC

486、Age(21);/添加一个元素array.Add(newCAge(40);/再添加一个元素pa=(CAge*)array0;/获取元素0array0=newCAge(30);/替换元素0;/CObArray:GetAt示例CObArrayarray;array.Add(newCAge(21);/元素0array.Add(newCAge(40);/元素16.3.3 使用简单数组集合类使用简单数组集合类3. 删除简单数组集合类的元素删除简单数组集合类的元素删除简单数组集合类中的元素一般需要进行以下几个步骤:(1)使用函数GetSize和整数下标值访问简单数组集合类中的元素。(2)若对象元素是在堆内

487、存中创建的,则使用delete操作符删除每一个对象元素。(3)调用函数RemoveAll删除简单数组集合类中的所有元素。例如,下面代码是一个CObArray的删除示例:CObArrayarray;CAge*pa1;CAge*pa2;array.Add(pa1=newCAge(21);array.Add(pa2=newCAge(40);ASSERT(array.GetSize()=2);for(inti=0;iTextOut(0,y,str);IMPLEMENT_SERIAL(CStudent,CObject,1)voidCStudent:Serialize(CArchive&ar)if(ar.

488、IsStoring()arstrNamestrIDfScore1fScore2fScore3strNamestrIDfScore1fScore2fScore3fAverage;例Ex_Student3) 添加并处理菜单项添加并处理菜单项(1)在菜单资源的主菜单中增加顶层菜单项“学生记录(&S)”,在该顶层菜单项中增加子菜单“添加(&A)”(ID_STUREC_ADD)。(2)用ClassWizard为CEx_StudentDoc类添加ID_STUREC_ADD的COMMAND消息映射,并在映射函数中添加下列代码:voidCEx_StudentDoc:OnSturecAdd()CAddDlgdl

489、g;if(IDOK=dlg.DoModal()/添加记录CStudent*pStudent=newCStudent(dlg.m_strName,dlg.m_strID,dlg.m_fScore1,dlg.m_fScore2,dlg.m_fScore3);m_stuObArray.Add(pStudent);SetModifiedFlag();/设置文档更改标志UpdateAllViews(NULL);/更新视图例Ex_Student3) 添加并处理菜单项添加并处理菜单项(3)在Ex_StudentDoc.cpp文件的开始处,增加包含CAddDlg的头文件。#includeEx_StudentD

490、oc.h#includeAddDlg.h“4) 完善代码完善代码(1)在Ex_StudentDoc.h文件中,为CEx_StudentDoc类添加下列成员变量和成员函数:public:CObArraym_stuObArray;intGetAllRecNum(void);CStudent*GetStudentAt(intnIndex);例Ex_Student4) 完善代码完善代码(2)在Ex_StudentDoc.cpp文件中,添加函数的实现代码:CStudent*CEx_StudentDoc:GetStudentAt(intnIndex)if(nIndexm_stuObArray.GetUpp

491、erBound()return0;/超界处理return(CStudent*)m_stuObArray.GetAt(nIndex);intCEx_StudentDoc:GetAllRecNum()returnm_stuObArray.GetSize();例Ex_Student4) 完善代码完善代码(3)在CEx_StudentDoc析构函数中添加下列代码:CEx_StudentDoc:CEx_StudentDoc()intnIndex=GetAllRecNum();while(nIndex-)deletem_stuObArray.GetAt(nIndex);m_stuObArray.Remov

492、eAll();(4)在Serialize函数中添加下列代码:voidCEx_StudentDoc:Serialize(CArchive&ar)if(ar.IsStoring()m_stuObArray.Serialize(ar);elsem_stuObArray.Serialize(ar);例Ex_Student4) 完善代码完善代码(5)在CEx_StudentView:OnDraw函数中添加下列代码:voidCEx_StudentView:OnDraw(CDC*pDC)CEx_StudentDoc*pDoc=GetDocument();ASSERT_VALID(pDoc);inty=0;f

493、or(intnIndex=0;nIndexGetAllRecNum();nIndex+)pDoc-GetStudentAt(nIndex)-Display(y,pDC);y+=16;(6)打开文档的字串资源IDR_MAINFRAME,将其内容修改为:Ex_StudentnStudentRecnEx_Stun记录文件(*.rec)n.recnExStudent.DocumentnEx_StuDocument(7)编译运行并测试,结果如前图6.13所示。6.3.5 使用使用CFile类类在MFC中,CFile类是一个文件I/O的基类。它直接支持非缓冲、二进制的磁盘文件的输入输出,也可以使用其派生类

494、处理文本文件(CStdioFile)和内存文件(CMemFile)。CFile类的读写功能类似于C语言中的fread和fwrite,而CStdioFile类的读写功能类似于C语言中的fgets和fputs。使用CFile类可以打开或关闭一个磁盘文件、向一个文件读或写数据等。下面分别说明。1. 文件的打开和关闭文件的打开和关闭在MFC中,使用CFile打开一个文件通常使用下列两个步骤:(1)构造一个不带任何参数的CFile对象;(2)调用成员函数Open并指定文件路径以及文件标志。6.3.5 使用使用CFile类类CFile类的Open函数原型如下:BOOL Open( LPCTSTR lpsz

495、FileName, UINT nOpenFlags, CFileException* pError = NULL );其中,lpszFileName用来指定一个要打开的文件路径,该路径可以是相对的、绝对的或是一个网络文件名(UNC)。nOpenFlags用来指定文件打开的标志,它的值见表6.9。表表6.9 CFile类的文件访问方式类的文件访问方式6.3.5 使用使用CFile类类2. 文件的读写和定位文件的读写和定位CFile类支持文件的读、写和定位操作。它们相关函数的原型如下: UINT Read( void* lpBuf, UINT nCount );此函数将文件中指定大小的数据读入指定

496、的缓冲区,并返回向缓冲区传输的字节数。需要说明的是,这个返回值可能小于nCount,这是因为可能到达了文件的结尾。 void Write( const void* lpBuf, UINT nCount );此函数将缓冲区的数据写到文件中。参数lpBuf用来指定要写到文件中的数据缓冲区的指针,nCount表示从数据缓冲区传送的字节数。对于文本文件,每行的换行符也被计算在内。 LONG Seek( LONG lOff, UINT nFrom );此函数用来定位文件指针的位置,若要定位的位置是合法的,此函数将返回从文件开始的偏移量。否则,返回值是不定的且激活一个CFileException对象。参数

497、lOff用来指定文件指针移动的字节数,nFrom表示指针移动方式,它可以是CFile:begin(从文件的开始位置)、CFile:current(从文件的当前位置)或CFile:end(从文件的最后位置,但lOff必须为负值才能在文件中定位,否则将超出文件)等。6.3.5 使用使用CFile类类3.获取文件的有关信息获取文件的有关信息CFile还支持获取文件状态,包括文件是否存在、创建与修改的日期和时间、逻辑大小和路径等。BOOL GetStatus( CFileStatus& rStatus ) const;static BOOL PASCAL GetStatus( LPCTSTR lpsz

498、FileName, CFileStatus& rStatus );若指定文件的状态信息成功获得,该函数返回TRUE,否则返回FALSE。其中,参数lpszFileName用来指定一个文件路径,这个路径可以是相对的或是绝对的,但不能是网络文件名。rStatus用来存放文件状态信息,它是一个CFileStatus结构类型,该结构具有下列成员:CTimem_ctime文件创建日期和时间CTimem_mtime文件最后一次修改日期和时间CTimem_atime文件最后一次访问日期和时间LONGm_size文件大小的字节数BYTEm_attribute文件属性charm_szFullName_MAX_P

499、ATH文件名6.3.5 使用使用CFile类类4. CFile和和CArchive类之间的关联类之间的关联可以将一个外部磁盘文件和一个CArchive对象关联起来。例如:CFiletheFile;theFile.Open(.,CFile:modeWrite);CArchivearchive(&theFile,CArchive:store);其中,CArchive构造函数的原型如下:CArchive( CFile* pFile, UINT nMode, int nBufSize = 4096, void* lpBuf = NULL );参数pFile用来指定与之关联的文件指针。nBufSize表

500、示内部文件的缓冲区大小,默认值为4096字节。lpBuf表示自定义的缓冲区指针,若为NULL,则表示缓冲区建立在堆内存中,当对象清除时,缓冲区内存也被释放;若指明用户缓冲区,对象消除时,缓冲区内存不会被释放。nMode用来指定文档是用于存入还是读取,它可以是CArchive:load(读取数据)、CArchive:store(存入数据)或CArchive:bNoFlushOnDelete(当析构函数被调用时,避免文档自动调用Flush。若设置这个标志,则必须在析构函数被调用之前调用Close。否则文件数据将被破坏)。也可将一个CArchive对象与CFile类指针相关联,如下面的代码(ar是C

501、Archive对象):constCFile*fp=ar.GetFile();6.4 视图及视图类视图及视图类MFC中的CView类及其它的派生类封装了视图的各种不同的功能,它们为用户实现最新的Windows特性提供了很大的便利。这些视图类如表6.10所示,它们都可以作为文档应用程序中视图类的基类,其设置的方法是在MFCAppWizard创建SDI/MDI的第6步中进行基类的选择。6.4.1 一般视图类的使用一般视图类的使用表表6.10 CView的派生类及其功能描述的派生类及其功能描述6.4.1 一般视图类的使用一般视图类的使用1. CEditView类类CEditView类对象是一种视图,像

502、CEdit类一样,它也提供窗口编辑控制功能,可以用来执行简单文本操作,如打印、查找、替换、剪贴板的剪切、复制和粘贴等。由于CEditView类自动封装上述功能的映射函数,因此只要在文档模板中使用CEditView类,那么应用程序的“编辑”菜单和“文件”菜单里的菜单项都可自动激活。6.4.1 一般视图类的使用一般视图类的使用例例Ex_Edit 创建一个基于创建一个基于CEditView类的单文档应用程序。类的单文档应用程序。(1)选择“文件”“新建”菜单,在弹出的“新建”对话框中选择“工程”标签,选择MFCAppWizard(exe)的项目类型,指定项目工作文件夹位置,输入项目名Ex_Edit,

503、单击确定按钮。(2)在向导的第1步中,将应用程序类型选为“单个文档”(SDI)。(3)保留默认选项,单击下一步按钮,直到出现向导的第6步,将CEx_EditView的基类选为CEditView,如图6.14所示。(4)单击完成按钮,编译运行,打开一个文档,结果如图6.15所示。图6.14更改CEx_EditView的基类 图6.15Ex_Edit运行结果 6.4.1 一般视图类的使用一般视图类的使用2. CRichEditView类类CRichEditView类使用了复合文本编辑控件,因此它支持混合字体格式和更大数据量的文本。CRichEditView类被设计成与CRichEditDoc和CR

504、ichEditCntrItem类一起使用,它们可实现一个完整的ActiveX包容器应用程序。3. CFormView类类CFormView类是一个非常有用的视图类,它具有许多无模式对话框的特点。像CDialog的派生类一样,CFormView的派生类也和相应的对话框资源相联系,它也支持对话框数据交换和对话框数据确认(DDX和DDV)。CFormView是所有表单视图(如CRecordView、CDaoRecordView、CHtmlView等)的基类;一个基于表单的应用程序能让用户在程序中创建和使用一个或多个表单。创建表单应用程序的基本方法除了在MFCAppWizard创建的第6步中选择CFo

505、rmView作为文档应用程序视图类的基类外,还可以通过选择“插入”“新建形式(NewForm)”菜单命令在文档应用程序中自动插入一个表单。6.4.1 一般视图类的使用一般视图类的使用4. CHtmlView 类类CHtmlView类是在文档视图结构中提供WebBrowser控件的功能。WebBrowser控件可以浏览网址,也可以作为本地文件和网络文件系统的窗口,它支持超级链接、统一资源定位(URL)导航器并维护历史列表等。5.CScrollView类类CScrollView类不仅能直接支持视图的滚动操作,而且还能管理视口的大小和映射模式,并能响应滚动条消息、键盘消息以及鼠标滚轮消息。6.4.2

506、 列表控件和列表视图列表控件和列表视图列表控件是一种极为有用的控件之一,它可以用“大图标”、“小图标”、“列表视图”或“报表视图”等四种不同的方式来显示一组信息,如图6.16所示。图6.16列表控件样式 6.4.2 列表控件和列表视图列表控件和列表视图1. 列表控件的风格及其修改列表控件的风格及其修改列表控件的风格有两类,一类是一般风格,如表6.11所示;另一类是VisualC+6.0在原有的基础上添加的扩展风格,如LVS_EX_FULLROWSELECT,表示整行选择,但它仅用于“报表视图”显示方式中。表表6.11 列表控件的一般风格列表控件的一般风格6.4.2 列表控件和列表视图列表控件和

507、列表视图2. 列表项的基本操作列表项的基本操作CListView按照MFC文档视图结构封装了列表控件CListCtrl类的功能。由于它又是从CCtrlView中派生的,因此它既可以调用CCtrlView的基类CView类的成员函数,又可以使用CListCtrl功能。当使用CListCtrl功能时,必需先要得到CListView封装的内嵌可引用的CListCtrl对象,这时可调用CListView的成员函数GetListCtrl,如下面的代码:CListCtrl&listCtrl=GetListCtrl();/listCtrl必须定义成引用列表控件类CListCtrl提供了许多用于列表项操作的成

508、员函数,如列表项与列的添加和删除等,下面分别介绍。(1)函数SetImageList用来为列表控件设置一个关联的图像列表,其原型如下:CImageList*SetImageList(CImageList*pImageList,intnImageList);其中,nImageList用来指定图像列表的类型,它可以是LVSIL_NORMAL(大图标)、LVSIL_SMALL(小图标)和LVSIL_STATE(表示状态的图像列表)。6.4.2 列表控件和列表视图列表控件和列表视图2. 列表项的基本操作列表项的基本操作函数InsertItem用来向列表控件中插入一个列表项。该函数成功时返回新列表项的索

509、引号,否则返回-1。函数原型如下:int InsertItem( const LVITEM* pItem );int InsertItem( int nItem, LPCTSTR lpszItem );int InsertItem( int nItem, LPCTSTR lpszItem, int nImage );其中,nItem用来指定要插入的列表项的索引号,lpszItem表示列表项的文本标签,nImage表示列表项图标在图像列表中的索引号;而pItem用来指定一个指向LVITEM结构的指针,其结构描述如下:typedefstruct_LVITEMUINT mask;/指明哪些参数有效i

510、nt iItem;/列表项索引int iSubItem;/子项索引UINTstate;/列表项状态UINTstateMask;/指明state哪些位是有效的,-1全部有效LPTSTRpszText;/列表项文本标签int cchTextMax;/文本大小int iImage;/在图像列表中列表项图标的索引号。LPARAMlParam; /32位值intiIndent; /项目缩进数量,1个数量等于1个图标的像素宽度LVITEM,FAR*LPLVITEM;结构中,mask最常用的值可以是:LVIF_TEXTpszText有效或必须赋值。LVIF_IMAGEiImage有效或必须赋值。LVIF_I

511、NDENTiIndent有效或必须赋值。6.4.2 列表控件和列表视图列表控件和列表视图2. 列表项的基本操作列表项的基本操作函数DeleteItem和DeleteAllItems分别用来删除指定的列表项和全部列表项,函数原型如下:BOOL DeleteItem( int nItem );BOOL DeleteAllItems( );函数FindItem用来查寻列表项,函数成功查找时返回列表项的索引号,否则返回-1。其原型如下:int FindItem( LVFINDINFO* pFindInfo, int nStart = -1 ) const;其中,nStart表示开始查找的索引号,-1表

512、示从头开始。pFindInfo表示要查找的信息,其结构描述如下:typedefstructtagLVFINDINFOUINTflags;/查找方式LPCTSTRpsz;/匹配的文本LPARAMlParam;/匹配的值POINTpt;/查找开始的位置坐标。UINTvkDirection;/查找方向,用虚拟方向健值表示。LVFINDINFO,FAR*LPFINDINFO;结构中,flags可以是下列值之一或组合:LVFI_PARAM查找内容由lParam指定。LVFI_PARTIAL查找内容由psz指定,不精确查找。LVFI_STRING查找内容由psz指定,精确查找。LVFI_WRAP若没有匹配

513、,再从头开始。LVFI_NEARESTXY 靠近pt位置查找,查找方向由vkDirection确定。6.4.2 列表控件和列表视图列表控件和列表视图2. 列表项的基本操作列表项的基本操作(5)函数Arrange用来按指定方式重新排列列表项,其原型如下:BOOL Arrange( UINT nCode );其中,nCode用来指定排列方式,它可以是下列值之一:LVA_ALIGNLEFT左对齐LVA_ALIGNTOP上对齐LVA_DEFAULT默认方式LVA_SNAPTOGRID使所有的图标安排在最接近的网格位置处(6)函数InsertColumn用来向列表控件插入新的一列,函数成功调用后返回新的

514、列的索引,否则返回-1。其原型如下:int InsertColumn( int nCol, const LVCOLUMN* pColumn );int InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat = LVCFMT_LEFT, int nWidth = -1, int nSubItem = -1 );其中,nCol用来指定新列的索引,lpszColumnHeading用来指定列的标题文本,nFormat用来指定列排列的方式,它可以是LVCFMT_LEFT(左对齐)、LVCFMT_RIGHT(右对齐)和LVCFMT_

515、CENTER(居中对齐);nWidth用来指定列的像素宽度,-1时表示宽度没有设置;nSubItem表示与列相关的子项索引,-1时表示没有子项。pColumn表示包含新列信息的LVCOLUMN结构地址,其结构描述如下:typedefstruct_LVCOLUMNUINTmask;/指明哪些参数有效intfmt;/列的标题或子项文本格式intcx;/列的像素宽度LPTSTRpszText;/列的标题文本intcchTextMax;/列的标题文本大小intiSubItem;/和列相关的子项索引intiImage;/图像列表中的图像索引intiOrder;/列的序号,最左边的列为0LVCOLUMN,

516、FAR*LPLVCOLUMN;结构中,mask可以是0或下列值之一或组合:LVCF_FMTfmt参数有效LVCF_IMAGEiImage参数有效LVCF_ORDERiOrder参数有效LVCF_SUBITEMiSubItem参数有效LVCF_TEXTpszText参数有效LVCF_WIDTHcx参数有效fmt可以是下列值之一:LVCFMT_BITMAP_ON_RIGHT 位图出现在文本的右边,对于从图像列表中选取的图像无效LVCFMT_CENTER文本居中LVCFMT_COL_HAS_IMAGES列表头的图像是在图像列表中LVCFMT_IMAGE从图像列表中显示一个图像LVCFMT_LEFT文

517、本左对齐LVCFMT_RIGHT文本右对齐6.4.2 列表控件和列表视图列表控件和列表视图2. 列表项的基本操作列表项的基本操作(7)函数DeleteColumn用来从列表控件中删除一个指定的列,其原型如下:BOOL DeleteColumn( int nCol );除了上述操作外,还有一些函数是用来设置或获取列表控件的相关属性的。例如SetColumnWidth用来设置指定列的像素宽度,GetItemCount用来返回列表控件中的列表项个数等。它们的原型如下:BOOLSetColumnWidth(intnCol,intcx);intGetItemCount();其中,nCol用来指定要设置的

518、列的索引号,cx用来指定列的像素宽度,它可以是LVSCW_AUTOSIZE,表示自动调整宽度。6.4.2 列表控件和列表视图列表控件和列表视图3. 列表控件的消息列表控件的消息在列表视图中,可以用MFCClassWizard映射的控件消息有公共控件消息(如NM_DBLCLK)、标题头控件消息以及列表控件消息。常用的列表控件消息有:LVN_BEGINDRAG用户按左鼠拖动列表列表项LVN_BEGINLABELEDIT用户对某列表项标签进行编辑LVN_COLUMNCLICK某列被按击LVN_ENDLABELEDIT用户对某列表项标签结束编辑LVN_ITEMACTIVATE用户激活某列表项LVN_I

519、TEMCHANGED当前列表项已被改变LVN_ITEMCHANGING当前列表项即将改变LVN_KEYDOWN某键被按下6.4.2 列表控件和列表视图列表控件和列表视图4. 示例示例例例Ex_List列表显示当前的文件(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_List,在创建的第6步将视图的基类选择为CListView。(2)为CEx_ListView类添加下列成员函数和成员函数:public:CImageListm_ImageList;CImageListm_ImageListSmall;CStringArraym_strArray;voidSetCtrlStyle(

520、HWNDhWnd,DWORDdwNewStyle)DWORDdwOldStyle;dwOldStyle=GetWindowLong(hWnd,GWL_STYLE);/获取当前风格if(dwOldStyle&LVS_TYPEMASK)!=dwNewStyle)dwOldStyle&=LVS_TYPEMASK;dwNewStyle|=dwOldStyle;SetWindowLong(hWnd,GWL_STYLE,dwNewStyle); /设置新风格其中,成员函数SetCtrlStyle用来设置列表控件的一般风格。例Ex_List(3)将项目工作区窗口切换到ResourceView页面,打开Acc

521、elerator节点下的IDR_MAINFRAME,为其添加一个键盘加速键Ctrl+Shift+X,其ID号为ID_VIEW_CHANGE。(4)用ClassWizard为CEx_ListView类添加ID_VIEW_CHANGE的COMMAND消息映射函数,并增加下列代码:voidCEx_ListView:OnViewChange()staticintnStyleIndex=1;DWORDstyle4=LVS_REPORT,LVS_ICON,LVS_SMALLICON,LVS_LIST;CListCtrl&m_ListCtrl=GetListCtrl();SetCtrlStyle(m_Lis

522、tCtrl.GetSafeHwnd(),stylenStyleIndex);nStyleIndex+;if(nStyleIndex3)nStyleIndex=0;这样,当程序运行后同时按下Ctrl、Shift和x键就会切换列表控件的显示方式。例Ex_List(5)用ClassWizard为CEx_ListView类添加NM_DBLCLK消息映射函数,并增加下列代码:voidCEx_ListView:OnDblclk(NMHDR*pNMHDR,LRESULT*pResult)LPNMITEMACTIVATElpItem=(LPNMITEMACTIVATE)pNMHDR;intnIndex=lpI

523、tem-iItem;if(nIndex=0)CListCtrl&m_ListCtrl=GetListCtrl();CStringstr=m_ListCtrl.GetItemText(nIndex,0);MessageBox(str);*pResult=0;这样,当双击某个列表项时,就是弹出一个消息对话框,显示该列表项的文本内容。例Ex_List(6)在CEx_ListView:OnInitialUpdate中添加下列代码:voidCEx_ListView:OnInitialUpdate()CListView:OnInitialUpdate();/创建图像列表m_ImageList.Create

524、(32,32,ILC_COLOR8|ILC_MASK,1,1);m_ImageListSmall.Create(16,16,ILC_COLOR8|ILC_MASK,1,1);CListCtrl&m_ListCtrl=GetListCtrl();m_ListCtrl.SetImageList(&m_ImageList,LVSIL_NORMAL);m_ListCtrl.SetImageList(&m_ImageListSmall,LVSIL_SMALL);LV_COLUMNlistCol;char*arCols4=文件名,大小,类型,修改日期;listCol.mask=LVCF_FMT|LVCF_

525、WIDTH|LVCF_TEXT|LVCF_SUBITEM;/添加列表头for(intnCol=0;nCol4;nCol+)listCol.iSubItem=nCol;listCol.pszText=arColsnCol;if(nCol=1)listCol.fmt=LVCFMT_RIGHT;elselistCol.fmt=LVCFMT_LEFT;m_ListCtrl.InsertColumn(nCol,&listCol);/查找当前目录下的文件CFileFindfinder;BOOLbWorking=finder.FindFile(*.*);intnItem=0,nIndex,nImage;CT

526、imem_time;CStringstr,strTypeName;while(bWorking)bWorking=finder.FindNextFile();if(finder.IsArchived()str=finder.GetFilePath();SHFILEINFOfi;/获取文件关联的图标和文件类型名SHGetFileInfo(str,0,&fi,sizeof(SHFILEINFO),SHGFI_ICON|SHGFI_LARGEICON|SHGFI_TYPENAME);strTypeName=fi.szTypeName;nImage=-1;for(inti=0;im_strArray.

527、GetSize();i+)if(m_strArrayi=strTypeName)nImage=i;break;if(nImage1024)str.Format(%dK,dwSize/1024);elsestr.Format(%d,dwSize);m_ListCtrl.SetItemText(nIndex,1,str);m_ListCtrl.SetItemText(nIndex,2,strTypeName);finder.GetLastWriteTime(m_time);m_ListCtrl.SetItemText(nIndex,3,m_time.Format(%Y-%m-%d);nItem+;

528、SetCtrlStyle(m_ListCtrl.GetSafeHwnd(),LVS_REPORT); /设置为报表方式m_ListCtrl.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);/设置扩展风格,使得列表项一行全项选择且显示出网格线m_ListCtrl.SetColumnWidth(0,LVSCW_AUTOSIZE);/设置列宽m_ListCtrl.SetColumnWidth(1,100);m_ListCtrl.SetColumnWidth(2,LVSCW_AUTOSIZE);m_ListCtrl.SetColumnWi

529、dth(3,200);例Ex_List(7)编译并运行,结果如图6.17所示。图6.17Ex_List运行结果6.4.3 树控件和树视图树控件和树视图1. 树形视图的风格树形视图的风格常见的树控件风格如表6.12所示,其修改方法与列表控件同的一般风格修改方法相同。表表6.12 树控件的一般风格树控件的一般风格6.4.3 树控件和树视图树控件和树视图2. 树控件的常用操作树控件的常用操作树控件类CTreeCtrl类提供了许多关于树控件操作的成员函数,如节点的添加和删除等。下面分别说明。(1)函数InsertItem用来向树控件插入一个新节点,操作成功后,函数返回新节点的句柄,否则返回NULL。函

530、数原型如下:HTREEITEM InsertItem( UINT nMask, LPCTSTR lpszItem,int nImage, int nSelectedImage, UINT nState, UINT nStateMask, LPARAM lParam, HTREEITEM hParent, HTREEITEM hInsertAfter );HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );HTREEITEM Insert

531、Item( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );其中,nMask用来指定要设置的属性,lpszItem用来指定节点的文本标签内容,nImage用来指定该节点图标在图像列表中的索引号,nSelectedImage表示该节点被选定时,其图标图像列表中的索引号,nState表示该节点的当前状态,它可以是TVIS_BOLD(加粗)、TVIS_EXPANDED(展开)和TVIS_SELECTED(选中)等,nS

532、tateMask用来指定哪些状态参数有效或必须设置,lParam表示与该节点关联的一个32位值,hParent用来指定要插入节点的父节点的句柄,hInsertAfter用来指定新节点添加的位置,它可以是TVI_FIRST(插到开始位置)、TVI_LAST(插到最后)和TVI_SORT(插入后按字母重新排序)。6.4.3 树控件和树视图树控件和树视图2. 树控件的常用操作树控件的常用操作(2)函数DeleteItem和DeleteAllItems分别用来删除指定的节点和全部的节点。它们的原型如下:BOOL DeleteAllItems( );BOOL DeleteItem( HTREEITEM

533、hItem );其中,hItem用来指定要删除的节点的句柄。如果hItem的值是TVI_ROOT,则所有的节点都被从此控件中删除。(3)函数Expand用来用来展开或收缩指定父节点的所有子节点,其原型如下:BOOL Expand( HTREEETEM hItem, UINT nCode );其中,hItem指定要被展开或收缩的节点的句柄,nCode用来指定动作标志,它可以是:TVE_COLLAPSE收缩所有子节点TVE_COLLAPSERESET 收缩并删除所有子节点TVE_EXPAND展开所有子节点TVE_TOGGLE如果当前是展开的则收缩,反之则展开6.4.3 树控件和树视图树控件和树视图

534、2. 树控件的常用操作树控件的常用操作(4)函数GetNextItem用来获取下一个节点的句柄。它的原型如下:HTREEITEM GetNextItem( HTREEITEM hItem, UINT nCode );其中,hItem指定参考节点的句柄,nCode用来指定与hItem的关系标志,常见的标志有:TVGN_CARET返回当前选择节点的句柄TVGN_CHILD返回第一个子节点句柄,hItem必须为NULLTVGN_NEXT返回下一个兄弟节点(同一个树支上的节点)句柄TVGN_PARENT返回指定节点的父节点句柄TVGN_PREVIOUS返回上一个兄弟节点句柄TVGN_ROOT返回hIt

535、em父节点的第一个子节点句柄(5)函数HitTest用来测试鼠标当前操作的位置位于哪一个节点中,并返回该节点句柄。它的原型如下:HTREEITEM HitTest( CPoint pt, UINT* pFlags );其中pFlags包含当前鼠标所在的位置标志,如下列常用定义:TVHT_ONITEM在节点上TVHT_ONITEMBUTTON在节点前面的按钮上TVHT_ONITEMICON在节点文本前面的图标上TVHT_ONITEMLABEL在节点文本上6.4.3 树控件和树视图树控件和树视图除了上述操作外,还有其他常见操作,如表6.13所示。表表6.13 CTreeCtrl类其他常见操作类其他

536、常见操作6.4.3 树控件和树视图树控件和树视图3. 树形视图控件的通知消息树形视图控件的通知消息同列表视图相类似,树视图也可以用ClassWizard映射公共控件消息和树控件消息。其中,常用的树控件消息有:TVN_BEGINDRAG开始拖放操作TVN_BEGINLABELEDIT开始编辑文本TVN_BEGINRDRAG鼠标右按钮开始拖放操作TVN_ENDLABELEDIT文本编辑结束TVN_ITEMEXPANDED含有子节点的父节点已展开或收缩TVN_ITEMEXPANDING含有子节点的父节点将要展开或收缩TVN_SELCHANGED当前选择节点发生改变TVN_SELCHANGING当前选

537、择节点将要发生改变6.4.3 树控件和树视图树控件和树视图4. 示例示例例例Ex_Tree 遍历本地磁盘所有的文件夹遍历本地磁盘所有的文件夹(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_Tree,在创建的第6步将视图的基类选择为CTreeView。(2)为CEx_TreeView类添加下列成员变量:public:CImageListm_ImageList;CStringm_strPath;/文件夹路径(3)为CEx_TreeView类添加成员函数InsertFoldItem,其代码如下:voidCEx_TreeView:InsertFoldItem(HTREEITEMhIt

538、em,CStringstrPath)CTreeCtrl&treeCtrl=GetTreeCtrl();if(treeCtrl.ItemHasChildren(hItem)return;CFileFindfinder;BOOLbWorking=finder.FindFile(strPath);while(bWorking)bWorking=finder.FindNextFile();if(finder.IsDirectory()&!finder.IsHidden()&!finder.IsDots()treeCtrl.InsertItem(finder.GetFileTitle(),0,1,hIt

539、em,TVI_SORT);例Ex_Tree(4)为CEx_TreeView类添加成员函数GetFoldItemPath,其代码如下:CStringCEx_TreeView:GetFoldItemPath(HTREEITEMhItem)CStringstrPath,str;strPath.Empty();CTreeCtrl&treeCtrl=GetTreeCtrl();HTREEITEMfolderItem=hItem;while(folderItem)intdata=(int)treeCtrl.GetItemData(folderItem);if(data=0)str=treeCtrl.Get

540、ItemText(folderItem);elsestr.Format(%c:,data);strPath=str+strPath;folderItem=treeCtrl.GetParentItem(folderItem);strPath=strPath+*.*;returnstrPath;例Ex_Tree(5)用ClassWizard为CEx_TreeView类添加TVN_SELCHANGED消息处理,并增加下列代码:voidCEx_TreeView:OnSelchanged(NMHDR*pNMHDR,LRESULT*pResult)NM_TREEVIEW*pNMTreeView=(NM_T

541、REEVIEW*)pNMHDR;HTREEITEMhSelItem=pNMTreeView-itemNew.hItem;/获取当前选择的节点CTreeCtrl&treeCtrl=GetTreeCtrl();CStringstrPath=GetFoldItemPath(hSelItem);if(!strPath.IsEmpty()InsertFoldItem(hSelItem,strPath);treeCtrl.Expand(hSelItem,TVE_EXPAND);*pResult=0;(6)在CEx_TreeView:PreCreateWindow函数中添加设置树控件风格代码:BOOLCEx

542、_TreeView:PreCreateWindow(CREATESTRUCT&cs)cs.style|=TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS;returnCTreeView:PreCreateWindow(cs);例Ex_Tree(7)在CEx_TreeView:OnInitialUpdate函数中添加下列代码:voidCEx_TreeView:OnInitialUpdate()CTreeView:OnInitialUpdate();CTreeCtrl&treeCtrl=GetTreeCtrl();m_ImageList.Create(16,1

543、6,ILC_COLOR8|ILC_MASK,2,1);treeCtrl.SetImageList(&m_ImageList,TVSIL_NORMAL);/获取Windows文件夹路径以便获取其文件夹图标CStringstrPath;GetWindowsDirectory(LPTSTR)(LPCTSTR)strPath,MAX_PATH+1);/获取文件夹及其打开时的图标,并添加到图像列表中SHFILEINFOfi;SHGetFileInfo(strPath,0,&fi,sizeof(SHFILEINFO),SHGFI_ICON|SHGFI_SMALLICON);m_ImageList.Add(

544、fi.hIcon);SHGetFileInfo(strPath,0,&fi,sizeof(SHFILEINFO),SHGFI_ICON|SHGFI_SMALLICON|SHGFI_OPENICON);m_ImageList.Add(fi.hIcon);/获取已有的驱动器图标和名称CStringstr;for(inti=0;iIsKindOf(RUNTIME_CLASS(CEx_SDIDoc);/“断言”m_pDocument指针所指向的CEx_SDIDoc类是一个RUNTIME_CLASS类型return(CEx_SDIDoc*)m_pDocument;6.5.1 文档与视图的相互作用文档与视

545、图的相互作用2. CDocument:UpdateAllViews函数函数如果文档中的数据发生了改变,那么所有的视图都必须被通知到,以便它们能够对所显示的数据进行相应的更新。UpdateAllViews函数就起到这样的作用,它的原型如下。void UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL );其中,参数pSender表示视图指针,若在应用程序文档类的成员函数中调用该函数,则此参数应为NULL,若该函数被应用程序视图类中的成员函数调用,则此参数应为this。lHint通常表示更新视图时发送信息

546、的提示标识值,pHint表示存贮信息的对象指针。当UpdateAllViews函数被调用时,如果参数pSender指向某个特定的视图对象,那么除了该指定的视图之外,文档的所有其他视图的OnUpdate函数都会被调用。6.5.1 文档与视图的相互作用文档与视图的相互作用3. CView:OnUpdate函数函数这是一个虚函数。当应用程序调用了CDocument:UpdateAllViews函数时,应用程序框架就会相应地调用该函数。virtual void OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint );其中,参数pSender表示

547、文档被更改的所在视图类指针,当为NULL时表示所有的视图需要更新。默认的OnUpdate函数(lHint=0,pHint=NULL)使得整个窗口矩形无效。如果用户想要视图的某部分无效,那么用户就要定义相关的提示(Hint)参数给出准确的无效区域;lHint和pHint含义同UpdateAllViews。事实上,hint机制主要用来在视图中根据提示标识值来获取文档或其他视图传递来的数据,例如将文档的CPoint数据传给所有的视图类,则有下列语句:GetDocument()-UpdateAllViews(NULL,1,(CObject*)&m_ptDraw);6.5.1 文档与视图的相互作用文档与

548、视图的相互作用4. CView:OnInitialUpdate函数函数当应用程序被启动时,或当用户从“文件”菜单中选择了“新建”或“打开”时,该CView虚函数都会被自动调用。该函数除了调用无提示参数(lHint=0,pHint=NULL)的OnUpdate函数之外,没做其他任何事情。但用户可以重载此函数对文档所需信息进行初始化操作。例如,如果用户应用程序中的文档大小是固定的,那么用户就可以在此重载函数中根据文档大小设置视图滚动范围;如果应用程序中的文档大小是动态的,那么用户就可在文档每次改变时调用OnUpdate来更新视图的滚动范围。6.5.1 文档与视图的相互作用文档与视图的相互作用5.

549、CDocument:OnNewDocument函数函数在文档应用程序中,当用户从“文件”菜单中选择“新建”命令时,框架将首先构造一个文档对象,然后调用该虚函数。这里是设置文档数据成员初始值的好地方,当然文档数据成员初始化处理还有其他的一些方法。例如,对于文档应用程序来说,用户还可在文档构造函数中添加初始化代码。MFCAppWizard为用户的派生文档类自动产生了重载的OnNewDocument函数,如下面的代码:BOOLCMyDoc:OnNewDocument()if(!CDocument:OnNewDocument()/注意一定要保证对基类函数的调用,returnFALSE;/Doiniti

550、alizationofnewdocumenthere.returnTRUE;6.5.2 应用程序对象指针的互调应用程序对象指针的互调1. 从文档类中获取视图对象指针从文档类中获取视图对象指针在文档类中有一个与其关联的各视图对象的列表,并可通过CDocument类的成员函数GetFirstViewPosition和GetNextView来定位相应的视图对象。GetFirstViewPosition函数用来获得与文档类相关联的视图列表中第一个可见视图的位置,GetNextView函数用来获取指定视图位置的视图类指针,并将此视图位置移动到下一个位置,若没有下一个视图,则视图位置为NULL。它们的原型

551、如下:virtual POSITION GetFirstViewPosition( ) const;virtual CView* GetNextView( POSITION& rPosition ) const;例如,下面代码是使用CDocument:GetFirstViewPosition和GetNextView重绘每个视图:voidCMyDoc:OnRepaintAllViews()POSITIONpos=GetFirstViewPosition();while(pos!=NULL)CView*pView=GetNextView(pos);pView-UpdateWindow();/实现上

552、述功能也可直接调用UpdateAllViews(NULL);6.5.2 应用程序对象指针的互调应用程序对象指针的互调2. 从视图类中获取文档对象和主框架对象指针从视图类中获取文档对象和主框架对象指针(2)在视图类中获取文档对象指针是很容易的,只需调用视图类中的成员函数GetDocument即可。而函数CWnd:GetParentFrame可实现从视图类中获取主框架指针,其原型如下: CFrameWnd* GetParentFrame( ) const;该函数将获得父框架窗口指针,它在父窗口链中搜索,直到一个CFrameWnd(或其派生类)被找到为止。成功时返回一个CFrameWnd指针,否则返

553、回NULL。3. 在主框架类中获取视图对象指针在主框架类中获取视图对象指针对于单文档应用程序来说,只需调用CFrameWnd类的GetActiveView成员函数即可,其原型如下: CView* GetActiveView( ) const;函数返回当前CView类指针,若没有当前视图,则返回NULL。6.5.3 切分窗口切分窗口1. 静态切分和动态切分静态切分和动态切分对于“静态切分”窗口来说,当窗口第一次被创建时,窗格就已经被切分好了,窗格的次序和数目不能再被改变,但用户可以移动切分条来调整窗格的大小。每个窗格通常是不同的视图类。对于“动态切分”窗口来说,它允许用户在任何时候对窗口进行切分

554、,用户既可以通过选择菜单项来对窗口进行切分,也可以通过拖动滚动条中的切分块对窗口进行切分。动态切分窗口中的窗格通常使用的是同一个视图类。当切分窗口被创建时,左上窗格通常被初始化成一个特殊的视图。当视图沿着某个方向被切分时,另一个新添加的视图对象被动态创建;当视图沿着两个方向被切分时,新添加的三个视图对象则被动态创建。当用户取消切分时,所有新添加的视图对象被删除,但最先的视图仍被保留,直到切分窗口本身消失为止。无论是静态切分还是动态切分,在创建时都要指定切分窗口中行和列的窗格最大数目。对于静态切分,窗格在初始时就按用户指定的最大数目划分好了;而对于动态切分窗口,当窗口构造时,第一个窗格就被自动创

555、建。动态切分窗口允许的最大窗格数目是2x2,而静态切分允许的最大窗格数目为16x16。6.5.3 切分窗口切分窗口2. 切分窗口的切分窗口的CSplitterWnd类操作类操作在MFC中,CSplitterWnd类封装了窗口切分过程中所需的功能函数,其中成员函数Create和CreateStatic分别用来创建“动态切分”和“静态切分”的文档窗口,函数原型如下:BOOL Create( CWnd* pParentWnd, int nMaxRows, int nMaxCols, SIZE sizeMin, CCreateContext* pContext, DWORD dwStyle = WS_

556、CHILD | WS_VISIBLE |WS_HSCROLL| WS_VSCROLL | SPLS_DYNAMIC_SPLIT, UINT nID = AFX_IDW_PANE_FIRST );BOOL CreateStatic( CWnd* pParentWnd, int nRows, int nCols, DWORD dwStyle = WS_CHILD | WS_VISIBLE, UINT nID = AFX_IDW_PANE_FIRST );其中,参数pParentWnd表示切分窗口的父框架窗口。nMaxRows表示窗口动态切分的最大行数(不能超过2)。nMaxCols表示窗口动态切分

557、的最大列数(不能超过2)。nRows表示窗口静态切分的行数(不能超过16)。nCols表示窗口静态切分的列数(不能超过16)。sizeMin表示动态切分时允许的窗格最小尺寸。6.5.3 切分窗口切分窗口2. 切分窗口的切分窗口的CSplitterWnd类操作类操作CSplitterWnd类成员函数CreateView用来为静态窗格指定一个视图类,并创建视图窗口,其函数原型如下: BOOL CreateView( int row, int col, CRuntimeClass* pViewClass, SIZE sizeInit, CCreateContext* pContext );其中,ro

558、w和col用来指定具体的静态窗格,pViewClass用来指定与静态窗格相关联的视图类,sizeInit表示视图窗口初始大小,pContext用来指定一个“创建上下文”指针。“创建上下文”结构CCreateContext包含当前文档视图框架结构。6.5.3 切分窗口切分窗口3. 静态切分窗口实现静态切分窗口实现利用CSplitterWnd成员函数,用户可以在文档应用程序的文档窗口中添加动态或静态切分功能。例例Ex_SplitSDI 将单文档应用程序中的文档窗口静态分成将单文档应用程序中的文档窗口静态分成3 x 2个窗格个窗格(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_Sp

559、litSDI。(2)打开框架窗口类MainFrm.h头文件,为CMainFrame类添加一个保护型的切分窗口的数据成员,如下面的定义:protected:/controlbarembeddedmembersCStatusBarm_wndStatusBar;CToolBarm_wndToolBar;CSplitterWnd m_wndSplitter;(3)用MFCClassWizard创建一个新的视图类CDemoView(基类为CView)用于与静态切分的窗格相关联。例Ex_SplitSDI(4)用MFCClassWizard为CMainFrame类添加OnCreateClient(当主框架窗

560、口客户区创建的时候自动调用该函数)函数重载,并添加下列代码:BOOLCMainFrame:OnCreateClient(LPCREATESTRUCTlpcs,CCreateContext*pContext)CRectrc;GetClientRect(rc);/获取客户区大小CSizepaneSize(rc.Width()/2-16,rc.Height()/3-16);/计算每个窗格的平均尺寸m_wndSplitter.CreateStatic(this,3,2);/创建3x2个静态窗格m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CDemoView),p

561、aneSize,pContext);/为相应的窗格指定视图类m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CDemoView),paneSize,pContext);m_wndSplitter.CreateView(1,0,RUNTIME_CLASS(CDemoView),paneSize,pContext);m_wndSplitter.CreateView(1,1,RUNTIME_CLASS(CDemoView),paneSize,pContext);m_wndSplitter.CreateView(2,0,RUNTIME_CLASS(CDemoVie

562、w),paneSize,pContext);m_wndSplitter.CreateView(2,1,RUNTIME_CLASS(CDemoView),paneSize,pContext);returnTRUE;例Ex_SplitSDI(5)在MainFrm.cpp源文件的开始处,添加视图类CDemoView的包含文件:#includeDemoView.h(6)编译并运行,结果如图6.19所示。第0,0窗格第1,0窗格第2,0窗格第0,1窗格第1,1窗格第2,1窗格切分条6.5.3 切分窗口切分窗口4. 动态切分窗口实现动态切分窗口实现例例Ex_DySplit 通过添加切分窗口组件来创建动态切

563、分通过添加切分窗口组件来创建动态切分(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_DySplit。(2)选择“工程”“添加工程”“ComponentsandControls”,弹出如图6.20所示的对话框。图6.20单文档应用程序的动态切分例Ex_DySplit(3)双击“VisualC+Components”,出现VisualC+支持的组件,选中SplitterBar,结果如图6.21所示。图6.21VisualC+支持的组件例Ex_DySplit(4)单击Insert按钮,出现一个消息对话框,询问是否要插入SplitterBar组件,单击确定按钮,弹出如图6.22所示的

564、对话框。从中可选择切分类型:Horizontal(水平切分)、Vertical(垂直切分)和Both(水平垂直切分)。(5)选中Both选项,单击OK按钮,回到图6.21对话框,单击结束按钮,动态切分就被添加到单文档应用程序的主框架窗口类CMainFrame中。(6)编译运行,结果如图6.23所示。垂直切分块水平切分块图6.22SplitterBar组件选项对话框 图6.23Ex_DySplit运行结果6.5.4 一档多视一档多视1. 一档多视模式一档多视模式MFC对于“一档多视”提供下列3个模式:(1)在各自MDI文档窗口中包含同一个视图类的多个视图对象。用户有时需要应用程序能为同一个文档打

565、开另一个文档窗口,以便能同时使用两个文档窗口来查看文档的不同部分内容。用MFCAppWizard创建的多文档应用程序支持这种模式,当用户选择“窗口”菜单的“新建窗口”命令时,系统就会为第一个文档窗口创建一个副本。(2)在同一个文档窗口中包含同一个视图类的多个视图对象。这种模式实际上是使用“切分窗口”机制使SDI应用程序具有多视的特征。(3)在单独一个文档窗口中包含不同视图类的多个视图对象。在该模式下,多个视图共享同一个文档窗口。它有点象“切分窗口”,但由于视图可由不同的视图类构造,所以同一个文档可以有不同的显示方法。例如,同一个文档可同时有文字显示方式及图形显示方式的视图。6.5.4 一档多视

566、一档多视2. 示例示例下面的示例是在一个多文档应用程序Ex_Rect中为同一个文档数据提供两种不同的显示和编辑方式,如图6.24所示。在左边的窗格中,用户可以调整小方块在右边窗格的坐标位置。而若在右边窗格中任意单击鼠标,相应的小方块会移动到当前鼠标位置处,且左边窗格的编辑框内容也随之发生改变。图6.24Ex_Rect运行结果6.5.4 一档多视一档多视例例Ex_Rect 一档多视示例一档多视示例1) 创建表单应用程序,设计表单创建表单应用程序,设计表单(1)用MFCAppWizard创建一个多文档应用程序Ex_Rect。在第6步中将视图的基类选择为CFormView。(2)打开表单模板资源ID

567、D_EX_RECT_FORM,调整表单模板大小,并依次添加如表6.15所示的控件。表表6.15 在表单中添加的控件在表单中添加的控件例Ex_Rect1) 创建表单应用程序,设计表单创建表单应用程序,设计表单(3)打开MFCClassWizard的MemberVariables标签,在Classname中选择CEx_RectView,选中所需的控件ID号,双击鼠标或单击AddVariables按钮。依次为表6.16中的控件添加成员变量。表表6.16 添加的控件变量添加的控件变量例Ex_Rect2) 添加添加CEx_RectDoc和和CEx_RectView类代码类代码在CEx_RectDoc类中

568、添加一个公有型的CPoint数据成员m_ptRect,用来记录小方块的位置。在CEx_RectDoc类的构造函数处添加下列代码:CEx_RectDoc:CEx_RectDoc()m_ptRect.x=m_ptRect.y=0;/或m_ptRect=CPoint(0,0)打开MFCClassWizard的MesssageMaps标签页,为编辑框IDC_EDIT1和IDC_EDIT2添加EN_CHANGE的消息映射,使它们的映射函数名都设为OnChangeEdit,并添加下列代码:voidCEx_RectView:OnChangeEdit()UpdateData(TRUE);CEx_RectDoc

569、*pDoc=(CEx_RectDoc*)GetDocument();pDoc-m_ptRect.x=m_CoorX;pDoc-m_ptRect.y=m_CoorY;CPointpt(m_CoorX,m_CoorY);pDoc-UpdateAllViews(NULL,2,(CObject*)&pt);例Ex_Rect2) 添加添加CEx_RectDoc和和CEx_RectView类代码类代码(4)用MFCClassWizard为CEx_RectView添加OnUpdate的消息函数,并添加下列代码:voidCEx_RectView:OnUpdate(CView*pSender,LPARAMlHi

570、nt,CObject*pHint)if(lHint=1)CPoint*pPoint=(CPoint*)pHint;m_CoorX=pPoint-x;m_CoorY=pPoint-y;UpdateData(FALSE);/在控件中显示CEx_RectDoc*pDoc=(CEx_RectDoc*)GetDocument();pDoc-m_ptRect=*pPoint;/保存在文档类中的m_ptRect例Ex_Rect2) 添加添加CEx_RectDoc和和CEx_RectView类代码类代码(5)在CEx_RectView:OnInitialUpdate中添加一些初始化代码:voidCEx_Rec

571、tView:OnInitialUpdate()CFormView:OnInitialUpdate();ResizeParentToFit();CEx_RectDoc*pDoc=(CEx_RectDoc*)GetDocument();m_CoorX=pDoc-m_ptRect.x; m_CoorY=pDoc-m_ptRect.y;m_SpinX.SetRange(0,1024);m_SpinY.SetRange(0,768);UpdateData(FALSE);(6)这时编译并运行程序,程序会出现一个运行错误。造成这个错误的原因是因为旋转按钮控件在设置范围时,会自动对其伙伴窗口(编辑框控件)进行

572、更新,而此时编辑框控件还没有完全创建好,因此需要进行一些处理。例Ex_Rect3) 处理旋转按钮控件的运行错误处理旋转按钮控件的运行错误(1)为CEx_RectView添加一个BOOL型的成员变量m_bEditOK。(2)在CEx_RectView构造函数中将m_bEditOK的初值设为FALSE。(3)在CEx_RectView:OnInitialUpdate函数的最后将m_bEditOK置为TRUE,如下面的代码:voidCEx_RectView:OnInitialUpdate()UpdateData(FALSE);m_bEditOK=TRUE;(4)在CEx_RectView:OnCha

573、ngeEdit函数的最前面添加下列语句:voidCEx_RectView:OnChangeEdit()if(!m_bEditOK)return;例Ex_Rect4) 新增新增CDrawView类,添加框架窗口切分功能类,添加框架窗口切分功能(1)用MFCClassWizard为添加一个新的CView的派生类CDrawView。(2)用MFCClassWizard为CChildFrame类添加OnCreateClient函数的重载,并添加下列代码:BOOLCChildFrame:OnCreateClient(LPCREATESTRUCTlpcs,CCreateContext*pContext)C

574、Rectrect;GetWindowRect(&rect);BOOLbRes=m_wndSplitter.CreateStatic(this,1,2);/创建2个水平静态窗格m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CEx_RectView),CSize(0,0),pContext);m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CDrawView),CSize(0,0),pContext);m_wndSplitter.SetColumnInfo(0,rect.Width()/2,10);/设置列宽m_wndS

575、plitter.SetColumnInfo(1,rect.Width()/2,10);m_wndSplitter.RecalcLayout();/重新布局returnbRes;/CMDIChildWnd:OnCreateClient(lpcs,pContext);例Ex_Rect4) 新增新增CDrawView类,添加框架窗口切分功能类,添加框架窗口切分功能(3)在ChildFrm.cpp的前面添加下列语句:#includeChildFrm.h#includeEx_RectView.h#includeDrawView.h(4)打开ChildFrm.h文件,为CChildFrame类添加下列成员

576、变量:public:CSplitterWndm_wndSplitter;例Ex_Rect4) 新增新增CDrawView类,添加框架窗口切分功能类,添加框架窗口切分功能(5)此时编译,程序会有一些错误。这些错误的出现是基于这样的一些事实:在用标准C/C+设计程序时,有一个原则即两个代码文件不能相互包含,而且多次包含还会造成重复定义的错误。(6)打开Ex_RectView.h文件,在classCEx_RectView:publicCFormView语句前面添加下列代码:classCEx_RectDoc;/声明CEx_RectDoc类需要再次使用classCEx_RectView:publicCF

577、ormView例Ex_Rect5) 添加添加CDrawView类代码类代码(1)为CDrawView类添加一个公有型的CPoint数据成员m_ptDraw,用来记录绘制小方块的位置。(2)在CDrawView:OnDraw函数中添加下列代码:voidCDrawView:OnDraw(CDC*pDC)CDocument*pDoc=GetDocument();CRectrc(m_ptDraw.x-5,m_ptDraw.y-5,m_ptDraw.x+5,m_ptDraw.y+5);pDC-Rectangle(rc);/绘制矩形,以后还会详细讨论(3)用MFCClassWizard为CDrawView

578、类添加OnInitialUpdate的消息函数,并添加下列代码:voidCDrawView:OnInitialUpdate()CView:OnInitialUpdate();CEx_RectDoc*pDoc=(CEx_RectDoc*)m_pDocument;m_ptDraw=pDoc-m_ptRect;例Ex_Rect5) 添加添加CDrawView类代码类代码(4)在DrawView.cpp文件的前面添加CEx_RectDoc类的包含语句:#includeEx_Rect.h#includeDrawView.h#includeEx_RectDoc.h(5)用MFCClassWizard为CD

579、rawView类添加OnUpdate的消息函数,并添加下列代码:voidCDrawView:OnUpdate(CView*pSender,LPARAMlHint,CObject*pHint)if(lHint=2)CPoint*pPoint=(CPoint*)pHint;m_ptDraw=*pPoint;Invalidate();例Ex_Rect5) 添加添加CDrawView类代码类代码(6)用MFCClassWizard为CDrawView类添加WM_LBUTTONDOWN的消息映射,并添加下列代码:voidCDrawView:OnLButtonDown(UINTnFlags,CPointp

580、oint)m_ptDraw=point;GetDocument()-UpdateAllViews(NULL,1,(CObject*)&m_ptDraw);Invalidate();/强迫调用CDrawView:OnDrawCView:OnLButtonDown(nFlags,point);(7)编译运行并测试,结果如前面图6.24所示。第第7章章 图形、文本和位图图形、文本和位图7.1 概述概述VisualC+的CDC(DeviceContext,设备环境)类是MFC中最重要的类之一,它封装了绘图所需要的所有函数,是用户编写图形和文字处理程序必不可少的。当然,绘制图形和文字时还必须指定相应的设

581、备环境。设备环境是由Windows保存的一个数据结构,该结构包含应用程序向设备输出时所需要的信息。7.1.1 设备环境类设备环境类(1)CPaintDC比较特殊,它的构造函数和析构函数都是针对OnPaint进行的,但用户一旦获得相关的CDC指针,就可以将它当成任何设备环境(包括屏幕、打印机)指针来使用。CPaintDC类的构造函数会自动调用BeginPaint,而它的析构函数则会自动调用EndPaint。(2)CClientDC只能在窗口的客户区(不包括边框、标题栏、菜单栏以及状态栏)中进行绘图,点(0,0)通常指的是客户区的左上角。而CWindowDC允许在窗口的任意位置中进行绘图,点(0,

582、0)指整个窗口的左上角。CWindowDC和CClientDC构造函数分别调用GetWindowDC和GetDC,但它们的析构函数都是调用ReleaseDC函数。(3)CMetaFileDC封装了在一个Windows图元文件中绘图的方法。图元文件是一系列与设备无关的图片的集合,由于它对图象的保存比像素更精确,因而往往在要求较高的场合下使用,例如AutoCAD的图像保存等。目前的Windows已使用增强格式(enhanced-format)的32位图元文件来进行操作。7.1.2 坐标映射坐标映射在讨论坐标映射之前,先来看看下列语句:pDC-Rectangle(CRect(0,0,200,200)

583、;它是在某设备环境中绘制出一个高为200个像素,宽也为200个像素的方块。由于默认的映射模式是MM_TEXT,其逻辑坐标(在各种映射模式下的坐标)和设备坐标(显示设备或打印设备坐标系下的坐标)相等。因此这个方块在1024x768的显示器上看起来要比在640x480的显示器上显得小一些,而且若将它打印在600dpi精度的激光打印机上,这个方块就会显得更小了。如表7.1所示。表表7.1 映射模式映射模式7.1.2 坐标映射坐标映射例例Ex_Draw 通过设置窗口和视口大小来改变显示的比例通过设置窗口和视口大小来改变显示的比例(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_Draw

584、。(2)在CEx_DrawView:OnDraw函数中添加下列代码:voidCEx_DrawView:OnDraw(CDC*pDC)CEx_DrawDoc*pDoc=GetDocument();ASSERT_VALID(pDoc);CRectrectClient;GetClientRect(rectClient);/获得当前窗口的客户区大小pDC-SetMapMode(MM_ANISOTROPIC);/设置MM_ANISOTROPIC映射模式pDC-SetWindowExt(1000,1000);/设置窗口范围pDC-SetViewportExt(rectClient.right,-rectC

585、lient.bottom);/设置视口范围pDC-SetViewportOrg(rectClient.right/2,rectClient.bottom/2);/设置视口原点pDC-Ellipse(CRect(-500,-500,500,500);例例Ex_Draw(3)编译运行,结果如图7.1所示。图7.1改变显示比例7.1.3 CPoint、CSize和和CRect在图形绘制操作中,常常需要使用MFC中的CPoint、CSize和CRect等简单数据类由于CPoint(点)、CSize(大小)和CRect(矩形)是对Windows的POINT、SIZE和RECT结构的封装,因此它们可以直接

586、使用各自结构的数据成员,如下所示:typedefstructtagPOINTtypedefstructtagSIZELONGx;/点的x坐标intcx;/水平大小LONGy;/点的y坐标intcy;/垂直大小POINT;SIZE;typedefstructtagRECTLONGleft;/矩形左上角点的x坐标LONGtop;/矩形左上角点的y坐标LONGright;/矩形右下角点的x坐标LONGbottom;/矩形右下角点的y坐标RECT;7.1.3 CPoint、CSize和和CRect1. CPoint、CSize和和CRect类的构造函数类的构造函数CPoint类带参数的常用构造函数原型

587、如下: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类带参数的常

588、用构造函数原型如下: 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 );7.1.3 CPoint、CSize和和CRect2. CRect类的常用操作类的常用操作由于一个CRect类对象包含用于定义矩形的左上角和右下角点的成员变量,因此在传递LPRECT、LPCRECT或RECT结构作为参数的任何地方,都可以

589、使用CRect对象来代替。CRect类的操作函数有很多,这里只介绍矩形的扩大、缩小以及两个矩形的“并”和“交”操作,更多的常用操作如表7.2所示。表表7.2 CRect类常用的成员函数类常用的成员函数7.1.3 CPoint、CSize和和CRect2. CRect类的常用操作类的常用操作成员函数InflateRect和DeflateRect用来扩大和缩小一个矩形。由于它们的操作是相互的,也就是说,若指定InflateRect函数的参数为负值,那么操作的结果是缩小矩形,因此下面只给出InflateRect函数的原型:void InflateRect( int x, int y );void I

590、nflateRect( SIZE size );void InflateRect( LPCRECT lpRect );void InflateRect( int l, int t, int r, int b );其中,x用来指定扩大CRect左、右边的数值。y用来指定扩大CRect上、下边的数值。size中的cx成员指定扩大左、右边的数值,cy指定扩大上、下边的数值。lpRect的各个成员用来指定扩大每一边的数值。l、t、r和b分别用来指定扩大CRect左、上、右和下边的数值。7.1.3 CPoint、CSize和和CRect2. CRect类的常用操作类的常用操作成员函数IntersectR

591、ect和UnionRect分别用来将两个矩形进行相交和合并,当结果为空时返回FALSE,否则返回TRUE。它们的原型如下:BOOL IntersectRect( LPCRECT lpRect1, LPCRECT lpRect2 );BOOL UnionRect( LPCRECT lpRect1, LPCRECT lpRect2 );其中,lpRect1和lpRect2用来指定操作的两个矩形。例如:CRectrectOne(125,0,150,200);CRectrectTwo(0,75,350,95);CRectrectInter;rectInter.IntersectRect(rectOne

592、,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);7.1.4 颜色和颜色对话框颜色和颜色对话框在MFC中,CDC使用的是RGB颜色空间,即选用红(R)、绿(G)、蓝(B)三种基色分量,通过对这三种基色不同比例的混合,可以得到不同的彩色效果。并且,MFC使用COLORREF数据类型来表示一个32位的RGB颜色,它也可以用下列的十六进制表

593、示:0x00bbggrr此形式的rr、gg、bb分别表示红、绿、蓝三个颜色分量的16进制值,最大为0xff。在具体操作RGB颜色时,还可使用下列的宏操作:GetBValue获得32位RGB颜色值中的蓝色分量GetGValue获得32位RGB颜色值中的绿色分量GetRValue获得32位RGB颜色值中的红色分量RGB将指定的R、G、B分量值转换成一个32位的RGB颜色值。MFC的CColorDialog类为应用程序提供了颜色选择通用对话框,如图7.2所示。图7.2颜色对话框7.1.4 颜色和颜色对话框颜色和颜色对话框CColorDialog类具有下列的构造函数:CColorDialog( COL

594、ORREF clrInit = 0, DWORD dwFlags = 0, CWnd* pParentWnd = NULL );其中,clrInit用来指定选择的默认颜色值,若此值没指定,则为RGB(0,0,0)(黑色)。pParentWnd用来指定对话框的父窗口指针。dwFlags用来表示定制对话框外观和功能的系列标志参数。它可以是下列值之一或”|”组合:CC_ANYCOLOR在基本颜色单元中列出所有可得到的颜色CC_FULLOPEN显示所有的颜色对话框界面。若此标志没有被设定,则用户单击“规定自定义颜色”按钮才能显示出定制颜色的界面CC_PREVENTFULLOPEN禁用“规定自定义颜色”

595、按钮CC_SHOWHELP在对话框中显示“帮助”按钮CC_SOLIDCOLOR在基本颜色单元中只列出所得到的纯色当对话框“OK”退出(即DoModal返回IDOK)时,可调用下列成员获得相应的颜色。 COLORREF GetColor( ) const;/ 返回用户选择的颜色。 void SetCurrentColor( COLORREF clr );/ 强制使用clr作为当前选择的颜色 static COLORREF * GetSavedCustomColors( );/返回用户自己定义颜色7.1.5 图形设备接口图形设备接口Windows为设备环境提供了各种各样的绘图工具,例如用于画线的“

596、画笔”、填充区域的“画刷”以及用于绘制文本的“字体”。MFC封装了这些工具,并提供相应的类来作为应用程序的图形设备接口GDI,这些类有一个共同的抽象基类CGdiObject,具体如表7.3所示。表表7.3 MFC的的GDI类类7.1.5 图形设备接口图形设备接口1. 使用使用GDI对象对象在选择GDI对象进行绘图时,往往遵循着下列的步骤:(1)在堆栈中定义一个GDI对象(如CPen、CBrush对象),然后用相应的函数(如CreatePen、CreateSolidBrush)创建此GDI对象。但要注意:有些GDI派生类的构造函数允许用户提供足够的信息,从而一步即可完成对象的创建任务,这些类有C

597、Pen、CBrush。(2)将构造的GDI对象选入当前设备环境中,但不要忘记将原来的GDI对象保存起来。(3)绘图结束后,恢复当前设备环境中原来的GDI对象。(4)由于GDI对象是在堆栈中创建中,当程序结束后,会自动删除程序创建的GDI对象。具体操作可像下面的代码过程:voidCMyView:OnDraw(CDC*pDC)CPenpenBlack;/定义一个画笔变量penBlack.CreatePen(PS_SOLID,2,RGB(0,0,0);/创建画笔/将此画笔选入当前设备环境并保存原来的画笔CPen*pOldPen=pDC-SelectObject(&penBlack);/用此画笔绘图p

598、DC-MoveTo(.);pDC-LineTo(.);/其他绘图函数pDC-SelectObject(pOldPen);/恢复设备环境中原来的画笔7.1.5 图形设备接口图形设备接口2. 库存的库存的GDI对象对象除了自定义的GDI对象外,Windows还包含了一些预定义的库存GDI对象。由于它们是Windows系统的一部分,因此用户用不着删除它们。CDC的成员函数SelectStockObject可以把一个库存对象选入当前设备环境中,并返回原先被选中的对象指针,同时使原先被选中的对象从设备环境中分离出来。如下面的代码:voidCEx_SDIView:OnDraw(CDC*pDC)CPenne

599、wPen(PS_SOLID,2,RGB(0,0,0)pDC-SelectObject(&newPen);pDC-MoveTo(.);pDC-LineTo(.);/其他绘图函数pDC-SelectStockObject(BLACK_PEN);/newPen被分离出来2. 库存的库存的GDI对象对象函数SelectStockObject可选用的库存GDI对象类型可以是下列值之一:BLACK_BRUSH黑色画刷DKGRAY_BRUSH深灰色画刷GRAY_BRUSH灰色画刷HOLLOW_BRUSH中空画刷LTGRAY_BRUSH浅灰色画刷NULL_BRUSH空画刷WHITE_BRUSH白色画刷BLAC

600、K_PEN黑色画笔NULL_PEN空画笔WHITE_PEN白色画笔DEVICE_DEFAULT_FONT设备默认字体SYSTEM_FONT系统字体7.2 简单图形绘制简单图形绘制图形的绘制通常需要先创建画笔和画刷,然后调用相应的绘图函数。7.2.1 画笔画笔画笔是Windows应用程序中用来绘制各种直线和曲线的一种图形工具,它可分为修饰画笔和几何画笔两种类型。在这两种类型中,几何画笔的定义最复杂,它不但有修饰画笔的属性,而且还跟画刷的样式、阴影线类型有关,通常用在对绘图有较高要求的场合。而修饰画笔只有简单的几种属性,通常用在简单的直线和曲线等场合。7.2.1 画笔画笔表表7.4 修饰画笔的风格

601、修饰画笔的风格7.2.1 画笔画笔创建一个修饰画笔,可以使用CPen类的CreatePen函数,其原型如下:BOOL CreatePen( int nPenStyle, int nWidth, COLORREF crColor );其中,参数nPenStyle、nWidth、crColor分别用来指定画笔的风格、宽度和颜色。此外,还有一个CreatePenIndirect函数也是用来创建画笔对象,它的作用与CreatePen函数是完全一样的,只是画笔的三个属性不是直接出现在函数参数中,而是通过一个LOGPEN结构间接地给出。BOOL CreatePenIndirect( LPLOGPEN lp

602、LogPen );此函数用由LOGPEN结构指针指定的相关参数创建画笔,LOGPEN结构如下:typedefstructtagLOGPEN/*lgpn*/UINTlopnStyle;/画笔风格,同上POINTlopnWidth;/POINT结构的y不起作用,而用x表示画笔宽度COLORREFlopnColor;/画笔颜色LOGPEN;7.2.2 画刷画刷画刷的属性通常包括填充色、填充图案和填充样式三种。画刷的填充色和画笔颜色一样,都是使用COLORREF颜色类型,画刷的填充图案通常是用户定义的88位图,而填充样式往往是CDC内部定义的一些特性,它们都是以HS_为前缀的标识,如图7.3所示:HS

603、_BDIAGONALHS_CROSSHS_DIAGCROSSHS_FDIAGONAL HS_HORIZONTAL HS_VERTICAL图7.3 画刷的填充样式7.2.2 画刷画刷CBrush类根据画刷属性提供了相应的创建函数,例如创建填充色画刷和填充样式画刷的函数为CreateSolidBrush和CreateHatchBrush,它们的原型如下:BOOL CreateSolidBrush( COLORREF crColor ); / 创建填充色画刷BOOL CreateHatchBrush( int nIndex, COLORREF crColor ); / 创建填充样式画刷其中,nInd

604、ex用来指定画刷的内部填充样式,而crColor表示画刷的填充色。与画笔相类似,也有一个LOGBRUSH逻辑结构用于画刷属性的定义,并通过CBrush的成员函数CreateBrushIndirect来创建,其原型如下:BOOL CreateBrushIndirect( const LOGBRUSH* lpLogBrush );其中,LOGBRUSH逻辑结构如下定义:typedefstructtagLOGBRUSH/lbUINTlbStyle;/风格COLORREFlbColor;/填充色LONGlbHatch;/填充样式LOGBRUSH;7.2.3 图形绘制图形绘制1. 画点、线画点、线(1)

605、画点是最基本的绘图操作之一,它是通过调用CDC:SetPixel或CDC:SetPixelV函数来实现的。这两个函数都是用来在指定的坐标上设置指定的颜色,只不过SetPixelV函数不需要返回实际像素点的RGB值;正是因为这一点,函数SetPixelV要比SetPixel快得多。COLORREF SetPixel( int x, int y, COLORREF crColor );COLORREF SetPixel( POINT point, COLORREF crColor );BOOL SetPixelV(int x, int y, COLORREF crColor);BOOL SetPi

606、xelV( POINT point, COLORREF crColor );实际显示像素的颜色未必等同于crColor所指定的颜色值,因为有时受设备限制,不能显示crColor所指定的颜色值,而只能取其近似值。与上述函数相对应的GetPixel函数是用来获取指定点的颜色。COLORREF GetPixel( int x, int y ) const;COLORREF GetPixel( POINT point ) const;1. 画点、线画点、线(2)画线也是特别常用的绘图操作之一。CDC的LineTo和MoveTo函数就是用来实现画线功能的两个函数,通过这两个函数的配合使用,可完成任何直线

607、和折线的绘制操作。这个当前位置还可用函数CDC:GetCurrentPosition来获得,其原型如下:CPoint GetCurrentPosition( ) const;LineTo函数正是经当前位置所在点为直线起始点,另指定直线终点,画出一段直线的。其原型如下:BOOL LineTo( int x, int y );BOOL LineTo( POINT point );7.2.3 图形绘制图形绘制2. 折线折线除了LineTo函数可用来画线之外,CDC中还提供了一系列用于画各种折线的函数。它们主要是Polyline、PolyPolyline和PolylineTo。这三个函数中,Polyl

608、ine和PolyPolyline既不使用当前位置,也不更新当前位置;而PolylineTo总是把当前位置作为起始点,并且在折线画完之后,还把折线终点所在位置设为新的当前位置。BOOL Polyline( LPPOINT lpPoints, int nCount );BOOL PolylineTo( const POINT* lpPoints, int nCount );这两个函数用来画一系列连续的折线。参数lpPoints是POINT或CPoint的顶点数组;nCount表示数组中顶点的个数,它至少为2。BOOL PolyPolyline( const POINT* lpPoints, con

609、st DWORD* lpPolyPoints, int nCount );此函数可用来绘制多条折线。其中lpPoints同前定义,lpPolyPoints表示各条折线所需的顶点数,nCount表示折线的数目。7.2.3 图形绘制图形绘制3. 矩形和圆角矩形矩形和圆角矩形CDC提供的Rectangle和RoundRect函数分别用于矩形和圆角矩形的绘制,它们的原型如下:BOOL Rectangle( int x1, int y1, int x2, int y2 );BOOL Rectangle( LPCRECT lpRect );BOOL RoundRect( int x1, int y1, i

610、nt x2, int y2, int x3, int y3 );BOOL RoundRect( LPCRECT lpRect, POINT point );参数lpRect的成员left,top,right,bottom分别表示x1,y1,x2,y2,point的成员x,y分别表示x3,y3;而x1,y1表示矩形的左上角坐标,x2,y2表示矩形的右上角坐标,x3,y3表示绘制圆角的椭圆大小,如图7.4所示。图7.4圆角矩形图7.5多边形填充模式7.2.3 图形绘制图形绘制4. 设置多边形填充模式设置多边形填充模式多边形填充模式决定了图形填充时寻找填充区域的方法,有两种选择:ALTERNATE和

611、WINDING。ALTERNATE模式是寻找相邻的奇偶边作为填充区域,而WINDING是按顺时针或逆时针进行寻找;一般情况,这两种模式的填充效果是相同的,但对于像五角星这样的图形,填充的结果大不一样,例如下面的代码,其结果如图7.5所示。.POINTpt5=247,10,230,90,290,35,210,30,275,85;CBrushbrush(HS_FDIAGONAL,RGB(255,0,0);CBrush*oldbrush=pDC-SelectObject(&brush);pDC-SetPolyFillMode(ALTERNATE);pDC-Polygon(pt,5);for(inti

612、=0;iSetPolyFillMode(WINDING);pDC-Polygon(pt,5);pDC-SelectObject(oldbrush);brush.DeleteObject();代码中,SetPolyFillMode是CDC类的一个成员函数,用来设置填充模式,它的参数可以是ALTERNATE和WINDING。7.2.3 图形绘制图形绘制5. 多边形多边形前面已经介绍过折线的画法,而多边形可以说就是由首尾相接的封闭折线所围成的图形。画多边形的函数Polygon原型如下:BOOL Polygon( LPPOINT lpPoints, int nCount );可以看出,Polygon函

613、数的参数形式与Polyline函数是相同的。但也稍有一点小差异。例如,要画一个三角形,使用Polyline函数,顶点数组中就得给出四个顶点(尽管始点和终点重复出现),而用Polygon函数则只需给出三个顶点。与PolyPolyline可画多条折线一样,使用PolyPolygon函数,一次可画出多个多边形,这两个函数的参数形式和含义也一样。BOOL PolyPolygon( LPPOINT lpPoints, LPINT lpPolyCounts, int nCount );7.2.3 图形绘制图形绘制6. 圆弧和椭圆圆弧和椭圆通过调用CDC的Arc函数可以画一条椭圆弧线或者整个椭圆。这个椭圆的

614、大小是由其外接矩形(本身并不可见)所决定的。Arc函数的原型如下:BOOL Arc( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 );BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd );这里,x1,y1,x2,y2或lpRect用来指定外接矩形的位置和大小,而椭圆中心与点(x3,y3)或ptStart所构成的射线与椭圆的交点就成为椭圆弧线的起始点,椭圆中心与点(x4,y4)或ptEnd所构成的射线与椭圆的交点就成为椭圆弧线的终点。椭圆上弧线始点到终点的部分

615、是要绘制的椭圆弧,如图7.6所示。图7.6 弧线中心外接矩形弧线(x1,y1)(x2,y2)起点坐标终点坐标7.2.3 图形绘制图形绘制7. 弦形和扇形弦形和扇形CDC类成员函数Chord和Pie是用来绘制弦形(图7.7)和扇形(图7.8),它们具有和Arc一样的参数。BOOL Chord( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 );BOOL Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd );BOOL Pie( int x1, int y1, int x

616、2, int y2, int x3, int y3, int x4, int y4 );BOOL Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd );图7.7 弦形中心外接矩形弦形(x1,y1)(x2,y2)起点坐标终点坐标图7.8 扇形中心外接矩形扇形(x1,y1)(x2,y2)起点坐标终点坐标7.2.3 图形绘制图形绘制8. Bzier曲线曲线Bzier曲线是最常见的非规则曲线之一,它的形状不仅便于控制,而且更主要的是它具有几何不变性(即它的形状不随坐标的变换而改变),因此在许多场合往往采用这种曲线。Bzier曲线属于三次曲线,只需给定四个点

617、(第一和第四个点是端点,另两个是控制点),就可唯一确定其形状,如图7.9所示。P1P2P3P4图7.9 Bzier曲线8. Bzier曲线曲线函数PolyBezier是用来画出一条或多条Bzier曲线的,其函数原型如下:BOOL PolyBezier( const POINT* lpPoints, int nCount );其中lpPoints是曲线端点和控制点所组成的数组,nCount表示lpPoints数组中的点数。如果lpPoints用于画多条Bzier曲线,那么除了第一条曲线要用到四个点之外,后面的曲线只需用三个点,因为后面的曲线总是把前一条曲线的终点作为自己的起始端点。函数PolyB

618、ezier不使用也不更新当前位置。如果需要使用当前位置,那么就应该使用PolyBezierTo函数。 BOOL PolyBezierTo( const POINT* lpPoints, int nCount );7.2.3 图形绘制图形绘制9. 绘图示例绘图示例下面来看一个简单的示例。它是用来表示一个班级某门课程的成绩分布,用一个直方图来反映90五个分数段的人数,它需要绘制五个矩形,相邻矩形的填充样式还要有所区别,并且还需要显示各分数段的人数。其结果如图7.10所示。图7.10Ex_Draw运行结果7.2.3 图形绘制图形绘制例例Ex_Draw 课程的成绩分布直方图课程的成绩分布直方图用MFC

619、AppWizard创建一个默认的单文档应用程序Ex_Draw。为CEx_DrawView类添加一个成员函数DrawScore,用来根据成绩来绘制直方图,该函数的代码如下:voidCEx_DrawView:DrawScore(CDC*pDC,float*fScore,intnNum)/fScore是成绩数组指针,nNum是学生人数intnScoreNum=0,0,0,0,0;/各成绩段的人数的初始值/下面是用来统计各分数段的人数for(inti=0;inNum;i+)intnSeg=(int)(fScorei)/10;/取数的十位上的值if(nSeg6)nSeg=5;/90分数段nScoreNu

620、mnSeg-5+;/各分数段计数intnSegNum=sizeof(nScoreNum)/sizeof(int);/计算有多少个分数段/求分数段上最大的人数intnNumMax=nScoreNum0;for(i=1;inSegNum;i+)if(nNumMaxSelectObject(&brush1);/将brush1选入设备环境CPen*oldPen=pDC-SelectObject(&pen);/将pen选入设备环境CRectrcSeg(rc);rcSeg.right=rcSeg.left+nSegWidth;/使每段的矩形宽度等于nSegWidthCStringstrSeg=90;CRe

621、ctrcStr;for(i=0;iSelectObject(&brush2);elsepDC-SelectObject(&brush1);rcSeg.top=rcSeg.bottom-nScoreNumi*nSegHeight-2;/计算每段矩形的高度pDC-Rectangle(rcSeg);if(nScoreNumi0)CStringstr;str.Format(%d人,nScoreNumi);pDC-DrawText(str,rcSeg,DT_CENTER|DT_VCENTER|DT_SINGLELINE);rcStr=rcSeg;rcStr.top=rcStr.bottom+2;rcSt

622、r.bottom+=20;pDC-DrawText(strSegi,rcStr,DT_CENTER|DT_VCENTER|DT_SINGLELINE);/后面还会讲到rcSeg.OffsetRect(nSegWidth,0);/右移矩形pDC-SelectObject(oldBrush);/恢复原来的画刷属性pDC-SelectObject(oldPen);/恢复原来的画笔属性例Ex_Draw(3)在CEx_DrawView:OnDraw函数中添加下列代码:voidCEx_DrawView:OnDraw(CDC*pDC)CEx_DrawDoc*pDoc=GetDocument();ASSERT

623、_VALID(pDoc);floatfScore=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)编译并运行,如前图7.10所示。7.3 字体与文字处理字体与文字处理字体是文字显示和打印的外观形式,它包括了文字的字样、风格和尺寸等多方面的属性。适当地选用不同的字体,可以大大地丰富文字的外在表现力。例如,把文字中某些重要

624、的字句用较粗的字体显示,能够体现出突出、强调的意图。7.3.1 字体和字体对话框字体和字体对话框1. 字体的属性和创建字体的属性和创建字体的属性有很多,但其主要属性有字样、风格和尺寸三个。字样是字符书写和显示时表现出的特定模式,例如,对于汉字,通常有宋体、楷体、仿宋、黑体、隶书以及幼圆等多种字样。字体风格主要表现为字体的粗细和是否倾斜等特点。字体尺寸是用来指定字符所占区域的大小,通常用字符高度来描述。字体尺寸可以取毫米或英寸作为单位,但为了直观起见,也常常采用一种称为“点”的单位,一点约折合为1/72英寸。1. 字体的属性和创建字体的属性和创建逻辑字体的具体属性可由LOGFONT结构来描述,这

625、里仅列最常用到的结构成员。typedefstructtagLOGFONT LONGlfHeight;/字体的逻辑高度 LONGlfWidth;/字符的平均逻辑宽度 LONGlfEscapement;/倾角 LONGlfOrientation;/书写方向 LONGlfWeight;/字体的粗细程度 BYTElfItalic;/斜体标志 BYTElfUnderline;/下划线标志 BYTElfStrikeOut;/删除线标志 BYTElfCharSet;/字符集,汉字必须为GB2312_CHARSET TCHARlfFaceNameLF_FACESIZE;/字样名称/LOGFONT;1. 字体的

626、属性和创建字体的属性和创建根据定义的逻辑字体,用户就可以调用CFont类的CreateFontIndirect函数创建文本输出所需要的字体,如下面的代码:LOGFONTlf;/定义逻辑字体的结构变量memset(&lf,0,sizeof(LOGFONT);/将lf中的所有成员置0lf.lfHeight=-13;lf.lfCharSet=GB2312_CHARSET;strcpy(LPSTR)&(lf.lfFaceName),黑体);/用逻辑字体结构创建字体CFontcf;cf.CreateFontIndirect(&lf);/在设备环境中使用字体CFont*oldfont=pDC-Select

627、Object(&cf);pDC-TextOut(100,100,Hello);pDC-SelectObject(oldfont);/恢复设备环境原来的属性cf.DeleteObject();/删除字体对象7.3.1 字体和字体对话框字体和字体对话框2. 使用字体对话框使用字体对话框CFontDialog类提供了字体及其文本颜色选择的通用对话框,如图7.11所示。它的构造函数如下:CFontDialog(LPLOGFONTlplfInitial = NULL,DWORDdwFlags = CF_EFFECTS|CF_SCREENFONTS,CDC* pdcPrinter =NULL,CWnd*p

628、ParentWnd = NULL);其中,参数lplfInitial是一个LOGFONT结构指针,用来设置对话框最初的字体特性。dwFlags指定选择字体的标志。pdcPrinter用来表示打印设备环境指针。pParentWnd表示对话框的父窗口指针。图7.11字体对话框2. 使用字体对话框使用字体对话框当字体对话框DoModal返回IDOK后,可使用下列的成员函数:void GetCurrentFont( LPLOGFONT lplf );/返回用户选择的LOGFONT字体CString GetFaceName( ) const;/ 返回用户选择的字体名称CString GetStyleNa

629、me( ) const;/ 返回用户选择的字体样式名称int GetSize( ) const;/返回用户选择的字体大小COLORREF GetColor( ) const;/返回用户选择的文本颜色int GetWeight( ) const;/返回用户选择的字体粗细程度BOOL IsStrikeOut( ) const;/判断是否有删除线BOOL IsUnderline( ) const;/判断是否有下划线BOOL IsBold( ) const;/判断是否是粗体BOOL IsItalic( ) const;/判断是否是斜体。2. 使用字体对话框使用字体对话框通过字体对话框可以创建一个字体,

630、如下面的代码:LOGFONTlf;CFontcf;memset(&lf,0,sizeof(LOGFONT);/将lf中的所有成员置0CFontDialogdlg(&lf);if(dlg.DoModal()=IDOK)dlg.GetCurrentFont(&lf);pDC-SetTextColor(dlg.GetColor();cf.CreateFontIndirect(&lf);.7.3.2 常用文本输出函数常用文本输出函数文本的最终输出不仅依赖于文本的字体,而且还跟文本的颜色、对齐方式等有很大关系。CDC类提供了四个输出文本的成员函数:TextOut、ExtTextOut、TabbedTex

631、tOut和DrawText。对于这四个函数,用户应根据具体情况来选用。例如,如果想要绘制的文本是一个多列的列表形式,那么采用TabbedTextOut函数,启用制表位,可以使绘制出来的文本效果更佳;如果要在一个矩形区域内绘制多行文本,那么采用DrawText函数,会更富于效率;如果文本和图形结合紧密,字符间隔不等,并要求有背景颜色或矩形裁剪特性,那么ExtTextOut函数将是最好的选择。如果没有什么特殊要求,那使用TextOut函数就显得简练了。下面介绍TextOut、TabbedTextOut和DrawText函数。virtual BOOL TextOut( int x, int y, L

632、PCTSTR lpszString, int nCount );BOOL TextOut( int x, int y, const CString& str );7.3.2 常用文本输出函数常用文本输出函数virtual CSize TabbedTextOut( int x, int y, LPCTSTR lpszString, int nCount, int nTabPositions, LPINTlpnTabStopPositions, int nTabOrigin );CSize TabbedTextOut( int x, int y, const CString& str, int n

633、TabPositions, LPINT lpnTabStopPositions, int nTabOrigin );virtual int DrawText( LPCTSTR lpszString, int nCount, LPRECT lpRect, UINT nFormat );int DrawText( const CString& str, LPRECT lpRect, UINT nFormat );7.3.2 常用文本输出函数常用文本输出函数DrawText函数是当前字体在指定矩形中对文本进行格式化绘制。参数中,lpRect用来指定文本绘制时的参考矩形,它本身并不显示;nFormat

634、表示文本的格式,它可以是下列的常用值之一或“|”组合:DT_BOTTOM下对齐文本,该值还必须与DT_SINGLELINE组合DT_CENTER水平居中DT_END_ELLIPSIS使用省略号取代文本末尾的字符DT_PATH_ELLIPSIS使用省略号取代文本中间的字符DT_EXPANDTABS使用制表位,缺省的制表长度为8个字符DT_LEFT左对齐DT_MODIFYSTRING将文本调整为能显示的字串DT_NOCLIP不裁剪DT_NOPREFIX不支持“&”字符转义DT_RIGHT右对齐DT_SINGLELINE指定文本的基准线为参考点DT_TABSTOP设置停止位。nFormat的高位字节

635、是每个制表位的数目DT_TOP上对齐DT_VCENTER垂直居中DT_WORDBREAK自动换行7.3.2 常用文本输出函数常用文本输出函数例例Ex_DrawText 绘制文本的简单示例绘制文本的简单示例(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_DrawText。(2)在CEx_DrawTextView:OnDraw中添加下列代码:voidCEx_DrawTextView:OnDraw(CDC*pDC)CEx_DrawTextDoc*pDoc=GetDocument();ASSERT_VALID(pDoc);CRectrc(10,10,200,140);pDC-Rect

636、angle(rc);pDC-DrawText(单行文本居中,rc,DT_CENTER|DT_VCENTER|DT_SINGLELINE);rc.OffsetRect(200,0);/将矩形向右偏移200pDC-Rectangle(rc);intnTab=40;/将一个Tab位的值指定为40个逻辑单位pDC-TabbedTextOut(rc.left,rc.top,绘制tTabt文本t示例,1,&nTab,rc.left);/使用自定义的停止位(Tab)nTab=80;/将一个Tab位的值指定为80个逻辑单位pDC-TabbedTextOut(rc.left,rc.top+20,绘制tTabt文

637、本t示例,1,&nTab,rc.left);/使用自定义的停止位(Tab)pDC-TabbedTextOut(rc.left,rc.top+40,绘制tTabt文本t示例,0,NULL,0);/使用默认的停止位例例Ex_DrawText(3)编译运行,结果如图7.12所示。图7.12Ex_DrawText运行结果7.3.3 文本格式化属性文本格式化属性原型如下:virtual COLORREF SetTextColor( COLORREF crColor );COLORREF GetTextColor( ) const;virtual COLORREF SetBkColor( COLORREF

638、 crColor );COLORREF GetBkColor( ) const;int SetBkMode( int nBkMode );int GetBkMode( ) const;其中,nBkMode用来指定文本背景模式,它可以是OPAQUE或TRANSPARENT(透明)。文本对齐方式的设置和获取是由CDC函数SetTextAlign和GetTextAlign决定的。它们的原型如下:UINT SetTextAlign( UINT nFlags );UINT GetTextAlign( ) const;7.3.3 文本格式化属性文本格式化属性上述两个函数中所用到的文本对齐标志如表7.5所示

639、。这些标志可以分为三组:TA_LEFT、TA_CENTER和TA_RIGHT确定水平方向的对齐方式,TA_BASELINE、TA_BOTTOM和TA_TOP确定上下方向的对齐方式,TA_NOUPDATECP和TA_UPDATECP确定当前位置的更新标志。这三组标志中,组与组之间的标志可使用“|”操作符。表表7.5 文本对齐标志文本对齐标志7.3.4 计算字符的几何尺寸计算字符的几何尺寸在打印和显示某段文本时,有必要了解字符的高度计算及字符的测量方式,才能更好地控制文本输出效果。在CDC类中,GetTextMetrics(LPTEXTMETRIClpMetrics)是用来获得指定映射模式下相关设

640、备环境的字符几何尺寸及其它属性的,其TEXTMETRIC结构描述如下(这里仅列出最常用的结构成员):typedefstructtagTEXTMETRIC/tminttmHeight;/字符的高度(ascent+descent)inttmAscent;/高于基准线部分的值inttmDescent;/低于基准线部分的值inttmInternalLeading;/字符内标高inttmExternalLeading;/字符外标高inttmAveCharWidth;/字体中字符平均宽度inttmMaxCharWidth;/字符的最大宽度/TEXTMETRIC;7.3.4 计算字符的几何尺寸计算字符的几何

641、尺寸原型如下: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 l

642、pnTabStopPositions ) const;其中,参数lpszString和str表示要计算的字符串,nCount表示字符串的字节长度,nTabPositions表示lpnTabStopPositions数组的大小,lpnTabStopPositions表示多个递增的制表位(逻辑坐标)的数组。函数返回当前设备环境下的一行字符串的宽度(CSize的cx)和高度(CSize的cy)。7.3.5 文档内容显示及其字体改变文档内容显示及其字体改变例例Ex_Text 显示文档内容并改变显示的字体显示文档内容并改变显示的字体(1)用MFCAppWizard创建一个单文档应用程序Ex_Text,在

643、创建的第6步将视图的基类选择为CScrollView。由于视图客户区往往显示不了文档的全部内容,因此需要视图支持滚动操作。(2)为CEx_TextDoc类添加CStringArray类型的成员变量m_strContents,用来将读取的文档内容保存。(3)在CEx_TextDoc:Serialize函数中添加读取文档内容的代码:voidCEx_TextDoc:Serialize(CArchive&ar)if(ar.IsStoring()elseCStringstr;m_strContents.RemoveAll();while(ar.ReadString(str)m_strContents.A

644、dd(str);例例Ex_Text(4)为CEx_TextView类添加LOGFONT类型的成员变量m_lfText,用来保存当前所使用的逻辑字体。(5)在CEx_TextView类构造函数中添加m_lfText的初始化代码:CEx_TextView:CEx_TextView()memset(&m_lfText,0,sizeof(LOGFONT);m_lfText.lfHeight=-12;m_lfText.lfCharSet=GB2312_CHARSET;strcpy(m_lfText.lfFaceName,宋体);例例Ex_Text(6)用MFCClassWizard为CEx_TextVi

645、ew类添加WM_LBUTTONDBLCLK(双击鼠标)的消息映射函数,并增加下列代码:voidCEx_TextView:OnLButtonDblClk(UINTnFlags,CPointpoint)CFontDialogdlg(&m_lfText);if(dlg.DoModal()=IDOK)dlg.GetCurrentFont(&m_lfText);Invalidate();CScrollView:OnLButtonDblClk(nFlags,point);这样,当双击鼠标左键后,就会弹出字体对话框,从中可改变字体的属性,单击确定按钮后,执行CEx_TextView:OnDraw中的代码。例

646、例Ex_Text(7)在CEx_TextView:OnDraw中添加下列代码:voidCEx_TextView:OnDraw(CDC*pDC)CEx_TextDoc*pDoc=GetDocument();ASSERT_VALID(pDoc);/创建字体CFontcf;cf.CreateFontIndirect(&m_lfText);CFont*oldFont=pDC-SelectObject(&cf);/计算每行高度TEXTMETRICtm;pDC-GetTextMetrics(&tm);intlineHeight=tm.tmHeight+tm.tmExternalLeading;inty=0

647、;inttab=tm.tmAveCharWidth*4;/为一个TAB设置4个字符/输出并计算行的最大长度intlineMaxWidth=0;CStringstr;CSizelineSize(0,0);for(inti=0;im_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(lineMaxWidthSelectObject

648、(oldFont);/多算一行,以滚动窗口能全部显示文档内容intnLines=pDoc-m_strContents.GetSize()+1;CSizesizeTotal;sizeTotal.cx=lineMaxWidth;sizeTotal.cy=lineHeight*nLines;SetScrollSizes(MM_TEXT,sizeTotal);/设置滚动逻辑窗口的大小例例Ex_Text(8)编译运行并测试,打开任意一个文本文件,结果如图7.13所示。图7.13Ex_Text运行结果7.4 位图、图标与光标位图、图标与光标7.4.1 使用图形编辑器使用图形编辑器在VisualC+6.0中

649、,图形编辑器可以创建和编辑任何位图格式的图像资源,除工具栏外,它还用于位图、图标和光标。它的功能很多,如提供一套完整的绘图工具来绘制256色的图形,进行位图的移动和复制以及含有若干个编辑工具等。由于图形编辑器的使用和Windows中的“绘图”工具相似,因此它的具体绘制操作在这里不再重复。这里仅讨论下列一些常用操作。如创建新的图标和光标、选用或定制显示设备和设置光标“热点”等。7.4.1 使用图形编辑器使用图形编辑器1. 创建一个新的图标或光标创建一个新的图标或光标在VisualC+6.0中,创建一个应用程序后,当按快捷键Ctrl+R就可打开“插入资源”对话框,从中选择Cursor(光标)或Ic

650、on(图标)资源类型,单击新建按钮后,系统为程序添加一个新的图标或光标资源,同时在开发环境右侧出现图形编辑器。图7.14是添加一个新的图标资源后出现的图形编辑器。图7.14添加图标后的图形编辑器新设备按钮1. 创建一个新的图标或光标创建一个新的图标或光标在创建新图标或光标的时候,图形编辑器首先创建的是一个适合于VGA环境中的图像,开始的时候它以屏幕色(透明方式)来填充。对于创建的新光标,其“热点”被初始化为左上角的点,坐标为(0,0)。默认情况下,图形编辑器所支持的显示设备如表7.6所示。表表7.6 创建图标或光标时可选用的显示设备创建图标或光标时可选用的显示设备7.4.1 使用图形编辑器使用

651、图形编辑器2. 选用和定制显示设备选用和定制显示设备在图形编辑器工作窗口的控制条上,有一个NewDeviceImage按钮,单击此按钮后,系统弹出相应的新设备列表,用户可以从中选取需要的显示设备,如图7.15所示。除了对话框列表框显示的设备外,还可以单击定制按钮,在弹出的对话框中定制新的显示设备,如图7.16所示,在这里可指定新设备的大小和颜色。图7.15图像设备选择对话框图图7.16显示设备的定制7.4.1 使用图形编辑器使用图形编辑器3. 设置光标热点设置光标热点Windows系统借助光标“热点”来确定光标实际的位置。在图形编辑器的控制条上或光标属性对话框中都可以看到当前的光标“热点”位置

652、。图7.17是添加一个新的光标资源后出现的图形编辑器。控制条设置热点按钮图7.17添加光标后的图形编辑器7.4.2 位图位图1. CBitmap类类函数原型如下:BOOL LoadBitmap( LPCTSTR lpszResourceName );BOOL LoadBitmap( UINT nIDResource );BOOL CreateBitmap( int nWidth, int nHeight, UINT nPlanes, UINT nBitcount, const void* lpBits );BOOL CreateBitmapIndirect( LPBITMAP lpBitmap

653、 );该函数直接用BITMAP结构来创建一个位图对象。BOOL CreateCompatibleBitmap( CDC* pDC, int nWidth, int nHeight );该函数为某设备环境创建一个指定的宽度(nWidth)和高度(nHeight)的位图对象。7.4.2 位图位图2. GDI位图的显示位图的显示由于位图不能直接显示在实际设备中,因此对于GDI位图的显示则必须遵循下列步骤:(1)调用CBitmap类的CreateBitmap、CreateCompatibleBitmap以及CreateBitmapIndirect函数创建一个适当的位图对象。(2)调用CDC:Creat

654、eCompatibleDC函数创建一个内存设备环境,以便位图在内存中保存下来,并与指定设备(窗口设备)环境相兼容;(3)调用CDC:SelectObject函数将位图对象选入内存设备环境中;(4)调用CDC:BitBlt或CDC:StretchBlt函数将位图复制到实际设备环境中。(5)使用之后,恢复原来的内存设备环境。2. GDI位图的显示位图的显示例例Ex_BMP 在视图中显示位图在视图中显示位图(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_BMP。(2)按快捷键Ctrl+R,弹出“插入资源”对话框,选择Bitmap资源类型。(3)单击导入按钮,出现“导入资源”对话框,

655、将文件类型选择为“所有文件(*.*)”,从外部文件中选定一个位图文件,然后单击Import按钮,该位图就被调入应用程序中。保留默认的位图资源标识IDB_BITMAP1。例例Ex_BMP(4)在CEx_BMPView:OnDraw函数中添加下列代码:voidCEx_BMPView:OnDraw(CDC*pDC)CEx_BMPDoc*pDoc=GetDocument();ASSERT_VALID(pDoc);CBitmapm_bmp;m_bmp.LoadBitmap(IDB_BITMAP1);/调入位图资源BITMAPbm;/定义一个BITMAP结构变量,以便获取位图参数m_bmp.GetObje

656、ct(sizeof(BITMAP),&bm);CDCdcMem;/定义并创建一个内存设备环境dcMem.CreateCompatibleDC(pDC);CBitmap*pOldbmp=dcMem.SelectObject(&m_bmp);/将位图选入内存设备环境中pDC-BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcMem,0,0,SRCCOPY);/将位图复制到实际的设备环境中dcMem.SelectObject(pOldbmp);/恢复原来的内存设备环境例例Ex_BMP(5)编译运行,结果如图7.18所示。图7.18 Ex_BMP运行结果7.4.3 图标图标1.

657、 图标的调入和清除图标的调入和清除在MFC中,使用CWinApp:LoadIcon函数可将一个图标资源调入并返回一个图标句柄。函数原型如下:HICON LoadIcon( LPCTSTR lpszResourceName ) const;HICON LoadIcon( UINT nIDResource ) const;其中,lpszResourceName和nIDResource分别表示图标资源的字符串名和标识。函数返回的是一个图标句柄。1. 图标的调入和清除图标的调入和清除如果不想创建新的图标资源,也可使用系统中预定义好的标准图标,这时需调用CWinApp:LoadStandardIcon函

658、数,其原型如下:HICON LoadStandardIcon( LPCTSTR lpszIconName ) const;其中,lpszIconName可以是下列值之一:IDI_APPLICATION默认的应用程序图标IDI_HAND手形图标(用于严重警告)IDI_QUESTION问号图标(用于提示消息)IDI_EXCLAMATION警告消息图标(惊叹号)IDI_ASTERISK消息图标全局函数DestroyIcon可以用来删除一个图标,并释放为图标分配的内存,其原型如下:BOOL DestroyIcon( HICON hIcon );其中,hIcon用来指定要删除的图标句柄。7.4.3 图标

659、图标2. 图标的绘制图标的绘制函数CDC:DrawIcon用来将一个图标绘制在指定的位置处,其原型如下:BOOL DrawIcon( int x, int y, HICON hIcon );BOOL DrawIcon( POINT point, HICON hIcon );其中,(x,y)和point用来指定图标绘制的位置,而hIcon用来指定要绘制的图标句柄。7.4.3 图标图标3. 应用程序图标的改变应用程序图标的改变在用MFCAppWizard创建的应用程序中,图标资源IDR_MAINFRAME用来表示应用程序窗口的图标,通过图形编辑器可将其内容直接修改。实际上,程序中还可使用GetCl

660、assLong和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窗口类的的图标句柄GCL_MENUNAME窗口

661、类的的菜单资源名称7.4.3 图标图标4. 示例示例(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_Icon。(2)新添四个图标资源,通过图像设备选择对话框(参见图7.15),选择Small(1616)作为图标的设备类型。图标资源ID号分别为默认的IDI_ICON1IDI_ICON4。(3)用图形编辑器绘制图标,结果如图7.19所示。图7.19创建的四个图标4. 示例示例(4)为CMainFrame类添加一个成员函数ChangeIcon,用来切换应用程序的图标。该函数的代码如下:voidCMainFrame:ChangeIcon(UINTnIconID)HICONhIconN

662、ew=AfxGetApp()-LoadIcon(nIconID);HICONhIconOld=(HICON)GetClassLong(m_hWnd,GCL_HICON);if(hIconNew!=hIconOld)DestroyIcon(hIconOld);SetClassLong(m_hWnd,GCL_HICON,(long)hIconNew);RedrawWindow();/重绘窗口4. 示例示例(5)在CMainFrame:OnCreate函数的最后添加计时器设置代码:intCMainFrame:OnCreate(LPCREATESTRUCTlpCreateStruct)if(CFram

663、eWnd:OnCreate(lpCreateStruct)=-1)return-1;.SetTimer(1,500,NULL);return0;4. 示例示例(6)用MFCClassWizard为CMainFrame类添加WM_TIMER的消息映射函数,并增加下列代码:voidCMainFrame:OnTimer(UINTnIDEvent)staticinticons=IDI_ICON1,IDI_ICON2,IDI_ICON3,IDI_ICON4;staticintindex=0;ChangeIcon(iconsindex);index+;if(index3)index=0;CFrameWnd

664、:OnTimer(nIDEvent);OnTimer函数的参数nIDEvent用来表示发送WM_TIMER消息的计时器的标识值。4. 示例示例(7)用MFCClassWizard为CMainFrame类添加WM_DESTROY的消息映射函数,并增加下列代码:voidCMainFrame:OnDestroy()CFrameWnd:OnDestroy();KillTimer(1);代码中,KillTimer函数是CWnd类成员函数,用来停止WM_TIMER消息的传送,其函数参数值用指定要停用的计时器标识值。(8)编译并运行。可以看到任务栏上的按钮以及应用程序的标题栏上四个图标循环显示的动态效果,显

665、示速度为每秒两帧。7.4.4 光标光标光标在Windows程序中起着非常重要的作用,它不仅能反映鼠标的运动位置,而且还可以表示程序执行的状态,引导用户的操作,使程序更加生动。例如沙漏光标表示“正在执行,请等待”,IE中手形光标表示“可以跳转”,另外还有一些有趣的动画光标。光标又称为“鼠标指针”。1. 使用系统光标使用系统光标Windows预定义了一些经常使用的标准光标,这些光标均可以使用函数CWinApp:LoadStandardCursor加载到程序中,其函数原型如下:HCURSOR LoadStandardCursor( LPCTSTR lpszCursorName ) const;其中,

666、lpszCursorName用来指定一个标准光标名,它可以是下列宏定义:IDC_ARROW标准箭头光标IDC_IBEAM标准文本输入光标IDC_WAIT漏斗型计时等待光标IDC_CROSS十字形光标IDC_UPARROW 垂直箭头光标IDC_SIZEALL四向箭头光标IDC_SIZENWSE 向下的双向箭头光标IDC_SIZENESW向上双向箭头光标IDC_SIZEWE左右双向箭头光标IDC_SIZENS上下双向箭头光标例如,加载一个垂直箭头光标IDC_UPARROW的代码如下:HCURSORhCursor;hCursor=AfxGetApp()-LoadStandardCursor(IDC_

667、UPARROW);7.4.4 光标光标2. 使用光标资源使用光标资源用编辑器创建或从外部调入的光标资源,可通过函数CWinApp:LoadCursor进行加载,其原型如下:HCURSOR LoadCursor( LPCTSTR lpszResourceName ) const;HCURSOR LoadCursor( UINT nIDResource ) const;其中,lpszResourceName和nIDResource分别用来指定光标资源的名称或ID号。例如,当光标资源为IDC_CURSOR1时,则可使用下列代码:HCURSORhCursor;hCursor=AfxGetApp()-L

668、oadCursor(IDC_CURSOR1);7.4.4 光标光标3. 更改程序中的光标更改程序中的光标更改应用程序中的光标除了可以使用GetClassLong和SetClassLong函数外,最简单的方法是用MFCClassWizard映射WM_SETCURSOR消息,该消息是当光标移动到一个窗口内并且还没有捕捉到鼠标时产生的。CWnd为此消息的映射函数定义这样的原型:afx_msgBOOLOnSetCursor(CWnd*pWnd,UINTnHitTest,UINTmessage);其中,pWnd表示拥有光标的窗口指针,nHitTest用来表示光标所处的位置,例如当为HTCLIENT时表示

669、光标在窗口的客户区中,而为HTCAPTION时表示光标在窗口的标题栏处,为HTMENU时表示光标在窗口的菜单栏区域等等。message用来表示鼠标消息。在OnSetCursor函数调用SetCursor来设置相应的光标,并将OnSetCursor函数返回TRUE,就可改变当前的光标了。7.4.4 光标光标4. 示例示例本示例用来根据当前鼠标所在的位置来确定单文档应用程序光标的类型,当处在标题栏时为一个动画光标,当处在客户区时为一个自定义光标。例例Ex_Cursor 改变应用程序光标改变应用程序光标(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_Cursor。(2)按快捷键Ct

670、rl+R,打开“插入资源”对话框,选择“Cursor”类型后,单击新建按钮。(3)在图形编辑器工作窗口的控制条上,单击“NewDeviceImage”按钮,从弹出的“NewDeviceImage”对话框中,单击定制按钮。(4)在弹出的“定制图像”对话框中,保留默认的大小和颜色数,单击确定按钮。回到“NewDeviceImage”对话框。(5)选择“32x32,16colors”设备类型,单击确定按钮。(6)在图形编辑器的“Device”组合框中,选择“Monochrome32x32”,打开系统Image菜单,选择“DeleteDeviceImage”命令,删除“Monochrome32x32”

671、设备类型。如果不这样做,加载后的光标不会采用“32x32,16colors”设备类型。例例Ex_Cursor(7)保留默认的ID号IDC_CURSOR1,用图形编辑器绘制光标图形,指定光标热点位置为(15,15),结果如图7.20所示。(8)为CMainFrame类添加一个成员变量m_hCursor,变量类型为光标句柄HCURSOR。图7.20创建的光标例例Ex_Cursor(9)用MFCClassWizard为CMainFrame类添加WM_SETCURSOR的消息映射函数,并增加下列代码:BOOLCMainFrame:OnSetCursor(CWnd*pWnd,UINTnHitTest,U

672、INTmessage)BOOLbRes=CFrameWnd:OnSetCursor(pWnd,nHitTest,message);if(nHitTest=HTCAPTION)m_hCursor=LoadCursorFromFile(c:windowscursorsglobe.ani);SetCursor(m_hCursor);bRes=TRUE;elseif(nHitTest=HTCLIENT)m_hCursor=AfxGetApp()-LoadCursor(IDC_CURSOR1);SetCursor(m_hCursor);bRes=TRUE;returnbRes;例例Ex_Cursor(1

673、0)编译运行并测试。当鼠标移动到标题栏时,光标变成了globe.ani的动画光标,而当移动到客户区时,光标变成了IDC_CURSOR1定义的形状。第8章数据库编程VisualC+6.0为用户提供了ODBC、DAO及OLEDB三种数据库方式。这三种方式中最简单也最常用的是ODBC,因此本章先来重点介绍MFC的ODBC编程方法和技巧,然后介绍基于OLEDB的ADO(ActiveXDataObjects,ActiveX数据对象)技术,最后介绍一些用于数据库的ActiveX控件。8.1 MFC ODBC数据库概述数据库概述ODBC是一种使用SQL的程序设计接口,使用ODBC能使用户编写数据库应用程序变

674、得容易简单,避免了与数据源相连接的复杂性。在VisualC+中,MFC的ODBC数据库类CDatabase(数据库类)、CRecordSet(记录集类)和CRecordView(记录视图类)可为用户管理数据库提供了切实可行的解决方案。8.1.1 数据库基本概念数据库基本概念1. 数据库和数据库和DBMS数据库是指以一定的组织形式存放在计算机存储介质上的相互关联的数据的集合。例如,把一个学校的教师、学生和课程等数据有序地组织起来,存储在计算机磁盘上,就构成了一个数据库。为了有效地管理数据库,常常需要一些数据库管理系统(DBMS)为用户提供对数据库操作的各种命令、工具及方法,包括数据库的建立和记录

675、的输入、修改、检索、显示、删除和统计等。流行的DBMS都提供了一个SQL接口。2. SQL作为用来在DBMS中访问和操作的语言,SQL(结构化查询语言)语句分为两类:一是DDL(DataDefinitionLanguage,数据定义语言)语句,它是用来创建表、索引等,另一是DML(DataManipulationLanguage,数据操作语言)语句,这些语句是用来读取数据、更新数据和执行其他类似操作的语句。8.1.1 数据库基本概念数据库基本概念3. ODBC、DAO和和OLE DBODBC提供了应用程序接口(API),使得任何一个数据库都可以通过ODBC驱动器与指定的DBMS相联。用户的程序

676、就可以通过调用ODBC驱动管理器中相应的驱动程序达到管理数据库的目的。作为MicrosoftWindowsOpenStandardsArchitecture(WOSA,Windows开放式服务体系结构)的主要组成部分,ODBC一直沿用至今。DAO类似于用MicrosoftAccess或MicrosoftVisualBasic编写的数据库应用程序,它使用Jet数据库引擎形成一系列的数据访问对象:数据库对象、表和查询对象、记录集对象等。它可以打开一个Access数据库文件(MDB文件),也可直接打开一个ODBC数据源以及使用Jet引擎打开一个ISAM(被索引的顺序访问方法)类型的数据源(dBASE

677、、FoxPro、Paradox、Excel或文本文件)。OLEDB试图提供一种统一的数据访问接口,并能处理除了标准关系型数据库中的数据之外,还能处理包括邮件数据、Web上的文本或图形、目录服务(DirectoryServices)以及主机系统中的IMS和VSAM数据。OLEDB提供一个数据库编程COM(组件对象模型)接口,使得数据的使用者(应用程序)可以使用同样的方法访问各种数据,而不用考虑数据的具体存储地点、格式或类型。8.1.1 数据库基本概念数据库基本概念4. ADOADO是目前在Windows环境中比较流行的客户端数据库编程技术。它是Microsoft为最新和最强大的数据访问范例OLE

678、DB而设计的,是一个便于使用的应用程序层接口。ADO使用户应用程序能够通过“OLEDB提供者”访问和操作数据库服务器中的数据。由于它兼具有强大的数据处理功能(处理各种不同类型的数据源、分布式的数据处理等等)和极其简单、易用的编程接口,因而得到了广泛的应用。ADO技术基于COM(ComponentObjectModel,组件对象模型),具有COM组件的许多优点,可以用来构造可复用应用框架,被多种语言支持,能够访问包括关系数据库、非关系数据库及所有的文件系统。另外,ADO还支持各种B/S与基于Web的应用程序,具有远程数据服务RDS(RemoteDataService)的特性,是远程数据存取的发展

679、方向。8.1.2 MFC ODBC向导过程向导过程用MFCAppWizard使用ODBC数据库的一般过程是:用Access或其他数据库工具构造一个数据库;在Windows中为刚才构造的数据库定义一个ODBC数据源;在创建数据库处理的文档应用程序向导中选择数据源;设计界面,并使控件与数据表字段关联。8.1.2 MFC ODBC向导过程向导过程1. 构造数据库构造数据库数据库表与表之间的关系构成了一个数据库。作为示例,这里用MicrosoftAccess创建一个数据库Student.mdb,其中暂包含一个数据表score,用来描述学生课程成绩,如表8.1所示。在表中包括上、下两部分,上部分是数据表

680、的记录内容,下部分是数据表的结构内容。表表8.1 学生课程成绩表学生课程成绩表(score)及其表结构及其表结构8.1.2 MFC ODBC向导过程向导过程2. 创建创建ODBC数据源数据源Windows中的ODBC组件是出现在系统的“控制面板”管理工具中,如图8.1所示。双击ODBC图标(在图8.1中已圈定),进入ODBC数据源管理器。在这里,用户可以设置ODBC数据源的一些信息。其中,“用户DSN”页面是用来定义用户自己在本地计算机使用的数据源名(DSN),如图8.2所示。图8.1Windows2000的管理工具图8.2ODBC数据源管理器2. 创建创建ODBC数据源数据源创建用户DSN的

681、过程如下。(1)单击添加按钮,弹出有一驱动程序列表的“创建新数据源”对话框,在该对话框中选择要添加用户数据源的驱动程序,这里选择“MicrosoftAccessDriver”,如图8.3所示。(2)单击完成按钮,进入指定驱动程序的安装对话框,单击选择按钮将前面创建的数据库调入,然后在数据源名输入“DatabaseExampleForVC+”,结果如图8.4所示。图8.3“创建新数据源”对话框图8.4ODBCAccess安装对话框2. 创建创建ODBC数据源数据源(3)单击确定按钮,刚才创建的用户数据源被添加在“ODBC数据源管理器”的“用户数据源”列表中。如图8.5所示。图8.5用户数据源列表

682、图8.6向导的第二步对话框8.1.2 MFC ODBC向导过程向导过程3. 在在MFC AppWizard中选择数据源中选择数据源用MFCAppWizard可以容易地创建一个支持数据库的文档应用程序,如下面的过程。用MFCAppWizard创建一个单文档应用程序Ex_ODBC。在向导的第2步对话框中加入数据库的支持,如图8.6所示。在该对话框中用户可以选择对数据库支持程序,其中各选项的含义如表8.2所示。表表8.2 MFC支持数据库的不同选项支持数据库的不同选项3. 在在MFC AppWizard中选择数据源中选择数据源选中“数据库查看使用文件支持”项,单击DataSource按钮,弹出“Da

683、tabaseOptions”对话框,从中选择ODBC的数据源“DatabaseExampleForVC+”,如图8.7所示。图8.7“DatabaseOptions”对话框3. 在在MFC AppWizard中选择数据源中选择数据源(4)保留其他默认选项,单击OK按钮,弹出如图8.8所示的“SelectDatabaseTables”对话框,从中选择要使用的表score。(5)单击OK按钮,又回到了向导的第2步对话框。(6)单击完成按钮。开发环境自动打开表单视图CEx_ODBCView的对话框资源模板IDD_EX_ODBC_FORM以及相应的对话框编辑器。(7)编译并运行,结果如图8.9所示。图

684、8.8 “Select Database Tables”对话框记录浏览按钮图8.9Ex_ODBC运行结果8.1.2 MFC ODBC向导过程向导过程4. 设计浏览记录界面设计浏览记录界面在上面的Ex_ODBC中,MFC为用户自动创建了用于浏览数据表记录的工具按钮和相应的“记录”菜单项。若用户选择这些浏览记录命令,系统会自动调用相应的函数来移动数据表的当前位置。若在表单视图CEx_ODBCView中添加控件并与表的字段相关联,就可以根据表的当前记录位置显示相应的数据。其步骤如下。(1)按照图8.10所示的布局,为表单对话框资源模板添加表8.3所示的控件。图8.10控件的设计表表8.3 表单对话框

685、控件及属性表单对话框控件及属性4. 设计浏览记录界面设计浏览记录界面(2)按快捷键Ctrl+W,弹出MFCClassWizard对话框,切换到MemberVariables页面,在Classname框中选择CEx_ODBCView,为上述控件添加相关联的数据成员。与以往添加控件变量不同的是,这里添加的控件变量都是由系统自动定义的,并与数据库表字段相关联的。例如,双击IDC_STUNO,在弹出的“AddMemberVariable”对话框中的成员变量下拉列表中选择要添加的成员变量名m_pSet-m_studentno,选择后,控件变量的类型将自动设置,如图8.11所示。图8.11为控件添加数据成

686、员4. 设计浏览记录界面设计浏览记录界面(3)按照上一步骤的方法,为表8.4所示的其他控件依次添加相关联的成员变量。需要说明的是,控件变量的范围和大小应与数据表中的字段一一对应。结果如图8.12所示。表表8.4 控件变量控件变量图8.12添加的控件变量图4. 设计浏览记录界面设计浏览记录界面(4)编译运行并测试,结果如图8.13所示。8.13Ex_ODBC最后运行结果8.1.3 ODBC数据表绑定更新数据表绑定更新其步骤如下:(1)按快捷键Ctrl+W,打开MFCClassWizard对话框,切换到“MemberVariables”页面。(2)在“Classname”的下拉列表中选择“CEx_

687、ODBCSet”,此时MFCClassWizard对话框的UpdateColumns和BindAll按钮被激活,如图8.14所示。图8.14“MFCClassWizard”对话框8.1.3 ODBC数据表绑定更新数据表绑定更新(3)单击UpdateColumns按钮,又弹出前面的“DatabaseOptions”对话框,选择ODBC数据源“DatabaseExampleForVC+”,如图8.15所示。(4)单击OK按钮,弹出如图8.16所示的“SelectDatabaseTables”对话框,从中选择要使用的表。图8.15“DatabaseOptions”对话框图8.16“SelectDat

688、abaseTables”对话框8.1.3 ODBC数据表绑定更新数据表绑定更新(5)单击OK按钮,又回到MFCClassWizard界面,如图8.17所示。(6)单击BindAll按钮,MFCWizard将自动为字段落添加相关联的变量。图8.17更新后的“MemberVariables”页面8.2 MFC ODBC应用编程应用编程使用MFC所供的ODBC类:CDatabase(数据库类)、CRecordSet(记录集类)和CRecordView(可视记录集类)。CDatabase类用来提供对数据源的连接,通过它可以对数据源进行操作;CRecordView类用来控制并显示数据库记录,该视图是直接

689、连到一个CRecordSet对象的表单视图。但在实际应用过程中,CRecordSet类是用户最关心的,因为它为用户提供了对表记录进行操作的许多功能,如查询记录、添加记录、删除记录、修改记录等,并能直接为数据源中的表映射一个CRecordSet类对象,方便用户的操作。CRecordSet类对象提供了从数据源中提取出表的记录集,并提供了两种操作形式:动态行集(Dynasets)和快照集(Snapshots)。动态行集能与其他用户所做的更改保持同步,而快照集则是数据的一个静态视图。8.2.1 查询记录查询记录使用CRecordSet类的成员变量m_strFilter、m_strSort和成员函数Op

690、en可以对表进行记录的查询和排序。先来看一个示例,该示例在前面的Ex_ODBC的表单中添加一个编辑框和一个查询按钮,单击查询按钮,将按编辑框中的学号内容对数据表进行查询,并将查找到的记录显示在前面添加的控件中。具体过程如下:(1)打开Ex_ODBC应用程序的表单资源,按图8.18所示的布局添加控件,其中添加的编辑框ID号设为IDC_EDIT_QUERY,“查询”按钮的ID号设为IDC_BUTTON_QUERY。图8.18要添加的控件8.2.1 查询记录查询记录(2)用MFCClassWizard为控件IDC_EDIT_QUERY添加关联变量m_strQuery。(3)在CEx_ODBCView

691、类中添加按钮控件IDC_BUTTON_QUERY的BN_CLICKED消息映射,并在映射函数中添加下列代码:voidCEx_ODBCView:OnButtonQuery()UpdateData();m_strQuery.TrimLeft();if(m_strQuery.IsEmpty()MessageBox(要查询的学号不能为空!);return;if(m_pSet-IsOpen()m_pSet-Close();/如果记录集打开,则先关闭m_pSet-m_strFilter.Format(studentno=%s,m_strQuery);/studentno是score表的字段名,用来指定查询

692、条件m_pSet-m_strSort=course;/course是score表的字段名,用来按course字段从小到大排序m_pSet-Open();/打开记录集if(!m_pSet-IsEOF()/如果打开记录集有记录UpdateData(FALSE);/自动更新表单中控件显示的内容elseMessageBox(没有查到你要找的学号记录!);代码中,m_strFilter和m_strSort是CRecordSet的成员变量,用来执行条件查询和结果排序。其中,m_strFilter称为“过滤字符串”,相当于SQL语句中WHERE后的条件串;而m_strSort称为“排序字符串”,相当于SQL

693、语句中ORDERBY后的字符串。若字段的数据类型是文本,则需要在m_strFilter字符串中将单引号将查询的内容括起来,对于数字,则不需要用单引号。8.2.1 查询记录查询记录(4)编译运行并测试,结果如图8.19所示。图8.19查询记录8.2.2 编辑记录编辑记录CRecordSet类为用户提供了许多对表记录进行操作的成员函数用来添加记录、删除记录和修改记录等。1. 增加记录增加记录增加记录是使用AddNew函数,但要求数据库必须是以“可增加”的方式打开的。下面的代码是在表的末尾增加新记录:m_pSet-AddNew();/在表的末尾增加新记录m_pSet-SetFieldNull(&(m

694、_pSet-m_studentno),FALSE);/设定m_studentno值不为空(NULL)m_pSet-m_studentno=21010503;./输入新的字段值m_pSet-Update();/将新记录存入数据库m_pSet-Requery();/刷新记录集,这在快照集方式下是必须的8.2.2 编辑记录编辑记录1. 删除记录删除记录可以直接使用CRecordSet:Delete函数来删除记录。需要说明的是,要使删除操作有效,还需要移动记录函数。例如下面的代码:CRecordsetStatusstatus;m_pSet-GetStatus(status);/获取当前记录集状态m_p

695、Set-Delete();/删除当前记录if(status.m_lCurrentRecord=0)/若当前记录索引号为0(0表示第一条记录)则m_pSet-MoveNext();/下移一个记录elsem_pSet-MoveFirst();/移动到第一个记录处UpdateData(FALSE);8.2.2 编辑记录编辑记录3. 修改记录修改记录函数CRecordSet:Edit可以用来修改记录,例如:m_pSet-Edit();/修改当前记录m_pSet-m_name=刘向东;/修改当前记录字段值.m_pSet-Update();/将修改结果存入数据库m_pSet-Requery();4. 撤消

696、操作撤消操作如果用户在进行增加或者修改记录后,希望放弃当前操作,则在调用CRecordSet:Update()函数之前调用CRecordSet:Move(AFX_MOVE_REFRESH)来撤消操作,便可恢复在增加或修改操作之前的当前记录。8.2.3 字段操作字段操作CRecordSet类中的成员变量m_nFields(用于保存数据表的字段个数)和成员函数GetODBCFieldInfo及GetFieldValue可以简化多字段的访问操作。GetODBCFieldInfo函数用来数据表中的字段信息,其函数原型如下:void GetODBCFieldInfo( short nIndex, COD

697、BCFieldInfo& fieldinfo );其中,nIndex用于指定字段索引号,0表示第一个字段,1表示第二个字段,以此类推。fieldinfo是CODBCFieldInfo结构参数,用来表示字段信息。CODBCFieldInfo结构体原型如下:structCODBCFieldInfoCStringm_strName;/字段名SWORDm_nSQLType;/字段的SQL数据类型UDWORDm_nPrecision;/字段的文本大小或数据大小SWORDm_nScale;/字段的小数点位数SWORDm_nNullability;/字段接受空值(NULL)能力;8.2.3 字段操作字段操作

698、结构体裁中,SWORD和UDWORD分别表示shortint和unsignedlongint数据类型。GetFieldValue函数用来获取数据表当前记录中指定字段的值,其常用的函数原型如下:void GetFieldValue( short nIndex, CString& strValue );其中,nIndex用于指定字段索引号,strValue用来返回字段的内容。除了上述字段操作外,CRecordSet类的成员函数GetRecordCount和GetStatus,还可分别用来获得表中的记录总数和当前记录的索引,其原型如下: long GetRecordCount( ) const; v

699、oid GetStatus( CRecordsetStatus& rStatus ) const;其中,参数rStatus是指向下列的CRecordsetStatus结构的对象:structCRecordsetStatuslongm_lCurrentRecord;/当前记录的索引,0表示第一个记录,/1表示第二个记录,依次类推。但-1表示在第一个记录之前,-2表示不确定。BOOLm_bRecordCountFinal;/记录总数是否是最终结果;8.2.3 字段操作字段操作例例Ex_Field 字段的编程操作字段的编程操作1) 为数据库为数据库Student.mdb添加一个数据表添加一个数据表c

700、ourse用MicrosoftAccess为数据库Student.mdb添加一个数据表course,如表8.5所示。表中上部分是数据表的记录内容,下部分是数据表的结构内容。表表8.5 课程信息表课程信息表(course)及其表结构及其表结构例Ex_Field2) 为文档应用程序添加为文档应用程序添加ODBC的支持的支持(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_Field,但在向导的第6步将CEx_FieldView的基类由默认的CView选择为CListView类。(6)将项目工作区窗口切换到FileView页面,展开HeaderFiles所有项,双击stdafx.h,

701、打开该文件。(3)在stdafx.h中添加ODBC数据库支持的头文件包含#include,如下面的代码:#ifndef_AFX_NO_AFXCMN_SUPPORT#include/MFCsupportforWindowsCommonControls#endif/_AFX_NO_AFXCMN_SUPPORT#include例Ex_Field3) 创建数据表创建数据表score的的CRecordSet派生类派生类(1)按快捷键Ctrl+W,打开MFCClassWizard对话框。单击AddClass按钮,从弹出的下拉菜单中选择“New”。(2)在弹出的“AddClass”对话框中指定CRecord

702、Set的派生类CCourseSet,结果如图8.23所示。图8.23 定义新的CRecordSet派生类3) 创建数据表创建数据表score的的CRecordSet派生类派生类(3)单击OK按钮,弹出“DatabaseOptions”对话框。从中选择ODBC的数据源“DatabaseExampleForVC+”,单击OK按钮,弹出“SelectDatabaseTables”对话框,从中选择要使用的表course。(4)单击OK按钮回到MFCClassWizard界面,单击确定按钮后,系统自动为用户生成CCourseSet类所需要的代码。(5)在CEx_FieldView:PreCreateWi

703、ndow函数中添加修改列表视图风格的代码:BOOLCEx_FieldView:PreCreateWindow(CREATESTRUCT&cs)cs.style&=LVS_TYPEMASK;cs.style|=LVS_REPORT;/报表方式returnCListView:PreCreateWindow(cs);3) 创建数据表创建数据表score的的CRecordSet派生类派生类(6)在CEx_FieldView:OnInitialUpdate函数中添加下列代码:voidCEx_FieldView:OnInitialUpdate()CListView:OnInitialUpdate();CL

704、istCtrl&m_ListCtrl=GetListCtrl();/获取内嵌在列表视图中的列表控件CCourseSetcSet;cSet.Open();/打开记录集CODBCFieldInfofield;/创建列表头for(UINTi=0;icSet.m_nFields;i+)cSet.GetODBCFieldInfo(i,field);m_ListCtrl.InsertColumn(i,field.m_strName,LVCFMT_LEFT,100);/添加列表项intnItem=0;CStringstr;while(!cSet.IsEOF()for(UINTi=0;im_pMainWnd;

705、/ 获得主框架窗口的指针CStatusBar*pStatus=&pFrame-m_wndStatusBar;/ 获得主框架窗口中的状态栏指针if(pStatus)CRecordsetStatusrStatus;pSet-GetStatus(rStatus);/获得当前记录信息str.Format(当前记录:%d总记录:%d,1+rStatus.m_lCurrentRecord,pSet-GetRecordCount();pStatus-SetPaneText(1,str);/更新第二个窗格的文本该函数先获得状态栏对象的指针,然后调用SetPaneText函数更新第二个窗格的文本。4) 在状态栏

706、中显示当前记录号和记录总数在状态栏中显示当前记录号和记录总数(3)在CEx_ODBCView的OnInitialUpdate函数处添加下列代码:voidCEx_FieldView:OnInitialUpdate()CStringstr;while(!cSet.IsEOF():DispRecNum(&cSet);cSet.Close();/关闭记录集4) 在状态栏中显示当前记录号和记录总数在状态栏中显示当前记录号和记录总数(4)在Ex_ODBCView.cpp文件的开始处增加下列语句:#includeEx_FieldDoc.h#includeEx_FieldView.h#includeCours

707、eSet.h#includeMainFrm.h(5)将MainFrm.h文件中的保护型变量m_wndStatusBar变成公共变量。(6)编译运行并测试,结果如图8.25所示。显示的记录信息图8.25Ex_Field最后运行结果8.2.4 多表处理多表处理 数据库中表与表之间往往存在着一定的关系,例如要显示一个学生的课程成绩信息,信息包括学号、姓名、课程号、课程所属专业、课程名称、课程类别、开课学期、课时数、学分、成绩,则要涉及到前面的学生课程成绩表(score)、课程表以及学生基本信息表。下面的示例在一个对话框中用两个控件来进行学生课程成绩信息的相关操作,如图8.26所示,左边是树视图,用来

708、显示学生成绩、专业和班级号三个层次信息,单击班级号,所有该班级的学生课程成绩信息将在右边的列表视图中显示出来。图8.26Ex_Student运行结果8.2.4 多表处理多表处理例例Ex_Student 多表处理多表处理1) 为数据库为数据库Student.mdb添加一个数据表添加一个数据表student用MicrosoftAccess为数据库Student.mdb添加一个数据表student,如表8.6所示。表中上部分是数据表的记录内容,下部分是数据表的结构内容。表表8.6 学生基本信息表学生基本信息表(student)及其表结构及其表结构例Ex_Student2) 创建并设计对话框应用程序创

709、建并设计对话框应用程序(1)用MFCAppWizard创建一个默认的基于对话框应用程序Ex_Student。(2)在打开的对话框资源模板中,删除取消按钮和默认的静态文本控件。(3)调整对话框大小,将对话框的标题文本改为“处理多表”,将确定按钮的标题文本改为“退出”。(4)参看图8.26的控件布局,向对话框中添加一个树控件,在其属性对话框中,选中“有按钮”、“有行(Lines,线)”、“Linesatroot”和“总是显示选择”属性。(5)向对话框中添加一个列表控件,在其属性对话框中,将“查看”属性选为“Report”。(6)用MFCClassWizard在CEx_StudentDlg类中,添加

710、树控件的控件变量为m_treeCtrl,添加列表控件的控件变量为m_listCtrl。3) 添加对添加对MFC ODBC的支持及记录集的支持及记录集在stdafx.h文件中添加ODBC数据库支持的头文件包含#include。用MFCClassWizard为数据表student、course和score分别创建CRecordSet派生类CStudentSet、CCourseSet和CScoreSet。例Ex_Student4) 完善左边树控件的代码完善左边树控件的代码(1)为CEx_StudentDlg类添加一个成员函数FindTreeItem,用来查找指定节点下是否有指定节点文本的子节点,该函

711、数的代码如下:HTREEITEMCEx_StudentDlg:FindTreeItem(HTREEITEMhParent,CStringstr)HTREEITEMhNext;CStringstrItem;hNext=m_treeCtrl.GetChildItem(hParent);while(hNext!=NULL)strItem=m_treeCtrl.GetItemText(hNext);if(strItem=str)returnhNext;elsehNext=m_treeCtrl.GetNextItem(hNext,TVGN_NEXT);returnNULL;4) 完善左边树控件的代码完善

712、左边树控件的代码(2)为CEx_StudentDlg类添加一个CImageList成员变量m_ImageList。(3)在CEx_StudentDlg:OnInitDialog中添加下列代码:BOOLCEx_StudentDlg:OnInitDialog()SetIcon(m_hIcon,FALSE);/Setsmalliconm_ImageList.Create(16,16,ILC_COLOR8|ILC_MASK,2,1);m_treeCtrl.SetImageList(&m_ImageList,TVSIL_NORMAL);SHFILEINFOfi;/定义一个文件信息结构变量SHGetFil

713、eInfo(C:Windows,0,&fi,sizeof(SHFILEINFO),SHGFI_ICON|SHGFI_SMALLICON);/获取文件夹图标m_ImageList.Add(fi.hIcon);SHGetFileInfo(C:Windows,0,&fi,sizeof(SHFILEINFO),SHGFI_ICON|SHGFI_SMALLICON|SHGFI_OPENICON); /获取打开文件夹图标m_ImageList.Add(fi.hIcon);HTREEITEMhRoot,hSpec,hClass;hRoot=m_treeCtrl.InsertItem(学生成绩,0,1);CS

714、tudentSetsSet;sSet.m_strSort=special;/按专业排序sSet.Open();while(!sSet.IsEOF()hSpec=FindTreeItem(hRoot,sSet.m_special);/查找是否有重复的专业节点if(hSpec=NULL)/若没有重复的专业节点hSpec=m_treeCtrl.InsertItem(sSet.m_special,0,1,hRoot);hClass=FindTreeItem(hSpec,sSet.m_studentno.Left(6);/查找是否有重复的班级节点if(hClass=NULL)/若没有重复的班级节点hCl

715、ass=m_treeCtrl.InsertItem(sSet.m_studentno.Left(6),0,1,hSpec);sSet.MoveNext();sSet.Close();returnTRUE;/returnTRUEunlessyousetthefocustoacontrol4) 完善左边树控件的代码完善左边树控件的代码(4)在Ex_StudentDlg.cpp文件的前面添加记录集类的包含文件,如下面的代码:#includeEx_StudentDlg.h#includeStudentSet.h#includeScoreSet.h#includeCourseSet.h(5)编译运行,结

716、果如图8.27所示。图8.27Ex_Student第一次运行结果例Ex_Student5) 完善右边列表控件的代码完善右边列表控件的代码(1)在CEx_StudentDlg:OnInitDialog函数中添加下列代码,用来创建列表标题头:BOOLCEx_StudentDlg:OnInitDialog()sSet.Close();/设置列表头CStringstrHeader=学号,姓名,课程号,课程所属专业,课程名称,课程类别,开课学期,课时数,学分,成绩;intnLong=80,80,80,180,180,80,80,80,80,80;for(intnCol=0;nColsizeof(strH

717、eader)/sizeof(CString);nCol+)m_listCtrl.InsertColumn(nCol,strHeadernCol,LVCFMT_LEFT,nLongnCol);returnTRUE;/returnTRUEunlessyousetthefocustoacontrol5) 完善右边列表控件的代码完善右边列表控件的代码(2)为CEx_StudentDlg类添加一个成员函数DispScoreAndCourseInfo,用来根据指定的条件在列表控件中用报表形式显示学生成绩的所有信息,该函数的代码如下:voidCEx_StudentDlg:DispScoreAndCourse

718、Info(CStringstrFilter)m_listCtrl.DeleteAllItems();/删除所有的列表项CScoreSetsSet;sSet.m_strFilter=strFilter;/设置过滤条件sSet.Open();/打开score表intnItem=0;CStringstr;while(!sSet.IsEOF()m_listCtrl.InsertItem(nItem,sSet.m_studentno);/插入学号/根据score表中的studentno(学号)获取student表中的姓名CStudentSetuSet;uSet.m_strFilter.Format(st

719、udentno=%s,sSet.m_studentno);uSet.Open();if(!uSet.IsEOF()m_listCtrl.SetItemText(nItem,1,uSet.m_studentname);uSet.Close();m_listCtrl.SetItemText(nItem,2,sSet.m_course);/根据score表中的course(课程号)获取course表中的课程信息CCourseSetcSet;cSet.m_strFilter.Format(courseno=%s,sSet.m_course);cSet.Open();UINTi=7;if(!cSet.I

720、sEOF()for(i=1;iitemNew.hItem;/获取当前选择的节点/如果当前的节点没有子节点,那说明该节点是班级号节点if(m_treeCtrl.GetChildItem(hSelItem)=NULL)CStringstrSelItem,str;strSelItem=m_treeCtrl.GetItemText(hSelItem);str.Format(studentnoLIKE%s%,strSelItem.Left(6);DispScoreAndCourseInfo(str);*pResult=0;6) 完善两控件的关联代码完善两控件的关联代码代码中,调用DispScoreAnd

721、CourseInfo函数是的参数是用来设置数据表(记录集)打开的过滤条件。str是类似的这样内容“studentnoLIKE210101%”,它使得所有学号前面是210101的记录被打开。%是SQL使用的通配符,由于%也是VisaulC+格式前导符,因为在代码中需要两个%。(2)编译运行并测试。8.3 ADO数据库编程数据库编程ADO最主要的优点是易于使用、速度快、内存开销小,它使用最少的网络流量,并且在前端和数据源之间使用最少的层数,它是一个轻量、高性能的接口。ADO实际上就是由一组Automation对象构成的组件,因此可以像使用其它任何Automation对象一样使用ADO。ADO中最重

722、要的对象有三个:Connection、Command和Recordset,它们分别表示“连接”对象、“命令”对象和“记录集”对象。8.3.1 ADO编程的一般过程编程的一般过程在MFC应用程序中使用ADO数据库的一般过程是:添加对ADO的支持;创建一个数据源连接;对数据源中的数据库进行操作;关闭数据源。这里先来介绍添加对ADO的支持以及数据源连接和关闭。1.添加对添加对ADO的支持的支持ADO编程有三种方式:使用预处理指令#import、使用MFC中的CIDispatchDriver和直接使用COM提供的API。这三种方式中,第一种最为简便,故这里采有用这种方法。下面以一个示例过程来说明在MF

723、C应用程序中添加对ADO的支持。1. 添加对添加对ADO的支持的支持例例Ex_ADO 添加对添加对ADO的支持的支持(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_ADO,但在向导的第6步将CEx_ADOView的基类由默认的CView选择为CListView类,以便更好地显示和操作数据表中的记录。(2)在CEx_ADOView:PreCreateWindow函数添加下列代码,用来设置列表视图内嵌列表控件的风格:BOOLCEx_ADOView:PreCreateWindow(CREATESTRUCT&cs)cs.style|=LVS_REPORT;/报表风格returnCLi

724、stView:PreCreateWindow(cs);例Ex_ADO(3)在stdafx.h文件中添加对ADO支持的代码:#endif/_AFX_NO_AFXCMN_SUPPORT#include/MFCsupportforWindowsCommonControls#importC:ProgramFilesCommonFilesSystemADOmsado15.dllno_namespacerename(EOF,adoEOF)#include/AFX_INSERT_LOCATION代码中,预编译命令#import是编译器将此命令中所指定的动态链接库文件引入到程序中,并从动态链接库文件中抽取出其

725、中的对象和类的信息。icrsint.h文件包含了VisualC+扩展的一些预处理指令、宏等的定义,用于与数据库数据绑定。例Ex_ADO(4)在CEx_ADOApp:InitInstance函数中添加下列代码,用来对ADO的COM环境进行初始化:BOOLCEx_ADOApp:InitInstance():CoInitialize(NULL);AfxEnableControlContainer();(5)在Ex_ADOView.h文件中为CEx_ADOView定义三个ADO对象指针变量:public:_ConnectionPtrm_pConnection;_RecordsetPtrm_pRecor

726、dset;_CommandPtrm_pCommand;代码中,_ConnectionPtr、_RecordsetPtr和_CommandPtr分别是ADO对象Connection、Recordset和Command的智能指针类型。8.3.1 ADO编程的一般过程编程的一般过程2. 连接数据源连接数据源只有建立了与数据库服务器的连接后,才能进行其他有关数据库的访问和操作。ADO使用Connection对象来建立与数据库服务器的连接,它相当于MFC中的CDatabase类。和CDatabase类一样,调用Connection对象的Open即可建立与服务器的连接。HRESULT Connection

727、:Open(_bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )其中,ConnectionString为连接字串,UserID是用户名,Password是登录密码,Options是选项,通常用于设置同步和异步等方式。_bstr_t是一个COM类,用于字符串BSTR(用于Automation的宽字符)操作。需要说明的是,正确设置ConnectionString是连接数据源的关键。不同的数据,其连接字串有所不同,见表8.7所示。表表8.7 Connection对象的连接字串格式对象的连接字串格式8.3.

728、1 ADO编程的一般过程编程的一般过程3. 关闭连接关闭连接用MFCClassWizard为CEx_ADOView映射WM_DESTROY消息,并添加下列代码:voidCEx_ADOView:OnDestroy()CListView:OnDestroy();if(m_pConnection)m_pConnection-Close();/关闭连接8.3.1 ADO编程的一般过程编程的一般过程4. 获取数据源信息获取数据源信息Connection对象除了建立与数据库服务器的连接外,还可以通过OpenSchema来获取数据源的自有信息,如:数据表信息、表字段信息以及所支持的数据类型等。下面的代码用来

729、获取student.mdb的数据表名和字段名,并将信息内容显示在列表视图中:voidCEx_ADOView:OnInitialUpdate()CListView:OnInitialUpdate();m_pConnection.CreateInstance(_uuidof(Connection); /初始化Connection指针m_pRecordset.CreateInstance(_uuidof(Recordset);/初始化Recordset指针m_pCommand.CreateInstance(_uuidof(Command);/初始化Recordset指针/连接数据源为Database

730、ExampleForVC+m_pConnection-ConnectionString=DSN=DatabaseExampleForVC+;m_pConnection-ConnectionTimeout=30;/允许连接超时时间,单位为秒HRESULThr=m_pConnection-Open(,0);if(hr!=S_OK)MessageBox(无法连接指定的数据库!);/获取数据表名和字段名_RecordsetPtrpRstSchema=NULL;/定义一个记录集指针pRstSchema=m_pConnection-OpenSchema(adSchemaColumns);/获取表信息/将表

731、信息显示在列表视图控件中CListCtrl&m_ListCtrl=GetListCtrl();CStringstrHeader3=序号,TABLE_NAME,COLUMN_NAME;for(inti=0;iadoEOF)str.Format(%d,nItem+1);m_ListCtrl.InsertItem(nItem,str);for(inti=1;iFields-GetItem(_bstr_t)(LPCSTR)strHeaderi)-Value;m_ListCtrl.SetItemText(nItem,i,value);pRstSchema-MoveNext();nItem+;pRstSc

732、hema-Close();4. 获取数据源信息获取数据源信息代码中,_uuidof用来获取对象的的全局唯一标识(GUID)。ConnectionTimeout是连接超时属性,单位为秒。OpenSchema方法中的adSchemaColumns是一个预定义的枚举常量,用来获取与“列”(字段)相关的信息记录集。该信息记录集的主要字段名有“TABLE_NAME”、“COLUMN_NAME”;类似的,若在OpenSchema方法中指定adSchemaTables枚举常量,则返回的记录集的字段名主要有“TABLE_NAME”、“TABLE_TYPE”。上述代码运行结果如图8.29所示。图8.29获取数据

733、源表信息8.3.2 Recordset对象使用对象使用Recordset是用来从数据表或某一个SQL命令执行后获得记录集,通过Recordset对象的AddNew、Update和Delete方法可实现记录的添加、修改和删除等操作。1. 读取数据表全部记录内容读取数据表全部记录内容下面的过程是将student.mdb中的course表中的记录显示在列表视图中。(1)打开菜单资源IDR_MAINFRAME,在顶层菜单“查看”下添加一个“显示Course表记录”子菜单,将其ID号设为ID_VIEW_COURSE。(2)按快捷键Ctrl+W,弹出ClassWizard对话框,向CEx_ADOView类

734、添加ID_VIEW_COURSE的COMMAND消息映射,保留默认的映射函数OnViewCourse,并在该函数中添加下列代码:voidCEx_ADOView:OnViewCourse()CListCtrl&m_ListCtrl=GetListCtrl();/删除列表中所有行和列表头m_ListCtrl.DeleteAllItems();intnColumnCount=m_ListCtrl.GetHeaderCtrl()-GetItemCount();for(inti=0;iOpen(Course,/指定要打开的表m_pConnection.GetInterfacePtr(),/获取当前数据库

735、连接的接口指针adOpenDynamic,/动态游标类型,可以使用Move等操作adLockOptimistic,adCmdTable);/建立列表控件的列表头FieldsPtrflds=m_pRecordset-GetFields();/获取当前表的字段指针_variant_tIndex;Index.vt=VT_I2;m_ListCtrl.InsertColumn(0,序号,LVCFMT_LEFT,60);for(i=0;iGetCount();i+)Index.iVal=i;m_ListCtrl.InsertColumn(i+1,(LPSTR)flds-GetItem(Index)-Get

736、Name(),LVCFMT_LEFT,140);/显示记录_bstr_tstr,value;intnItem=0;CStringstrItem;while(!m_pRecordset-adoEOF)strItem.Format(%d,nItem+1);m_ListCtrl.InsertItem(nItem,strItem);for(i=0;iGetCount();i+)Index.iVal=i;str=flds-GetItem(Index)-GetName();value=m_pRecordset-GetCollect(str);m_ListCtrl.SetItemText(nItem,i+1

737、,(LPCSTR)value);m_pRecordset-MoveNext();nItem+;m_pRecordset-Close();代码中,_variant_t是一个用于COM的VARIANT类,VARIANT类型是一个C结构,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。Index.iVal=i;m_ListCtrl.InsertColumn(i+1,(LPSTR)flds-GetItem(Index)-GetName(),LVCFMT_LEFT,140);/显示记录_bstr_tstr,value;intnItem=0;CStringstrIte

738、m;while(!m_pRecordset-adoEOF)strItem.Format(%d,nItem+1);m_ListCtrl.InsertItem(nItem,strItem);for(i=0;iGetCount();i+)Index.iVal=i;str=flds-GetItem(Index)-GetName();value=m_pRecordset-GetCollect(str);m_ListCtrl.SetItemText(nItem,i+1,(LPCSTR)value);m_pRecordset-MoveNext();nItem+;m_pRecordset-Close();1.

739、 读取数据表全部记录内容读取数据表全部记录内容(3)编译运行并测试,结果如图8.30所示。图8.30显示Course表所有记录8.3.2 Recordset对象使用对象使用2. 添加、修改和删除记录添加、修改和删除记录记录的添加、修改和删除是通过Recordset对象的AddNew、Update和Delete方法来实现的。例如,向course表中新添加一个记录可有下列代码:/打开记录集m_pRecordset-AddNew();/添加新记录m_pRecordset-PutCollect(courseno,_variant_t(2112111);m_pRecordset-PutCollect(c

740、oursehourse,_variant_t(60);m_pRecordset-Update();/使添加有效/关闭记录集2. 添加、修改和删除记录添加、修改和删除记录若从course表中删除一个记录可有下列代码:/打开记录集m_pRecordset-Delete(adAffectCurrent); /删除当前行m_pRecordset-MoveFirst();/调用Move方法,使删除有效/关闭记录集若从course表中修改一个记录可有下列代码:/打开记录集m_pRecordset-PutCollect(courseno,_variant_t(2112111);m_pRecordset-Pu

741、tCollect(coursehourse,_variant_t(60);m_pRecordset-Update();/使修改有效/关闭记录集特别强调的是,数据库的表名不能与特别强调的是,数据库的表名不能与ADO的某些关键字串同名,例如:的某些关键字串同名,例如:user等。等。另外,通常用Command对象执行SQL命令来实现数据表记录的查询、添加、更新和删除等操作,而用Recordset对象获取记录集,用来显示记录内容。8.3.3 Command对象使用对象使用Command对象用来执行SQL命令。下面先来简单介绍SQL几个常用语句。1. SELECT语句语句一个典型的SQL查询可以从指定

742、的数据库表中“选择”信息,这时就需要使用SELECT语句来执行。SELECT语句格式如下:SELECT 字段名 FROM 表名WHERE子句ORDER BY子句它的最简单形式是:SELECT * FROM tableName其中,星号(*)用来指定从数据库的tableName表中选择所有的字段(列)。若要从表中选择指定字段的记录,则将星号(*)用字段列表来代替,多个字段之间用逗号分隔。1. SELECT语句语句1) WHERE子句子句在数据表查询SELECT语句中,经常还需要使用WHERE子句来设定查询的条件。它的一般形式如下:SELECT column1,column2, FROM tabl

743、eName WHERE conditionWHERE子句中的条件可以(大于)、=(大于等于)、=(等于)、(不等于)和LIKE等运算符。其中,LIKE用于匹配条件的查询,它可以使用“%”和“_(下划线)”等通配符,“%”表示可以出现0个或多个字符,“_”表示该位置处只能出现1个字符。例如:SELECT*FROMScoreWHEREstudentnoLIKE 21%则将Score表中所有学号以21打头的记录查询出来。注意,LIKE后面的字符串是以单引号来标识。再如:SELECT*FROMScoreWHEREstudentnoLIKE210105_则将Score表中所有学号以210105打头的,且

744、学号为8位的记录查询出来。WHERE子句中的条件还可用AND(与)、OR(或)以及NOT(非)运算符来构造复合条件查询,例如:若查询Score表中成绩(score)在70分到80分之间的记录,则可有下列语句:SELECT*FROMScoreWHEREscore=701. SELECT语句语句2) ORDER BY子句子句在数据表查询SELECT语句中,若将查询到的记录进行排序,则可使用ORDERBY子句。如下面的形式:SELECT column1,column2, FROM tableName WHERE conditionORDER BY col1,co2, ASC | DESC其中,ASC

745、表示升序(从低到高),DESC表示降序(从高到低),col1、col2、分别用来指定是按什么字段来排序。当指定多个字段时,则先按col1排序,当有相同col1的记录时,则相同的记录按col2排序,依此类推。8.3.3 Command对象使用对象使用2. INSERT语句语句INSERT语句是用来向表中插入一个新的记录。该语句的常用形式是:INSERT INTOtableName(col1,col2,col3,colN)VALUES(val1,val2,val3,valN)其中,tableName用来指定插入新记录的数据表,tableName后跟一对圆括号,包含一个以逗号分隔的列(字段)名的列表

746、,VALUES后面的圆括号内是一个以逗号分隔的值列表,它与tableName后面的列名列表是一一对应的。需要说明的是,若某个记录的某个字段值是字符串,则需要用单引号来括起来。例如:INSERT INTO Student(studentno,studentname)VALUES (21010503,张小峰)将在Student中插入一个新行,其中studentno(学号)为“21010503”,studentname(学生姓名)为“张小峰”,对于该记录的其它字段值,由于没有指定相应的值,其结果由系统决定。8.3.3 Command对象使用对象使用3. UPDATE语句语句UPDATE语句用于更新表

747、中的数据。该语句的常用形式是: UPDATE tableName SET column1=value1,column2=value2,columnN=valueN WHERE condition该语句可以更新tableName表中一行记录或多行记录的数据,这取决于WHERE后面的条件。关键字SET后面是以逗号分隔的“列名/值”列表。例如: UPDATE Student SET studentname=王鹏 WHERE studentno=21010503将学号为“21010503”的记录中的studentname字段内容更新为“王鹏”。4. DELETE语句语句DELETE语句用来从表中删除记

748、录,其常用形式如下: DELETE FROM tableName WHERE condition该语句可以删除tableNam表中一行记录或多行记录,这取决于WHERE后面的条件。8.4 数据库相关的数据库相关的ActiveX控件控件在前面的数据库处理中,一次只能显示出一行记录,且修改或添加等操作不能“可视化”地进行。为了弥补MFC的这种不足,在VisualC+6.0中允许用户使用一些ActiveX控件用来更好地操作数据库,这些控件包括MSFlexGrid、RemoteData、DBGrid等。8.4.1使用使用MSFlexGrid控件控件MicrosoftFlexGrid(MSFlexGri

749、d)控件可以显示网格数据,也可以对其进行操作。它提供了高度灵活的网格排序、合并和格式设置功能,网格中可以包含字符串和图片。利用MSFlexGrid可以将某个表的所有记录显示。下面以示例的形式来说明其使用过程。8.4.1 使用使用MSFlexGrid控件控件例例Ex_Grid 使用使用RemoteData和和DBGrid控件控件1. 将控件的类添加到项目中将控件的类添加到项目中(1)用MFCAppWizard创建一个单文档应用程序Ex_Grid。(2)在向导的第2步对话框中,选中“数据库查看使用文件支持”项,单击DataSource按钮,弹出“DatabaseOptions”对话框,从中选择OD

750、BC的数据源“DatabaseExampleForVC+”。保留其他默认选项,单击OK按钮,在弹出的“SelectDatabaseTables”对话框中,选择要使用的表score。(3)单击OK按钮,又回到了向导的第2步对话框。单击完成按钮。开发环境自动打开表单视图CEx_GridView的对话框资源模板IDD_EX_GRID_FORM。1. 将控件的类添加到项目中将控件的类添加到项目中(4)在打开的表单资源模板中右击鼠标,从弹出的快捷菜单中选择“InsertActiveControl”命令,出现如图8.31所示“插入Active控件”对话框。(5)在对话框的控件列表中选择MicorsoftF

751、lexGridControl控件,单击确定按钮,该控件就添加到表单资源中,参看图8.33,调整其大小和位置。图8.31 “插入ActiveX控件”对话框例例Ex_Grid2. 修改修改MSFlexGrid控件属性控件属性右击添加的MicorsoftFlexGridControl控件,从弹出的菜单中选择“属性”或“属性MSFlexGridObject”命令均可打开该控件的属性对话框,如图8.32所示。MSFlexGrid控件的属性要比VisualC+的控件属性要多,如“General”、“通用”、“样式”、“字体”、“颜色”、“图片”等。这些属性不仅能设置控件的字体、颜色,而且能设置网格的行数和

752、列数以及其他的功能。例例Ex_Grid3. 编程控制编程控制(1)保留默认的属性及其控件标识符IDC_MSFLEXGRID1。(2)用MFCClassWizard在CEx_GridView类中为刚才添加的MSFlexGrid控件设置一个相关联的控件变量m_MSFGrid。需要说明的是,在此步骤中会出现一些对话框,用于询问是否要添加相关控件的类代码等,选择是。(3)在CEx_ODBCView类的OnInitialUpdate函数中添加下列代码:voidCEx_GridView:OnInitialUpdate()while(!m_pSet-IsEOF() m_pSet-MoveNext();m_p

753、Set-MoveFirst();m_MSFGrid.SetCols(m_pSet-m_nFields+1); /根据字段数设置单元格最大列数m_MSFGrid.SetRows(m_pSet-GetRecordCount()+1);/根据记录数设置单元格最 大行数m_MSFGrid.SetColWidth(-1,1440);/将所有的单元格都设为相同的列宽。-1表示所有的列,列宽单位为一个点的/1/20(一个点是1/72英寸),也就是说,1440刚好为1英寸。/定义单元格的表头m_MSFGrid.SetRow(0);m_MSFGrid.SetCol(0);/定位到(0,0)单元格m_MSFGri

754、d.SetText(记录号);/设置其显示内容m_MSFGrid.SetCellAlignment(4);/设置单元格对齐方式,4表示水平和垂直居中CODBCFieldInfofield;for(UINTi=0;im_nFields;i+)m_MSFGrid.SetRow(0);m_MSFGrid.SetCol(i+1);m_pSet-GetODBCFieldInfo(i,field);/获取指定字段信息m_MSFGrid.SetText(field.m_strName);m_MSFGrid.SetCellAlignment(4);intiRow=1;while(!m_pSet-IsEOF()

755、/将表的记录内容显示在单元格中CStringstr;str.Format(记录%d,iRow);m_MSFGrid.SetRow(iRow);m_MSFGrid.SetCol(0);m_MSFGrid.SetText(str);m_MSFGrid.SetCellAlignment(4);for(UINTi=0;im_nFields;i+)m_MSFGrid.SetRow(iRow); m_MSFGrid.SetCol(i+1);m_pSet-GetFieldValue(i,str);/获取指定字段值,并自动转换成字符串m_MSFGrid.SetText(str);m_MSFGrid.SetCe

756、llAlignment(4);iRow+;m_pSet-MoveNext();m_MSFGrid.SetRow(1);m_MSFGrid.SetCol(1);m_pSet-MoveFirst();3. 编程控制编程控制4.编译运行,结果如图8.33所示。图8.33MSFlexGrid控件的结果8.4.2 RemoteData和和DBGrid控件控件MSFlexGrid控件提供界面友好的网格,使表的记录内容能全部地显示出来,但却没有表处理的常用功能,如添加记录、修改记录和删除记录等。而DBGrid控件不仅能自动全部显示表的记录内容,而且常见的记录操作也都能很好地支持。需要注意的是,DBGrid控

757、件还必须用RemoteData控件来提供数据源,但它最大的好处是不需要任何程序代码就能实现表的处理。通过被绑定的控件提供对存储在远程ODBC数据源中数据的存取。RemoteData控件允许在某一记录集的行与行之间移动,且允许显示和操作来自于被绑定的控件各行里的数据。RemoteData控件在远程数据对象(RDO)和数据识别的被绑定的控件之间提供了接口。通过RemoteData控件,能够:建立起与基于其本身属性的数据源的连接。创建RDO的结果集。把当前行的数据传送给相应被绑定的控件。允许对当前行指针进行定位。将对被绑定的控件所做的任何更改反传给数据源。8.4.2 RemoteData和和DBGr

758、id控件控件例例Ex_DBCtrl 使用使用RemoteData和和DBGrid控件控件(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_DBCtrl,但在向导的第6步将CEx_DBCtrlView的基类由默认的CView选择为CFormView类。(2)在打开的表单资源模板中右击鼠标,从弹出的快捷菜单中选择“InsertActiveControl”命令,出现“插入Active控件”对话框。(3)在对话框的控件列表中选择MicrosoftRemoteDataControl,单击确定按钮,RemoteData控件就添加到表单资源中,调整其大小和位置。(4)右击该控件,从弹出的菜单

759、中选择“属性”或“PropertiesRemoteDataCtlObject”命令,打开该控件的属性对话框(参看图8.34)。(5)在“Control(控件)”页面中,从“DataSource”的下拉列表中选择所需要的数据源名“DatabaseExampleForVC+”。例例Ex_DBCtrl(6)在“SQL”编辑框中键入SQL操作语句“SELECT*FROMscoreORDERBYstudentno”是检索学生课程成绩表score的所有记录,并按学号排序。设置的结果如图8.34所示。(7)将RemoteData控件属性对话框切换到All页面,单击CursorDriver选项,在右侧的组合框

760、中将其属性选择“1-ODBCcursor”。结果如图8.35所示。图8.34设置RemoteData控件的Control属性图8.35设置CursorDriver属性例例Ex_DBCtrl(8)再次右击表单资源模板,从弹出的快捷菜单中选择“InsertActiveControl”命令,在弹出的“插入Active控件”对话框中找到要添加的DBGrid控件,单击确定按钮。(9)参看图8.38,调整添加的DBGrid控件的大小和位置,打开该控件的属性对话框,将数据源(DataRource)设置为RemoteData控件IDC_REMOTEDATACTL1,如图8.36所示。图8.36 设置DBGrid控件的数据源例例Ex_DBCtrl(10)在对话框编辑器的控件布局栏上,单击测试工具按钮(),结果如图8.37所示。按ESC键结束测试。(11)编译运行并测试,结果如图8.38。图8.37DBGrid控件测试结果图8.38Ex_DBCtrl运行结果

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 建筑/环境 > 施工组织

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号