存储过程 打印输出出一些CString,有没有可以偷懒的方法

下次自动登录
现在的位置:
& 综合 & 正文
(VC++) CString Format的用法
在MFC程序中,使用CString来处理字符串是一个很不错的选择。CString既可以处理Unicode标准的字符串,也可以处理ANSI标准的字符串。CString的Format方法给我们进行字符串的转换带来了很大的方便,比如常见的int、float和double这些数字类型转换为CString字符串只需一行代码就可以实现。
  先看看Format用于转换的格式字符:
十进制整数(int)
十进制整数(long)
十进制浮点数(float)
十进制浮点数(double)
无符号十进制数
十六进制数
  1、int转换为CString:
  int number=15;
  //str="15"
  str.Format(_T("%d"),number);
  //str=" 15"(前面有两个空格;4表示将占用4位,如果数字超过4位将输出所有数字,不会截断)
  str.Format(_T("%4d"),number);
  //str="0015"(.4表示将占用4位,如果数字超过4位将输出所有数字,不会截断)
  str.Format(_T("%.4d"),number);
  long转换为CString的方法与上面相似,只需要把%d改为%ld就可以了。
  2、double转换为CString:
  double num=1.46;
  //str="1.46"
  str.Format(_T("%lf"),num);
  //str="1.5"(.1表示小数点后留1位,小数点后超过1位则四舍五入)
  str.Format(_T("%.1lf"),num);
  //str="1.4600"
  str.Format(_T("%.4f"),num);
  //str=" 1.4600"(共占七位,前面有1个空格)
  str.Format(_T("%7.4f"),num);
  float转换为CString的方法也同上面相似,将lf%改为f%就可以了。
  3、将十进制数转换为八进制:
  int num=255;
  //str="377"
  str.Format(_T("%o"),num);
  //str=""
  str.Format(_T("%.8o"),num);
Format是一个很常用,却又似乎很烦的方法,以下是它的完整概貌,以供大家查询之用:
格式化字符串forma("%d",12)意思是将一个整形的格式化的字符(我认为是保持其形状不变)
1).格式说明总是以%字符开始,以下是不同类型数据的格式方式%号后的说明:
d输出带符号十进制数
o输出无符号八进制数
x输出无符号十六进制数
u输出无符号数
c输出单个字符
s输出一串字符
f输出实数(6位小数)
e以指数形式输出实数
g选用f与e格式中输出宽度较小的格式,不输出0
ld输入输出long型数据
lf输入输出double型数据
m数据输出宽度为m
.n输出小数位数为n 一、字符串 首先看它的声明:
function Format(const Format: const Args: array of const):
事实上Format方法有两个种形式,另外一种是三个参数的,主要区别在于它是线程安全的,
但并不多用,所以这里只对第一个介绍:
function Format(const Format: const Args: array of const):
Format参数是一个格式字符串,用于格式化Args里面的值的。Args又是什么呢,
它是一个变体数组,即它里面可以有多个参数,而且每个参数可以不同。
如以下例子:
Format("my name is %6s","wind");
返回后就是
my name is wind
现在来看Format参数的详细情况:
Format里面可以写普通的字符串,比如"my name is"
但有些格式指令字符具有特殊意义,比如"%6s"
格式指令具有以下的形式:
"%" [index ":"] ["-"] [width] ["." prec] type
它是以"%"开始,而以type结束,type表示一个具体的类型。中间是用来
格式化type类型的指令字符,是可选的。
先来看看type,type可以是以下字符:
d 十制数,表示一个整型值
u 和d一样是整型值,但它是无符号的,而如果它对应的值是负的,则返回时
是一个2的32次方减去这个绝对值的数
如:Format("this is %u",-2);
返回的是:this is
f 对应浮点数
e 科学表示法,对应整型数和浮点数,
比如Format("this is %e",-2.22);
返回的是:this is -2.0
等一下再说明如果将数的精度缩小
g 这个只能对应浮点型,且它会将值中多余的数去掉
比如Format("this is %g",02.200);
返回的是:this is 2.2
n 只能对应浮点型,将值转化为号码的形式。看一个例子就明白了
Format("this is %n",);
返回的是this is 4,552.22
注意有两点,一是只表示到小数后两位,等一下说怎么消除这种情况
二是,即使小数没有被截断,它也不会也像整数部分一样有逗号来分开的
m 钱币类型,但关于货币类型有更好的格式化方法,这里只是简单的格式化
另外它只对应于浮点值
Format("this is %m",9552.21);
返回:this is ¥9,552.21
p 对应于指针类型,返回的值是指针的地址,以十六进制的形式来表示
Format("this is %p",p);
Edit1的内容是:this is
s 对应字符串类型,不用多说了吧
x 必须是一个整形值,以十六进制的形式返回
Format("this is %X",15);
返回是:this is F
类型讲述完毕,下面介绍格式化Type的指令:
[index ":"] 这个要怎么表达呢,看一个例子
Format("this is %d %d",12,13);
其中第一个%d的索引是0,第二个%d是1,所以字符显示的时候
是这样 this is 12 13
而如果你这样定义:
Format("this is %1:d %0:d",12,13);
那么返回的字符串就变成了
this is 13 12
现在明白了吗,[index ":"] 中的index指示Args中参数显示的
还有一种情况,如果这样Format("%d %d %d %0:d %d", 1, 2, 3, 4) ; 将返回1 2 3 1 2。
如果你想返回的是1 2 3 1 4,必须这样定:
Format("%d %d %d %0:d %3:d", 1, 2, 3, 4) ; 但用的时候要注意,索引不能超出Args中的个数,不然会引起异常
如Format("this is %2:d %0:d",12,13);
//error由于Args中只有12 13 两个数,所以Index只能是0或1,这里为2就错了
[width] 指定将被格式化的值占的宽度,看一个例子就明白了
Format("this is %4d",12);
输出是:this is 12
这个是比较容易,不过如果Width的值小于参数的长度,则没有效果。
如:Format("this is %1d",12);
输出是:this is 12
["-"] 这个指定参数向左齐,和[width]合在一起最可以看到效果:
Format("this is %-4d,yes",12);
输出是:this is 12 ,yes
["." prec] 指定精度,对于浮点数效果最佳:
Format('this is %.2f',['1.1234]);
输出 this is 1.12
Format('this is %.7f',['1.1234]);
输了 this is 1.1234000
而对于整型数,如果prec比如整型的位数小,则没有效果
反之比整形值的位数大,则会在整型值的前面以0补之
Format('this is %.7d',[1234]);
输出是:this is 0001234]
对于字符型,刚好和整型值相反,如果prec比字符串型的长度大
则没有效果,反之比字符串型的长度小,则会截断尾部的字符
Format('this is %.2s',['1234']);
输出是 this is 12
而上面说的这个例子:
Format('this is %e',[-2.22]);
返回的是:this is -2.00E+000
怎么去掉多余的0呢,这个就行啦
Format('this is %.2e',[-2.22]);
好了,第一个总算讲完了,应该对他的应用很熟悉了吧
m_result.Format("您选的城市是:\r\n %s,\r\n您选的人是:\r\n%s",city1 +
city3,people);
UpdateData(0);
格式小结: (1)最常用的格式是%d,含义是以10进制形式打印一个整数。 如果输出的整数是负数,则,输出的第一个字符就是‘-’号 (2)%u格式与%d格式类似,只不过要求打印无符号10进制整数。 (3)%o格式请求输出8进制整数 (4)%x和%X格式请求输出16进制整数。
%x格式中用小写字母a,b,c,d,e,f来表示10到15之间的数
%X格式中用大写字母A,B,C,D,E,F来表示10到15之间的数 共同点:8进制和16进制整数总是作为无符号数处理的。 (5)%s格式用于打印字符串,与之对应的参数应该是一个字符指针,待输出的字符始于该指针所指向的地址,直到出现一个空字符('\0') 才终止。 (6)%c格式用于打印单个字符:例如:
printf("%c",c); 等价于 putchar?; (7)%g,%f和%e这三个格式用于打印浮点值。
%g格式用于打印那些不需要按列对齐的浮点数特别有用。其作用有二: 一,去掉该数尾多余的零(没有达到六位的数) 二,保留六位有效数字(多余六位的)
%e格式用于打印浮点数时,一律显示地使用指数形式:例如:输出圆周率时是:3. 两者的区别:
%g格式打印出的数是总共6位有效数字
%e格式打印出小数点后的6位有效数字
%f禁止使用指数形式来表示浮点数。因此圆周率输出为:3.141593 (但注意它的精度要求:也是小数点后6位有效数字) (8)%%格式用于打印一个%字符。 (9)%E和%G只是在输出时用大写字母(E)代替了小写字母(e) 另外需要注意的一些知识点: *************************************** 对齐规则: (1)当指定宽度大于要输出位数时,数右对齐,左端补空格 当前缀'-'号时,想要数左对齐,右端补空格 大大的前提:只有当“指定宽度”存在时,前缀'-'才有意义。 经验:一般来说,左端对齐的形式看上去要美观整齐一点。 *************************************** 输出正负号的技巧:(记住)例如:
printf("%+d %+d %+d\n",-5,0,5); 只要在中间加个“+”号就行。作用是输出符号位(即,数的正负号) 如果不希望正数的前面出现‘+’号,可用下面的方法 *************************************** 只要在中间加个“ ”号(即:空格)就行。(记住)例如: 作用:如果一个数是非负数,就在它的前面插入一个空格。
for(i=-3;i&=3;i++)
printf("% d\n",i); //注意%和d之间有一个空格 输出结果如下:
3 问题:如果‘+’和‘ ’同时出现在“中间”时,要以‘+’为准。 两个符号的共同点:用于对齐输出的数:(尤其对于小数来说)
两种格式:%+e和% e
C语言中基本的输入输出函数有:
putchar ():把变量中的一个字符常量输出到显示器屏幕上;
getchar ();从键盘上输入一个字符常量,此常量就是该函数的值;
printf ();把键盘中的各类数据,加以格式控制输出到显示器屏幕上;
scanf ();从键盘上输入各类数据,并存放到程序变量中;
puts ():把数组变量中的一个字符串常量输出到显示器屏幕上;
gets ():从键盘上输入一个字符串常量并放到程序的数组中.
sscanf(); 从一个字符串中提取各类数据。
putchar() 和 getchar() 顾名思议就是从输入流中获取一个字符和输出一个字符,比较简单,不再多讲。 例子如下:
char c = getchar();
格式化输入输出scanf()和printf()是最有用的,所以重点讲一下。
printf(): 一般形式:
printf("格式控制".输出列表);
eg : printf("a=%d,b=%f,c=%c\n",a,b,c);
1;格式控制. 格式控制是用双引号括起来的字符串,也称"转换控制字符串",它包含以下两部分信息. 格式说明:由"%"和格式字符组成,如%d,%f,%c,他的作用是把输出数据转换为指定格式输出,格式的说明总是由"%"字符开始的.
普通字符:需要原样输出的字符,或者是一些有特殊含义的字符,如\n,\t。
2;输出列表 就是需要输出的一些数据,也可以是表达式,如果在函数中需要输出多个变量或表达式,则要用逗号隔开.
一些特殊字符的输出: 单引号,双引号,和反斜杠的输出在前面加转义字符”\” 如:”\’” , “\”” , “\\”
%的输出用两个连在一起的%%,即printf(“%%”); 常用的格式说明如下: 格式字符
d 以十进制形式输出带符号整数(正数不输出符号)
o 以八进制形式输出无符号整数(不输出前缀O)
x 以十六进制形式输出无符号整数(不输出前缀OX)
u 以十进制形式输出无符号整数
f 以小数形式输出单精度实数
lf以小数形式输出双精度实数
e 以指数形式输出单、双精度实数
g 以%f%e中较短的输出宽度输出单、双精度实数
c 输出单个字符
s 输出字符串
这里强调一下:网上很多文章都说f 和lf是一样的,即不管单精度,双精度浮点数,都可以用f, 但我在POJ上做过测试,输出Double时用f确实也可以 ,但读入时,用f就报WA,所以大家如果对Double进行读写的话,都用lf吧。 说到Double,再啰嗦一句,建议大家要用到浮点数时都用Double,不要用float,因为在很多情况下,float精度不够会导致WA。 特殊: 对64位整数的输入输出,在POJ上的C++环境下(即VC),64位整数是:
__int64 (注意int前面是两个下划线) 输入输出格式为”%I64d”. 在G++环境下(即Dev C++) 64位整数是
long long 输入输出格式为”%lld”. 输出宽度   用十进制整数来表示输出的最少位数。 注意若实际位数多于定义的宽度,则按实际位数输出, 若实际位数少于定义的宽度则补以空格或0。 精度   精度格式符以“.”开头,后跟十进制整数。意义是:如果输出数字,则表示小数的位数;如果输出的是字符, 则表示输出字符的个数;若实际位数大于所定义的精度数,则截去超过的部分。 标志格式字符
- 结果左对齐,右边填空格
+ 输出符号(正号或负号)空格输出值为正时冠以空格,为负时冠以负号 例如:
double c=32;
printf(“%020.4”); 表示输出精确到小数点后4位,输出占20位,若有空余的位补0.
scanf的很多用法都是和printf对应的,故不再赘述。 说一下scanf一个特别好用的地方,就是可以滤去一些不想要的东西。 举例说明如下: 比如输入为日期 yyyy-mm-dd,就可以这样写:
int year,moth,
scanf(“%d-%d-%d”,&year,&moth,&day); 再比如:
scanf("%3d %*3d %2d",&m,&n); 输入113 118 69回车(系统将113赋予m,将69赋予n,因为*号表示跳过它相应的数据所以118不赋予任何变量)
puts()用的不多,且基本都能用printf()代替,故不再多说。
gets()是从输入流中获取一行字符串放入字符数组中:
char in[100];
gets(in); 大家可能最容易出错的地方就是字符串的输入,所以强调一下: 能进行字符,字符串输入的有:
getchar(), scanf(“%c”); scanf(“%s”), gets() 其中getchar() 和 scanf(“%c”)的功能是一样的。 需要注意的是,这两个函数读入的是输入流中当前位置的字符, 比如:<span lang="EN-U
&&&&推荐文章:
【上篇】【下篇】以下是我学vc++看的几篇文章:不是本人所写:如果作者有需要的话:请联系我 - CSDN博客
以下是我学vc++看的几篇文章:不是本人所写:如果作者有需要的话:请联系我
一走进Visual C++ 1 理解VC工程 2 MFC编程特点 3使用Wizard& 二 MFC程序结构分析 1 WINDOWS程序工作原理 2 建立应用程序&&& 3 程序结构剖析&&& 3.1 类CMYAPP&&& 3.2 类CMAINFRAME&&& 3.3类CMyView与CMyDoc&& 三 深入MFC类库 1 处理用户输入&&& 1.1 定义对话框资源&& 1.2 定义对话框类 2 有关屏幕输出&&& 2.1 设备上下文工作原理&&& 2.2 实例绘图原理剖析 2.3 绘图操作实现 2.4 有关屏幕映射方式 3&&& 文件处理 3.1 对象持续化简述&& 3.2 实例分析 3.3 与文件处理关系密切的类CFile& 4 DAO技术&&& 4.1 DAO与ODBC&& 4.2 使用MFC实现DAO技术 5 打印&& 5.1打印和显示 5.2打印分页& 5.3 打印工作的开始和结束&& 5.4 打印程序实例& 四、VC程序调试 1.1 调试环境的建立&& 1.2调试的一般过程&&& 1.3 如何设置断点 1.4 控制程序的运行&& 1.5 查看工具的使用&& 2 高级调试技术&&& 2.1 TRACE 宏的利用&&& 2.2 ASSERT宏的利用&& 2.3 ASSERT_VALID宏的利用以及类的AssertValid()成员函的重载 2.4对象的DUMP函数的利用& 3 内存漏洞的检查& 五Visual C++与多媒体& 1 对声音的处理&&& 1.1媒体控制接口& 1.2波形混音器&&& 2 多媒体文件I/O& 3多媒体图形图像技术&& 4图像合成 5 FLC动画&&& 6热点&&&    Visual C++作为一个功能非常强大的可视化应用程序开发工具,是计算机界公认的最优秀的应用开发工具之一。Microsoft的基本类库MFC使得开发Windows应用程序比以往任何时候都要容易。本光盘教学软件的目的就是为了让你学会在Visual C++环境下,利用微软的基本类库MFC开发出功能强大的Windows应用程序。在本章节的内容当中,我们将向您介绍使用VC开发软件需要用到的一些基本概念,使用MFC进行编程的基本特点,以及VISUAL C++集成开发环境提供的一系列编程辅助工具&&WIZARD的使用方法。1 理解VC工程Visual C++作为一种程序设计语言,它同时也是一个集成开发工具,提供了软件代码自动生成和可视化的资源编辑功能。在使用Visual C++开发应用程序的过程中,系统为我们生成了大量的各种类型的文件,在本节中将要详细介绍Visual C++中这些不同类型的文件分别起到什么样的作用,在此基础上对Visual C++如何管理应用程序所用到的各种文件有一个全面的认识。首先要介绍的是扩展名为dsw的文件类型,这种类型的文件在VC中是级别最高的,称为Workspace文件。在VC中,应用程序是以Project的形式存在的,Project文件以.dsp扩展名,在Workspace文件中可以包含多个Project,由Workspace文件对它们进行统一的协调和管理。与dsw类型的Workspace文件相配合的一个重要的文件类型是以opt为扩展名的文件,这个文件中包含的是在Workspace文件中要用到的本地计算机的有关配置信息,所以这个文件不能在不同的计算机上共享,当我们打开一个Workspace文件时,如果系统找不到需要的opt类型文件,就会自动地创建一个与之配合的包含本地计算机信息的opt文件。上面提到Project文件的扩展名是dsp,这个文件中存放的是一个特定的工程,也就是特定的应用程序的有关信息,每个工程都对应有一个dsp类型的文件。以clw为扩展名的文件是用来存放应用程序中用到的类和资源的信息的,这些信息是VC中的ClassWizard工具管理和使用类的信息来源。对应每个应用程序有一个readme.txt文件,这个文件中列出了应用程序中用到的所有的文件的信息,打开并查看其中的内容就可以对应用程序的文件结构有一个基本的认识。在应用程序中大量应用的是以h和cpp为扩展名的文件,以h为扩展名的文件称为头文件。以cpp为扩展名的文件称为实现文件,一般说来h为扩展名的文件与cpp为扩展名的文件是一一对应配合使用的,在h为扩展名的文件中包含的主要是类的定义,而在cpp为扩展名的文件中包含的主要是类成员函数的实现代码。在应用程序中经常要使用一些位图、菜单之类的资源,VC中以rc为扩展名的文件称为资源文件,其中包含了应用程序中用到的所有的windows资源,要指出的一点是rc文件可以直接在VC集成环境中以可视化的方法进行编辑和修改。最后要介绍的是以rc2为扩展名的文件,它也是资源文件,但这个文件中的资源不能在VC的集成环境下直接进行编辑和修改,而是由我们自己根据需要手工地编辑这个文件。对于以ico,bmp等为扩展名的文件是具体的资源,产生这种资源的途径很多。使用rc资源文件的目的就是为了对程序中用到的大量的资源进行统一的管理。2 MFC编程特点如果你曾经使用过传统的windows编程方法开发应用程序,你会深刻地体会到,即使是开发一个简单的 windows应用程序也需要对windows的编程原理有很深刻的认识,同时也要手工编写很多的代码。因为程序的出错率几乎是随着代码长度的增加呈几何级数增长的,这就使得调试程序变得非常困难。所以传统的windows编程是需要极大的耐心和丰富的编程经验的。近几年来,面向对象技术无论是在理论还是实践上都在飞速地发展。面向对象技术中最重要的就是&对象&的概念,它把现实世界中的气球、自行车等客观实体抽象成程序中的&对象&。这种&对象&具有一定的属性和方法,这里的属性指对象本身的各种特性参数。如气球的体积,自行车的长度等,而方法是指对象本身所能执行的功能,如气球能飞,自行车能滚动等。一个具体的对象可以有许多的属性和方法,面向对象技术的重要特点就是对象的封装性,对于外界而言,并不需要知道对象有哪些属性,也不需要知道对象本身的方法是如何实现的,而只需要调用对象所提供的方法来完成特定的功能。从这里我们可以看出,当把面向对象技术应用到程序设计中时,程序员只是在编写对象方法时才需要关心对象本身的细节问题,大部分的时间是放在对对象的方法的调用上,组织这些对象进行协同工作。MFC的英文全称是Microsoft Fundation Classes,即微软的基本类库,MFC的本质就是一个包含了许多微软公司已经定义好的对象的类库,我们知道,虽然我们要编写的程序在功能上是千差万别的,但从本质上来讲,都可以化归为用户界面的设计,对文件的操作,多媒体的使用,数据库的访问等等一些最主要的方面。这一点正是微软提供MFC类库最重要的原因,在这个类库中包含了一百多个程序开发过程中最常用到的对象。在进行程序设计的时候,如果类库中的某个对象能完成所需要的功能,这时我们只要简单地调用已有对象的方法就可以了。我们还可以利用面向对象技术中很重要的&继承&方法从类库中的已有对象派生出我们自己的对象,这时派生出来的对象除了具有类库中的对象的特性和功能之外,还可以由我们自己根据需要加上所需的特性和方法,产生一个更专门的,功能更为强大的对象。当然,你也可以在程序中创建全新的对象,并根据需要不断完善对象的功能。正是由于MFC编程方法充分利用了面向对象技术的优点,它使得我们编程时极少需要关心对象方法的实现细节,同时类库中的各种对象的强大功能足以完成我们程序中的绝大部分所需功能,这使得应用程序中程序员所需要编写的代码大为减少,有力地保证了程序的良好的可调试性。最后要指出的是MFC类库在提供的对象的各种属性和方法都是经过谨慎的编写和严格的测试,可靠性很高,这就保证了使用MFC类库不会影响程序的可靠性和正确性。3使用WizardVisual C++是一种功能强大的通用程序设计语言,它提供了各种向导和工具帮助我们来实现所需的功能,在一定程度上实现了软件的自动生成和可视化编程。下面就为你介绍VC集成环境中几个最主要的开发工具的使用方法。首先要介绍的是Appwizard工具,这个工具的作用是帮助我们一步步地生成一个新的应用程序,并且自动生成应用程序所需的基本代码。下面我们就介绍使用Appwizard生成一个应用程序的具体步骤。单击File菜单New菜单项,系统弹出的对话让我们选择所要创建的文件类型,这里的文件分成了Files,Project,Workspaces,Other documents四种大类型,每一个类型下面又包含许多具体的文件类型,选中Projects标签,标签下的工作区中列出的是各种不同的应用程序类型,比如dll类型的动态链接库,exe类型的可执行程序等,这里选中MFC Appwizard(exe)选项,表示要创建的是一个使用MFC基本类 库进行编程的可执行程序。如下图1.1所示:图 1.1 选好后在project name一栏中为程序起一个名字为test,在location一栏中为程序定义文件存放的目录,对话框右下角的platforms一栏中的Win32项表示要创建的程序是建立在32位的windows平台基础上。单击OK按钮,就启动了使用MFC方式开发应用程序的 Appwizard功能。 &&&&&&&&&& &图1.2Wizard让我们选择程序的类型和程序中的资源所用的语种,这里不妨选择程序类型为单文档界面,语种为英语,然后单击NEXT按钮。 &&&&&&&&&& 图1.3Wizard让我们选择是否需要提供数据库方面的支持,这里选择NONE,然后单击NEXT按钮。 &&&&&& 图1.4 下面选择程序中对复合文档的支持,这里选择NONE。 &&&&& 图 1.5 接着选择程序的其它一些特性,如提供对WINSOCK的支持等。这里对系统的缺省值不作改变,如下图1.6所示。单击NEXT按钮。  &&&&&&&&&& 图1.6在第五步中,对话框上部选择是否为程序自动生成注释,对话框的下部用来选择使用MFC类库的方式是动态链接库方式还是静态链接方式,使用动态链接库方式时在以后生成的可执行应用程序中并不真正包含MFC类库中的对象,而使用静态链接方式时,则把MFC库中的代码生成为应用程序的一部分,这时生成的应用程序也就相对大一些。选好后单击NEXT按钮。 图1.7 进入APPWIZARD的最后一个步骤,对话框中的提示信息指明了系统将要自动创建的对象和相关文件,以及派生出这些对象的MFC的基类等内容。在这一步当中,我们还可以对视图类的基类进行选择,单击FINISH按钮。 图1.8 游览一下对话框中对将要生成的程序的有关信息的描述后单击OK按钮。系统就自动为我们生成一个使用MFC基本类库的应用程序的基本框架,在以后将会对这个框架的内容作详细的介绍。  图1.9 接下来介绍VC集成环境中提供的一个很重要的工具CLASSWIZARD,它主要是用来管理程序中的对象和消息的,这个工具对于MFC编程显得尤为重要。单击VIEW菜单的CLASSWIZARD项,就可以运行MFC CLASSWIZARD,在这个对话框中就可以对程序中的对象和消息进行管理了。图1.10 在对话框中的MESSAGE MAPS标签下,PROJECT栏中的内容代表当前程序的名字。CLASSWNAME下拉列表框列出的就是程序当前用到的所有类的名字,在MESSAGE 一栏中列出的就是一个选中的类所能接收到的所有的消息,在WINDOWS程序设计中,消息是个极为重要的概念,用户通过窗口界面的各种操作最后都转化为发送到程序中的对象的各种消息,下面就向您介绍在WINDOWS程序设计中最常用的一些消息:1 窗口消息:WM_CREATE,WM_DESTROY,WM_CLOSE我们创建一个窗口对象的时候,这个窗口对象在创建过程中收到的就是WM_CREATE消息,对这个消息的处理过程一般用来设置一些显示窗口前的初始化工作,如设置窗口的大小,背景颜色等,WM_DESTROY消息指示窗口即将要被撤消,在这个消息处理过程中,我们就可以做窗口撤消前的一些工作。WM_CLOSE wm_close消息发生在窗口将要被关闭之前,在收到这个消息后,一般性的操作是回收所有分配给这个窗口的各种资源。在windows系统中资源是很有限的,所以回收资源的工作还是非常重要的。2 键盘消息:WM_CHAR,WM_KEYDOWN,WM_KEYUP这三个消息用来处理用户的键盘数据,当用户在键盘上按下某个键的时候,会产生WM_KEYDOWN消息,释放按键的时候又回产生WM_KEYUP消息,所以WM_KEYDOWN与WM_KEYUP消息一般总是成对出现的,至于WM_CHAR消息是在用户的键盘输入能产生有效的ASCII码时才会发生。这里特别提醒要注意前两个消息与WM_CHAR消息在使用上是有区别的。在前两个消息中,伴随消息传递的是按键的虚拟键码,所以这两个消息可以处理非打印字符,如方向键,功能键等。而伴随WM_CHAR消息的参数是所按的键的ASCII码,ASCII码是可以区分字母的大小写的。而虚拟键码是不能区分大小写的。3 鼠标消息:WM_MOUSEMOVE,WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBCLICK,WM_RBUTTONDOWN, WM_RBUTTONUP,WM_RBUTTONDBCLICK这组消息是与鼠标输入相关的,WM_MOUSEMOVE消息发生在鼠标移动的时候,剩余的六个消息则分别对应于鼠标左右键的按下、释放、双击事件,要指出的是WINDOWS系统并不是在鼠标每移动一个像素时都产生MOUSEMOVE消息,这一点要特别注意。4 另一组窗口消息:WM_MOVE , WM_SIZE , WM_PAINT 当窗口移动的时候产生WM_MOVE 消息,窗口的大小改变的时候产生WM_SIZE消息,而当窗口工作区中的内容需要重画的时候就会产生WM_PAINT消息。5 焦点消息WM_SETFOCUS,WM_KILLFOCUS当一个窗口从非活动状态变为具有输入焦点的活动状态的时候,它就会收到WM_SETFOCUS消息,而当窗口失去输入焦点的时候它就会收到WM_KILLFOCUS消息。6 定时器消息:WM_TIMER当我们为一个窗口设置了定时器资源之后,系统就会按规定的时间间隔向窗口发送WM_TIMER消息,在这个消息中就可以处理一些需要定期处理的事情。最后要指出的一点是,在WINDOWS环境下,消息的来源是多方面的,最常见的是用户的操作产生消息,系统在必要的时候也会向程序发送系统消息,其他在运行中的程序也可以向程序发送消息。此外,在程序的内部,也可以根据需要在适当的时候主动产生消息,比如主动产生WM_PAINT消息以实现需要的重画功能。&&& 上面介绍了MESSAGE栏中主要的消息,在MEMBER FUNCTION一栏中列出的是目前被选中的类已经有的成员函数。这些成员函数一般说来是与这个类可以接收的消息一一对应的。也就是说,一个成员函数一般总是用来处理某个特定的消息。如果在MESSAGE栏中的某个消息在程序中需要处理,但目前还没有相应的类成员函数,比如这里选中WM_TIMER这个消息,它目前还没有相应的对应的类的成员函数,单击ADD FUNCTION按钮,图1.11系统就自动为WM_TIMER消息在类中添加了对应的成员函数ONTIMER,单击EDITCODE按钮,可以发现系统已经自动生成了完成ONTIMER函数所需的基本代码,我们只要在这些基本代码的基础上再添加所需要的代码就可以了。注意对话框中的ADD CLASS按钮,它用来往当前应用程序中添加一个新的类 。单击后选中NEW菜单,图1.12 系统弹出了NEW CLASS对话框用于生成一个新的类。在这个对话框中需要为类起个名字,设置类文件的名字,另外还要在BASE CLASS一栏 的下拉列表框中选择某个已有的类作为基类,设好需要的信息后单击OK就生成了一个新的类。图1.13CLASS WIZARD还有一些很强大的功能,这里就不再详细介绍,你会在不断的学习中慢慢地了解和掌握。最后介绍一下集成环境提供的一个重要工具RESOUCR EDITOR,也就是资源编辑器。在VC开发的应用程序中要用到大量的位图,菜单,工具条,对话框等各种资源。这些资源对于程序而言是相对独立的,所以可以对它们进行单独的编辑,然后使用在程序中。而RESOUCE EDITOR正是为编辑资源提供了一种可视化的开发方法。极大地减轻了程序员的负担。单击FILE菜单的OPEN菜单项,然后在对话框中选择打开TEST.RC文件,就可以开始使用资源编辑器了。在左边的工作区中按类型列出了程序中用到的所有的资源,双击其中的某个类型,比如双击MENU资源,MENU目录的下面列出的就是系统已经有的 MENU类型的资源,选中其中一个并双击,在右边的工作区中列出了这个资源当前的样子,我们就可以在工作区中对资源进行可视化的编辑和修改了。&&& &图1.14 如何添加一个资源呢?单击INSERT菜单,选中RESOURCE菜单项,系统弹出INSERT RESOURCE对话框。如图1.15。    图1.15在图1.15这个对话框,在这个对话框中选中一种资源类型,比如选择CURSOR类型,然后单击NEW按钮。在左边的工作区中就出现了我们新生成的资源的标识符,双击这个标识符,在右边的工作区中就可以对这个新的指针形状资源进行可视化编辑了。如图1.16。通过这部分内容的介绍,相信您已经对使用VISUAL C++开发MFC应用程序的图1.16基本步骤有了认识。在下一章的内容当中,我们将结合WINDOWS的工作原理,详细地向您解释MFC类库的基本结构,以及MFC应用程序的基本框架&&文档/视图结构。  二 MFC程序结构分析1 WINDOWS程序工作原理WINDOWS程序设计是一种完全不同于传统的DOS方式的程序设计方法,它是一种事件驱动方式的程序设计模式。在程序提供给用户的界面中有许多可操作的可视对象。用户从所有可能的操作中任意选择,被选择的操作会产生某些特定的事件,这些事件发生后的结果是向程序中的某些对象发出消息,然后这些对象调用相应的消息处理函数来完成特定的操作。WINDOWS应用程序最大的特点就是程序没有固定的流程,而只是针对某个事件的处理有特定的子流程,WINDOWS应用程序就是由许多这样的子流程构成的。从上面的讨论中可以看出,WINDOWS应用程序在本质上是面向对象的。程序提供给用户界面的可视对象在程序的内部一般也是一个对象,用户对可视对象的操作通过事件驱动模式触发相应对象的可用方法。程序的运行过程就是用户的外部操作不断产生事件,这些事件又被相应的对象处理的过程。下面是WINDOWS程序工作原理的示意图。                2 建立应用程序&&& 在介绍AppWizard的时候,我们已经建立了一个名字为TEST的工程,事实上这个框架程序已经可以编译运行了。在BUILD菜单中选择REBUILD ALL菜单项,系统开始编译由APPWIZARD自动生成的程序框架中所有文件中的源代码,并且链接生成可执行的应用程序。在BUILD菜单中选择 EXECUTE菜单项,应用程序就开始开始运行了,虽然我们没有编写一行代码,但是可以看出由系统自动生成的应用程序的界面已经有了一个标准 WINDOWS应用程序所需的几个组成部分,我们要做的事情是往这个应用程序添加必要的代码以完成我们所需要的功能。接下来将要对WINDOWS自动生成的这个应用程序框架作详细的介绍,让你对MFC方式的WINDOWS应用程序的工作原理有全面的认识,只有这样你才会知道应该如何往程序框架当中添加需要的代码。图2.13 程序结构剖析为了让您对MFC方式的程序的框架有一个总体的认识,这里设计了一个表示程序中的主要类之间的关系的图表: &&& 这个图表表示了使用MFC方式的应用程序的四个主要类之间的关系,从中可以看出,CMYAPP类主要的作用是用来处理消息的,它统一管理程序收到的所有的消息,然后把消息分配到相应的对象。CMAINFRAME是CMYVIEW的父类,也就是说视窗VIEW显示在主框窗MAINFRAME的客户区中。类CMYVIEW的作用是显示数据,而数据的来源是类CMYDOC,在MFC程序中,程序的数据是放在文档当中的,而显示数据则是利用视窗方式,文档与视窗分离带来的好处就是一个文档可以同时具有多个视窗,每个视窗只显示文档中的一部分数据,或者以特定的风格显示文档中的数据。文档与视窗分离的另一个好处就是在程序中可以处理多个文档,通过对不同的视窗的处理达到对不同的文档分别处理的目的。使用过传统的WINDOWS编程方法的人都知道,在应用程序中有一个重要的函数WINMAIN(),这个函数是应用程序的基础,用户的操作所产生的消息正是经过这个函数的处理派送到对应的对象中进行处理。在MFC方式的WINDOWS应用程序中,用来处理消息的是系统自动生成的MFC中的类CWINAPP的派生类CMYAPP,下面就从这个类开始介绍应用程序的框架。3.1 类CMYAPP类CMYAPP是应用程序运行的基础,注意这一行代码,可以看出这个类是由MFC中的类CWINAPP派生来的。在这个类中除了有一般类都有的构造函数,一个重要的成员函数就是INITINSTANCE,我们知道,在WINDOWS环境下面可以运行同一程序的多个实例,函数INITINSTANCE的作用就是在生成的一个新的实例的时候,完成一些初始化的工作。注意这一行代码,它的作用就是生成一个 CMYAPP类型的对象,生成的时候系统会主动调用INITINSTANCE函数完成一些必要的初始化工作。下面研究INITINSTANCE 函数所做的事情,注意这一行代码,它定义了一个文档模板对象指针PDOCTEMPLATE,通过NEW操作符,系统动态生成了这个文档模板对象,然后使用 ADDDOCTEMPLATE函数把这个文档模板对象加入到应用程序所维护的文档模板链表当中,这个文档模板PDOCTEMPLATE的作用就是把程序用到的框架窗口,CMAINFRAME,文档CMYDOC,视窗CMYVIEW与应用对象CMYAPP联系起来。CMYAPP类提供了用户与WINDOWS应用程序之间进行交流的界面。在生成这个类的对象后,这个对象自动地把自身与WINDOWS系统建立联系,接收WINDOWS传送的消息,并交给程序中相应的对象去处理,这就免去了程序员许多的工作,使得开发C++的 WINDOWS程序变得简单方便。&&& 3.2 类CMAINFRAME类CMAINFRAME是由MFC中的CFRAMEWND派生来的,所以它也是一个框架窗口。前面已经指出,CMAINFRAME是类CMYVIEW的父类,也就是说CMYVIEW类的对象显示在主框架窗口的客户区中。在类CMAINFRAME 中,系统已经从类CFRAMEWND那里继承了处理窗口的一般事件的WINDOWS消息,比如改变窗口的大小,窗口最小化等等的成员函数,因此编程的时候程序员不需要再关心此类消息的处理,从而减轻了程序员的负担。当然,如果确实需要重新编写处理此类消息的成员函数,则需要对原有的成员函数进行重载。在MFC程序中,我们并不需要经常对CMAINFRAME类进行操作,更多的是对视窗类进行操作,达到对程序中的数据进行编辑和修改的目的。最后要指出的是,在MFC方式的程序中,当程序的一个实例被运行的时候,系统根据前面在CMYAPP类中介绍的文档模板对象自动生成类CMAINFRAME,CMYVIEW,CMYDOC的对象,而不需要程序员主动地去创建这些类的对象。  3.3类CMyView与CMyDoc之所以把CMyView类和CMyDoc类一起介绍是因为这两个类是密切相关的,下面的框图可以说明文档与视窗的关系。   在这个框图当中,文档是由文档模板对象生成的,并由应用程序对象管理,而用户则是通过与文档相联系的视窗对象来存储、管理应用程序的数据,用户与文档之间的交互则是通过与文档相关联的视窗对象来进行的。生成一个新的文档的时候,MFC程序同时生成一个框架窗口,并且在框架窗口的客户区中生成一个视窗对象作为框架窗口的子窗口,这个子窗口以可视化的方式表现文档中的内容。视窗的重要功能就是负责处理用户的鼠标、键盘等操作,通过对视窗对象的处理达到处理文档对象的目的。要指出的一点是,WINDOWS应用程序分单文档界面SDI和多文档界面MDI两种,在单文档界面中,文档窗口与主框架窗口是同一概念。而这时的视窗对象则是显示在文档窗口的客户区当中。我们先前生成的TEST程序使用的就是单文档界面方式,此时文档窗口是主框架窗口,即类CMAINFRAME的对象。下面将以一个例子来说明这两个类之间的关系。前面已经提到,文档类是用来存放程序中的数据的,所以我们首先在文档类CMyDoc中加入一个成员变量用来存放数据。在左边的工作区用右键单击CMyDoc选项,在弹出的菜单中选中Add member variable菜单项。图2.2 系统弹出Add Member Variable对话框。Variable Type一栏用来输入成员变量的类型。这里设置为CString,即字符串类型,Variable Declaration一栏用来输入变量的名字,这里不妨输入为mystring,Access组合框用来设置成员变量的访问权限,缺省为Public,设好后单击OK按钮关闭对话框。如下图2.3所示:图2.3 这时,如果打开类CMyDoc的头文件、可以发现其中已经自动加入了我们定义的公有变量mystring。这个变量就可以作为我们的文档类的数据存储空间,因为mystring是公有成员,它就可以被文档对应的视窗所处理了。在VIEW菜单中选择ClassWizard菜单项,系统打开MFC ClassWizard对话框,接下来我们要为视窗类添加处理键盘事件的成员函数。在Classname一栏中选中类CMyView,然后在messages一栏中选中消息wm_char,单击add function按钮,系统就自动往CMyView类中添加了处理wm_char消息的成员函数的框架。单击edit code按钮,接下来对OnChar这个成员函数进行编辑和修改。可以看出系统已经自动在这个成员函数中添加了CMyView的基类CView的WM_CHAR消息的处理函数。注意这一行代码:BEGIN_MESSAGE_MAP(CMyView, CView)//{{AFX_MSG_MAP(CMyView)ON_WM_CHAR()ON_WM_LBUTTONDOWN()ON_WM_CANCELMODE()//}}AFX_MSG_MAP// Standard printing commandsON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)END_MESSAGE_MAP()它被放在mfc的消息映射宏BEGIN_MESSAGE_MAP中,它的作用就是把windows系统发来的WM_CHAR消息连接到CMyView类的成员函数OnChar上,即把这个成员函数作为处理WM_CHAR消息的过程。接下来我们就往这个成员函数中添加处理WM_CHAR消息的具体代码。首先在OnChar函数中添加如下的代码:void CMyView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {// TODO: Add your message handler code here and/or call defaultCMyDoc *pdoc=GetDocument();}这段代码的作用是首先定义一个指向文档类对象的指针pdoc,然后利用CMyView类的成员函数 getdocument()获取指向当前视窗类所对应的文档类对象的指针,并把这个指针赋给定义的文档类型指针pdoc,这样我们在后面就可以用 &pdoc_&mystring&的方式访问文档类中定义的公有数据成员mystring了。接着往函数OnChar中添加如下的代码:pdoc_&mystring=nCCClientDC mydc(this);mydc.TextOut(0,0,pdoc_&mystring,pdoc_&mystring.GetLength());&&& 这段代码中的第一行代码的作用是根据从消息WM_CHAR中传来的参数nchar,也就是键盘中输入的字符的ASCII码,把输入的字符添加到文档中的字符串对象mystring中。在介绍第二行代码前要先介绍设备描述表的概念。设备描述表也称为设备上下文,在windows环境中,当需要对一个对象,如打印机,屏幕,窗口等进行输出时,就必须先获取这个对象的设备描述表,然后通过这个设备描述表来进行输出。使用设备描述表带来的最大的好处就是输出格式的一致性,因为输出不再是直接针对具体的设备,而是通过统一格式的设备描述表间接地实现。第二行代码的作用就是定义并生成了一个当前视窗的客户区的设备描述表对象MYDC,其中的参数THIS是面向对象语言中的一个重要的关键字。指代成员函数所在类的对象的指针。在生成了视窗的客户区的设备描述表MYDC之后,我们可以利用它在视窗的客户区中输出数据了。这段代码的第三行就是调用设备描述表MYDC的方法TEXTOUT,在视窗的客户区中输出文档中的字符串MYSTRING了。我们在前面曾经指出,一个文档可以对应多个视窗。如果用户通过某个视窗更改了文档中的数据,就比如上面的代码当中,我们通过视窗CMYWIVE更改了文档中的字符串对象MYSTRING,那么系统又如何维护同一文档的不同的视窗显示的数据的一致性呢?我们接着在OnChar函数中输入如下的代码:pdoc_&UpdateAllViews(this,0L,0);这行代码的作用就是通知本视窗所在的文档的所有其他的视窗,文档中的数据已经更新,这些视窗应该重新从文档中取回数据用来显示,这样就维持了同一文档的所有视窗的数据的一致性。这一行是视窗类中对文档的数据作了修改以后需要加的一条典型语句。接下来运行这个程序,在BUILD菜单中选择REBUILD ALL菜单项来编译连接应用程序,然后单击BUILD菜单的EXECUTE菜单项运行程序,从键盘上输入一些字符,可以发现这些字符显示在视窗也就是主窗口的客户区当中。 而这些字符的实际位置是存放在文档对象的成员变量MYSTRING这个字符串中。改变一下窗口的大小,可以发现显示的数据都没有了,这是因为我们在窗口尺寸改变的时候没有把数据重新输出到窗口的客户区中。关闭应用程序,找到CMyView类的成员函数ONDRAW,在其中添加如下的代码:void CMyView::OnDraw(CDC* pDC){&& CMyDoc* pDoc = GetDocument();&& ASSERT_VALID(pDoc);&& // TODO: add draw code for native data here&& pDC_&TextOut(0,0,pDoc_&mystring);}当窗口的大小改变的时候,应用程序会调用ONDRAW这个函数,我们添加的这行代码的作用就是把字符串对象MYSTRING重新显示在窗口的客户区当中,这样在窗口大小改变的时候,数据依然显示在窗口客户区中。再次编译运行这个程序,可以发现窗口大小的改变不再影响数据的显示了。在前面的内容当中,我们已经介绍了使用MFC编制程序的基本结构。MFC的内容非常丰富,下面我们将针对软件的基本任务:接受用户输入、处理程序输出、进行文件处理以及数据库访问技术,向您介绍如何使用MFC编写程序,实现这些基本的功能。三 深入MFC类库1 处理用户输入程序从用户那里得到数据,经过自己的处理,再把计算的结果输出到屏幕、打印机或者其他的输出设备上,这是软件工作的基本模型。消息和键盘消息是最基本的输入信息,除此之外,MFC封装了一系列的使用户可以进行可视化的输入的界面对象,比方说对话框以及可以布置在上面的编辑框、按钮、列表控件、树形控件等等。使程序支持用户输入的手段更加丰富。 图3.1 1.1 定义对话框资源下面我们通过一个例子来介绍如何设计一个基于对话框的界面,接受用户输入的数据,并且将它们以图形化的方式显示出来。我们将制作这样的一个有用的程序。它访问一个保存歌曲曲目的数据库,用户可以通过对话框让用户定义一个曲目表,并选定一张背景图,然后在一个没有系统菜单的窗口客户区上滚动显示曲目的名字。在许多的娱乐场所的大屏幕上我们都可以看到类似的东西。在这部分的内容当中,我们着重介绍如何获得和处理用户输入数据的工作,在后面的处理用户输出的内容当中,我们还将使用这个例子来说明如何在屏幕上进行输出工作。&&& 下面我们来介绍如何定义一个对话框资源。在WORKSPACE窗口当中RESOURCE一页,在DIALOG小图标上面单击鼠标右键,弹出菜单,选择INSERT命令,如图3.2所示:图3.2接下来在弹出的资源类型对话框中选择DIALOG,表示添加一个对话框资源。单击NEW按钮。图3.3接下来我们就开始布置这个对话框。对话框上面已经有了两个按钮:OK和CANCEL。如下图3.4所示。它们的作用分别是确认用户通过对话框进行的输入和取消前面的输入工作。把它们的标题分别改为确定和取消。布置其它的控件。每一个对话框以及对话框上面的每一个控件都有一个ID号码,它们的定义包括在RESOURCE.H 这个头文件当中。比方说,这个对话框的ID号是IDD_DIALOG2,确认按钮的ID号是IDOK,取消按钮的ID号是IDCANCEL,MFC将通过 ID号来访问这些资源。单击DIALOG工具条上面的TEST按钮可以测试对话框运行的效果。需要注意的是我们这里我们定义的对话框只是一个资源,如果要使这个对话框真正实现它的功能,必须在程序当中定义一个使用这个资源的对话框类。图3.4 1.2 定义对话框类下面我们就定义一个对话框类。在VIEW菜单当中选择CLASS WIZARD命令,单击ADD CLASS按钮,在弹出的菜单当中选择NEW命令,在NAME一栏当中输入新类的名字,在BASE CLASS列表框当中选择需要继承MFC当中的哪一个类。在DIALOG ID列表框当中选择对话框资源的ID号码,在这个实例当中,我们不使用OLE AUTOMATION,所以在这个组框当中选择NONE。在FILE NAME一栏显示的是这个类的定义写在哪一个文件当中。  图3.5单击图3.5中所示CHANGE按钮,在HEADER FILE和IMPLEMENTATION FILE当中分别敲入新类的声明和定义分别写在哪个文件当中,单击OK按钮确认,这样我们就完成了对新的对话框类的定义。单击OK 按钮,CLASS WIZARD将按照我们刚才的要求进行对话框类定义的工作。打开WORKSPACE,选择FILE VIEW一页,在SOURCE FILES和HEADER FILES组当中到CLASS WIZARD已经新建了两个文件,并将它们加入了工程当中。SongDlg.h当中内容是CSongDlg这个类的声明,SongDlg.cpp这个文件当中的内容是这个类的实现。但是目前的程序只是包含了实现一个对话框的最基本功能的代码,调用这个对话框类的DoModal函数之后可以运行它。但是用户通过对话框进行的所有的输入工作都不会被接受。下面,我们就着手完成实现对话框接受用户输入功能的工作。这里核心的工作就是实现对布置在对话框当中的控件的控制。控制又可以分两种类型:第一种是与界面上的控件交换数据,在对话框中的某些响应函数当中编写取出用户在对话框当中输入的数据。比方说在用户单击了确认输入的按钮,触发了该按钮的单击事件的时候,我们就要从输入新歌的编辑框当中取出曲目字符串保存到数据库当中,并将其显示在曲目列表当中。我们可以使用MFC提供的一种叫做对话框数据交换(DDX)的机制来从编辑控件当中取出数据。在MFC的对话框类CDialog中已经封装了这种机制。它的工作原理就是在对话框资源中的编辑框和对话框类的一个成员变量之间建立连接。然后由MFC自动地完成在成员变量和控件之间的数据交换工作。首先打开CLASSWIZARD,选中MEMBER VARIABLE这一页,在CLASS NAME列表框当中选择CSongDlg,选择曲目编辑框的ID号IDC_EDIT1,单击ADD VARIABLE按钮。 图3.6在MEMBER VARIABLE NAME 一栏当中敲入变量的名字,在CATEGORY列表框当中可以选择变量的类型,VALUE表示生成一个数据变量,CONTROL类型的变量可以被用来对控件资源进行另一种类型的控制。它的类型依赖于前面选中的控件资源,比方说如果为一个编辑框控件成一个CONTROL类型的成员变量,那么它只能是CEdit 类型的。我们将在后面的内容当中具体地介绍如何使用CONTROL类型的成员变量。图3.7生成一个VALUE变量,它的数据类型是字符串。单击OK按钮。这时WIZARD就自动地添加了进行对话框数据交换所有的代码。打开对话框类的头文件和实现文件,我们发现当中增加了一个CString类型的成员变量:// Dialog Data&& //{{AFX_DATA(CSongDlg)&& enum { IDD = IDD_DIALOG2 };&& CString m_&& //}}AFX_DATA并且在建构函数当中对这个变量进行了初始化:CSongDlg::CSongDlg(CWnd* pParent /*=NULL*/)&& : CDialog(CSongDlg::IDD, pParent){&& //{{AFX_DATA_INIT(CSongDlg)&& m_songname = _T(&&);&& //}}AFX_DATA_INIT}在新生成的对话框类CSongDlg 中有如下一个虚函数:virtual void DoDataExchange(CDataExchange* pDX);//DDX/DDV support DoDataExchange函数就是对话框类和对话框资源进行DDX数据交换的函数。在对话框初始化的时候或者在程序中调用UpdateData()函数的时候,这个函数将会被调用。DDX_TEXT这个函数可以处理多种类型的数据成员变量与控件资源之间的数据交换。这中间包括int,uint,long,DWORD,CString,float,double等。PDX这个参数是一个指向一个 CDataExchange对象的指针通过它我们可以设置进行数据交换的方法。比方说:数据交换的方向。这段代码就可以通过PDX的这个标志志判断数据交换的方向是从变量到控件还是从控件到变量,然后进行不同的处理。进行数据交换之后,程序当中就可以通过成员变量来使用用户输入的数据了。对控件资源的另外一种类型的控制就是要操纵界面控件的外观。比方说,我们可以通过生成一个CONTROL类型的成员变量来控制对话框当中的列表控件。和VALUE类型变量的添加方法一样,我们可以使用CLASSWIZARD生成一个CListControl 类型的对象,在DoDataExchange当中增加了这样的代码:DDX_Control(pDX, IDC_LIST1, m_listCtrl1);DDX_CONTROL也是对话框数据交换机制提供的一个函数,它的作用和DDX_TEXT大致一样。使用刚才定义的控件对象m_listCtrl1,就可以对列表框资源进行操纵了。当对话框开始运行的时候,我们需要从数据库当中取出已经入库的曲目的名字将其显示在曲目列表框当中。这个工作应该在对话框响应WM_INITDIALOG消息的时候来做。使用CLASS WIZARD来添加这个消息响应函数。在左边的列表框当中选定CSongDlg这个类,在消息列表框当中选定对话框初始化消息,单击ADD FUNCTION按钮,WIZARD就自动地在这个类的声明当中重载了基类的这个成员函数并且在实现文件当中加入了函数体。单击EDIT CODE 按钮,就可以在函数体当中加入我们自己的代码了。 图3.8在响应WM_INITDIALOG消息的处理函数CSongDlg::OnInitDialog中添加如下一段代码:COleVariant&& & LV_ITEM&&&&&& CString&&&&&& Name(&song_name&);char&&&&&& str[50];lvitem.iItem = 0;if(globalRS_&IsOpen())globalRS_&Close();CString strQuery = _T(&Select * from &);strQuery += &SONGS&;globalRS_&Open(dbOpenDynaset,strQuery);globalRS_&m_bCheckCacheForDirtyFields = FALSE; if(globalRS_&IsOpen()){&& if(!globalRS_&GetRecordCount())&&&&& return 0;&& globalRS_&MoveFirst();&& while(!globalRS_&IsEOF())&& && {&&&&& &var& =globalRS_&GetFieldValue(_T(&[&) + Name + _T(&]&));&&&&& &&& lvitem.mask = LVIF_TEXT | LVIF_IMAGE| LVIF_DI_SETITEM;&& lvitem.iItem++ ;&& lvitem.iSubItem = 0;&& strcpy(str , (LPCTSTR)CString(V_BSTRT(&var)));&& lvitem.pszText = && lvitem.iImage = 0;&& m_originsonglist.InsertItem(&lvitem);&& globalRS_&MoveNext();&& }}if(globalRS_&IsOpen())globalRS_&Close();这里使用了DAO技术来访问数据库并使用读出的字符串向列表控件当中添加条目。关于DAO技术的使用方法,我们在其他的章节当中会有详细地介绍。我们关心的是下面这段代码:&&& lvitem.mask = LVIF_TEXT | LVIF_IMAGE| LVIF_DI_SETITEM;&& lvitem.iItem++ ;&& lvitem.iSubItem = 0;&& strcpy(str , (LPCTSTR)CString(V_BSTRT(&var)));&& lvitem.pszText = && lvitem.iImage = 0;&& m_originsonglist.InsertItem(&lvitem);它执行了对列表控件添加条目的操作。这里需要用到WIN32提供的一个结构:LV_ITEM。我们可以从VC的HELP中找到其定义:typedef struct _LV_ITEM { UINT int iI int iSubI UINT UINT stateM LPTSTR pszT int cchTextM int iI // index of the list view item抯 icon LPARAM lP // 32_bit value to associate with item } LV_ITEM;为了添加一个条目,我们首先在在这个结构当中填写条目的信息,然后把它传给列表对象的添加条目函数InsertItem就可以了。接下来的这段代码位于响应中曲目列表框当中删去选定曲目的按钮单击事件当中。要实现从列表控件当中删去的条目的操作,只要把需要删去的条目的索引号传递给列表对象的删去条目函数DeleteItem就可以了。&&& int totalN&& totalNum = m_selsonglist.GetItemCount();&& int step = 0;&& LV_ITEM&&&  &&& lvitem.mask = LVIF_TEXT | LVIF_IMAGE| LVIF_DI_SETITEM;&& lvitem.iSubItem = 0;&& lvitem.iImage = 0;&& while(step &= totalNum)&& {&&&&& if(m_selsonglist.GetItemState(step,LVIS_SELECTED))&&&&& { &&&&&&& m_selsonglist.DeleteItem(step); &&&&& }&&&&& step++;&& }我们在这总结一下对对话框上面的控件进行控制所需的工作:首先在对话框类当中定义成员变量,然后调用对话框的成员变量的函数来操纵界面控件。2 有关屏幕输出2.1 设备上下文工作原理在绝大多数的WINDOWS应用都需要在屏幕上显示自己的数据。由于WINDOWS是一个设备无关的操作系统,所以任何向屏幕上进行输出的功能都要间接地通一个叫做设备上下文(device context)的对象来完成。我们向设备上下文提出屏幕输出的要求,然后由WINDOWS自己来调用具体的输出设备的驱动程序来完成实际的输出工作。围绕设备上下文,MFC提供了一系列与其配合使用的绘图对象,这其中包括画笔对象、刷子对象以及字体对象等等。它们的工作模型是这样的:首先对设备上下文对象&&我们简称为DC对象&&进行设置,然后选择进行屏幕输出所需要的工具,最后用DC对象的输出函数绘制图形。屏幕输出的目标一般都是窗口的客户区,它是一个万能的输出区域,可以接受无论是文本、位图、还是其他类型的数据(比方说OLE对象)。2.2 实例绘图原理剖析在有关用户输入部分的内容当中,我们曾经介绍过一个实例,它访问一个保存歌曲曲目的数据库,用户可以通过对话框让用户定义一个曲目表,并选定一张背景图,然后在一个没有系统菜单的窗口客户区上滚动显示曲目的名字。我们已经介绍了通过对话框介绍用户输入的方法,接下来就着重介绍如何把用户输入的信息在屏幕上显示出来。我们将用户选定的字符串在一张背景图上面滚动输出。前面已经介绍了使用设备上下文进行工作的基本模型。即:首先选择绘图的工具,然后调用DC 的绘图函数来进行绘制。在WINDOWS当中,每次窗口的外观发生改变的时候都会发出一个WM_PAINT消息,窗口的重绘工作都是在响应这个消息的处理函数当中进行的。可以使用CLASSWIZARD来添加这个消息响应函数。之后,就可以在这个函数当中进行屏幕输出了。还有什么时候会触发重绘事件呢?在程序中调用CWND的UpdateWindow 和RedrawWindow数的时候都会触发重绘事件。我们还可以直接使用SendMessage函数向一个指定的窗口送出重绘消息。另外调用CWND的 Invalidate函数可以指示重绘的时候是否需要擦去背景,如果使用InvalidateRect函数还可以设置客户区的无效区域,系统重绘的时候将只把该区域的内容重新绘制,我们首先在窗口的客户区上帖一张位图,然后滚动输出文本。如何实现滚动输出呢我们的方法是在程序中设置一个定时器,在定时器计时已满事件WM_TIMER触发的时候来,调用REDRAWWINDOW函数,触发重绘事件,我们只要在它的消息响应函数ONPAINT当中重新绘制背景,擦去原来的文本,然后不断的改变文本输出的位置就可以达到目的了。您可能会重绘整个背景的做法会很耗费了过多的系统时间并且可能产背景的闪烁。这种担心是必要的。在WINDOWS95当中,系统对重绘的机制进行了优化,在我们没有指定无效区域的情况之下,系统自己会选择一个最小的无效区域,只对这一区域进行重绘。2.3 绘图操作实现下面介绍绘图操作的源程序使您对设备上下文的使用有一个大致的了解。首先生成一个设备上下文。CPaintDC是MFC提供的一个从CDC继承出来的类。使用它有什么好处呢? 如果直接使用CDC的话,我们需要首先调用CWnd的BeginPaint函数为重绘工作做一些准备工作,在完成绘制之还要用EndPaint函数表示结束绘制工作。所有的绘图操作都必须在这两个函数之间完成。CPaintDC封装了这两个函数,自动地对它们进行调用,使用者无须再去进行这些调用。CPaintDC dc(this);BITMAPm_bitmap_&GetBitmap(&bm); CDC dcIif (!dcImage.CreateCompatibleDC(&dc))&& CBitmap* pOldBitmap = dcImage.SelectObject(m_bitmap);dc.BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &dcImage, 0, 0, SRCCOPY);以上这段代码完成向屏幕上面输出位图的工作。首先根据资源生成一个位图对象,然后生成成一个和CPaintDC一致的内存DC对象,在内存DC当中选择这个位图。BITMAP是一个WIN32提供的位图结构,我们将这幅位图的信息保存在这个结构当中。这样做的原因是由于在使用到位图的位置及大小信息。BITMAP结构的定义如下:typedef struct tagBITMAP { /* bm */int bmTint bmWint bmHint bmWidthBBYTE bmPBYTE bmBitsPLPVOID bmB} BITMAP;作好这些准备工作之后。调用DC对象的BitBlt函数把位图从内存DC当中贴到绘图DC当中来。前四个参数指示了位图在目的DC上的位置和大小。第五个参数是原来保存位图的内存DC的地址。接下来的两个参数是从位图的哪一点开始进行拷。最后这个参数设置该位图和屏幕上当前内容的相互关系,SRCCOPY的意思是拷贝过来覆盖原来的内容。这个参数还有其他的许多选择比方说取反操作或者异或操作,设置不同的参数可以获得丰富的效果。下面介绍如何输出文本。首先对DC对象进行设置:dc.SetBkMode(TRANSPARENT);dc.SetTextColor(RGB(0 , 155 , nowX*nowX));这里把文本输出的背景置为透明,然后设置输出文本的前景颜色。 下面这段程序的意思是为将要输出的文本选择字体。LOGFONTmemset(&logfont, 0, sizeof(logfont));logfont.lfWeight = 50;logfont.lfHeight = 50;lstrcpy(logfont.lfFaceName, &黑体&);nowFont.CreateFontIndirect(&logfont);dc.SelectObject(&nowFont); 首先声一个WIN32提供的字体信息结构。为其分配内存空间。再按照我们的要求填写这个字体结构,设置字体的宽度,设置字体的高度,选择字体种类,根据这个结构生成一个CFONT字体对象,让DC对象选中这个字体对象,最后使用DC的文本输出函数来输出一个字符串。总而言之,进行屏幕输出的规则如下:第一 必须通过CDC对象进行屏幕输出;第二 设置DC对象的输出属性;第三 选择绘图工具第四 用CDC对象的绘图函数。有关屏幕输出的内容就介绍到这里。2.4 有关屏幕映射方式在一般的情况之下,我们都以像素作为绘图的单位,我们称之为设备坐标。我们在进行绘图操作的时候,不可避免的要用到设备坐标系。WINDOWS提供了几种映射方式,或称坐标系。可以通过它们来和设备上下文相联系。比方说不管是什么样的显示设备,如果我们需要在上面显示一个2英寸高,2英寸宽的矩形,该怎样处理呢?这就要依赖于我们所设定的坐标系。如果我们指定了MM_TEXT 方式,这时坐标原点就位于屏幕的左上角,X轴和Y轴的方向分别指向我们面对屏幕的右方和下方,它的绘图单位是像素,如果一英寸对应72个像素的话,我们就需要这样绘制这个矩形:DC.Rectangle(CRect( 0,0,72*2,72*2));所以我们如果我们指定了MM_LOENGLISH 方式,那么一个绘图单位就是百分之一英寸,坐标原点仍然位于屏幕的左上角,但是X轴和Y轴的方向恰好和MM_TEXT方式下的轴方向相反,同样完成绘制上面提到的矩形的工作,我们就需要写出这样的代码:DC.Rectangle(CRect(0,0,200,_200));可见,坐标系的选择对我们编写程序有很大的影响。此外,在有些时候,我们需要在几个不同的坐标系下面工作,那么还需要在进行在这些坐标系之间的转换工作。所以 ,我们有必要在这里详细介绍以下WINDOWS的坐标映射方法。一般来说,最常用的就是WM_TEXT方式。在WM_TEXT坐标方式下面,坐标被映射到了像素,X的值向右方递增,Y的值向下递增,并且可以通过调用CDC的SetViewpotOrg函数来改变坐标原点。下面的代码把屏幕映射方式设为MM_TEXT方式,并且把坐标原点设在(300,300)处:DC.SetMapMode(MM_TEXT);DC.SetViewportOrg(CPoint(300,300));另外,WINDOWS提供了一组非常重要的比例固定的映射方式,在这些映射方式下面,我们可以改变它的坐标原点,却无法改变它的比例因子。对于MM_LOENGLISH映射方式,我们已经知道它的X值是向右递减的,Y的值是向下递减的,所有的固定比例的映射方式都遵循这一原则。它们的比例因子也各不相同。我们列表如下:映射方式逻辑单位MM_LOENGLISH0.01英寸MM_HIENGLISH0.001英寸MM_LOMETRIC0.1毫米MM_HIMETRIC0.01毫米MM_TWIPS1/1440英寸最后一种映射方式MM_TWIPS常常用语打印机,一个&twip&单位相当于1/20个点(一点近似与1/72)英寸。例如,如果指定的MM_TWIPS一个社单位,那么对于12点大小的字模来说,字符的高度为12x20,即240个twip。除了固定比例的映射方式,WINDOWS还提供了比例可变的映射方式,在这种映射方式下面,我们除了可以改变它们比例因子之外还可以改变比例因子。借助于这样的映射方式,当用户改变窗口的尺寸的时候,绘制的图形的大小也可以根据比例发生相应的变化;同样,当我们翻转某个轴的时候,他们所绘制的图像,也以另外的一个轴为轴心进行翻转。这样的映射方式有两种:MM_ISOTROPIC和 MM_ANIOTROPIC。在MM_ISOTROPIC方式下,纵横的比例为1:1,换句话说,无论比例因子如何变化,我们画出的图形不会改变自己的形状。但是在MM_ANIOSTROPIC方式下面,X和Y的比例因子可以独立地变化,图形的形状可以发生变化。我们分析下面这段程序:void CAView::OnDraw(CDC *pDC){&& CRect clientDC;&& GetClientRect(clientRect);&& pDC_&SetMapMode(MM_ANISOTROPIC);&& pDC_&SetWindowExt();&& pDC_&SetViewportExt(clientRect.right,_clientRect.bottom);&& pDC_&SetViewportOrg(clientRect.right/2, clientRect.bottom/2);&& pDC_&Ellipse(CRect(_500, _500, 500, 500));}这段代码的功能是这样的,首先取得窗口客户区矩形的大小,然后用SetWindowExt和SetViewportExt函数设定比例,结果窗口尺寸的大小被设为1000个逻辑单位高和1000个逻辑单位宽,坐标原点被设为窗口的中心,在这样的设置之下,绘制出一个半径为500个逻辑单位的椭圆。在这里如果将映射方式改变为MM_ISOTROPIC那么就将画出一个圆。圆的直径是窗口举行宽和高的最小值。下面我们给出逻辑单位到设备单位的公式:X比例因子 = X视口范围/X窗口范围Y比例因子 = Y视口范围/Y窗口范围设备X = 逻辑X *X比例因子 + X坐标原点偏移设备Y = 逻辑Y *Y比例因子 + Y坐标原点偏移当我们设定了设备上下文的映射方式之后,就可以直接使用逻辑坐标作为其参数了,但是从WM_MOUSEMOVE消息所获得的鼠标的坐标值是设备坐标。许多其他的MFC库函数,尤其是类CRect的成员函数,只接受设备坐标。所以我们有时要利用CDC的LPtoDP和DPtoLP在逻辑坐标和设备坐标之间进行转换的工作。下面我们列出进行坐标映射工作的时候所要遵循的一些规则:可以认为CDC的所有成员函数都以逻辑坐标作为参数,但和CRect有关的函数例外。 可以认为CWnd 的成员函数都以设备坐标作为参数。 所有的HIT_TEST操作都应该考虑设备坐标。 以逻辑坐标的形式来保存数据,否则用户对窗口进行滚动操作的时候,这个数据就不再有效了。  3 文件处理 几乎所有的软件都需要将程序当中的信息保存在磁盘存储器上面。这些信息可能是程序运行的初始化数据,或者是程序中计算得到的结果,还可能是程序经常用到的资料。从磁盘存储器上存取数据的工作往往是通过文件操作或者数据库操作来完成的。关于数据库操作的内容,我们将在后面的章节当中进行详细的介绍,在下面的内容中,我们主要讨论VC如何实现一般意义上的数据存取工作。VC是面向对象的开发平台,在使用MFC编写的程序中,我们定义和生成了各种各样的对象,通过它们之间的协同工作完成程序的功能。所以在MFC中,程序的存取工作的核心内容就是如何实现这些对象的持续化。一个可以实现持续化的对象知道如何保存和载入它们自己的数据。比方说,在程序使用MFC文档/视图结构的时候,如果为文档对象提供正确的持续化功能,它将在您需要的时候自动地保存和恢复自己的数据,并且保持用户最新的修改结果。需要注意的是对象的持续化同样是将数据保存到磁盘文件中去,它的好处在于MFC对二进制文件的存取过程进行了封装,使用来实现对象存取的程序代码得到了简化。当然,如果您更喜欢直接操作文件进行数据的存取工作,MFC也提供了更为直接的渠道。CFile这个类封装了所有对文件的常用操作,使用CFile对象处理文件会比使用API函数简单得多。 3.1 对象持续化简述在MFC当中,对象的持续化功能主要是通过文档/视图结构当中文档对象的序列化机制来实现的。下面,我们将详细介绍如何使用序列化机制来实现对象的持续化。序列化,简单地说就是向一个持久性的存储媒体&&如磁盘文件保存对象或读取对象的过程。可以实现序列化的类&&即从CObject继承而来的类,有一个叫做Serialize的成员函数,序列化工作主要是在这个函数当中进行的。我们使用一张示意图来说明序列化的原理。 MFC使用一个类型为CArchive的归档对象充当磁盘文件与程序中的对象的中介。归档对象总是与一个 CFile对象相关联,从中获得一些进行序列化所需要的信息,比如说文件名,以及存取标志等。对象的持续化的主要工作就是将自己的成员变量或者当前状态保存起来。我们可以使用经过重载的流入和流出操作符直接向归档对象中保存或者取出变量成员的值,而将这些数据保存到磁盘文件中的工作由CArchive对象指示CFile对象来完成。当用户在打开或保存拥有文档对象数据的文件或者使用文档对象的Open 、Save、Save As菜单命令时,MFC便会自动调用Serialize函数。使类实现序列化,需要五个步骤:1、从Cobject 类或其派生类派生用户类;2、在类声明中使用DECLARE_SERIAL宏;3、重载Serialize 函数;4、定义不用变量的构造函数;5、在类实现文件中使用宏IMPLEMENT_SERIAL。在下一节中,我们将以一个具体的实例来详细说明如何实现类的序列化。  3.2 实例分析在描述了类序列化的基本原理之后,让我们来看一个具体的序列化实例DrawLine。启动这个实例。图3.9在这个实例中,我们将完成对直线的简单绘制。在视图按下鼠标左键,然后拖动鼠标到一个新位置松开鼠标,程序就画出一条直线。再来看看这个程序的基本构成。 除了基本的文档类和视类之外,我们还有一个直线类CLine,它有四个int成员变量m_x1,m_y1, m_x2,m_y2,用来记录直线两个端点的X轴和Y轴方向坐标,此外有一个Draw成员函数,Draw是根据直线的以上四个成员变量,在视图客户区中绘出直线。在WorkSpace的ClassView中双击类CLine的Draw函数,则可以看到Draw的实现。void CLine::Draw (CDC *PDC){PDC_&MoveTo (m_x1, m_y1);PDC_&LineTo (m_x2, m_y2);}我们对视类CDrawLineView的OnLButtonDown,OnMouseMove,OnLButtonUp 三条消息进行了处理,在WorkSpace的ClassView中同样可看到它们的实现。此外我们在文档类CDrawLineDoc中有一个成员变量m_LineArray,它是用来记录我们在视图客户区所画的直线。函数GetLine是根据索引取得m_LineArray中的一条直线,GetNumLines则是取得直线的总数的。在了解了基本的程序结构之后,下面对直线对象进行序列化处理。1 从Object 类中派生并使用宏DECLARE_SERIAL打开定义CLine这个类的头文件line.h,可以看到这个类是从CObject 类派生出来的。要对CLine类实现序列化,需要在类的声明中加入宏DECLARE_SERIAL的调用,并在类的实现文件中,加入宏IMPLEMENT_SERIAL 的调用。CObject 类拥有基本的序列化功能,通过对此类的继承实现可以获得这些功能,此外一个无参数的构造函数是不可缺少的。我们打开Line.h后,在CLine类定义中第一句就可以是DECLARE__SERIAL(CLine),这个宏不需要加分号。2 重载Serialize 成员函数我们要实现序列化,先对其进行改造,在WorkSpace的ClassView中选择CLine类,单击鼠标右键,选择Add Member Function增加一个成员函数:图3.10VC将会跳出如3.11所示下添加函数的对话框:  图3.11在Function Type输入void,在Function Declaretion输入 Serialize(CArchive& ar),然后选择Virtual,按OK即可。然后在ClassView中可以看到这个函数。下面我们编辑这个函数,双击WorkSpace显示的CLine类的Serialize函数,则转到Line.cpp中其实现处。这个函数的实现如下:void CLine::Serialize(CArchive & ar){CObject::Serialize(ar); if (ar.IsStoring()){ar&&m_x1&&m_y1;ar&&m_x2&&m_y2;}else{ar&&m_x1&&m_y1;ar&&m_x2&&m_y2;} }首先调用基类的Serialize函数,CObject::Serialize(ar);然后判断是保存数据还是载入数据,然后再根据判断的结果进行实际的存取工作。这里ar就是框架程序传递给序列化函数的归档对象指针。当调用完基类的序列化函数后,判断ar的状态,当ar.IsStoring()返回真时,这时进行数据保存;当ar.IsStoring()返回非真时,这时CArchive 对象要求读取数据。3 使用操作符&& 存取数据 在上面的代码中我们用到了&&和&&,在这里对它们作一个介绍,&& 和&&是一种操作符,用来指示向CArchive对象读取还是保存数据,必要时我们可以重载重定向符。如ar&& m_x1&&m_y1;这一句,其中&&表示从ar中读出数据m_x1,m_y2,这个符号及&&可以连用,亦可以分开来用,如ar&&m_x1;ar&&m_y1;同样ar&&m_x1&&m_y1;中的& &是把数据存入ar中。4 文档对象序列化在对直线对象进行序列化函数处理之后,接下来,我们对文档类进行改写,首先实现文档类的序列化函数。双击WorkSpace中CDrawLineDoc类Serialize函数打开它,文档类的Serialize函数是个虚拟成员函数的,其缺省实现是不做任何工作的。我们再来看文档类的序列化函数,CDrawLineDoc类的序列化函数主要是对CLine类对象的序列化函数的调用,其大体情况是这样的:当进行保存时,我们先得到直线的总数&&调用GetNumLines函数,然后用一个循环对每一条直线对象调用序列化函数;当进行读取数据时,亦先得到直线总条数&&从文件中读出,然后同样用一个循环,每次读出一条直线&&调用对象的序列化函数,然后把它加入到文档类的成员变量m_LineArray中去,直到直线读完。这个函数的源代码如下:void CDrawLineDoc::Serialize(CArchive& ar){& && int linenum=GetNumLines();&& if (ar.IsStoring())&& {&&&&& ar &&&&&&& for(int i=0;i&i++)&&&&&&& m_LineArray.GetAt(i)_&Serialize(ar);&& }&& else&& {&&&&& m_LineArray.RemoveAll();&&&&& ar &&&&&&& CLine L&&&&& for(int i=0;i&i++)&&&&& {&&&&&&& CLine *PLine = new CLine ();&&&&&&& PLine_&Serialize(ar);&&&&&&& m_LineArray.Add (PLine); &&&&& }&&&&& UpdateAllViews(NULL);&& }}5 连接视图接着,我们对文档类和视类进行一些必须的处理:在ClassWizard 中,对CDrawLineDoc增加两个成员函数:OnNewDocument和OnOpenDocument。对OnNewDocument 的调用是当新生成一个文档时,所以此时需要处理文档类的成员变量m_LineArray,我们将它里面的直线全部除去,同时调用UpdateAllViews(NULL),更新视图,我们对这个函数的代码作如下改动:BOOL CDrawLineDoc::OnNewDocument(){&& if (!CDocument::OnNewDocument())&&&&& return FALSE;&&& // TODO: add reinitialization code here&&& // (SDI documents will reuse this document)&& m_LineArray.RemoveAll();&& UpdateAllViews(NULL);&& return TRUE;}框架程序对OnOpenDocument的调用是在我们打开文件时,所以这个时候,在我们读取数据之前,同样要清除m_LineArray所有的直线,我们对这个函数的代码作如下改动:BOOL CDrawLineDoc::OnOpenDocument(LPCTSTR lpszPathName) {&& m_LineArray.RemoveAll();&& UpdateAllViews(NULL);&& if (!CDocument::OnOpenDocument(lpszPathName))&&&&& return FALSE; &&& // TODO: Add your specialized creation code here&& && return TRUE;}我们还需接着对视类的OnDraw进行改写,让它可以实现重画,在这里我们只是简单地对每一个直线类对象调用Draw成员函数,对每一条直线进行重画,我们对这个函数的代码作如下改动:void CDrawLineView::OnDraw(CDC* pDC){&&& CDrawLineDoc* pDoc = GetDocument();&& ASSERT_VALID(pDoc); && // TODO: add draw code for native data here&& for (int i = 0; i&pDoc_&GetNumLines(); i++)&& {&&&&& (pDoc_&GetLine(i)) _&Draw(pDC);&& }}至此我们对程序的序列化已经完成,对程序编译运行即可。&&& 3.3 与文件处理关系密切的类CFileCFile类用来处理正常文件的I/O操作,它直接供无缓冲的、二进制磁盘输入/输出服务,并且通过其派生类间接支持文本文件和内存文件。因为CFile类是基本上封装在CArchive类之中了,所以我们只对这个类作简单介绍。CFile类有三个构造函数,其原型如图所示。virtual BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL )其中:hFile为一个已打开文件的句柄。LpszFileName指定所想要文件的路径的字符串。路径可以是相对的或绝对的。NOpenFlags指共享和存取方式,对于这个标志的说明,我们留到后面专门说明。CFile类用Open来创建和打开文件。使用Open创建新文件,必须有一个文件名,并且选择一定的打开方式:CFile对磁盘文件的定点读和写是通过函数Read ,Write和Seek进行的。 virtual UINT Read( void* lpBuf, UINT nCount ); virtual void Write( const void* lpBuf, UINT nCount ); virtual LONG Seek( LONG lOff, UINT nFrom );函数Read:返回传输给缓冲区的字节数。如果读到文件尾,返回值可能小于nCount 值。LpBuf:指向用户定义的缓冲区的指针,用来接收数据;nCount:要从文件中读出的最大字节数;函数Write:写缓冲区数据到文件中;Seek用于定位文件指针位置,如果所请求的位置合法,则返回距离文件头的新字节偏移。文件的打开和关闭是相对的,打开一个文件之后,必须把它关闭,文件的关闭是相当简单的,用CFile对象调用Close函数即可。下面描述文件共享和存取标志。下列标志指定打开文件时可执行的动作。可以用OR来组合下面所列的选项。一个存取许可和一个共享选项是必需的;modeCreate 和modeNoInberit方式是可选的。modeCreate指示构造函数创建一个新文件,如果该文件已存在,则该文件截短为0。 modeRead 打开文件用于读。 modeReadWrite 打开文件用于读写。 modeWrite 打开文件用于只写。 modeNoInberit 阻止文件被子进程继承。 shareDenyNone 打开文件,不允许其它进程读或写该文件。如果该文件已由其它任何进程用兼容方式打开,则Create将失败。 shareDenyWrite 打开文件,不允许其它进程写该文件。如果该文件已由其它任何进程用兼容方式或写方式打开,则Create将失败。 shareDenyRead 打开文件,不允许其它进程读该文件。如果该文件已由其它任何进程用兼容方式或读方式打开,则Create将失败。 shareExclusive 以独占方式打开文件,不允许其它进程写该文件。如果该文件已用其它读或写方式打开,即使是当前进程打开,则构造也将失败。 shareCompat 以兼容方式打开文件,允许给定机器上任何进程打开该文件任意次。如果该文件已用其它任何共享方式打开,构造将失败。 typeText 设置文本方式,对回车换行进行特殊处理,它只用于派生类。 typeBinary 设置二进制方式,它只用于派生类。  4 DAO技术4.1 DAO与ODBC在WINDOWS环境下进行数据库访问工作您有两种选择:使用DAO技术或者使用ODBC技术。ODBC(OPEN DATABASE CONNECTIVITY)即开放式数据库互联,作为WINDOWS开放栍准结构的一个重要部分已经为很多的WINDOWS程序员所熟悉。DAO(DATA ACCESS OBJECTS)即数据访问对象集(DATA ACCESS OBJECTS)是MICROSOFT提供的基于一个数据库对象集合的访问技术。它们都是WINDOWS API的一个部分,可以独立于DBMS进行数据库访问。那么ODBC和DAO的区别在哪里呢?ODBC和DAO访问数据库的机制是完全不同的。ODBC的工作依赖于数据库制造商提供的驱动程序,使用ODBC API的时候,WINDOWS的ODBC管理程序,把数据库访问的请求传递给正确的驱动程序,驱动程序再使用SQL语句指示DBMS完成数据库访问工作。DAO则绕开了中间环节,直接使用MICROSOFT提供的数据库引擎(MICROSOFT JET DATABASE ENGINE)提供的数据库访问对象集进行工作。速度比ODBC快。数据库引擎目前已经达到了3.0版本。它是DAO、MS ACCESS、MS VISUAL BASIC等等WINDOWS应用进行数据库访问的基础。引擎本身的数据库格式为MDB,也支持对目前流行的绝大多数数据库格式的访问,当然MDB是数据库引擎中效率最高的数据库。&&& 如果您使用客户机/服务器模型的话,建议您使用ODBC方案;如果您希望采用MDB格式的数据库,或者利用数据库引擎的速度,那么DAO是更好的选择。4.2 使用MFC实现DAO技术MFC对所有的DAO对象都进行了封装。使用MFC进行DAO编程,首先要为每一个打开的数据库文件提供一个数据库对象──CDaoDatabase,由这个对象管理数据库的连接。然生成记录集对象──CDaoRecordset,通过它来进行查询、操作、更新等等的工作。如果需要在程序中管理数据库的结构,则需要使用DAO当中的表结构信息对象CDaoTableInfo及字段定义对象 CDaoFieldInfo来进行获得或者改变数据库表结构的工作。CDaoDatabase、CDaoRecordset、 CDaoTableDefInfo、CDaoFieldInfo是使用MFC进行DAO编程的最基本也是最常用的类。下面,我们通过一个实例来介绍如何使用MFC的DAO类来进行数据库访问的工作。在这个实例当中,我们将在程序当中建立一个学生档案管理数据库,并通过对话框来添加、删除和浏览记录。我们首先看以下程序运行的情况。我们将针对程序的功能依次介绍如何生成和使用数据库对象、记录集对象以及如何通过记录集来操纵数据库。我们将通过解释对数据库进行操作的源程序来介绍如何用MFC来实现DAO技术。下面介绍如何建库:首先新建一个数据库对象。newDatabase = new CDaoDnewDatabase_&Create(_T(&stdfile.mdb&), dbLangGeneral, dbVersion30);利用数据库引擎在磁盘上建立一个MDB格式的数据库文件。stdfile.mdb是在磁盘上面建立的数据库文件的名字,dbLangGeneral 是语言选项。dbVersion30这是数据库引擎版本选项。图3.12然后新建一个数据库表定义信息对象。CDaoTableDef *TableITableInfo = new CDaoTableDef(newDatabase);TableInfo_&Create(_T(&student&));新建一个字段定义信息对象。按要求填写字段定义信息对象。定义字段名称:FieldInfo_&m_strName = CString(&studentName&);定义字段类型:FieldInfo_&m_nType = dbT定义字段所占的字节数大小:FieldInfo_&m_lSize = 10;定义字段特性:FieldInfo_&m_lAttributes = dbVariableField | dbUpdatableFdbVariableField参数的意思是该字段的所占的字节数是可变的。& dbUpdatableField参数的意思是该字段的值是可变的。根据字段定义对象在数据库表对象当中生成字段。TableInfo_&CreateField(*FieldInfo);在生成了所有的字段之后,将新的数据库表的定义填加到数据库对象当中去。TableInfo_&Append();下面介绍如何进行数据库操作:首先生成记录集对象:Recordset = new CDaoRecordset(newDatabase);然后使用SQL语句打开记录集对象。首先把SQL语句记入一个字符串:CString strQuery = _T(&Select * from student&);使用这个字符串打开记录集。Recordset_&Open(dbOpenDynaset , strQuery);&& dbOpenDynaset参数的意思是表示记录集打开的类型。dbOpenDynaset的意思是打开一个可以双向滚动的动态记录集。这个记录集中的记录是使用我们定义的SQL语句对数据库进行查询得到的。这个参数还有另外的两种选择:dbOpenTable参数指示打开一个数据表类型的记录集,使用这种类型的记录集只能对单一的数据库中的记录进行操纵。如果使用dbOpenSnapshot参数表示打开的是映像记录集,它实际上是所选择的记录集的一个静态的拷贝,在只需要进行查询操作或者希望制作报表的时候,使用这种记录集比较合适,它不会对数据库中的数据进行修改。接下来对记录集当中的一个标志位赋值,说明是否要求自动地标记出CACHE当中经改变的记录。使用记录集的时候是DAO把被检索出的记录读入CACHE,所有的操纵都是针对CACHE中的记录进行的,要实现对数据库当中的记录更新必须把CACHE记录中被改变的字段的值写回到数据库文件当中去。这个标志位的作用就是当CACHE中的数据改变的时候,是否需要自动的标记出记录中那些应该被写回的字段。下面介绍如何填加一个记录。 m_Recordset _&AddNew();m_Recordset_&Update();使用AddNew()这个函数可以在数据表记录集或者是动态记录集当中添加新的记录,调用AddNew() 之后必须接着调用Update()来确认这个添加动作,将新的记录保存到数据库文件当中去。新的记录在数据库当中的位置取决于当前记录集的类型:如果是动态记录集,新记录都将被插入到记录集的末尾。如果是数据表记录集的话,当数据库表中定义了主键的时候新记录将按照库表的排序规则插入到合适的地方;如果没有定义主键那么新记录也会被插入到记录集的末尾。用AddNew()会改变记录集的当前记录。只有将当前记录定位在新记录上,才能填写它的数据。所以我们使用MoveLast函数使刚刚添加的记录成为当前记录,然后调用Edit函数对新记录进行编辑。m_Recordset_&MoveLast();m_Recordset_&Edit();依次给新记录的字段进行赋值:COleVariant&& var1(m_Name , VT_BSTRT);m_Recordset_&SetFieldValue(_T(&studentName&) , var1);COleVariant&& var2(m_ID , VT_BSTRT);m_Recordset_&SetFieldValue(_T(&studentID&) , var2);COleVariant&& var3(m_Class , VT_BSTRT);m_Recordset_&SetFieldValue(_T(&studentClass&) , var3);COleVariant&& var4(m_SID , VT_BSTRT);m_Recordset_&SetFieldValue(_T(&studentSID&) , var4);COleVariant 这个类封装了WIN32提供的VARIANT这个结构以及对它的操作。这个类当中可以存储多种类型的数据。需要注意的是这种包容能力是通过C语言当中的UNION提供的,就是说一个COleVariant 对象只能保存一种类型的数据。我们先把字段的值装入OLE变体对象,再使用这个变体对象对记录中的字段进行赋值。VT_BSTRT参数的作用是在生成OLE变体对象的时候指示将要封入的数据的类型为字符串。当对所有的字段都结束赋值后,调用Update 函数来保存刚才的修改。m_Recordset_&Update(); 注意,在调用Update函数之前,如果进行了改变当前记录的操作,那么前面进行的所有的赋值工作都将丢失,而且不会给出任何的警告。这段代码从记录集中取出一个记录的值,这里同样要用到OLE变体对象}

我要回帖

更多关于 matlab打印输出 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信