• 10.45 MB
  • 2022-04-22 11:37:35 发布

Visual C++ 实用教程 第3版 (郑阿奇 著) 电子工业出版社 课后答案

  • 781页
  • 当前文档由用户上传发布,收益归属用户
  1. 1、本文档共5页,可阅读全部内容。
  2. 2、本文档内容版权归属内容提供方,所产生的收益全部归内容提供方所有。如果您对本文有版权争议,可选择认领,认领后既往收益都归您。
  3. 3、本文档由用户上传,本站不保证质量和数量令人满意,可能有诸多瑕疵,付费之前,请仔细先通过免费阅读内容等途径辨别内容交易风险。如存在严重挂羊头卖狗肉之情形,可联系本站下载客服投诉处理。
  4. 文档侵权举报电话:19940600175。
'课后答案网您最真诚的朋友www.hackshp.cn网团队竭诚为学生服务,免费提供各门课后答案,不用积分,甚至不用注册,旨在为广大学生提供自主学习的平台!课后答案网:www.hackshp.cn视频教程网:www.efanjy.comPPT课件网:www.ppthouse.com 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!第1章基本C++语言C++是在20世纪80年代初期由贝尔实验室设计的一种在C语言的基础上增加了对面向对象程序设计支持的语言,它是目前应用最为广泛的编程语言。本章先来说明C++程序结构,然后详细讨论数据类型、运算符与表达式、基本语句、函数和预处理、构造类型、指针和引用等内容。需要说明的是,在学习本章之前最好先课后答案网做实验1。1.1C++程序结构同其他程序设计语言一样,www.hackshp.cnC++也有自己的程序结构。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.1.1几个C++程序下面先来介绍几个比较简单的C++程序。[例Ex_Simple1]一个简单的C++程序#include"stdafx.h"/*C++程序的基本结构*/#includevoidmain()课后答案网{doubler,area;//声明变量cout<<"输入圆的半径:www.hackshp.cn";//显示提示信息cin>>r;//从键盘上输入变量r的值area=3.14159*r*r;//计算面积cout<<"圆的面积为:"<voidDoDraw(intnum);//声明一个全局函数voidmain(){intnum=5;//课后答案网定义并初始化变量DoDraw(num);//函数的调用}voidDoDraw(intnum)www.hackshp.cn//函数的定义{for(inti=0;iclassCDrawArray//定义一个类{public:voidDoDraw(intnum);//声明类的公有成员函数};课后答案网voidCDrawArray::DoDraw(intnum)//成员函数的实现{for(inti=0;i#definePI3.14159课后答案网voidmain(){doubler=100.0,area;www.hackshp.cnarea=PI*r*r;cout<<"圆的面积是:"<constdoublePI=3.14159;voidmain(){doubler=100.0,area;课后答案网area=PI*r*r;cout<<"圆的面积是:"<”,表示括号中的内容是必需指定,若为方括号“[]”,则括号中的内容是可选的,本书作此约定):课后答案网<数据类型><变量名表>;变量名和数据类型是告诉编译器要为其分配多少内存空间,以及变量中要存取的是什么类型的数据。例如:intnNum1;www.hackshp.cnintnNum2;intnNum3;doublex;这样,nNum1、nNum2、nNum3分别占用4个字节的存储空间,其存取的数据类型是int型,称之为“整型变量”,而x则占用8个字节的存储空间,存取的数据类型是double型,称之为“双精度实型变量”。有时,为使代码简洁,还可以将同类型的变量定义在一行语句中,不过同类型的变量名要用逗号(,)分隔。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.2.3变量2.变量的初始化程序中常需要对一些变量预先设置初值,即将初值存储在变量名所代表的内存空间,这一过程称为初始化。在C++中,变量初始化是在定义变量时同时赋初值。例如:课后答案网intnNum1=3;//指定nNum1为整型变量,初值为3doublex=1.28;//指定x为双精度实变量,初值为1.28charwww.hackshp.cnc=‘G’;//指定c为字符变量,初值为‘G’也可以在多个变量的定义语句中单独对某个变量初始化,如:intnNum1,nNum2=3,nNum3;变量的初始化还有另外一种形式,例如:intnX(1),nY(3);表示nX和nY是整型变量,它们的初值分别为1和3。。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.2.4基本输入、输出1.输出流(cout)通过cout可以输出一个整数、实数、字符及字符串,cout中的插入符“<<”可以连续写多个,每个后面可以跟一个要输出的常量、变量、转义序列符、对象以及表达式等。[例Ex_CoutEndl]cout课后答案网的输出算子endl#includevoidmain(){www.hackshp.cncout<<"ABCDt"<<1234<<"t"<voidmain(){intnNum=1234;doublefNum课后答案网=12.3456789;cout<<"1234567890"<><表达式1>[>><表达式2>...]其中,提取符“>>”可以连续写多个,每个后面跟一个表达式,该表达式通常是获得输入值的变量或对象。例如:课后答案网intnNum1,nNum2,nNum3;cin>>nNum1>>nNum2>>nNum3;要求用户从键盘上输入三个整数。输入时,必须在3个数值之间加上一些空格来分隔,空格的个数不限,最后用回车键结束输入www.hackshp.cn(书中出现的“”表示输入一个回车键,特此约定);或者在每个数值之后按回车键。例如,上述输入语句执行时,用户可以输入:12920或12920 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.2.4基本输入、输出3.格式算子格式算子oct、dec和hex能分别将输入或输出的数值转换成oct、dec和hex八进制、十进制及十六进制,例如:[例Ex_Algorism]格式算子的使用#includevoidmain(){课后答案网intnNum;cout<<"PleaseinputaHexinteger:";cin>>hex>>nNum;cout<<"Octt"<voidmain(){www.hackshp.cnshortnTotal,nNum1,nNum2;nNum1=nNum2=1000;nTotal=nNum1*nNum2;cout<>=它们都是在赋值符“=”之前加上其它运算符而构成的,其中的算术复合赋值运算符的含义如表1.3所示。课后答案网表1.3复合赋值运算符www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.2赋值运算符1.复合赋值复合运算符的优先级和赋值符的优先级一样,在C++的所有运算符中只高于逗号运算符,而且复合赋值运算符的结合性也是从右至课后答案网左的,所以在组成复杂的表达式时要特别小心。例如:www.hackshp.cna*=b-4/c+d;等效于a=a*(b-4/c+d);而不等效于a=a*b-4/c+d; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.2赋值运算符2.多重赋值所谓多重赋值是指在一个赋值表达式中出现两个或更多的赋值符(“=”),例如:nNum1=nNum2=nNum3=100;由于赋值符的结合性是从右至左的,因此上述的赋值是这样的过程:首先对赋值表达式nNum3=100求值,即将100赋值给nNum3,同时该赋值表达式取得值100;然后将该值赋给课后答案网nNum2,这是第二个赋值表达式,该赋值表达式也取得值100;最后将100赋给nNum1。由于赋值是一个表达式,所以它几乎可以出现在程序的任何地方,例如:a=7+(b=8)www.hackshp.cn(赋值表达式值为15,a值为15,b值为8)a=(c=7)+(b=8)(赋值表达式值为15,a值为15,c值为7,b值为8) 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.3数据类型转换在进行运算时,往往要遇到混合数据类型的运算问题。例如一个整型数和一个实数相加就是一个混合数据类型的运算。C++采用两种方法对数据类型进行转换,一种是“自动转换”,另一种是“强制转换”。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.3数据类型转换自动转换自动转换是将数据类型从低到高的顺序进行转换,如图1.2所示。课后答案网www.hackshp.cn图1.2类型转换的顺序例如:10+‘a’+2*1.25-5.0/4L的运算次序如下:(1)进行2*1.25的运算,将2和1.25都转换成double型,结果为double型的2.5。(2)进行5.0/4L的运算,将长整型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。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.3数据类型转换2.强制转换强制转换是在程序中通过指定数据类型来改变图1.2所示的类型转换顺序,将一个变量从其定义的类型改变为另一种新的类型。强制类型有下列两种格式:(<类型名>)<表达式><类型名>(<表达式>)这里的“类型名”是任何合法的课后答案网C++数据类型,例如float、int等。通过类型的强制转换可以将“表达式”转换成适当的类型,例如:doublef=3.56;intnNum;www.hackshp.cnnNum=(int)f;或者nNum=int(f);都是将使nNum的值变为3。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.4关系运算符关系运算是逻辑运算中比较简单的一种。所谓“关系运算”实际上是比较两个操作数是否符合给定的条件。若符合条件,则关系表达式的值为“真”,否则为“假”。在C++编译系统中,往往将“真”表示为“true”或1,将“假”表示为“false”或0。而任课后答案网何不为0的数被认为是“真”,0被认为是“假”。由于关系运算需要两个操作数,所以关系运算符都是双目运算符。C++提供了下列www.hackshp.cn6种关系运算符:<(小于),<=(小于等于),>(大于),>=(大于等于),==(相等于),!=(不等于)其中,前4种的优先级相同且高于后面的两种。例如:a==b>c等效于a==(b>c)但关系运算符的优先级低于算术运算符(其他可参见表1.4)。例如:a=b3&&2||8<4-!0表达式的运算过程是这样的:(1)进行“!0”的运算,结果为1(“真”)。(2)进行“4-1”运算,结果为3,这样表达式变成“5>3&&2||8<3”。(3)处理“5>3”,结果为“真”,这里用1表示。(4)处理“8<3”,结果为“假”,这里用0表示。这样表达式变成“1&&2||0”(5)进行“1&&2”的运算,结果为1(“真”),因为2是不等于0的数。(6)最后结果为1(“真”)。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.6位运算符位运算符是对操作数按其在计算机内表示的二制数逐位地进行逻辑运算或移位运算,参与运算的操作数只能是整型常量或变量。C++语言提供了六种位运算符:~(按位求反,单目运算符)<<(左移,双目运算符)>>(右移,双目运算符课后答案网)&(按位与,双目运算符)^(按位异或,双目运算符)|(按位或,双目运算符)“按位求反”是将一个二进制数的每一位求反,即www.hackshp.cn0变成1,1变成0。“按位与”是将两个操作数对应的每个二进制位分别进行逻辑与操作。“按位或”是将两个操作数对应的每个二进制位分别进行逻辑或操作。“按位异或”是将两个操作数对应的每个二进制位分别进行异或操作。“左移”是将左操作数的二进制值向左移动指定的位数,它具有下列格式:操作数<<移位的位数 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.7三目运算符C++中惟一的三目运算符是条件运算符,其格式如下:<条件表达式>?<表达式1>:<表达式2>“条件表达式”是C++中可以产生“真”和“假”结果的任何表达式,如果条件课后答案网表达式的结果为“真”,则执行表达式1,否则执行表达式2。例如:nNum=(a>b)?10:8;注意,只有在表达式www.hackshp.cn2后面才能出现分号结束符,“表达式1”和“表达式2”中都不能有分号。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.8自增和自减运算符单目运算符自增(++)和自减(--)为整型变量加1或减1提供一种非常有效的方法。++和--既可放在变量的左边也可以出现在变量的右边,分别称为前缀运算符和后缀运算符。例如:i++;或++i;(等效于i=i+1;或i+=1;)i--;或--i;(等效于i=i-1;或i-=1;)这是要特别注意:课后答案网若前缀运算符和后缀运算符仅用于某个变量的增1和减1,则这两都是等价的,但如果将这两个运算符和其他的运算符组合在一起,在求值次序上就会产生根本的不同:www.hackshp.cn如果用前缀运算符对一个变量增1(减1),在将该变量增1(减1)后,用新的值在表达式中进行其他的运算。如果用后缀运算符对一个变量增1(减1),用该变量的原值在表达式进行其他的运算后,再将该变量增1(减1)。例如:a=5;b=++a-1;//相当于a=a+1;b=a–1;和a=5;b=a++-1;//相当于b=a–1;a=a+1; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.8自增和自减运算符虽然它们中的a值的结果都是6,但b的结果却不一样,前者为5,后者为4。在自增和自减混合运算时,一要注意次序,二要注意变量是表示相应的存储空间这个特性。例如:b=a++*--a*a++;//相当于a=a–1;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=a–1;a=a–1;a=a–1;b=a*a*a;www.hackshp.cn显然执行该语句后的结果为a=2,b=8。而VisualC++先计算--a*--a,即其相当于:a=a–1;a=a–1;b=a*a;此时a=3,b=9;然后计算9*--a,即其相当于:a=a–1;b=9*a;结果a=2,b=18。无论是何种处理方式,可千万不要认为是b=4*3*2,那是完全错误的,因为变量a是表示一个相应的存储空间,在同一运行周期中,其存储的数值不应有两种可能。再比如,若a的初值为5,当有:b=a++*--a*--a;//执行该语句后,TC++或BC++:a=4,b=27。VC++:a=4,b=48。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.9逗号运算符逗号运算符是优先级最低的运算符,它可以使多个表达式放在一行上,从而大大简化了程序。在计算时,C++将从左至右逐个计算每个表达式,最终整个表达式的结果是最后计算的那个表达式的类型和值。例如:j=(i=12,i+8);式中,i=12,i+8是含逗号运算符的表达式,计算次序是先计算表达式课后答案网i=12,然后再计算i+8,整个表达式的值是最后一个表达式的值,即i+8的值20,从而j的结果是20。再如:www.hackshp.cnd=(a=1,b=a+2;c=b+3);d的结果为6。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.10sizeof运算符sizeof的目的是返回操作数所占的内存空间大小(字节数),它具有下列两种格式:sizeof(<表达式>)sizeof(<数据类型>)例如:sizeof(“Hello”)//计算字符串常量“课后答案网Hello”所占内存的字节大小,结果为6sizeof(int)//计算整型int所占内存的字节数需要说明的是,由于同一类型的操作数在不同的计算机中占用的存储字节数可能不同,因此sizeofwww.hackshp.cn的结果有可能不一样。例如sizeof(int)的值可能是4,也可能是2。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.3.11优先级、结合性和运算次序课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.4.1表达式语句、空语句和复合语句[例Ex_Block]块语句的变量使用范围。#includevoidmain(){inti=5,j=6;课后答案网cout<)<语句1>[else<语句2>]这里的if、else是C++的关键字。注意,课后答案网if后的一对圆括号不能省。当“表达式”为“真”(true)或不为0时,将执行语句1。当“表达式”为“假”(false或0)时,语句2被执www.hackshp.cn行。其中,else可省略,即变成这样的简单的if语句:if(<表达式>)<语句>当“表达式”为“真”(true)或不为0时,语句被执行。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.4.2选择结构语句[例Ex_Compare]输入两个整数,比较两者的大小#includevoidmain(){intnNum1,nNum2;cout<<"Pleaseinputtwointegernumbers:";课后答案网cin>>nNum1>>nNum2;if(nNum1!=nNum2)if(nNum1>nNum2)www.hackshp.cncout<"<nNum2。当然,表达式的类型也可以是任意的数值类型(包括整型、实型、字符型等)。例如:if(3)cout>>"Thisisanumber3";执行结果是输出"Thisisanumber3";因为3是一个不为0的数,条件总为“真”。(2)适当添加花括号(“{}”)来增加程序的可读性。例如:上面例Ex_Compare中的条件语句还可写成下列形式,其结果是一样的。课后答案网if(nNum1!=nNum2){if(nNum1>nNum2)cout<"<nNum2)cout<"<){case<常量表达式课后答案网1>:[语句1]case<常量表达式2>:[语句2]...case<常量表达式n>:[语句n][defaultwww.hackshp.cn:语句n+1]}其中switch、case、default都是关键字,当表达式的值与case中某个表达式的值相等时,就执行该case中“:”号后面的所有语句。若case中所有表达式的值都不等于表达式的值,则执行default:后面的语句,若default省略,则跳出switch结构。需要注意的是:switch后面的表达式可以是整型、字符型或枚举型的表达式,而case后面的常量表达式的类型必须与其匹配。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.4.2选择结构语句[例Ex_Switch]根据成绩的等级输出相应的分数段#includevoidmain(){charchGrade;cout<<"Pleaseinputachar(A~E):";cin>>chGrade;switch(chGrade)课后答案网{case"A":case"a":cout<<"90--100"<)<语句>while是关键字,<语句>是此循环的循环体,它可以是一条语句,也可以是多条语句。当为多条语句时,一定要用花括号课后答案网(“{}”)括起来,使之成为复合语句,如果不加花括号,则while的范围只到while后面第一条语句。当表达式为非0(“真”)时便开始执行while循环体中的语句,然后反复执行,每次执行都会判断表达式是否为非0,若等于www.hackshp.cn0(“假”),则终止循环。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.4.3循环结构语句[例Ex_SumWhile]求整数1到50的和#includevoidmain(){intnNum=1,nTotal=0;while(nNum<=50)课后答案网{nTotal+=nNum;nNum++;www.hackshp.cn}cout<<"Thesum,from1to50,is:"<while(<表达式>)其中do和while都是C++关键字,<语句>是此循环的循环体,它可以是一条语句,也可以是复合语句。当语句执行到while时,将判断表达式是否为非0值,若是,则继续执行循环体,直到下一次表达式等于0课后答案网为止。例如Ex_SumWhile用do...while循环语句可改写成:[例Ex_SumDoWhile]求整数1到50的和#includevoidmain()www.hackshp.cn{intnNum=1,nTotal=0;do{nTotal+=nNum;nNum++;}while(nNum<=50);cout<<"Thesum,from1to50,is:"<其中for是关键字,<语句>是此循环的循环体,它可以是一条语句,也可以是复合语句。一般情况下,[表达式1]用作循环变量的初始化,课后答案网[表达式2]是循环体的判断条件,当等于非0(true)时,开始执行循环体,然后计算[表达式3],再判断表达式2的值是否为非0,若是,再执行循环体,再计算表达式3,如此反复,直到表达式2等于0(false)为止。其流程如图1.3所示。www.hackshp.cn图1.3for循环流程图 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.4.3循环结构语句例如,Ex_SumWhile用for循环语句可改写成:[例Ex_SumFor]求整数1到50的和#includevoidmain(){intnTotal=0;课后答案网for(intnNum=1;nNum<=50;nNum++)nTotal+=nNum;cout<<"Thesum,from1to50,is:"<voidmain(){www.hackshp.cnfor(intnNum=1;nNum<=100;nNum++){if(nNum%7==0)continue;cout<<函数名>(<形式参数表>){<若干语句>课后答案网函数体}可以看出,一个函数的定义是由函数名、函数类型、形式参数表和函数体四个部分组成的。函数类型决定了函数所需要的返回值类型,它可以是函数或数组之外www.hackshp.cn的任何有效的C++数据类型,包括构造的数据类型、指针等。如果不需要函数有返回值,只要定义函数的类型为void即可。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.5.1函数的定义和调用2.函数的声明声明一个函数可按下列格式进行:<函数类型><函数名>(<形式参数表>);其中,形参的变量名可以省略。但要注意,函数声课后答案网明的内容应和函数的定义应相同。例如对于www.hackshp.cnsum()函数的声明如下:intsum(intx,inty);和intsum(int,int);是等价的。但末尾的分号“;”不要忘记。需要说明的是,函数的声明又可称为对函数的原型进行说明。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.5.1函数的定义和调用3.函数的调用函数调用的一般形式为:<函数名>(<实际参数表>);所谓“实际参数”(简称“实参”),它与“形参”相对应,是实际调用函数时所给定的常量、变量或表达式,且必须有确定的值。例如:inta[5]={7,9,6,3,4};课后答案网sum(a[0],6);或sum(a[0]*a[1],a[2]+a[3]);www.hackshp.cn等都是合法的调用。需要注意的是:实参与形参的个数应相等,类型应一致,且按顺序对应,一一传递数据。C++中,调用一个函数的方式可以有很多,例如: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。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.5.2函数的参数传递[例Ex_SwapValue]交换函数两个参数的值。#includevoidswap(floatx,floaty){floattemp;temp=x;x=y;y=temp;课后答案网cout<<"x="<>a;if(a<0)www.hackshp.cn{a=-a;intb;//b的作用域起始处…}//b的作用域终止处}//a的作用域终止处代码中,声明的局部变量a和b处在不同的块中。其中变量a是在fun函数的函数体块中,因此在函数体这个范围内,该变量是可见的。而b是在if语句块中声明的,故它的作用域是从声明处开始到if语句结束处终止。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.5.3作用域和存储类型1.作用域(2)函数原型作用域。例如:doublemax(doublex,doubley);课后答案网和doublemax(double,double);www.hackshp.cn是等价的。不过,从程序的可读性考虑,在声明函数原型时,为每一个形参指定有意义的标识符,并且和函数定义时的参数名相同,是一个非常好的习惯。(3)函数作用域。(4)文件作用域。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.5.3作用域和存储类型2.变量的存储类型这些存储类型的声明是按下列格式进行的:<存储类型><数据类型><变量名表>;(1)自动类型(auto)。一般说来,用自动存储类型声明的变量都是限制在某个程序范围内使用的,即为局部变量。从系统角度来说,自动存储类型变量是采用动态分配方式来分配内存空间的。因此,当程序执行到超出该变量的作用域时,就释放它所占用的内存空间,其值也随之消失了。课后答案网在C++语言中,声明一个自动存储类型的变量是在变量类型前面加上关键字auto,例如:autointi;若自动存储类型的变量是在函数内或语句块中www.hackshp.cn声明的,则可省略关键字auto,例如:inti;(2)静态类型(static)。静态类型变量也是一种局部变量。它和自动存储类型的变量的最大不同之处在于:静态类型变量在内存中是以固定地址存放的,它采用静态分配方式来分配内存空间的。在这种方式下,只要程序还在继续执行,静态类型变量的值就一直有效,不会随它所在的函数或语句块的结束而消失。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.5.3作用域和存储类型在C++语言中,声明一个静态类型的变量是在变量类型前面加上关键字static。例如:[例Ex_Static]使用静态类型的局部变量#includevoidcount(){inti=0;staticintj=0;//静态类型课后答案网cout<<”i=”<课后答案网voiddisplay(inta,intb=2,intc=3)//在函数的定义中设置默认参数{cout<<"a="<0时 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.5.5函数的递归调用#includelongfactorial(intn);voidmain(){课后答案网cout<inlinefloatfmax(floatx,floaty){returnx>y?x:y;}voidmain(){课后答案网floata,b;cout<<"请输入两个实数:";cin>>a>>b;www.hackshp.cncout<<"最大的数为:"<intsum(intx,inty);intsum(intx,inty,intz);doublesum(doublex,doubley);课后答案网doublesum(doublex,doubley,doublez);voidmain(){cout<(参数名表)字符串在字符串中包括了括号内的参数,称为形参,以后在程序中这些形参将被实参替换。例如例如:#defineMAX(a,b)((a)>(b)?(a):(b))课后答案网其中(a,b)是宏MAX的参数表,如果在程序出现下列语句:x=MAX(3,9);则预处理后变成:www.hackshp.cnx=((3)>(9)?(3):(9));//结果为9很显然,带参数的宏相当于一个函数的功能,但却比函数简捷。需要注意的是:定义有参宏时,宏名与左圆括号之间不能留有空格。否则,编译器将空格以后的所有字符均作为替代字符串,而将该宏视为无参数的宏定义。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.5.8预处理3.文件包含命令所谓“文件包含”是指将另一个源文件的内容合并到源程序中。C++语言提供了#include命令用来实现文件包含的操作,它有下列两种格式:课后答案网#include<文件名>#include“www.hackshp.cn文件名”在使用#include命令需要注意的是,一条#include命令只能包含一个文件,若想包含多个文件须用多条文件包含命令。例如:#include#include... 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.5.8预处理4.条件编译命令一般情况下,源程序中所有的语句都参加编译,但有时也希望根据一定的条件去编译源文件的不同部分,这就是“条件编译”。条件编译使得同一源程序在不同的编译条件下得到不同的目标代码。C++提供的条件编译命令有几种常用的形式,现分别介绍如下:课后答案网第一种形式#ifdef<标识符><程序段www.hackshp.cn1>[#else<程序段2>]#endif其中,#ifdef、#else和#endif都是关键字,<程序段>是由若干条预处理命令或语句组成的。这种形式的含义是:如果标识符已被#define命令定义过,则编译<程序段1>,否则编译<程序段2>。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.5.8预处理[例Ex_UseIfdef]使用#ifdef条件编译命令#include#defineLIvoidmain(){#ifdefLIcout<<"Hello,LI!n";课后答案网#elsecout<<"Hello,everyone!n";#endif}www.hackshp.cn运行结果:(2)第二种形式#ifndef<标识符><程序段1>[#else<程序段2>]#endif这与前一种形式的区别仅在于,如果标识符没有被#define命令定义过,则编译<程序段1>,否则就编译<程序段2>。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.5.8预处理[例Ex_UseIfdef]使用#ifdef条件编译命令(3)第三种形式#if<表达式1><程序段1>[#elif<表达式2><程序段2>课后答案网...][#else<程序段www.hackshp.cnn>]#endif其中,#if、#elif、#else和#endif是关键字。它的含义是,如果<表达式1>为“真”就编译<程序段1>,否则如果<表达式2>为“真”就编译<程序段2>,...,如果各表达式都不为“真”就编译<程序段n>。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6构造类型1.6.1数组数组是相同类型的元素的有序集合,每个元素在数组中的位置可用统一的数组名和下标来惟一确定。1.数组的定义定义一个数组可按下列格式进行:<数据类型><数组名>[<常量表达式1>][<常量表达式2>]...<数组名>后面的[<常量表达式课后答案网1>][<常量表达式2>]...用于确定数组的维数和大小。如:inta[10];它表示数组名为a,一维数组,此数组有10个元素,每个元素的类型都是整型。又如:floatb[2][3];charwww.hackshp.cnc[4][5][6];其中,b是实型的二维数组,它有2x3个元素,每个元素的类型都是实型。c是字符型的三维数组,它有4x5x6个元素,每个元素的类型都是字符型。一般地,表示某维大小的常量表达式中不能包含变量,但可以包括常量和符号常量,其值必须是一个确定的整型数值,且数值大于1。例如:inta[4-2][3*6];constintSIZE=18;intb[SIZE];都是合法的数组定义。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.1数组2.数组元素的引用数组定义后,就可以引用数组中的元素,引用时按下列格式:<数组名>[<下标>]...例如a[0]、b[5]等,这里的0和5是数组的下标,a和b是定义过的数组名。需要注意的是:(1)C++数组的下标总是从0开始的,也就是说,a[0]是数组a的第一个元素;但下标一定要小于数组定义时的大小。也就是说,长度为课后答案网n的数组,其下标范围为0~(n-1)。例如,inta[5];它的数组元素下标应从0到4,而没有a[5]这个数组元素。(2)下标可以是整型常量或整型表达式,且引用的元素下标个数应与数组定义的维数一致。例如:www.hackshp.cnintd[2][3][4];是三维数组,d[0][2][3],d[0][1][2]都是合法的元素,但d[1][2]或d[1][3][]等都是不合法的数组元素的引用。(3)数组定义后,系统会根据数组的大小开辟相应的内存,并依照下标的高低依次存放数组中的各个元素。例如一维数组a[5]的存放次序是:a[0],a[1],a[2],a[3],a[4]。(4)由于每一个数组元素都可以看成是一个与数组类型相同的变量,因此在程序中对某个或所有数组元素进行赋值或其他处理时,它的操作与变量类似。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.1数组3.一维数组的初始化和赋值数组中的元素既可以在数组定义的同时赋初值,即初始化,也可以在定义后赋值。例如:inta[5]={1,2,3,4,5};是将整数1,2,3,4,5分别赋于数组a的各个元素,注意要将这些初值用花括号“{}”括课后答案网起来。它与下列的赋值语句的结果是相同的:a[0]=1;a[1]=2;a[2]=3;a[3]=4;a[4]=5;当然,也可以给其中的一部分元素赋值。例如:www.hackshp.cnintb[5]={1,2};是将数组b的元素b[0],b[1]分别赋予1,2的值。需要说明的是,在对数组进行初始化中,若没有明确列举元素值的元素,则其值均为0。有时,在对全部数组元素赋初值时,可以不指定数组的长度;例如intc[]={1,2,3,4,5};系统将据数值的个数自动定义c数组的长度,这里是5。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.1数组4.二维数组的初始化和赋值二维数组可以看成一个具有行和列的数据表,例如intb[3][4];它在内存空间的存放次序是:b[0][0],b[0][1],b[0][2],b[0][3],//第0行b[1][0],b[1][1],b[1][2],b[1][3],//第1行b[2][0],b[2][1],课后答案网b[2][2],b[2][3]。//第2行可见,在数组b[3][4]中,3表示行数,4表示列数。因此,在进行二维数组进行初始化时可以采用以“行”为单位来进行。例如:intd[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};其中,最外面的一对花括号里面的www.hackshp.cn{1,2,3,4}是对第0行元素进行初始化,{5,6,7,8}是对第1行元素进行初始化,{9,10,11,12}是对第2行元素进行初始化。每对花括号里的数据个数均不能大于列数。它等价于intd[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};//依次对元素进行初始化 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.1数组5.排序数组作为一种常见的数据结构,在许多方面都得到了应用。“排序”就是一个常用的应用实例。例如:[例Ex_ArraySort]把5个整型数按从小到大的次序排列#include#defineN5voidmain(){inta[N]={20,40,-50,7,13};课后答案网intmin,k;for(inti=0;ia[j]){min=a[j];k=j;}}a[k]=a[i];a[i]=min;}for(i=0;i#defineN5voidmain(){inta[N]={20,40,-50,7,13};for(inti=1;ia[j+1]){inttemp;temp=a[j];a[j]=a[j+1];a[j+1]=temp;}}}for(i=0;i#includevoidchange(charch[5]);课后答案网voidmain(){charname[5]="Ding";cout<;<成员定义2>;...<成员定义n>;}[结构变量名表]; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.3结构体结构体定义是以关键字struct开始的,[结构体名]应是一个有效的标识符,若该结构体变量以后不再定义,结构体名也可不指定。结构体中的每个成员都必须通过“成员定义”来确定成员名及其类型。例如:structPERSON{intage;课后答案网//年龄charsex;//性别floatweight;//体重charname[25];//姓名}family_member;www.hackshp.cn其中,PERSON是结构体名,该结构有4个成员变量。family_member是跟随结构体一起定义的结构体变量。当然,也可以在结构体定义后再定义结构体变量。例如:structPERSONsister;//struct关键字可以省略PERSONbrother;PERSONyou,me,he;或PERSONpersons[10];//定义一个结构体数组都是合法的结构体变量的定义。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.3结构体需要注意:(1)在定义结构体时,不要忘记最后一个花括号的结尾后面的分号“;”。(2)结构体的成员变量类型既可以是基本数据类型,也可以是其他合法的类型。相同类型的成员变量也可用一行语句来定义,但定义的变量之间要用逗号隔开。例如:课后答案网structSTUDENT{PERSONone;//用已定义的结构体类型声明成员floateng,phy,mathwww.hackshp.cn,poli;//英语、物理、数学和政治的成绩}; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.3结构体2.结构体变量的初始化结构体变量的初始化的一般形式是在变量后面加上:={<初值列表>};例如:structPOINT{intx;课后答案网inty;}spot={20,40};//依次使spot中的x为20,y为40或POINTpt2={100};www.hackshp.cn//只是使pt2中的x为100都是合法的初始化形式。若当结构体中的本身又是一个结构体变量时,其初始化可用“{}”来增加其可读性。例如:structRECT{POINTptLeftTop;intnWidth;intnHeight;}rc={{10,20},40,50}; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.3结构体3.结构体变量的引用(1)当一个结构体变量定义之后,就可引用这个变量。使用时,遵循下列规则:只能引用结构体变量中的成员变量,并使用下列格式:<结构体变量名>.<成员变量名>例如:cout<structPERSON{intage;//年龄floatweight;//体重charname[25];//姓名};课后答案网voidprint(PERSONone){cout<structPERSON{intage;//年龄floatweight;//体重charname[25];//姓名};课后答案网voidprint(PERSONone){cout<>temp.name>>temp.age>>temp.weight;returntemp;}voidmain(){PERSONone=getperson();print(one);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.4传递结构体参数[例Ex_StructReturn]将结构体的值作为参数传给函数运行结果为:由于getperson函数返回一个结构体的值,因此需要先将用户输入的数据保课后答案网存到临时结构体变量temp中,然后返回temp的值。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.5共用体定义一个共用体可用下列格式:union<共用体名>{<成员定义1>;<成员定义2>;...<成员定义n>;课后答案网}[共用体变量名表];//注意最后的分号不要忘记。例如:unionNumericTypewww.hackshp.cn{intiValue;//整型变量,4个字节长longlValue;//长整型变量,4个字节长floatfValue;//实型,8个字节长};这时,系统为NumericType开辟了8个字节的内存空间,因为成员fValue是实型,所占空间最大。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.6枚举类型枚举也是一种构造类型,它是一系列的有标识名的整型常量的集合,其主要功能是增加程序代码的可读性。它的格式如下:enum<枚举类型名>{<枚举常量表>}[枚举变量];//注意最后的分号不要忘记。enum是关键字,枚举常量表中的枚举常量名之间要用逗号分隔,例如:enumDays{Sun,Mon,Tue,Wed,Thu,Fri,Sat}today;课后答案网其中Days是定义的一个枚举类型名,它有七个枚举常量(又称枚举值、枚举元素)。默认时,系统为每一个枚举常量都对应一个整数,并从0开始,逐个增1,也就是说枚举常量Sun等于0,Mon等于1,Tue等于2等等。这些默认的值也可重新指定,例如:www.hackshp.cnenumColors{Black,Blue,Green=4,Cyan,Red=8,Yellow,White};则各枚举常量对应的整数依次为0,1,4,5,8,9,10。上述定义中,today是定义的枚举类型Days的变量,也可以用下列格式来定义。例如:enumDaystoday,yesterday;或Daystomorrow;//省略enum关键字 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.6枚举类型枚举变量最终的值只能等于该枚举类型中的某个枚举常量,而不能用一个整型数值直接赋值。例如:today=Mon;//合法,值为1tomorrow=today;//合法,值为1inti=today;课后答案网//合法,值为1yesterday=3;//不合法,不能直接赋值需要注意的是,不要在定义枚举类型的同时,再对枚举常量、枚举变量及枚举类型名重新定义。例如下列的定义是不合法的:www.hackshp.cninttomorrow;intYellow;intSun; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.6.7用typedef定义类型使用typedef可以将已有的数据类型名用新的类型名(别名)来代替,它具有下列格式:typedef<已有的数据类型名><类型别名>;例如:typedeffloatFLOAT;typedefcharCH10[10];这里,FLOAT表示float类型,CH10表示具有10个元素的字符数组类型。这样在以后的代码中,就可以使用这些类型名定义新的变量,如:课后答案网FLOATx,y;CH10a,b;//a和b都是具有10个元素的字符数组。它们等价于floatx,y;www.hackshp.cncharx[10],y[10];typedef几乎可以对所有的数据类型进行定义,但却不能用来定义变量;而且与struct、union、enum等相比,它不能构造出新的数据类型。下面的一些示例可帮助理解:typedefunsignedlongULONG;ULONGul;//等价于"unsignedlongul;"typedefstructmystructtag{…}MYSTRUCT;MYSTRUCTms;//等价于"structmystructtagms;" 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.1指针和指针变量inti=5;int*p=&i;C++中定义一个指针变量可按下列格式:<数据类型>*<指针变量名1>[,*<指针变量名2>,...];int*pInt1,*pInt2;//pInt1,pInt2是指向整型变量的指针float*pFloat;//pFloat课后答案网是一个指向实型变量的指针char*pChar;//pChar是一个指向字符型变量的指针,它通常用来处理字符串需要说明的是,绝大多数情况下,都可以将指针变量简称为“指针”。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.2&和*运算符C++中有两个专门用于指针的运算符:&(取地址运算符)、*(取值运算符)运算符“&”只能对变量操作,作用是取该变量的地址。运算符“*”用是取指针或地址所指内存单元中存储的内容。例如:inta=3;//课后答案网整型变量,初值为3int*p=&a;//指向整型变量的指针,其值等于a的地址intb=*p;//将指针所指的地址中的内容赋值给b,值为3。上述赋值是在指针变量定义时进行的;当然,也可以在程序中进行赋值。例如:inta=3;www.hackshp.cn//整型变量,初值为3int*pi;//指向整型变量的指针pi=p;//将指针p的地址赋给指针pi,使得它们都是指向a的指针,//它等价于pi=&a;注意在pi前没有*。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.3指针运算除了前面的赋值运算外,指针还有算术运算和关系运算。1.指针的算术运算在实际应用中,指针的算术运算主要是对指针加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如,若有:int*ptr;指针变量ptr加上整数n课后答案网后,即ptr=ptr+n。编译器会把指针ptr的值加上sizeof(int)*n,在32位机器中,sizeof(int)等于4。由于地址是以字节为单位的,故ptr所指向的存储单元向高地址方向移动了sizeof(int)*n字节。这里的int是指针变量ptr的数据类型,若定义成float型,则ptr=ptr+n是使ptr向高地址方向移动了sizeof(floatwww.hackshp.cn)*n字节。因此,<指针变量>=<指针变量>+n它是使指针变量所指向的存储单元向高地址方向移动了sizeof(指针变量类型)*n个字节。类似的:<指针变量>=<指针变量>-n它是使指针变量所指向的存储单元向低地址方向移动了sizeof(指针变量类型)*n个字节。当n为1时,指针变量的上述加减运算就是指针变量的自增(++)、自减(--)运算。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.3指针运算2.指针的关系运算两个指针变量的关系运算是根据两个指针变量值的大小来进行比较。在实际应用中,通常是比较两个指针反映地址的前后关系或判断指针变量的值是否为0。例如:[例Ex_PointerOp]将字符数组a中的n个字符按相反顺序存放#includevoidmain(){chara[]="Chinese";课后答案网char*p1=a,*p2=a,temp;while(*p2!="")p2++;p2--;//将p2指向a的最后一个元素while(p1voidmain(){inta[6]={1,2,3,4,5,6};int*p=a;课后答案网//用数组名a给指针初始化intsum=0;for(inti=0;i<6;i++){www.hackshp.cnsum+=*p;p++;}cout<voidmain(){inta[]={5,8,7,6,2,7,3};课后答案网inty,*p=&a[1];y=(*--p)++;cout<voidmain(){inta[3][3]={1,2,3,4,5,6,7,8,9};inty=0;课后答案网for(inti=0;i<3;i++)for(intj=0;j<3;j++)y+=(*(a+i))[j];www.hackshp.cncout<#includestructPERSON{intage;//年龄charsex;//性别floatweight;课后答案网//体重charname[25];//姓名};voidmain(){www.hackshp.cnstructPERSONone;structPERSON*p;//指向PERSON类型的指针变量p=&one;p->age=32;p->sex=’M’;p->weight=(float)80.2;strcpy(p->name,"LiMing");cout<<”姓名:”<<(*p).name<”称为指向运算符,它的左边必须是一个指针变量,它等效于指针变量所指向的结构体类型变量,如课后答案网p->name和(*p).name是等价的,都是引用结构PERSON类型变量one中的成员name,由于成员运算符“.”优先于“*”运算符,所以(*p).name中的*p两侧括号不能省,否则*p.name与*(p.name)等价,但这里的*(p.name)是错误的。www.hackshp.cn若将结构体变量看成一个整体,那么指向结构体变量数组的指针操作和指向数组的指针操作是一样的。例如若有:PERSONmany[10],*pp;pp=many;//等价于pp=&many[0];则pp+i与many+i是等价的,(pp+i)->name与many[i].name是等价的,等等。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.6多级指针如果指针变量中存储的是另一个指针变量的地址,或指向指针类型的指针变量,称为多级指针。[例Ex_MultiPointer]使用多级指针#includevoidmain(){课后答案网intnum=4;int*pnum=#int**ppnum=&pnum;cout<<**ppnum<voidswap(int*x,int*y);voidmain(){inta=7,b=11;课后答案网swap(&a,&b);cout<<“a=”<*<函数名>(<形式参数表>){<函数体>}它与一般函数定义基本相同,只不过在函数名前面增加了一个“*”号,用来指明函课后答案网数返回的是一个指针,该指针所指向的数据类型由函数类型决定。例如:www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.7指针和函数[例Ex_PointerRreturn]返回指针的函数:用来将一个字符串逆序输出#includechar*flip(char*str){char*p1,*p2,ch;p1=p2=str;while(*p2!="")p2++;课后答案网p2--;while(p1(*<指针名>)(<参数表>);例如:课后答案网int(*func)(chara,charb);就是定义的一个函数指针。int为函数的返回类型,*表示后面的func是一个指针变量名。该函数具有两个字符型参数www.hackshp.cna和b。需要说明的是,由于()的优先级大于*,所以下面是返回指针的函数定义而不是函数指针定义:int*func(chara,charb);一旦定义了函数指针变量,就可以给它赋值。由于函数名表示该函数的入口地址,因此可以将函数名赋给指向函数的指针变量。但一般来说,赋给函数指针变量的函数的返回值类型与参数个数、顺序要和函数指针变量相同。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!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;www.hackshp.cn//错误,两个指针指向的函数不一致fp2=fn3(5);//错误,函数赋给函数指针时,不能加括号函数指针变量赋值后,就可以使用指针来调用函数了。调用函数的格式如下:(*<指针名>)(<实数表>);或<指针名>(<实数表>);例如:(*fp2)(5);或fp2(5); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.7指针和函数[例Ex_FuncPointer1]函数指针的使用#includedoubleadd(doublex,doubley){return(x+y);}doublemul(doublex,doubley){return(x*y);}voidmain()课后答案网{double(*func)(double,double);//定义一个函数指针变量doublea,b;www.hackshp.cncharop;cout<<"输入两个实数及操作方式,"+"表示"加","*"表示乘:";cin>>a>>b>>op;if(op=="+")func=add;//将函数名赋给指针elsefunc=mul;cout<doubleadd(doublex,doubley){return(x+y);}doublemul(doublex,doubley){return(x*y);}课后答案网voidop(double(*func)(double,double),doublex,doubley){cout<<"x="<voidadd(doublex,doubley){cout<>op;www.hackshp.cnswitch(op){case"+":func[0](x,y);break;case"-":func[1](x,y);break;case"*":func[2](x,y);break;case"/":func[3](x,y);break;case"0":return;}}while(1);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.8带参数的主函数main()到目前为止,我们所接触到的main()函数都是不带参数的。但在实际应用中,程序有时需要从命令行输入参数。例如:c:>copyfile1file2这是一个常用的DOS命令。当它运行时,操作系统将命令行参数以字符串的形式传递给main()。为了能使程序处理这些参数,需要main()带有参数,其最常用的格式是:课后答案网数据类型main(intargc,char*argv[])其中,第一个int型参数用来存放命令行参数的个数,实际上argc所存放的数值比命令行参数的个数多www.hackshp.cn1,即将命令字(或称为可执行文件名,如copy)也计算在内。第二个参数argv是一个一维的指针数组,用来存放命令行中各个参数和命令字的字符串,且规定:argv[0]存放命令字argv[1]存放命令行中第一个参数argv[2]存放命令行中第二个参数argv[3]存放命令行中第三个参数…这里,argc的值和argv[]各元素的值都是系统自动赋值的。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.8带参数的主函数main()[例Ex_Main]处理命令行参数#includevoidmain(intargc,char*argv[]){cout<<"这个程序的程序名是:"<Ex_MainabcdEF运行结果为: 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.9new和delete在C++中,使用运算符new和delete能有效地、直接地进行动态内存的分配和释放。运算符new返回指定类型的一个指针,如果分配失败(如没有足够的内存空间)时则返回0。例如:double*p;p=newdouble;课后答案网*p=30.4;//将值存在在开辟的单元中系统自动根据double类型的空间大小开辟一个内存单元,并将地址放在指针p中。当然,也可在开辟内存单元时,对单元里的值进行初始化。例如上述代码可www.hackshp.cn写成:double*p;p=newdouble(30.4);运算符delete操作是释放new请求到的内存。例如:deletep;它的作用是将p指针的内存单元释放,指针变量p仍然有效,它可以重新指向另一个内存单元。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.9new和delete需要注意的是:(1)new和delete须配对使用。也就是说,用new为指针分配内存,当使用结束之后,一定要用delete来释放已分配的内存空间。(2)运算符delete必须用于先前new分配的有效指针。如果使用了未定义的其它任何类型的指针,就会带来严重问题,如系统崩溃等。(3)new可以为数组分配内存,但当释放时,也可告诉课后答案网delete数组有多少个元素。例如:int*p;p=newint[10];www.hackshp.cn//分配整型数组的内存,数组中有10元素if(!p){cout<<”内存分配失败!”;exit(1);//中断程序执行}for(inti=0;i<10;i++)p[i]=i;//给数组赋值...delete[10]p;//告诉delete数组有多少个元素,或delete[]p; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.10引用C++中提供了一个与指针密切相关的特殊数据类型——“引用”。引用是一个变量的别名,定义引用类型变量,实质上是给一个已定义的变量起一个别名,系统不会为引用类型变量分配内存空间,只是使引用类型变量与其相关联的变量使用同一个内存空间。定义引用类型变量的一般格式为:课后答案网<数据类型>&<引用名>=<变量名>或<数据类型>&(<变量名>)其中,变量名必须是一个已定义过的变量。例如:inta=3;int&ra=a;这样,ra就是一个引用,它是变量a的别名。所有对这个引用ra的操作,实质上就是对被引用对象a的操作。例如:ra=ra+2;实质上是a加2,a的结果为5。但是如果给引用赋一个新值,结果会怎样的呢? 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.10引用[例Ex_Reference]给引用重新赋值#includevoidmain(){inta;int&ra=a;a=5;课后答案网cout<<"a="<voidswap(int&x,int&y);voidmain(){课后答案网inta(7),b(11);swap(a,b);cout<<“a=”<doublearea;double&CalArea(doubler){area=3.141593*r*r;returnarea;课后答案网}voidmain(){doublec=CalArea(5.0);www.hackshp.cndouble&d=CalArea(10.0);cout<data用来指向结点p的数据域,(*p).next或p->next用来指向结点p的下一个结点。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.12简单链表2.链表的输出由于链表中的各个结点是由指针链接在一起的,因此只要知道链表的头指针(即head),那么就可以定义一个指针p,先指向第一个结点,输出p所指向的结点数据,然后根据结点p找到下一个结点,再输出,直到链表的最后一个结点(指针为空)。程序如下:voidOutputList(NODE*head){课后答案网NODE*current=head;while(current!=NULL){www.hackshp.cncout<data<<"t";current=current->next;}cout<<"n";} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.12简单链表3.链表的插入如果要在链表中的结点a之前插入新结点b,则需要考虑下列几种情况:(1)插入前链表是一个空表,这时插入新结点b后,链表如图1.7(a)所示,实线表示插入前的指针,虚线为插入后的指针(下同)。(2)若a是链表的第一结点,则插入后,结点b为第一个结点,如图1.7(b)所示。(3)若链表中存在a,且不是第一个结点,则首先要找出a的上一个结点ak,令ak的指针域指向b,令b的指针域指向a,即可完成插入,如图课后答案网1.7(c)所示。(4)若链表中不存在a,则先找到链表的最后一个结点an,并令an的指针域指向结点b,而b结点的指针域为空。如图1.7(d)所示。www.hackshp.cnheada...headbb(a)(b)...aka......anbb(c)(d)图1.7链表的插入 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.12简单链表程序如下(设aData是结点a中的数据,bData是结点b中的数据):voidInsertList(NODE**head,intaData,intbData){NODE*p,*a,*b;b=(NODE*)new(NODE);//分配一个新结点bb->data=bData;p=*head;if(p==NULL)//若链表是空,符合情况(1){*head=b;//将b作为第一个结点b->next=NULL;课后答案网}elseif(p->data==aData){//若a是第一个结点,符合情况(2)b->next=p;*head=b;}elsewww.hackshp.cn{while(p->data!=aData&&p->next!=NULL){//查找结点aa=p;p=p->next;}if(p->data==aData){//有结点a,符合情况(3)a->next=b;b->next=p;}else{//没有结点a,符合情况(4)p->next=b;b->next=NULL;}}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!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不存在,则不作任何改变。www.hackshp.cnheadaa1...(a)...akaak+1...(b)图1.8链表的删除 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.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;}else{www.hackshp.cnwhile(p->data!=aData&&p->next!=NULL){//查找结点aa=p;p=p->next;}if(p->data==aData){//有结点a,,符合情况(2)a->next=p->next;deletep;}}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.12简单链表以上的OutputList、InsertList和DeleteList是操作链表的三个函数,使用它们可形成完整的程序:[例Ex_SimpleList]使用简单链表#includestructNODE{intdata;NODE*next;};NODE*head=NULL;课后答案网intdata[6]={25,41,17,98,5,67};voidInsertList(NODE**head,intaData,intbData);voidDeleteList(NODE**head,intaData);voidOutputList(NODE*head);www.hackshp.cnvoidmain(){for(inti=0;i<6;i++)InsertList(&head,data[0],data[i]);OutputList(head);DeleteList(&head,98);DeleteList(&head,41);OutputList(head);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.7.12简单链表[例Ex_SimpleList]使用简单链表结果如下:代码中,若将在指定结点之前插入结点的函数InsertList改为在链表最后添加结点,设函数名为AppendList课后答案网,则这样的函数应如何实现?www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!第2章C++面向对象程序设计在传统的结构化程序设计方法中,数据和处理数据的程序是分离的。当对某段程序进行修改或删除时,整个程序中所有与其相关的部分都要进行相应的修改,从而程序代码的维护比较困难。为了避免这种情况的发生,C++引用了面向对象的设计方法,它是将数据及处理数据的相应函数“封装”到一个“类”中,类的实例称为“对象”。在一个对象内,只有属于该对象的函数课后答案网才可以存取该对象的数据。这样,其他函数就不会无意中破坏它的内容,从而达到保护和隐藏数据的效果。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.1类的定义类的定义一般地分为声明部分和实现部分。声明部分是用来声明该类中的成员,包含数据成员(或称“成员变量”)的声明和成员函数的声明。成员函数是用来对数据成员进行操作的,又称为“方法”。实现部分是用来对成员函数的定义。概括说来,声明部分将告诉使用者“干什么”,而实现部分是告诉使用者“怎么干”。课后答案网C++中定义类的一般格式如下:class<类名>{private:www.hackshp.cn[<私有数据和函数>]public:[<公有数据和函数>]};<各个成员函数的实现>其中,class是定义类的关键字,class的后面是用户定义的类名,通常用大写的C字母开始的标识符作为类名,C用来表示类(Class),以与对象、函数及其他数据类型相区别。类中的数据和函数是类的成员,分别称为数据成员和成员函数。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.1类的定义定义类时还应注意:(1)类中的数据成员的数据类型可以是任意的,但不允许对所定义的数据成员进行初始化,例如类CMeter中,下面的定义是错误的:classCMeter{...private:intm_nPos=10;//课后答案网错误...};(2)在“public:”或“private:”后面定义的所有成员都是公有或私有的,直到下一个“public:”或www.hackshp.cn“private:”出现为止。若成员前面没有类似“public:”或“private:”,则所定义的成员是private(私有),这是类的默认设置。(3)关键字public和private可以在类中出现多次,且前后的顺序没有关系;但最好先声明公有成员,后声明私有成员,因为public成员是用户最关心的。(4)除了public和private外,关键字protected(保护)也可修饰成员的类型,它与private两者基本相似,但在类的继承时有所不同(后面还会讲到)。(5)数据成员的类型可以是任意的,包含整型、浮点型、字符型、数组、指针和引用等,也可以是另一个类的对象。(6)尽量将类单独存放在一个文件中或将类的声明放在.h文件中而将成员函数的实现放在与.h文件同名的.cpp文件中。以后将会看到,VisualC++6.0为用户创建的应用程序框架中都是将各个类以.h和同名的.cpp文件组织的。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.2对象的定义一个类定义后,就可以定义该类的对象,如下面的格式:<类名><对象名表>其中,类名是用户已定义过的类的标识符,对象名可以有一个或多个,多个时要用逗号分隔。被定义的对象既可以是一个普通对象,也可以是一个数组对象或指针对象。例如:CMetermyMeter,*Meter,Meters[2];课后答案网这时,myMeter是类CMeter的一个普通对象,Meter和Meters分别是该类的一个指针对象和对象数组。一个对象的成员就是该对象的类所定义的数据成员(成员变量)和成员函数。访问对象的成员变量和成员函数和访问变量和函数的方法是一样的,只不过要在成员www.hackshp.cn前面加上对象名和成员运算符“.”,其表示方式如下:<对象名>.<成员变量><对象名>.<成员函数>(<参数表>) 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.3构造函数和析构函数1.构造函数前面已提及,在类的定义中是不能对数据成员进行初始化的。为了能给数据成员自动设置某些初始值,这时就要使用类的特殊成员函数——构造函数。构造函数的最大特点是在对象建立时它会被自动执行,因此用于变量、对象的初始化代码一般放在构造函数中。C++规定:构造函数必须与相应的类同名,它可以带参数,也可以不带参数,与一般的成员函数定义相同,也可以重载。例如:课后答案网classCMeter{public:CMeter(intwww.hackshp.cnnPos)//带参数的构造函数{m_nPos=nPos;}...}这样若有:CMeteroMeter(10),oTick(20);则会自动调用构造函数CMeter(intnPos),从而使得对象oMeter中的私有成员m_nPos的值为10;使得对象oTick中的私有成员m_nPos的值为20。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.3构造函数和析构函数2.析构函数与构造函数相对应的是析构函数。析构函数是另一个特殊的C++成员函数,它只是在类名称前面加上一个“~”符号。每一个类只有一个析构函数,没有任何参数,也不返回任何值。例如:classCMeter{课后答案网public:...~CMeter()www.hackshp.cn{}//析构函数...}析构函数只有在下列两种情况下才会被自动调用:(1)当对象定义在一个函数体中,该函数调用结束后,析构函数被自动调用。(2)用new为对象分配动态内存,当使用delete释放对象时,析构函数被自动调用。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.3构造函数和析构函数3.默认构造函数和析构函数系统自动生成的默认构造函数和析构函数如下所示:CMeter()//默认构造函数的形式{}课后答案网~CMeter()//默认析构函数的形式{}需要说明的是,在用户定义一个对象时,编译器会自动www.hackshp.cn根据对象定义的格式选择相应的构造函数。例如:CMeterm1,m2;由于m1和m2不带任何参数,当类没有用户定义的构造函数时,则编译器就会使用默认构造函数对m1和m2进行初始化。用默认构造函数对对象进行初始化时,则将对象的所有数据成员都初始化为零或空。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.3构造函数和析构函数4.构造函数的重载构造函数可以被重载,C++会根据对象定义中的参数选择合适的构造函数。例如:[例Ex_ConOverLoad]构造函数的重载#includeclassCDate{public:课后答案网CDate();CDate(intday);CDate(intmonth,intday);CDate(intyear,intmonth,intday);www.hackshp.cn//其他公共成员private:intnYear,nMonth,nDay;};CDate::CDate(){nMonth=7;nDay=30;nYear=2002;cout<classCDate{public:CDate(intyear=2002,intmonth=7,intday=30){nYear=year;nMonth=month;nDay=day;课后答案网cout<::<拷贝构造函数名>(const<类名>&<引用名>)其中,const是一个类型修饰符,被它修饰的对象是一个不能被更新的常量。例如:www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.3构造函数和析构函数[例Ex_ConCopy]拷贝构造函数的使用#includeclassCDate{public:CDate(intyear=2002,intmonth=7,intday=30){cout<<"调用构造函数"<classCDatepublic:CDate(intyear=2002,intmonth=7,intday=30)课后答案网{cout<<"调用构造函数"<::<构造函数名>(形参表):对象1(参数表),对象2(参数表),…,对象n(参数表){}课后答案网其中,对象1、对象2、…、对象n就是该类使用的其他类的对象,冒号“:”后面的列表称为成员初始化列表。下面来看一个示例:www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.4对象成员初始化[例Ex_InitMultObject]对象成员的初始化#includeclassCPoint{public:CPoint(intx,inty){nPosX=x;nPosY=y;}voidShowPos()课后答案网{cout<<"当前位置:x="<<类名>::<静态数据成员名>=<值>例如: 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.5静态成员[例Ex_StaticData]静态数据成员的使用#includeclassCSum{public:CSum(inta=0,intb=0){nSum+=a+b;}intGetSum(){returnnSum;}voidSetSum(intsum){nSum=sum;}课后答案网private:staticintnSum;//声明静态数据成员};intCSum::nSum=0;www.hackshp.cn//静态数据成员的初始化voidmain(){CSumone(10,2),two;cout<<"sum="<classCSum{public:CSum(inta=0,intb=0){nSum+=a+b;}课后答案网intGetSum(){returnnSum;}voidSetSum(intsum){nSum=sum;}staticvoidwww.hackshp.cnShowData(CSumone);//声明静态成员函数private:staticintnSum;};voidCSum::ShowData(CSumone)//静态成员函数的实现{cout<<"直接使用静态成员"<classCPoint{public:CPoint(){m_x=m_y=0;}CPoint(unsignedx,unsignedy)课后答案网{m_x=x;m_y=y;}voidPrint(){cout<<"Point("<const<对象名>定义常对象时,修饰符const可以放在类名后面,也可以放在类名前面。例如:classCOne课后答案网{public:COne(inta,intb){x=a;y=b;}www.hackshp.cn…private:intx,y;};constCOnea(3,4);COneconstb(5,6);其中,a和b都是COne对象常量,初始化后就不能再被更新。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.7常类型2.常指针和常引用常指针也是使用关键字const来修饰的。但需要说明的是,const的位置不同,其含意也不同,它有三种形式。第一种形式是将const放在指针变量的类型之前,表示声明一个指向常量的指针。此时,在程序中不能通过指针来改变它所指向的数据值,但可以改变指针本身的值。例如:inta=1,b=2;constint*p1=&a;//声明指向int型常的指针p1,指针地址为a的地址*p1=2;课后答案网//错误,不能更改指针所指向的数据值p1=&b;//正确,指向常量的指针本身的值是可以改变的第二种形式是将const放在指针定义语句的指针名前,表示指针本身是一个常量,称为指针常量或常指针。因此,不能改变这种指针变量的值,但可以改变指变量所指向的数据值。例如:inta=1,b=2;www.hackshp.cnint*constp1=&a;//声明指向int型常的指针p1,指针地址为a的地址int*constp2;//错误,在声明指针常量时,必须初始化*p1=2;//正确,指针所指向的数据值可以改变p1=&b;//错误,指针常量本身的值是不可改变的第三种形式是将const在上述两个地方都加,表示声明一个指向常量的指针常量,指针本身的值不可改变,而且它所指向的数据的值也不能通过指针改变。例如:inta=1,b=2;constint*constpp=&a;*pp=2;//错误pp=&b;//错误 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.7常类型[例Ex_ConstPara]常参数的函数传递#includeclassCOne{public:voidprint(constint*p,intn)//使用常参数{课后答案网cout<<"{"<<*p;for(inti=1;i<函数名>(<参数表>)const;其中,constwww.hackshp.cn是加在函数说明后面的类型修饰符,它是函数类型的一个组成部分,因此,在函数实现部分也要带const关键字。例如: 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.7常类型[例Ex_ConstFunc]常成员函数的使用#includeclassCOne{public:COne(inta,intb){x=a;y=b;}voidprint();voidprint()const;//声明常成员函数private:课后答案网intx,y;};voidCOne::print(){cout<classCOne{public:课后答案网COne(inta):x(a),r(x)//常数据成员的初始化{}voidprint();constint&r;www.hackshp.cn//引用类型的常数据成员private:constintx;//常数据成员staticconstinty;//静态常数据成员};constintCOne::y=10;//静态数据成员的初始化voidCOne::print(){cout<<"x="<classCOne{public:COne(){x=y=0;}COne(inta,intb){x=a;y=b;}课后答案网voidcopy(COne&a);//对象引用作函数参数voidprint(){cout<M这样的表达式中,其中pa是一个指向A类对象的指针。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.1.9类的作用域和对象的生存期对象的生存期是指对象从被创建开始到被释放为止的时间。按生存期的不同,对象可分为如下三种:(1)局部对象:当对象被定义时调用构造函数,该对课后答案网象被创建,当程序退出定义该对象所在的函数体或程序块时,调用析构函数,释放该对象。www.hackshp.cn(2)静态对象:当程序第一次执行所定义的静态对象时,该对象被创建,当程序结束时,该对象被释放。(3)全局对象:当程序开始时,调用构造函数创建该对象,当程序结束时调用析构函数释放该对象。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.2继承和派生类2.2.1.单继承从一个基类定义一个派生类可按下列格式:class<派生类名课后答案网>:[<继承方式>]<基类名>{www.hackshp.cn[<派生类的成员>]};其中,继承方式有3种:public(公有)、private(私有)及protected(保护),若继承方式没有指定,则被指定为默认的public方式。继承方式决定了派生类的继承基类属性的使用权限,下面分别说明。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.2.1.单继承1.公有继承(public)公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的。例如:classCStick:publicCMeter{intm_nStickNum;课后答案网//声明一个私有数据成员public:voidDispStick();//声明一个公有成员函数};/www.hackshp.cn/注意分号不能省略voidCStick::DispStick(){m_nStickNum=GetPos();//调用基类CMeter的成员函数cout<classCMeter{public:CMeter(intnPos=10){m_nPos=nPos;}~CMeter(){}voidStepIt(){m_nPos++;}课后答案网intGetPos(){returnm_nPos;}protected:voidSetPos(intnPos){m_nPos=nPos;}private:www.hackshp.cnintm_nPos;};classCStick:publicCMeter//从CMeter派生,公有继承{intm_nStickNum;//声明一个私有数据成员public:voidDispStick();//声明一个公有成员函数voidSetStick(intnPos){SetPos(nPos);//类中调用基类的保护成员}}; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!voidCStick::DispStick(){m_nStickNum=GetPos();//调用基类CMeter的成员函数cout<classCMeter{public:课后答案网CMeter(intnPos=10){m_nPos=nPos;}~CMeter(){}voidStepIt(){m_nPos++;}intGetPos(){returnm_nPos;}www.hackshp.cnprotected:voidSetPos(intnPos){m_nPos=nPos;}private:intm_nPos;};classCStick:privateCMeter//从CMeter派生,私有继承{intm_nStickNum;//声明一个私有数据成员public:voidDispStick();//声明一个公有成员函数voidSetStick(intnPos) 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!{SetPos(nPos);//调用基类的保护成员}intGetStick(){returnGetPos();//调用基类的公有成员}};voidCStick::DispStick(){课后答案网m_nStickNum=GetPos();//调用基类CMeter的成员函数cout<#includeclassCAnimal{public:CAnimal(char*pName="noname");~CAnimal();课后答案网voidsetName(char*pName){strncpy(name,pName,sizeof(name));}char*getName(void)www.hackshp.cn{returnname;}private:charname[20];};CAnimal::CAnimal(char*pName){setName(pName);cout<<"调用CAnimal的构造函数!"<:[<继承方式1>]<基类名1>,[<继承方式2>]<基类名2>,...{[<派生类的成员>]};课后答案网其中的继承方式还是前面的3种:public、private和protected。例如:classAwww.hackshp.cn{...}classB{...}classC:publicA,privateB{...}由于派生类C继承了基类A和B,具有多继承性,因此派生类C的成员包含了基类A中成员和B中成员以及该类本身的成员。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.2.4虚基类一般说来,在派生类中对基类成员的访问应该是惟一的,但是,由于多继承情况下,可能造成对基类中某成员的访问出现了不惟一的情况,这种情况称为基类成员调用的二义性。例如:[例Ex_Conflict]基类成员调用的二义性#includeclassA{public:intx;课后答案网A(inta=0){x=a;}};classB1:publicA{www.hackshp.cnpublic:inty1;B1(inta=0,intb=0):A(b){y1=a;}};classB2:publicA{public:inty2;B2(inta=0,intb=0):A(b){y2=a;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!};classC:publicB1,publicB2{public:intz;C(inta,intb,intd,inte,intm):B1(a,b),B2(d,e){z=m;}voidprint()课后答案网{cout<<"x="<=10.0){//计算位数base=base*10.0;temp=temp/10.0;课后答案网n++;}if(n>=15)strcpy(strResult,"金额超过范围!");else{www.hackshp.cntemp=amount*100.0;for(intm=n;m>=0;m--){intd=(int)(temp/base);temp=temp-base*(double)d;base=base/10.0;strcat(strResult,datastr[d]);strcat(strResult,basestr[m]);}}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!returnstrResult;}voidmain(){CMoneymoney(1234123456789.123);cout<<(string)money<#includeclassCdemo{public:CDemo(char*s)课后答案网{ps=newchar[strlen(s)+1];strcpy(ps,s);}~CDemo(){if(ps)delete[]ps;}voidprint()www.hackshp.cn{cout<课后答案网classCCounter{public:www.hackshp.cnCCounter(){unCount=0;}CCounteroperator++();CCounteroperator++(int);voidprint(){cout<voidmain(){intnNum=12345;doubledNum=12345.6789;char*str[]={"This","is","aTest!"};cout.setf(ios::oct|ios课后答案网::showbase|ios::showpos);//设置标志:八进制,显示基和正号cout<#includevoidmain(){intnNum=12345;doubledNum=12345.6789;课后答案网char*str[]={"This","is","aTest!"};cout<voidmain(){inti,s;charbuf[80];cout<<"输入一个整数:课后答案网";cin>>i;s=cin.rdstate();cout<<"流状态为:www.hackshp.cn"<>i;s=cin.rdstate();}}运行结果如下: 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.5.3使用输入输出成员函数1.输入操作的成员函数数据的输入/输出可以分为三大类:字符类、字符串和数据。(1)使用get和getline函数用于输入字符或字符串的成员函数课后答案网get原型如下:intget();istream&get(char&rch);istream&get(char*www.hackshp.cnpch,intnCount,chardelim="n");第一种形式是从输入流中提取一个字符,并转换成整型数值。第二种形式是从输入流中提取字符到rch中。第三种形式是从输入流中提取一个字符串并由pch返回,nCount用来指定提取字符的最多个数,delim用来指定结束字符,默认时是‘n’。函数getline原型如下:istream&getline(char*pch,intnCount,chardelim="n");它是用来从输入流中提取一个输入行,并把提取的字符串由pch返回,nCount和delim的含义同上。这些函数可以从输入流中提取任何字符,包括空格等。例如: 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.5.3使用输入输出成员函数[例Ex_GetAndGetLine]get和getline的使用#includevoidmain(){chars1[80],s2[80],s3[80];cout<<"请键入一个字符:";cout<voidmain(){chardata[80];cout<<"请输入:"<>”和“<<”运算符,以便用户利用标准的输入输出流来输入输出自己定义的数据类型(包括类),实现对象的输入输出。重载这两个运算符时,虽然可使用别的方法,但最好将重载声明为类的友元函数,以便能访问类中的私有成员。下面来看一个示例。[例Ex_ExtractAndInsert]提取和插入运算符的重载#include课后答案网classCStudent{public:friendostream&operator<<(ostream&os,CStudent&stu);www.hackshp.cnfriendistream&operator>>(istream&is,CStudent&stu);private:charstrName[10];//姓名charstrID[10];//学号floatfScore[3];//三门成绩};ostream&operator<<(ostream&os,CStudent&stu){os<>(istream&is,CStudent&stu){cout<<"请输入学生信息"<>stu.strName;课后答案网cout<<"学号:";is>>stu.strID;cout<<"三门成绩:";is>>stu.fScore[0]>>stu.fScore[1]>>stu.fScore[2];www.hackshp.cnreturnis;}voidmain(){CStudentone;cin>>one;cout<>”;而向一个文件写入数据,可以使用put、write函数以及插入符“<<”。下面举例来说明文件的操作过程和方法。[例Ex_File]将文件内容保存在另一文件中,并将内容显示在屏幕上#include课后答案网#include//文件操作必须的头文件voidmain(){fstreamwww.hackshp.cnfile1;//定义一个fstream类的对象用于读file1.open("Ex_DataFile.txt",ios::in);if(!file1){cout<<"Ex_DataFile.txt不能打开!n";return;}fstreamfile2;//定义一个fstream类的对象用于写file2.open("Ex_DataFileBak.txt",ios::out|ios::trunc);if(!file2){cout<<"Ex_DataFileBak.txt不能创建!n";file1.close();return;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!charch;while(!file1.eof()){file1.read(&ch,1);cout<#include#include#includeclassCStudent{public:课后答案网CStudent(char*name,char*id,floatscore=0);voidprint();friendostream&operator<<(ostream&os,CStudent&stu);friendistream&operator>>(iswww.hackshp.cntream&is,CStudent&stu);private:charstrName[10];//姓名charstrID[10];//学号floatfScore;//成绩};CStudent::CStudent(char*name,char*id,floatscore){strncpy(strName,name,10);strncpy(strID,id,10);fScore=score;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!voidCStudent::print(){cout<>(istream&is,CStudent&stu){charname[10];charid[10];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;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!voidmain(){CStudentstu1("MaWenTao","99001",88);CStudentstu2("LiMing","99002",92);CStudentstu3("WangFang","99003",89);CStudentstu4("YangYang","99004",90);CStudentstu5("DingNing","99005",80);fstreamfile1;file1.open("student.dat",ios::out|ios::in|ios::binary);file1<>*one;www.hackshp.cnone->print();file1.seekp(size*1);file1>>*one;one->print();file1.seekp(size*2,ios::cur);file1>>*one;one->print();file1.close();deleteone;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.5.7随机文件操作[例Ex_FileSeek]使用seekp指定文件指针的位置运行结果如下:课后答案网www.hackshp.cn程序中,先将五个学生记录保存到文件中,然后移动文件指针,读取相应的记录,最后将数据输出到屏幕上。需要说明的是,由于文件流file1既可以读(ios::in)也可以写(ios::out),因此用seekg代替程序中的seekp,其结果也是一样的。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!第3章MFC基本应用程序的建立3.1Windows编程基础编制一个功能强大和易操作的Windows应用程序所需要的代码肯定会比一般的C++程序要多得多,但并不是所有的代码都需要自己从头开始编写,课后答案网因为VisualC++不仅提供了常用的Windows应用程序的基本框架,而且可以在框架程序中直接调www.hackshp.cn用Win32API(ApplicationProgrammingInterface,应用程序接口)函数。这样,用户仅需要在相应的框架位置中添加自己的代码或修改部分代码就可实现Windows应用程序的许多功能。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.1简单的Windows应用程序先来看一个最简单的Windows应用程序Ex_HelloMsg。[例Ex_HelloMsg]一个简单的Windows应用程序#includeintWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow){MessageBox(NULL,"课后答案网你好,我的VisualC++世界!","问候",0);return0;}在VisualC++6.0运行上述程序需要进行以下步骤:(1)选择“文件”www.hackshp.cn→“新建”菜单命令,显示出“新建”对话框。在“工程”标签页面的列表框中,选中Win32Application项。(2)在工程编辑框中键入Win32应用程序项目名称Ex_HelloMsg。在“位置”编辑框中直接键入文件夹名称,或单击浏览按钮选择一个已有的文件夹。(3)单击[确定]按钮继续。一个询问项目类型的Win32应用程序向导将被显示,选中Anemptyproject项。单击[完成]按钮,系统将显示该应用程序向导的创建信息,单击[确定]按钮系统将自动创建此应用程序。(4)再次选择“文件”→“新建”菜单命令,显示出“新建”对话框。在“文件”标签页面左边的列表框中选择C++SourceFile项,在右边的文件框中键入Ex_HelloMsg.cpp,单击[确定]按钮。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.1简单的Windows应用程序[例Ex_HelloMsg]一个简单的Windows应用程序(5)输入上面的代码,运行程序,结果如图3.1所示。从上面的程序可以看出:●C++控制台应用程序以main函数作为进入程序的初始入口点,但在Windows应用程序中,main主函数被WinMain函数取代。WinMain函数的原型如下:intWINAPIWinMain(课后答案网HINSTANCEhInstance,//当前实例句柄HINSTANCEhPrevInstancewww.hackshp.cn,//前一实例句柄LPSTRlpCmdLine,//指向命令行参数的指针intnCmdShow)//窗口的显示状态图3.1Ex_HelloMsg运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.1简单的Windows应用程序所谓句柄是一个标识Windows资源(如菜单、图标、窗口等)和设备等对象的数据指针类型。通常,一个句柄变量可用来对系统中某些资源的间接引用。●每一个C++Windows应用程序都需要Windows.h头文件,它还包含了其他的一些课后答案网Windows头文件。这些头文件定义了Windows的所有数据类型、函数调用、数据结构和符号常量。www.hackshp.cn●程序中,MessageBox是一个Win32API函数,用来弹出一个消息对话框。该函数第一个参数用来指定父窗口句柄,即对话框所在的窗口句柄。第二、三个参数分别用来指定显示的消息内容和对话框窗口的标题,最后一个参数用来指定在对话框中显示的按钮。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.1简单的Windows应用程序[例Ex_HelloWin]一个完整的Windows应用程序#includeLRESULTCALLBACKWndProc(HWND,UINT,WPARAM,LPARAM);//窗口过程intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow){HWNDhwnd;//窗口句柄MSGmsg;课后答案网//消息WNDCLASSwndclass;//窗口类wndclass.style=CS_HREDRAW|CS_VREDRAW;wndclass.lpfnWndProc=WndProc;wndclass.cbClsExtrawww.hackshp.cn=0;wndclass.cbWndExtra=0;wndclass.hInstance=hInstance;wndclass.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)) 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!{//注册窗口MessageBox(NULL,"窗口注册失败!","HelloWin",0);return0;}hwnd=CreateWindow("HelloWin",//窗口类名"我的窗口",//窗口标题WS_OVERLAPPEDWINDOW,//窗口样式CW_USEDEFAULT,//窗口最初的x位置CW_USEDEFAULT,//窗口最初的y位置CW_USEDEFAULT,//课后答案网窗口最初的x大小CW_USEDEFAULT,//窗口最初的y大小NULL,//父窗口句柄NULL,//窗口菜单句柄hInstance,//www.hackshp.cn应用程序实例句柄NULL);//创建窗口的参数ShowWindow(hwnd,nCmdShow);//显示窗口UpdateWindow(hwnd);//更新窗口,包括窗口的客户区//进入消息循环:当从应用程序消息队列中检取的消息是WM_QUIT时,则退出循环。while(GetMessage(&msg,NULL,0,0)){TranslateMessage(&msg);//转换某些键盘消息DispatchMessage(&msg);//将消息发送给窗口过程,这里是WndProc} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!}returnmsg.wParam;}LRESULTCALLBACKWndProc(HWNDhwnd,UINTmessage,WPARAMwParam,LPARAMlParam){switch(message){caseWM_CREATE:课后答案网//窗口创建产生的消息return0;caseWM_LBUTTONDOWN:MessageBox(NULL,"你好,我的VisualC++世界!","问候",0);www.hackshp.cnreturn0;caseWM_DESTROY://当窗口关闭时产生的消息PostQuitMessage(0);return0;}returnDefWindowProc(hwnd,message,wParam,lParam);//执行默认的消息处理} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.1简单的Windows应用程序[例Ex_HelloWin]一个完整的Windows应用程序程序运行后,单击鼠标左键,就会弹出一个对话框,结果如图3.2所示。课后答案网www.hackshp.cn图3.2Ex_HelloWin运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.1简单的Windows应用程序窗口过程函数WndProc用来接收和处理各种不同的消息,而主函数WinMain通常要完成以下几步工作:(1)调用API函数RegisterClass注册应用程序的窗口类。(2)调用相关API函数创建和显示窗口,并进行其它必要的初始化处理。其中,函数课后答案网CreateWindow用来创建已注册窗口类的窗口。Windows每一个窗口都有一些基本属性,如窗口标题、窗口位置和大小、应用程序图标、鼠标指针、菜单和背景颜色等。窗口类就是充当这些属性的模板。www.hackshp.cn(3)创建和启动应用程序的消息循环。Windows应用程序接受各种不同的消息,包括键盘消息、鼠标以及窗口产生的各种消息。Windows系统首先将消息放入消息队列中,应用程序的消息循环就是从应用程序的消息队列中检取消息,并将消息发送相应的窗口过程函数中作进一步处理。API函数GetMessage和DispatchMessage就是起到这样的作用。(4)如果接收到WM_QUIT消息,则退出应用程序。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.1简单的Windows应用程序开始执行初始化应用程序初始化并创建课后答案网应用程序窗口进入消息循环并获取一条消息www.hackshp.cn消息是否为否应用程序中是否是处理消息WM_QUIT?处理了此消息是否终止程序转送消息给窗口进行默认处理图3.3Windows应用程序的基本流程 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.2Windows编程特点一个完整的Windows应用程序除了WinMain函数外,还包含用于处理用户动作和窗口消息的窗口函数。这不同于一个C++的控制台应用程序,可以将整个程序包含在main函数中。事实上,它们的区别还远不止这些,不久还会发现一个Windows应用程序还常常具有这样的一些特性:●消息驱动机制课后答案网●图形设备接口(GDI)●基于资源的程序设计●动态链接库●进程和线程www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.2Windows编程特点1.消息驱动机制在Windows操作环境中,无论是系统产生的动作或是用户运行应用程序产生的动作,都称为事件(Events)产生的消息(Message)。例如,在Windows桌面(传统风格)上,双击应用程序的快捷图标,系统就会执行该应用程序。在Windows的应用程序中,也是通过接收消息、分发消息、处理消息来和用户进行交互的。课后答案网这种消息驱动的机制是Windows编程的最大特点。需要注意的是,许多Windows消息都经过了严格的定义,并且适用于所有的应用程序。例如,当用户按下鼠标的左键时系统就会发送www.hackshp.cnWM_LBUTTONDOWN消息,而当用户敲了一个字符键时系统就会发送WM_CHAR消息,若用户进行菜单选择或工具按钮单击等操作时,系统又会相应地发送WM_COMMAND消息给相应的窗口等等。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.2Windows编程特点2.图形设备接口(GDI)在传统的DOS环境中,想要在打印机上打印一幅图形是一件非常复杂的事情,因为用户必须根据打印机类型和指令规则向打印机输送数据。而Windows则提供了一个抽象的接口,称为图形设备接口(GraphicalDeviceInterface,简称GDI),使得用户直接利用系统的GDI函数就能方便实现输入或输出,而不必关心与系统相连的外部设备的类型。课后答案网3.基于资源的程序设计Windows应用程序常常包含众多图形元素,例如光标、菜单、工具栏、位图、对话框等,在Windowswww.hackshp.cn环境下,每一个这样的元素都作为一种可以装入应用程序的资源来存放。这些资源就像C++程序中的常量一样,可以被编辑、修改,也可以被其他应用程序所共享。VisualC++6.0中就提供这样的编辑器,可“所见即所得”地对这些不同类型的资源进行设计、编辑等。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.2Windows编程特点4.动态链接库动态链接库提供了一些特定结构的函数,能被应用程序在运行过程中装入和连接,且多个程序可以共享同一个动态链接库,这样就可以大大节省内存和磁盘空间。从编程角度来说,动态链接库可以提高程序模块的灵活性,因为它本身是可以单独设计、编译和调试的。Windows提供了应用程序可利用的丰富的函数调用,大多数用于实现其课后答案网用户界面和在显示器上显示的文本和图形,都是通过动态链接库来实现的。这些动态链接库是一些具有.DLL扩展名或者有时是.EXE扩展名的文件。www.hackshp.cn在Windows操作系统中,最主要的DLL有KERNEL32.DLL、GDI32.DLL和USER32.DLL三个模块。其中,KERNEL32用来处理存储器低层功能、任务和资源管理等Windows核心服务;GDI32用来提供图形设备接口,管理用户界面和图形绘制,包括Windows元文件、位图、设备描述表和字体等;而USER32负责窗口的管理,包括消息、菜单、光标、计时器以及其它与控制窗口显示相关的一些功能。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.2Windows编程特点5.进程和线程在32位Windows多任务操作系统中,采用了进程和线程的管理模式。进程是装入内存中正在执行的应用程序。进程包括私有的虚拟地址空间、代码、数据及其它操作系统资源,如文件、管道以及对该进程可见的同步对象等。进程包括了一个或多个在进程上下文内运行的线程。线程是操作系统分配CPU时间的基本实体。线程可以执行应用程序代码的任何部分,包括当前正在被其它线程执行的那些部课后答案网分。同一进程的所有线程共享同样的虚拟地址空间、全局变量和操作系统资源。在一个应用程序中,可以包括一个或多个进程,每个进程由一个或多个线程构成。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.3Windows基本数据类型表3.1列出了一些在Windows编程中常用的基本数据类型。表3.1Windows常用的基本数据类型课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.1.3Windows基本数据类型表3.2列出了常用的预定义句柄,它们的类型均为void*,即一个32位指针。表3.2Windows常用的句柄类型课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.2编制一个MFC应用程序前面的Ex_HelloMsg和Ex_HelloWin都是基于WindowsAPI的C++应用程序。显然,随着应用程序的复杂性,C++应用程序代码也必然越复杂。为了帮助用户处理那些经常使用又复杂繁琐的各种课后答案网Windows操作,VisualC++设计了一套基础类库(MicrosoftFoundationClassLibrary,简称MFC)。MFC把Windows编程规范中的大多数内容封装成为各种类,使程序员从繁杂的编程中解脱出来,提高了编程和代码效率。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.2.1设计一个MFC程序在理解MFC机制之前,先来看一个MFC应用程序。[例Ex_HelloMFC]一个MFC应用程序#include//MFC头文件classCHelloApp:publicCWinApp//声明应用程序类{public:virtualBOOLInitInstance();课后答案网};CHelloApptheApp;//建立应用程序类的实例classCMainFrame:publicCFrameWnd//声明主窗口类{www.hackshp.cnpublic:CMainFrame(){//创建主窗口Create(NULL,"我的窗口",WS_OVERLAPPEDWINDOW,CRect(0,0,400,300));}protected:afx_msgvoidOnLButtonDown(UINTnFlags,CPointpoint);DECLARE_MESSAGE_MAP()}; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!//消息映射入口BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd)ON_WM_LBUTTONDOWN()//单击鼠标左键消息的映射宏END_MESSAGE_MAP()//定义消息映射函数voidCMainFrame::OnLButtonDown(UINTnFlags,CPointpoint){MessageBox("你好,我的课后答案网VisualC++世界!","问候",0);CFrameWnd::OnLButtonDown(nFlags,point);}BOOLCHelloApp::InitInstance()//每当应用程序首次执行时都要调用的初始化函数{www.hackshp.cnm_pMainWnd=newCMainFrame();m_pMainWnd->ShowWindow(m_nCmdShow);m_pMainWnd->UpdateWindow();returnTRUE;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.2.1设计一个MFC程序在VisualC++6.0运行上述MFC程序需要进行以下步骤:(1)选择“文件”→“新建”菜单命令,显示出“新建”对话框。在“工程”标签页面的列表框中,选中Win32Application项,创建一个Ex_HelloMFC空应用程序项目。(2)再次选择“文件”课后答案网→“新建”菜单命令,显示出“新建”对话框。在文件标签页面左边的列表框中选择C++SourceFile项,在右边的文件框中键入Ex_HelloMFC.cpp,单击[确定]按钮。(3)输入上面的代码。选择“工程”www.hackshp.cn→“设置”菜单命令,在出现的对话框中选择“General”标签。然后在“MicrosoftFoundationClasses”组合框中,选择“UseMFCinaSharedDLL”,如图3.4所示。单击[确定]按钮。(4)程序运行后,单击鼠标左键,就会弹出一个对话框,结果同Ex_HelloWin。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.2.1设计一个MFC程序课后答案网www.hackshp.cn图3.4设置工程属性 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.2.2理解程序代码按照MFC的消息映射机制,映射一个消息的过程是由三个部分组成的:(1)在处理消息的类中,使用消息宏DECLARE_MESSAGE_MAP声明对消息映射的支持,并在该宏之前声明消息处理函数。例如前面示例中的:protected:afx_msgvoidOnLButtonDown(UINTnFlags,CPointpoint);DECLARE_MESSAGE_MAP()(2)使用BEGIN_MESSAGE_MAP课后答案网和END_MESSAGE_MAP宏在类声明之后的地方定义该类支持的消息映射入口点,所有消息映射宏都添加在这里,当然不同的消息MFC都会有不同的消息映射宏。例如:BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd)www.hackshp.cn…END_MESSAGE_MAP()其中,BEGIN_MESSAGE_MAP带有两个参数,第一个参数用来指定需要支持消息映射的用户派生类,第二个参数指定该类的基类。(3)定义消息处理函数。例如:voidCMainFrame::OnLButtonDown(UINTnFlags,CPointpoint){MessageBox("你好,我的VisualC++世界!","问候",0);CFrameWnd::OnLButtonDown(nFlags,point);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.3使用MFCAppWizard3.3.1应用程序框架类型这些类型基本满足了各个层次的用户的需要,但一般地,用户更关心的是MFCAppWizard(exe)应用程序框架,因为它包含用户最常用、最基本的三种应用程序类型:单文档、多文档和基于对话框的应用程序。课后答案网表3.3MFCAppWizard创建的应用程序类型www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.3.2创建一个单文档应用程序用MFCAppWizard(MFC应用程序向导)可以方便地创建一个通用的Windows单文档应用程序,其步骤如下。1.开始选择“文件”→“新建”菜单,在弹出的“新建”对话框中,可以看到工程标签页面中,显示出一系列的应用程序项目类型;选择课后答案网MFCAppWizard(exe)的项目类型(该类型用于创建可执行的Windows应用程序),将项目工作文件夹定位在“D:VisualC++6.0程序”,并在工程编辑框中输入项目名www.hackshp.cnEx_SDIHello,结果如图3.5所示。图3.5MFCAppWizard的“新建”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.3.2创建一个单文档应用程序2.第一步单击[确定]按钮,出现如图3.6所示的对话框,进行下列选择:(1)从应用程序类型单个文档(SDI)、多重文档(MDI)和基本对话框(基于对话框的应用程序)中选择“单个文档”。(2)决定应用程序中是否需要课后答案网MFC的文档视图(“文档/查看体系结构支持”)结构的支持。若不选定此项,则程序中的磁盘文件的打开、保存以及文档和视图的相互作用等功能需要用户来实现,且将跳过Step2~Step5,直接弹出“Step6”对话框。一般情况下,应选中此项。www.hackshp.cn(3)选择资源所使用的语言,这里是“中文[中国]”。3.第二步单击[下一个]按钮,出现如图3.7所示的对话框,让用户选择程序中是否加入数据库的支持(有关数据库的内容将在以后的章节中介绍)。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!课后答案网www.hackshp.cn图3.6MFCAppWizard的“Step1”对话框图3.7MFCAppWizard的“Step2”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.3.2创建一个单文档应用程序4.第三步单击[下一个]按钮进入下一步,出现如图3.8所示的对话框。允许用户在程序中加入复合文档、自动化、ActiveX控件的支持。5.第四步单击[下一个]按钮进入下一步,出现如图3.9所示的对话框,对话框的前几项依次确定对浮动工具条、打印与预览以及通信等特性的支持。课后答案网www.hackshp.cn图3.8MFCAppWizard的“Step3”对话框图3.9MFCAppWizard的“Step4”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.3.2创建一个单文档应用程序6.第五步保留以上默认值,单击[下一个]按钮进入下一步。弹出如图3.10所示的对话框,这里出现三个方面的选项,供用户来选择:(1)应用程序的主窗口是MFC标准风格还是窗口左边有切分窗口的浏览器风格;(2)在源文件中是否加入注释用来引导用户编写程序代码;(3)使用动态链接库还是静态链接库。课后答案网www.hackshp.cn图3.10MFCAppWizard的“Step5”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.3.2创建一个单文档应用程序7.第六步保留默认选项,单击[下一步]按钮进行下一步,出现如图3.11所示的对话框。在这里,用户可以对MFCAppWizard提供的默认类名、基类名、各个源文件名进行修改。单击[完成]按钮出现一个信息对话框,显示出用户在前面几个步骤中作出的选择课后答案网内容,单击[确定]按钮系统开始创建,并又回到了VisualC++6.0的主界面。8.编译并运行到这里为止,用户虽然没有编写任何程序代码,但www.hackshp.cnMFCAppWizard已经根据用户的选择内容自动生成基本的应用程序框架。单击编译工具栏上的运行工具按钮“”或按快捷键Ctrl+F5,系统开始编连并运行生成的单文档应用程序可执行文件Ex_SDIHello.exe,运行结果如图3.12所示。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!标题栏菜单栏工具栏课后答案网文档窗口www.hackshp.cn状态栏图3.12Ex_SDIHello运行结果图3.11MFCAppWizard的“Step6”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.3.3MFC应用程序项目组织1.项目的文件组织在VisualC++6.0中,项目中所有的源文件都是采用文件夹的方式进行管理的,它将项目名作为文件夹名,在此文件夹下包含源程序代码文件(.cpp,.h)、项目文件(.dsp)以及项目工作区文件(.dsw)等。表3.4列出了这些文件类型的的含义。课后答案网表3.4VisualC++6.0文件类型的含义www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.3.3MFC应用程序项目组织除了上述文件外,还有相应的Debug(调试)或Release(发行)、Res(资源)等子文件夹。例如上述创建的单文档应用程序项目Ex_SDIHello,其各文件的组织如图3.13所示。当然,不同类型的项目的文件类型及数目会有所不同。编连过程产生的文件Debug或Release文件夹运行文件Ex_SDIHello.exe课后答案网图标文件Ex_SDIHello.ico等Res文件夹工具栏资源Toolbar.bmpEx_SDIHellowww.hackshp.cn文件夹其他资源Ex_SDIHello.rc2等类文件.cpp,.h资源文件Ex_SDIHello.rc项目及项目工作区文件其他文件图3.13Ex_SDIHello项目的文件组织 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.3.3MFC应用程序项目组织2.MFC类结构在开发环境中,VisualC++6.0是通过左边的项目工作区窗口来对项目进行各种管理。项目工作区窗口包含3个页面,它们分别是ClassView页、ResourceView页和FileView页,通过单击项目区窗口底部的页面标签进行切换。将VisualC++6.0项目工作区窗口切换到ClassView页面,可以看到MFC为单文档应用程序项目Ex_SDIHello自动创建了类CAboutDlg、CEx_SDIHelloApp、CEx_SDIHelloDoc、CEx_SDIHelloView和CMainFrame课后答案网。这些MFC类之间的继承和派生关系如图3.14所示。www.hackshp.cnCObjectCWinTreadCCmdTargetCDocumentCWinAppCWndCViewCFrameWndCDialog及控件CMDIFrameWndCMDIChildWndCMiniFrameWnd图3.14MFC类的基本层次结构 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4使用类向导MFC类向导(ClassWizard)是VisualC++6.0中又一个非常有用的工具。它能自动为一个项目添加一个类、进行消息和数据映射、创建OLEAutomation(自动化)属性和方法以及进行ActiveX事件处理等。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.1类向导概述打开MFC类向导可以使用下列几种方法:(1)选择“查看”→“建立类向导”菜单或直接使用Ctrl+W快捷键。(2)在源代码文件的文档编辑窗口中,右击鼠标,从弹出的快捷菜单中选择“建立类向导”命令。当MFC类向导打开后,就会弹出如图课后答案网3.15所示的MFCClassWizard对话框。www.hackshp.cn图3.15MFCClassWizard对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.1类向导概述可以看到ClassWizard对话框包含了5个标签页面,它们各自含义如下:●MessageMaps(消息映射):用来添加、删除和编程处理消息的成员函数。●MemberVariables(成员变量):添加或删除与控件相关联的成员变量(或称数据课后答案网成员),以便与控件进行数据交换。这些控件所在的类一般是从CDialog、CPropertyPage、CRecordView或CDaoRecordView中派生的类。●Automation(www.hackshp.cn自动化):为支持Automation的类(如ActiveX控件类)添加属性和方法。●ActiveXEvents(ActiveX事件):为ActiveX控件类添加触发事件的支持。●ClassInfo(类信息):有关项目中类的其他信息。一般来说,MFCClassWizard对话框最前两项是用户最关心的,也是最经常使用的,因为几乎所有的代码编写都要利用这两个标签项。由于MemberVariables功能以后还会详细讨论,因此这里仅讨论MessageMaps(消息映射)与类的添加和删除方法。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.2消息和消息映射1.消息分类Windows应用程序中的消息主要有三种类型。(1)窗口消息(Windowsmessage)。这类消息主要是指由WM_开头的消息(WM_COMMAND除外),一般由窗口类和视图类对象来处理。窗口消息往往带有参数,以标志处理消息的方法。(2)控件的通知消息(Controlnotifications)课后答案网。当控件的状态发生改变(例如用户在控件中进行输入)时,控件就会向其父窗口发送WM_COMMAND通知消息。应用程序框架处理控件消息的方法和窗口消息相同,但按钮的BN_CLICKED通知消息除外,它的处理方法与命令消息相同。www.hackshp.cn(3)命令消息(Commandmessage)。命令消息主要包括由用户交互对象(菜单、工具条的按钮、快捷键等)发送的WM_COMMAND通知消息。命令消息的处理方式与其他两种消息不同,它能够被多种对象接收、处理,这些对象包括文档类、文档模板类、应用程序本身以及窗口和视类等;而窗口消息和控件的通知消息是由窗口对象接收并处理的,这里的窗口对象是指从CWnd中派生的类的对象,它包括CFrameWnd、CMDIFrameWnd、CMDIChildWnd、CView、CDialog以及从这些类派生的对象等。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.2消息和消息映射2.ClassWizard映射消息的一般方法在MFC中,绝大多数消息都可由MFC的ClassWizard来映射。将ClassWizard对话框切换到MessageMaps页面(参看图3.15),可以看到它有许多选项,如项目组合框、类组合框等。各项功能说明如表3.5所示。表课后答案网3.5ClassWizard对话框的MessageMaps页面功能www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.2消息和消息映射2.ClassWizard映射消息的一般方法例如,若向CEx_SDIHelloView中添加WM_LBUTTOMDOWN的消息映射,则可按下列步骤进行:(1)按Ctrl+W快捷键打开MFCClassWizard对话框。(2)在Classname组合框中,将类名选定为CEx_SDIHelloView。此时,ObjectIDs和Messages列表内容会相应的改变。(3)在ObjectIDs列表框中选定课后答案网CEx_SDIHelloView,而在Messages列表中选定WM_LBUTTOMDOWN消息。(4)双击Messages列表中的WM_LBUTTOMDOWN消息或单击[AddFunction]按钮,都会在CEx_SDIHelloView类中添加该消息的映射函数OnLButtonDown,同时在Memberfuncions列表中显示这一消息映射函数和被映射的消息,结果如图www.hackshp.cn3.16所示。(5)单击[EditCode]按钮后,ClassWizard对话框退出,并转向文档窗口,定位到OnLButtonDown函数源代码处。(6)添加下列代码:voidCEx_SDIHelloView::OnLButtonDown(UINTnFlags,CPointpoint){MessageBox("你好,我的VisualC++世界!","问候",0);CView::OnLButtonDown(nFlags,point);}(7)这样就完成了一个消息映射过程。程序运行后,在窗口客户区单击鼠标左键,就会弹出一个消息对话框。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.2消息和消息映射课后答案网www.hackshp.cn图3.16映射WM_LBUTTONDOWN消息 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.2消息和消息映射3.消息映射代码查看CEx_SDIHelloView程序代码,可以发现:ClassWizard为WM_LBUTTOMDOWN的消息映射作了以下三个方面内容的安排:(1)在头文件Ex_SDIHelloView.h中声明消息处理函数OnLButtonDown:protected://{{AFX_MSG(CEx_SDIHelloView)afx_msgvoidOnLButtonDown(UINTnFlags,CPointpoint);课后答案网//}}AFX_MSGDECLARE_MESSAGE_MAP()代码中的//{{AFX_MSG(CEx_SDIHelloView)www.hackshp.cn和//}}AFX_MSG之间的部分是ClassWizard定义的专门用作消息映射函数声明的标记。表示该程序块中的消息映射声明是由ClassWizard来自动管理的,用户一般不需要去更改。需要说明的是,凡//{{和//}}之间的程序代码块均由ClassWizard自动管理。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.2消息和消息映射3.消息映射代码(2)在Ex_SDIHelloView.cpp源文件前面的消息映射入口处,添加了相应的映射宏:BEGIN_MESSAGE_MAP(CEx_SDIHelloView,CView)//消息映射开始//{{AFX_MSG_MAP(CEx_SDIHelloView)ON_WM_LBUTTONDOWN()//}}AFX_MSG_MAP课后答案网…END_MESSAGE_MAP()//消息映射结束(3)在Ex_SDIHelloView.cpp文件中写入一个空的消息处理函数的模板,以便用户填入具体代码,如下面的框架:www.hackshp.cnvoidCEx_SDIHelloView::OnLButtonDown(UINTnFlags,CPointpoint){//TODO:Addyourmessagehandlercodehereand/orcalldefaultCView::OnLButtonDown(nFlags,point);}事实上,根据ClassWizard产生的上述消息映射过程,用户可以自己手动添加一些MFCClassWizard不支持的消息映射函数,以完成特定的功能。例如,Ex_HelloMFC示例就是按照上述过程添加消息映射的。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.2消息和消息映射4.键盘消息当用户按下一个键或组合键时,Windows将WM_KEYDOWN或WM_SYSKEYDOWN放入具有输入焦点的应用程序窗口的消息队列中。当键被释放时,Windows则把WM_KEYUP或WM_SYSKEYUP消息放入消息队列中。对于字符键来说,还会在这两个消息之间产生WM_CHAR消息。MFCClassWizard能自动添加了当前类的WM_KEYDOWN和WM_KEYUP击键消息处理函数的调用,它们具有下列函数原型:课后答案网afx_msgvoidOnKeyDown(UINTnChar,UINTnRepCnt,UINTnFlags);afx_msgvoidOnKeyUp(UINTnChar,UINTnRepCnt,UINTnFlags);afx_msg是MFCwww.hackshp.cn用于定义消息函数的标志,参数nChar表示虚拟键代码,nRepCnt表示当用户按住一个键时的重复计数,nFlags表示击键消息标志。所谓虚拟键代码,是指与设备无关的键盘编码。在VisualC++中,最常用的虚拟键代码已被定义在Winuser.h中,例如:VK_SHIFT表示SHIFT键,VK_F1表示功能键F1等。同击键消息一样,MFC中的ClassWizard也提供相应的字符消息处理框架,并自动添加了当前类的WM_CHAR消息处理函数调用,它具有下列函数原型:afx_msgvoidOnChar(UINTnChar,UINTnRepCnt,UINTnFlags);参数nChar表示键的ASCII码,nRepCnt表示当用户按住一个键时的重复计数,nFlags表示字符消息标志。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.2消息和消息映射5.鼠标消息当用户对鼠标进行操作时,像键盘一样也会产生对应的消息。通常,Windows只将键盘消息发送给具有输入焦点的窗口,但鼠标消息不受这种限制。只要鼠标移过窗口的客户区时,就会向该窗口发送WM_MOUSEMOVE(移动鼠标)消息。这里的客户区是指窗口中用于输出文档的区域。由于MFC头文件中定义的与鼠标按钮相关的标识使用了课后答案网LBUTTON(左)、MBUTTON(中)和RBUTTON(右),因而当在窗口的客户区中按下或释放一个鼠标键时,还会产生如表3.6所示的消息。www.hackshp.cn表3.6客户区鼠标消息 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.2消息和消息映射5.鼠标消息对于所有这些消息来说,ClassWizard都会将映射成类似afx_msgvoidOnXXXX的消息处理函数,如前面WM_LBUTTONDOWN的消息函数OnLButtonDown,它们具有函数原型:afx_msgvoidOnXXXX(UINTnFlags,CPointpoint);其中,point表示鼠标光标在屏幕的课后答案网(x,y)坐标;nFlags表示鼠标按钮和键盘组合情况,它可以是下列值的组合(MK前缀表示“鼠标键”):●MK_CONTROL——键盘上的Ctrl键被按下●MK_LBUTTONwww.hackshp.cn——鼠标左按钮被按下●MK_MBUTTON——鼠标中按钮被按下●MK_RBUTTON——鼠标右按钮被按下●MK_SHIFT——键盘上的Shift键被按下若想知道某个键被按下,可用对应的标识与nFlags进行逻辑“与”(&)运算,所得结果若为TRUE(非0)时,则表示该键被按下。例如,若收到了WM_LBUTTONDOWN消息,且值nFlags&MK_CONTROL是TRUE时,则表明按下鼠标左键的同时也按下Ctrl键。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.2消息和消息映射6.计时器消息应用程序是通过CWnd的SetTimer函数来设置并启动计时器的,这个函数的原型如下:UINTSetTimer(UINTnIDEvent,UINTnElapse,void(CALLBACKEXPORT*lpfnTimer)(HWND,UINT,UINT,DWORD));参数nIDEvent用来指定该计时器的标识值(不能为0),当应用程序需要多个计时器时可多次调用该函数,但每一个计时器的标识值应是唯一的,各不相同。课后答案网nElapse表示计时器的时间间隔(单位为毫秒),lpfnTimer是一个回调函数的指针,该函数由应用程序来定义,用来处理计时器WM_TIMER消息。一般情况下该参数为NULL,此时WM_TIMER消息被放入到应用程序消息队列中供CWnd对象处理。SetTimer函数成功调用后返回新计时器的标识值。当应用程序不www.hackshp.cn再使用计时器时,可调用CWnd::KillTimer函数来停止WM_TIMER消息的传送,其函数原型如下:BOOLKillTimer(intnIDEvent);其中nIDEvent和用户调用SetTimer函数设置的计时器标识值是一致的。对于WM_TIMER消息,ClassWizard会将其映射成具有下列原型的消息处理函数:afx_msgvoidOnTimer(UINTnIDEvent);通过nIDEvent可判断出WM_TIMER是哪个计时器传送的。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.2消息和消息映射7.其他窗口消息在系统中,除了用户输入产生的消息外,还有许多系统根据应用程序的状态和运行过程产生的消息,有时也需要用户进行处理。(1)WM_CREATE消息。该消息是在窗口对象创建后,Windows向视图发送的第一个消息;如果用户有什么工作需要在初始化时处理,就可在该消息处理函数中加入所需代码。但是,由于课后答案网WM_CREATE消息发送时,窗口对象还未完成,窗口还不可见,因此在该消息处理函数OnCreate内,不能调用那些依赖于窗口处于完成激活状态的Windows函数,如窗口的绘图函数等。www.hackshp.cn(2)WM_CLOSE或WM_DESTROY消息。当用户从系统菜单中关闭窗口或者父窗口被关闭时,Windows都会发送WM_CLOSE消息;而WM_DESTROY消息是在窗口从屏幕消失后发送的,因此它紧随WM_CLOSE之后。(3)WM_PAINT消息。当窗口的大小发生变化、窗口内容发生变化、窗口间的层叠关系发生变化或调用函数UpdateWindow或RedrawWindow时,系统都将产生WM_PAINT消息,表示要重新绘制窗口的内容。该消息处理函数的原型是;afx_msgvoidOnPaint();用ClassWizard映射该消息的目的是执行自己的图形绘制代码。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.3类的添加和删除1.类的添加给项目添加一个类有很多方法,例如选择“工程”→“添加工程”→“Files”菜单命令,可将外部源文件所定义的类添加到项目中。但是如果使用MFC的ClassWizard,就可以从大多数MFC类中派生一个类,并且创建的类代码自动包含MFC所必需的消息映射等机制。课后答案网用MFCClassWizard给项目添加一个类通常是按下列步骤进行的:(1)按快捷键Ctrl+W启动MFCClassWizard对话框。单击[AddClass]按钮,从弹出的下拉菜单中选择www.hackshp.cnNew命令,弹出如图3.17所示的NewClass对话框。(2)对话框中,Name是用来输入用户定义的类名,注意要以“C”字母打头,以保持与MFC标识符命名规则一致;FileName是该类的源代码文件名,单击[Change]按钮可改变源文件名称及其在磁盘中的位置;Baseclass用来指定该类的基类;DialogID是当选择CDialog作为基类时指定对话框的资源ID号。最下面的Automation是用来设置对自动化的支持。(3)单击[OK]按钮,一个新类就会自动添加到项目中。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.3类的添加和删除1.类的添加课后答案网www.hackshp.cn图3.17NewClass对话框图 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.3类的添加和删除2.类的删除当添加的类需要删除时,则需要按下列步骤进行:将VisualC++6.0打开的所有文档窗口关闭。将项目工作区窗口切换到FileView页面,展开SourceFiles和HeaderFiles结点,分别选定要删除类的对应课后答案网.h和.cpp文件,按下Delete键,删除这两个文件。选择“文件”→“关闭工作区”菜单命令,关闭项目。从实际的文件夹中删除对应的.h和.cpp文件与.clw文件。这样,当调入项目文件后,按www.hackshp.cnCtrl+W快捷键就会弹出一个对话框,询问是否重新建立ClassWizard数据文件,单击[是]按钮,出现如图3.18所示的SelectSourceFiles对话框。单击右下的[AddAll]按钮即可将.h和.cpp所包含的类删除。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.4.3类的添加和删除2.类的删除课后答案网www.hackshp.cn3.18SelectSourceFiles对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!第4章对话框和常用控件对话框是Windows应用程序中最重要的用户界面元素之一,是与用户交互的重要手段。在程序运行过程中,对话框可用于捕捉用户的输入信息或数据。对话框是一个特殊类型的窗口,任何对窗口进行的操作(如移动、最大化、最小化等)也可在对话框中实施。一般来说,在对话框中通过各种控件课后答案网(如按钮、编辑框、列表框、组合框等)来和用户进行交互,控件是在系统内部定义的用于和用户交互的基本单元。4.1对话框的使用www.hackshp.cn在VisualC++6.0应用程序中,使用一个对话框的一般过程是:①添加对话框资源;②设置对话框的属性;③添加和布局控件;④创建对话框类;⑤添加对话框代码;⑥在程序中调用对话框。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.1资源与资源标识1.资源的分类先用MFCAppWizard创建一个单文档应用程序Ex_SDI,然后项目工作区窗口切换到“ResourceView”页面,展开所有的节点,如图4.1所示。课后答案网www.hackshp.cn资源类别资源标识符图4.1Ex_SDI资源视图 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.资源的分类可以看出,一个单文档应用程序所使用的资源可分为下列几类:(1)快捷键列表(Accelerator)。一系列组合键的集合,被应用程序用来引发一个动作。该列表一般与菜单命令相关联,用来代替鼠标操作。(2)对话框(Dialog)。含有按钮、列表框、编辑框等各种控件的窗口。(3)图标(Icon)。代表应用程序显示在课后答案网Windows桌面上的位图,它同时有32x32像素和16x16像素两种规格。(4)菜单(Menu)。用户通过菜单可以完成应用程序的大部分操作。(5)字串表(StringTable)www.hackshp.cn。应用程序使用的全局字符串或其他标识符。(6)工具栏按钮(Toolbar)。工具栏外观是以一系列具有相同尺寸的位图组成的,它通常与一些菜单命令相对应,用以提高用户的工作效率。(7)版本信息(Version)。包含应用程序的版本、用户注册码等相关信息。除了上述常用资源类别外,VisualC++6.0应用程序中还可有鼠标指针、HTML等,也可以自己添加新的资源类别。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.1资源与资源标识2.ID标识符一般地,要遵循下列规则:在标识符名称中允许使用字母a~z、A~Z、0~9以及下划线。标识符名称不区分大小写字母,如new_idd与New_Idd是相同的标识符。不能以数字开头,如8BIT是不合法的标识符名。除了上述规则外,出于习惯,VisualC++还提供了一些常用的定义标识符名称的前缀供用户使用、参考,见表4.1课后答案网。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.2添加对话框资源在一个MFC应用程序中添加对话框资源,通常按下列步骤进行:(1)选择“插入”→“资源”菜单,或按快捷键Ctrl+R打开“插入资源”对话框,在对话框中可以看到资源列表中存在Dialog项,若单击Dialog项左边的“+”号,将展开对话框资源的不同类型选项,如图4.2课后答案网所示,表4.2列出各种类型的对话框资源的不同用途。www.hackshp.cn图4.2“插入资源”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.2添加对话框资源其中,[新建]按钮是用来创建一个由“资源类型”列表中指定类型的新资源,[定制]按钮是用来创建“资源类型”列表中没有的新类型的资源,[导入]按钮是用于将外部已有的位图、图标、光标或其他定制的资源添加到当前应用程序中。课后答案网表4.2对话框资源类型www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.2添加对话框资源(2)对展开的不同类型的对话框资源不作任何选择,选中“Dialog”,单击[新建]按钮,系统就会自动为当前应用程序添加了一个对话框资源,并出现如图4.3所示的界面。课后答案网控件工具栏默认标识符www.hackshp.cn对话框模板布局工具栏图4.3添加对话框资源后的开发环境 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.3设置对话框属性在对话框模板处右击鼠标,从弹出的快捷菜单中选择“属性”菜单项,出现如图4.4所示的对话框属性窗口。课后答案网www.hackshp.cn图4.4对话框属性窗口 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.3设置对话框属性可以看出,对话框的属性有General(一般)、Styles(风格)、MoreStyles(更多风格)、ExtendedStyles(扩展风格)、MoreExtendedStyles(更多扩展风格)等部分,这里仅介绍最常用的General属性,如表4.3所示。课后答案网表4.3对话框的General属性www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.3设置对话框属性将添加的对话框的属性进行以下3点修改,结果如图4.5所示:●将对话框标识符改成IDD_DIALOG_FIRST;●将对话框标题改为“我的第一个对话框”;●单击[字体]按钮,通过弹出的字体对话框将对话框内的文本设置成“宋体,9”,以使自己课后答案网的对话框和Windows中的对话框保持外观上的一致。www.hackshp.cn图4.5对话框属性修改后的界面 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.4添加和布局控件一旦对话框资源被打开或被创建,就会出现对话框编辑器,通过它可以在对话框中进行控件的添加和布局等操作。1.控件的添加对话框编辑器最初打开时,控件工具栏是随之出现的,利用此工具栏中的各个按钮可以顺利完成控件的添加。图4.6课后答案网说明了各个按钮所对应的控件类型。控件的选择静态图片静态文本编辑框组框按钮www.hackshp.cn复选框单选框组合框列表框水平滚动条垂直滚动条旋转按钮进展条滑动条热键列表视图树形视图标签动画复合编辑日期选择月历IP地址用户定制控件扩展组合框图4.6控件工具栏和各按钮含义 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.4添加和布局控件在对话框中添加一个控件的方法有下列几种:●在控件工具栏中单击某控件,此时的鼠标箭头在对话框内变成“十”字形状;在对话框指定位置单击鼠标左键,则此控件被添加到对话框的相应位置,再拖动刚添加控件的选择框可改变其大小和位置。课后答案网●在控件工具栏中单击某控件,此时的鼠标箭头对话框内变成“十”字形状;在指定位置处单击鼠标左键不放,拖动鼠标至满意位置,释放鼠标键。●用鼠标左键点中控件工具栏中的某控件,并按住鼠标左键不放;在移动鼠标www.hackshp.cn到对话框的指定位置的过程中,用户会看到一个虚线框,下面带有该控件的标记;释放鼠标左键,新添加的控件立即出现在对话框中。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.4添加和布局控件2.控件的选取控件的删除、复制和布局操作一般都要先选取控件,若选取单个控件,则可以下列方法:●用鼠标直接选取。首先保证在控件工具栏中的选择按钮()是被选中的,然后移动鼠标指针至指定的控件上,单击鼠标左键即可。课后答案网●用助记符来选取。如果控件的标题中带有下划线的字符,这个字符就是助记符,选择时直接按下该助记符键或“Alt+助记符”组合键即可。●用Tab键选取。在对话框编辑器中,系统会根据控件的添加次序自动设置相应www.hackshp.cn的Tab键次序。利用Tab键,用户可在对话框内的控件中进行选择。每按一次Tab键依次选取对话框中的下一个控件,若按住Shift键,再单击Tab键则选取上一个控件。对于多个控件的选取,可采用下列方法:●先在对话框内按住鼠标左键不放,拖出一个大的虚框,然后释放鼠标,则被该虚框所包围的控件都将被选取。●先按住Shift键不放,然后用鼠标选取控件,直到所需要的多个控件选取之后再释放Shift键。若在选取时,对已选取的控件再选取一下,则取消该控件选取。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.4添加和布局控件需要注意的是:(1)一旦单个控件被选取后,其四周由选择框包围着,选择框上还有几个(通常是八个)蓝色实心小方块,拖动它可改变控件的大小,如图4.7(a)所示。(2)多个控件被选取后,其中只有一个控件的选择框有几个蓝色实心小方块,这个控件称为主要控件,而其他控件的选择框的小方块是空心的。如图课后答案网4.7(b)所示。www.hackshp.cn(a)(b)图4.7单个控件和多个控件的选择框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.4添加和布局控件3.控件的删除、复制和布局当单个控件或多个控件被选取后,按方向键或用鼠标拖动控件的选择框可移动控件。若在鼠标拖动过程中还按住Ctrl键则复制控件。若按Del键可将选取的控件删除。当然还有其他一些编辑操作,但这些操作方法和一般的文档编辑器基本相同,这里不再重复。课后答案网对于控件的布局,对话框编辑器中提供了控件布局工具栏,如图4.8所示,它可以自动地排列对话框内的控件,并能改变控件的大小。www.hackshp.cn图4.8控件布局工具栏 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.控件的删除、复制和布局与布局工具相对应的菜单命令在“编排”菜单下,而且大部分命令均有相应的快捷键,如图4.9所示。表4.4还列出菜单命令及其相应的功能与快捷键描述。“编排”菜单不是在VisualC++6.0开发环境一开始就出现的,而是随着对话框编辑器的打开而显示的。课后答案网表4.4“编排”菜单命令的快捷键及功能描述www.hackshp.cn图4.9“编排”菜单命令项 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.4添加和布局控件4.测试对话框“编排”菜单下的Test命令或布局工具栏上的测试按钮是用来模拟所编辑的对话框的运行情况,帮助用户检验对话框是否符合用户的设计要求以及控件功能是否有效等。5.操作示例下面来向对话框添加一个静态文本控件。一个静态文本控件就是一个文本标签,如图4.10所示。右击添加的控件,从弹出的快捷菜单中课后答案网选择“属性”,出现如图4.11所示的属性对话框。www.hackshp.cn图4.11静态文本控件的属性对话框图4.10添加的静态文本控件 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.5创建对话框类在对话框资源模板的空白区域(没有其他元素或控件)内双击鼠标,将弹出如图4.12所示的对话框,询问是否为对话框资源创建一个新类。单击[OK]按钮,将弹出如图4.13所示的NewClass对话框。在Name框中输入类名CFirstDlg。Baseclass和DialogID内容是由系统自动设置的,一般无需修改。从Baseclass框的内容可以看出,用户对话框类是从基类CDialog派生而来的。单击[OK]按钮,一个基于对话框资源模板的对话框类课后答案网CFirstDlg就创建好了。此时,出现MFCClassWizard(MFC类向导)对话框。www.hackshp.cn图4.12“AddingaClass”对话框图4.13“NewClass”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.6添加对话框代码在MFCClassWizard对话框,查看“Classname”列表中是否选择了CFirstDlg,若不是,则在IDs列表中选择CFirstDlg。在Messages框中找到并选定课后答案网WM_INITDIALOG消息,如图4.14。www.hackshp.cn图4.14“MFCClassWizard”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.6添加对话框代码在Memberfunctions列表框中选择刚添加的OnInitDialog函数,单击[EditCode]按钮(或直接在函数名双击鼠标),将自动出现该函数代码编辑窗口,在此函数中添加下列代码:BOOLCFirstDlg::OnInitDialog(){CDialog::OnInitDialog();课后答案网//TODO:AddextrainitializationhereCStatic*pWnd=(CStatic*)GetDlgItem(IDC_STATIC_1);pWnd->SetWindowText("www.hackshp.cn这是我的第一个对话框!");returnTRUE;//returnTRUEunlessyousetthefocustoacontrol//EXCEPTION:OCXPropertyPagesshouldreturnFALSE}代码中,CStatic是静态文本控件的MFC类,SetWindowText是CWnd的一个成员函数,用来设置窗口的文本内容,由于控件类是CWnd的子类(派生类),因此可以使用基类的SetWindowText来改变静态文本控件显示的内容。GetDlgItem也是CWnd类的一个成员函数,用来获得对话框中控件(参数是控件的ID标识符,这里是IDC_STATIC_1)的窗口指针。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.7在程序中调用对话框在项目工作区窗口中选择ResourceView页面,双击资源“Menu”项中的IDR_MAINFRAME,则菜单编辑器窗口出现在主界面的右边,相应的Ex_SDI项目的菜单资源被显示出来,在菜单的最右一项,VisualC++为用户留出了一个空位置,用来输入新的菜单项,如图4.15所示。课后答案网www.hackshp.cn菜单的空位置图4.15Ex_SDI菜单资源 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.7在程序中调用对话框(2)在菜单的空位置上双击鼠标左键,则出现它的属性对话框,在标题框中输入“测试(&T)”,结果如图4.16所示,其中符号&用来其后面的字符作为该菜单项的助记符,这样当按住“Alt”键不放,再敲击该助记符键时,对应的菜单项就会被选中,或在菜单打开时,直接按相应的助记符键,对应的菜单项也会被选中。课后答案网www.hackshp.cn子菜单的空位置保存可见按钮图4.16Ex_SDI菜单资源 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.7在程序中调用对话框(3)单击菜单属性对话框中的“保存可见”(KeepVisible)按钮,使此属性对话框一直可见。单击“测试”菜单项下方的空位置,在属性对话框中,输入标题“对话框(&D)”,在ID框输入该菜单项的资源标识:ID_TEST_DLG,结果如图4.17所示,单击属性对话框右上角的关闭按钮。课后答案网(4)单击“测试”菜单项并按住鼠标左键不放,移动鼠标,将“测试”菜单项移到“查看”和“帮助”菜单项之间,然后释放鼠标。结果如图www.hackshp.cn4.18所示。图4.17修改菜单项属性图4.18菜单项“对话框”拖放后的位置 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.7在程序中调用对话框(5)按Ctrl+W快捷键,弹出MFCClassWizard对话框。在MessageMaps页面中,从Classname列表中选择CMainFrame,在IDs列表中选择ID_TEST_DLG,然后在Messages框中选择COMMAND消息。(6)单击[AddFunction]按钮或双击COMMAND消息,出现AddMemberFunction对话框,输入成员函数的名称。系统默认的函数名为OnTestDlg,如图4.19所示。该函数是对菜单项课后答案网ID_TEST_DLG的映射,也就是说,当在应用程序运行时,用户选择“测试”→“对话框”,则该函数OnTestDlg被调用,执行函数中的代码。www.hackshp.cn图4.19添加成员函数 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.7在程序中调用对话框(7)单击[OK]按钮,在ClassWizard的Memberfunctions列表中将列出新增加的成员函数。选择此函数,单击[EditCode]按钮(或直接双击函数名),在此成员函数中添加下列代码:voidCMainFrame::OnTestDlg(){课后答案网CFirstDlgdlg;//定义对话框类对象dlg.DoModal();//显示对话框}www.hackshp.cn代码中,DoModal是CDialog基类成员函数,用来将对话框按模式方式来显示。(8)在OnTestDlg函数的实现文件MainFrm.cpp的前面添加CFirstDlg类的包含语句,即:#include"Ex_SDI.h"#include"MainFrm.h"#include"FirstDlg.h" 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.7在程序中调用对话框(9)编译并运行。在应用程序菜单上,选择“测试”→“对话框”菜单项,将出现如图4.20的对话框,这个对话框就是刚才添加的对话框。课后答案网www.hackshp.cn图4.20对话框的显示 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.8使用无模式对话框对话框有两种类型,一种是模式对话框,另一是无模式对话框。1.模式对话框所谓“模式对话框”是指当对话框被弹出,用户必须在对话框中作出相应的操作,在退出对话框之前,对话框所在应用程序的其它操作不能继续执行。一般情况下,模式对话框会有课后答案网[OK](确定)和[Cancel](取消)按钮。单出[OK]按钮,系统认定用户在对话框中的选择或输入有效,对话框退出;单击[Cancel]按钮,对话框中的选择或输入无效,对话框退出,程序恢复原有状态。模式对话框的应用范围较广,上面示例中的对话框和平常所见到的大多数对话框www.hackshp.cn都是模式对话框。2.无模式对话框所谓“无模式对话框”是指当对话框被弹出后,一直保留在屏幕上,用户可继续在对话框所在的应用程序中进行其它操作;当需要使用对话框时,只需象激活一般窗口一样单击对话框所在的区域即可激活。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.8使用无模式对话框下面在单文档应用程序Ex_SDI中创建并使用无模式对话框,其步骤如下:(1)在该项目中添加一个对话框资源,将其标识符设为IDD_DIALOG_SECOND,标题设为“无模式对话框”,对话框字体和大小设为“宋体,9号”。(2)为IDD_DIALOG_SECOND对话框资源创建一个对话框类CSecondDlg。(3)按Ctrl+W快捷键打开MFCClassWizard,从“Classname”列表中选择CSecondDlg,在IDs列表中选择课后答案网IDOK,它是对话框中[OK]按钮的标识符,然后在Messages框中选择BN_CLICKED(单击按钮)消息,单击[AddFunction]按钮或双击BN_CLICKED消息,出现“AddMemberFunction”对话框以输入成员函数的名称,保留系统默认的函数名为www.hackshp.cnOnOK,如图4.21所示。该函数是对[OK]按钮单击消息的映射,即当用户单击此对话框中的[OK]按钮时,OnOK函数被执行。(4)修改CSecondDlg::OnOK函数中的代码。voidCSecondDlg::OnOK(){DestroyWindow();//终止对话框显示deletethis;//删除对话框,释放内存空间}代码中,DestroyWindow是对话框基类CWnd的一个成员函数,用来终止窗口。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!课后答案网www.hackshp.cn图4.21映射IDOK消息 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.8使用无模式对话框(5)用MFCClassWizard来添加[Cancel]按钮(标识符为IDCANCEL)的BN_CLICKED消息映射,并修改其映射函数CSecondDlg::OnCancel的代码。voidCSecondDlg::OnCancel(){DestroyWindow();deletethis;}课后答案网(6)将项目工作区切换到ClassView(类视图)页面,展开CMainFrame类的所有成员,双击OnTestDlg就会在文档窗口中自动定位到该函数的实现代码处,将其修改成下列代码:voidCMainFrame::OnTestDlg()www.hackshp.cn{CSecondDlg*pDlg=newCSecondDlg;//使用new来为对话框分配内存空间pDlg->Create(IDD_DIALOG_SECOND);//创建对话框pDlg->ShowWindow(SW_NORMAL);//显示对话框}代码中,Create函数可以用来以一个对话框资源来创建对话框,ShowWindow是CWnd一个成员函数,用来显示对话框,SW_NORMAL用来指定将窗口显示成一般常用的状态。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.1.8使用无模式对话框(7)在文件MainFrm.cpp的前面添加CSecondDlg类的包含语句,即:#include"FirstDlg.h"#include"SecondDlg.h"(8)编译并运行。在应用程序菜单上,多次选择“测试”→“对话框”菜单项,将会在课后答案网同一个位置中出现多个对话框,拖动这些对话框到适当位置,如图4.22所示。www.hackshp.cn图4.22无模式对话框显示的结 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2控件的创建和使用方法控件是在系统内部定义的能够完成特定功能的控制程序单元。在应用程序中使用控件不仅简化了编程,还能完成常用的各种功能。为了更好地发挥控件作用,用户还必须理解和掌握控件的属性、消息以及创建和使用的方法。4.2.1控件的创建方法控件的创建方式有以下两种:一种是在对话框模板中用编辑器指定控件,也就是说,将控件的父窗口指定为对话框,这样做的好处是明显的,因为当应用程序启课后答案网动该对话框时,Windows系统就会为对话框创建控件;而当对话框消失时,控件也随之清除。另一种方式是将控件看作是任一窗口的子窗口,并通过调用相应的Create函数来创建。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.1控件的创建方法[例Ex_DlgCtrls]使用控件的编程方式来创建一个按钮(1)选择“文件”→“新建”菜单,在弹出的新建对话框的项目类型列表中选择MFCAppWizard(exe)类型,在工程框中输入项目名课后答案网Ex_DlgCtrls,结果如图4.23所示。www.hackshp.cn图4.23MFCAppWizard的“新建”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.1控件的创建方法(2)单击[确定]按钮,在出现的“Step1”对话框中选择“基本对话”应用程序类型,单击[完成]按钮,创建一个默认的基于对话框的应用程序项目。(3)将项目工作区切换到ClassView页面,展开CEx_DlgCtrlsDlg类,右击CEx_DlgCtrlsDlg类名,弹出如图课后答案网4.24所示的快捷菜单。www.hackshp.cn图4.24弹出的快捷菜单 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.1控件的创建方法4)从快捷菜单中选择AddMemberVariable(添加成员变量),在出现的对话框中定义一个CButton类对象m_btnWnd,通常以“m_”来作为变量的开头,表示“成员”(member)的意思。如图4.25所示,单击[OK]按钮。课后答案网www.hackshp.cn图4.25添加成员变量 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!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*fontwww.hackshp.cn=this->GetFont();//获取对话框的字体m_btnWnd.SetFont(font);//设置控件字体returnTRUE;//returnTRUEunlessyousetthefocustoacontrol}由于OnInitDialog函数在对话框初始化时被调用,因此将对话框中的一些初始化代码都添加在此函数中。代码中,Create用来创建一个按钮控件,该函数第一个参数用来指定该按钮的标题,第二个参数用来指定控件的风格,第三个参数用来指定它在父窗口中的位置和大小,第四个参数用来指定父窗口指针,最后一个参数是指定该控件的标识值。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.1控件的创建方法(6)编译并运行,结果如图4.26所示。课后答案网www.hackshp.cn图4.26控件创建的结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.2控件的消息及消息映射当控件的状态发生改变时,控件就会向其父窗口发送消息,这个消息称为“通知消息”。对于每个消息,系统都会用一个MSG结构来记录,MSG具有下列结构:typedefstructtagMSG{//msgHWNDhwnd;课后答案网//接收到消息的窗口句柄UINTmessage;//消息WPARAMwParam;//消息的附加信息,它的含义取决于messageLPARAMlParam;//消息的附加信息,它的含义取决于messageDWORDwww.hackshp.cntime;//消息传送时的时间POINTpt;//消息传送时,光标所在的屏幕坐标}MSG;对于一般控件来说,其通知消息通常是一条WM_COMMAND消息,这条消息的wParam参数的低位字中含有控件标识符,wParam参数的高位字则为通知代码,lParam参数则是指向控件的句柄。而对于有些控件,其通知消息通常是一条WM_NOTIFY消息,这条消息的wParam参数是发送通知消息的控件的标识符,而lParam参数则是指向一个结构指针。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.2控件的消息及消息映射1.映射控件消息在MFC中,映射一个控件消息是非常简便的。例如下面的步骤是用来映射按钮命令消息。打开Ex_DlgCtrls应用程序项目。将项目工作区窗口切换到ResourseView页面,双击Dialog资源下的标识IDD_EX_DLGCTRLS_DIALOG,打开该对话框资源模板。删除“TODO:在这里设置对话控制。”控件,添加一个按钮控件,保留其默认属课后答案网性。如图4.27所示。www.hackshp.cn图4.27添加一个按钮 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.2控件的消息及消息映射(4)按快捷键Ctrl+W,打开MFCClassWizard对话框,查看“Classname”列表中是否选择了CEx_DlgCtrlsDlg,在IDs列表中选择IDC_BUTTON1,这是添加按钮后,系统自动为此按钮设置的默认标识符,然后在Messages框中选择BN_CLICKED消息。(5)单击[AddFunction]按钮或双击BN_CLICKED消息,出现“AddMemberFunction”对话框,在这里可以输入成员函数的名称,系统默认的函数名为课后答案网OnButton1。如图4.28所示。www.hackshp.cn图4.28添加按钮消息映射函数 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.2控件的消息及消息映射(6)单击[OK]按钮,在MFCClassWizard的“Memberfunctions”列表中将列出新增加的成员函数。选择此函数,单击[EditCode]按钮(或直接在函数名双击鼠标),开发环境的文档窗口中将自动打开该函数所在的源代码文件,并定位到该函数的实现代码处。在此成员函数中添加下列代码:voidCEx_DlgCtrlsDlg::OnButton1()课后答案网{MessageBox("你按下了"Button1"按钮!");}(7)编译并运行,当单击www.hackshp.cn[Button1]按钮时,就会执行OnButton1函数,弹出一个消息对话框。这就是按钮BN_CLICKED消息的映射过程,其他控件的消息也可以类似操作。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.2控件的消息及消息映射2.映射控件通用消息上述过程是映射一个控件的某一个消息,事实上也可通过WM_COMMAND消息的映射来处理一个或多个控件的通用消息,如下面的步骤:打开MFCClassWizard对话框,在“Classname”列表中是否选择了CEx_DlgCtrlsDlg,在IDs列表中选择CEx_DlgCtrlsDlg,在Messages框中找到并双击OnCommand,这样OnCommand消息函数就添加好了,如图课后答案网4.29所示。www.hackshp.cn图4.29添加OnCommand函数重载 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!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((nID==IDC_BUTTON1)&&(nCode==BN_CLICKED))www.hackshp.cnMessageBox("这是在OnCommand处理的结果!");returnCDialog::OnCommand(wParam,lParam);}注意:第一条if语句中,201是前面用Create创建按钮时指定的标识值。(3)编译并运行。当单击如前图4.27所示的[Button1]按钮时,就会弹出一个消息对话框。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.3控件的数据交换(DDX)和数据校验(DDV)使用MFCClassWizard可以很容易地为一个控件定义关联变量并可设置其数据范围。例如,下面的步骤是为CEx_DlgCtrlsDlg类的按钮控件IDC_BUTTON1添加并使用其关联变量m_RelBtn。(1)打开MFCClassWizard课后答案网,并切换到MemberVariables页面,如图4.30所示。www.hackshp.cn图4.30“MemberVariables”页面 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.3控件的数据交换(DDX)和数据校验(DDV)(2)选定Classname为CEx_DlgCtrlsDlg,然后在ControlIDs列表中,选定所要关联的控件ID标识符IDC_BUTTON1,双击鼠标左键或单击[AddVariable]按钮,弹出AddMemberVariable对话框,在对话框设置变量的名称、类别和数据类型,如图4.31所示。课后答案网www.hackshp.cn图4.31“AddMemberVariable”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.3控件的数据交换(DDX)和数据校验(DDV)(3)在Membervariablename框中填好与控件相关联的成员变量m_RelBtn,单击[OK]按钮,又回到MFCClassWizard对话框的MemberVariables页面中,在ControlIDs列表中出现刚才添加的控件关联变量(或直接称之为“控件变量”)。(4)单击[确定]按钮后,打开CEx_DlgCtrlsDlg类源文件,可以发现MFCClassWizard对上述操作作了以下三方面的修改。课后答案网●在Ex_DlgCtrlsDlg.h文件中,添加控件关联变量的声明,代码如下面的加粗部分://DialogData//{{AFX_DATA(CEx_DlgCtrlsDlg)www.hackshp.cnenum{IDD=IDD_EX_DLGCTRLS_DIALOG};//枚举类型CButtonm_RelBtn;CStringm_strEdit;//}}AFX_DATA 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.3控件的数据交换(DDX)和数据校验(DDV)●在Ex_DlgCtrlsDlg.cpp文件中的CEx_DlgCtrlsDlg构造函数实现代码处,添加了控件变量的一些初始代码:CEx_DlgCtrlsDlg::CEx_DlgCtrlsDlg(CWnd*pParent/*=NULL*/):CDialog(CEx_DlgCtrlsDlg::IDD,pParent){课后答案网//{{AFX_DATA_INIT(CEx_DlgCtrlsDlg)m_strEdit=_T("");//}}AFX_DATA_INITwww.hackshp.cn…} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.3控件的数据交换(DDX)和数据校验(DDV)●在Ex_DlgCtrlsDlg.cpp文件中的DoDataExchange函数体内,添加了控件的DDX/DDV代码,它们都是一些以DDV_或DDX_开头的函数调用。voidCEx_DlgCtrlsDlg::DoDataExchange(CDataExchange*pDX){CDialog::DoDataExchange(pDX);课后答案网//调用此函数作为DDX的开始//{{AFX_DATA_MAP(CEx_DlgCtrlsDlg)DDX_Control(pDX,IDC_BUTTON1,m_RelBtn);DDX_Text(pDX,www.hackshp.cnIDC_EDIT1,m_strEdit);//将IDC_EDIT1与m_strEdit进行数据交换DDV_MaxChars(pDX,m_strEdit,20);//校验m_strEdit的最大字符个数不超过20//}}AFX_DATA_MAP} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.3控件的数据交换(DDX)和数据校验(DDV)(5)当为一个控件定义一个关联的数据变量后,就可以使用CWnd::UpdateData函数实现控件数据的输入和读取。例如,将CEx_DlgCtrlsDlg::OnButton1修改成下列代码:voidCEx_DlgCtrlsDlg::OnButton1(){课后答案网UpdateData();//默认参数值是TRUEm_RelBtn.SetWindowText(m_strEdit);}www.hackshp.cn代码中,UpdateData函数只有一个为TRUE或FALSE的参数。当调用UpdateData(FALSE)时,数据由控件相关联的成员变量向控件传输,当调用UpdateData(TRUE)或不带参数的UpdateData时,数据从控件向相关联的成员变量复制。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.2.3控件的数据交换(DDX)和数据校验(DDV)(6)运行该程序,当在编辑框中输入“Hello”后,单击[Button1]按钮,OnButton1函数中的UpdateData将编辑框内容保存到m_strEdit变量中,从而执行下一条语句后按钮的名称就变成了编辑框控件中的内容“Hello”,其结果如图4.33所示课后答案网www.hackshp.cn图4.33使用控件的数据成员变量 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3常用控件根据控件的特性和功能,一般可将其分为三类:Windows公共控件、ActiveX控件以及MFC新增的一些控件等。表4.5列出了本书所用到的常用控件类。表4.5常用控件类课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1静态控件和按钮1.静态控件一个静态控件是用来显示一个字符串、框、矩形、图标、位图或增强的图元文件。它可以被用来作为标签、框或用来分隔其它的控件。一个静态控件一般不接收用户输入,也不产生通知消息。在对话框编辑器的控件工具栏中,属于静态控件的有:静态文本()、组框()和静态图片()三种。其中,静态图片控件的一般属性对话框如图课后答案网4.34所示。表4.6列出了其一般属性的各个项的含义。在属性对话框中,用户可以选择图片“类型”、“图像”两个组合框中的有关选项内www.hackshp.cn容,并可将应用程序资源中的图标、位图等内容显示在该静态图片控件中。另外,用户还可设置其风格来改变控件的外观以及图像在控件的位置等。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.静态控件课后答案网图4.34静态图片控件的General属性对话框www.hackshp.cn表4.6静态图片控件的General和Style属性 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1静态控件和按钮2.按钮在Windows中所用的按钮是用来实现一种开与关的输入,常见的按钮有3种类型:按键按钮、单选按钮、复选框按钮,如图4.37所示。课后答案网www.hackshp.cn图4.37按钮的不同类型 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1静态控件和按钮2.按钮(1)按键按钮。按键按钮通常可以立即产生某个动作,执行某个命令,因此也常被称为命令按钮。按键按钮有两种风格:标准按键按钮和默认按键按钮。(2)单选按钮。单选按钮的外形是在文本前有一个圆圈,当它被选中时,单选按钮中就标上一个黑点,它可分为一般和自动两种类型。在自动类型中,用户若选课后答案网中同组按钮中的某个单选按钮,则其余的单选按钮的选中状态就会清除,保证了多个选项始终只有一个被选中。(3)复选框。复选框的外形是在文本前有一个空心方框,当它被选中时,复选框www.hackshp.cn中就加上一个“”标记,通常复选框只有选中和未选中两种状态,若复选框前面有一个灰色是“”,则这样的复选框是三态复选框,如图4.37的Check2,它表示复选框的选择状态是“不确定”。设定成三态复选框的方法是在复选框属性对话框的Style页面中选中“(状态)Tri-state”项。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1静态控件和按钮3.按钮的消息在按钮映射的消息中,常见的只有两个:BN_CLICKED(单击按钮)和BN_DOUBLE-CLICKED(双击按钮)。4.按钮选中操作最常用的按钮操作是设置或获取一个按钮或多个按钮的选中状态。CButton类的成员函数SetCheck和GetCheck分别用来设置或获取指定按钮的选中状态,其原型如下:课后答案网voidSetCheck(intnCheck);intGetCheck()const;其中,nCheck和www.hackshp.cnGetCheck函数返回的值可以是:0表示不选中,1表示选中,2表示不确定(仅用于三态按钮)。而对于同组多个单选按钮的选中状态的设置或获取,需要使用通用窗口类CWnd的成员函数CheckRadioButton和GetCheckedRadioButton,它们的原型如下:voidCheckRadioButton(intnIDFirstButton,intnIDLastButton,intnIDCheckButton);intGetCheckedRadioButton(intnIDFirstButton,intnIDLastButton);其中,nIDFirstButton和nIDLastButton分别指定同组单选按钮的第一个和最后一个按钮ID值,nIDCheckButton用来指定要设置选中状态的按钮ID值,函数GetCheckedRadioButton返回被选中的按钮ID值。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1静态控件和按钮5.示例:制作问卷调查问卷调查是日常生活中经常遇到的调查方式。例如,图4.38就是一个问卷调查对话框,它针对“上网”话题提出了三个问题,每个问题都有四个选项,除最后一个问题外,其余都是单项选择。课后答案网www.hackshp.cn图4.38上网问卷调查对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1静态控件和按钮[例Ex_Research]制作问卷调查1)创建并设计对话框(1)创建一个默认的对话框应用程序Ex_Research。(2)VisualC++会自动打开对话框编辑器并显示对话框资源模板。单击对话框编辑器工具栏上的切换网格按钮,显示对话框网格,将对话框标题改为“上网问卷课后答案网调查”。(3)调整对话框的大小,删除对话框中间的“TODO:在这里设置对话控制。”静态www.hackshp.cn文本控件,将[确定]和[取消]按钮移至对话框的下方,并向对话框中添加组框(Group)控件,然后调整其大小和位置。(4)右击添加的组框控件,从弹出的快捷菜单中选择“属性”菜单,出现该控件的属性对话框,在属性对话框窗口中可以看到它的ID为默认的IDC_STATIC。将其Caption属性内容由“Static”改成“你的年龄”。在组框控件的Styles属性中,“水平对齐”属性用来指定文本在顶部的左边(Left)、居中(Center)还是右边(Right)。默认(Default)选项表示左对齐。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1[例Ex_Research]1)创建并设计对话框(5)在组框内添加4个单选按钮,默认的ID依次为IDC_RADIO1、IDC_RADIO2、IDC_RADIO3和IDC_RADIO4。在其属性对话框中将ID属性内容分别改成IDC_AGE_L18、IDC_AGE_18T27、IDC_AGE_28T38和IDC_AGE_M38,然后将其“标题”Caption课后答案网属性内容分别改成“<18”、“18-27”、“28-38”和“>38”,最后调整位置,结果如图www.hackshp.cn4.39所示。图4.39添加的组框和单选按钮 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1[例Ex_Research]1)创建并设计对话框(6)接下来添加一个静态文本,标题设为“你使用的接入方式:”,然后在其下再添加4个单选按钮,标题分别是“FTTL或ADSL”、“单位LAN”、“拨号56K”和“其他”,课后答案网并将相应的ID属性依次改成:IDC_CM_FTTL、IDC_CM_LAN、IDC_CM_56K和IDC_CM_OTHER。用对话框编辑器工具栏的按钮命令调整控件左右之间的间距,结果如图www.hackshp.cn4.40所示。图4.40再添加单选框图 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1[例Ex_Research]1)创建并设计对话框(7)在对话框的下方,再添加一个组框控件,其标题为“你上网主要是”。然后添加四个复选框,其标题分别为“收发邮件”、“浏览资料”、“聊天游戏”和“其他”,ID分别为IDC_DO_POP、IDC_DO_READ、IDC_DO_GAME和IDC_DO_OTHER。结果如图4.41所示。课后答案网www.hackshp.cn图4.41三个问题全部添加后的对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1[例Ex_Research]1)创建并设计对话框(8)单击工具栏上的测试对话框按钮。对话框测试后,可以发现:顺序添加的这8个单选按钮全部变成一组,也就是说,在这组中只有一个单选按钮被选中,这不符合我们的本意。解决这个问题的最好的办法是将每一组中的第一个单选按钮的Group(组)属性选中。课后答案网(9)分别将这二个问题中的第一个单选按钮的Group(组)属性均选中。如图4.42所示是对第二个问题设置的结果。www.hackshp.cn图4.42选中“Group”属性 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1[例Ex_Research]1)创建并设计对话框(10)单击对话框编辑器工具栏上的切换辅助线按钮,然后将对话框中的控件调整到辅助线以内,并适当对其他控件进行调整。这样,整个问卷调查的对话框就设计好了,单击工具栏上的测试对话框按钮课后答案网进行测试。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1[例Ex_Research](2)完善代码(1)将项目工作区切换到ClassView(类视图)页面,展开CEx_ResearchDlg类的所有成员,双击OnInitDialog函数名就会在文档窗口中自动定位到该函数的实现代码处,在此函数添加下列初始化代码:BOOLCEx_ResearchDlg::OnInitDialog(){课后答案网CDialog::OnInitDialog();…CheckRadioButton(IDC_AGE_L18,www.hackshp.cnIDC_AGE_M38,IDC_AGE_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标识符)的窗口指针。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1[例Ex_Research](2)完善代码(2)用MFCClassWizard在CEx_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;www.hackshp.cn//获取第二个问题的用户选择str=str+"n你使用的接入方式:";nID=GetCheckedRadioButton(IDC_CM_FTTL,IDC_CM_OTHER);GetDlgItemText(nID,strCtrl);//获取指定控件的标题文本str=str+strCtrl;//获取第三个问题的用户选择str=str+"n你上网主要是:n";UINTnCheckIDs[4]={IDC_DO_POP,IDC_DO_READ,IDC_DO_GAME,IDC_DO_OTHER};CButton*pBtn; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!for(inti=0;i<4;i++){pBtn=(CButton*)GetDlgItem(nCheckIDs[i]);if(pBtn->GetCheck()){pBtn->GetWindowText(strCtrl);str=str+strCtrl;课后答案网str=str+"";}}www.hackshp.cnMessageBox(str);CDialog::OnOK();}代码中,GetDlgItemText是CWnd类成员函数,用来获得对话框(或其他窗口)中的指定控件的窗口文本。在单选按钮和复选框中,控件的窗口文本就是它们的标题属性内容。该函数有两个参数,第一个参数用来指定控件的标识,第二个参数是返回的窗口文本。后面的函数GetWindowText的作用与GetDlgItemText相同,也是获取窗口的文本内容。不过,GetWindowText使用更加广泛,但要注意这两个函数在使用上的不同。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.1[例Ex_Research](2)完善代码(3)编译并运行,出现“上网问卷调查”对话框,当回答问题后,按[确定]按钮,出现如图4.43所示的消息对话框,显示用户选择的内容。课后答案网www.hackshp.cn图4.43显示用户选择的内容 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2编辑框和旋转按钮控件1.编辑框编辑框是一个让用户从键盘输入和编辑文本的矩形窗口,用户可以通过它,很方便地输入各种文本、数字或者口令,也可使用它来编辑和修改简单的文本内容。当编辑框被激活且具有输入焦点时,就会出现一个闪动的插入符(又可称为文本光标),表明当前插入点的位置。课后答案网(1)编辑框的属性和通知消息。用对话框编辑器可以方便地设置编辑框的属性和风格,如图4.44所示。表4.7还列出其中各项的含义。当编辑框的文本修改或者被滚动时,会向其父www.hackshp.cn窗口发送一些消息,如表4.8所示。图4.44编辑框的属性对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!表4.7编辑框的Style属性课后答案网www.hackshp.cn表4.8编辑框的通知消息 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2编辑框和旋转按钮控件1.编辑框(2)编辑框的基本操作。由于编辑框的形式多样,用途各异,因此下面针对编辑框的不同用途,分别介绍一些常用操作,以实现一些基本功能。①口令设置。口令设置在编辑框中不同于一般的文本编辑框,用户输入的每个字符都被一个特殊的字符代替显示,这个特殊的字符称为口令字符。默认的口令字符是“*”,应用程序可以用成员函数课后答案网CEdit::SetPasswordChar来定义自己的口令字符,其函数原型如下:voidSetPasswordChar(TCHARch);其中,参数ch表示设定的口令字符;当www.hackshp.cnch=0时,编辑框内将显示实际字符。②选择文本。当在编辑框中编辑文本时,往往需要选定文本作为整体进行各种编辑操作。用户可以用鼠标或键盘来选择文本。用鼠标来选择文本的操作方法是:在要选择的文本的一端按下鼠标左键并拖动鼠标,到另一端释放鼠标键。用键盘来选择文本的方法是:在按光标方向移动键的同时,按住Shift键。③设置编辑框的页面边距。设置编辑框的页面边距可以使文本在编辑框显示更具满意效果,这在多行编辑框中尤为重要,应用程序可通过调用成员函数CEdit::SetMargins来实现,这个函数的原型如下:voidSetMargins(UINTnLeft,UINTnRight);其中,参数nLeft和nRight分别用来指定左、右边距的像素大小。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2编辑框和旋转按钮控件④剪帖板操作。编辑框通过CEdit类的Copy、Paste和Cut成员函数来实现文本的复制、粘贴、剪切的操作,并还自动支持键盘快捷操作,其对应的快捷健分别为Ctrl+C、Ctrl+V和Ctrl+X。若应用程序调用CEdit::Undo函数时,则还可撤消当前的操作,再调用一次该函数,则恢复刚才的操作。例如下面的代码:if(m_Edit.CanUndo())m_Edit.Undo();⑤获取多行编辑框文本。获取多行编辑框控件的文本可以有两种方法:一种是使用DDX/DDV,当将编辑框控件所关联的变量类型选定为CString(字符串类)后,则不管多行编辑框的文本有多少都可用此变量来保存,从而能课后答案网简单地解决多行文本的读取。但这种方法不能单独获得多行编辑框中的某一行文本。另一种方法是使用编辑框CEdit类的相关成员函数来获取文本。例如,下面的代码将显示编辑框中第二行的文本内容:charstr[100];www.hackshp.cnif(m_Edit.GetLineCount()>=2)//判断多行编辑框的文本是否有两行以上{intnChars;nChars=m_Edit.LineLength(m_Edit.LineIndex(1));//获取第二行文本的字符个数//0表示第一行,1表示第二行,依次类推。LineIndex用于将文本行转换成//能被LineLength识别的索引m_Edit.GetLine(1,str,nChars);//获取第二行文本str[nChars]="";MessageBox(str);}代码中,由于调用GetLine获得某行文本内容时,并不能自动在文本后添加文本的结束符‘’,因此需要首先获得某行文本的字符数,然后设置文本的结束符。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2编辑框和旋转按钮控件2.旋转按钮控件“旋转按钮控件”(也称为上下控件)是一对箭头按钮,用户点击它们来增加或减小某个值,比如一个滚动位置或显示在相应控件中的一个数字。如图4.45所示。伙伴窗口课后答案网旋转按钮www.hackshp.cn图4.45旋转按钮控件及其伙伴窗口 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2编辑框和旋转按钮控件2.旋转按钮控件(1)旋转按钮控件常用的风格。旋转按钮控件有许多风格,它们都可以通过旋转按钮控件属性对话框进行设置,如图4.46所示,其中各项的含义见表4.9。课后答案网www.hackshp.cn图4.46旋转按钮控件属性对话框表4.9旋转按钮控件的Style属性 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2编辑框和旋转按钮控件2.旋转按钮控件(2)旋转按钮控件的基本操作。MFC的CSpinButtonCtrl类提供了旋转按钮控件的各种操作函数,使用它们可以进行基数、范围、位置设置和获取等基本操作。成员函数SetBase是用来设置其基数的,这个基数值决定了伙伴窗口显示的数字是十进制还是十六进制。如果成功则返回先前的基数值,如果给出的是一个无效的基数则返回一个非零值。函数的原型如下:课后答案网intSetBase(intnBase);其中参数nBase表示控件的新的基数,如10表示十进制,16表示十六进制等。与此函数相对应的成员函数www.hackshp.cnGetBase是获取旋转按钮控件的基数。成员函数SetPos和SetRange分别用来设置旋转按钮控件的当前位置和范围,它们的函数原型如下:intSetPos(intnPos);voidSetRange(intnLower,intnUpper);其中,参数nPos表示控件的新位置,它必须在控件的上限和下限指定的范围之内。nLower和nUpper表示控件的上限和下限。任何一个界限值都不能大于0x7fff或小于-0x7fff。与这两个函数相对应的成员函数GetPos和GetRange分别用来获取旋转按钮控件的当前位置和范围。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2编辑框和旋转按钮控件2.旋转按钮控件(3)旋转按钮控件的通知消息。旋转按钮控件的通知消息只有一个:UDN_DELTAPOS,它是在当控件的当前数值将要改变时向其父窗口发送的。3.示例:用对话框输入学生成绩在一个简单的学生成绩结构中,常常有学生的姓名、学号以及三门成绩等内容。课后答案网为了能够输入这些数据,需要设计一个对话框,如图4.47所示。www.hackshp.cn图4.47学生成绩输入对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2编辑框和旋转按钮控件[例Ex_Input]用对话框输入学生成绩1)创建并设计对话框(1)用MFCAppWizard(exe)创建一个默认的单文档应用程序Ex_Input。(2)添加一个新的对话框资源,通过其属性对话框,将ID标识符改为IDD_INPUT,标题为“学生成绩输入”,将对话框字体改为“宋体,9号”。将[OK]和[Cancel]按钮标题改为“确定”和“取消”。课后答案网(3)显示对话框网格,调整对话框的大小,然后将[确定]和[取消]按钮移至对话框的下方。(4)向对话框添加如表4.10所示的控件,调整控件的位置,结果如图4.48所示。www.hackshp.cn表4.10学生成绩输入对话框添加的控件图4.48设计的学生成绩输入对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2[例Ex_Input]2)完善CInputDlg类代码(1)在MFCClassWizard的MemberVariables页面中,确定Classname中是否已选择了CInputDlg,选中所需的控件ID标识符,双击鼠标或单击AddVariables按钮。依次为表4.11控件增加成员变量。课后答案网表4.11控件变量www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2[例Ex_Input]2)完善CInputDlg类代码(2)用MFCClassWizard为CInputDlg添加WM_INITDIALOG消息映射,添加下列代码:BOOLCInputDlg::OnInitDialog(){CDialog::OnInitDialog();课后答案网m_spinScore1.SetRange(0,100);//设置旋转按钮控件范围m_spinScore2.SetRange(0,100);m_spinScore3.SetRange(www.hackshp.cn0,100);returnTRUE;//returnTRUEunlessyousetthefocustoacontrol} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2[例Ex_Input]2)完善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_fScore1<0.0)m_fScore1=0.0f;www.hackshp.cnif(m_fScore1>100.0)m_fScore1=100.0f;UpdateData(FALSE);//将变量的内容显示在控件中*pResult=0;}代码中,NM_UPDOWN结构用于反映旋转控件的当前位置(由成员iPos指定)和增量大小(由成员iDelta指定)。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2[例Ex_Input]3)调用对话框(1)打开Ex_Input单文档应用程序的菜单资源,添加顶层菜单项“测试(&T)”,在其下添加一个菜单项“学生成绩输入(&I)”,ID为ID_TEST_INPUT。(2)用MFCClassWizard为CMainFrame类添加菜单项ID_TEST_INPUT的COMMAND消息映射,取默认的映射函数名,并添加下列代码:voidCMainFrame::OnTestInput()课后答案网{CInputDlgdlg;if(IDOK==dlg.DoModal()){//获取对话框数据www.hackshp.cnCStringstr;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”就是一个格式符,每一个格式符依次对应于该函数的后面参数表中的参数项。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.2[例Ex_Input]3)调用对话框(3)在文件MainFrm.cpp的前面添加CInputDlg类的头文件包含:#include"MainFrm.h"#include"InputDlg.h"(4)编译并运行,在应用程序的菜单上,选择“测试”课后答案网→“学生成绩输入”菜单项,将弹出如前图4.47所示的对话框。单击成绩1的旋转按钮控件将以0.5增量来改变它的伙伴窗口的数值。而成绩2和成绩3的旋转按钮控件由于设置了Setbuddyinteger属性,因此它按默认增量www.hackshp.cn1自动改变伙伴窗口的数值。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.3列表框1.列表框的风格和消息按性质来分,列表框有单选、多选、扩展多选以及非选四种类型,如图4.50所示。课后答案网www.hackshp.cn图4.50不同类型的列表框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.列表框的风格和消息列表框还有一系列其它风格,用来定义列表框的外观及操作方式,这些风格可在如图4.51所示的列表框属性对话框中设置。表4.12列出Style各项的含义。课后答案网www.hackshp.cn图4.51列表框的属性对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.列表框的风格和消息表4.12列表框的Style属性课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.列表框的风格和消息当列表框中发生了某个动作,如用户双击选择了列表框中某一项时,列表框就会向其父窗口发送一条通知消息。常用的通知消息如表4.13所示。课后答案网表4.13列表框的通知消息www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.3列表框2.列表框的基本操作需要注意的是:列表框的项除了用字符串来标识外,还常常通过索引来确定。索引表明项目在列表框中排列的位置,它是以0为基数的,即列表框中第一项的索引是0,第二项的索引是1,依次类推。(1)添加列表项。列表框创建时是一个空的列表,需要用户添加或插入一些列表项。CListBox课后答案网类成员函数AddString和InsertString分别用来添加列表项,其函数原型如下:intAddString(LPCTSTRlpszItem);intInsertString(intwww.hackshp.cnnIndex,LPCTSTRlpszItem);其中,列表项的字符串文本由参数lpszItem来指定。虽然两个函数成功调用时都将返回列表项在列表框的索引,错误时返回LB_ERR,空间不够时,返回LB_ERRSPACE。但InsertString函数不会对列表项进行排序,不管列表框控件是否具有sort属性,只是将列表项插在指定索引的列表项之前,若nIndex等于-1,则列表项添加在列表框末尾。而AddString函数当列表框控件具有sort属性时会自动将添加的列表项进行排序。函数原型中,LPCTSTR类型用来表示一个常值字符指针,这里可以将其理解成是一个常值字符串类型。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.列表框的基本操作(2)删除列表项。CListBox类成员函数DeleteString和ResetContent分别用来删除指定的列表项和清除列表框所有项目。它们的函数原型如下:intDeleteString(UINTnIndex);//nIndex指定要删除的列表项的索引voidResetContent();课后答案网(3)查找列表项。为了保证列表项不会重复地添加在列表框中,有时还需要对列表项进行查找。www.hackshp.cnCListBox类成员函数FindString和FindStringExact分别用来在列表框中查找所匹配的列表项。其中,FindStringExact的查找精度最高。intFindString(intnStartAfter,LPCTSTRlpszItem)const;intFindStringExact(intnIndexStart,LPCTSTRlpszFind)const;其中,lpszFind和lpszItem指定要查找的列表项文本,nStartAfter和nIndexStart指定查找的开始位置,若为-1,则从头至尾查找。查到后,这两个函数都将返回所匹配列表项的索引,否则返回LB_ERR。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.列表框的基本操作(4)列表框的单项选择。当选中列表框中某个列表项,用户可以使用CListBox::GetCurSel来获取这个结果,与该函数相对应的CListBox::SetCurSel函数是用来设定某个列表项呈选中状态(高亮显示)。intGetCurSel()const;//返回当前选择项的索引intSetCurSel(intnSelect);其中,nSelect指定要设置的列表项索引,错误时这两个函数都将返回课后答案网LB_ERR。若要获取某个列表项的字符串,可使用下列函数:intGetText(intnIndex,LPTSTRlpszBuffer)const;voidGetText(intwww.hackshp.cnnIndex,CString&rString)const;其中,nIndex指定列表项索引,lpszBuffer和rString是用来存放列表项文本。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.列表框的基本操作(5)列表框的多项选择。当在列表框的Style属性对话框中选中多选(Multiple)或扩展多选(Extended)类型后,就可以在列表框中进行多项选择。要想获得选中的多个选项,需要用MFCClassWizard映射列表框控件的LBN_SELCHANGE消息,并添加类似下面的一些代码:voidCListBoxDlg::OnSelchangeList1(){intnCount=m_list.GetSelCount();课后答案网//获取用户选中的项数if(nCount==LB_ERR)return;int*buffer=newint[nCount];//开辟缓冲区m_list.GetSelItems(nCount,buffer);//将各个选项的索引号内容存放在缓冲区中CStringallStr=NULL,str;www.hackshp.cnfor(inti=0;iEnableWindow(FALSE);//使[删除]按钮灰显returnTRUE;//returnTRUEunlessyousetthefocustoacontrol} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_City](5)打开MFCClassWizard,切换到MesssageMaps页面,为按钮IDC_BUTTON_ADD添加BN_CLICKED的消息映射,并增加下列代码:voidCCityDlg::OnButtonAdd(){if(!IsValidate())return;intnIndex=m_ListBox.FindStringExact(-1,m_strCity);课后答案网if(nIndex!=LB_ERR){MessageBox("该城市已添加!");return;}nIndex=m_ListBox.AddString(m_strCity);m_ListBox.SetItemData(www.hackshp.cnnIndex,m_dwZipCode);}(6)用MFCClassWizard为按钮IDC_BUTTON_DEL添加BN_CLICKED的消息映射,并增加下列代码:voidCCityDlg::OnButtonDel(){intnIndex=m_ListBox.GetCurSel();if(nIndex!=LB_ERR)m_ListBox.DeleteString(nIndex);elseGetDlgItem(IDC_BUTTON_DEL)->EnableWindow(FALSE);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_City](7)用MFCClassWizard为列表框IDC_LIST1添加LBN_SELCHANGE(当前选择项发生改变)的消息映射,并增加下列代码。这样,当单击列表框的城市名时,将会在编辑框中显示出城市名和邮政编码。voidCCityDlg::OnSelchangeList1(){intnIndex=m_ListBox.GetCurSel();课后答案网if(nIndex!=LB_ERR){m_ListBox.GetText(nIndex,m_strCity);www.hackshp.cnm_dwZipCode=m_ListBox.GetItemData(nIndex);UpdateData(FALSE);//使用当前列表项所关联的内容显示在控件上GetDlgItem(IDC_BUTTON_DEL)->EnableWindow(TRUE);}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_City]3.调用对话框(1)打开Ex_City单文档应用程序的菜单资源,添加顶层菜单项“测试(&T)”,在其下添加一个菜单项“城市邮政编码(&C)”,ID为ID_TEST_CITY。(2)用MFCClassWizard为CMainFrame类添加菜单项ID_TEST_CITY的COMMAND消息映射,取默认的映射函数名,并添加下列代码:课后答案网voidCMainFrame::OnTestCity(){www.hackshp.cnCCityDlgdlg;dlg.DoModal();}(3)在文件MainFrm.cpp的前面添加CCityDlg类的头文件包含:#include"MainFrm.h"#include"CityDlg.h"(4)编译运行后,在应用程序的菜单上,选择“测试”→“城市邮政编码”菜单项,将弹出如前图4.52所示的对话框。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.4组合框作为用户输入的接口,前面的列表框和编辑框各有其优点。例如,列表框中可列出用户所需的各种可能的选项,这样一来,用户不需要记住这些项,只需进行选择操作即可,但用户却不能输入列表框中列表项之外的内容。虽然编辑框能够允许用户输入内容,但却没有列表框的选择操作。于是很自然地产生这样的想法:把常用的项列在列表框中以供选择,而同时提供编辑框,允许用户输入列表框中课后答案网所没有的新项。组合框正是这样的一种控件,它结合列表框和编辑框的特点,取二者之长,从而完成较为复杂的输入功能。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.4组合框1.组合框的风格类型和消息按照组合框的主要风格特征,可把组合框分为3类:简单组合框、下拉式组合框、下拉式列表框。简单组合框和下拉式组合框都包含有列表框和编辑框,但是简单组合框中的列表框不需要下拉,是直接显示出来的,而当用户单击下拉式组合框中的下拉按钮时,下拉的列表框才被显示出来。下拉式列表框虽然具有下拉式的列表,却没有课后答案网文字编辑功能。如图4.54所示。www.hackshp.cn图4.54组合框的类型 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.4组合框组合框还有其他一些风格,这些风格可在如图4.55所示的组合框的属性对话框中设置。其各项含义见表4.16。课后答案网图4.55组合框的属性对话框www.hackshp.cn表4.16组合框的Style属性 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.4组合框在组合框的通知消息中,有的是列表框发出的,有的是编辑框发出的,如表4.17所示。表4.17组合框的通知消息课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.4组合框2.组合框常见操作组合框的操作大致分为两类,一类是对组合框中的列表框进行操作,另一类是对组合框中的编辑框进行操作。这些操作都可以调用CComboBox成员函数来实现,见表4.18。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!表4.18CComboBox类常用成员函数课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.4组合框3.示例:城市邮政编码和区号前面的示例中,只是简单的涉及到城市名和邮政编码的对应关系。实际上,城市名还和区号一一对应,为此本例需要设计这样的对话框,如图4.56所示。单击[添加]按钮将城市名、邮政编码和区号添加到组合框中,在添加前同样需要进行重复性的判断。选择组合框中的城市名,将在编辑框中显示出邮政编码和区课后答案网号,单击[修改]按钮,将以城市名作为组合框的查找关键字,找到后修改其邮政编码和区号内容。www.hackshp.cn图4.56城市邮政编码和区号 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_CityCode]创建并使用城市邮政编码和区号对话框1.添加并设计对话框(1)用MFCAppWizard(exe)创建一个默认的单文档应用程序Ex_CityCode。(2)向应用程序中添加一个对话框资源IDD_CITYZONE,标题定为“城市邮政编码和区号”,字体设为“宋体,课后答案网9号”,创建此对话框类为CCityZoneDlg。(3)删除原来的[Cancel]按钮,将[OK]按钮标题改为“退出”。(4)打开对话框网格,参看图www.hackshp.cn4.56的控件布局,为对话框添加如表4.19所示的一些控件。表4.19城市邮政编码和区号对话框添加的控件 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_CityCode]2.完善CCityZoneDlg类代码(1)打开MFCClassWizard的MemberVariables页面,看看Classname是否是CCityZoneDlg,然后选中所需的控件ID标识符,双击鼠标或单击AddVariables按钮。依次为下列控件增加成员变量。如表4.20所示。课后答案网表4.20控件变量www.hackshp.cn(2)将项目工作区切换到ClassView页面,右击CCityZoneDlg类名,从弹出的快捷菜单中选择“AddMemberFunction”,弹出AddMemberFunction对话框,在FunctionType(函数类型)框中输入BOOL,在FunctionDeclaration(函数声明)框中输入IsValidate,单击[OK]按钮。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_CityCode]2.完善CCityZoneDlg类代码(3)在CCityZoneDlg::IsValidate函数输入下列代码:BOOLCCityZoneDlg::IsValidate(){UpdateData();m_strCity.TrimLeft();课后答案网if(m_strCity.IsEmpty()){MessageBox("城市名输入无效!");returnFALSE;}m_strZip.TrimLeft();www.hackshp.cnif(m_strZip.IsEmpty()){MessageBox("邮政编码输入无效!");returnFALSE;}m_strZone.TrimLeft();if(m_strZone.IsEmpty()){MessageBox("区号输入无效!");returnFALSE;}returnTRUE;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_CityCode]2.完善CCityZoneDlg类代码(4)打开MFCClassWizard,切换到MesssageMaps页面,为按钮IDC_BUTTON_ADD添加BN_CLICKED的消息映射,并增加下列代码:voidCCityZoneDlg::OnButtonAdd(){课后答案网if(!IsValidate())return;intnIndex=m_ComboBox.FindStringExact(-1,m_strCity);if(nIndex!=CB_ERR)www.hackshp.cn{MessageBox("该城市已添加!");return;}CStringstrData;strData.Format(“%s,%s”,m_strZip,m_strZone);//将邮政编码和区号合并为一个字符串m_ComboBox.SetItemDataPtr(nIndex,newCString(strData));} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_CityCode]2.完善CCityZoneDlg类代码(5)用MFCClassWizard为按钮IDC_BUTTON_CHANGE添加BN_CLICKED的消息映射,并增加下列代码:voidCCityZoneDlg::OnButtonChange(){课后答案网if(!IsValidate())return;intnIndex=m_ComboBox.FindStringExact(-1,m_strCity);if(nIndex!=CB_ERR)www.hackshp.cn{delete(CString*)m_ComboBox.GetItemDataPtr(nIndex);CStringstrData;strData.Format("%s,%s",m_strZip,m_strZone);m_ComboBox.SetItemDataPtr(nIndex,newCString(strData));}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_CityCode]2.完善CCityZoneDlg类代码(7)用MFCClassWizard为组合框IDC_COMBO1添加CBN_SELCHANGE(当前选择项发生改变)的消息映射,并增加下列代码:voidCCityZoneDlg::OnSelchangeCombo1(){intnIndex=m_ComboBox.GetCurSel();课后答案网if(nIndex!=CB_ERR){m_ComboBox.GetLBText(nIndex,m_strCity);www.hackshp.cnCStringstrData=*(CString*)m_ComboBox.GetItemDataPtr(nIndex);//分解字符串intn=strData.Find(",");m_strZip=strData.Left(n);//前面的n个字符m_strZone=strData.Mid(n+1);//从中间第n+1字符到未尾的字符串UpdateData(FALSE);}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_CityCode]2.完善CCityZoneDlg类代码(7)用MFCClassWizard为对话框添加WM_DESTROY的消息映射,并增加下列代码:voidCCityZoneDlg::OnDestroy()//此消息是当对话框关闭时发送的{课后答案网for(intnIndex=m_ComboBox.GetCount()-1;nIndex>=0;nIndex--){//删除所有与列表项相关联的www.hackshp.cnCString数据,并释放内存delete(CString*)m_ComboBox.GetItemDataPtr(nIndex);}CDialog::OnDestroy();} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_CityCode]3.调用对话框(1)打开Ex_CityCode单文档应用程序的菜单资源,添加顶层菜单项“测试(&T)”,在其下添加一个菜单项“城市邮政编码和区号(&Z)”,ID为ID_TEST_CITYZONE。(2)用MFCClassWizard为CMainFrame类添加菜单项ID_TEST_CITYZONE的COMMAND消息映射,取默认的映射函数名,并添加下列代码:课后答案网voidCMainFrame::OnTestCityzone(){CCityZoneDlgwww.hackshp.cndlg;dlg.DoModal();}(3)在文件MainFrm.cpp的前面添加CCityZoneDlg类的头文件包含:#include"MainFrm.h"#include"CityZoneDlg.h"(4)编译运行并测试。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.5进展条、滚动条和滑动条1.进展条进展条是一个如图4.58所示的控件。除了能表示一个过程进展情况外,使用进展条还可表明温度、水平面或类似的测量值。课后答案网图4.58进展条(1)进展条的风格。打开进展条的属性对话框,如图4.59所示,可以看到它的风格属性并不是很多。其中,边框(Border)用来指定进展条是否有边框,垂直(Vertical)用来指定进展是水平还是垂直的,若选中,则为垂直的。平滑www.hackshp.cn(Smooth)表示平滑地填充进展条,若不选中则表示将用块来填充,就像图4.58那样。图4.59进展条Style属性对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.5进展条、滚动条和滑动条1.进展条(2)进展条的基本操作。进展条的基本操作有:设置其范围、当前位置、设置增量等。这些操作都是通过CProgressCtrl类的相关成员函数来实现的。intSetPos(intnPos);intGetPos();这两个函数分别用来设置和获取进展条的当前位置。需要说课后答案网明的是,这个当前位置是指在SetRange中的上限和下限范围之间的位置。voidSetRange(shortnLower,shortnUpper);voidSetRange32(intnLower,intnUpper);voidGetRange(int&www.hackshp.cnnLower,int&nUpper);它们分别用来设置和获取进展条范围的上限和下限值。一旦设置后,还会重画此进展条来反映新的范围。成员函数SetRange32为进展条设置32位的范围。参数nLower和nUpper分别表示范围的下限(默认值为0)和上限(默认值为100)。intSetStep(intnStep);该函数用来设置进展条的步长并返回原来的步长,默认步长为10。intStepIt();该函数将当前位置向前移动一个步长并重画进展条以反映新的位置。函数返回进展条上一次的位置。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.5进展条、滚动条和滑动条2.滚动条滚动条是一个独立的窗口,虽然它具有直接的输入焦点,但却不能自动地滚动窗口内容,因此,它的使用受到一定的限制。根据滚动条的走向,可分为垂直滚动条和水平滚动条两种类型。这两种类型滚动条的组成部分都是一样的,两端都有两个箭头按钮,中间有一个可沿滚动条方向课后答案网移动的滚动块。如图4.60所示。www.hackshp.cn滚动箭头按钮滚动条滚动块图4.60滚动条外观 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.5进展条、滚动条和滑动条2.滚动条(1)滚动条的基本操作。滚动条的基本操作一般包括设置和获取滚动条的范围及滚动块的相应位置。由于滚动条控件的默认滚动范围是0到0,因此在使用滚动条之前必须设定其滚动范围。在MFC的CScrollBar类中,函数SetScrollRange是用来设置滚动条的滚动范围,其原型为:SetScrollRange(intnMinPos课后答案网,intnMaxPos,BOOLbRedraw=TRUE);其中,nMinPos和nMaxPos表示滚动位置的最小值和最大值。bRedraw为重画标志,当为TRUE时,滚动条被重画。在CScrollBar类中,设置滚动块位置操作是由www.hackshp.cnSetScrollPos函数来完成的,其原型如下:intSetScrollPos(intnPos,BOOLbRedraw=TRUE);其中,nPos表示滚动块的新位置,它必须是在滚动范围之内。与SetScrollRange和SetScrollPos相对应的两个函数是分别用来获取滚动条的当前范围以及当前滚动位置:voidGetScrollRange(LPINTlpMinPos,LPINTlpMaxPos);intGetScrollPos();其中,LPINT是整型指针类型,lpMinPos和lpMaxPos分别用来返回滚动块最小和最大滚动位置。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.5进展条、滚动条和滑动条2.滚动条(2)WM_HSCROLL或WM_VSCROLL消息。当用户对滚动条进行操作时,滚动条就会向父窗口发送WM_HSCROLL或WM_VSCROLL消息(分别对应于水平滚动条和垂直滚动条)。这些消息是通过MFCClassWizard在其对话框(滚动条的父窗口)中进行映射的,并产生相应的消息映射函数OnHScroll和OnVScroll,这两个函数具有下列原型:课后答案网afx_msgvoidOnHScroll(UINTnSBCode,UINTnPos,CScrollBar*pScrollBar);afx_msgvoidOnVScroll(UINTnSBCode,UINTnPos,CScrollBar*pScrollBar);其中,nPoswww.hackshp.cn表示滚动块的当前位置,pScrollBar表示由滚动条控件的指针,nSBCode表示滚动条的通知消息。图4.61表示当鼠标单击滚动条的不同部位时,所产生的不同通知消息。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.滚动条SB_LINEUPSB_PAGEUPSB_THUMBTRACK和SB_THUMBPOSITION课后答案网SB_PAGEDOWNwww.hackshp.cnSB_LINEDOWNSB_LINELEFTSB_LINERIGHTSB_PAGELEFTSB_PAGERIGHT图4.61滚动条通知代码与位置的关系 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.滚动条表4.21列出了各通知消息的含义。表4.21滚动条的通知消息课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.5进展条、滚动条和滑动条3.滑动条滑动条控件是由滑动块和可选的刻度线组成的。当用户用鼠标或方向键移动滑动块时,该控件发送通知消息来表明这些改变。滑动条是按照应用程序中指定的增量来移动。例如,如果用户指定此滑动条的范围为5,则滑动块只能有课后答案网6个位置:在滑动条控件最左边的一个位置和另外5个在此范围内每隔一个增量的位置。通常,这些位置都是由相应的刻度线来标识。如图4.62所示。www.hackshp.cn滑动块刻度线图4.62带刻度线的滑动条 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.5进展条、滚动条和滑动条3.滑动条(1)滑动条的风格和消息。滑动条控件有许多风格,它们都可以通过滑动条控件的属性对话框进行设置,如图4.63所示。表4.22列出该属性对话框的各项含义。课后答案网www.hackshp.cn图4.63滑动条属性对话框表4.22滑动条控件的Style属性 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.滑动条(2)滑动条的基本操作。MFC的CSliderCtrl类提供了滑动条控件的各种操作函数,这其中包括范围、位置设置和获取等。成员函数SetPos和SetRange分别用来设置滑动条的位置和范围,其原型如下:voidSetPos(intnPos);voidSetRange(intnMin课后答案网,intnMax,BOOLbRedraw=FALSE);其中,参数nPos表示新的滑动条位置。bMin和nMax表示滑动条的最小和最大位置,bRedraw表示重画标志,为TRUE时,滑动条被重画。与这两个函数相对应的成员函数GetPoswww.hackshp.cn和GetRange是分别用来获取滑动条的位置和范围的。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.滑动条成员函数SetTic用来设置滑动条控件中的一个刻度线的位置。函数成功调用后返回非零值;否则返回0。函数原型如下:BOOLSetTic(intnTic);其中,参数nTic表示刻度线的位置。成员函数SetTicFreq用来设置显示在滑动条中的刻度线的疏密程度。其函数原型如下:课后答案网voidSetTicFreq(intnFreq);其中,参数nFreq表示刻度线的疏密程度。例如,如果参数被设置为2,则在滑动条的范围中每两个增量显示一个刻度线。要使这个函数有效,必须在属性对话框www.hackshp.cn中选中Autoticks项。成员函数ClearTics用来从滑动条控件中删除当前的刻度线。其函数原型如下:voidClearTics(BOOLbRedraw=FALSE);其中,参数bRedraw表示重画标志。若该参数为TRUE,则在选择被清除后重画滑动条。成员函数SetSelection用来设置一个滑动条控件中当前选择的开始和结束位置。其函数原型如下:voidSetSelection(intnMin,intnMax);其中,参数nMin、nMax表示滑动条的开始和结束位置。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.3.5进展条、滚动条和滑动条4.示例:调整对话框背景颜色设置对话框背景颜色有许多方法,这里采用最简单的也是最直接的方法,即通过映射WM_CTLCOLOR(当子窗口将要绘制时发送的消息,以便能使用指定的颜色绘制控件)来达到改变背景颜色的目的。本例通过滚动条和两个滑动条来调整VisualC++所使用的RGB课后答案网颜色的三个分量:R(红色)、G(绿色)和B(蓝色),如图4.64所示。www.hackshp.cn图4.64调整对话框背景颜色 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.示例:调整对话框背景颜色[例Ex_Color]调整对话框背景颜色1)添加并设计对话框(1)用MFCAppWizard(exe)创建一个默认的单文档应用程序Ex_Color。(2)向应用程序中添加一个对话框资源IDD_COLOR,标题定为“调整对话框背景课后答案网颜色”,字体设为“宋体,9号”,创建此对话框类为CBkColorDlg。(3)删除原来的[Cancel]按钮,将[OK]按钮的标题改为“退出”。(4)打开对话框网格,参看图www.hackshp.cn4.64的控件布局,为对话框添加如表4.23所示的一些控件。表4.23对话框添加的控件 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!例Ex_Color]2)完善CBkColorDlg类代码(1)打开ClassWizard的MemberVariables页面,看看Classname是否是CBkColorDlg,选中所需的控件ID标识符,双击鼠标。依次为下列控件增加成员变量。如表4.24所示。课后答案网表4.24控件变量www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!例Ex_Color]2)完善CBkColorDlg类代码(2)为CBkColorDlg类添加两个成员变量,一个是int型m_nRedValue,用来设置颜色RGB中的红色分量,另一个是画刷CBrush类对象m_Brush,用来设置对话框背景所需要的画刷。(3)用MFCClassWizard为CBkColorDlg类添加WM_INITDIALOG消息映射,并添加下列初始化代码:课后答案网BOOLCBkColorDlg::OnInitDialog(){CDialog::OnInitDialog();www.hackshp.cnm_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;//returnTRUEunlessyousetthefocustoacontrol} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!例Ex_Color]2)完善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_nRedValue--;//单击滚动条左边箭头break;caseSB_LINERIGHT:www.hackshp.cnm_nRedValue++;//单击滚动条右边箭头break;caseSB_PAGELEFT:m_nRedValue-=10;break;caseSB_PAGERIGHT:m_nRedValue+=10;break;caseSB_THUMBTRACK:m_nRedValue=nPos;break;}if(m_nRedValue<0)m_nRedValue=0;if(m_nRedValue>255)m_nRedValue=255;m_scrollRed.SetScrollPos(m_nRedValue);}Invalidate();//使对话框无效,强迫系统重绘对话框CDialog::OnHScroll(nSBCode,nPos,pScrollBar);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!例Ex_Color]2)完善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();www.hackshp.cn//使画刷和对象分离m_Brush.CreateSolidBrush(color);//创建颜色画刷pDC->SetBkColor(color);//设置背景颜色return(HBRUSH)m_Brush;//返回画刷句柄,以便系统使此画刷绘制对话框}代码中,COLORREF是用来表示RGB颜色的一个32位的数据类型,它是VisualC++中一种专门用来定义颜色的数据类型。(画刷的详细用法以后还会讨论) 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!例Ex_Color]3.调用对话框(1)打开Ex_Color单文档应用程序的菜单资源,添加顶层菜单项“测试(&T)”,在其下添加一个菜单项“调整对话框背景颜色(&O)”,ID为ID_TEST_COLOR。(2)用MFCClassWizard为CMainFrame类添加菜单项ID_TEST_COLOR的COMMAND消息映射,取默认的映射函数名,并添加下列代码:课后答案网voidCMainFrame::OnTestColor(){CBkColorDlgwww.hackshp.cndlg;dlg.DoModal();}(3)在文件MainFrm.cpp的前面添加CBkColorDlg类的头文件包含:#include"MainFrm.h"#include"BkColorDlg.h"(4)编译运行并测试。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.4通用对话框和消息对话框4.4.1通用对话框Windows提供了一组标准用户界面对话框,它们都有相应的MFC库中的类来支持。用户或许早已熟悉了全部或大部分的这些对话框,因为众多的Windows的应用程序早已使用过它们,其中包括VisualC++。所有这些通用对话框类都是从一个公共的基类CCommonDialog课后答案网派生而来。表4.25列出了这些通用对话框。表4.25MFC的通用对话框www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.4.1通用对话框通用文件对话框类CFileDialog的构造函数的原型如下:CFileDialog(BOOLbOpenFileDialog,LPCTSTRlpszDefExt=NULL,LPCTSTRlpszFileName=NULL,DWORDdwFlags=OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,LPCTSTRlpszFilter=课后答案网NULL,CWnd*pParentWnd=NULL);参数中,当bOpenFileDialog为TRUE时表示文件打开对话框,为FALSE时表示文件保存对话框。lpszDefExt用来指定文件扩展名。若用户在文件名编辑框中没有键入扩展名,则系统在文件名后自动添加www.hackshp.cnlpszDefExt指定的扩展名。lpszFileName用来在文件名编辑框中指定开始出现的文件名,若为NULL时,则不出现。dwFlags用来指定对话框的界面标志,当为OFN_HIDEREADONLY时表示隐藏对话框中的“只读”复选框,当为OFN_OVER-WRITEPROMPT时表示文件保存时,若有指定的文件有重名,则出现提示对话框。pParentWnd用来指定对话框的父窗口指针。lpszFilter参数用来确定出现在文件列表框中的文件类型。它由一对或多对字符串组成,每对字符串中第一个字符串表示过滤器名称,第二个字符串表示文件扩展名,若指定多个扩展名则用“;”分隔,字符串最后用两个“|”结尾。注意:字符串应好写在一行,若一行写不下则用“”连接。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.4.2消息对话框消息对话框是最简单的一类对话框,它只是用来显示信息的。在VisualC++6.0的MFC类库中就提供相应的函数实现这样的功能,使用时,直接在程序中调用它们即可。它们的函数原型如下:intAfxMessageBox(LPCTSTRlpszText,UINTnType=MB_OK,UINTnIDHelp=0);课后答案网intMessageBox(LPCTSTRlpszText,LPCTSTRlpszCaption=NULL,UINTnType=MB_OK);这两个函数都是用来创建和显示消息对话框的,它们和前面示例www.hackshp.cnEx_HelloMsg使用到的Win32API函数MessageBox是不同的,前者是MFC类库中的函数。AfxMessageBox是全程函数,可以用在任何地方。而MessageBox只能控件、对话框、窗口等一些窗口类中使用。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.4.2消息对话框表4.26消息对话框常用图标类型课后答案网www.hackshp.cn表4.27消息对话框常用按钮类型 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.4.2消息对话框在使用消息对话框时,图标类型和按钮类型的标识可使用“|”来组合,例如下面的代码产生如图4.66所示的结果。intnChoice=MessageBox("你喜欢VisualC++吗?","提问",MB_OKCANCEL|MB_ICONQUESTION);if(nChoice==IDYES){//...课后答案网}www.hackshp.cn图4.66消息对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!第5章菜单、工具栏和状态栏文档应用程序的框架窗口常常包含菜单、工具栏、状态栏、图标和光标等内容,它们是Windows应用程序中不可缺少的界面元素,其风格和外观有时直接影响着用户对软件的评价。许多优秀的软件(如MicrosoftOffice)为增加对用户的吸引力,不惜资源将它们做得多姿多彩,甚至达到真三维的效果。本章将从它们最简单的用法开始入手,逐步深入直到对其进行编程控制。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1菜单为了使Windows程序更容易操作,菜单的显示都遵循下列一些规则:●若单击某菜单项后,将弹出一个对话框,那么在该菜单项文本后有“…”。●若某项菜单有子菜单,那么在该菜单项文本后有“”。●若菜单项需要助记符,则用括号将带下划线的字母括起来。助记符与课后答案网Alt构成一个组合键,当按住“Alt”键不放,再敲击该字母时,对应的菜单项就会被选中。●若某项菜单需要快捷键的支持,则一般将其列在相应菜单项文本之后。所谓“快捷键”是一个组合键,如www.hackshp.cnCtrl+N,使用时是先按下“Ctrl”健不放,然后再按“N”键。任何时候按下快捷键,相应的菜单命令都会被执行。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1菜单图5.1是一个菜单样例,注意它们的规则含义。需要说明的是,在常见的菜单系统中,最上面的一层水平排列的菜单称为“顶层菜单”,每一个顶层菜单项可以是一个简单的菜单命令,也可以是下拉(Popup)菜单,在下拉菜单中的每一个菜单项也可是菜单命令或下拉菜单,这样一级一级下去可以构造出复杂的菜单系统。课后答案网顶层菜单www.hackshp.cn弹出菜单图5.1菜单样例 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.1用编辑器设计菜单1.编辑菜单在顶层菜单的最后一项,VisualC++为用户留出了一个空位置,用来输入新的顶层菜单项。在菜单的空位置上双击鼠标左键,出现菜单项的属性对话框,在标题框中输入“测试(&T)”,结果如图5.2所示,其中符号&用来将其后面的字符作为该菜单项的助记符,这样当按住“Alt”键不放,再敲击该助记符键时,对应的菜单项就会被选中,或在菜单打开时,直接按相应的助记符键,对应的菜单项也会被选中。课后答案网www.hackshp.cn顶层菜单的空位置子菜单的空位置保存可见按钮图5.2Ex_SDI菜单资源 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.1用编辑器设计菜单1.编辑菜单单击“测试”菜单项下方的空位置,在菜单项属性对话框中,输入标题“切换菜单(&D)”,在ID框输入该菜单项的资源标识符:ID_TEST_CHANGE,结果如图5.3所示。课后答案网www.hackshp.cn图5.3修改菜单项属性关闭菜单项属性对话框,将新添加的菜单项拖放到“查看”和“帮助”菜单项之间,结果如图5.4所示。需要说明的是,菜单项位置改变后,其属性并没改变。图5.4菜单项“测试”拖放后的位置 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.1用编辑器设计菜单2.菜单命令的消息映射(1)选择“查看”→“建立类向导”菜单命令或按Ctrl+W快捷键,则出现MFCClassWizard对话框,并自动切换到MessageMaps页面。从“Classname”列表中选择CMainFrame,在IDs列表中选择ID_TEST_CHANGE,然后在Messages框中选择COMMAND课后答案网消息。如图5.5所示。www.hackshp.cn图5.5菜单命令消息的映射 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.菜单命令的消息映射(2)单击[AddFunction]按钮或双击COMMAND消息,出现“AddMemberFunction”对话框以输入成员函数的名称。系统默认的函数名为OnTestChange,如图5.6所示。该函数是对菜单项ID_TEST_CHANGE的映射,也就是说,当应用程序运行后,用户选择“测试”→“对话框”菜单时,该函数OnTestDlg被调用,执行函数中的代码。课后答案网www.hackshp.cn图5.6添加映射函数(3)单击[OK]按钮,在ClassWizard的“Memberfunctions”列表中将列出新增加的成员函数。选择此函数,单击[EditCode]按钮(或直接在函数名双击鼠标),在此成员函数中添加下列代码:voidCMainFrame::OnTestChange(){//TODO:AddyourcommandhandlercodehereAfxMessageBox("现在就切换吗?");} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.菜单命令的消息映射(4)编译并运行。在应用程序的顶层菜单上,单击“测试”菜单项,然后将鼠标移动到弹出的子菜单项“切换菜单”上,则结果如图5.7所示,此时状态栏上显示该菜单项的提示信息,该信息就是在前图5.3的菜单项属性对话框“提示”框中设置的内课后答案网容。单击“切换菜单”,则弹出一个消息对话框,显示内容“现在就切换吗?”。www.hackshp.cn图5.7Ex_SDI运行后的菜单 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.2使用键盘快捷键(1)展开项目工作区窗口中Accelerator的资源项,双击IDR_MAINFRAME,出现如图5.8的加速键资源列表。课后答案网www.hackshp.cn下端的空行图5.8Ex_SDI的加速键资源 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.2使用键盘快捷键(2)建立一个新的加速键时,只要双击加速键列表的最下端的空行,弹出如图5.9所示的“AccelProperties”(加速键属性)对话框,其中可设置的属性如表5.2所示课后答案网www.hackshp.cn图5.9加速键属性对话框表5.2加速键General属性对话框的各项含义 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.2使用键盘快捷键(3)在加速键属性对话框中,先选择在Ex_SDI应用程序菜单资源添加的“切换菜单”菜单项ID_TEST_CHANGE作为要联用的加速键的ID标识符,然后单击[下一键]按钮,并按下Ctrl+1作为此加速键的键值。需要说明的是,为了使其他用户能查看并使用该加速键,还需在相应的菜单项文本后面添加加速键内容。例如,可将ID_TEST_CHANGE课后答案网菜单项的标题改成“切换菜单(&C)tCtrl+1”,其中“t”是将后面的“Ctrl+1”www.hackshp.cn定位到一个水平制表位。(4)编译运行并测试。当程序运行后,按Ctrl+1键将执行相应的菜单命令。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.3菜单的编程控制1.创建菜单CMenu类的CreateMenu和CreatePopupMenu分别用来创建一个菜单或子菜单框架,它们的原型如下:BOOLCreateMenu();//产生一个空菜单BOOLCreatePopupMenu();课后答案网//产生一个空的弹出式子菜单2.装入菜单将菜单从资源装入应用程序中,需调用CMenu成员函数LoadMenu,或者用SetMenu对应用程序菜单进行重新设置。www.hackshp.cnBOOLLoadMenu(LPCTSTRlpszResourceName);BOOLLoadMenu(UINTnIDResource);其中,lpszResourceName为菜单资源名称,nIDResource为菜单资源ID标识符。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.3菜单的编程控制3.添加菜单项当菜单创建后,用户可以调用AppendMenu或InsertMenu函数来添加一些菜单项。但每次添加时,AppendMenu是将菜单项添加在菜单的末尾处,而InsertMenu在菜单的指定位置处插入菜单项,并将后面的菜单项依次下移。BOOLAppendMenu(UINTnFlags,UINTnIDNewItem=0,LPCTSTRlpszNewItem=NULL);BOOLAppendMenu(UINT课后答案网nFlags,UINTnIDNewItem,constCBitmap*pBmp);BOOLInsertMenu(UINTnPosition,UINTnFlags,UINTnIDNewItem=0,LPCTSTRlpszNewItem=NULL);BOOLInsertMenu(UINTwww.hackshp.cnnPosition,UINTnFlags,UINTnIDNewItem,constCBitmap*pBmp);其中,nIDNewItem表示新菜单项的资源ID标识符,lpszNewItem表示新菜单项的内容,pBmp用于菜单项的位图指针,nPosition表示新菜单项要插入的菜单项位置。nFlags表示要增加的新菜单项的状态信息,它的值影响其他参数的含义,如表5.3所示。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!表5.3nFlags的值及其对其他参数的影响课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.3菜单的编程控制4.删除菜单项调用DeleteMenu函数可将指定的菜单项删除,函数DeleteMenu的原型如下:BOOLDeleteMenu(UINTnPosition,UINTnFlags);其中,参数nPosition表示要删除的菜单项位置,它由nFlags进行说明。若当nFlags为MF_BYCOMMAND时,nPosition表示菜单项的ID标识符,而当nFlags为MF_BYPOSITION时,课后答案网nPosition表示菜单项的位置(第一个菜单项位置为0)。5.获取菜单项下面的3个CMenu成员函数分别获得菜单的项数、菜单项的ID标识符以及弹出式子菜单的句柄。www.hackshp.cnUINTGetMenuItemCount()const;该函数用来获得菜单的菜单项数,调用失败后返回-1。UINTGetMenuItemID(intnPos)const;该函数用来获得由nPos指定菜单项位置(以0为基数)的菜单项的标识号,若nPos是SEPARATOR(分隔符),则返回-1。CMenu*GetSubMenu(intnPos)const;该函数用来获得指定菜单的弹出式菜单的菜单句柄。该弹出式菜单位置由参数nPos指定,开始的位置为0。若菜单不存在,则创建一个临时的菜单指针。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.3菜单的编程控制[例Ex_Menu]用程序添加并处理一个菜单项(1)创建一个默认的单文档应用程序Ex_Menu。(2)选择“查看”菜单→“ResourceSymbols…”命令,弹出如图5.10所示的“资源符号”对话框,它能对应用程序中的资源标识符进行管理。由于程序中要添加的菜单项需要一个标识值,最好用一个标识符来代替这个值,这是一个好的习惯。因课后答案网此这里通过“资源符号”对话框来创建一个新的标识符。www.hackshp.cn图5.10“资源符号”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Menu](3)单击[新建]按钮,弹出如图5.11所示的“NewSymbol”(新标识符)对话框。在名字(Name)框中输入一个新的标识符ID_NEW_MENUITEM。在值(Value)框中,输入该ID的值,系统要求用户定义的ID值应大于15(0X000F)而小于61440(0XF000)。保留默认的课后答案网ID值101,单击[确定]按钮。www.hackshp.cn图5.11新标识符对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Menu](4)关闭“资源符号”对话框,在CMainFrame::OnCreate函数中添加下列代码,该函数在框架窗口创建时自动调用。intCMainFrame::OnCreate(LPCREATESTRUCTlpCreateStruct){...CMenu*pSysMenu=GetMenu();课后答案网//获得程序菜单指针CMenu*pSubMenu=pSysMenu->GetSubMenu(1);//获得第二个子菜单的指针CStringStrMenuItem("新的菜单项");pSubMenu->AppendMenu(MF_SEPARATOR);www.hackshp.cn//增加一水平分隔线pSubMenu->AppendMenu(MF_STRING,ID_NEW_MENUITEM,StrMenuItem);//在子菜单中增加一菜单项m_bAutoMenuEnable=FALSE;//关闭系统自动更新菜单状态pSysMenu->EnableMenuItem(ID_NEW_MENUITEM,MF_BYCOMMAND|MF_ENABLED);//激活菜单项DrawMenuBar();//更新菜单return0;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Menu](5)此时编译运行后,结果如图5.12所示。但此时选择“新的菜单项”命令不会有反应。课后答案网www.hackshp.cn图5.12程序添加的菜单项 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Menu](6)用MFCClassWizard在CMainFrame添加OnCommand消息函数的重载,并添加下列代码:BOOLCMainFrame::OnCommand(WPARAMwParam,LPARAMlParam){//wParam的低字节表示菜单、控件、加速键的命令课后答案网IDif(LOWORD(wParam)==ID_NEW_MENUITEM)MessageBox("你选中了新的菜单项");returnCFrameWnd::OnCommand(wParam,lParam);www.hackshp.cn}(7)编译运行并测试。这时当选择菜单“编辑”→“新的菜单项”命令后,就会弹一个对话框,显示“你选中了新的菜单项”消息。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.4使用快捷菜单快捷菜单是一种浮动的弹出式菜单,它是一种新的用户界面设计风格。当用户按下鼠标右键时,就会相应地弹出一个浮动菜单,其中提供了几个与当前选择内容相关的选项。用资源编辑器和MFC库的CMenu::TrackPopupMenu函数可以很容易地创建这样的菜单,CMenu::TrackPopupMenu课后答案网函数原型如下:BOOLTrackPopupMenu(UINTnFlags,intx,inty,CWnd*pWnd,LPCRECTlpRect=NULL);该函数用来显示一个浮动的弹出式菜单,其位置由各参数决定。其中,nFlags表示菜单在屏幕显示的位置以及鼠标按钮标志,如表www.hackshp.cn5.4所示。表5.4nFlags的值及其对其他参数的影响 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.1.4使用快捷菜单[例Ex_ContextMenu]使用快捷菜单(1)创建一个默认的单文档应用程序Ex_ContextMenu。用MFCClassWizard在CEx_ContextMenuView类添加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,MF_BYPOSITION);if(str.Left(4)=="文件"){nSubMenuPos=i;break;}}if(nSubMenuPos<0)return;//没有找到,返回pSysMenu->GetSubMenu(nSubMenuPos)->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_ContextMenu](3)在Ex_ContextMenuView.cpp文件的前面添加CMainFrame类的文件包含:#include"Ex_ContextMenuView.h"#include"MainFrm.h"(4)运行并测试。当用户在应用程序窗口的客户区中右击鼠标,会弹出如图5.13的快捷菜单。课后答案网www.hackshp.cn客户区图5.13快捷菜单 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.2工具栏5.2.1使用工具栏编辑器选择菜单“文件”→“打开工作区”,将前面的单文档应用程序Ex_SDI调入或重新创建。在项目工作区窗口中选择ResourceView页面,双击“Toolbar”项中的IDR_MAINFRAME,则工具栏编辑器出现在主界面的右边,如图课后答案网5.14所示。www.hackshp.cn空按钮图形工具箱颜色工具箱图5.14工具栏编辑器窗口 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.2.1使用工具栏编辑器下面就工具栏按钮的一般操作进行说明。(1)创建一个新的工具栏按钮。(2)移动一个按钮。(3)删除一个按钮。(4)在工具栏中插入空格。课后答案网在工具栏中插入空格有以下几种情况:●如果按扭前没有任何空格,拖动该按钮向右移动并当覆盖相邻按钮的一半以上时,释放鼠标键,则此按钮前出现空格。www.hackshp.cn●如果按钮前有空格而按钮后没有空格,拖动该按钮向左移动并当按钮的左边界接触到前面按钮时,释放鼠标键,则此按钮后将出现空格。●如果按钮前后均有空格,拖动该按钮向右移动并当接触到相邻按钮时,则此按钮前的空格保留,按钮后的空格消失。相反,拖动该按钮向左移动并当接触到前一个相邻按钮时,则此按钮前面的空格消失,后面的空格保留。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.2.1使用工具栏编辑器工具栏按钮的一般操作进行说明(5)工具栏按钮属性的设置。双击某按钮弹出其属性对话框,如图5.15所示。属性对话框中的各项说明见表5.5。课后答案网www.hackshp.cn图5.15工具栏按钮属性对话框表5.5工具栏按钮属性对话框的各项含义 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.2.1使用工具栏编辑器工具栏按钮的一般操作进行说明(6)工具栏和菜单相结合。工具栏和菜单相结合是指当选择工具按钮或菜单命令时,操作结果是一样的。使它们结合的具体方法是在工具按钮的属性对话框中将按钮的ID标识符设置为相关联的菜单项ID。需要说明的是,对于单独工具按钮命令消息的映射方法跟菜单命令是一样的。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.2.2多个工具栏的使用[例Ex_SDI]使用多个工具栏1)添加并更改应用程序菜单(1)创建一个默认的单文档应用程序Ex_SDI。(2)按快捷键Ctrl+R,弹出“插入资源”对话框,在资源类型中选定“Menu”,如图5.16。课后答案网www.hackshp.cn图5.16“插入资源”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI]1)添加并更改应用程序菜单(3)单击[新建]按钮,系统就会为应用程序添加一个新的菜单资源,并自动赋给它一个默认的标识符名称(第一次为IDR_MENU1,以后依次为IDR_MENU2、IDR_MENU3、...),同时自动打开这个新的菜单资源,如图课后答案网5.17所示。www.hackshp.cn菜单空位置菜单默认ID图5.17添加菜单资源后的开发环境 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI]1)添加并更改应用程序菜单(4)在Menu资源的ID_MENU1上右击鼠标,从弹出的快捷菜单中选择“Properties”命令,出现如图5.18所示的菜单属性对话框,在这里可以重新指定菜单资源ID,设置菜单资源的语言和条件,这个条件用来决定菜单资源包含到哪个环境中,例如当指定条件为课后答案网_DEBUG,则菜单资源只存在于Debug编译环境中。www.hackshp.cn图5.18菜单属性对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI]1)添加并更改应用程序菜单(5)为菜单ID_MENU1添加一个顶层弹出菜单项“测试(&T)”,并在该菜单下添加一个子菜单项“返回(&R)”,ID设为ID_TEST_RETURN,如图5.19所示。需要再次强调的是,符号&用来指定后面的字符是一个助记符。课后答案网www.hackshp.cn图5.19设计新的菜单资源(6)打开Ex_SDI程序菜单资源IDR_MAINFRAME,在“查看”菜单的最后添加一个子菜单项“显示测试菜单(&M)”,ID设为ID_VIEW_TEST。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI]1)添加并更改应用程序菜单(7)为CMainFrame类添加一个CMenu类型的成员变量m_NewMenu。如下面的代码:classCMainFrame:publicCFrameWnd{课后答案网…//Attributespublic:www.hackshp.cnCMenum_NewMenu;(8)按快捷键Ctrl+W打开MFCClassWizard对话框,切换到MessageMaps页面,从“Classname”列表中选择CMainFrame,分别为菜单项ID_VIEW_TEST和ID_TEST_RETURN添加COMMAND消息映射,使用默认的消息映射函数名,并添中下列代码: 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI](8)添中下列代码:voidCMainFrame::OnViewTest(){m_NewMenu.Detach();//使菜单对象和菜单句柄分离m_NewMenu.LoadMenu(IDR_MENU1);SetMenu(NULL);//清除应用程序菜单SetMenu(&m_NewMenu课后答案网);//设置应用程序菜单}voidCMainFrame::OnTestReturn(){m_NewMenu.Detach();www.hackshp.cnm_NewMenu.LoadMenu(IDR_MAINFRAME);SetMenu(NULL);SetMenu(&m_NewMenu);}代码中,LoadMenu和Detach都是CMenu类成员函数,LoadMenu用来装载菜单资源,而Detach是使菜单对象与菜单句柄分离。在调用LoadMenu后,菜单对象m_NewMenu就拥有一个菜单句柄,当再次调用LoadMenu时,由于菜单对象的句柄已经创建,因而会发生运行时错误,但当菜单对象与菜单句柄分离后,就可以再次创建菜单了。SetMenu是CWnd类的一个成员函数,用来设置应用程序的菜单。(9)第一次编译运行并测试。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI]2.添加并设计工具栏按钮(1)在项目工作区的ResourceView页面中,展开Toolbar(工具栏)资源,双击双击“Toolbar”项中的IDR_MAINFRAME,显示工具栏编辑器。(2)利用工具栏编辑器设计两个工具按钮,其位置和内容如图5.20所示。(3)双击刚才设计的第一个工具按钮,弹出该工具按钮的属性对话框,将该工具课后答案网按钮的ID号设为ID_TEST_RETURN,在提示框内键入“返回应用程序主菜单n返回主菜单”。www.hackshp.cn图5.20设计的两个工具栏按钮 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI]2.添加并设计工具栏按钮(4)双击刚才设计的第二个工具按钮,弹出该工具按钮的属性对话框,将该工具按钮的ID号设为ID_VEW_TEST,在提示框内键入“显示测试菜单n显示测试菜单”。(5)第二次编译运行并测试。当程序运行后,将鼠标移至刚才设计的第一个工具课后答案网按钮处,这时在状态栏上显示出“返回应用程序主菜单”信息,若稍等片刻后,还会弹出提示小窗口,显示出“返回主菜单”字样,如图www.hackshp.cn5.21所示。单击新添加的这两个按钮,会执行相应的菜单命令。图5.21工具按钮提示 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI]3.添加工具栏(1)在项目工作区切的ResourceView页面中,展开Toolbar(工具栏)资源,用鼠标单击IDR_MAINFRAME不松开,然后按下Ctrl键,移动鼠标将IDR_MAINFRAME拖到Toolbar资源名称上,这样就复制了工具栏默认资源IDR_MAINFRAME,复制后的资源标识系统自动设为课后答案网IDR_MAINFRAME1。(2)右击工具栏资源IDR_MAINFRAME1,从弹出的快捷菜单中选择Properties命令,如图5.22所示,将ID设为IDR_TOOLBAR1。(3)双击IDR_TOOLBAR1www.hackshp.cn,打开工具栏资源,按图5.23删除不要的工具按钮。图5.23删除不要的工具按钮图5.22工具栏属性对话框(4)在CMainFrame类中添加一个成员变量m_wndTestBar,变量类型为CToolBar。CToolBar类封装了工具栏的操作。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI]3.添加工具栏(5)在CMainFrame::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);if(!nRes||!m_wndTestBar.LoadToolBar(IDR_TOOLBAR1)){www.hackshp.cnTRACE0("Failedtocreatetoolbarn");return-1;//failtocreate}…m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);m_wndTestBar.EnableDocking(CBRS_ALIGN_ANY);EnableDocking(CBRS_ALIGN_ANY);DockControlBar(&m_wndToolBar);DockControlBar(&m_wndTestBar);…return0;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI]4.完善程序代码(1)事实上这不是本例要的结果。还需调用CFrameWnd类的成员函数ShowControlBar来使程序一开始运行时隐藏工具栏IDR_TOOLBAR1。(2)在CMainFrame::OnCreate函数中添加下列代码:intCMainFrame::OnCreate(LPCREATESTRUCTlpCreateStruct)课后答案网{…ShowControlBar(&m_wndTestBar,FALSE,FALSE);//www.hackshp.cn关闭测试工具栏return0;}代码中,ShowControlBa函数有三个参数,第一个参数用来指定要操作的工具栏或状态栏指针,第二个参数是一个布尔型,当为TRUE时表示显示,否则表示隐藏,第三个参数用来表示是否延迟显示或隐藏,当为FALSE时表示立即显示或隐藏。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI]4.完善程序代码(3)在CMainFrame::OnViewTest和CMainFrame::OnTestReturn函数中添加下列代码:voidCMainFrame::OnViewTest(){课后答案网…ShowControlBar(&m_wndTestBar,TRUE,FALSE);//显示测试工具栏ShowControlBar(&m_wndToolBar,FALSE,FALSE);//www.hackshp.cn关闭主工具栏}voidCMainFrame::OnTestReturn(){…ShowControlBar(&m_wndTestBar,FALSE,FALSE);//关闭测试工具栏ShowControlBar(&m_wndToolBar,TRUE,FALSE);//显示主工具栏} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDI]4.完善程序代码(4)编译运行并测试,结果如图5.25所示,左边是一开始运行的结果,右边是单击工具按钮运行的结果。课后答案网www.hackshp.cn图5.25Ex_SDI最后运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.3状态栏状态栏是一条水平长条,位于应用程序的主窗口的底部。它可以分割成几个窗格,用来显示多组信息。状态栏的定义用MFCAppWizard创建的单文档或多文档应用程序框架中,有一个静态的indicators数组,它是在课后答案网MainFrm.cpp文件中定义的,被MFC用作状态栏的定义。图5.26列出了indicators数组元素与标准状态栏窗格的关系。www.hackshp.cn图5.26indicators数组的定义 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.3.2状态栏的常用操作VisualC++6.0中可以方便地对状态栏进行操作,如增减窗格、在状态栏中显示文本、改变状态栏的风格和大小等,并且MFC的CStatusBar类封装了状态栏的大部分操作。1.增加和减少窗格状态栏中的窗格可以分为信息行窗格和指示器窗格两类。若在状态栏中增加一个课后答案网信息行窗格,则只需在indicators数组中的适当位置中增加一个ID_SEPARATOR标识即可;若在状态栏中增加一个用户指示器窗格,则在indicators数组中的适当位置增加一个在字符串表中定义过的资源www.hackshp.cnID,其字符串的长度表示用户指示器窗格的大小。若状态栏减少一个窗格,其操作与增加相类似,只需减少indicators数组元素即可。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.3.2状态栏的常用操作2.在状态栏上显示文本有3种办法可以在状态栏窗格显示文本信息:(1)调用CWnd::SetWindowText更新信息行窗格(或窗格0)中的文本。由于状态栏也是一种窗口,故在使用时可直接调用。若状态栏变量为m_wndStatusBar,则m_wndStatusBar.SetWindowText(“消息”)语句将在信息行窗格(或窗格0)内显示课后答案网“消息”字样。(2)手动处理状态栏的www.hackshp.cnON_UPDATE_COMMAND_UI更新消息,并在处理函数中调用CCmdUI::SetText函数。(3)调用CStatusBar::SetPaneText函数更新任何窗格(包括信息行窗格)中的文本。此函数原型描述如下:BOOLSetPaneText(intnIndex,LPCTSTRlpszNewText,BOOLbUpdate=TRUE);其中,lpszNewText表示要显示的字符串。nIndex是表示设置的窗格索引(第一个窗格的索引为0)。若bUpdate为TRUE,则系统自动更新显示的结果。值得注意的是,在使用第2种方法时,应按一定的步骤进行。例如下面的示例过程是在状态栏的最右边两个窗格中显示出当前鼠标在窗口客户区的位置。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.3.2状态栏的常用操作[例Ex_SDIMouse]将鼠标在窗口客户区的位置显示在状态栏上(1)创建一个默认的单文档应用程序Ex_SDIMouse。(2)将项目工作区切换到ClassView页面,展开CMainFrame所有项,双击构造函数CMainFrame,在文档窗口中出现该函数的定义,在它的前面就是状态栏数组的定义。课后答案网(3)将状态栏indicators数组的定义改为下列代码:staticUINTindicators[]={ID_SEPARATOR,www.hackshp.cnID_SEPARATOR,}; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDIMouse](4)由于鼠标移动消息WM_MOUSEMOVE在CMainFrame类映射后不起作用,因此只能映射到CEx_SDIMouseView类中。但是,这样一来,就需要更多的代码,因为状态栏对象m_wndStatusBar是在CMainFrame类定义的成员变量,因而需要在CEx_SDIMouseView类中添加访问CMainFrame类的代码。CEx_SDIMouseView::OnMouseMove函数代码如下:voidCEx_SDIMouseView::OnMouseMove(UINTnFlags,CPointpoint){课后答案网CStringstr;CMainFrame*pFrame=(CMainFrame*)AfxGetApp()->m_pMainWnd;//获得主窗口指针CStatusBar*www.hackshp.cnpStatus=&pFrame->m_wndStatusBar;//获得主窗口中的状态栏指针if(pStatus){str.Format("X=%d,Y=%d",point.x,point.y);//格式化文本pStatus->SetPaneText(1,str);//更新第二个窗格的文本}CView::OnMouseMove(nFlags,point);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDIMouse](5)将MainFrm.h文件中的受保护变量m_wndStatusBar变成公共变量。(6)在Ex_SDIMouseView.cpp文件的开始处增加下列语句:#include"Ex_SDIMouseView.h"#include"MainFrm.h"(7)编译并运行,结果如图课后答案网5.27所示。www.hackshp.cn图5.27鼠标的位置显示在状态栏上 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.3.2状态栏的常用操作3.改变状态栏的风格在MFC的CStatusBar类中,有两个成员函数可以改变状态栏风格,它们是:voidSetPaneInfo(intnIndex,UINTnID,UINTnStyle,intcxWidth);voidSetPaneStyle(intnIndex,UINTnStyle);其中,参数nIndex表示要设置的状态栏窗格的索引,课后答案网nID用来为状态栏窗格指定新的ID,cxWidth表示窗格的像素宽度,nStyle表示窗格的风格类型,用来指定窗格的外观,例如SBPS_POPOUT表示窗格是凸起来的,具体见表5.6。www.hackshp.cn表5.6状态栏窗格的风格类型 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!5.4交互对象的动态更新[例Ex_Update]交互对象的动态更新(1)创建一个默认的单文档应用程序Ex_Update。(2)将项目工作区窗口切换到ResourceView页面,展开Toolbar资源节点。(3)选中Toolbar资源IDR_MAINFRAME,然后按下Ctrl键不放,移动鼠标将IDR_MAIN-FRAME拖到课后答案网Toolbar资源名称上,这样就复制了工具栏默认资源IDR_MAINFRAME,复制后的资源标识系统自动设为IDR_MAINFRAME1。(4)右击IDR_MAINFRAME1,从弹出的快捷菜单中选择Properties命令,在弹出的属性对话框中将www.hackshp.cnID改为IDR_NEWBAR。(5)将IDR_NEWBAR工具按钮删除几个以与IDR_MAINFRAME有所区别。(6)打开MainFrm.h文件,在CMainFrame类中声明一个CToolBar类变量m_wndNewBar,一个BOOL型的成员变量m_bNewBar。protected://controlbarembeddedmembersCStatusBarm_wndStatusBar;CToolBarm_wndToolBar;CToolBarm_wndNewBar;BOOLm_bNewBar; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Update](7)在CMainFrame::OnCreate中添加下列代码:intCMainFrame::OnCreate(LPCREATESTRUCTlpCreateStruct){if(CFrameWnd::OnCreate(lpCreateStruct)==-1)return-1;if(!m_wndNewBar.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)||!m_wndNewBar.LoadToolBar(IDR_NEWBAR)){www.hackshp.cnTRACE0("Failedtocreatenewbarn");return-1;//failtocreate}…m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);m_wndNewBar.EnableDocking(CBRS_ALIGN_ANY);EnableDocking(CBRS_ALIGN_ANY);DockControlBar(&m_wndToolBar);DockControlBar(&m_wndNewBar);return0;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Update](8)打开菜单资源IDR_MAINFRAME,在“查看”菜单下添加一个菜单项“新工具栏(&N)”,ID标识符设定为ID_VIEW_NEWBAR。(9)用MFCClassWizard在CMainFrame类中添加菜单ID_VIEW_NEWBAR的COMMAND和UPDATE_COMMAND_UI两个消息映射,并在映射函数中添加下列代码:voidCMainFrame::OnViewNewbar()课后答案网{m_bNewBar=!m_bNewBar;ShowControlBar(&m_wndNewBar,m_bNewBar,FALSE);//www.hackshp.cn显示或隐藏工具栏}voidCMainFrame::OnUpdateViewNewbar(CCmdUI*pCmdUI){m_bNewBar=m_wndNewBar.IsWindowVisible();pCmdUI->SetCheck(m_bNewBar);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Update]代码中,OnUpdateViewNewbar是ID_VIEW_NEWBAR的更新命令消息的消息映射函数。该函数只有一个参数,它是指向CCmdUI对象的指针。CCmdUI类仅用于ON_UPDATE_COMMAND_UI消息映射函数,它的成员函数将对菜单项、工具按钮等用户交互对象起作用,具体如表5.7所示。表5.7CCmdUI课后答案网类的成员函数对用户交互对象的作用www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!第6章框架窗口、文档和视图尽管窗口、文档和视图是MFC的基础,但可能也是最不易理解的部分,因为其概念性比传统编程所需的WindowsAPI函数更强一些6.1框架窗口框架窗口可分为两类:一类是应用程序主窗口,另一类是文档窗口。6.1.1主窗口和文档窗口课后答案网主框架窗口是应用程序直接放置在桌面(DeskTop)上的那个窗口,每个应用程序只能有一个主框架窗口,主框架窗口的标题栏上往往显示应用程序的名称。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.1.1主窗口和文档窗口文档窗口对于单文档应用程序来说,它和主框架窗口是一致的,即主框架窗口就是文档窗口;而对于多文档应用程序,文档窗口是主框架窗口的子窗口,如图6.1所示。课后答案网www.hackshp.cn主框架窗口文档窗口 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.1.2窗口风格的设置窗口风格既可以通过MFCAppWizard来设置,也可以在主窗口或文档窗口类的PreCreateWindow函数中修改CREATESTRUCT结构,或是可以调用CWnd类的成员函数ModifyStyle和ModifyStyleEx来更改。1.窗口风格窗口风格通常有一般(课后答案网以WS_为前缀)和扩展(以WS_EX_为前缀)两种形式。这两种形式的窗口风格可在函数CWnd::Create或CWnd::CreateEx参数中指定,其中CreateEx函数可同时支持以上两种风格,而CWnd::Create只能指定窗口的一般风格。需要说明的是,对于控件和对话框这样的窗口来说,它们的窗口风格可直www.hackshp.cn接通过其属性对话框来设置。常见的一般窗口风格如表6.1所示。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.窗口风格表6.1窗口的一般风格课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.1.2窗口风格的设置2.用MFCAppWizard设置MFCAppWizard有一个[高级]按扭(在创建单文档或多文档应用程序过程的第四步中),允许用户指定有关SDI和MDI框架窗口的属性,图6.2表示了AdvancedOptions对话框的WindowStyles页面,其中的选项含义见表6.2。但在该对话框中,用户只能设定少数几种窗口风格。课后答案网www.hackshp.cn表6.2高级选项对话框窗口风格的各项含义图6.2高级选项对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.1.2窗口风格的设置3.修改CREATESTRUCT结构当窗口创建之前,系统自动调用PreCreateWindow虚函数。在用MFCAppWizard创建文档应用程序结构时,MFC已为主窗口或文档窗口类自动重载了该虚函数。用户可以在此函数中通过修改CREATESTRUCT结构来设置窗口的绝大多数风格。例如,在单文档应用程序中,框架窗口默认的风格是WS_OVERLAPPEDWINDOW和FWS_ADDTOTITLE的组合,更改其风格可如下列的代码:课后答案网BOOLCMainFrame::PreCreateWindow(CREATESTRUCT&cs){//新窗口不带有[最大化]按钮cs.style&=~WS_MAXIMIZEBOX;www.hackshp.cn//将窗口的大小设为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)/2;returnCFrameWnd::PreCreateWindow(cs);}代码中,前面有“::”作用域符号的函数是指全局函数,一般都是一些API函数。“cs.style&=~WS_MAXIMIZEBOX;”中的“~”是按位取“反”运算符,它将WS_MAXIMIZEBOX的值按位取反后,再和cs.style值按位“与”,其结果是将cs.style值中的WS_MAXIMIZEBOX标志位清零。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.1.2窗口风格的设置4.使用ModifyStyle和ModifyStyleExCWnd类中的成员函数ModifyStyle和ModifyStyleEx也可用来更改窗口的风格,其中ModifyStyleEx还可更改窗口的扩展风格。这两个函数具有相同的参数,其含义如下。BOOLModifyXXXX(DWORDdwRemove,DWORDdwAdd,UINTnFlags=0);其中,参数dwRemove用来指定需要删除的风格,dwAdd用来指定需要增加的风格,nFlags表示SetWindowPos的标志,课后答案网0(默认)表示更改风格的同时不调用SetWindowPos函数。由于框架窗口在创建时不能直接设定其扩展风格,因此只能通过调用ModifyStyle函数来进行。例如用MFCClassWizard为一个多文档应用程序Ex_MDI的子文档窗口类CChildFrame添加OnCreateClientwww.hackshp.cn消息处理,并增加下列代码:BOOLCChildFrame::OnCreateClient(LPCREATESTRUCTlpcs,CCreateContext*pContext){ModifyStyle(0,WS_VSCROLL,0);returnCMDIChildWnd::OnCreateClient(lpcs,pContext);}这样,当窗口创建客户区时就会调用虚函数OnCreateClient。运行结果如图6.3所示。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.1.2窗口风格的设置课后答案网添加的滚动条www.hackshp.cn图6.3为文档子窗口添加垂直滚动条 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.1.3窗口状态的改变1.用ShowWindow改变窗口的显示状态当应用程序运行时,Windows会自动调用应用程序框架内部的WinMain函数,并自动查找该应用程序类的全局变量theApp,然后自动调用用户应用程序类的虚函数InitInstance,该函数会进一步调用相应的函数来完成主窗口的构造和显示工作,如下面的代码(以单文档应用程序项目课后答案网Ex_SDI为例):BOOLCEx_SDIApp::InitInstance(){…www.hackshp.cnm_pMainWnd->ShowWindow(SW_SHOW);//显示窗口m_pMainWnd->UpdateWindow();//更新窗口returnTRUE;}代码中,m_pMainWnd是主框架窗口指针变量,ShowWindow是CWnd类的成员函数,用来按指定的参数显示窗口,该参数的值如表6.3所示。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.用ShowWindow改变窗口的显示状态表6.3ShowWindow函数的参数值课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.1.3窗口状态的改变1.用ShowWindow改变窗口的显示状态通过指定ShowWindow函数的参数值可以改变改变窗口显示状态。例如下面的代码是将窗口的初始状态设置为“最小化”:BOOLCEx_SDIApp::InitInstance(){课后答案网...m_pMainWnd->ShowWindow(SW_SHOWMINIMIZED);m_pMainWnd->UpdateWindow();www.hackshp.cnreturnTRUE;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.1.3窗口状态的改变2.用SetWindowPos或MoveWindow改变窗口的大小和位置CWnd中的SetWindowPos是一个非常有用的函数;它不仅可以改变窗口的大小、位置,而且还可以改变所有窗口在堆栈排列的次序(Z次序),这个次序是根据它们在屏幕出现的先后来确定的。其中,参数pWndInsertAfter课后答案网表示窗口对象指针,它可以下列预定义窗口对象的地址:wndBottom将窗口放置在Z次序中的底层wndTopwww.hackshp.cn将窗口放置在Z次序中的顶层wndTopMost设置最顶窗口wndNoTopMost将窗口放置在所有最顶层的后面,若此窗口不是最顶窗口,则此标志无效 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.用SetWindowPos或MoveWindow改变窗口的大小和位置x和y表示窗口新的左上角坐标,cx和cy分别表示表示窗口新的宽度和高度,nFlags表示窗口新的大小和位置方式,如表6.4所示。课后答案网表6.4常用nFlags值及其含义www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.用SetWindowPos或MoveWindow改变窗口的大小和位置voidMoveWindow(intx,inty,intnWidth,intnHeight,BOOLbRepaint=TRUE);voidMoveWindow(LPCRECTlpRect,BOOLbRepaint=TRUE);其中,参数x和y表示窗口新的左上角坐标,nWidth和nHeight表示窗口新的宽度和高度,bRepaint用于指定窗口是否重绘,lpRect表示窗口新的大小和位置。//使用SetWindowPos函数的示例课后答案网m_pMainWnd->SetWindowPos(NULL,100,100,0,0,SWP_NOSIZE|SWP_NOZORDER);//使用MoveWindow函数的示例CRectrcWindow;www.hackshp.cnm_pMainWnd->GetWindowRect(rcWindow);m_pMainWnd->MoveWindow(100,100,rcWindow.Width(),rcWindow.Height(),TRUE);当然,改变窗口的大小和位置的CWnd成员函数还不止以上两个。例如CenterWindow函数是使窗口居于父窗口中央,就像下面的代码:CenterWindow(CWnd::GetDesktopWindow());//将窗口置于屏幕中央AfxGetMainWnd()->CenterWindow();//将主框架窗口居中 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.2文档模板用MFCAppWizard创建的单文档(SDI)或多文档(MDI)应用程序均包含应用程序类、文档类、视图类和框架窗口类,这些类是通过文档模板来有机地联系在一起。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.2.1文档模板类文档应用程序框架结构是在程序运行一开始构造的,在单文档应用程序(设项目名为Ex_SDI)的应用程序类InitInstance函数中,可以看到这样的代码:BOOLCEx_SDIApp::InitInstance(){课后答案网…CSingleDocTemplate*pDocTemplate;pDocTemplate=newCSingleDocTemplate(www.hackshp.cnIDR_MAINFRAME,//资源IDRUNTIME_CLASS(CEx_SDIDoc),//文档类RUNTIME_CLASS(CMainFrame),//主框架窗口类RUNTIME_CLASS(CEx_SDIView));//视图类AddDocTemplate(pDocTemplate);…returnTRUE;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.2.2文档模板字串资源表6.5文档模板字符串的含义课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.2.2文档模板字串资源实际上,文档模板字串资源内容既可直接通过字串资源编辑器进行修改,也可以在文档应用程序创建向导的第四步中,通过“AdvancedOptions”对话框中的“DocumentTemplateStrings”页面来指定,如图6.4所示。课后答案网www.hackshp.cn4501326图6.4AdvancedOptions对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.2.3使用多个文档类型[例Ex_MDIDemo]使用多个文档类型(1)用MFCAppWizard创建一个默认的多文档应用程序项目Ex_MDIDemo。(2)打开项目工作区窗口中StringTable的资源项,双击该项下的StringTable,打开字符串表资源,如图课后答案网6.5所示。www.hackshp.cn图6.5Ex_MDIDemo字符串表资源 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_MDIDemo](3)双击IDR_MAINFRAME列表项,弹出字符串属性对话框,将其标题修改为“多个文档类型实例”,结果如图6.6所示。(4)双击IDR_EX_MDITYPE列表项,在字符串属性对话框中,将其内容修改为:nPicturenMDIDemo图片n图片文件(*.bmp)n.bmpnExMDIDemo.DocumentnEx_MDIDocument(5)拖动字符串表编辑器右边的滚动块,直到出现最后一个字符串项,双击最后的空课后答案网行,在字符串属性对话框中将ID设为IDR_OTHERTYPE,标题内容设为:nTxtnMDIDemo文本n文本文件(*.txt,*.cpp,*.h)n.txt;*.cpp;*.hnExMDIDemo.DocumentnEx_MDIDocumentwww.hackshp.cn结果如图6.7所示。图6.6修改IDR_MAINFRAME字符串标题图6.7添加新的字符串项 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_MDIDemo](6)按快捷键Ctrl+W,打开MFCClassWizard,单击[AddClass]按钮,从弹出的菜单中选择New,出现“NewClass”对话框,在Name框中输入类名COtherDoc,在Baseclass组合框中选择基类CDocument,结果如图6.8所示。课后答案网www.hackshp.cn图6.8添加新的文档类COtherDoc(7)单击[OK]按钮,新的文档类COtherDoc就添加到Ex_MDIDemo项目中。类似的,再添加一个新的视图类COtherView,基类为CView。单击[确定]按钮,关闭MFCClassWizard对话框。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_MDIDemo](8)修改CEx_MDIDemoApp::InitInstance函数代码,如下所示:BOOLCEx_MDIDemoApp::InitInstance(){…CMultiDocTemplate*pDocTemplate;pDocTemplate=newCMultiDocTemplate(课后答案网IDR_EX_MDITYPE,RUNTIME_CLASS(CEx_MDIDemoDoc),RUNTIME_CLASS(CChildFrame),RUNTIME_CLASS(CEx_MDIDemoView));www.hackshp.cnAddDocTemplate(pDocTemplate);pDocTemplate=newCMultiDocTemplate(IDR_OTHERTYPE,//指定新的资源RUNTIME_CLASS(COtherDoc),//指定新的文档类RUNTIME_CLASS(CChildFrame),RUNTIME_CLASS(COtherView));//指定新的视图类AddDocTemplate(pDocTemplate);…returnTRUE;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_MDIDemo](9)在类CEx_MDIDemoApp源代码文件Ex_MDIDemo.cpp的开始处,添加包含前面创建的两个派生类的头文件:#include"Ex_MDIDemoView.h"#include"OtherDoc.h"#include"OtherView.h"课后答案网(10)编译运行并测试。在程序运行的一开始弹出文档类型的“新建”对话框,如图6.9所示。选择“www.hackshp.cnMDIDemo图片”,单击[确定]后,出现CEx_MDIDemo主框架窗口界面,同时出现标题为“Picture1”的文档窗口。选择“文件”→“新建”菜单,又会出现如图6.9所示的“新建”对话框,选择“MDIDemo文本”,单击[确定]后,出现标题为“Txt1”的文档窗口。结果如图6.10所示。选择“文件”→“打开”菜单,出现如图6.11所示的文件打开对话框。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_MDIDemo]课后答案网图6.9文档类型新建对话框www.hackshp.cn图6.10多类型文档窗口显示图6.11文件打开对话框中的文件类型 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3文档序列化用户处理的数据往往需要存盘作永久备份。将文档类中的数据成员变量的值保存在磁盘文件中,或者将存储的文档文件中的数据读取到相应的成员变量中。这个过程称为序列化(Serialize)。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.1文档序列化过程在使用MFC程序结构进行文件序列化操作之前,先来看看对文档不同操作后的具体程序运行过程。1.创建空文档应用程序类的InitInstance函数在调用了AddDocTemplate函数之后,会通过CWinApp::ProcessShellCommand课后答案网间接调用CWinApp的另一个非常有用的成员函数OnFileNew,并依次完成下列工作:(1)构造文档对象,但并不从磁盘中读数据。(2)构造主框架类www.hackshp.cnCMainFrame的对象,并创建该主框架窗口,但不显示。(3)构造视图对象,并创建视图窗口,也不显示。(4)通过内部机制,使文档、主框架和视图“对象”之间“真正”建立联系。注意与AddDocTemplate函数的区别,AddDocTemplate函数建立的是“类”之间的联系。(5)调用文档对象的CDocument::OnNewDocument虚函数,并调用CDocument::DeleteContents虚函数来清除文档对象的内容。(6)调用视图对象的CView::OnInitialUpdate虚函数对视图进行初始化操作。(7)调用框架对象的CFrameWnd::ActiveFrame虚函数,以便显示出带有菜单、工具栏、状态栏以及视图窗口的主框架窗口。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.1文档序列化过程2.打开文档当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_SETUP,www.hackshp.cnCWinApp::OnFilePrintSetup)END_MESSAGE_MAP()OnFileOpen函数还会进一步完成下列工作:弹出通用文件“打开”对话框,供用户选择一个文档。(1)文档指定后,调用文档对象的CDocument::OnOpenDocument虚函数。该函数将打开文档,并调用DeleteContents清除文档对象的内容,然后创建一个CArchive对象用于数据的读取,接着又自动调用Serialize函数。(2)调用视图对象的CView::OnInitialUpdate虚函数。(3)除了使用“文件(File)”→“打开(Open)”菜单项外,用户也可以选择最近使用过的文件列表来打开相应的文档。在应用程序的运行过程中,系统会记录下4个(默认)最近使用过的文件,并将文件名保存在Windows的注册表中。当每次启动应用程序时,应用程序都会最近使用过的文件名称显示在“文件(File)”菜单中。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.1文档序列化过程3.保存文档当MFCAppWizard创建应用程序时,它会自动将“文件(File)”菜单中的“保存(Save)”命令与文档类CDocument的OnFileSave函数在内部关联起来,但用户在程序框架中看不到相应的代码。课后答案网OnFileSave函数还会进一步完成下列工作:(1)弹出通用文件“保存”对话框,让用户提供一个文件名。(2)调用文档对象的CDocument::OnSaveDocument虚函数,接着又自动调用Serialize函数,将www.hackshp.cnCArchive对象的内容保存在文档中。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.1文档序列化过程4.关闭文档当用户试图关闭文档(或退出应用程序)时,应用程序会根据用户对文档的修改与否来进一步完成下列任务:(1)若文档内容已被修改,则弹出一个消息对话框,询问用户是否需要将文档保存。当用户选择“是”,则应用程序执行课后答案网OnFileSave过程。(2)调用CDocument::OnCloseDocument虚函数,关闭所有与该文档相关联的文档窗口及相应的视图,调用文档类www.hackshp.cnCDocument的DeleteContents清除文档数据。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.2文档序列化操作从上述的单文档序列化过程可以看出:打开和保存文档时,系统都会自动调用Serialize函数。事实上,MFCAppWizard在创建文档应用程序框架时已在文档类中重载了Serialize函数,通过在该函数中添加代码可达到实现数据序列化的目的。例如,在Ex_SDI单文档应用程序的文档类中有这样的默认代码:voidCEx_SDIDoc::Serialize(CArchive&ar)课后答案网{if(ar.IsStoring())//当文档数据需要存盘时{//TODO:addstoringcodeherewww.hackshp.cn}else//当文档数据需要读取时{//TODO:addloadingcodehere}}代码中,Serialize函数的参数ar是一个CArchive类引用变量。通过判断ar.IsStoring的结果是“真”还是“假”就可决定向文档写或读数据。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.2文档序列化操作CArchive(归档)类提供对文件数据进行缓存,它同时还保存一个内部标记,用来标识文档是存入(写盘)还是载入(读盘)。每次只能有一个活动的存档与ar相连。通过CArchive类可以简化文件操作,它提供“<<”和“>>”运算符,用于向文件写入简单的数据类型以及从文件中读取它们,表课后答案网6.6列出了CArchive所支持的的常用数据类型。表6.6ar中可以使用<<和>>运算符的数据类型www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.2文档序列化操作除了“<<”和“>>”运算符外,CArchive类还提供成员函数ReadString和WriteString用来从一个文件对象中读写一行文本,它们的原型如下:BoolReadString(CString&rString);LPTSTRReadString(LPTSTR课后答案网lpsz,UINTnMax);voidWriteString(LPCTSTRlpsz);其中,lpsz用来指定读或写的文本内容,nMax用来指定可以读出的最大字符个数。需要说明的是,当向一个文件写一行字符串时,字符www.hackshp.cn‘’和‘n’都不会写到文件中,在使用时要特别注意。下面举一个简单的示例来说明Serialize函数和CArchive类的文档序列化操作方法。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.2文档序列化操作[例Ex_SDIArchive]一个简单的文档序列化示例(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_SDIArchive。(2)打开StringTable资源,将文档模板字串资源IDR_MAINFRAME内容修改为:文档序列化操作nnn自定义文件(*.my)n.mynExSDIArchive.DocumentnEx_SDIDocument(3)为CEx_SDIArchiveDoc课后答案网类添加下列成员变量:public:charm_chArchive[100];//读写数据时使用CStringwww.hackshp.cnm_strArchive;//读写数据时使用BOOLm_bIsMyDoc;//用于判断文档(4)在CEx_SDIArchiveDoc类构造函数中添加下列代码:CEx_SDIArchiveDoc::CEx_SDIArchiveDoc(){m_bIsMyDoc=FALSE;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDIArchive](5)在CEx_SDIArchiveDoc::OnNewDocument函数中添加下列代码:BOOLCEx_SDIArchiveDoc::OnNewDocument(){if(!CDocument::OnNewDocument())returnFALSE;课后答案网strcpy(m_chArchive,"&这是一个用于测试文档的内容!");m_strArchive="这是一行文本!";m_bIsMyDocwww.hackshp.cn=TRUE;returnTRUE;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDIArchive](6)在CEx_SDIArchiveDoc::Serialize函数中添加下列代码:voidCEx_SDIArchiveDoc::Serialize(CArchive&ar){if(ar.IsStoring()){if(m_bIsMyDoc)课后答案网//是自己的文档{for(inti=0;i>m_chArchive[0];//读取文档首字符if(m_chArchive[0]=="&")//是自己的文档{ 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!for(inti=1;i>m_chArchive[i];ar.ReadString(m_strArchive);CStringstr;str.Format("%s%s",m_chArchive,m_strArchive);课后答案网AfxMessageBox(str);m_bIsMyDoc=TRUE;}else//不是自己的文档{www.hackshp.cnm_bIsMyDoc=FALSE;AfxMessageBox("打开的文档无效!");}}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SDIArchive](7)将文档模板字串资源IDR_MAINFRAME内容修改如下:文档序列化操作nnn自定义文件(*.my)n.mynExSDIArchive.DocumentnEx_SDIDocument(8)编译运行并测试。程序运行后,选择“文件”→“另存为”菜单,指定一个文档名1.my,然后选择“文件”课后答案网→“新建”菜单,再打开该文档,结果就会弹出对话框,显示该文档的内容,如图www.hackshp.cn6.12所示。图6.12显示文档内容 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.3使用简单数组集合类简单数组集合类是一个大小动态可变的数组,数组中的元素可用下标运算符“[]”来访问(从0开始),设置或获取元素数据。若要设置超过数组当前个数的元素的值,可以指定是否使数组自动扩展。当数组不需扩展时,访问数组集合类的速度与访问标准C++中的数组的速度同样快。以下的基本操作对所有的简单数组集合类都适用。1.简单数组集合类的构造及元素的添加对简单数组集合类构造的方法都是一样的,均是使用各自的构造函数,它们的原型如课后答案网下:CByteArrayCByteArray();CDWordArraywww.hackshp.cnCDWordArray();CObArrayCObArray();CPtrArrayCPtrArray();CStringArrayCStringArray();CUIntArrayCUIntArray();CWordArrayCWordArray();下面的代码说明了简单数组集合类的两种构造方法:CObArrayarray;//使用默认的内存块大小CObArray*pArray=newCObArray;//使用堆内存中的默认的内存块大小 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.3使用简单数组集合类1.简单数组集合类的构造及元素的添加它们的原型如下:voidSetSize(intnNewSize,intnGrowBy=-1);intGetSize()const;课后答案网向简单数组集合类添加一个元素,可使用成员函数Add和Append,原型如下:intAdd(CObject*newElement);intAppend(constCObArray&www.hackshp.cnsrc);其中,Add函数是向数组的末尾添加一个新元素,且数组自动增1。如果调用的函数SetSize的参数nGrowBy的值大于1,那么扩展内存将被分配。此函数返回被添加的元素序号,元素序号就是数组下标。参数newElement表示要添加的相应类型的数据元素。而Append函数是向数组的末尾添加由src指定的另一个数组的内容。函数返回加入的第一个元素的序号。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.3使用简单数组集合类2.访问简单数组集合类的元素在MFC中,一个简单数组集合类元素的访问既可以使用GetAt函数,也可使用“[]”操作符,例如://CObArray::operator[]课后答案网示例CObArrayarray;CAge*pa;//CAge是一个用户类array.Add(newCAge(21));www.hackshp.cn//添加一个元素array.Add(newCAge(40));//再添加一个元素pa=(CAge*)array[0];//获取元素0array[0]=newCAge(30);//替换元素0;//CObArray::GetAt示例CObArrayarray;array.Add(newCAge(21));//元素0array.Add(newCAge(40));//元素1 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.3使用简单数组集合类3.删除简单数组集合类的元素删除简单数组集合类中的元素一般需要进行以下几个步骤:(1)使用函数GetSize和整数下标值访问简单数组集合类中的元素。(2)若对象元素是在堆内存中创建的,则使用delete操作符删除每一个对象元素。(3)调用函数RemoveAll课后答案网删除简单数组集合类中的所有元素。例如,下面代码是一个CObArray的删除示例:CObArrayarray;CAge*pa1;www.hackshp.cnCAge*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){www.hackshp.cnif(ar.IsStoring())ar<>strName>>strID>>fScore1>>fScore2>>fScore3>>fAverage;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Student]3)添加并处理菜单项(1)在菜单资源的主菜单中增加顶层菜单项“学生记录(&S)”,在该顶层菜单项中增加子菜单“添加(&A)”(ID_STUREC_ADD)。(2)用ClassWizard为CEx_StudentDoc类添加ID_STUREC_ADD的COMMAND消息映射,并在映射函数中添加下列代码:课后答案网voidCEx_StudentDoc::OnSturecAdd(){CAddDlgdlg;www.hackshp.cnif(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);//更新视图}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Student]3)添加并处理菜单项(3)在Ex_StudentDoc.cpp文件的开始处,增加包含CAddDlg的头文件。#include"Ex_StudentDoc.h"#include"AddDlg.h“4)完善代码课后答案网(1)在Ex_StudentDoc.h文件中,为CEx_StudentDoc类添加下列成员变量和成员函数:public:www.hackshp.cnCObArraym_stuObArray;intGetAllRecNum(void);CStudent*GetStudentAt(intnIndex); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Student]4)完善代码(2)在Ex_StudentDoc.cpp文件中,添加函数的实现代码:CStudent*CEx_StudentDoc::GetStudentAt(intnIndex){if((nIndex<0)||nIndex>m_stuObArray.GetUpperBound())课后答案网return0;//超界处理return(CStudent*)m_stuObArray.GetAt(nIndex);}www.hackshp.cnintCEx_StudentDoc::GetAllRecNum(){returnm_stuObArray.GetSize();} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Student]4)完善代码(3)在CEx_StudentDoc析构函数中添加下列代码:CEx_StudentDoc::~CEx_StudentDoc(){intnIndex=GetAllRecNum();while(nIndex--)课后答案网deletem_stuObArray.GetAt(nIndex);m_stuObArray.RemoveAll();}(4)在Serializewww.hackshp.cn函数中添加下列代码:voidCEx_StudentDoc::Serialize(CArchive&ar){if(ar.IsStoring()){m_stuObArray.Serialize(ar);}else{m_stuObArray.Serialize(ar);}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Student]4)完善代码(5)在CEx_StudentView::OnDraw函数中添加下列代码:voidCEx_StudentView::OnDraw(CDC*pDC){CEx_StudentDoc*课后答案网pDoc=GetDocument();ASSERT_VALID(pDoc);inty=0;for(intnIndex=0;nIndexGetAllRecNum();nIndex++)www.hackshp.cn{pDoc->GetStudentAt(nIndex)->Display(y,pDC);y+=16;}}(6)打开文档的字串资源IDR_MAINFRAME,将其内容修改为:Ex_StudentnStudentRecnEx_Stun记录文件(*.rec)n.recnExStudent.DocumentnEx_StuDocument(7)编译运行并测试,结果如前图6.13所示。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.5使用CFile类在MFC中,CFile类是一个文件I/O的基类。它直接支持非缓冲、二进制的磁盘文件的输入输出,也可以使用其派生类处理文本文件(CStdioFile)和内存文件(CMemFile)。CFile类的读写功能类似于C语言中的fread和fwrite,而CStdioFile类的读写功能类似于C语言中的fgets和fputs。使用CFile类可以打开或关闭一个磁盘文件、向一个文件读或写数据等。下面分课后答案网别说明。1.文件的打开和关闭在MFC中,使用www.hackshp.cnCFile打开一个文件通常使用下列两个步骤:(1)构造一个不带任何参数的CFile对象;(2)调用成员函数Open并指定文件路径以及文件标志。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.5使用CFile类CFile类的Open函数原型如下:BOOLOpen(LPCTSTRlpszFileName,UINTnOpenFlags,CFileException*pError=NULL);其中,lpszFileName用来指定一个要打开的文件路径,该路径可以是相对的、绝对的或是一个网络文件名(UNC)。nOpenFlags用来指定文件打开的标志,它的值见表6.9。课后答案网表6.9CFile类的文件访问方式www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.5使用CFile类2.文件的读写和定位CFile类支持文件的读、写和定位操作。它们相关函数的原型如下:UINTRead(void*lpBuf,UINTnCount);此函数将文件中指定大小的数据读入指定的缓冲区,并返回向缓冲区传输的字节数。需要说明的是,这个返回值可能小于nCount,这是因为可能到达了文件的结尾。课后答案网voidWrite(constvoid*lpBuf,UINTnCount);此函数将缓冲区的数据写到文件中。参数lpBuf用来指定要写到文件中的数据缓冲区的指针,www.hackshp.cnnCount表示从数据缓冲区传送的字节数。对于文本文件,每行的换行符也被计算在内。LONGSeek(LONGlOff,UINTnFrom);此函数用来定位文件指针的位置,若要定位的位置是合法的,此函数将返回从文件开始的偏移量。否则,返回值是不定的且激活一个CFileException对象。参数lOff用来指定文件指针移动的字节数,nFrom表示指针移动方式,它可以是CFile::begin(从文件的开始位置)、CFile::current(从文件的当前位置)或CFile::end(从文件的最后位置,但lOff必须为负值才能在文件中定位,否则将超出文件)等。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.5使用CFile类3.获取文件的有关信息CFile还支持获取文件状态,包括文件是否存在、创建与修改的日期和时间、逻辑大小和路径等。BOOLGetStatus(CFileStatus&rStatus)const;staticBOOLPASCALGetStatus(LPCTSTR课后答案网lpszFileName,CFileStatus&rStatus);若指定文件的状态信息成功获得,该函数返回TRUE,否则返回FALSE。其中,参数lpszFileName用来指定一个文件路径,这个路径可以是相对的或是绝对的,但不能是网络文件名。www.hackshp.cnrStatus用来存放文件状态信息,它是一个CFileStatus结构类型,该结构具有下列成员:CTimem_ctime文件创建日期和时间CTimem_mtime文件最后一次修改日期和时间CTimem_atime文件最后一次访问日期和时间LONGm_size文件大小的字节数BYTEm_attribute文件属性charm_szFullName[_MAX_PATH]文件名 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.3.5使用CFile类4.CFile和CArchive类之间的关联可以将一个外部磁盘文件和一个CArchive对象关联起来。例如:CFiletheFile;theFile.Open(...,CFile::modeWrite);CArchivearchive(&theFile,CArchive::store);课后答案网其中,CArchive构造函数的原型如下:CArchive(CFile*pFile,UINTnMode,intnBufSize=4096,void*lpBuf=NULL);参数pFile用来指定与之关联的文件指针。nBufSize表示内部文件的缓冲区大小,默认值为4096www.hackshp.cn字节。lpBuf表示自定义的缓冲区指针,若为NULL,则表示缓冲区建立在堆内存中,当对象清除时,缓冲区内存也被释放;若指明用户缓冲区,对象消除时,缓冲区内存不会被释放。nMode用来指定文档是用于存入还是读取,它可以是CArchive::load(读取数据)、CArchive::store(存入数据)或CArchive::bNoFlushOnDelete(当析构函数被调用时,避免文档自动调用Flush。若设置这个标志,则必须在析构函数被调用之前调用Close。否则文件数据将被破坏)。也可将一个CArchive对象与CFile类指针相关联,如下面的代码(ar是CArchive对象):constCFile*fp=ar.GetFile(); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4视图及视图类MFC中的CView类及其它的派生类封装了视图的各种不同的功能,它们为用户实现最新的Windows特性提供了很大的便利。这些视图类如表6.10所示,它们都可以作为文档应用程序中视图类的基类,其设置的方法是在MFCAppWizard创建SDI/MDI的第6步中进行基类的选择。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.1一般视图类的使用课后答案网表6.10CView的派生类及其功能描述www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.1一般视图类的使用1.CEditView类CEditView类对象是一种视图,像CEdit类一样,它也提供窗口编辑控制功能,可以用来执行简单文本操作,如打印、查找、替换、剪贴板的剪切、复制和粘贴等。由于CEditView类自动封装上述功能的映射函数,因此只要在文档模板中使用CEditView类,那么应用程序的“编辑”菜单和“文件”菜单里的菜单项都可课后答案网自动激活。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.1一般视图类的使用[例Ex_Edit]创建一个基于CEditView类的单文档应用程序。(1)选择“文件”→“新建”菜单,在弹出的“新建”对话框中选择“工程”标签,选择MFCAppWizard(exe)的项目类型,指定项目工作文件夹位置,输入项目名Ex_Edit,单击[确定]按钮。(2)在向导的第1步中,将应用程序类型选为“单个文档”课后答案网(SDI)。(3)保留默认选项,单击[下一步]按钮,直到出现向导的第6步,将CEx_EditView的基类选为CEditView,如图6.14所示。(4)单击[完成]按钮,编译运行,打开一个文档,结果如图www.hackshp.cn6.15所示。图6.14更改CEx_EditView的基类图6.15Ex_Edit运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.1一般视图类的使用2.CRichEditView类CRichEditView类使用了复合文本编辑控件,因此它支持混合字体格式和更大数据量的文本。CRichEditView类被设计成与CRichEditDoc和CRichEditCntrItem类一起使用,它们可实现一个完整的ActiveX包容器应用程序。3.CFormView类课后答案网CFormView类是一个非常有用的视图类,它具有许多无模式对话框的特点。像CDialog的派生类一样,CFormView的派生类也和相应的对话框资源相联系,它也支持对话框数据交换和对话框数据确认www.hackshp.cn(DDX和DDV)。CFormView是所有表单视图(如CRecordView、CDaoRecordView、CHtmlView等)的基类;一个基于表单的应用程序能让用户在程序中创建和使用一个或多个表单。创建表单应用程序的基本方法除了在MFCAppWizard创建的第6步中选择CFormView作为文档应用程序视图类的基类外,还可以通过选择“插入”→“新建形式(NewForm)”菜单命令在文档应用程序中自动插入一个表单。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.1一般视图类的使用4.CHtmlView类CHtmlView类是在文档视图结构中提供WebBrowser控件的功能。WebBrowser控件可以浏览网址,也可以作为本地文件和网络文件系统的窗口,它支持超级链接、统一资源定位(URL)导航器并维护历史列表等。5.CScrollView类课后答案网CScrollView类不仅能直接支持视图的滚动操作,而且还能管理视口的大小和映射模式,并能响应滚动条消息、键盘消息以及鼠标滚轮消息。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.2列表控件和列表视图列表控件是一种极为有用的控件之一,它可以用“大图标”、“小图标”、“列表视图”或“报表视图”等四种不同的方式来显示一组信息,如图6.16所示。课后答案网www.hackshp.cn图6.16列表控件样式 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.2列表控件和列表视图1.列表控件的风格及其修改列表控件的风格有两类,一类是一般风格,如表6.11所示;另一类是VisualC++6.0在原有的基础上添加的扩展风格,如LVS_EX_FULLROWSELECT,表示整行选择,但它仅用于“报表视图”显示方式中。表6.11列表控件的一般风格课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.2列表控件和列表视图2.列表项的基本操作CListView按照MFC文档视图结构封装了列表控件CListCtrl类的功能。由于它又是从CCtrlView中派生的,因此它既可以调用CCtrlView的基类CView类的成员函数,又可以使用CListCtrl功能。当使用CListCtrl功能时,必需先要得到CListView封装的内嵌可引用的CListCtrl课后答案网对象,这时可调用CListView的成员函数GetListCtrl,如下面的代码:CListCtrl&listCtrl=GetListCtrl();//listCtrl必须定义成引用列表控件类CListCtrlwww.hackshp.cn提供了许多用于列表项操作的成员函数,如列表项与列的添加和删除等,下面分别介绍。(1)函数SetImageList用来为列表控件设置一个关联的图像列表,其原型如下:CImageList*SetImageList(CImageList*pImageList,intnImageList);其中,nImageList用来指定图像列表的类型,它可以是LVSIL_NORMAL(大图标)、LVSIL_SMALL(小图标)和LVSIL_STATE(表示状态的图像列表)。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.2列表控件和列表视图2.列表项的基本操作函数InsertItem用来向列表控件中插入一个列表项。该函数成功时返回新列表项的索引号,否则返回-1。函数原型如下:intInsertItem(constLVITEM*pItem);intInsertItem(intnItem课后答案网,LPCTSTRlpszItem);intInsertItem(intnItem,LPCTSTRlpszItem,intnImage);其中,nItem用来指定要插入的列表项的索引号,lpszItem表示列表项的文本标签,nImage表示列表项图标在图像列表中的索引号;而www.hackshp.cnpItem用来指定一个指向LVITEM结构的指针,其结构描述如下: 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!typedefstruct_LVITEM{UINTmask;//指明哪些参数有效intiItem;//列表项索引intiSubItem;//子项索引UINTstate;//列表项状态UINTstateMask;课后答案网//指明state哪些位是有效的,-1全部有效LPTSTRpszText;//列表项文本标签intcchTextMax;www.hackshp.cn//文本大小intiImage;//在图像列表中列表项图标的索引号。LPARAMlParam;//32位值intiIndent;//项目缩进数量,1个数量等于1个图标的像素宽度}LVITEM,FAR*LPLVITEM;结构中,mask最常用的值可以是:LVIF_TEXTpszText有效或必须赋值。LVIF_IMAGEiImage有效或必须赋值。LVIF_INDENTiIndent有效或必须赋值。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.2列表控件和列表视图2.列表项的基本操作函数DeleteItem和DeleteAllItems分别用来删除指定的列表项和全部列表项,函数原型如下:BOOLDeleteItem(intnItem);BOOLDeleteAllItems();函数FindItem用来查寻列表项,函数成功查找时返回列表项的索引号,否则返回-1。其原型如下:intFindItem(LVFINDINFO*pFindInfo,intnStart=-1)const;其中,nStart表示开始查找的索引号课后答案网,-1表示从头开始。pFindInfo表示要查找的信息,其结构描述如下:typedefstructtagLVFINDINFO{UINTwww.hackshp.cnflags;//查找方式LPCTSTRpsz;//匹配的文本LPARAMlParam;//匹配的值POINTpt;//查找开始的位置坐标。UINTvkDirection;//查找方向,用虚拟方向健值表示。}LVFINDINFO,FAR*LPFINDINFO;结构中,flags可以是下列值之一或组合:LVFI_PARAM查找内容由lParam指定。LVFI_PARTIAL查找内容由psz指定,不精确查找。LVFI_STRING查找内容由psz指定,精确查找。LVFI_WRAP若没有匹配,再从头开始。LVFI_NEARESTXY靠近pt位置查找,查找方向由vkDirection确定。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.2列表控件和列表视图2.列表项的基本操作(5)函数Arrange用来按指定方式重新排列列表项,其原型如下:BOOLArrange(UINTnCode);其中,nCode用来指定排列方式,它可以是下列值之一:LVA_ALIGNLEFT课后答案网左对齐LVA_ALIGNTOP上对齐LVA_DEFAULT默认方式LVA_SNAPTOGRIDwww.hackshp.cn使所有的图标安排在最接近的网格位置处(6)函数InsertColumn用来向列表控件插入新的一列,函数成功调用后返回新的列的索引,否则返回-1。其原型如下:intInsertColumn(intnCol,constLVCOLUMN*pColumn);intInsertColumn(intnCol,LPCTSTRlpszColumnHeading,intnFormat=LVCFMT_LEFT,intnWidth=-1,intnSubItem=-1);其中,nCol用来指定新列的索引,lpszColumnHeading用来指定列的标题文本,nFormat用来指定列排列的方式,它可以是LVCFMT_LEFT(左对齐)、LVCFMT_RIGHT(右对齐)和LVCFMT_CENTER(居中对齐);nWidth用来指定列的像素宽度,-1时表示宽度没有设置;nSubItem表示与列相关的子项索引,-1时表示没有子项。pColumn表示包含新列信息的LVCOLUMN结构地址,其结构描述如下: 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!typedefstruct_LVCOLUMN{UINTmask;//指明哪些参数有效intfmt;//列的标题或子项文本格式intcx;//列的像素宽度LPTSTRpszText;//列的标题文本intcchTextMax;//列的标题文本大小intiSubItem;//和列相关的子项索引intiImage;//图像列表中的图像索引intiOrder;课后答案网//列的序号,最左边的列为0}LVCOLUMN,FAR*LPLVCOLUMN;结构中,mask可以是0或下列值之一或组合:LVCF_FMTfmt参数有效LVCF_IMAGEwww.hackshp.cniImage参数有效LVCF_ORDERiOrder参数有效LVCF_SUBITEMiSubItem参数有效LVCF_TEXTpszText参数有效LVCF_WIDTHcx参数有效fmt可以是下列值之一:LVCFMT_BITMAP_ON_RIGHT位图出现在文本的右边,对于从图像列表中选取的图像无效LVCFMT_CENTER文本居中LVCFMT_COL_HAS_IMAGES列表头的图像是在图像列表中LVCFMT_IMAGE从图像列表中显示一个图像LVCFMT_LEFT文本左对齐LVCFMT_RIGHT文本右对齐 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.2列表控件和列表视图2.列表项的基本操作(7)函数DeleteColumn用来从列表控件中删除一个指定的列,其原型如下:BOOLDeleteColumn(intnCol);除了上述操作外,还有一些函数是用来设置或获取列表控件的相关属性的。例如SetColumnWidth用来设置指定列的像素宽度,课后答案网GetItemCount用来返回列表控件中的列表项个数等。它们的原型如下:BOOLSetColumnWidth(intnCol,intcx);intGetItemCount(www.hackshp.cn);其中,nCol用来指定要设置的列的索引号,cx用来指定列的像素宽度,它可以是LVSCW_AUTOSIZE,表示自动调整宽度。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.2列表控件和列表视图3.列表控件的消息在列表视图中,可以用MFCClassWizard映射的控件消息有公共控件消息(如NM_DBLCLK)、标题头控件消息以及列表控件消息。常用的列表控件消息有:LVN_BEGINDRAG用户按左鼠拖动列表列表项LVN_BEGINLABELEDIT课后答案网用户对某列表项标签进行编辑LVN_COLUMNCLICK某列被按击LVN_ENDLABELEDIT用户对某列表项标签结束编辑LVN_ITEMACTIVATEwww.hackshp.cn用户激活某列表项LVN_ITEMCHANGED当前列表项已被改变LVN_ITEMCHANGING当前列表项即将改变LVN_KEYDOWN某键被按下 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.2列表控件和列表视图4.示例[例Ex_List]列表显示当前的文件(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_List,在创建的第6步将视图的基类选择为CListView。(2)为CEx_ListView类添加下列成员函数和成员函数:public:CImageListm_ImageList;课后答案网CImageListm_ImageListSmall;CStringArraym_strArray;voidSetCtrlStyle(HWNDhWnd,DWORDdwNewStyle)www.hackshp.cn{DWORDdwOldStyle;dwOldStyle=GetWindowLong(hWnd,GWL_STYLE);//获取当前风格if((dwOldStyle&LVS_TYPEMASK)!=dwNewStyle){dwOldStyle&=~LVS_TYPEMASK;dwNewStyle|=dwOldStyle;SetWindowLong(hWnd,GWL_STYLE,dwNewStyle);//设置新风格}}其中,成员函数SetCtrlStyle用来设置列表控件的一般风格。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_List](3)将项目工作区窗口切换到ResourceView页面,打开Accelerator节点下的IDR_MAINFRAME,为其添加一个键盘加速键Ctrl+Shift+X,其ID号为ID_VIEW_CHANGE。(4)用ClassWizard为CEx_ListView类添加ID_VIEW_CHANGE的COMMAND消息映射函数,并增加下列代码:课后答案网voidCEx_ListView::OnViewChange(){staticintnStyleIndex=1;www.hackshp.cnDWORDstyle[4]={LVS_REPORT,LVS_ICON,LVS_SMALLICON,LVS_LIST};CListCtrl&m_ListCtrl=GetListCtrl();SetCtrlStyle(m_ListCtrl.GetSafeHwnd(),style[nStyleIndex]);nStyleIndex++;if(nStyleIndex>3)nStyleIndex=0;}这样,当程序运行后同时按下Ctrl、Shift和x键就会切换列表控件的显示方式。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_List](5)用ClassWizard为CEx_ListView类添加NM_DBLCLK消息映射函数,并增加下列代码:voidCEx_ListView::OnDblclk(NMHDR*pNMHDR,LRESULT*pResult){LPNMITEMACTIVATElpItem=(LPNMITEMACTIVATE)pNMHDR;课后答案网intnIndex=lpItem->iItem;if(nIndex>=0){www.hackshp.cnCListCtrl&m_ListCtrl=GetListCtrl();CStringstr=m_ListCtrl.GetItemText(nIndex,0);MessageBox(str);}*pResult=0;}这样,当双击某个列表项时,就是弹出一个消息对话框,显示该列表项的文本内容。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_List](6)在CEx_ListView::OnInitialUpdate中添加下列代码:voidCEx_ListView::OnInitialUpdate(){CListView::OnInitialUpdate();//创建图像列表课后答案网m_ImageList.Create(32,32,ILC_COLOR8|ILC_MASK,1,1);m_ImageListSmall.Create(16,16,ILC_COLOR8|ILC_MASK,1,1);CListCtrl&m_ListCtrl=GetListCtrl();www.hackshp.cnm_ListCtrl.SetImageList(&m_ImageList,LVSIL_NORMAL);m_ListCtrl.SetImageList(&m_ImageListSmall,LVSIL_SMALL);LV_COLUMNlistCol;char*arCols[4]={"文件名","大小","类型","修改日期"};listCol.mask=LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!//添加列表头for(intnCol=0;nCol<4;nCol++){listCol.iSubItem=nCol;listCol.pszText课后答案网=arCols[nCol];if(nCol==1)listCol.fmt=LVCFMT_RIGHT;www.hackshp.cnelselistCol.fmt=LVCFMT_LEFT;m_ListCtrl.InsertColumn(nCol,&listCol);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!//查找当前目录下的文件CFileFindfinder;BOOLbWorking=finder.FindFile("*.*");intnItem=0,nIndex,nImage;CTimem_time;课后答案网CStringstr,strTypeName;while(bWorking){www.hackshp.cnbWorking=finder.FindNextFile();if(finder.IsArchived()){str=finder.GetFilePath();SHFILEINFOfi; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!//获取文件关联的图标和文件类型名SHGetFileInfo(str,0,&fi,sizeof(SHFILEINFO),SHGFI_ICON|SHGFI_LARGEICON|SHGFI_TYPENAME);strTypeName=fi.szTypeName;nImage=-1;for(inti=0;i1024)str.Format("%dK",dwSize/1024);else课后答案网str.Format("%d",dwSize);m_ListCtrl.SetItemText(nIndex,1,str);www.hackshp.cnm_ListCtrl.SetItemText(nIndex,2,strTypeName);finder.GetLastWriteTime(m_time);m_ListCtrl.SetItemText(nIndex,3,m_time.Format("%Y-%m-%d"));nItem++;}}SetCtrlStyle(m_ListCtrl.GetSafeHwnd(),LVS_REPORT);//设置为报表方式m_ListCtrl.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!//设置扩展风格,使得列表项一行全项选择且显示出网格线m_ListCtrl.SetColumnWidth(0,LVSCW_AUTOSIZE);//设置列宽m_ListCtrl.SetColumnWidth(1,100);m_ListCtrl.SetColumnWidth(2,课后答案网LVSCW_AUTOSIZE);m_ListCtrl.SetColumnWidth(3,200);}www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_List](7)编译并运行,结果如图6.17所示。课后答案网www.hackshp.cn图6.17Ex_List运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.3树控件和树视图1.树形视图的风格常见的树控件风格如表6.12所示,其修改方法与列表控件同的一般风格修改方法相同。课后答案网表6.12树控件的一般风格www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.3树控件和树视图2.树控件的常用操作树控件类CTreeCtrl类提供了许多关于树控件操作的成员函数,如节点的添加和删除等。下面分别说明。(1)函数InsertItem用来向树控件插入一个新节点,操作成功后,函数返回新节点的句柄,否则返回NULL。函数原型如下:HTREEITEMInsertItem(UINT课后答案网nMask,LPCTSTRlpszItem,intnImage,intnSelectedImage,UINTnState,UINTnStateMask,LPARAMlParam,HTREEITEMhParent,HTREEITEMhInsertAfter);HTREEITEMInsertItem(LPCTSTRlpszItem,HTREEITEMhParent=TVI_ROOT,HTREEITEMhInsertAfter=www.hackshp.cnTVI_LAST);HTREEITEMInsertItem(LPCTSTRlpszItem,intnImage,intnSelectedImage,HTREEITEMhParent=TVI_ROOT,HTREEITEMhInsertAfter=TVI_LAST);其中,nMask用来指定要设置的属性,lpszItem用来指定节点的文本标签内容,nImage用来指定该节点图标在图像列表中的索引号,nSelectedImage表示该节点被选定时,其图标图像列表中的索引号,nState表示该节点的当前状态,它可以是TVIS_BOLD(加粗)、TVIS_EXPANDED(展开)和TVIS_SELECTED(选中)等,nStateMask用来指定哪些状态参数有效或必须设置,lParam表示与该节点关联的一个32位值,hParent用来指定要插入节点的父节点的句柄,hInsertAfter用来指定新节点添加的位置,它可以是TVI_FIRST(插到开始位置)、TVI_LAST(插到最后)和TVI_SORT(插入后按字母重新排序)。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.3树控件和树视图2.树控件的常用操作(2)函数DeleteItem和DeleteAllItems分别用来删除指定的节点和全部的节点。它们的原型如下:BOOLDeleteAllItems();BOOLDeleteItem(HTREEITEM课后答案网hItem);其中,hItem用来指定要删除的节点的句柄。如果hItem的值是TVI_ROOT,则所有的节点都被从此控件中删除。(3)函数Expandwww.hackshp.cn用来用来展开或收缩指定父节点的所有子节点,其原型如下:BOOLExpand(HTREEETEMhItem,UINTnCode);其中,hItem指定要被展开或收缩的节点的句柄,nCode用来指定动作标志,它可以是:TVE_COLLAPSE收缩所有子节点TVE_COLLAPSERESET收缩并删除所有子节点TVE_EXPAND展开所有子节点TVE_TOGGLE如果当前是展开的则收缩,反之则展开 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.3树控件和树视图2.树控件的常用操作(4)函数GetNextItem用来获取下一个节点的句柄。它的原型如下:HTREEITEMGetNextItem(HTREEITEMhItem,UINTnCode);其中,hItem指定参考节点的句柄,nCode用来指定与hItem的关系标志,常见的标志有:TVGN_CARET返回当前选择节点的句柄TVGN_CHILD课后答案网返回第一个子节点句柄,hItem必须为NULLTVGN_NEXT返回下一个兄弟节点(同一个树支上的节点)句柄TVGN_PARENT返回指定节点的父节点句柄TVGN_PREVIOUSwww.hackshp.cn返回上一个兄弟节点句柄TVGN_ROOT返回hItem父节点的第一个子节点句柄(5)函数HitTest用来测试鼠标当前操作的位置位于哪一个节点中,并返回该节点句柄。它的原型如下:HTREEITEMHitTest(CPointpt,UINT*pFlags);其中pFlags包含当前鼠标所在的位置标志,如下列常用定义:TVHT_ONITEM在节点上TVHT_ONITEMBUTTON在节点前面的按钮上TVHT_ONITEMICON在节点文本前面的图标上TVHT_ONITEMLABEL在节点文本上 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.3树控件和树视图除了上述操作外,还有其他常见操作,如表6.13所示。表6.13CTreeCtrl类其他常见操作课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.4.3树控件和树视图3.树形视图控件的通知消息同列表视图相类似,树视图也可以用ClassWizard映射公共控件消息和树控件消息。其中,常用的树控件消息有:TVN_BEGINDRAG开始拖放操作TVN_BEGINLABELEDIT课后答案网开始编辑文本TVN_BEGINRDRAG鼠标右按钮开始拖放操作TVN_ENDLABELEDIT文本编辑结束TVN_ITEMEXPANDEDwww.hackshp.cn含有子节点的父节点已展开或收缩TVN_ITEMEXPANDING含有子节点的父节点将要展开或收缩TVN_SELCHANGED当前选择节点发生改变TVN_SELCHANGING当前选择节点将要发生改变 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!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(HTREEITEMhItem,CStringstrPath){www.hackshp.cnCTreeCtrl&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,hItem,TVI_SORT);}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例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);www.hackshp.cnif(data==0)str=treeCtrl.GetItemText(folderItem);elsestr.Format("%c:\",data);strPath=str+"\"+strPath;folderItem=treeCtrl.GetParentItem(folderItem);}strPath=strPath+"*.*";returnstrPath;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Tree](5)用ClassWizard为CEx_TreeView类添加TVN_SELCHANGED消息处理,并增加下列代码:voidCEx_TreeView::OnSelchanged(NMHDR*pNMHDR,LRESULT*pResult){NM_TREEVIEW*pNMTreeView=(NM_TREEVIEW*)pNMHDR;HTREEITEMhSelItem=pNMTreeView->itemNew.hItem;//课后答案网获取当前选择的节点CTreeCtrl&treeCtrl=GetTreeCtrl();CStringstrPath=GetFoldItemPath(hSelItem);if(!strPath.IsEmpty()){InsertFoldItem(hSelItem,www.hackshp.cnstrPath);treeCtrl.Expand(hSelItem,TVE_EXPAND);}*pResult=0;}(6)在CEx_TreeView::PreCreateWindow函数中添加设置树控件风格代码:BOOLCEx_TreeView::PreCreateWindow(CREATESTRUCT&cs){cs.style|=TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS;returnCTreeView::PreCreateWindow(cs);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Tree](7)在CEx_TreeView::OnInitialUpdate函数中添加下列代码:voidCEx_TreeView::OnInitialUpdate(){CTreeView::OnInitialUpdate();CTreeCtrl&treeCtrl=GetTreeCtrl();m_ImageList.Create(16,课后答案网16,ILC_COLOR8|ILC_MASK,2,1);treeCtrl.SetImageList(&m_ImageList,TVSIL_NORMAL);//获取Windows文件夹路径以便获取其文件夹图标CStringstrPath;www.hackshp.cnGetWindowsDirectory((LPTSTR)(LPCTSTR)strPath,MAX_PATH+1);//获取文件夹及其打开时的图标,并添加到图像列表中SHFILEINFOfi;SHGetFileInfo(strPath,0,&fi,sizeof(SHFILEINFO),SHGFI_ICON|SHGFI_SMALLICON);m_ImageList.Add(fi.hIcon);SHGetFileInfo(strPath,0,&fi,sizeof(SHFILEINFO),SHGFI_ICON|SHGFI_SMALLICON|SHGFI_OPENICON);m_ImageList.Add(fi.hIcon); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!//获取已有的驱动器图标和名称CStringstr;for(inti=0;i<32;i++){str.Format("%c:\","A"+i);SHGetFileInfo(str,0,&fi,sizeof(SHFILEINFO),课后答案网SHGFI_ICON|SHGFI_SMALLICON|SHGFI_DISPLAYNAME);if(fi.hIcon){www.hackshp.cnintnImage=m_ImageList.Add(fi.hIcon);HTREEITEMhItem=treeCtrl.InsertItem(fi.szDisplayName,nImage,nImage);treeCtrl.SetItemData(hItem,(DWORD)("A"+i));}}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Tree](8)编译并运行,结果如图6.18所示。课后答案网www.hackshp.cn图6.18Ex_Tree运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5文档视图结构文档和视是编程者最关心的,应用程序的大部分代码都会被添加在这两个类中。文档和视紧密相联,是用户与文档之间的交互接口;用户通过文档视图结构可实现数据的传输、编辑、读取和保存等。但文档、视图以及和应用程序框架的相关部分之间还包含了一系列非常复杂的相互作用。切分窗口及一档多视是文档和视图相互作用的典型实例。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.1文档与视图的相互作用1.CView::GetDocument函数当MFCAppWizard产生应用程序CView类时,它同时也创建一个安全类型的GetDocument函数,它返回的是指向用户派生文档类的指针。该函数是一个内联(inline)函数,如下面的代码:CEx_SDIDoc*CEx_SDIView::GetDocu课后答案网ment()//non-debugversionisinline{ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEx_SDIDoc)));//“断言”m_pDocumentwww.hackshp.cn指针所指向的CEx_SDIDoc类是一个RUNTIME_CLASS类型return(CEx_SDIDoc*)m_pDocument;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.1文档与视图的相互作用2.CDocument::UpdateAllViews函数如果文档中的数据发生了改变,那么所有的视图都必须被通知到,以便它们能够对所显示的数据进行相应的更新。UpdateAllViews函数就起到这样的作用,它的原型如下。voidUpdateAllViews(CView*课后答案网pSender,LPARAMlHint=0L,CObject*pHint=NULL);其中,参数pSender表示视图指针,若在应用程序文档类的成员函数中调用该函数,则此参数应为NULL,若该函数被应用程序视图类中的成员函数调用,则此参数应为this。www.hackshp.cnlHint通常表示更新视图时发送信息的提示标识值,pHint表示存贮信息的对象指针。当UpdateAllViews函数被调用时,如果参数pSender指向某个特定的视图对象,那么除了该指定的视图之外,文档的所有其他视图的OnUpdate函数都会被调用。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.1文档与视图的相互作用3.CView::OnUpdate函数这是一个虚函数。当应用程序调用了CDocument::UpdateAllViews函数时,应用程序框架就会相应地调用该函数。virtualvoidOnUpdate(CView*pSender,LPARAMlHint,CObject*pHint);其中,参数pSender表示文档被更改的所在视图类指针,当为课后答案网NULL时表示所有的视图需要更新。默认的OnUpdate函数(lHint=0,pHint=NULL)使得整个窗口矩形无效。如果用户想要视图的某部分无效,那么用户就要定义相关的提示www.hackshp.cn(Hint)参数给出准确的无效区域;lHint和pHint含义同UpdateAllViews。事实上,hint机制主要用来在视图中根据提示标识值来获取文档或其他视图传递来的数据,例如将文档的CPoint数据传给所有的视图类,则有下列语句:GetDocument()->UpdateAllViews(NULL,1,(CObject*)&m_ptDraw); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.1文档与视图的相互作用4.CView::OnInitialUpdate函数当应用程序被启动时,或当用户从“文件”菜单中选择了“新建”或“打开”时,该CView虚函数都会被自动调用。该函数除了调用无提示参数(lHint=0,pHint=NULL)的OnUpdate函数之外,没做其他任何事情。课后答案网但用户可以重载此函数对文档所需信息进行初始化操作。例如,如果用户应用程序中的文档大小是固定的,那么用户就可以在此重载函数中根据文档大小设置视图滚动范围;如果应用程序中的文档大小是动态的,那么用户就可在文档每次改www.hackshp.cn变时调用OnUpdate来更新视图的滚动范围。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.1文档与视图的相互作用5.CDocument::OnNewDocument函数在文档应用程序中,当用户从“文件”菜单中选择“新建”命令时,框架将首先构造一个文档对象,然后调用该虚函数。这里是设置文档数据成员初始值的好地方,当然文档数据成员初始化处理还有其他的一些方法。例如,对于文档应用程序来课后答案网说,用户还可在文档构造函数中添加初始化代码。MFCAppWizard为用户的派生文档类自动产生了重载的OnNewDocument函数,如下面的代码:www.hackshp.cnBOOLCMyDoc::OnNewDocument(){if(!CDocument::OnNewDocument())//注意一定要保证对基类函数的调用,returnFALSE;//Doinitializationofnewdocumenthere.returnTRUE;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.2应用程序对象指针的互调1.从文档类中获取视图对象指针在文档类中有一个与其关联的各视图对象的列表,并可通过CDocument类的成员函数GetFirstViewPosition和GetNextView来定位相应的视图对象。GetFirstViewPosition函数用来获得与文档类相关联的视图列表中第一个可见视图的位置,GetNextView函数用来获取指定视图位置的视图类指针,并将此视图位置移动到下一个位置,若没有下一个视图,则视图位置为课后答案网NULL。它们的原型如下:virtualPOSITIONGetFirstViewPosition()const;virtualCView*GetNextView(POSITION&rPosition)const;例如,下面代码是使用www.hackshp.cnCDocument::GetFirstViewPosition和GetNextView重绘每个视图:voidCMyDoc::OnRepaintAllViews(){POSITIONpos=GetFirstViewPosition();while(pos!=NULL){CView*pView=GetNextView(pos);pView->UpdateWindow();}}//实现上述功能也可直接调用UpdateAllViews(NULL); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.2应用程序对象指针的互调2.从视图类中获取文档对象和主框架对象指针(2)在视图类中获取文档对象指针是很容易的,只需调用视图类中的成员函数GetDocument即可。而函数CWnd::GetParentFrame可实现从视图类中获取主框架指针,其原型如下:CFrameWnd*GetParentFrame()const;课后答案网该函数将获得父框架窗口指针,它在父窗口链中搜索,直到一个CFrameWnd(或其派生类)被找到为止。成功时返回一个CFrameWnd指针,否则返回NULL。3.在主框架类中获取视图对象指针www.hackshp.cn对于单文档应用程序来说,只需调用CFrameWnd类的GetActiveView成员函数即可,其原型如下:CView*GetActiveView()const;函数返回当前CView类指针,若没有当前视图,则返回NULL。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.3切分窗口1.静态切分和动态切分对于“静态切分”窗口来说,当窗口第一次被创建时,窗格就已经被切分好了,窗格的次序和数目不能再被改变,但用户可以移动切分条来调整窗格的大小。每个窗格通常是不同的视图类。课后答案网对于“动态切分”窗口来说,它允许用户在任何时候对窗口进行切分,用户既可以通过选择菜单项来对窗口进行切分,也可以通过拖动滚动条中的切分块对窗口进www.hackshp.cn行切分。动态切分窗口中的窗格通常使用的是同一个视图类。当切分窗口被创建时,左上窗格通常被初始化成一个特殊的视图。当视图沿着某个方向被切分时,另一个新添加的视图对象被动态创建;当视图沿着两个方向被切分时,新添加的三个视图对象则被动态创建。当用户取消切分时,所有新添加的视图对象被删除,但最先的视图仍被保留,直到切分窗口本身消失为止。无论是静态切分还是动态切分,在创建时都要指定切分窗口中行和列的窗格最大数目。对于静态切分,窗格在初始时就按用户指定的最大数目划分好了;而对于动态切分窗口,当窗口构造时,第一个窗格就被自动创建。动态切分窗口允许的最大窗格数目是2x2,而静态切分允许的最大窗格数目为16x16。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.3切分窗口2.切分窗口的CSplitterWnd类操作在MFC中,CSplitterWnd类封装了窗口切分过程中所需的功能函数,其中成员函数Create和CreateStatic分别用来创建“动态切分”和“静态切分”的文档窗口,函数原型如下:课后答案网BOOLCreate(CWnd*pParentWnd,intnMaxRows,intnMaxCols,SIZEsizeMin,CCreateContext*pContext,DWORDwww.hackshp.cndwStyle=WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL|SPLS_DYNAMIC_SPLIT,UINTnID=AFX_IDW_PANE_FIRST);BOOLCreateStatic(CWnd*pParentWnd,intnRows,intnCols,DWORDdwStyle=WS_CHILD|WS_VISIBLE,UINTnID=AFX_IDW_PANE_FIRST);其中,参数pParentWnd表示切分窗口的父框架窗口。nMaxRows表示窗口动态切分的最大行数(不能超过2)。nMaxCols表示窗口动态切分的最大列数(不能超过2)。nRows表示窗口静态切分的行数(不能超过16)。nCols表示窗口静态切分的列数(不能超过16)。sizeMin表示动态切分时允许的窗格最小尺寸。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.3切分窗口2.切分窗口的CSplitterWnd类操作CSplitterWnd类成员函数CreateView用来为静态窗格指定一个视图类,并创建视图窗口,其函数原型如下:BOOLCreateView(introw,intcol,CRuntimeClass*pViewClass,SIZE课后答案网sizeInit,CCreateContext*pContext);其中,row和col用来指定具体的静态窗格,pViewClass用来指定与静态窗格相关联的视图类,sizeInit表示视图窗口初始大小,pContext用来指定一个“创建上下文”指针。“创建上下文”结构www.hackshp.cnCCreateContext包含当前文档视图框架结构。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.3切分窗口3.静态切分窗口实现利用CSplitterWnd成员函数,用户可以在文档应用程序的文档窗口中添加动态或静态切分功能。[例Ex_SplitSDI]将单文档应用程序中的文档窗口静态分成3x2个窗格(1)用MFCAppWizard课后答案网创建一个默认的单文档应用程序Ex_SplitSDI。(2)打开框架窗口类MainFrm.h头文件,为CMainFrame类添加一个保护型的切分窗口的数据成员,如下面的定义:protected:www.hackshp.cn//controlbarembeddedmembersCStatusBarm_wndStatusBar;CToolBarm_wndToolBar;CSplitterWndm_wndSplitter;(3)用MFCClassWizard创建一个新的视图类CDemoView(基类为CView)用于与静态切分的窗格相关联。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SplitSDI](4)用MFCClassWizard为CMainFrame类添加OnCreateClient(当主框架窗口客户区创建的时候自动调用该函数)函数重载,并添加下列代码: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),paneSize,pContext);//为相应的窗格指定视图类m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CDemoView),www.hackshp.cnpaneSize,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(CDemoView),paneSize,pContext);m_wndSplitter.CreateView(2,1,RUNTIME_CLASS(CDemoView),paneSize,pContext);returnTRUE;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_SplitSDI](5)在MainFrm.cpp源文件的开始处,添加视图类CDemoView的包含文件:#include"DemoView.h"(6)编译并运行,结果如图6.19所示。课后答案网第0,0窗格第0,1窗格www.hackshp.cn第1,1窗格第1,0窗格切分条第2,0窗格第2,1窗格 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.3切分窗口4.动态切分窗口实现[例Ex_DySplit]通过添加切分窗口组件来创建动态切分(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_DySplit。(2)选择“工程”→“添加工程”→“ComponentsandControls”,弹出如图6.20所示的课后答案网对话框。www.hackshp.cn图6.20单文档应用程序的动态切分 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_DySplit](3)双击“VisualC++Components”,出现VisualC++支持的组件,选中SplitterBar,结果如图6.21所示。课后答案网www.hackshp.cn图6.21VisualC++支持的组件 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_DySplit](4)单击[Insert]按钮,出现一个消息对话框,询问是否要插入SplitterBar组件,单击[确定]按钮,弹出如图6.22所示的对话框。从中可选择切分类型:Horizontal(水平切分)、Vertical(垂直切分)和Both(水平垂直切分)。(5)选中Both选项,单击[OK]按钮,回到图6.21对话框,单击[结束]按钮,动态切分就被添加到单文档应用程序的主框架窗口类课后答案网CMainFrame中。(6)编译运行,结果如图6.23所示。www.hackshp.cn垂直切分块水平切分块图6.22SplitterBar组件选项对话框图6.23Ex_DySplit运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.4一档多视1.一档多视模式MFC对于“一档多视”提供下列3个模式:(1)在各自MDI文档窗口中包含同一个视图类的多个视图对象。用户有时需要应用程序能为同一个文档打开另一个文档窗口,以便能同时使用两个文档窗口来查看文档的不同部分内容。用课后答案网MFCAppWizard创建的多文档应用程序支持这种模式,当用户选择“窗口”菜单的“新建窗口”命令时,系统就会为第一个文档窗口创建一个副本。www.hackshp.cn(2)在同一个文档窗口中包含同一个视图类的多个视图对象。这种模式实际上是使用“切分窗口”机制使SDI应用程序具有多视的特征。(3)在单独一个文档窗口中包含不同视图类的多个视图对象。在该模式下,多个视图共享同一个文档窗口。它有点象“切分窗口”,但由于视图可由不同的视图类构造,所以同一个文档可以有不同的显示方法。例如,同一个文档可同时有文字显示方式及图形显示方式的视图。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.4一档多视2.示例下面的示例是在一个多文档应用程序Ex_Rect中为同一个文档数据提供两种不同的显示和编辑方式,如图6.24所示。在左边的窗格中,用户可以调整小方块在右边窗格的坐标位置。而若在右边窗格中任意单击鼠标,相应的小方块会移动到当前鼠标位置处,且左边窗格的编辑框内容也随之发生改变。课后答案网www.hackshp.cn图6.24Ex_Rect运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6.5.4一档多视[例Ex_Rect]一档多视示例1)创建表单应用程序,设计表单(1)用MFCAppWizard创建一个多文档应用程序Ex_Rect。在第6步中将视图的基类选择为CFormView。(2)打开表单模板资源课后答案网IDD_EX_RECT_FORM,调整表单模板大小,并依次添加如表6.15所示的控件。www.hackshp.cn表6.15在表单中添加的控件 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Rect]1)创建表单应用程序,设计表单(3)打开MFCClassWizard的MemberVariables标签,在Classname中选择CEx_RectView,选中所需的控件ID号,双击鼠标或单击AddVariables按钮。依次为表6.16中的控件添加成员变量。课后答案网表6.16添加的控件变量www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Rect]2)添加CEx_RectDoc和CEx_RectView类代码在CEx_RectDoc类中添加一个公有型的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的消息映射,使它们的映射函数名都设为www.hackshp.cnOnChangeEdit,并添加下列代码:voidCEx_RectView::OnChangeEdit(){UpdateData(TRUE);CEx_RectDoc*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);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Rect]2)添加CEx_RectDoc和CEx_RectView类代码(4)用MFCClassWizard为CEx_RectView添加OnUpdate的消息函数,并添加下列代码:voidCEx_RectView::OnUpdate(CView*pSender,LPARAMlHint,CObject*pHint){课后答案网if(lHint==1){CPoint*pPoint=(CPoint*)pHint;www.hackshp.cnm_CoorX=pPoint->x;m_CoorY=pPoint->y;UpdateData(FALSE);//在控件中显示CEx_RectDoc*pDoc=(CEx_RectDoc*)GetDocument();pDoc->m_ptRect=*pPoint;//保存在文档类中的m_ptRect}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Rect]2)添加CEx_RectDoc和CEx_RectView类代码(5)在CEx_RectView::OnInitialUpdate中添加一些初始化代码:voidCEx_RectView::OnInitialUpdate(){CFormView::OnInitialUpdate();课后答案网ResizeParentToFit();CEx_RectDoc*pDoc=(CEx_RectDoc*)GetDocument();m_CoorX=pDoc->m_ptRect.x;m_CoorY=pDoc->m_ptRect.y;www.hackshp.cnm_SpinX.SetRange(0,1024);m_SpinY.SetRange(0,768);UpdateData(FALSE);}(6)这时编译并运行程序,程序会出现一个运行错误。造成这个错误的原因是因为旋转按钮控件在设置范围时,会自动对其伙伴窗口(编辑框控件)进行更新,而此时编辑框控件还没有完全创建好,因此需要进行一些处理。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Rect]3)处理旋转按钮控件的运行错误(1)为CEx_RectView添加一个BOOL型的成员变量m_bEditOK。(2)在CEx_RectView构造函数中将m_bEditOK的初值设为FALSE。(3)在CEx_RectView::OnInitialUpdate函数的最后将m_bEditOK置为TRUE,如下面的代码:课后答案网voidCEx_RectView::OnInitialUpdate(){…UpdateData(FALSE);www.hackshp.cnm_bEditOK=TRUE;}(4)在CEx_RectView::OnChangeEdit函数的最前面添加下列语句:voidCEx_RectView::OnChangeEdit(){if(!m_bEditOK)return;…} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Rect]4)新增CDrawView类,添加框架窗口切分功能(1)用MFCClassWizard为添加一个新的CView的派生类CDrawView。(2)用MFCClassWizard为CChildFrame类添加OnCreateClient函数的重载,并添加下列代码:BOOLCChildFrame::OnCreateClient(LPCREATESTRUCTlpcs,CCreateContext*pContext){课后答案网CRectrect;GetWindowRect(&rect);BOOLbRes=m_wndSplitter.CreateStatic(this,1,2);www.hackshp.cn//创建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_wndSplitter.SetColumnInfo(1,rect.Width()/2,10);m_wndSplitter.RecalcLayout();//重新布局returnbRes;//CMDIChildWnd::OnCreateClient(lpcs,pContext);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Rect]4)新增CDrawView类,添加框架窗口切分功能(3)在ChildFrm.cpp的前面添加下列语句:#include"ChildFrm.h"#include"Ex_RectView.h"#include"DrawView.h"课后答案网(4)打开ChildFrm.h文件,为CChildFrame类添加下列成员变量:public:CSplitterWndwww.hackshp.cnm_wndSplitter; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Rect]4)新增CDrawView类,添加框架窗口切分功能(5)此时编译,程序会有一些错误。这些错误的出现是基于这样的一些事实:在用标准C/C++设计程序时,有一个原则即两个代码文件不能相互包含,而且多次包含还会造成重复定义的错误。(6)打开Ex_RectView.h课后答案网文件,在classCEx_RectView:publicCFormView语句前面添加下列代码:classCEx_RectDoc;//声明CEx_RectDoc类需要再次使用classCEx_RectView:publicCFormViewwww.hackshp.cn{…} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Rect]5)添加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);www.hackshp.cn//绘制矩形,以后还会详细讨论}(3)用MFCClassWizard为CDrawView类添加OnInitialUpdate的消息函数,并添加下列代码:voidCDrawView::OnInitialUpdate(){CView::OnInitialUpdate();CEx_RectDoc*pDoc=(CEx_RectDoc*)m_pDocument;m_ptDraw=pDoc->m_ptRect;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Rect]5)添加CDrawView类代码(4)在DrawView.cpp文件的前面添加CEx_RectDoc类的包含语句:#include"Ex_Rect.h"#include"DrawView.h"#include"Ex_RectDoc.h"课后答案网(5)用MFCClassWizard为CDrawView类添加OnUpdate的消息函数,并添加下列代码:voidCDrawView::OnUpdate(CView*pSender,LPARAMlHint,CObject*pHint)www.hackshp.cn{if(lHint==2){CPoint*pPoint=(CPoint*)pHint;m_ptDraw=*pPoint;Invalidate();}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Rect]5)添加CDrawView类代码(6)用MFCClassWizard为CDrawView类添加WM_LBUTTONDOWN的消息映射,并添加下列代码:voidCDrawView::OnLButtonDown(UINTnFlags,CPointpoint){课后答案网m_ptDraw=point;GetDocument()->UpdateAllViews(NULL,1,(CObject*)&m_ptDraw);Invalidate();www.hackshp.cn//强迫调用CDrawView::OnDrawCView::OnLButtonDown(nFlags,point);}(7)编译运行并测试,结果如前面图6.24所示。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!第7章图形、文本和位图7.1概述VisualC++的CDC(DeviceContext,设备环境)类是MFC中最重要的类之一,它封装了绘图所需要的所有函数,是用户编写图形和文字处理程序必不可少的。当然,绘制图形和文字时还必须指定相应的设备环境。设备环境是由Windows保存的一个数据结构,该结构包含应用程序向设备输出时所需要的信息。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.1设备环境类(1)CPaintDC比较特殊,它的构造函数和析构函数都是针对OnPaint进行的,但用户一旦获得相关的CDC指针,就可以将它当成任何设备环境(包括屏幕、打印机)指针来使用。CPaintDC类的构造函数会自动调用BeginPaint,而它的析构函数则会自动调用EndPaint课后答案网。(2)CClientDC只能在窗口的客户区(不包括边框、标题栏、菜单栏以及状态栏)中进行绘图,点(0,0)通常指的是客户区的左上角。而CWindowDC允许在窗口的任意位置中进行绘图,点www.hackshp.cn(0,0)指整个窗口的左上角。CWindowDC和CClientDC构造函数分别调用GetWindowDC和GetDC,但它们的析构函数都是调用ReleaseDC函数。(3)CMetaFileDC封装了在一个Windows图元文件中绘图的方法。图元文件是一系列与设备无关的图片的集合,由于它对图象的保存比像素更精确,因而往往在要求较高的场合下使用,例如AutoCAD的图像保存等。目前的Windows已使用增强格式(enhanced-format)的32位图元文件来进行操作。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.2坐标映射在讨论坐标映射之前,先来看看下列语句:pDC->Rectangle(CRect(0,0,200,200));它是在某设备环境中绘制出一个高为200个像素,宽也为200个像素的方块。由于默认的映射模式是MM_TEXT,其逻辑坐标(在各种映射模式下的坐标)和设备坐标(显示设备或打印设备坐标系下的坐标)相等。因此这个方块在1024x768的显示器上看起来要比在课后答案网640x480的显示器上显得小一些,而且若将它打印在600dpi精度的激光打印机上,这个方块就会显得更小了。如表7.1所示。www.hackshp.cn表7.1映射模式 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.2坐标映射[例Ex_Draw]通过设置窗口和视口大小来改变显示的比例(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_Draw。(2)在CEx_DrawView::OnDraw函数中添加下列代码:voidCEx_DrawView::OnDraw(CDC*pDC){CEx_DrawDoc*pDoc课后答案网=GetDocument();ASSERT_VALID(pDoc);CRectrectClient;GetClientRect(rectClient);www.hackshp.cn//获得当前窗口的客户区大小pDC->SetMapMode(MM_ANISOTROPIC);//设置MM_ANISOTROPIC映射模式pDC->SetWindowExt(1000,1000);//设置窗口范围pDC->SetViewportExt(rectClient.right,-rectClient.bottom);//设置视口范围pDC->SetViewportOrg(rectClient.right/2,rectClient.bottom/2);//设置视口原点pDC->Ellipse(CRect(-500,-500,500,500));} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Draw](3)编译运行,结果如图7.1所示。课后答案网www.hackshp.cn图7.1改变显示比例 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.3CPoint、CSize和CRect在图形绘制操作中,常常需要使用MFC中的CPoint、CSize和CRect等简单数据类由于CPoint(点)、CSize(大小)和CRect(矩形)是对Windows的POINT、SIZE和RECT结构的封装,因此它们可以直接使用各自结构的数据成员,如下所示:typedefstructtagPOINTtypedefstructtagSIZE{{LONGx;//点的课后答案网x坐标intcx;//水平大小LONGy;//点的y坐标intcy;//垂直大小}POINT;}SIZE;www.hackshp.cntypedefstructtagRECT{LONGleft;//矩形左上角点的x坐标LONGtop;//矩形左上角点的y坐标LONGright;//矩形右下角点的x坐标LONGbottom;//矩形右下角点的y坐标}RECT; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.3CPoint、CSize和CRect1.CPoint、CSize和CRect类的构造函数CPoint类带参数的常用构造函数原型如下:CPoint(intinitX,intinitY);CPoint(POINTinitPt);其中,initX和initY分别用来指定CPoint的成员x和y的值。initPt用来指定一个POINT结构或CPoint对象来初始化课后答案网CPoint的成员。CSize类带参数的常用构造函数原型如下:CSize(intinitCX,intinitCY);CSize(SIZEwww.hackshp.cninitSize);其中,initCX和initCY用来分别设置CSize的cx和cy成员。initSize用来指定一个SIZE结构或CSize对象来初始化CSize的成员。CRect类带参数的常用构造函数原型如下:CRect(intl,intt,intr,intb);CRect(constRECT&srcRect);CRect(LPCRECTlpSrcRect);CRect(POINTpoint,SIZEsize);CRect(POINTtopLeft,POINTbottomRight); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.3CPoint、CSize和CRect2.CRect类的常用操作由于一个CRect类对象包含用于定义矩形的左上角和右下角点的成员变量,因此在传递LPRECT、LPCRECT或RECT结构作为参数的任何地方,都可以使用CRect对象来代替。CRect类的操作函数有很多,这里只介绍矩形的扩大、缩小以及两个矩形的“并”和“交”操作,更多的常用操作如表课后答案网7.2所示。表7.2CRect类常用的成员函数www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.3CPoint、CSize和CRect2.CRect类的常用操作成员函数InflateRect和DeflateRect用来扩大和缩小一个矩形。由于它们的操作是相互的,也就是说,若指定InflateRect函数的参数为负值,那么操作的结果是缩小矩形,因此下面只给出InflateRect函数的原型:voidInflateRect(int课后答案网x,inty);voidInflateRect(SIZEsize);voidInflateRect(LPCRECTlpRect);voidInflateRect(intwww.hackshp.cnl,intt,intr,intb);其中,x用来指定扩大CRect左、右边的数值。y用来指定扩大CRect上、下边的数值。size中的cx成员指定扩大左、右边的数值,cy指定扩大上、下边的数值。lpRect的各个成员用来指定扩大每一边的数值。l、t、r和b分别用来指定扩大CRect左、上、右和下边的数值。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.3CPoint、CSize和CRect2.CRect类的常用操作成员函数IntersectRect和UnionRect分别用来将两个矩形进行相交和合并,当结果为空时返回FALSE,否则返回TRUE。它们的原型如下:BOOLIntersectRect(LPCRECTlpRect1,LPCRECTlpRect2);BOOLUnionRect(LPCRECT课后答案网lpRect1,LPCRECTlpRect2);其中,lpRect1和lpRect2用来指定操作的两个矩形。例如:CRectrectOne(125,0,150,200);CRectrectTwo(0,75,www.hackshp.cn350,95);CRectrectInter;rectInter.IntersectRect(rectOne,rectTwo);//结果为(125,75,150,95)ASSERT(rectInter==CRect(125,75,150,95));rectInter.UnionRect(rectOne,rectTwo);//结果为(0,0,350,200)ASSERT(rectInter==CRect(0,0,350,200)); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.4颜色和颜色对话框在MFC中,CDC使用的是RGB颜色空间,即选用红(R)、绿(G)、蓝(B)三种基色分量,通过对这三种基色不同比例的混合,可以得到不同的彩色效果。并且,MFC使用COLORREF数据类型来表示一个32位的RGB颜色,它也可以用下列的十六进制表示:0x00bbggrr此形式的rr、gg、bb分别表示红、绿、蓝三个颜色分量的16进制值,最大为0xff。在具体操作RGB颜色时,还可使用下列的宏操作:课后答案网GetBValue获得32位RGB颜色值中的蓝色分量GetGValue获得32位RGB颜色值中的绿色分量GetRValuewww.hackshp.cn获得32位RGB颜色值中的红色分量RGB将指定的R、G、B分量值转换成一个32位的RGB颜色值。MFC的CColorDialog类为应用程序提供了颜色选择通用对话框,如图7.2所示。图7.2颜色对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.4颜色和颜色对话框CColorDialog类具有下列的构造函数:CColorDialog(COLORREFclrInit=0,DWORDdwFlags=0,CWnd*pParentWnd=NULL);其中,clrInit用来指定选择的默认颜色值,若此值没指定,则为RGB(0,0,0)(黑色)。pParentWnd用来指定对话框的父窗口指针。dwFlags用来表示定制对话框外观和功能的系列标志参数。它可以是下列值之一或”|”组合:CC_ANYCOLOR在基本颜色单元中列出所有可得到的颜色CC_FULLOPEN课后答案网显示所有的颜色对话框界面。若此标志没有被设定,则用户单击“规定自定义颜色”按钮才能显示出定制颜色的界面CC_PREVENTFULLOPEN禁用“规定自定义颜色”按钮CC_SHOWHELPwww.hackshp.cn在对话框中显示“帮助”按钮CC_SOLIDCOLOR在基本颜色单元中只列出所得到的纯色当对话框“OK”退出(即DoModal返回IDOK)时,可调用下列成员获得相应的颜色。COLORREFGetColor()const;//返回用户选择的颜色。voidSetCurrentColor(COLORREFclr);//强制使用clr作为当前选择的颜色staticCOLORREF*GetSavedCustomColors();//返回用户自己定义颜色 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.5图形设备接口Windows为设备环境提供了各种各样的绘图工具,例如用于画线的“画笔”、填充区域的“画刷”以及用于绘制文本的“字体”。MFC封装了这些工具,并提供相应的类来作为应用程序的图形设备接口GDI,这些类有一个共同的抽象基类表7.3MFC的GDI类CGdiObject,具体如表课后答案网7.3所示。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.5图形设备接口1.使用GDI对象在选择GDI对象进行绘图时,往往遵循着下列的步骤:(1)在堆栈中定义一个GDI对象(如CPen、CBrush对象),然后用相应的函数(如CreatePen、CreateSolidBrush)创建此GDI对象。但要注意:有些GDI派生类的构造函数允许用户提供足够的信息,从而一步即可完成对象的创建任务,这些类有CPen、CBrush。(2)将构造的GDI对象选入当前设备环境中,但不要忘记将原来的GDI对象保存起来。(3)绘图结束后,恢复当前设备环境中原来的课后答案网GDI对象。(4)由于GDI对象是在堆栈中创建中,当程序结束后,会自动删除程序创建的GDI对象。具体操作可像下面的代码过程:voidCMyView::OnDraw(CDC*pDC){www.hackshp.cnCPenpenBlack;//定义一个画笔变量penBlack.CreatePen(PS_SOLID,2,RGB(0,0,0));//创建画笔//将此画笔选入当前设备环境并保存原来的画笔CPen*pOldPen=pDC->SelectObject(&penBlack);//用此画笔绘图pDC->MoveTo(...);pDC->LineTo(...);//…其他绘图函数pDC->SelectObject(pOldPen);//恢复设备环境中原来的画笔} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.1.5图形设备接口2.库存的GDI对象除了自定义的GDI对象外,Windows还包含了一些预定义的库存GDI对象。由于它们是Windows系统的一部分,因此用户用不着删除它们。CDC的成员函数SelectStockObject可以把一个库存对象选入当前设备环境中,并返回原先被选中的对象指针,同时使原先被选中的对象从设备环境中分离出来。如下面的代码:课后答案网voidCEx_SDIView::OnDraw(CDC*pDC){CPennewPen(PS_SOLID,2,RGB(0,0,0)))www.hackshp.cnpDC->SelectObject(&newPen);pDC->MoveTo(...);pDC->LineTo(...);//…其他绘图函数pDC->SelectStockObject(BLACK_PEN);//newPen被分离出来} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.库存的GDI对象函数SelectStockObject可选用的库存GDI对象类型可以是下列值之一:BLACK_BRUSH黑色画刷DKGRAY_BRUSH深灰色画刷GRAY_BRUSH灰色画刷HOLLOW_BRUSH课后答案网中空画刷LTGRAY_BRUSH浅灰色画刷NULL_BRUSH空画刷WHITE_BRUSHwww.hackshp.cn白色画刷BLACK_PEN黑色画笔NULL_PEN空画笔WHITE_PEN白色画笔DEVICE_DEFAULT_FONT设备默认字体SYSTEM_FONT系统字体 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2简单图形绘制图形的绘制通常需要先创建画笔和画刷,然后调用相应的绘图函数。7.2.1画笔画笔是Windows应用程序中用来绘制各种直线和曲线的一种图形工具,它可分为修饰画笔和几何画笔两种类型。在这两种类型中,几何画笔的定义最复杂,它不但有修饰画笔的属性,而且还跟画刷的样式、阴影线类型有关,通常用在对绘图课后答案网有较高要求的场合。而修饰画笔只有简单的几种属性,通常用在简单的直线和曲线等场合。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.1画笔课后答案网表7.4修饰画笔的风格www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.1画笔创建一个修饰画笔,可以使用CPen类的CreatePen函数,其原型如下:BOOLCreatePen(intnPenStyle,intnWidth,COLORREFcrColor);其中,参数nPenStyle、nWidth、crColor分别用来指定画笔的风格、宽度和颜色。此外,还有一个CreatePenIndirect函数也是用来创建画笔对象,它的作用与CreatePen函数是完全一样的,只是画笔的三个属性不是直接出现在函数参数课后答案网中,而是通过一个LOGPEN结构间接地给出。BOOLCreatePenIndirect(LPLOGPENlpLogPen);此函数用由LOGPENwww.hackshp.cn结构指针指定的相关参数创建画笔,LOGPEN结构如下:typedefstructtagLOGPEN{/*lgpn*/UINTlopnStyle;//画笔风格,同上POINTlopnWidth;//POINT结构的y不起作用,而用x表示画笔宽度COLORREFlopnColor;//画笔颜色}LOGPEN; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.2画刷画刷的属性通常包括填充色、填充图案和填充样式三种。画刷的填充色和画笔颜色一样,都是使用COLORREF颜色类型,画刷的填充图案通常是用户定义的8×8位图,而填充样式往往是CDC内部定义的一些特性,它们都是以HS_为前缀的标识,如图7.3所示:课后答案网www.hackshp.cnHS_BDIAGONALHS_CROSSHS_DIAGCROSSHS_FDIAGONALHS_HORIZONTALHS_VERTICAL图7.3画刷的填充样式 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.2画刷CBrush类根据画刷属性提供了相应的创建函数,例如创建填充色画刷和填充样式画刷的函数为CreateSolidBrush和CreateHatchBrush,它们的原型如下:BOOLCreateSolidBrush(COLORREFcrColor);//创建填充色画刷BOOLCreateHatchBrush(intnIndex,COLORREFcrColor);//创建填充样式画刷其中,nIndex用来指定画刷的内部填充样式,而crColor表示画刷的填充色。与画笔相类似,也有一个课后答案网LOGBRUSH逻辑结构用于画刷属性的定义,并通过CBrush的成员函数CreateBrushIndirect来创建,其原型如下:BOOLCreateBrushIndirect(constLOGBRUSH*www.hackshp.cnlpLogBrush);其中,LOGBRUSH逻辑结构如下定义:typedefstructtagLOGBRUSH{//lbUINTlbStyle;//风格COLORREFlbColor;//填充色LONGlbHatch;//填充样式}LOGBRUSH; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.3图形绘制1.画点、线(1)画点是最基本的绘图操作之一,它是通过调用CDC::SetPixel或CDC::SetPixelV函数来实现的。这两个函数都是用来在指定的坐标上设置指定的颜色,只不过SetPixelV函数不需要返回实际像素点的RGB值;正是因为这一点,函数SetPixelV要比SetPixel课后答案网快得多。COLORREFSetPixel(intx,inty,COLORREFcrColor);COLORREFSetPixel(POINTpoint,COLORREFcrColor);BOOLSetPixelV(intwww.hackshp.cnx,inty,COLORREFcrColor);BOOLSetPixelV(POINTpoint,COLORREFcrColor);实际显示像素的颜色未必等同于crColor所指定的颜色值,因为有时受设备限制,不能显示crColor所指定的颜色值,而只能取其近似值。与上述函数相对应的GetPixel函数是用来获取指定点的颜色。COLORREFGetPixel(intx,inty)const;COLORREFGetPixel(POINTpoint)const; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.画点、线(2)画线也是特别常用的绘图操作之一。CDC的LineTo和MoveTo函数就是用来实现画线功能的两个函数,通过这两个函数的配合使用,可完成任何直线和折线的绘制操作。这个当前位置还可用函数CDC::GetCurrentPosition来获得,其原型如下:CPointGetCurrentPosition()const;课后答案网LineTo函数正是经当前位置所在点为直线起始点,另指定直线终点,画出一段直线的。其原型如下:BOOLLineTo(intwww.hackshp.cnx,inty);BOOLLineTo(POINTpoint); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.3图形绘制2.折线除了LineTo函数可用来画线之外,CDC中还提供了一系列用于画各种折线的函数。它们主要是Polyline、PolyPolyline和PolylineTo。这三个函数中,Polyline和PolyPolyline既不使用当前位置,也不更新当前位置;而PolylineTo总是把当前位置作为起始点,并且在折线画完之后,还把折线终点所在位置设为新的当前位课后答案网置。BOOLPolyline(LPPOINTlpPoints,intnCount);BOOLPolylineTo(constPOINT*www.hackshp.cnlpPoints,intnCount);这两个函数用来画一系列连续的折线。参数lpPoints是POINT或CPoint的顶点数组;nCount表示数组中顶点的个数,它至少为2。BOOLPolyPolyline(constPOINT*lpPoints,constDWORD*lpPolyPoints,intnCount);此函数可用来绘制多条折线。其中lpPoints同前定义,lpPolyPoints表示各条折线所需的顶点数,nCount表示折线的数目。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.3图形绘制3.矩形和圆角矩形CDC提供的Rectangle和RoundRect函数分别用于矩形和圆角矩形的绘制,它们的原型如下:BOOLRectangle(intx1,inty1,intx2,inty2);BOOLRectangle(LPCRECTlpRect);BOOLRoundRect(int课后答案网x1,inty1,intx2,inty2,intx3,inty3);BOOLRoundRect(LPCRECTlpRect,POINTpoint);参数lpRect的成员left,top,right,bottom分别表示x1,y1,x2,y2,point的成员x,y分别表示x3,y3;而www.hackshp.cnx1,y1表示矩形的左上角坐标,x2,y2表示矩形的右上角坐标,x3,y3表示绘制圆角的椭圆大小,如图7.4所示。图7.5多边形填充模式图7.4圆角矩形 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.3图形绘制4.设置多边形填充模式多边形填充模式决定了图形填充时寻找填充区域的方法,有两种选择:ALTERNATE和WINDING。ALTERNATE模式是寻找相邻的奇偶边作为填充区域,而WINDING是按顺时针或逆时针进行寻找;一般情况,这两种模式的填充效果是相同的,但对于像五角星这样的图形,填充的结果大不一样,例如下面的代码,其结果如图7.5所示。...课后答案网POINTpt[5]={{247,10},{230,90},{290,35},{210,30},{275,85}};CBrushbrush(HS_FDIAGONAL,RGB(255,0,0));CBrush*oldbrush=pDC->SelectObject(&brush);www.hackshp.cnpDC->SetPolyFillMode(ALTERNATE);pDC->Polygon(pt,5);for(inti=0;i<5;i++)pt[i].x+=80;pDC->SetPolyFillMode(WINDING);pDC->Polygon(pt,5);pDC->SelectObject(oldbrush);brush.DeleteObject();代码中,SetPolyFillMode是CDC类的一个成员函数,用来设置填充模式,它的参数可以是ALTERNATE和WINDING。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.3图形绘制5.多边形前面已经介绍过折线的画法,而多边形可以说就是由首尾相接的封闭折线所围成的图形。画多边形的函数Polygon原型如下:BOOLPolygon(LPPOINTlpPoints,intnCount);可以看出,Polygon函数的参数形式与课后答案网Polyline函数是相同的。但也稍有一点小差异。例如,要画一个三角形,使用Polyline函数,顶点数组中就得给出四个顶点(尽管始点和终点重复出现),而用Polygon函数则只需给出三个顶点。与PolyPolylinewww.hackshp.cn可画多条折线一样,使用PolyPolygon函数,一次可画出多个多边形,这两个函数的参数形式和含义也一样。BOOLPolyPolygon(LPPOINTlpPoints,LPINTlpPolyCounts,intnCount); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.3图形绘制6.圆弧和椭圆通过调用CDC的Arc函数可以画一条椭圆弧线或者整个椭圆。这个椭圆的大小是由其外接矩形(本身并不可见)所决定的。Arc函数的原型如下:BOOLArc(intx1,inty1,intx2,inty2,intx3,inty3,intx4,inty4);BOOLArc(LPCRECTlpRect,POINTptStart,POINTptEnd);这里,x1,y1,x2,y2或lpRect课后答案网用来指定外接矩形的位置和大小,而椭圆中心与点(x3,y3)或ptStart所构成的射线与椭圆的交点就成为椭圆弧线的起始点,椭圆中心与点(x4,y4)或www.hackshp.cnptEnd所构成的射线与椭圆的交点就成为椭圆弧线的终点。椭圆上弧线始点到终点的部分是要绘制的椭圆弧,如图7.6所示。终点坐标(x1,y1)弧线外接矩形中心起点坐标(x2,y2)图7.6弧线 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.3图形绘制7.弦形和扇形CDC类成员函数Chord和Pie是用来绘制弦形(图7.7)和扇形(图7.8),它们具有和Arc一样的参数。BOOLChord(intx1,inty1,intx2,inty2,intx3,inty3,intx4,inty4);BOOLChord(LPCRECT课后答案网lpRect,POINTptStart,POINTptEnd);BOOLPie(intx1,inty1,intx2,inty2,intx3,inty3,intx4,inty4);BOOLPie(LPCRECTlpRect,POINTptStart,POINTptEnd);www.hackshp.cn终点坐标终点坐标(x1,y1)弦形(x1,y1)扇形外接矩形外接矩形中心中心起点坐标起点坐标(x2,y2)(x2,y2)图7.7弦形图7.8扇形 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.3图形绘制8.Bézier曲线Bézier曲线是最常见的非规则曲线之一,它的形状不仅便于控制,而且更主要的是它具有几何不变性(即它的形状不随坐标的变换而改变),因此在许多场合往往采用这种曲线。Bézier曲线属于三次曲线,只需给定四个点(第一和第四个点是端点,另两个是控制点)课后答案网,就可唯一确定其形状,如图7.9所示。P2www.hackshp.cnP4P1P3图7.9Bézier曲线 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.Bézier曲线函数PolyBezier是用来画出一条或多条Bézier曲线的,其函数原型如下:BOOLPolyBezier(constPOINT*lpPoints,intnCount);其中lpPoints是曲线端点和控制点所组成的数组,nCount表示lpPoints数组中的点数。如果lpPoints用于画多条Bézier曲线,那么除了第一条曲线要用到四个点之外,后面的曲线只需用三个点,因为后面的曲线总是把前一条曲线的终点作为课后答案网自己的起始端点。函数PolyBezier不使用也不更新当前位置。如果需要使用当前位置,那么就应该使用PolyBezierTowww.hackshp.cn函数。BOOLPolyBezierTo(constPOINT*lpPoints,intnCount); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.3图形绘制9.绘图示例下面来看一个简单的示例。它是用来表示一个班级某门课程的成绩分布,用一个直方图来反映<60、60~69、70~79、80~89以及>90五个分数段的人数,它需要绘制五个矩形,相邻矩形的填充样式还要有所区别,并且还需要显示各分数段的人数。其结果如图7.10课后答案网所示。www.hackshp.cn图7.10Ex_Draw运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.2.3图形绘制[例Ex_Draw]课程的成绩分布直方图用MFCAppWizard创建一个默认的单文档应用程序Ex_Draw。为CEx_DrawView类添加一个成员函数DrawScore,用来根据成绩来绘制直方图,该函数的代码如下:voidCEx_DrawView::DrawScore(CDC*pDC,float*fScore,intnNum)//fScore是成绩数组指针,课后答案网nNum是学生人数{intnScoreNum[]={0,0,0,0,0};//各成绩段的人数的初始值//下面是用来统计各分数段的人数www.hackshp.cnfor(inti=0;i90分数段nScoreNum[nSeg-5]++;//各分数段计数}intnSegNum=sizeof(nScoreNum)/sizeof(int);//计算有多少个分数段 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!//求分数段上最大的人数intnNumMax=nScoreNum[0];for(i=1;iSelectObject(&brush1);//将brush1选入设备环境CPen*oldPen=pDC->SelectObject(&pen);//将pen选入设备环境CRectrcSeg(rc);rcSeg.right=rcSeg.left+nSegWidth;//使每段的矩形宽度等于nSegWidthCStringstrSeg[]={"<60","60-70","70-80","80-90",">=90"};CRectrcStr;for(i=0;iSelectObject(&brush2);elsepDC->SelectObject(&brush1);rcSeg.top=rcSeg.bottom-nScoreNum[i]*nSegHeight-2;//计算每段矩形的高度pDC->Rectangle(rcSeg);if(nScoreNum[i]>0){课后答案网CStringstr;str.Format("%d人",nScoreNum[i]);www.hackshp.cnpDC->DrawText(str,rcSeg,DT_CENTER|DT_VCENTER|DT_SINGLELINE);}rcStr=rcSeg;rcStr.top=rcStr.bottom+2;rcStr.bottom+=20;pDC->DrawText(strSeg[i],rcStr,DT_CENTER|DT_VCENTER|DT_SINGLELINE);//后面还会讲到rcSeg.OffsetRect(nSegWidth,0);//右移矩形}pDC->SelectObject(oldBrush);//恢复原来的画刷属性pDC->SelectObject(oldPen);//恢复原来的画笔属性} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Draw](3)在CEx_DrawView::OnDraw函数中添加下列代码:voidCEx_DrawView::OnDraw(CDC*pDC){CEx_DrawDoc*pDoc=GetDocument();ASSERT_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));}www.hackshp.cn(4)编译并运行,如前图7.10所示。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.3字体与文字处理字体是文字显示和打印的外观形式,它包括了文字的字样、风格和尺寸等多方面的属性。适当地选用不同的字体,可以大大地丰富文字的外在表现力。例如,把文字中某些重要的字句用较粗的字体显示,能够体现出突出、强调的意图。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.3.1字体和字体对话框1.字体的属性和创建字体的属性有很多,但其主要属性有字样、风格和尺寸三个。字样是字符书写和显示时表现出的特定模式,例如,对于汉字,通常有宋体、楷体、仿宋、黑体、隶书以及幼圆等多种字样。字体风格主要表现为字体的粗细和是否倾斜等特点。课后答案网字体尺寸是用来指定字符所占区域的大小,通常用字符高度来描述。字体尺寸可以取毫米或英寸作为单位,但为了直观起见,也常常采用一种称为“点”的单位,一点约折合为www.hackshp.cn1/72英寸。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.字体的属性和创建逻辑字体的具体属性可由LOGFONT结构来描述,这里仅列最常用到的结构成员。typedefstructtagLOGFONT{LONGlfHeight;//字体的逻辑高度LONGlfWidth;课后答案网//字符的平均逻辑宽度LONGlfEscapement;//倾角LONGlfOrientation;//书写方向LONGwww.hackshp.cnlfWeight;//字体的粗细程度BYTElfItalic;//斜体标志BYTElfUnderline;//下划线标志BYTElfStrikeOut;//删除线标志BYTElfCharSet;//字符集,汉字必须为GB2312_CHARSETTCHARlfFaceName[LF_FACESIZE];//字样名称//…}LOGFONT; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.字体的属性和创建根据定义的逻辑字体,用户就可以调用CFont类的CreateFontIndirect函数创建文本输出所需要的字体,如下面的代码:LOGFONTlf;//定义逻辑字体的结构变量memset(&lf,0,sizeof(LOGFONT));//将lf中的所有成员置0lf.lfHeight=-13;课后答案网lf.lfCharSet=GB2312_CHARSET;strcpy((LPSTR)&(lf.lfFaceName),"www.hackshp.cn黑体");//用逻辑字体结构创建字体CFontcf;cf.CreateFontIndirect(&lf);//在设备环境中使用字体CFont*oldfont=pDC->SelectObject(&cf);pDC->TextOut(100,100,"Hello");pDC->SelectObject(oldfont);//恢复设备环境原来的属性cf.DeleteObject();//删除字体对象 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.3.1字体和字体对话框2.使用字体对话框CFontDialog类提供了字体及其文本颜色选择的通用对话框,如图7.11所示。它的构造函数如下:CFontDialog(LPLOGFONTlplfInitial=NULL,DWORDdwFlags=CF_EFFECTS|CF_SCREENFONTS,CDC*pdcPrinter=NULL,CWnd*pParentWnd=NULL);其中,参数lplfInitial是一个课后答案网LOGFONT结构指针,用来设置对话框最初的字体特性。dwFlags指定选择字体的标志。pdcPrinter用来表示打印设备环境指针。pParentWnd表示对话框的父窗口指针。www.hackshp.cn图7.11字体对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.使用字体对话框当字体对话框DoModal返回IDOK后,可使用下列的成员函数:voidGetCurrentFont(LPLOGFONTlplf);//返回用户选择的LOGFONT字体CStringGetFaceName()const;//返回用户选择的字体名称CStringGetStyleName()const;//返回用户选择的字体样式名称intGetSize()const;//返回用户选择的字体大小COLORREFGetColor()const;课后答案网//返回用户选择的文本颜色intGetWeight()const;//返回用户选择的字体粗细程度BOOLIsStrikeOut()const;//判断是否有删除线BOOLIsUnderline()const;www.hackshp.cn//判断是否有下划线BOOLIsBold()const;//判断是否是粗体BOOLIsItalic()const;//判断是否是斜体。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.使用字体对话框通过字体对话框可以创建一个字体,如下面的代码:LOGFONTlf;CFontcf;memset(&lf,0,sizeof(LOGFONT));//将lf中的所有成员置0CFontDialogdlg(&lf);课后答案网if(dlg.DoModal()==IDOK){dlg.GetCurrentFont(&lf);www.hackshp.cnpDC->SetTextColor(dlg.GetColor());cf.CreateFontIndirect(&lf);...} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.3.2常用文本输出函数文本的最终输出不仅依赖于文本的字体,而且还跟文本的颜色、对齐方式等有很大关系。CDC类提供了四个输出文本的成员函数:TextOut、ExtTextOut、TabbedTextOut和DrawText。对于这四个函数,用户应根据具体情况来选用。例如,如果想要绘制的文本是一个多列的列表形式,那么采用课后答案网TabbedTextOut函数,启用制表位,可以使绘制出来的文本效果更佳;如果要在一个矩形区域内绘制多行文本,那么采用DrawText函数,会更富于效率;如果文本和图形结合紧密,字符间隔不等,并要求有背景颜色或矩形裁剪特性,那么www.hackshp.cnExtTextOut函数将是最好的选择。如果没有什么特殊要求,那使用TextOut函数就显得简练了。下面介绍TextOut、TabbedTextOut和DrawText函数。virtualBOOLTextOut(intx,inty,LPCTSTRlpszString,intnCount);BOOLTextOut(intx,inty,constCString&str); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.3.2常用文本输出函数virtualCSizeTabbedTextOut(intx,inty,LPCTSTRlpszString,intnCount,intnTabPositions,LPINTlpnTabStopPositions,intnTabOrigin);CSizeTabbedTextOut(intx,inty,constCString&str,intnTabPositions,LPINT课后答案网lpnTabStopPositions,intnTabOrigin);virtualintDrawText(LPCTSTRlpszString,intnCount,LPRECTlpRect,UINTnFormat);www.hackshp.cnintDrawText(constCString&str,LPRECTlpRect,UINTnFormat); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.3.2常用文本输出函数DrawText函数是当前字体在指定矩形中对文本进行格式化绘制。参数中,lpRect用来指定文本绘制时的参考矩形,它本身并不显示;nFormat表示文本的格式,它可以是下列的常用值之一或“|”组合:DT_BOTTOM下对齐文本,该值还必须与DT_SINGLELINE组合DT_CENTER水平居中DT_END_ELLIPSIS课后答案网使用省略号取代文本末尾的字符DT_PATH_ELLIPSIS使用省略号取代文本中间的字符DT_EXPANDTABS使用制表位,缺省的制表长度为8个字符DT_LEFTwww.hackshp.cn左对齐DT_MODIFYSTRING将文本调整为能显示的字串DT_NOCLIP不裁剪DT_NOPREFIX不支持“&”字符转义DT_RIGHT右对齐DT_SINGLELINE指定文本的基准线为参考点DT_TABSTOP设置停止位。nFormat的高位字节是每个制表位的数目DT_TOP上对齐DT_VCENTER垂直居中DT_WORDBREAK自动换行 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!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->Rectangle(rc);pDC->DrawText("单行文本居中",rc,DT_CENTER|DT_VCENTER|DT_SINGLELINE);www.hackshp.cnrc.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文本t示例",1,&nTab,rc.left);//使用自定义的停止位(Tab)pDC->TabbedTextOut(rc.left,rc.top+40,"绘制tTabt文本t示例",0,NULL,0);//使用默认的停止位} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_DrawText](3)编译运行,结果如图7.12所示。课后答案网www.hackshp.cn图7.12Ex_DrawText运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.3.3文本格式化属性原型如下:virtualCOLORREFSetTextColor(COLORREFcrColor);COLORREFGetTextColor()const;virtualCOLORREFSetBkColor(COLORREFcrColor);COLORREFGetBkColor()const;intSetBkMode(intnBkMode课后答案网);intGetBkMode()const;其中,nBkModewww.hackshp.cn用来指定文本背景模式,它可以是OPAQUE或TRANSPARENT(透明)。文本对齐方式的设置和获取是由CDC函数SetTextAlign和GetTextAlign决定的。它们的原型如下:UINTSetTextAlign(UINTnFlags);UINTGetTextAlign()const; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.3.3文本格式化属性上述两个函数中所用到的文本对齐标志如表7.5所示。这些标志可以分为三组:TA_LEFT、TA_CENTER和TA_RIGHT确定水平方向的对齐方式,TA_BASELINE、TA_BOTTOM和TA_TOP确定上下方向的对齐方式,TA_NOUPDATECP和TA_UPDATECP确定当前位置的更新标志。这三组标志中,组与组之间的标志可使用“课后答案网|”操作符。表7.5文本对齐标志www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.3.4计算字符的几何尺寸在打印和显示某段文本时,有必要了解字符的高度计算及字符的测量方式,才能更好地控制文本输出效果。在CDC类中,GetTextMetrics(LPTEXTMETRIClpMetrics)是用来获得指定映射模式下相关设备环境的字符几何尺寸及其它属性的,其TEXTMETRIC结构描述如下(这里仅列出最常用的结构成员):typedefstructtagTEXTMETRIC课后答案网{//tminttmHeight;//字符的高度(ascent+descent)inttmAscent;www.hackshp.cn//高于基准线部分的值inttmDescent;//低于基准线部分的值inttmInternalLeading;//字符内标高inttmExternalLeading;//字符外标高inttmAveCharWidth;//字体中字符平均宽度inttmMaxCharWidth;//字符的最大宽度//…}TEXTMETRIC; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.3.4计算字符的几何尺寸原型如下:CSizeGetTextExtent(LPCTSTRlpszString,intnCount)const;CSizeGetTextExtent(constCString&str)const;CSizeGetTabbedTextExtent(LPCTSTRlpszString,intnCount,intnTabPositions,LPINT课后答案网lpnTabStopPositions)const;CSizeGetTabbedTextExtent(constCString&str,intnTabPositions,LPINTlpnTabStopPositions)const;其中,参数lpszStringwww.hackshp.cn和str表示要计算的字符串,nCount表示字符串的字节长度,nTabPositions表示lpnTabStopPositions数组的大小,lpnTabStopPositions表示多个递增的制表位(逻辑坐标)的数组。函数返回当前设备环境下的一行字符串的宽度(CSize的cx)和高度(CSize的cy)。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.3.5文档内容显示及其字体改变[例Ex_Text]显示文档内容并改变显示的字体(1)用MFCAppWizard创建一个单文档应用程序Ex_Text,在创建的第6步将视图的基类选择为CScrollView。由于视图客户区往往显示不了文档的全部内容,因此需要视图支持滚动操作。(2)为CEx_TextDoc类添加CStringArray类型的成员变量m_strContents,用来将读取的文档内容保存。课后答案网(3)在CEx_TextDoc::Serialize函数中添加读取文档内容的代码:voidCEx_TextDoc::Serialize(CArchive&ar){www.hackshp.cnif(ar.IsStoring()){…}else{CStringstr;m_strContents.RemoveAll();while(ar.ReadString(str))m_strContents.Add(str);}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例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;www.hackshp.cnstrcpy(m_lfText.lfFaceName,"宋体");} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Text](6)用MFCClassWizard为CEx_TextView类添加WM_LBUTTONDBLCLK(双击鼠标)的消息映射函数,并增加下列代码:voidCEx_TextView::OnLButtonDblClk(UINTnFlags,CPointpoint){CFontDialogdlg(&m_lfText);课后答案网if(dlg.DoModal()==IDOK){dlg.GetCurrentFont(&m_lfText);www.hackshp.cnInvalidate();}CScrollView::OnLButtonDblClk(nFlags,point);}这样,当双击鼠标左键后,就会弹出字体对话框,从中可改变字体的属性,单击[确定]按钮后,执行CEx_TextView::OnDraw中的代码。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例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);//计算每行高度www.hackshp.cnTEXTMETRICtm;pDC->GetTextMetrics(&tm);intlineHeight=tm.tmHeight+tm.tmExternalLeading;inty=0;inttab=tm.tmAveCharWidth*4;//为一个TAB设置4个字符//输出并计算行的最大长度intlineMaxWidth=0;CStringstr;CSizelineSize(0,0);for(inti=0;im_strContents.GetSize();i++) 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!{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(oldFont);www.hackshp.cn//多算一行,以滚动窗口能全部显示文档内容intnLines=pDoc->m_strContents.GetSize()+1;CSizesizeTotal;sizeTotal.cx=lineMaxWidth;sizeTotal.cy=lineHeight*nLines;SetScrollSizes(MM_TEXT,sizeTotal);//设置滚动逻辑窗口的大小} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Text](8)编译运行并测试,打开任意一个文本文件,结果如图7.13所示。课后答案网www.hackshp.cn图7.13Ex_Text运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4位图、图标与光标7.4.1使用图形编辑器在VisualC++6.0中,图形编辑器可以创建和编辑任何位图格式的图像资源,除工具栏外,它还用于位图、图标和光标。它的功能很多,如提供一套完整的绘图工具来绘制256色的图形,进行位图的移动和复制以及含有若干个编辑工具等。课后答案网由于图形编辑器的使用和Windows中的“绘图”工具相似,因此它的具体绘制操作在这里不再重复。这里仅讨论下列一些常用操作。如创建新的图标和光标、选用www.hackshp.cn或定制显示设备和设置光标“热点”等。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.1使用图形编辑器1.创建一个新的图标或光标在VisualC++6.0中,创建一个应用程序后,当按快捷键Ctrl+R就可打开“插入资源”对话框,从中选择Cursor(光标)或Icon(图标)资源类型,单击[新建]按钮后,系统为程序添加一个新的图标或光标资源,同时在开发环境右侧出现图形编辑器。课后答案网图7.14是添加一个新的图标资源后出现的图形编辑器。www.hackshp.cn新设备按钮图7.14添加图标后的图形编辑器 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.创建一个新的图标或光标在创建新图标或光标的时候,图形编辑器首先创建的是一个适合于VGA环境中的图像,开始的时候它以屏幕色(透明方式)来填充。对于创建的新光标,其“热点”被初始化为左上角的点,坐标为(0,0)。默认情况下,图形编辑器所支持的显示设备如表7.6所示。课后答案网表7.6创建图标或光标时可选用的显示设备www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.1使用图形编辑器2.选用和定制显示设备在图形编辑器工作窗口的控制条上,有一个NewDeviceImage按钮,单击此按钮后,系统弹出相应的新设备列表,用户可以从中选取需要的显示设备,如图7.15所示。除了对话框列表框显示的设备外,还可以单击课后答案网[定制]按钮,在弹出的对话框中定制新的显示设备,如图7.16所示,在这里可指定新设备的大小和颜色。www.hackshp.cn图7.15图像设备选择对话框图图7.16显示设备的定制 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.1使用图形编辑器3.设置光标热点Windows系统借助光标“热点”来确定光标实际的位置。在图形编辑器的控制条上或光标属性对话框中都可以看到当前的光标“热点”位置。图7.17是添加一个新的光标资源后出现的图形编辑器。课后答案网设置热点按钮www.hackshp.cn控制条图7.17添加光标后的图形编辑器 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.2位图1.CBitmap类函数原型如下:BOOLLoadBitmap(LPCTSTRlpszResourceName);BOOLLoadBitmap(UINTnIDResource);BOOLCreateBitmap(intnWidth课后答案网,intnHeight,UINTnPlanes,UINTnBitcount,constvoid*lpBits);BOOLCreateBitmapIndirect(LPBITMAPlpBitmap);该函数直接用BITMAP结构来创建一个位图对象。BOOLCreateCompatibleBitmap(CDC*www.hackshp.cnpDC,intnWidth,intnHeight);该函数为某设备环境创建一个指定的宽度(nWidth)和高度(nHeight)的位图对象。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.2位图2.GDI位图的显示由于位图不能直接显示在实际设备中,因此对于GDI位图的显示则必须遵循下列步骤:(1)调用CBitmap类的CreateBitmap、CreateCompatibleBitmap以及CreateBitmapIndirect函数创建一个适当的位图对象。课后答案网(2)调用CDC::CreateCompatibleDC函数创建一个内存设备环境,以便位图在内存中保存下来,并与指定设备(窗口设备)环境相兼容;(3)调用CDC::SelectObjectwww.hackshp.cn函数将位图对象选入内存设备环境中;(4)调用CDC::BitBlt或CDC::StretchBlt函数将位图复制到实际设备环境中。(5)使用之后,恢复原来的内存设备环境。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.GDI位图的显示[例Ex_BMP]在视图中显示位图(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_BMP。(2)按快捷键Ctrl+R,弹出“插入资源”对话框,选择Bitmap资源类型。(3)单击[导入]按钮,出现“导入资源”对话框,将文件类型选择为“所有文件(*.*)”,课后答案网从外部文件中选定一个位图文件,然后单击[Import]按钮,该位图就被调入应用程序中。保留默认的位图资源标识www.hackshp.cnIDB_BITMAP1。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例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.GetObject(sizeof(BITMAP),&bm);CDCdcMem;www.hackshp.cn//定义并创建一个内存设备环境dcMem.CreateCompatibleDC(pDC);CBitmap*pOldbmp=dcMem.SelectObject(&m_bmp);//将位图选入内存设备环境中pDC->BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcMem,0,0,SRCCOPY);//将位图复制到实际的设备环境中dcMem.SelectObject(pOldbmp);//恢复原来的内存设备环境} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_BMP](5)编译运行,结果如图7.18所示。课后答案网www.hackshp.cn图7.18Ex_BMP运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.3图标1.图标的调入和清除在MFC中,使用CWinApp::LoadIcon函数可将一个图标资源调入并返回一个图标句柄。函数原型如下:HICONLoadIcon(LPCTSTRlpszResourceName)const;HICONLoadIcon(UINT课后答案网nIDResource)const;其中,lpszResourceName和nIDResource分别表示图标资源的字符串名和标识。函数返回的是一个图标句柄。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.图标的调入和清除如果不想创建新的图标资源,也可使用系统中预定义好的标准图标,这时需调用CWinApp::LoadStandardIcon函数,其原型如下:HICONLoadStandardIcon(LPCTSTRlpszIconName)const;其中,lpszIconName可以是下列值之一:IDI_APPLICATION课后答案网默认的应用程序图标IDI_HAND手形图标(用于严重警告)IDI_QUESTION问号图标(用于提示消息)IDI_EXCLAMATIONwww.hackshp.cn警告消息图标(惊叹号)IDI_ASTERISK消息图标全局函数DestroyIcon可以用来删除一个图标,并释放为图标分配的内存,其原型如下:BOOLDestroyIcon(HICONhIcon);其中,hIcon用来指定要删除的图标句柄。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.3图标2.图标的绘制函数CDC::DrawIcon用来将一个图标绘制在指定的位置处,其原型如下:BOOLDrawIcon(intx,inty,HICONhIcon);BOOLDrawIcon(POINTpoint,HICONhIcon);其中,(x,y)和point用来指定图标绘制的位置,而课后答案网hIcon用来指定要绘制的图标句柄。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.3图标3.应用程序图标的改变在用MFCAppWizard创建的应用程序中,图标资源IDR_MAINFRAME用来表示应用程序窗口的图标,通过图形编辑器可将其内容直接修改。实际上,程序中还可使用GetClassLong和SetClassLong函数重新指定应用程序窗口的图标,函数原型如下:课后答案网DWORDSetClassLong(HWNDhWnd,intnIndex,LONGdwNewLong);DWORDGetClassLong(HWNDhWnd,intnIndex);其中,hWnd用来指定窗口类句柄,www.hackshp.cndwNewLong用来指定新的32位值。nIndex用来指定与WNDCLASSEX结构相关的索引,它可以是下列值之一:GCL_HBRBACKGROUND窗口类的背景画刷句柄GCL_HCURSOR窗口类的的光标句柄GCL_HICON窗口类的的图标句柄GCL_MENUNAME窗口类的的菜单资源名称 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.3图标4.示例(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_Icon。(2)新添四个图标资源,通过图像设备选择对话框(参见图7.15),选择Small(16×16)作为图标的设备类型。图标资源ID号分别为默认的IDI_ICON1~IDI_ICON4。(3)用图形编辑器绘制图标,结果如图课后答案网7.19所示。www.hackshp.cn图7.19创建的四个图标 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.示例(4)为CMainFrame类添加一个成员函数ChangeIcon,用来切换应用程序的图标。该函数的代码如下:voidCMainFrame::ChangeIcon(UINTnIconID){HICONhIconNew=AfxGetApp()->LoadIcon(nIconID);课后答案网HICONhIconOld=(HICON)GetClassLong(m_hWnd,GCL_HICON);if(hIconNew!=hIconOld){www.hackshp.cnDestroyIcon(hIconOld);SetClassLong(m_hWnd,GCL_HICON,(long)hIconNew);RedrawWindow();//重绘窗口}} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.示例(5)在CMainFrame::OnCreate函数的最后添加计时器设置代码:intCMainFrame::OnCreate(LPCREATESTRUCTlpCreateStruct){if(CFrameWnd::OnCreate(lpCreateStruct)==-1)return-1;...课后答案网SetTimer(1,500,NULL);return0;}www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.示例(6)用MFCClassWizard为CMainFrame类添加WM_TIMER的消息映射函数,并增加下列代码:voidCMainFrame::OnTimer(UINTnIDEvent){staticinticons[]={IDI_ICON1,课后答案网IDI_ICON2,IDI_ICON3,IDI_ICON4};staticintindex=0;ChangeIcon(icons[index]);index++;www.hackshp.cnif(index>3)index=0;CFrameWnd::OnTimer(nIDEvent);}OnTimer函数的参数nIDEvent用来表示发送WM_TIMER消息的计时器的标识值。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.示例(7)用MFCClassWizard为CMainFrame类添加WM_DESTROY的消息映射函数,并增加下列代码:voidCMainFrame::OnDestroy(){CFrameWnd::OnDestroy();课后答案网KillTimer(1);}代码中,KillTimerwww.hackshp.cn函数是CWnd类成员函数,用来停止WM_TIMER消息的传送,其函数参数值用指定要停用的计时器标识值。(8)编译并运行。可以看到任务栏上的按钮以及应用程序的标题栏上四个图标循环显示的动态效果,显示速度为每秒两帧。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.4光标光标在Windows程序中起着非常重要的作用,它不仅能反映鼠标的运动位置,而且还可以表示程序执行的状态,引导用户的操作,使程序更加生动。例如沙漏光标表示“正在执行,请等待”,IE中手形光标表示“可以跳转”,另外还有一些有趣的动画光标。光标又称为“鼠标指针”。1.使用系统光标课后答案网Windows预定义了一些经常使用的标准光标,这些光标均可以使用函数CWinApp::LoadStandardCursor加载到程序中,其函数原型如下:HCURSORLoadStandardCursor(LPCTSTRwww.hackshp.cnlpszCursorName)const;其中,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_UPARROW); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.4光标2.使用光标资源用编辑器创建或从外部调入的光标资源,可通过函数CWinApp::LoadCursor进行加载,其原型如下:HCURSORLoadCursor(LPCTSTRlpszResourceName)const;HCURSORLoadCursor(UINT课后答案网nIDResource)const;其中,lpszResourceName和nIDResource分别用来指定光标资源的名称或ID号。例如,当光标资源为IDC_CURSOR1时,则可使用下列代码:HCURSORhCursor;www.hackshp.cnhCursor=AfxGetApp()->LoadCursor(IDC_CURSOR1); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.4光标3.更改程序中的光标更改应用程序中的光标除了可以使用GetClassLong和SetClassLong函数外,最简单的方法是用MFCClassWizard映射WM_SETCURSOR消息,该消息是当光标移动到一个窗口内并且还没有捕捉到鼠标时产生的。CWnd为此消息的映射函数定义这样的原型:课后答案网afx_msgBOOLOnSetCursor(CWnd*pWnd,UINTnHitTest,UINTmessage);其中,pWnd表示拥有光标的窗口指针,nHitTest用来表示光标所处的位置,例如当为HTCLIENTwww.hackshp.cn时表示光标在窗口的客户区中,而为HTCAPTION时表示光标在窗口的标题栏处,为HTMENU时表示光标在窗口的菜单栏区域等等。message用来表示鼠标消息。在OnSetCursor函数调用SetCursor来设置相应的光标,并将OnSetCursor函数返回TRUE,就可改变当前的光标了。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!7.4.4光标4.示例本示例用来根据当前鼠标所在的位置来确定单文档应用程序光标的类型,当处在标题栏时为一个动画光标,当处在客户区时为一个自定义光标。[例Ex_Cursor]改变应用程序光标(1)用MFCAppWizard课后答案网创建一个默认的单文档应用程序Ex_Cursor。(2)按快捷键Ctrl+R,打开“插入资源”对话框,选择“Cursor”类型后,单击[新建]按钮。www.hackshp.cn(3)在图形编辑器工作窗口的控制条上,单击“NewDeviceImage”按钮,从弹出的“NewDeviceImage”对话框中,单击[定制]按钮。(4)在弹出的“定制图像”对话框中,保留默认的大小和颜色数,单击[确定]按钮。回到“NewDeviceImage”对话框。(5)选择“32x32,16colors”设备类型,单击[确定]按钮。(6)在图形编辑器的“Device”组合框中,选择“Monochrome[32x32]”,打开系统Image菜单,选择“DeleteDeviceImage”命令,删除“Monochrome[32x32]”设备类型如果不这样做加载后的光标不会采用“32x3216colors”设备类型 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Cursor](7)保留默认的ID号IDC_CURSOR1,用图形编辑器绘制光标图形,指定光标热点位置为(15,15),结果如图7.20所示。(8)为CMainFrame类添加一个成员变量m_hCursor,变量类型为光标句柄HCURSOR。课后答案网www.hackshp.cn图7.20创建的光标 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Cursor](9)用MFCClassWizard为CMainFrame类添加WM_SETCURSOR的消息映射函数,并增加下列代码:BOOLCMainFrame::OnSetCursor(CWnd*pWnd,UINTnHitTest,UINTmessage){BOOLbRes=CFrameWnd::OnSetCursor(pWnd,nHitTest,message);if(nHitTest==HTCAPTION)课后答案网{m_hCursor=LoadCursorFromFile("c:\windows\cursors\globe.ani");SetCursor(m_hCursor);www.hackshp.cnbRes=TRUE;}elseif(nHitTest==HTCLIENT){m_hCursor=AfxGetApp()->LoadCursor(IDC_CURSOR1);SetCursor(m_hCursor);bRes=TRUE;}returnbRes;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Cursor](10)编译运行并测试。当鼠标移动到标题栏时,光标变成了globe.ani的动画光标,而当移动到客户区时,光标变成了IDC_CURSOR1定义的形状。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!第8章数据库编程VisualC++6.0为用户提供了ODBC、DAO及OLEDB三种数据库方式。这三种方式中最简单也最常用的是ODBC,因此本章先来重点介绍MFC的ODBC编程方法和技巧,然后介绍基于OLEDB的ADO(ActiveXDataObjects,ActiveX数据对象)技术,最后介绍一些用于数据库的课后答案网ActiveX控件。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1MFCODBC数据库概述ODBC是一种使用SQL的程序设计接口,使用ODBC能使用户编写数据库应用程序变得容易简单,避免了与数据源相连接的复杂性。在VisualC++中,MFC的ODBC数据库类CDatabase(数据库类)、CRecordSet(记录集类)和CRecordView(记录视图类课后答案网)可为用户管理数据库提供了切实可行的解决方案。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1.1数据库基本概念1.数据库和DBMS数据库是指以一定的组织形式存放在计算机存储介质上的相互关联的数据的集合。例如,把一个学校的教师、学生和课程等数据有序地组织起来,存储在计算机磁盘上,就构成了一个数据库。为了有效地管理数据库,常常需要一些数据库管理系统课后答案网(DBMS)为用户提供对数据库操作的各种命令、工具及方法,包括数据库的建立和记录的输入、修改、检索、显示、删除和统计等。流行的DBMS都提供了一个SQL接口。2.SQLwww.hackshp.cn作为用来在DBMS中访问和操作的语言,SQL(结构化查询语言)语句分为两类:一是DDL(DataDefinitionLanguage,数据定义语言)语句,它是用来创建表、索引等,另一是DML(DataManipulationLanguage,数据操作语言)语句,这些语句是用来读取数据、更新数据和执行其他类似操作的语句。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1.1数据库基本概念3.ODBC、DAO和OLEDBODBC提供了应用程序接口(API),使得任何一个数据库都可以通过ODBC驱动器与指定的DBMS相联。用户的程序就可以通过调用ODBC驱动管理器中相应的驱动程序达到管理数据库的目的。作为MicrosoftWindowsOpenStandardsArchitecture(WOSA,Windows开放式服务体系结构)的主要组成部分,ODBC一直沿用至今。DAO类似于用MicrosoftAccess课后答案网或MicrosoftVisualBasic编写的数据库应用程序,它使用Jet数据库引擎形成一系列的数据访问对象:数据库对象、表和查询对象、记录集对象等。它可以打开一个Access数据库文件(MDB文件),也可直接打开一个ODBC数据源以及使用Jet引擎打开一个www.hackshp.cnISAM(被索引的顺序访问方法)类型的数据源(dBASE、FoxPro、Paradox、Excel或文本文件)。OLEDB试图提供一种统一的数据访问接口,并能处理除了标准关系型数据库中的数据之外,还能处理包括邮件数据、Web上的文本或图形、目录服务(DirectoryServices)以及主机系统中的IMS和VSAM数据。OLEDB提供一个数据库编程COM(组件对象模型)接口,使得数据的使用者(应用程序)可以使用同样的方法访问各种数据,而不用考虑数据的具体存储地点、格式或类型。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1.1数据库基本概念4.ADOADO是目前在Windows环境中比较流行的客户端数据库编程技术。它是Microsoft为最新和最强大的数据访问范例OLEDB而设计的,是一个便于使用的应用程序层接口。ADO使用户应用程序能够通过“OLEDB提供者”访问和操作数据库服务器中的数据。由于它兼具有强大的数据处理功能课后答案网(处理各种不同类型的数据源、分布式的数据处理等等)和极其简单、易用的编程接口,因而得到了广泛的应用。www.hackshp.cnADO技术基于COM(ComponentObjectModel,组件对象模型),具有COM组件的许多优点,可以用来构造可复用应用框架,被多种语言支持,能够访问包括关系数据库、非关系数据库及所有的文件系统。另外,ADO还支持各种B/S与基于Web的应用程序,具有远程数据服务RDS(RemoteDataService)的特性,是远程数据存取的发展方向。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1.2MFCODBC向导过程用MFCAppWizard使用ODBC数据库的一般过程是:①用Access或其他数据库工具构造一个数据库;②在Windows中为刚才构造的数据库定义一个ODBC数据源;③在创建数据库处理的文档应用程序向导中选择数据源;④设计界面,并使控件与数据表字段关联。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1.2MFCODBC向导过程1.构造数据库数据库表与表之间的关系构成了一个数据库。作为示例,这里用MicrosoftAccess创建一个数据库Student.mdb,其中暂包含一个数据表score,用来描述学生课程成绩,如表8.1所示。在表中包括上、下两部分,上部分是数据表的记录内容,下部分是数据表的结构内容。课后答案网表8.1学生课程成绩表(score)及其表结构www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1.2MFCODBC向导过程2.创建ODBC数据源Windows中的ODBC组件是出现在系统的“控制面板”管理工具中,如图8.1所示。双击ODBC图标(在图8.1中已圈定),进入ODBC数据源管理器。在这里,用户可以设置ODBC数据源的一些信息。其中,“用户DSN”页面是用来定义用户自己在课后答案网本地计算机使用的数据源名(DSN),如图8.2所示。www.hackshp.cn图8.1Windows2000的管理工具图8.2ODBC数据源管理器 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.创建ODBC数据源创建用户DSN的过程如下。(1)单击[添加]按钮,弹出有一驱动程序列表的“创建新数据源”对话框,在该对话框中选择要添加用户数据源的驱动程序,这里选择“MicrosoftAccessDriver”,如图8.3所示。课后答案网(2)单击[完成]按钮,进入指定驱动程序的安装对话框,单击[选择]按钮将前面创建的数据库调入,然后在数据源名输入“DatabaseExampleForVC++”,结果如www.hackshp.cn图8.4所示。图8.3“创建新数据源”对话框图8.4ODBCAccess安装对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.创建ODBC数据源(3)单击[确定]按钮,刚才创建的用户数据源被添加在“ODBC数据源管理器”的“用户数据源”列表中。如图8.5所示。课后答案网www.hackshp.cn图8.5用户数据源列表图8.6向导的第二步对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1.2MFCODBC向导过程3.在MFCAppWizard中选择数据源用MFCAppWizard可以容易地创建一个支持数据库的文档应用程序,如下面的过程。用MFCAppWizard创建一个单文档应用程序Ex_ODBC。在向导的第2步对话框中加入数据库的支持,如图课后答案网8.6所示。在该对话框中用户可以选择对数据库支持程序,其中各选项的含义如表8.2所示。www.hackshp.cn表8.2MFC支持数据库的不同选项 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.在MFCAppWizard中选择数据源选中“数据库查看使用文件支持”项,单击[DataSource]按钮,弹出“DatabaseOptions”对话框,从中选择ODBC的数据源“DatabaseExampleForVC++”,如图8.7所示。课后答案网www.hackshp.cn图8.7“DatabaseOptions”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.在MFCAppWizard中选择数据源(4)保留其他默认选项,单击[OK]按钮,弹出如图8.8所示的“SelectDatabaseTables”对话框,从中选择要使用的表score。(5)单击[OK]按钮,又回到了向导的第2步对话框。(6)单击[完成]按钮。开发环境自动打开表单视图CEx_ODBCView的对话框资源模板IDD_EX_ODBC_FORM课后答案网以及相应的对话框编辑器。(7)编译并运行,结果如图8.9所示。www.hackshp.cn记录浏览按钮图8.8“SelectDatabaseTables”对话框图8.9Ex_ODBC运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1.2MFCODBC向导过程4.设计浏览记录界面在上面的Ex_ODBC中,MFC为用户自动创建了用于浏览数据表记录的工具按钮和相应的“记录”菜单项。若用户选择这些浏览记录命令,系统会自动调用相应的函数来移动数据表的当前位置。若在表单视图CEx_ODBCView课后答案网中添加控件并与表的字段相关联,就可以根据表的当前记录位置显示相应的数据。其步骤如下。(1)按照图8.10www.hackshp.cn所示的布局,为表单对话框资源模板添加表8.3所示的控件。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!课后答案网图8.10控件的设计www.hackshp.cn表8.3表单对话框控件及属性 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.设计浏览记录界面(2)按快捷键Ctrl+W,弹出MFCClassWizard对话框,切换到MemberVariables页面,在Classname框中选择CEx_ODBCView,为上述控件添加相关联的数据成员。与以往添加控件变量不同的是,这里添加的控件变量都是由系统自动定义的,并与数据库表字段相关联的。例如,双击IDC_STUNO,在弹出的“AddMemberVariable”对话框中的成员变量下拉列表中选择要添加的成员变量名课后答案网m_pSet->m_studentno,选择后,控件变量的类型将自动设置,如图8.11所示。www.hackshp.cn图8.11为控件添加数据成员 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.设计浏览记录界面(3)按照上一步骤的方法,为表8.4所示的其他控件依次添加相关联的成员变量。需要说明的是,控件变量的范围和大小应与数据表中的字段一一对应。结果如图8.12所示。表8.4控件变量课后答案网www.hackshp.cn图8.12添加的控件变量图 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.设计浏览记录界面(4)编译运行并测试,结果如图8.13所示。课后答案网www.hackshp.cn8.13Ex_ODBC最后运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1.3ODBC数据表绑定更新其步骤如下:(1)按快捷键Ctrl+W,打开MFCClassWizard对话框,切换到“MemberVariables”页面。(2)在“Classname”的下拉列表中选择“CEx_ODBCSet”,此时MFCClassWizard对话框的[UpdateColumns]和[BindAll]课后答案网按钮被激活,如图8.14所示。www.hackshp.cn图8.14“MFCClassWizard”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1.3ODBC数据表绑定更新(3)单击[UpdateColumns]按钮,又弹出前面的“DatabaseOptions”对话框,选择ODBC数据源“DatabaseExampleForVC++”,如图8.15所示。(4)单击[OK]按钮,弹出如图8.16所示的“SelectDatabaseTables”对话框,从中选择要使用的表。课后答案网www.hackshp.cn图8.15“DatabaseOptions”对话框图8.16“SelectDatabaseTables”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.1.3ODBC数据表绑定更新(5)单击[OK]按钮,又回到MFCClassWizard界面,如图8.17所示。(6)单击[BindAll]按钮,MFCWizard将自动为字段落添加相关联的变量。课后答案网www.hackshp.cn图8.17更新后的“MemberVariables”页面 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2MFCODBC应用编程使用MFC所供的ODBC类:CDatabase(数据库类)、CRecordSet(记录集类)和CRecordView(可视记录集类)。CDatabase类用来提供对数据源的连接,通过它可以对数据源进行操作;CRecordView类用来控制并显示数据库记录,该视图是直接连到一个CRecordSet对象的表单视图。但在实际应用过程中,CRecordSet类是用户最关心的,因为它为用户提供了对表记录进行操作的许多功能,课后答案网如查询记录、添加记录、删除记录、修改记录等,并能直接为数据源中的表映射一个CRecordSet类对象,方便用户的操作。CRecordSetwww.hackshp.cn类对象提供了从数据源中提取出表的记录集,并提供了两种操作形式:动态行集(Dynasets)和快照集(Snapshots)。动态行集能与其他用户所做的更改保持同步,而快照集则是数据的一个静态视图。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2.1查询记录使用CRecordSet类的成员变量m_strFilter、m_strSort和成员函数Open可以对表进行记录的查询和排序。先来看一个示例,该示例在前面的Ex_ODBC的表单中添加一个编辑框和一个[查询]按钮,单击[查询]按钮,将按编辑框中的学号内容对数据表进行查询,并将查找到的记录显示在前面添加的控件中。具体过程如下:(1)打开Ex_ODBC应用程序的表单资源,按图课后答案网8.18所示的布局添加控件,其中添加的编辑框ID号设为IDC_EDIT_QUERY,“查询”按钮的ID号设为IDC_BUTTON_QUERY。www.hackshp.cn图8.18要添加的控件 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2.1查询记录(2)用MFCClassWizard为控件IDC_EDIT_QUERY添加关联变量m_strQuery。(3)在CEx_ODBCView类中添加按钮控件IDC_BUTTON_QUERY的BN_CLICKED消息映射,并在映射函数中添加下列代码:voidCEx_ODBCView::OnButtonQuery(){课后答案网UpdateData();m_strQuery.TrimLeft();if(m_strQuery.IsEmpty())www.hackshp.cn{MessageBox("要查询的学号不能为空!");return;}if(m_pSet->IsOpen())m_pSet->Close();//如果记录集打开,则先关闭m_pSet->m_strFilter.Format("studentno="%s"",m_strQuery);//studentno是score表的字段名,用来指定查询条件m_pSet->m_strSort="course"; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!//course是score表的字段名,用来按course字段从小到大排序m_pSet->Open();//打开记录集if(!m_pSet->IsEOF())//如果打开记录集有记录UpdateData(FALSE);//自动更新表单中控件显示的内容else课后答案网MessageBox("没有查到你要找的学号记录!");}代码中,m_strFilterwww.hackshp.cn和m_strSort是CRecordSet的成员变量,用来执行条件查询和结果排序。其中,m_strFilter称为“过滤字符串”,相当于SQL语句中WHERE后的条件串;而m_strSort称为“排序字符串”,相当于SQL语句中ORDERBY后的字符串。若字段的数据类型是文本,则需要在m_strFilter字符串中将单引号将查询的内容括起来,对于数字,则不需要用单引号。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2.1查询记录(4)编译运行并测试,结果如图8.19所示。课后答案网www.hackshp.cn图8.19查询记录 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2.2编辑记录CRecordSet类为用户提供了许多对表记录进行操作的成员函数用来添加记录、删除记录和修改记录等。1.增加记录增加记录是使用AddNew函数,但要求数据库必须是以“可增加”的方式打开的。下面的代码是在表的末尾增加新记录:课后答案网m_pSet->AddNew();//在表的末尾增加新记录m_pSet->SetFieldNull(&(m_pSet->m_studentno),FALSE);//设定m_studentnowww.hackshp.cn值不为空(NULL)m_pSet->m_studentno="21010503";......//输入新的字段值m_pSet->Update();//将新记录存入数据库m_pSet->Requery();//刷新记录集,这在快照集方式下是必须的 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2.2编辑记录1.删除记录可以直接使用CRecordSet::Delete函数来删除记录。需要说明的是,要使删除操作有效,还需要移动记录函数。例如下面的代码:CRecordsetStatusstatus;m_pSet->GetStatus(status);课后答案网//获取当前记录集状态m_pSet->Delete();//删除当前记录if(status.m_lCurrentRecord==0)//若当前记录索引号为0(0表示第www.hackshp.cn一条记录)则m_pSet->MoveNext();//下移一个记录elsem_pSet->MoveFirst();//移动到第一个记录处UpdateData(FALSE); 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2.2编辑记录3.修改记录函数CRecordSet::Edit可以用来修改记录,例如:m_pSet->Edit();//修改当前记录m_pSet->m_name="刘向东";//修改当前记录字段值......课后答案网m_pSet->Update();//将修改结果存入数据库m_pSet->Requery();4.撤消操作www.hackshp.cn如果用户在进行增加或者修改记录后,希望放弃当前操作,则在调用CRecordSet::Update()函数之前调用CRecordSet::Move(AFX_MOVE_REFRESH)来撤消操作,便可恢复在增加或修改操作之前的当前记录。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2.3字段操作CRecordSet类中的成员变量m_nFields(用于保存数据表的字段个数)和成员函数GetODBCFieldInfo及GetFieldValue可以简化多字段的访问操作。GetODBCFieldInfo函数用来数据表中的字段信息,其函数原型如下:voidGetODBCFieldInfo(shortnIndex,CODBCFieldInfo&fieldinfo);其中,nIndex用于指定字段索引号,课后答案网0表示第一个字段,1表示第二个字段,以此类推。fieldinfo是CODBCFieldInfo结构参数,用来表示字段信息。CODBCFieldInfo结构体原型如下:structCODBCFieldInfowww.hackshp.cn{CStringm_strName;//字段名SWORDm_nSQLType;//字段的SQL数据类型UDWORDm_nPrecision;//字段的文本大小或数据大小SWORDm_nScale;//字段的小数点位数SWORDm_nNullability;//字段接受空值(NULL)能力}; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2.3字段操作结构体裁中,SWORD和UDWORD分别表示shortint和unsignedlongint数据类型。GetFieldValue函数用来获取数据表当前记录中指定字段的值,其常用的函数原型如下:voidGetFieldValue(shortnIndex,CString&strValue);其中,nIndex用于指定字段索引号,课后答案网strValue用来返回字段的内容。除了上述字段操作外,CRecordSet类的成员函数GetRecordCount和GetStatus,还可分别用来获得表中的记录总数和当前记录的索引,其原型如下:longGetRecordCount()const;www.hackshp.cnvoidGetStatus(CRecordsetStatus&rStatus)const;其中,参数rStatus是指向下列的CRecordsetStatus结构的对象:structCRecordsetStatus{longm_lCurrentRecord;//当前记录的索引,0表示第一个记录,//1表示第二个记录,依次类推。但-1表示在第一个记录之前,-2表示不确定。BOOLm_bRecordCountFinal;//记录总数是否是最终结果}; 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2.3字段操作[例Ex_Field]字段的编程操作1)为数据库Student.mdb添加一个数据表course用MicrosoftAccess为数据库Student.mdb添加一个数据表course,如表8.5所示。表中上部分是数据表的记录内容,下部分是数据表的结构内容。课后答案网表8.5课程信息表(course)及其表结构www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Field]2)为文档应用程序添加ODBC的支持(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_Field,但在向导的第6步将CEx_FieldView的基类由默认的CView选择为CListView类。(6)将项目工作区窗口切换到FileView页面,展开HeaderFiles所有项,双击stdafx.h,打开该文件。课后答案网(3)在stdafx.h中添加ODBC数据库支持的头文件包含#include,如下面的代码:#ifndef_AFX_NO_AFXCMN_SUPPORTwww.hackshp.cn#include//MFCsupportforWindowsCommonControls#endif//_AFX_NO_AFXCMN_SUPPORT#include 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Field]3)创建数据表score的CRecordSet派生类(1)按快捷键Ctrl+W,打开MFCClassWizard对话框。单击[AddClass]按钮,从弹出的下拉菜单中选择“New”。(2)在弹出的“AddClass”对话框中指定CRecordSet的派生类CCourseSet,结果如图8.23所示。课后答案网www.hackshp.cn图8.23定义新的CRecordSet派生类 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3)创建数据表score的CRecordSet派生类(3)单击[OK]按钮,弹出“DatabaseOptions”对话框。从中选择ODBC的数据源“DatabaseExampleForVC++”,单击[OK]按钮,弹出“SelectDatabaseTables”对话框,从中选择要使用的表course。(4)单击[OK]按钮回到MFCClassWizard界面,单击[确定]按钮后,系统自动为用户生成CCourseSet类所需要的代码。课后答案网(5)在CEx_FieldView::PreCreateWindow函数中添加修改列表视图风格的代码:BOOLCEx_FieldView::PreCreateWindow(CREATESTRUCT&cs)www.hackshp.cn{cs.style&=~LVS_TYPEMASK;cs.style|=LVS_REPORT;//报表方式returnCListView::PreCreateWindow(cs);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3)创建数据表score的CRecordSet派生类(6)在CEx_FieldView::OnInitialUpdate函数中添加下列代码:voidCEx_FieldView::OnInitialUpdate(){CListView::OnInitialUpdate();CListCtrl&m_ListCtrl=GetListCtrl();课后答案网//获取内嵌在列表视图中的列表控件CCourseSetcSet;cSet.Open();//打开记录集CODBCFieldInfowww.hackshp.cnfield;//创建列表头for(UINTi=0;im_pMainWnd;//课后答案网获得主框架窗口的指针CStatusBar*pStatus=&pFrame->m_wndStatusBar;//www.hackshp.cn获得主框架窗口中的状态栏指针if(pStatus){CRecordsetStatusrStatus;pSet->GetStatus(rStatus);//获得当前记录信息str.Format("当前记录:%d总记录:%d",1+rStatus.m_lCurrentRecord,pSet->GetRecordCount());pStatus->SetPaneText(1,str);//更新第二个窗格的文本}}该函数先获得状态栏对象的指针,然后调用SetPaneText函数更新第二个窗格的文本。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4)在状态栏中显示当前记录号和记录总数(3)在CEx_ODBCView的OnInitialUpdate函数处添加下列代码:voidCEx_FieldView::OnInitialUpdate(){…CStringstr;课后答案网while(!cSet.IsEOF()){…www.hackshp.cn}::DispRecNum(&cSet);cSet.Close();//关闭记录集} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4)在状态栏中显示当前记录号和记录总数(4)在Ex_ODBCView.cpp文件的开始处增加下列语句:#include"Ex_FieldDoc.h"#include"Ex_FieldView.h"#include"CourseSet.h"#include"MainFrm.h"课后答案网(5)将MainFrm.h文件中的保护型变量m_wndStatusBar变成公共变量。(6)编译运行并测试,结果如图www.hackshp.cn8.25所示。显示的记录信息图8.25Ex_Field最后运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2.4多表处理数据库中表与表之间往往存在着一定的关系,例如要显示一个学生的课程成绩信息,信息包括学号、姓名、课程号、课程所属专业、课程名称、课程类别、开课学期、课时数、学分、成绩,则要涉及到前面的学生课程成绩表(score)、课程表以及学生基本信息表。下面的示例在一个对话框中用两个控件来进行学生课程成绩信息的相关操作,如图8.26所示,左边是树视图,用来显示学生成绩、专业和班级号三个层次信息,单击班级号,所有该班级的学生课程成绩信息将在右边的列表视图中显示出来。课后答案网www.hackshp.cn图8.26Ex_Student运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.2.4多表处理[例Ex_Student]多表处理1)为数据库Student.mdb添加一个数据表student用MicrosoftAccess为数据库Student.mdb添加一个数据表student,如表8.6所示。表中上部分是数据表的记录内容,下部分是数据表的结构内容。课后答案网表8.6学生基本信息表(student)及其表结构www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Student]2)创建并设计对话框应用程序(1)用MFCAppWizard创建一个默认的基于对话框应用程序Ex_Student。(2)在打开的对话框资源模板中,删除[取消]按钮和默认的静态文本控件。(3)调整对话框大小,将对话框的标题文本改为“处理多表”,将[确定]按钮的标题文本改为“退出”。课后答案网(4)参看图8.26的控件布局,向对话框中添加一个树控件,在其属性对话框中,选中“有按钮”、“有行www.hackshp.cn(Lines,线)”、“Linesatroot”和“总是显示选择”属性。(5)向对话框中添加一个列表控件,在其属性对话框中,将“查看”属性选为“Report”。(6)用MFCClassWizard在CEx_StudentDlg类中,添加树控件的控件变量为m_treeCtrl,添加列表控件的控件变量为m_listCtrl。3)添加对MFCODBC的支持及记录集在stdafx.h文件中添加ODBC数据库支持的头文件包含#include。用MFCClassWizard为数据表student、course和score分别创建CRecordSet派生类CStudentSet、CCourseSet和CScoreSet。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Student]4)完善左边树控件的代码(1)为CEx_StudentDlg类添加一个成员函数FindTreeItem,用来查找指定节点下是否有指定节点文本的子节点,该函数的代码如下:HTREEITEMCEx_StudentDlg::FindTreeItem(HTREEITEMhParent,CStringstr){HTREEITEMhNext;课后答案网CStringstrItem;hNext=m_treeCtrl.GetChildItem(hParent);while(hNext!=NULL)www.hackshp.cn{strItem=m_treeCtrl.GetItemText(hNext);if(strItem==str)returnhNext;elsehNext=m_treeCtrl.GetNextItem(hNext,TVGN_NEXT);}returnNULL;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4)完善左边树控件的代码(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;//定义一个文件信息结构变量SHGetFileInfo("C:\Windows",www.hackshp.cn0,&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);CStudentSetsSet;sSet.m_strSort="special";//按专业排序 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!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)//若没有重复的班级节点www.hackshp.cnhClass=m_treeCtrl.InsertItem(sSet.m_studentno.Left(6),0,1,hSpec);sSet.MoveNext();}sSet.Close();returnTRUE;//returnTRUEunlessyousetthefocustoacontrol} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4)完善左边树控件的代码(4)在Ex_StudentDlg.cpp文件的前面添加记录集类的包含文件,如下面的代码:#include"Ex_StudentDlg.h"#include"StudentSet.h"#include"ScoreSet.h"#include"CourseSet.h"课后答案网(5)编译运行,结果如图8.27所示。www.hackshp.cn图8.27Ex_Student第一次运行结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Student]5)完善右边列表控件的代码(1)在CEx_StudentDlg::OnInitDialog函数中添加下列代码,用来创建列表标题头:BOOLCEx_StudentDlg::OnInitDialog(){…sSet.Close();课后答案网//设置列表头CStringstrHeader[]={"学号","姓名","课程号","课程所属专业","课程名称www.hackshp.cn","课程类别","开课学期","课时数","学分","成绩"};intnLong[]={80,80,80,180,180,80,80,80,80,80};for(intnCol=0;nColitemNew.hItem;//获取当前选择的节点//如果当前的节点没有子节点,那说明该节点是班级号节点www.hackshp.cnif(m_treeCtrl.GetChildItem(hSelItem)==NULL){CStringstrSelItem,str;strSelItem=m_treeCtrl.GetItemText(hSelItem);str.Format("studentnoLIKE"%s%%"",strSelItem.Left(6));DispScoreAndCourseInfo(str);}*pResult=0;} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!6)完善两控件的关联代码代码中,调用DispScoreAndCourseInfo函数是的参数是用来设置数据表(记录集)打开的过滤条件。str是类似的这样内容“studentnoLIKE210101%”,它使得所有学号前面是210101的记录被打开。%是SQL使用的通配符,由于%也是VisaulC++格式前导符,因为在代码中需要两个%。(2)编译运行并测试。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.3ADO数据库编程ADO最主要的优点是易于使用、速度快、内存开销小,它使用最少的网络流量,并且在前端和数据源之间使用最少的层数,它是一个轻量、高性能的接口。ADO实际上就是由一组Automation对象构成的组件,因此可以像使用其它任何Automation对象一样使用ADO。ADO中最重要的对象有三个:Connection、Command和Recordset,它们分别表示“连接”对象、“命令”对象和“记录集”对象。课后答案网www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.3.1ADO编程的一般过程在MFC应用程序中使用ADO数据库的一般过程是:①添加对ADO的支持;②创建一个数据源连接;③对数据源中的数据库进行操作;④关闭数据源。课后答案网这里先来介绍添加对ADO的支持以及数据源连接和关闭。1.添加对ADO的支持ADO编程有三种方式:使用预处理指令www.hackshp.cn#import、使用MFC中的CIDispatchDriver和直接使用COM提供的API。这三种方式中,第一种最为简便,故这里采有用这种方法。下面以一个示例过程来说明在MFC应用程序中添加对ADO的支持。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.添加对ADO的支持[例Ex_ADO]添加对ADO的支持(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_ADO,但在向导的第6步将CEx_ADOView的基类由默认的CView选择为CListView类,以便更好地显示和操作数据表中的记录。(2)在CEx_ADOView::PreCreateWindow课后答案网函数添加下列代码,用来设置列表视图内嵌列表控件的风格:BOOLCEx_ADOView::PreCreateWindow(CREATESTRUCT&cs){www.hackshp.cncs.style|=LVS_REPORT;//报表风格returnCListView::PreCreateWindow(cs);} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_ADO](3)在stdafx.h文件中添加对ADO支持的代码:#endif//_AFX_NO_AFXCMN_SUPPORT#include//MFCsupportforWindowsCommonControls#import"C:ProgramFilesCommonFilesSystemADOmsado15.dll"no_namespacerename("EOF","adoEOF")课后答案网#include//{{AFX_INSERT_LOCATION}}代码中,预编译命令www.hackshp.cn#import是编译器将此命令中所指定的动态链接库文件引入到程序中,并从动态链接库文件中抽取出其中的对象和类的信息。icrsint.h文件包含了VisualC++扩展的一些预处理指令、宏等的定义,用于与数据库数据绑定。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_ADO](4)在CEx_ADOApp::InitInstance函数中添加下列代码,用来对ADO的COM环境进行初始化:BOOLCEx_ADOApp::InitInstance(){::CoInitialize(NULL);课后答案网AfxEnableControlContainer();…}(5)在Ex_ADOView.hwww.hackshp.cn文件中为CEx_ADOView定义三个ADO对象指针变量:public:_ConnectionPtrm_pConnection;_RecordsetPtrm_pRecordset;_CommandPtrm_pCommand;代码中,_ConnectionPtr、_RecordsetPtr和_CommandPtr分别是ADO对象Connection、Recordset和Command的智能指针类型。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.3.1ADO编程的一般过程2.连接数据源只有建立了与数据库服务器的连接后,才能进行其他有关数据库的访问和操作。ADO使用Connection对象来建立与数据库服务器的连接,它相当于MFC中的CDatabase类。和CDatabase类一样,调用Connection对象的Open即可建立与服务器的连接。HRESULTConnection::Open(_bstr_tConnectionString,_bstr_tUserID,_bstr_tPassword,longOptions)其中,ConnectionString为连接字串,课后答案网UserID是用户名,Password是登录密码,Options是选项,通常用于设置同步和异步等方式。_bstr_t是一个COM类,用于字符串BSTR(用于Automation的宽字符)操作。需要说明的是,正确设置www.hackshp.cnConnectionString是连接数据源的关键。不同的数据,其连接字串有所不同,见表8.7所示。表8.7Connection对象的连接字串格式 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.3.1ADO编程的一般过程3.关闭连接用MFCClassWizard为CEx_ADOView映射WM_DESTROY消息,并添加下列代码:voidCEx_ADOView::OnDestroy(){课后答案网CListView::OnDestroy();if(m_pConnection)m_pConnection->Close();//www.hackshp.cn关闭连接} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.3.1ADO编程的一般过程4.获取数据源信息Connection对象除了建立与数据库服务器的连接外,还可以通过OpenSchema来获取数据源的自有信息,如:数据表信息、表字段信息以及所支持的数据类型等。下面的代码用来获取student.mdb的数据表名和字段名,并将信息内容显示在列表视图中:voidCEx_ADOView::OnInitialUpdate(){课后答案网CListView::OnInitialUpdate();m_pConnection.CreateInstance(__uuidof(Connection));//初始化Connection指针m_pRecordset.CreateInstwww.hackshp.cnance(__uuidof(Recordset));//初始化Recordset指针m_pCommand.CreateInstance(__uuidof(Command));//初始化Recordset指针//连接数据源为"DatabaseExampleForVC++"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);//获取表信息 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!//将表信息显示在列表视图控件中CListCtrl&m_ListCtrl=GetListCtrl();CStringstrHeader[3]={"序号","TABLE_NAME","COLUMN_NAME"};for(inti=0;i<3;i++)m_ListCtrl.InsertColumn(i,strHeader[i],LVCFMT_LEFT,120);intnItem=0;CStringstr;_bstr_tvalue;while(!(pRstSchema->adoEOF))课后答案网{str.Format("%d",nItem+1);m_ListCtrl.InsertItem(nItem,str);for(inti=1;i<3;i++)www.hackshp.cn{value=pRstSchema->Fields->GetItem((_bstr_t)(LPCSTR)strHeader[i])->Value;m_ListCtrl.SetItemText(nItem,i,value);}pRstSchema->MoveNext();nItem++;}pRstSchema->Close();} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!4.获取数据源信息代码中,__uuidof用来获取对象的的全局唯一标识(GUID)。ConnectionTimeout是连接超时属性,单位为秒。OpenSchema方法中的adSchemaColumns是一个预定义的枚举常量,用来获取与“列”(字段)相关的信息记录集。该信息记录集的主要字段名有“TABLE_NAME”、“COLUMN_NAME”;类似的,若在OpenSchema方法中指定adSchemaTables课后答案网枚举常量,则返回的记录集的字段名主要有“TABLE_NAME”、“TABLE_TYPE”。上述代码运行结果如图www.hackshp.cn8.29所示。图8.29获取数据源表信息 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.3.2Recordset对象使用Recordset是用来从数据表或某一个SQL命令执行后获得记录集,通过Recordset对象的AddNew、Update和Delete方法可实现记录的添加、修改和删除等操作。1.读取数据表全部记录内容下面的过程是将student.mdb中的course表中的记录显示在列表视图中。(1)打开菜单资源IDR_MAINFRAME,在顶层菜单“查看”下添加一个“显示Course表课后答案网记录”子菜单,将其ID号设为ID_VIEW_COURSE。(2)按快捷键Ctrl+Wwww.hackshp.cn,弹出ClassWizard对话框,向CEx_ADOView类添加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(),//获取当前数据库连接的接口指针adOpenDynamic,//动态游标类型,可以使用Move等操作adLockOptimistic,adCmdTable);//建立列表控件的列表头FieldsPtrflds=m_pRecordset->GetFields();//获取当前表的字段指针_variant_tIndex;Index.vt=VT_I2;课后答案网m_ListCtrl.InsertColumn(0,"序号",LVCFMT_LEFT,60);for(i=0;i<(int)flds->GetCount();i++){www.hackshp.cnIndex.iVal=i;m_ListCtrl.InsertColumn(i+1,(LPSTR)flds->GetItem(Index)->GetName(),LVCFMT_LEFT,140);}//显示记录_bstr_tstr,value;intnItem=0;CStringstrItem;while(!m_pRecordset->adoEOF) 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!{strItem.Format("%d",nItem+1);m_ListCtrl.InsertItem(nItem,strItem);for(i=0;i<(int)flds->GetCount();i++){Index.iVal=i;str=flds->GetItem(Index)->GetName();课后答案网value=m_pRecordset->GetCollect(str);m_ListCtrl.SetItemText(nItem,i+1,(LPCSTR)value);}www.hackshp.cnm_pRecordset->MoveNext();nItem++;}m_pRecordset->Close();}代码中,_variant_t是一个用于COM的VARIANT类,VARIANT类型是一个C结构,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!{Index.iVal=i;m_ListCtrl.InsertColumn(i+1,(LPSTR)flds->GetItem(Index)->GetName(),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;i<(int)flds->GetCount();i++){www.hackshp.cnIndex.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();} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.读取数据表全部记录内容(3)编译运行并测试,结果如图8.30所示。课后答案网www.hackshp.cn图8.30显示Course表所有记录 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.3.2Recordset对象使用2.添加、修改和删除记录记录的添加、修改和删除是通过Recordset对象的AddNew、Update和Delete方法来实现的。例如,向course表中新添加一个记录可有下列代码://打开记录集课后答案网m_pRecordset->AddNew();//添加新记录m_pRecordset->PutCollect("courseno",_variant_t("2112111"));m_pRecordset->PutCollect("coursehourse",_variant_t(60));www.hackshp.cn…m_pRecordset->Update();//使添加有效//关闭记录集 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!2.添加、修改和删除记录若从course表中删除一个记录可有下列代码://打开记录集…m_pRecordset->Delete(adAffectCurrent);//删除当前行m_pRecordset->MoveFirst();课后答案网//调用Move方法,使删除有效//关闭记录集若从course表中修改一个记录可有下列代码://打开记录集www.hackshp.cnm_pRecordset->PutCollect("courseno",_variant_t("2112111"));m_pRecordset->PutCollect("coursehourse",_variant_t(60));…m_pRecordset->Update();//使修改有效//关闭记录集特别强调的是,数据库的表名不能与ADO的某些关键字串同名,例如:user等。另外,通常用Command对象执行SQL命令来实现数据表记录的查询、添加、更新和删除等操作,而用Recordset对象获取记录集,用来显示记录内容。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.3.3Command对象使用Command对象用来执行SQL命令。下面先来简单介绍SQL几个常用语句。1.SELECT语句一个典型的SQL查询可以从指定的数据库表中“选择”信息,这时就需要使用SELECT语句来执行。SELECT语句格式如下:SELECT字段名FROM课后答案网表名[WHERE子句][ORDERBY子句]…它的最简单形式是:SELECT*FROMtableName其中,星号(*)www.hackshp.cn用来指定从数据库的tableName表中选择所有的字段(列)。若要从表中选择指定字段的记录,则将星号(*)用字段列表来代替,多个字段之间用逗号分隔。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.SELECT语句1)WHERE子句在数据表查询SELECT语句中,经常还需要使用WHERE子句来设定查询的条件。它的一般形式如下:SELECTcolumn1,column2,…FROMtableNameWHEREconditionWHERE子句中的条件可以<(小于)、>(大于)、<=(小于等于)、>=(大于等于)、=(等于)、<>(不等于)和课后答案网LIKE等运算符。其中,LIKE用于匹配条件的查询,它可以使用“%”和“_(下划线)”等通配符,“%”表示可以出现0个或多个字符,“_”表示该位置处只能出现www.hackshp.cn1个字符。例如:SELECT*FROMScoreWHEREstudentnoLIKE‘21%’则将Score表中所有学号以21打头的记录查询出来。注意,LIKE后面的字符串是以单引号来标识。再如:SELECT*FROMScoreWHEREstudentnoLIKE‘210105__’则将Score表中所有学号以210105打头的,且学号为8位的记录查询出来。WHERE子句中的条件还可用AND(与)、OR(或)以及NOT(非)运算符来构造复合条件查询,例如:若查询Score表中成绩(score)在70分到80分之间的记录,则可有下列语句:SELECT*FROMScoreWHEREscore<=80ANDscore>=70 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.SELECT语句2)ORDERBY子句在数据表查询SELECT语句中,若将查询到的记录进行排序,则可使用ORDERBY子句。如下面的形式:SELECTcolumn1,column2,…FROMtableName[WHEREcondition]ORDERBYcol1,co2,…课后答案网ASC|DESC其中,ASC表示升序(从低到高),DESC表示降序(从高到低),col1、col2、…分别用来指定是按什么字段来排序。当指定多个字段时,则先按col1排序,当有相同col1的记录时,则相同的记录按www.hackshp.cncol2排序,依此类推。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.3.3Command对象使用2.INSERT语句INSERT语句是用来向表中插入一个新的记录。该语句的常用形式是:INSERTINTOtableName(col1,col2,col3,…,colN)VALUES(val1,val2,val3,…valN)其中,tableName用来指定插入新记录的数据表,课后答案网tableName后跟一对圆括号,包含一个以逗号分隔的列(字段)名的列表,VALUES后面的圆括号内是一个以逗号分隔的值列表,它与tableName后面的列名列表是一一对应的。需要说明的是,若某个记录的某个字段值是字符串,则需要用单引号来括起来。例如:www.hackshp.cnINSERTINTOStudent(studentno,studentname)VALUES("21010503","张小峰")将在Student中插入一个新行,其中studentno(学号)为“21010503”,studentname(学生姓名)为“张小峰”,对于该记录的其它字段值,由于没有指定相应的值,其结果由系统决定。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.3.3Command对象使用3.UPDATE语句UPDATE语句用于更新表中的数据。该语句的常用形式是:UPDATEtableNameSETcolumn1=value1,column2=value2,…,columnN=valueNWHEREcondition该语句可以更新tableName课后答案网表中一行记录或多行记录的数据,这取决于WHERE后面的条件。关键字SET后面是以逗号分隔的“列名/值”列表。例如:UPDATEStudentSETstudentname="王鹏"WHEREstudentno="21010503"将学号为“21010503”www.hackshp.cn的记录中的studentname字段内容更新为“王鹏”。4.DELETE语句DELETE语句用来从表中删除记录,其常用形式如下:DELETEFROMtableNameWHEREcondition该语句可以删除tableNam表中一行记录或多行记录,这取决于WHERE后面的条件。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.4数据库相关的ActiveX控件在前面的数据库处理中,一次只能显示出一行记录,且修改或添加等操作不能“可视化”地进行。为了弥补MFC的这种不足,在VisualC++6.0中允许用户使用一些ActiveX控件用来更好地操作数据库,这些控件包括MSFlexGrid、RemoteData、DBGrid等。8.4.1使用MSFlexGrid课后答案网控件MicrosoftFlexGrid(MSFlexGrid)控件可以显示网格数据,也可以对其进行操作。它提供了高度灵活的网格排序、合并和格式设置功能,网格中可以包含字符串和图片。利用MSFlexGridwww.hackshp.cn可以将某个表的所有记录显示。下面以示例的形式来说明其使用过程。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.4.1使用MSFlexGrid控件[例Ex_Grid]使用RemoteData和DBGrid控件1.将控件的类添加到项目中(1)用MFCAppWizard创建一个单文档应用程序Ex_Grid。(2)在向导的第2步对话框中,选中“数据库查看使用文件支持”项,单击[DataSource]按钮,弹出“DatabaseOptions”对话框,从中选择ODBC的数据源“Datab课后答案网aseExampleForVC++”。保留其他默认选项,单击[OK]按钮,在弹出的“Selectwww.hackshp.cnDatabaseTables”对话框中,选择要使用的表score。(3)单击[OK]按钮,又回到了向导的第2步对话框。单击[完成]按钮。开发环境自动打开表单视图CEx_GridView的对话框资源模板IDD_EX_GRID_FORM。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!1.将控件的类添加到项目中(4)在打开的表单资源模板中右击鼠标,从弹出的快捷菜单中选择“InsertActiveControl”命令,出现如图8.31所示“插入Active控件”对话框。(5)在对话框的控件列表中选择MicorsoftFlexGridControl控件,单击[确定]按钮,该控件就添加到表单资源中,参看图8.33,调整其大小和位置。课后答案网www.hackshp.cn图8.31“插入ActiveX控件”对话框 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Grid]2.修改MSFlexGrid控件属性右击添加的MicorsoftFlexGridControl控件,从弹出的菜单中选择“属性”或“属性MSFlexGridObject”命令均可打开该控件的属性对话框,如图8.32所示。MSFlexGrid控件的属性要比VisualC++的控件属性要多,如“General”、“通用”、课后答案网“样式”、“字体”、“颜色”、“图片”等。这些属性不仅能设置控件的字体、颜色,而且能设置网格的行数和列数以及其他的功能。www.hackshp.cn 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_Grid]3.编程控制(1)保留默认的属性及其控件标识符IDC_MSFLEXGRID1。(2)用MFCClassWizard在CEx_GridView类中为刚才添加的MSFlexGrid控件设置一个相关联的控件变量m_MSFGrid。需要说明的是,在此步骤中会出现一些对话框,用于询问是否要添加相关控件的类代码等,选择[是]。(3)在CEx_ODBCView类的课后答案网OnInitialUpdate函数中添加下列代码:voidCEx_GridView::OnInitialUpdate(){…while(!m_pSet->IsEOF())www.hackshp.cnm_pSet->MoveNext();m_pSet->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英寸。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!//定义单元格的表头m_MSFGrid.SetRow(0);m_MSFGrid.SetCol(0);//定位到(0,0)单元格m_MSFGrid.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);}www.hackshp.cnintiRow=1;while(!m_pSet->IsEOF()){//将表的记录内容显示在单元格中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++) 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!{m_MSFGrid.SetRow(iRow);m_MSFGrid.SetCol(i+1);m_pSet->GetFieldValue(i,str);//获取指定字段值,并自动转换成字符串m_MSFGrid.SetText(str);m_MSFGrid.SetCellAlignment(4);课后答案网}iRow++;m_pSet->MoveNext();www.hackshp.cn}m_MSFGrid.SetRow(1);m_MSFGrid.SetCol(1);m_pSet->MoveFirst();} 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!3.编程控制4.编译运行,结果如图8.33所示。课后答案网www.hackshp.cn图8.33MSFlexGrid控件的结果 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.4.2RemoteData和DBGrid控件MSFlexGrid控件提供界面友好的网格,使表的记录内容能全部地显示出来,但却没有表处理的常用功能,如添加记录、修改记录和删除记录等。而DBGrid控件不仅能自动全部显示表的记录内容,而且常见的记录操作也都能很好地支持。需要注意的是,DBGrid控件还必须用RemoteData控件来提供数据源,但它最大的好处是不需要任何程序代码就能实现表的处理。课后答案网通过被绑定的控件提供对存储在远程ODBC数据源中数据的存取。RemoteData控件允许在某一记录集的行与行之间移动,且允许显示和操作来自于被绑定的控件各行里的数据。www.hackshp.cnRemoteData控件在远程数据对象(RDO)和数据识别的被绑定的控件之间提供了接口。通过RemoteData控件,能够:●建立起与基于其本身属性的数据源的连接。●创建RDO的结果集。●把当前行的数据传送给相应被绑定的控件。●允许对当前行指针进行定位。●将对被绑定的控件所做的任何更改反传给数据源。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知!8.4.2RemoteData和DBGrid控件[例Ex_DBCtrl]使用RemoteData和DBGrid控件(1)用MFCAppWizard创建一个默认的单文档应用程序Ex_DBCtrl,但在向导的第6步将CEx_DBCtrlView的基类由默认的CView选择为CFormView类。(2)在打开的表单资源模板中右击鼠标,从弹出的快捷菜单中选择“InsertActiveControl”命令,出现“插入课后答案网Active控件”对话框。(3)在对话框的控件列表中选择MicrosoftRemoteDataControl,单击[确定]按钮,RemoteData控件就添加到表单资源中,调整其大小和位置。(4)右击该控件,从弹出的菜单中选择“属性”或“www.hackshp.cnPropertiesRemoteDataCtlObject”命令,打开该控件的属性对话框(参看图8.34)。(5)在“Control(控件)”页面中,从“DataSource”的下拉列表中选择所需要的数据源名“DatabaseExampleForVC++”。 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_DBCtrl](6)在“SQL”编辑框中键入SQL操作语句“SELECT*FROMscoreORDERBYstudentno”是检索学生课程成绩表score的所有记录,并按学号排序。设置的结果如图8.34所示。(7)将RemoteData控件属性对话框切换到All页面,单击CursorDriver选项,在右侧的组合框中将其属性选择“课后答案网1-ODBCcursor”。结果如图8.35所示。www.hackshp.cn图8.34设置RemoteData控件的Control属性图8.35设置CursorDriver属性 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_DBCtrl](8)再次右击表单资源模板,从弹出的快捷菜单中选择“InsertActiveControl”命令,在弹出的“插入Active控件”对话框中找到要添加的DBGrid控件,单击[确定]按钮。(9)参看图8.38,调整添加的DBGrid控件的大小和位置,打开该控件的属性对话框,将数据源(DataRource)课后答案网设置为RemoteData控件IDC_REMOTEDATACTL1,如图8.36所示。www.hackshp.cn图8.36设置DBGrid控件的数据源 课后答案网:www.hackshp.cn若侵犯了您的版权利益,敬请来信告知![例Ex_DBCtrl](10)在对话框编辑器的控件布局栏上,单击测试工具按钮(),结果如图8.37所示。按ESC键结束测试。(11)编译运行并测试,结果如图8.38。课后答案网www.hackshp.cn图8.37DBGrid控件测试结果图8.38Ex_DBCtrl运行结果'