Professional Documents
Culture Documents
Visual C++编程与项目开发
Visual C++编程与项目开发
s lC++ 编程与项目开发
ua
李 英 编著
图书在版编目(
CIP数据
Vis lC++编程与项目开发/李英编著.—上海:华东理
ua
工大学出版社, 2008.1
ISBN978 7 5628 2204 2
Ⅰ.Vis
ual.
.. Ⅱ.李... Ⅲ.C 语 言 程序设计
Ⅳ.
TP312
V
isu
alC++编程与项目开发
·······················································································
编 著/李 英
责任编辑 / 李国平 纪冬梅
责任校对 / 金慧娟 张 波
封面设计 / 戚亮轩
出版发行 / 华东理工大学出版社
社 址:上海市梅陇路130号, 200237
电 话:( 64250306(营销部)
021)
传 真:
(021)
64252707
网 址:
www. hd
lgpres
s.c
om.
cn
印 刷 / 江苏通州市印刷总厂有限公司
开 本 /787mm×1092mm 1/16
印 张 /26
字 数 /646千字
版 次 /2008年1月第1版
印 次 /2008年1月第1次
印 数 /1—3050册
(本书如有印装质量问题,请到出版社营销部调换。)
内 容 简 介
随着计算机可视化技术的发展,可视化编程技术已广泛应用于教育、科研、工程和金融
等领域,越来越多的人员开始研究并应用可视化编程技术。 Visual C+ + 是 Microsoft 公司
目前极为流行的可视化软件开发工具之一 。
目前有许多介绍 Visual C+ + 的书,有的以介绍基础知识为主,有的以项目的开发为主。
以介绍基础知识为主的书,容易偏向于知识点的简单罗列。 以项目开发为主的书以一个项
目的开发过程来讲述,如果读者某一个环节没有掌握好,那么对这本书后面的内容就难以掌
握,并且在程序编制过程中一个项目贯穿始终,由于重要概念和内容没有单独提出,读者难
以读懂代码,很难掌握。
本书根据作者多年的本科教学和研究生教学经验以及实际开发经验写成,围绕项目开
发所需的基本知识点将复杂的概念用通俗易懂的语言和配以实例的方式来说明,最后综合
运用编程基础知识和软件项目开发知识给出项目开发的实例 。 本书力求深入浅出,重点和
全面相统一。书中列举的大量实例是经过精心编制而成的,在实例说明中采用了较为独特
的说明方式:先将实例程序要实现的功能和界面表示出来,然后给出每个实例详细的开发制
作步骤和源代码;读者根据实例说明的步骤都可方便地将实例制作出来(在教学中已验证)。
本书主要讲述 C+ + 语言基础、VC+ + 编程技术、软件项目开发过程和开发实例。 全书共包
括十七章:第一章软件开发环境与软件项目开发过程,第二章 C+ + 语言基础,第三章 Windows
应用程序编程与 MFC,第四章文档/视图结构及其编程,第五章程序界面设计,第六章对话框
与控件,第七章绘图,第八章文件操作,第九章打印,第十章异常处理,第十一章数据库编程,
第十二章动态链接库,第十三章 ActiveX 控件,第十四章多媒体技术,第十五章多进程与多
线程编程,第十六章网络通信编程,第十七章项目开发实例—学生管理信息系统。 张春亮同
志参与本书第十七章的编写和第十七章例题的制作 。在本书的编写过程中得到顾春华等同
志的帮助,在此深表感谢。
由于笔者水平有限,本书不足之处在所难免,敬请读者批评指正。
编 者
2007 年 11 月
目 录
1
6 Vi s
ualC++编程规范 …………………………………………………………… 24
1
61 基本要求 ……………………………………………………………………… 24
1
62 命名 …………………………………………………………………………… 24
1
63 注释与可读性 ………………………………………………………………… 25
1
64 结构化要求 …………………………………………………………………… 25
第二章 C++语言基础 ……………………………………………………………… 26
1 简单的 C++程序和 C++语言的特点 ………………………………………… 26
2
2
11 简单的 C++程序 …………………………………………………………… 26
2
12 C++语言的基本特点 ……………………………………………………… 27
2 数据类型、变量和运算符 …………………………………………………………… 28
2
2
21 基本数据类型 ………………………………………………………………… 28
2
22 加修饰符的基本数据类型 …………………………………………………… 29
2
23 变量 …………………………………………………………………………… 29
2
24 数组 …………………………………………………………………………… 30
2
25 结构 …………………………………………………………………………… 32
2
26 枚举 …………………………………………………………………………… 33
2
27 联合 …………………………………………………………………………… 33
2
28 指针 …………………………………………………………………………… 34
2
29 类型定义 ……………………………………………………………………… 35
2
210 运算符 ………………………………………………………………………… 35
3 流程控制语句 ……………………………………………………………………… 38
2
2
31 表达式语句和块语句 ………………………………………………………… 38
2
32 选择语句 ……………………………………………………………………… 38
2
33 swi
tch分支语句 ……………………………………………………………… 39
2
34 循环语句 ……………………………………………………………………… 40
2
35 转移语句 ……………………………………………………………………… 40
4 函数 ………………………………………………………………………………… 41
2
2
41 函数定义 ……………………………………………………………………… 41
2
42 函数的参数传递 ……………………………………………………………… 41
2
43 局部变量和静态变量 ………………………………………………………… 42
2
44 内联函数 ……………………………………………………………………… 43
2
45 函数重载 ……………………………………………………………………… 43
2
46 函数模板 ……………………………………………………………………… 44
2
47 多态性和虚函数 ……………………………………………………………… 44
5 类和对象 …………………………………………………………………………… 46
2
2
51 类的定义和声明 ……………………………………………………………… 46
2
52 对象 …………………………………………………………………………… 47
2
53 构造函数和析构函数 ………………………………………………………… 48
2
54 继承和派生 …………………………………………………………………… 48
2 2
55 t
his指针 ……………………………………………………………………… 50
目 录
……………………………………………………………………………………………………………………………………………
6 常类型(
2 con
st ……………………………………………………………………… 50
2
61 常引用 ………………………………………………………………………… 50
2
62 常对象 ………………………………………………………………………… 51
2
63 常成员函数 …………………………………………………………………… 52
2
64 常数据成员 …………………………………………………………………… 52
7 运算符重载 ………………………………………………………………………… 53
2
2
8 I /O输入/输出)流结构 …………………………………………………………… 55
9 异常处理 …………………………………………………………………………… 56
2
2
91 异常处理的语法 ……………………………………………………………… 56
2
92 异常处理的执行过程 ………………………………………………………… 57
第三章 Wi
ndows应用程序编程与 MFC ………………………………………… 59
1 MFC 类库 …………………………………………………………………………… 59
3
3
11 MFC 基础类库 ………………………………………………………………… 60
3
12 COb
jet类 …………………………………………………………………… 60
c
2 利用 MFC 创建 Wi
3 ndows应用程序框架 ………………………………………… 64
3 程序中的文件和主要类 …………………………………………………………… 70
3
3
31 程序中的文件和主要类 ……………………………………………………… 70
3
32 应用程序类( CMyPain
terApp类)…………………………………………… 70
3
33 程序的其他类 ………………………………………………………………… 74
4 消息和消息处理 …………………………………………………………………… 75
3
3
41 消息的分类 …………………………………………………………………… 76
3
42 消息映射 ……………………………………………………………………… 77
3
43 消息处理函数 ………………………………………………………………… 79
3
44 消息传递 ……………………………………………………………………… 83
第四章 文档/视图结构及其编程 …………………………………………………… 85
1 文档 ………………………………………………………………………………… 85
4
4
11 使用文档管理数据的一般步骤 ……………………………………………… 85
4
12 文档类(
CDo
c t类)中的主要数据成员和成员函数 …………………… 85
umen
4
13 多文档类型 …………………………………………………………………… 87
2 视图 ………………………………………………………………………………… 87
4
4
21 视图操作的一般步骤 ………………………………………………………… 87
4
22 视图类( ew 类)中的主要成员函数 ……………………………………… 87
CVi
4
23 多视图 ………………………………………………………………………… 88
4
24 派生的视图类 ………………………………………………………………… 88
3 框架(边框窗口)类 ………………………………………………………………… 89
4
4 文档模板类 ………………………………………………………………………… 90
4
5 文档/视图结构各对象之间的关系 ………………………………………………… 90
4
6 文档/视图结构编程实例 …………………………………………………………… 91
4
4
61 单文档应用程序实例 ………………………………………………………… 91
4
62 多文档应用程序实例 ………………………………………………………… 92 3
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
4
63 多视图应用程序实例 ………………………………………………………… 94
4
64 文档与视图结构之间相互作用关系分析实例 ……………………………… 99
第五章 程序界面设计 ………………………………………………………………… 100
1 界面设计原则 ……………………………………………………………………… 100
5
5
11 界面布局原则 ………………………………………………………………… 100
5
12 用户帮助模型 ………………………………………………………………… 102
2 菜单 ………………………………………………………………………………… 102
5
5
21 菜单资源编辑器 ……………………………………………………………… 102
5
22 CMenu类 ……………………………………………………………………… 103
5
23 CCmdUI类 …………………………………………………………………… 104
5
24 标准菜单编程实例 …………………………………………………………… 105
5
25 带有图标的菜单编程实例 …………………………………………………… 107
5
26 快捷菜单(上下文菜单,右键菜单)编程实例 ………………………………… 108
5
27 动态菜单编程实例 …………………………………………………………… 109
3 工具栏 ……………………………………………………………………………… 111
5
5
31 工具栏资源编辑器 …………………………………………………………… 111
5
32 CToo
l r类 …………………………………………………………………… 112
Ba
5
33 常规工具栏编程实例 ………………………………………………………… 113
5
34 下拉式工具栏按钮编程实例 ………………………………………………… 115
5
4 CReBar和 CD
ial r …………………………………………………………… 117
ogBa
5
41 CReBar类 ……………………………………………………………………… 117
5
42 CDi
al r类 ………………………………………………………………… 118
ogBa
5
43 编程实例 ……………………………………………………………………… 118
5 状态栏 ……………………………………………………………………………… 121
5
5
51 CS
tat
u r类 ………………………………………………………………… 121
sBa
5
52 状态栏的创建 ………………………………………………………………… 121
5
53 编程实例 ……………………………………………………………………… 122
第六章 对话框与控件 ………………………………………………………………… 125
1 对话框基本知识 …………………………………………………………………… 125
6
6
11 对话框的组成 ………………………………………………………………… 125
6
12 对话框的类型 ………………………………………………………………… 125
6
13 编写对话框程序的流程 ……………………………………………………… 126
2 消息对话框 ………………………………………………………………………… 126
6
6
21 消息对话框函数 ……………………………………………………………… 126
6
22 消息对话框编程实例 ………………………………………………………… 127
3 对话框资源编辑器 ………………………………………………………………… 128
6
4 控件 ………………………………………………………………………………… 128
6
5 对话框类与对话框调用 …………………………………………………………… 131
6
6
51 对话框类 ……………………………………………………………………… 131
4 6
52 对话框调用及其编程实例 …………………………………………………… 131
目 录
……………………………………………………………………………………………………………………………………………
7
45 绘矩形 ………………………………………………………………………… 184
7
46 绘椭圆 ………………………………………………………………………… 184
7
47 绘弧线 ………………………………………………………………………… 185
7
48 绘位图 ………………………………………………………………………… 185
5 绘图编程实例 ……………………………………………………………………… 186
7
7
51 GDI对象和基本绘图函数的应用编程实例 ………………………………… 186
7
52 鼠标绘图编程实例 …………………………………………………………… 192
7
53 动态绘图编程实例 …………………………………………………………… 202
第八章 文件操作 ……………………………………………………………………… 207
8
1 CF i
le类 ……………………………………………………………………………… 207
8
11 打开文件 ……………………………………………………………………… 208
8
12 关闭文件 ……………………………………………………………………… 209
8
13 文件读写 ……………………………………………………………………… 209
8
14 文件定位 ……………………………………………………………………… 209
2 文件流f
8 st
ream …………………………………………………………………… 210
8
21 打开文件 ……………………………………………………………………… 210
8
22 关闭文件 ……………………………………………………………………… 211
8
23 编程实例 ……………………………………………………………………… 211
8
3 CArch
ive类与序列化 ……………………………………………………………… 214
8
31 创建可序列化的类 …………………………………………………………… 214
8
32 序列化对象 …………………………………………………………………… 215
8
33 CArchi
ve类的数据成员和成员函数 ………………………………………… 215
8
34 Se
ria
lie函数串行化处理数据 ……………………………………………… 216
z
第九章 打印 ……………………………………………………………………………… 219
1 MFC 的基本打印和打印预览 ……………………………………………………… 219
9
9
11 缺省打印实例 ………………………………………………………………… 219
9
12 视类中的打印函数 …………………………………………………………… 220
9
13 打印控制过程 ………………………………………………………………… 221
2 打印缩放、映射模式选择及其编程实例 …………………………………………… 222
9
3 多页打印及其编程实例 …………………………………………………………… 223
9
第十章 异常处理 ……………………………………………………………………… 226
10
1 CEx
cet
pion类 …………………………………………………………………… 226
10
11 CEx
cet
pion类的函数 ……………………………………………………… 226
10
12 CEx
cet
pion类的派生类 …………………………………………………… 227
2 文件异常 CF
10 i
leEx
cet
pion类 ……………………………………………………… 227
10
21 CF
ileEx
cet
pion类数据成员 ………………………………………………… 227
10
22 CF
ileEx
cet
pion类成员函数 ………………………………………………… 228
10
23 编程实例 ……………………………………………………………………… 228
6 3 数据库异常类 ……………………………………………………………………… 229
10
目 录
……………………………………………………………………………………………………………………………………………
第十三章 Ac
tieX 控件 ………………………………………………………………… 278
v
13
1 Ac t
iveX 的基本概念 ……………………………………………………………… 278
13
11 组件对象模型 COM ………………………………………………………… 278
13
12 对象链接与嵌入 ……………………………………………………………… 279
13
13 自动化服务器与自动化控制器 ……………………………………………… 280
13
14 Acti
veX 控件 ………………………………………………………………… 280
13
2 Ac t
iveX 控件编程实例 …………………………………………………………… 280
13
21 Ac
tieX 控件框架的创建 …………………………………………………… 281
v
13
22 控件的类 ……………………………………………………………………… 282
13
23 Ac
tieX 控件的测试 ………………………………………………………… 283
v
13
24 控件的外观设计 ……………………………………………………………… 283
13
25 设置属性 ……………………………………………………………………… 287
13
26 设置事件 ……………………………………………………………………… 289
13
27 设置方法 ……………………………………………………………………… 289
13
3 Ac t
iveX 控件的注册 ……………………………………………………………… 290
13
4 Ac t
iveX 控件应用编程实例 ……………………………………………………… 290
第十四章 多媒体技术 ………………………………………………………………… 293
1 多媒体文件格式 …………………………………………………………………… 293
14
2 播放多媒体文件 …………………………………………………………………… 293
14
3 MCI控制方法 …………………………………………………………………… 294
14
14
31 MCI设备类型 ……………………………………………………………… 294
14
32 MCI函数接口 ……………………………………………………………… 294
14
33 常用的 MCI命令消息 ……………………………………………………… 295
4 多媒体文件调用编程实例 ………………………………………………………… 296
14
第十五章 多进程与多线程编程 ……………………………………………………… 298
1 多进程编程 ………………………………………………………………………… 298
15
15
11 进程 …………………………………………………………………………… 298
15
12 创建进程 ……………………………………………………………………… 298
15
13 结束进程 ……………………………………………………………………… 300
15
14 多进程编程实例 ……………………………………………………………… 301
2 多线程编程 ………………………………………………………………………… 304
15
15
21 线程的创建与结束 …………………………………………………………… 304
15
22 线程的调度和优先级 ………………………………………………………… 306
15
23 线程间通信 …………………………………………………………………… 307
15
24 多线程编程实例 ……………………………………………………………… 308
3 线程同步 …………………………………………………………………………… 313
15
15
31 线程同步的概念 ……………………………………………………………… 313
15
32 临界区 ………………………………………………………………………… 313
15
33 事件内核对象 ………………………………………………………………… 313
8 15
34 互斥内核对象 ………………………………………………………………… 314
目 录
……………………………………………………………………………………………………………………………………………
15
35 信号量内核对象 ……………………………………………………………… 314
15
36 线程同步编程实例 …………………………………………………………… 315
第十六章 网络通信编程 ……………………………………………………………… 320
16
1 TCP/ IP 协议 ……………………………………………………………………… 320
16
11 TCP/IP 协议的体系结构 …………………………………………………… 320
16
12 IP 地址和通信端口 ………………………………………………………… 320
16
13 客户机/服务器模式 ………………………………………………………… 321
16
2 So c
ket概念与 Wi
ndowsSo
cke I …………………………………………… 322
tAP
16
21 Soc
ket的类型 ………………………………………………………………… 322
16
22 阻塞和非阻塞 ………………………………………………………………… 322
16
23 WindowsSocke I主要函数 …………………………………………… 322
tAP
16
24 s
tructso
ckaddr结构 ………………………………………………………… 325
16
25 WindowsSocke I辅助函数 …………………………………………… 325
tAP
16
3 Wi ndowsSo
c t编程流程与编程实例 ………………………………………… 326
ke
16
31 流套接字编程流程 …………………………………………………………… 326
16
32 数据报套接字编程流程 ……………………………………………………… 328
16
33 WindowsSocke I编程实例 …………………………………………… 328
tAP
4 MFC 中的 Wi
16 nso
ck ……………………………………………………………… 335
16
41 CAsyncSo
cket类及其主要成员函数 ……………………………………… 336
16
42 CAsyncSo
c t类编程实例 ………………………………………………… 337
ke
16
43 CSo
c t类 …………………………………………………………………… 343
ke
5 串行端口通信编程 ………………………………………………………………… 343
16
16
51 Wi I串行端口通信函数及编程实例 …………………………… 344
ndowsAP
16
52 利用端口函数实现串行端口通信编程 ……………………………………… 351
16
53 MSComm 控件及其编程实例 ……………………………………………… 351
第十七章 项目开发实例———学生管理信息系统 ………………………………… 354
1 管理信息系统设计原则 …………………………………………………………… 354
17
2 需求分析 …………………………………………………………………………… 354
17
17
21 系统主要功能 ………………………………………………………………… 354
17
22 数据流 ………………………………………………………………………… 355
3 系统设计 …………………………………………………………………………… 356
17
17
31 系统的功能模块 ……………………………………………………………… 356
17
32 业务流程设计 ………………………………………………………………… 357
17
33 数据库设计 …………………………………………………………………… 357
4 详细设计 …………………………………………………………………………… 359
17
17
41 主体框架模块 ………………………………………………………………… 359
17
42 登录权限验证模块 …………………………………………………………… 363
17
43 重新封装 ADO ……………………………………………………………… 367
17
44 院系数据管理模块 …………………………………………………………… 374
17
45 学生数据管理模块 …………………………………………………………… 377 9
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
17
46 课程成绩管理模块 …………………………………………………………… 386
17
47 课程成绩统计模块 …………………………………………………………… 389
17
48 帮助的制作 …………………………………………………………………… 393
5 项目包装和项目打包 ……………………………………………………………… 394
17
17
51 项目包装 ……………………………………………………………………… 394
17
52 项目打包 ……………………………………………………………………… 395
参考文献 …………………………………………………………………………………… 400
10
第一章 软件开发环境与软件
项目开发过程
软件开发环境是支持软件产品开发的软件系统。 20 世纪 90 年代,面向对象技术和构
架/构件技术受到学术界和工业界的重视,出现了支持这些技术的软件开发环境。 Visual
C++ 就是其中的一种。软件工程是要用工程化、规范化的方法实现软件的开发和维护,软件
工程确定和规范了软件项目的开发过程和开发方法 。
本章将介绍 Visual C++ 60 集成开发环境(IDE),从软件工程的角度介绍软件项目开发
的各个过程及各过程的主要任务 。
在本书以下的叙述中,为了简单起见,将 Visual C++ 60 简称为 VC60。
本章要点:
* VC60 的用户界面
* VC60 的菜单使用
* ClassWizard 的使用方法
* 软件项目开发过程
* Visual C++ 编程规范
1
1 VC6
0 用户界面
VC60 是 Microsoft Developer Studio 的组件之一,而后者通常被称为集成开发环境
(IDE),亦即 VC60 的用户界面。图 1 1 是 VC60 的用户界面,它的界面是智能化的,而且非
1
图1 1 VC6.
0用户界面
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
常宽容,鼓励用户去实践和尝试。
VC60 的用户界面由上端的菜单栏和工具栏、左边的工程工作区 (ProjectWorkspace)、
右边的进行文件与资源编辑的主工作区以及下端的输出窗口 (Output)和状态栏组成。 在调
试时,平台还提供各种窗口,包括观察窗口、变量窗口、寄存器窗口、存储器窗口、调试堆栈窗
口和反汇编窗口等。
1
11 工程工作区窗口
在工程工作区窗口下端有 3 个标签:ClassView、ResourceView 和 FileView。
(1)ClassView
ClassView 视图中以树形结构显示了某一工程创建的所有类,并在每个类中列出了成员
变量和成员函数,图 1 2 即显示了工程工作区的 ClassView 视图。 每一个类首先列出带有
紫色图标的成员函数,然后才是带有绿蓝色图标的成员变量,每个成员的图标左边都有一个
标志,以表示成员类型和存取类别的信息,保护型成员图标旁边的标志为一钥匙,私有成员
的标志是一挂锁,而公有成员图标旁边没有标志。
在 ClassView 视图中双击类名,会在主工作区中打开这个类的头文件,显示出类的声
明;而双击某个类的成员,则主工作区中会显示该成员的定义代码。 右击某个类名,可以弹
出一个快捷菜单对类进行操作。
(2)ResourceView
单击 ResourceView 标签,会在工程工作区窗口中将应用程序中所有的可视资源显示为
树状结构,如图 1 3 所示。
图1 2 C
las
sVew 视图
i 图1 3 Re
sou
rceV
iew 视图
在 Windows 编程中,经常会用到不同种类的资源,例如位图、光标、图标、菜单、工具栏和
对话框模板等。该视图用于管理各种资源。
在编程过程中,程序员可能只是需要查看工程中资源文件的内容,而不是打开一个资
2 源。例如,程序员可能试图在所有的对话框资源中搜索一个字符串,而不必单独地打开每一
第一章 软件开发环境与软件项目开发过程
……………………………………………………………………………………………………………………………………………
个对话框资源。这时可以使用以文本形式打开资源文件的方法看到资源,并且执行文本编
辑器支持的全局操作。
资源编辑器具有可视化的技术和界面,可以快速并且简单地创建和修改应用程序的资
源。可以使用资源编辑器创建一个新的资源,修改一个已经存在的资源,拷贝一个已经存在
的资源或删除一个旧的资源。使用资源编辑器的主要目的就是为了简单而方便地进行上述
的操作。当创建或者打开一个资源时,相应的资源编辑器就自动打开了。 同时,也可以在一
个文本格式的文件里打开一个资源文件 。
(3)FileView
在 FileView 视图中,工程文件以树状结构列表,列表文件分为源文件、头文件、资源文
件和帮助文件 4 种类型,如图 1 4 所示。
在此视图中用鼠标右键单击文件夹,在弹出的如图 1 5 所示的右键菜单 (快捷菜单 )中
选择 New Folder,就会弹出如图 1 6 所示的新建文件夹对话框。在对话框中,用户可以定义
文件夹的名称和其所包含文件的扩展名,从而将自定义的文件种类加入到工程 。
图1 5 鼠标放在文件夹上的右键菜单
图1 4 F
ileV
iew 视图 图1 6 NewFo
ldr对话框
e
在 FileView 视图中能够对工程文件进行添加、删除和拷贝,如果在视图中存在多个工
程,则可以通过拖曳在工程之间交换文件 。双击视图中列出的文件,就可以将此文件打开进
行编辑。而删除文件时,选择要删除的文件,按下 Del 键即可。在 FileView 视图中单击鼠标
右键,可以弹出快捷菜单,对项目工程和文件进行操作。 其中,某些文件前向下的箭头表示
该文件正在被当前设置使用。
1
12 主工作区窗口
用户在开发应用程序的过程中,源代码和资源的编辑工作都是在主工作区窗口中进行
的。当双击工程工作区中的某一项时,在主工作区中就会出现相应的屏幕显示 。 例如,在工
程工作区的 ClassView 视图中双击某成员函数,主工作区中就会出现该成员函数的源代码。 3
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
图1 7 定义语法着色(
Opt
ios对话框)
n
语法着色可以帮助用户发现一些由于粗心而造成的错误,这些错误往往不容易被发现。
例如,用户在定义整型变量时,将 int 输入为 itn,由于 itn 不会变成蓝色,这就提醒了用户
关键字拼写错误从而在进行编译之前防止了许多编译错误的发生 。
1
13 输出窗口
图 1 1 所示的下部空白区是输出窗口,用于显示输出内容和错误信息,该窗口有 5 个标
签:Build、Debug、Find in Files 1、Find in Files 2 和 Results。
Build 标签:用于显示编译和链接的结果。
Debug 标签:用于通知来自调试器的提示。
Find in Files 1 标签:用于显示 Find in Files 查找的结果。
Find in Files 2 标签:用于显示两个 Find in Files 结果区域,其一显示前一结果。
Results 标签:用于显示使用 profiler 等工具后的结果。
1
2 VC6
0 菜单介绍
菜单栏包括 10 个菜单项,界面显示 9 个,其中 Debug 菜单与 Build 菜单切换显示。菜单项
提供的命令可以完成几乎所有 VC60 的功能,因此了解和掌握这些菜单命令是非常必要的。
1
21 F
ile菜单
4 File(文件)菜单中主要包括编程过程中对文件进行操作的命令选项,见表 1 1。
第一章 软件开发环境与软件项目开发过程
……………………………………………………………………………………………………………………………………………
表1 1 F
ile菜单
菜 单 项 作 用
New 创建一个新的文件、工程、工程工作区或其他文件
Open 打开一个现存文件或工程
Close 关闭打开的文件
Open Workspace 打开一个工程的工作区
Save Workspace 保存工作区中的内容
Close Workspace 关闭工程工作区
Save 保存当前活动窗口中的内容
Save As 用新的文件名来保存打开的文件
快速保存所有窗口中的内容,对于新的窗口,将激活 Save As
Save All
对话框窗口来选择文件
Page Setup 页面设置
Print 打印全部或部分当前被编辑的窗口
Recent Files 最近打开的文件
Recent Workspaces 最近打开的工程
Exit 关闭 VC60
1
22 Ed
it菜单
Edit(编辑)菜单主要用于文本文件的编辑、查找和替换,见表 1 2。
表1 2 Ed
it菜单
菜 单 项 作 用
Undo 取消最近的编辑操作
Redo 恢复 Undo 操作
Cut 剪切
Copy 拷贝
Paste 粘贴
Delete 删除被选择的内容。若清除的内容可恢复,则 Undo 按钮有效
Select All 选定当前文件的所有内容。在对话框编辑窗口中,则选择所有控件
Find 在当前窗口文件中寻找指定内容
Find in Files 在多个文件中寻找指定内容
Replace 在文件中对指定内容进行替换
Go To 弹出 Go To 对话框,将光标移动到指定行
显示一个允许读者在 Code 窗口中使用、创建或删除位置标志符,移
Bookmarks
到下一个或上一个书签(bookmark),或清除所有书签
Advanced 给出针对 Code 窗口中代码的一系列编辑方法
打开 BreakPoints 对话框,该对话框可设置、删除、禁止、激活或查
Breakpoints
看断点,读者设置的断点将作为工程的一部分保存起来
List Members 给出一个所选类或结构中合法成员变量或函数的列表
Type Info 获得类型信息
Parameter Info 获得参数信息
当用户输入了足够的字符后,系统将弹出一个用户已输入单词的列表,
Complete Word 允许用户从中选择自己要输入的整个单词。当系统能够根据用户已输
入字符唯一的匹配以前输入的单词时,系统将自动拼出整个单词 5
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
1
23 V
iew 菜单
View(视图)菜单主要用于对集成开发环境中一些窗口的控制,见表 1 3。
表1 3 V
iew 菜单
菜 单 项 作 用
显示所选对象的属性对话框,对于任何一个对象,都可以
Properties
选择此项来查看该对象的属性
1
24 I
nse
rt菜单
Insert(插入)菜单主要用于创建新的类、表单或资源,也可以通过对菜单项的选择将已
有文件插入到当前文件中或将新的 ATL 对象插入到项目中,见表 1 4。
表1 4 I
nse
rt菜单
菜 单 项 作 用
1
25 P
roec
j t菜单
Project(项目)菜单主要用于项目和工作区的管理,用于向当前项目内插入类、资源和
6 文件,见表 1 5。
第一章 软件开发环境与软件项目开发过程
……………………………………………………………………………………………………………………………………………
表1 5 Pr
oje
ct菜单
菜 单 项 作 用
Set Active Project 设置某工程为工作区中的活动工程
Add to Project 向项目中添加文件、文件夹、数据链接以及组件等
Dependencies 能够编辑工程之间的依赖关系
弹出图 1 8 所示 Project Settings 对话框,该对话
Settings
框有以下 10 个标签,见表 1 6
Export Makefile 能够以 Make 文件格式导出可建立的工程
Insert Project into
能够向工作区中插入工程
Workspace
图1 8 Pr
oje
ctS
ett
ins对话框
g
表1 6 Pr
oje
ctS
ett
ins对话框标签
g
标 签 作 用
用于在 Appwizard 建立工程时选择静态库或共享 DLL,以及确定中间文件(源文件
general
和对象)和输出文件(EXE、DLL、DCX)存放的目录
Debug 设置调试器的有关选项
编译器设置。默认情况下 Category 组合框选中 General。用户可通过从组合框
C/C++ 选中一个种类来改变 Category 的设置。用户可改变优化标准(可选择的标准是:
Maximize Speed、Minimize Size、Customize 和 Disable)和警告等级
Link 控制着链接选项,这些选择不需改变,设置分为 5 个种类
Resource 能够修改应用程序所使用的语言,也能选择编译到应用程序中的资源
OLE Type 用于从对象描述文件(ODL)建立类型库
用于处理 Go To Definition,Go To Declaration 及其他类似选项时使用的 Browse
Browse Info
Info(.bsc)文件
Custom Build 允许用户按自己的过程创建应用程序
Pre_Link Step 在链接步骤之前,允许插入预链接命令
Post_Build Step 可以在其他步骤已成功完成之后增加自己的步骤
7
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
1
26 Bu
id菜单
l
Build(生成)菜单主要用于编译和建立应用程序,见表 1 7。
表1 7 Bu
ild菜单
菜 单 项 作 用
Compile 编译当前的源代码文件
Build 编译当前的可执行文件
Rebuild All 重新编译所有的文件
Batch Build 设置要通过批处理编译的工程
Clean 删除工程编译过程中的中间文件和输出文件
Start Debug 开始程序的调试,程序运行到断点或一直到结束
Debugger Remote 打开 Remote Connection 对话框,用于链接远程过
Connection 程,对远程过程进行调试
Execute 执行一个 exe 可执行文件
Set Active Configuration 将某个工程的编译或链接配置激活
Configurations 配置当前项目的输出模式
Profile 设置工程的配置文件
1
27 Deb
ug菜单
启动 VC60 的调试器后(可按 F5 键),集成开发环境中的 Build 菜单被 Debug 菜单取代,
Debug 菜单中包括了调试过程中常用到的命令,见表 1 8。
表1 8 De
bug菜单
菜 单 项 作 用
Go 开始程序的调式,程序运行到断点或一直到结束
Restart 重新开始程序的调试
Stop Debugging 停止程序的调试
Break 暂停程序的调试
Apply Code Changes 调试过程中把对代码的更改应用到源文件中
调试过程中单步执行下一条语句,并且能够对程序中任
Step Into 何可访问的函数调用进行跟踪,进入函数内部,查看其运
行情况
与 Step Into 功能类似,区别在于它并不对程序中的函数
Step Over
调用进行跟踪,而是直接执行该调用语句
可以用此命令使程序直接向下运行,直到从函数内部返
Step Out
回,在函数调用语句后的语句处停止
Run to Cursor 程序运行到光标所在的位置时停止
Step Into Specific
单步执行选定的函数
Function
弹出对话框,显示各种异常并设置程序对异常是否停止
Exceptions
运行
显示程序调试过程中所有可用的线程,可以对其进行设
Threads
置,挂起和恢复线程并设置焦点
Modules 显示当前程序中装入的模块
Show Next Statement 显示即将执行的代码行
8 QuickWatch 查看、修改变量和表达式,也可以将其添加到观察窗口
第一章 软件开发环境与软件项目开发过程
……………………………………………………………………………………………………………………………………………
1
28 Too
ls菜单
Tools(工具)菜单主要用于定制菜单、工具箱和工具栏,设置宏等操作,见表 1 9。
表1 9 To
ols菜单
菜 单 项 作 用
Source Browser 弹出浏览器窗口
Close Source Browser File 关闭浏览文件
出现可视化组件管理器,它可以极大地方便各种
Visual Component Manager
组件的应用
Register Control 向 Windows 操作系统注册 OLE 控件
Error Lookup 检查 Win32 API 函数返回的标准错误代码信息
ActiveX Control
为测试 Active 控件提供了一个简单的环境
Test Container
提供安装在系统上的所有 OLE 和 ActiveX 对象的
OLE/COM Object Viewer
信息
Spy++ 提供观察 Windows 操作细节的方法
在调试应用程序时,可以用来激活各种级别的调
MFC Tracer 试信 息, 再 由 MFC 发 送 到 Developer Studio 的
Output 窗口
弹出 Customize 对话框,可以定制命令、工具栏、菜
Customize
单和快捷键
弹出如图 1 7 所示的 Options 对话框,用户可以
Options 在这个对话框中修改 VC60 的环境设置。Options
对话框中的标签见表 1 10
表1 10 Op
tio
ns对话框标签
标 签 作 用
Editor 选择滚动条、拖放和下拉,并设置自动保存和加载
Tabs 设置与 Tabs 键和缩进相关的选项
Debug 设置调试过程中所显示的信息
Compatibility 选择模拟其他编辑器全部或部分界面
Build 生成一个外部编译文件或者一个建立日志
Directories 设置包含文件、可执行文件、库以及源文件的路径
Workspace 设置浮动窗口、状态栏和工程重新加载
Data View 管理 Data View 的显示
Macros 设置重新加载一个修改宏的规则
Help System 设置帮助系统的语言与集合
Format 设置色彩体系,包括对源文件和其他窗口的语法着色方法
1
29 Wi
ndows菜单
Windows(窗口)菜单主要用于开发环境中窗口及其属性的控制,见表 1 11。 9
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
表1 11 Wi
ndows菜单
菜 单 项 作 用
New Window 打开包含当前原文件的新窗口
Split 将窗口拆分为多个窗口
Docking View 控制窗口的停靠特征
Close 关闭当前窗口
Close All 关闭主工作区内的所有窗口
Next 使下一个窗口成为当前窗口
Previous 使上一个窗口成为当前窗口
Cascade 使当前所有打开的窗口在屏幕上向下重叠排放
Tile Horizontally 使主工作区内当前所有打开的窗口在屏幕上横向平铺
Tile Vertically 使主工作区内当前所有打开的窗口在屏幕上纵向平铺
Open Windows 列出最近打开的窗口文件名,最多可以列出 10 个文件名
Windows 弹出 Windows 对话框,从中可关闭、保存或激活选中窗口
1
210 He
lp菜单
Help(帮助)菜单提供了各种浏览、检索 VC60 技术文档的工具以及帮助用户查看 VC60
的帮助信息、升级信息以及版本信息等,见表 1 12。
表1 12 He
lp菜单
菜 单 项 作 用
Contents 打开 MSDN Library 帮助系统,并进入其中的目录标签
Search 进入帮助系统的搜索标签
Index 进入帮助系统的索引标签
Use Extension Help 启用扩充帮助系统,而禁止 MSDN Library 帮助系统
弹出 Help Keyboard 对话框,选择上端下拉框中的选项,可
Keyboard Map
以查看所定义的快捷键
Tip of the Day 显示 Tip of the Day 对话框
Technical Support 得到 Microsoft 所提供的技术支持网址
Microsoft on the Web 列出 Developer Studio 和其他产品的 Web 地址列表
About Visual C++ 弹出VC++ 的 About 对话框
1
211 右键菜单(快捷菜单)
在 VC60 中有许多右键菜单,鼠标在不同位置点击右键弹出的右键菜单是不相同的,下
面主要介绍两种右键菜单。
表1 13 右键菜单(鼠标放在类上)
菜 单 项 作 用
Go to Definition 打开类的头文件,光标到类的定义处
续 表
菜 单 项 作 用
Add Virtual Function 弹出向类中添加虚函数对话框
Add Windows Message Handler 弹出向类中添加消息函数对话框
References 类的信息
Derived Classes 查看本类有哪些派生类
Base Classes 查看本类的基类
New Folder 弹出新建文件夹对话框
对 class View 视图中所选类的成员变量
Group by Access
和函数按公有、 保护和私有类型进行排列
Docking View 工程工作区窗口的显示方式
Hide 显示或隐藏工程工作区窗口
Properties 类的特性
表1 14 右键菜单(鼠标放在菜单、工具条空白处)
菜 单 项 作 用
Output 打开或关闭输出窗口
Workspace 打开或关闭工程工作区
Standard 打开或关闭 Standard 工具栏
Build 打开或关闭 Build 工具栏
Build MiniBar 打开或关闭 Build MiniBar 工具栏
ATL 打开或关闭 ATL
Resource 打开或关闭 Resource 工具栏
Edit 打开或关闭 Edit 工具栏
Debug 打开或关闭 Debug 工具栏
Browse 打开或关闭 Browse 工具栏
Database 打开或关闭 Database 工具栏
Source Control 打开或关闭 Source Control 工具栏
WizardBar 打开或关闭 WizardBar 工具栏
Customize 打开或关闭 Customize 对话框
3 使用 C
1 las
sWi
zar
d
在 VC60 中打开一个工程后,选择菜单 View- > ClassWizard 或使用快捷键 Ctrl+ W,打
开如图 1 9 所示的 MFC ClassWizard 对话框,该对话框包含 5 个标签,现分述如下。
1
31 Me
ss s标签
ageMap
此标签内容用于处理消息映射,允许程序员添加或删除 Windows 消息句柄,这是设计
Windows 事件驱动程序的基本环节。默认情况下,MFC ClassWizard 对话框显示此标签内容,
如图 1 9 所示,它包括的内容见表 1 15。 11
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
图1 9 C
las
sWi
zad的 Me
r ssa
geMa
ps标签
表1 15 Me
ssa
geMa
ps标签的内容
控 件 作 用
Project 下拉列表框 选择要处理的工程
Class name 下拉列表框 选择要处理的类
Object IDs 列表框 列出了应用程序中所有能够接受消息的对象标识符
Messages 列表框 列出了当前所选对象所能处理的消息
Member functions 列表框 列出了当前类中的所有成员函数
Add Class 按钮 打开 New Class 对话框,用于创建新的类库
Add Function 按钮 能够为工程添加新的消息处理成员函数
Delete Function 按钮 能够删除在 Member functions 列表框所选中的消息处理函数
Edit Code 按钮 能够编辑在 Member functions 列表框所选中的消息处理函数
1
32 Membe
rVa
riab
les标签
在该标签中能够为应用程序中的类创建成员变量(也称数据成员),这些类往往是和一
些控件联系在一起的。此标签如图 1 10 所示,它包括的内容见表 1 16。
表1 16 Memb
erVa
ria
bls标签内容
e
控 件 作 用
Project 下拉列表框 选择要处理的工程
Class name 下拉列表框 选择要处理的类
控件成员列表框 列出了控件标识符、与控件对应的变量及变量类型
Add Class 按钮 打开 New Class 对话框,用于创建新的类库
Add Variable 按钮 能够为选定的控件增加成员变量
Delete Variable 按钮 能够删除选定控件的成员变量
Update Columns 按钮 能够选择数据源,该按钮只对记录集有效
12 Bind All 按钮 能够进行数据源绑定
第一章 软件开发环境与软件项目开发过程
……………………………………………………………………………………………………………………………………………
图1 10 C
las
sWi
zad的 Memb
r erVa
ria
bls标签
e
1
33 Au
toma
tin标签
o
此标 签 为 程 序 员 提 供 了 对 OLE 自 动 化 类 的 方 法 和 属 性 的 管 理 和 使 用, 主 要 用 于
Automation 功能管理,此标签如图 1 11 所示,它包括的内容见表 1 17。
表1 17 Au
toma
tin标签内容
o
控 件 作 用
Project 下拉列表框 选择要处理的工程
Class name 下拉列表框 选择要处理的类
列出了已添加到当前类中的方法及属性的外部名,供 Automation
External names 列表框
客户程序使用
Implementation 编辑框 显示 External names 列表框中所选项的属性
Add Class 按钮 打开 New Class 对话框,用于创建新的类库
允许 用 户 为 当 前 类 添 加 新 的 Automation 方 法,该 项 只 对
Add Method 按钮
Automation 类有效
允许 用 户 为 当 前 类 添 加 新 的 Automation 属 性,该 项 只 对
Add Property 按钮
Automation 类有效
Delete 按钮 允许用户删除当前所选定的方法或属性名
Edit Code 按钮 能够编辑在 Member functions 列表框所选中的消息处理函数
Data Binding 按钮 用于指定 Automation 控件支持的数据绑定层次
13
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
图1 11 C
las
sWi
zad的 Au
r toma
tin标签
o
1
34 Ac
tiveXEven
ts标签
此标签管理 ActiveX 类所支持的 ActiveX 事件。此标签如图 1 12 所示,它包括的内容
见表 1 18。
图1 12 C
las
sWi
zad的 Ac
r tiv
eXEv
ent标签
表1 18 Ac
tiv
eXEv
ent标签内容
控 件 作 用
Project 下拉列表框 选择要处理的工程
Class name 下拉列表框 选择要处理的类
14
第一章 软件开发环境与软件项目开发过程
……………………………………………………………………………………………………………………………………………
续 表
控 件 作 用
列出了已添加到当前类中的成员函数与成员变量名,供 Automation 客
External names 列表框
户程序使用
Implementation 编辑框 显示 External names 列表框中所选项的属性
Add Class 按钮 打开 New Class 对话框,用于创建新的类库
能够为 当 前 类 添 加 新 的 Automation 事 件, 该 项 只 对 Automation 类
Add Event 按钮
有效
Delete 按钮 能够删除当前所选定的 Automation 事件
1
35 C
las标签
s
此标签用于显示应用程序中所包含类的一般信息,包括定义的头文件和源文件类名,以
及与之相联系的基类。此标签如图 1 13 所示,它包括的内容见表 1 19。
表1 19 Ca
lssI
nfo 标签内容
控 件 作 用
Project 下拉列表框 选择要处理的工程
Class name 下拉列表框 选择要处理的类
File details 组框 列出当前类源文件、头文件、基类与资源信息
Message filter 下拉列表框 列出当前类的所有有效信息,并按所属窗口分类
Foreign class 下拉列表框 列出与当前类有关的外部类名
Foreign variable 下拉列表框 列出与当前类有关的外部变量
Add Class 按钮 打开 New Class 对话框,用于创建新的类库
图1 13 C
las
sWi
zad的 C
r las标签
s
15
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
4 软件项目开发过程
1
1
41 软件生存期
软件工程是要用工程化、规范化的方法实现软件的开发和维护。 软件生存期模型是对
软件的整个开发过程从工程学的意义上给出的总体结构框架 。 有多种软件生存期模型。 如
瀑布模型、逐增模型、变化模型等。根据瀑布模型软件项目开发过程的总体流程如图 1 14
所示。
图1 14 软件开发过程
1
42 制定计划
制定计划是项目开发的第一步,通常包括以下几个方面。
(1)项目实施计划
这是软件开发的综合性计划,通常应包括任务、进度、人力、环境、资源和组织等多个方
面。其目的是提供一个框架,使得软件项目的主管人员可以对资源 、成本以及进度合理的估
算。这些估算应在软件项目开始后的一个有限时间内完成 。
(2)质量保证计划
把软件开发的质量要求具体的规定为每一个开发阶段可以检查的质量指标和保 证
活动。
(3)软件测试计划
规定测试活动的任务、测试方法、测试进度、测试资源和测试人员的职责等。
(4)文档编制计划
规定所开发软件项目应编写的文档种类 、内容、进度和编写人员的职责等。
(5)用户培训计划
规定对用户进行技术培训的目标 、要求、进度和参与技术培训的人员的职责等 。
(6)综合支持计划
规定项目开发过程中所需的支持条件,以及如何获取和利用这些支持条件 。
(7)软件分发与维护计划
16 规定软件项目完成后,用户在运行阶段应如何进行软件维护以及软件维护的任务、方
第一章 软件开发环境与软件项目开发过程
……………………………………………………………………………………………………………………………………………
法、资源和维护人员的职责等。
1
43 需求分析
需求分析的基本任务是把“用户需要系统做什么?”转换成一个完全的精细的软件逻辑
模型并写出软件的需求规格说明书,准确地表达用户的要求。 目的是使系统开发方与用户
方对软件的目的和要达到的功能达成一致的见解 。
(1)需求分析阶段的基本任务
① 确定目标系统的综合要求
这也就是解决所开发的软件要做什么,做到什么程度。主要从以下四个方面考虑:
● 功能需求 即要划分出软件系统必须完成的全部功能 。
● 性能需求 给出所开发软件的技术性能指标,包括存储容量限制、运行时间限制、安
全保密性要求等。
● 环境要求
主要表现为对系统运行环境的要求 。运行环境一般包括软件支持环境和
硬件运行环境以及系统运行的人文环境 。
● 扩充要求
虽然系统的扩充要求不属于当前系统的开发范围,但经过分析知道用户
将来还要做什么工作。因此,了解系统的扩充要求,留给用户一个系统再扩充的自由环境,
对软件开发方和用户来说,都将是很有益的。
② 分析系统的数据要求
对系统中所涉及的全部数据进行分析是需求分析阶段的一个很重要的任务 。 软件开发
人员需要准确而全面地定义数据,给出数据的结构及其处理的直观而规范的描述 。
③ 导出系统的逻辑模型
此阶段的逻辑模型是指比较详细的数据流图 、数据词典等。 逻辑模型的导出过程,也就
是数据流图的分解和细化过程。
④ 修正开发计划
通过需求分析阶段的工作,会进一步加深对系统的了解,也就可能发现可行性分析阶段
制定的软件计划中存在的问题,修正这些问题,对系统的开发意义很大。
⑤ 开发原型系统
开发原型系统是指在需求分析阶段建造软件 “样机”。 其目的主要是显示系统的功能,
使用户通过实践来获得未来系统将如何工作的直观概念,从而使用户可以更准确地提出和
修改他们的要求,使所开发的系统、软件能够得到更加令人满意的结果 。
⑥ 写出需求规格说明书
需求规格说明书,又称软件规格说明书,它是软件需求分析阶段的结果,是软件开发和
软件验收以及软件管理的重要依据 。
(2)需求分析的结构化方法(SA)
结构化分析方法主要采取自顶向下逐层分解的分析思想和原则 。 利用结构化分析
方法对目标系统进行分析,分 析 的 结 果 将 以 规 格 化 的 软 件 需 求 说 明 书 的 形 式 给 出 。 软
件需求说明书由一套分层的数 据 流 图 、一 本 数 据 词 典 、一 组 小 说 明 、相 关 的 一 些 补 充 材
料组成 。
① 数据流图
数据流图描述系统由哪些部分组成以及各部分之间有什么联系 。 数据流图从数据传递 17
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
和加工的角度,以图形的方式来刻画系统中数据流从输入到输出的移动变化过程,具体实例
可参见第 17 章的图 17 1。
② 数据词典
数据词典描述系统中的每一个数据 。数据流图给出了系统的组成及其内部各元素相互
之间的关系,但却未说明数据元素的含意。 仅靠数据流图人们无法理解它所描述的对象。
数据词典的任务是对数据流图中出现的所有被命名的图形元素,包括数据流、加工、数据文
件、数据元素,以及数据的源、汇点等,在数据词典中作为一个词条加以定义,使得每一个图
形元素都有一个准确的解释。
1
44 软件设计
软件设计阶段的主要任务就是根据需求分析得到的结果进行软件的设计工作,即在明
确了“做什么”的基础上,解决“如何做”的问题。
软件设计阶段通常分为两个步骤:一是系统设计,二是详细设计。 系统设计阶段主要解
决的问题是如何构成实现系统需求的程序模块及其结构 。这包括如何把欲开发的软件系统
划分成若干个模块,并确定模块间的相互关系,以及模块之间信息是怎样传递的等问题 。 详
细设计就是对每个模块内部进行算法设计 。
(1)系统设计
系统设计就是设计系统的 功 能 模 块,画 出 系 统 的 模 块 结 构 图 。 结 构 化 设 计 方 法 用
模块结构图表达系统模块之间的关系 。 由于数据流图和模块结构图之间存在着一定的
联系,因此结构化设计方法可 以 和 需 求 分 析 中 采 用 的 结 构 化 分 析 方 法 很 好 地 衔 接 。 使
用结构化设计方法的关键是恰当地划分模块,恰当地处理模块之间的联系,以便达到较
好的设计效果 。
① 结构化设计方法的步骤
结构化设计方法是在模块化 、自顶向下 、逐层细化的结构程序设计等技术基础上发
展起来的一种方法,其基本设计思想是将系统设计成由相对独立 、功能单一的模块组成
的结构 。
用结构化设计方法进行系统设计时一般分为两步 。
建立初始结构图:一个满足系统要求的初始结构图,可以从需求分析阶段使用结构化
●
分析方法获得的数据流程图中导出 。
改进初始结构图:建立初始结构图之后,应该审查分析这个结构,通过模块的分解或
●
合并来降低模块间的相互联系,也即降低模块间的耦合度,提高模块的聚合度。
② 结构化设计的描述工具———模块结构图
模块结构图又被称作程序结构图,是采用结构化设计方法进行软件总体设计时的重要
描述工具。模块结构图被用来描述系统的层次和分块结构关系,它能够很清晰地表达出模
块之间的相互联系。
模块结构图中的模块以矩形表示,在矩形框内可以标以模块的名字或功能的缩写字。
模块间的连线表示两个模块间的调用关系。 通常处在不同位置的两个模块,上层为调用模
块,下层为被调用模块。 在结构化设计中模块有 3 种形式,即顺序调用、选择调用和循环
调用。
18 ③ 从数据流程图导出模块结构图
第一章 软件开发环境与软件项目开发过程
……………………………………………………………………………………………………………………………………………
(2)详细设计
系统设计完成软件系统的总体模块结构设计,并规定了各个模块的功能及各模块之间
的相互联系。详细设计就是对每个模块内部进行算法设计 。
① 结构化详细设计的思想和方法
结构化详细设计使用一组基本逻辑构造,利用这组逻辑构造可以构成任何程序。 这些
构造就是顺序构造、条件构造和重复构造。顺序构造是实现任何一个算法的基本处理步骤;
条件构造根据某种逻辑条件的出现而选择相应的处理步骤;而重复构造则是为循环而设置
的。这 3 种构造是结构程序设计的基础。
任何一个程序,不管它的应用领域或者技术复杂性如何,都可以仅用这 3 种结构化构造
来设计和实现。
② 结构化详细设计的描述工具———流程图
结构化详细设计的描述工具有流程图 、方块图、HIPO 图和 PAD 图等。流程图又被称为程
序框图。流程图往往独立于各种程序设计语言,而且具有直观、清晰的特点。 用于描述结构
化程序的流程图由上述 3 种基本结构组成。
详细设计完成后应交付的文件主要有详细设计说明书和初步的模块开发卷宗 。
1
45 编码
所谓软件编码,就是将软件详细设计转换成计算机可以接受的程序代码,也就是选择一
种特定程序设计语言来编写源程序 。
1
46 测试
为了保证软件质量需要进行软件测试,软件测试的主要任务是发现并排除在分析、设
计、编码各个阶段中可能产生的各种类型的错误并加以修正,以便得到一个可运行的、可靠
的软件系统。它是保证软件可靠性的主要方法之一 。
(1)软件测试的一些基本原则
① 在开始测试时,不应默认程序中不会找到错误。
② 测试不应由编写程序的个人或小组承担 。
③ 测试文件必须说明预期的输出结果 。
④ 要对合理的和不合理的输入数据都进行测试 。
⑤ 除检查程序功能是否完备外,还应检查程序功能是否多余,换句话说,还应当检查该
程序是否产生了不希望的副作用 。
⑥ 应该完整地保留所有的测试文件 (包括测试数据集、预期的结果、程序执行的记录
等),直至该软件产品废弃不用为止。
(2)软件测试的基本手段
软件测试的基本手段有 3 种,即动态检测、静态检测和软件的正确性证明。 动态检测方
法中使用的最为普遍的是动态分析技术。 可以把程序看成是一个函数,此函数描述了输入
和输出之间的关系。输入的全体叫程序的定义域,输出的全体叫程序的值域。 该方法是使
程序有控制的运行,从多个角度观察程序运行时的行为,以发现其中存在的错误。 一个动态
检测过程可分为以下 5 步:
① 选取在定义域中的有效值,或定义域外的无效值。 19
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
② 对已选的值决定预期的结果。
③ 用选取的值执行程序。
④ 观察程序的行为,并获取结果。
⑤ 将结果与预期的结果对比,如果不吻合,则证明程序存在错误。
这种方法的有效性取决于测试中选取的用来运行程序的值,即所谓的测试用例。 人们
希望用最小的测试用例组合得到最大的测试彻底度 。 因此,如何设计好的测试用例与如何
衡量彻底度,便成为测试工作两个关键性的技术问题 。
按照产生测试用例的不同方式,动态测试可分为功能测试和结构测试两种。 功能测试
又叫“黑盒测试”。它从需求分析的系统说明书出发,按程序的输入、输出特性和类型选择测
试数据。结构测试又叫“白盒测试”,测试用例的产生涉及程序的具体结构,所以它反映程序
的结构性质。产生的测试用例应使程序的所有语句至少执行一次;或使程序的所有通路至
少通过一次,也即使程序的每个分支至少通过一次。 另外,动态检测技术还有接口测试,它
包括测试数据接口和控制接口。测试数据接口主要是测试例行程序或模块间的数据传递的
正确性;测试控制接口主要是测试例行程序或模块间的调用关系的正确性 。
静态测试的对象可以是需求文件、设计文件或程序,应力争找出其中的全部错误或可疑
之处。静态测试时不执行被分析的程序 。
流程图分析法也是常用的一种静态测试技术,它是通过分析程序的流程图来实现错误
检测的,它只分析代码的结构而不执行代码 。因此,流程图分析方法比较适合于编码实现阶
段。流程图分析所能得到的信息主要有:
① 语法错误信息。
② 每个语句中标识符的引用分析,如变量、参数等。
③ 每个例行程序调用的子例行程序和函数 。
④ 未给初值的变量。
⑤ 已定义的但未使用的变量。
⑥ 未经说明或无用的符号。
⑦ 对任何一组输入数据均不可能执行到的代码段 。
使用流程图分析比较直观,而且能为动态测试产生测试数据,并显示各种测试数据执行
的路径,便于分析测试结果。
(3)软件测试的基本步骤
软件测试可分为 3 个基本步骤:单元测试、组装测试和确认测试。
① 单元测试
单元测试是对程序的每个模块进行独立测试 。 根据详细设计的说明,应测试重要的控
制路径,力求在模块范围内发现错误。
单元测试一般以白盒法为主,而且可以使多个模块平行进行。 在单元测试中,一般同时
还对模块接口、局部数据接口进行测试。
② 组装测试
组装测试即把每个已通过测试的模块并入软件总体结构中进行组装和测试 。 每并入一
个模块,都要找出由此产生的错误。组装测试通常采用黑盒法来设计测试用例 。
③ 确认测试
20 确认测试是根据软件需求说明书中定义的全部功能和性能要求及确认测试计划,来测
第一章 软件开发环境与软件项目开发过程
……………………………………………………………………………………………………………………………………………
试整个软件系统是否达到要求,并提交最终的用户手册和操作手册 。
确认测试应交付的文件有确认测试分析报告、最终的用户手册和操作手册及项目开发
总结报告。
1
47 软件维护
软件维护是指在软件系统交付用户使用以后,为了改正错误或满足新的需要而修改软
件的过程。它是软件系统提交用户运行阶段对软件系统实施的质量保证手段 。 软件系统交
付用户使用以后,在软件运行过程中仍然存在一些原因需要对软件进行变动 。
5 面向对象方法的软件项目开发过程
1
1
51 软件生存期与类生存期
为适应面向对象开发方法的特点,给出如图 1 15 的软件生存期模型。
在图 1 15 中,各个阶段的顺序是线性的,但开发过程实际上不是线性的。 使用瀑布模
型时存在的问题是它没有考虑超出一个单独的项目的情形,也没有考虑任何比整个应用更
小的“产品”。软件开发经济学强调软件部件的复用,因此,必须重视把开发可复用的软件作
为应用开发过程的一部分。面向对象方法把类当做单元,而且可分别考虑类的生存期与软
件的生存期。
图1 15 一个基于复用的软件生存期
在面向对象软件开发过程中,需要特别重视复用。 因此,软件部件应当独立于当初开发
它们的应用而存在的类生存期。 部件的开发瞄准某些局部的设计和实现,因此能够帮助解
决当前的问题,但为了在以后的项目中使用,它们还应当足够通用。 在以后的应用开发中,
可以调整这些独立部件以产生适合于特定问题解决的功能 。类就是一个希望能够复用的单
元。图 1 16 为类生存期模型。
类生存期与软件生存期是交叉的 。就是说,类的标识是软件生存期的一个阶段,但类生
存期的步骤独立于任一特殊应用的开发 。这样可以完整地描述一个基本实体 。 而不仅仅考
虑当前正在开发的系统。
通常面向对象的设计(OOD)分为两个阶段:高层设计 (或系统设计 )和低层设计 (或详细
设计)。高层设计主要是建立应用的体系结构,包括像开发用户界面那样的问题的解决部 21
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
图1 16 类生存期
分。低层设计集中于类的详细设计阶段 。
1
52 面向对象的软件项目开发过程
下面把软件生存期与类生存期放在一起,阐述面向对象的软件项目开发过程,图 1 15
为开发的步骤。
(1)分析阶段
分析阶段包括两个步骤:论域分析和应用分析。 它们都要标识问题论域中的抽象。 在
分析中,需要找到特定对象,基于对象的公共特性把它们组合成集合,直到最后能够标识出
对这个问题的一个抽象为止。
① 论域分析
论域分析开发问题论域的模型。 论域分析应当在应用分析之前进行,因为在了解问题
之前应当对问题敞开思想考虑,不应加以限制。客户可能会改变需求,而且问题环境也可能
改变,所以,论域分析应考察问题论域内的一个较宽的范围,分析覆盖的范围应比直接要解
决的问题更广。
② 应用分析
应用(或系统)分析细化在论域分析阶段所开发出来的信息,并且把注意力集中于当前
要解决的问题。因为通过论域分析,分析人员具有了较宽的论域知识,因而能开发出更好的
抽象。
面向对象分析(OOA)是软件开发过程中的问题定义阶段,图 1 17 是 Coad 与 Yourdon 提
出的面向对象分析的五个层次的概念模型 。
(2)高层设计(系统设计)
在一个纯面向对象环境中,系统设计与类设计常常是同一的过程,但还是应当把系统与
类的设计分开。
系统设计包括将系统划分为子系统的决策;子系统的软、硬件分配;设计框架的主要概
念和策略性决策。
构建待开发软件的总体模型。在这个阶段,标识在计算机环境中进行问题解决工作所
需要的概念,并增加了一批需要的类。 这些类包括那些可使用应用软件与系统的外部世界
22 交互的类。此阶段的输出是适合应用软件要求的类 、类间的关系、应用的子系统视图规格说
第一章 软件开发环境与软件项目开发过程
……………………………………………………………………………………………………………………………………………
图1 17 面向对象分析的概念模型
明。通常,利用面向对象设计得到的系统框架如图 1 18 所示。
图1 18 OOD设计导出的结构
① 面向对象设计的五个层次和四个部分 。
面向对象设计建立的是求解域的对象模型 。 在设计阶段,OOD 模型是 OOA 模型的扩展。
OOD 模型同样包括 OOA 模型的五个层次,但同时又引进了四个部分。
问题域部分(PDC):面向对象分析的结果直接放入该部分 。
人机交互部分(HIC):包括对用户分类、描述人机交互的脚本、设计命令层次结构、设计
详细的交互、生成用户界面的原型、定义 HIC 类等。
任务管理部分(TMC):识别任务 (进程)、任务所提供的服务、任务的优先级、进程的驱动 23
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
模式,以及任务与其他进程和外界如何通信等 。
数据管理部分(DMC):确定数据存储模式,如使用文件系统、关系数据库管理系统还是面
向对象数据库管理系统等。
② 子系统间的交互方式。
在应用系统中,子系统之间的关系可分为客户/服务器关系和同等伙伴关系两种。 这两
种关系对应两种交互方式,即客户/服务器交互方式和同等伙伴交互方式 。
客户/服务器交互方式 客户/服务器交互方式也称单向交互方式。 在该交互方式中,
作为客户的子系统调用作为服务器的子系统,执行某些服务后并返回结果。 在该交互中,任
何交互行为都是客户驱动的,因此,作为客户的子系统必须了解作为服务器的子系统的接
口,而服务器不必了解客户的接口。
同等伙伴交互方式 同等伙伴交互方式也称双向交互方式。 在该交互方式中,每个子
系统都可能调用其他子系统,因此,每个子系统都必须了解其他子系统的接口,子系统间必
须相互了解接口。
(3)详细设计(类的开发)
详细设计阶段基本上是类的开发,因为一个应用往往是用一个单个的类表示,它亦可分
成几个类。高层设计将标识对各个类的要求,且给出类的定义。 这个阶段贯穿于各个类的
生存期。结果是支持这个应用的一组类。
(4)实例的建立
这个阶段的结果就是对问题的解决 。在此阶段要建立各个对象的实例 。
(5)组装测试
这一阶段把系统当做一个完整的应用来进行测试 。各个类的封装和类测试的完备性可
减少组装测试所需要的时间。
(6)维护
维护的要求将影响应用和各个类 。多数维护活动发生在类级。
1
6 V
is lC++ 编程规范
ua
在软件项目的开发过程中,制定一个统一的编程规范是非常必要的,这样可以使得软件
的可维护性大大提高,以下主要说明在 Visual C++( 简称 VC++) 平台下进行项目开发时的编
程规范。
1
61 基本要求
(1)程序结构清晰,简单易懂,单个函数的程序行数不得超过 100 行。
(2)尽量使用标准库函数和公共函数 。
(3)不要随意定义全局变量,尽量使用局部变量。
(4)使用括号以避免二义性。
1
62 命名
包括变量命名、函数命名、资源 ID 号、类名等,命名必须具有一定的实际意义。
24 (1)变量命名:形式为 xAbcFgh,x 由变量的类型确定,Abc、Fgh 表示连续意义字符串,如
第一章 软件开发环境与软件项目开发过程
……………………………………………………………………………………………………………………………………………
果连续意义字符串仅有两个,可都大写。
(2)函数命名:第一个字母大写,要求用大小写字母组合规范函数名 。
(3)资源 ID 号:资源 ID 号全部用大写字母,根据意义确定 ID 号。
(4)类名:第一字母为大写的 C,后面用 AbcFgh 的形式,Abc、Fgh 表示连续意义字符串。
1
63 注释与可读性
(1)作为中文版的软件,原则上注释要求使用中文。
(2)每个源文件都有文件头说明。
文件开头的注释内容包括:文件名、公司名称、版权、作者名称、时间、文件描述、版本等。
复杂的算法还需要加上流程说明 。
(3)每个函数都有函数头说明。
在函数的原型说明包括引用外部函数和内部函数 。
引用外部函数必须在右侧注明函数来源:模块名及文件名。
如:void UpdateDBTable();//来源于:模块 1/stu.c
引用内部函数必须在右侧注明函数来源:文件名。
如:void ShowTable();//来源于:tec.c
在函数的源文件中,函数注释包括:输入、输出、函数描述、流程处理、全局变量、调用模
块等,复杂函数需要加上变量用途说明 。
(4)程序中的注释:修改时间、作者、方便理解的说明等。
(5)当定义或引用主要变量时,注释能反映其含义,常量定义有相应说明。
(6)注释可以与语句在同一行,也可在上一行。
(7)注释行数(不包括程序头和函数头说明部分)应占总行数的 1/5 到 1/3。
(8)利用缩进来显示程序逻辑结构,缩进量一致并以 Tab 键为单位。
(9)循环、分支层次不要超过 5 层。
(10)一目了然的语句不加注释。
1
64 结构化要求
(1)禁止出现两条等价的支路。
(2)禁止 GOTO 语句。
(3)每个函数都有函数头说明。
25
第二章 C++语言基础
1 简单的C++程序和C++语言的特点
2
2
11 简单的C++程序
【例2 1】 一个简单的C++ 程序 Ex02_1 及在VC++ 平台下,如何编写、编译C++ 程序。
(1)在资源管理器中,在 D 盘下新建一个文件夹 Ex02_1。
(2)在VC++ 平台下,新建一个C++ Source 文件,文件名为 Ex02_1.cpp。
① 启动 VC60。
② 点击菜单:File- > New,弹出图 2 1 所示的界面,选择 Files 标签下的 C++ Source
File,在 Files 中输入文件名 Ex02_1,在 Location 中输入文件保存的路径名 D:\Ex02_1。
③ 单击图 2 1 中的“OK”按钮,出现图 2 2 所示的文件编辑界面。
图2 1 新建文件 图2 2 文件编辑界面
26
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
为方便起见,程序每一行都加上了行号,实际编程时不能加。下面对各语句加以解释:
① 第 1 条语句是C++ 语言的注释语句。其中,“
//”是C++ 语言的注释符号。
② 第 2 条语句用于将定义输入/输出函数( 的头文件 iostream.h 包含到程序中来。
cout,cin)
③ 第 3 条语句说明主程序的开始,表示程序返回的是 void 类型的数据,其传递值也是
void 类型。
④ 第 5 条语句声明了一个整型变量 i。
⑤ 第 6 条语句和第 7 条语句是C++ 语言的输出语句,可以将指定的字符串输出到屏幕上。
⑥ 第 8 条语句是C++ 语言的输入语句,表示从屏幕上向变量中输入一个值 。
从上面这个程序可以看出,C++ 语言继承了 C 语言的许多特点,同时增加了许多的功能。
(4)编译
单击菜单:Build- > Compile Ex02_1.cpp,弹出图 2 3 所示的界面,点击按钮 “是”,自动
生成项目框架,见图 2 4。
图2 3 是否生成项目框架 图2 4 生成的项目框架
(5)运行
单击菜单:Build- > Execute 或按工具栏中的 按钮 (或按
Ctrl+ F5),运行界面如图 2 5。
2
12 C++语言的基本特点 图2 5 运行的界面
C++ 语言是一种面向对象的程序设计语言,该语言提出了把数据和对数据的操作封装在 27
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
一起的类、对象和方法的机制,并通过派生、继承和多态性等特性,实现软件重用和程序自动
生成,使得软件(特别是大型复杂软件)的构造和维护变得更加有效和容易,并使软件开发能
更自然地反映事物的本质,从而大大提高了软件的开发效率和质量 。其基本特点如下。
(1)抽象(Abstract)
面向对象方法中的抽象是对具体问题 (对象)进行概括,抽出一类对象的公共性质并加
以描述的过程。
(2)封装(Encapsulation)
就是将数据和对数据的操作方法即函数组合在一起构成类 (Class),从而实现数据抽象
和数据隐藏。封装意味着对象应具有明确的功能,并有能和其他对象相互作用的接口。 也
意味着对象内部代码受到保护,只有处于对象中的代码才可以访问该对象的内部数据,这就
是数据的隐藏。
(3)继承(Inheritance)
继承的概念是从人工智能工程衍生而来的,利用继承可以避免相同的内容重复出现,能
够节省大量的时间以及存储空间 。在C++ 语言中,可以从一个类派生另一个类。 派生类 (也
称之为子类)继承了其父类和祖先类的数据成员和成员函数,并通过声明新的数据成员和成
员函数来增加新的功能。
(4)多态性(Polymorphism)
对象在接收到同样的函数调用时,会产生不同的执行动作,这样的功能叫多态性。 多态
是通过重载函数和虚函数等技术来实现的 。
2 数据类型 、变量和运算符
2
程序是由数据和处 理 数 据 的 操 作 组 成 的 。 因 此 学 习 C++ 编 程 之 前,必 须 首 先 了 解
C++ 语言的基本数据类型和运算符,以及两者结合后对数据的简单处理方式 ———运算和
表达式 。
2
21 基本数据类型
C++ 提供了大量可以使用的预先定义的数据类型 。 也可以把不同的数据类型结合起来
生成更复杂的数据类型。类型名可以是一个标识符、一个关键字或者是一个表达式。
基本数据类型是C++ 类型系统的基本组成部分。 这些基本数据类型包括字符类型、整
数类型、浮点类型、双精度类型和无值类型,相应的关键字是 c
har、
int、
fl
oat、do
ube和 v
l oid。
这些数据类型的位数和相应的取值范围如表 2 1 所示。
表2 1 基本数据类型的位数与取值
类型名 表示的含义 位 数 范 围
char 字符 8 - 128~127
int 整数 16 - 32 768~32 767
float 浮点数 32 34E - 38~34E+38
double 双精度浮点数 64 17E - 308~17E+308
void 无值 0 无值
28
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
2
22 加修饰符的基本数据类型
在基本数据类型中除 void 类型外,其他类型前面可以加类型修饰符用于改变基本类型
的含义,以满足各种情况下的需要。 C++ 的类型修饰符包括:signed (有符号 )、unsigned (无
符号)、short(短型)和 long(长型)。修饰符 signed 和 unsigned 可以用于字符类型和整型,
而 short 和 long 可以用于整型,long 还可以用于双精度类型。 表 2 2 给出了基本数据类
型和类型修饰符的组合情况。
除了这些基本数据类型以外,C++ 的数据类型还包括常量(整常量、浮点常量和字符串常
量)、类、结构、数组和指针等,其他数据类型将在以后进行讨论 。
表2 2 组合类型的位数与取值
类 型 名 位数 范 围 类 型 名 位数 范 围
char 8 - 128~127 unsigned int 16 0~65 535
- 2 147 483 648~
signed char 8 - 128~127 long int 32
2 147 483 647
- 2 147 483 648~
unsigned char 8 0~225 signed long int 32
2 147 483 647
short int 16 - 32 768~32 767 unsigned long int 32 0~4 294 967 295
2
23 变量
程序要对数据进行读、写等操作。 当存储特定值或存储计算结果时,往往需要用到变
量。变量是用于表示一条信息的名称 。
无论什么时候要访问存储在变量内的信息,只需要使用变量的名称即可。因为C++ 对数
据的类型要求很严格,所以必须在使用变量之前,先定义这个数据的类型。
命名变量
1
在给变量起名时,受到以下一些限制:
(1)变量名不能以数字开头。
(2)变量名不能有空格。
(3)变量名中除了能使用 26 个英文字母和 0~9 的数字外,只能使用“_”。
(4)变量名不能与C++ 语言中的特定名称相同,这些名称是关键词。
(5)变量名不能与C++ 中的库函数名相同。
下面一些是合法的变量名:
29
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
wave_play
RightOn
Number12
下面是一些非法的变量名:
case 这是个关键词
3Left 以数字开头
A Num 中间带有空格
+ - Ri 带有非法字符
定义变量
2
在使用一个变量之前,必须先定义这个变量。 要定义变量,只需先注明变量的类型,再
写出变量的名称,例如:
int Number1;
float Length;
double width;
如果需要定义的变量的类型都相同,可以把相同类型的变量定义写在同一行里,中间以
逗号相隔。例如:
变量初始化
3
在定义一个变量的同时,也可以给变量一个初始值。 这个初始值是这个变量第一次使
用时的值,初始值写在变量名和“= ”的后面,例如:
int b = 200;
long a = 32700;
char string1, string2 = \n , string3 = a ;
2
24 数组
数组是一组相同变量的集合,其中的每一个变量称为数组的元素。 数组可分为一维数
组和多维数组。说明数组的一般形式为:
数据类型 数组名[元素个数]
其中数据类型说明数组元素的类型,但不能为 void 类型和函数类型。 元素个数是整型
数,用来说明数组的大小,方括号是个数组类型说明符。 在使用数组前,必须对数组进行
声明。
下面是一些对数组的声明:
一维数组包含的元素个数与其下标数一致,而多维数组包含的变量个数是其下标的连
乘积。如:
定义了数组后就在内存中为其分配了一定的存储空间,数组名表示数组存储区在内存
中的首地址,是一个地址常量。在引用数组时应该遵循下列规则:
·数组下标的最小值为 0;
·数组元素的个数为方括号中的值,有效下标为 0 到元素个数的值减 1;
·在调用数组元素时,应给数组元素赋值,否则其值无法预料,通常非常之大;
·要使用有效下标。
C++ 可以对数组进行初始化,初始化的数值依次放在一个大括号中,数据之间用逗号分
隔,数据可写在多行:
·若初始化数据的个数少于数组元素的个数,则多于数据个数的数组元素赋值为 0;
·若初始化数据的个数多于数组元素的个数,则编译时会报错;
·方括号中元素个数的值可以缺省,也可用初始化数据的个数指定该值 。
数组的初始化有许多方式,下面是对静态数组常用的初始化方式:
初始化的结果是:
多维数组初始化时,只有最左边的方括号的数字可以缺省,例如:
多维数组在存放数据时,是按一定的下标排列规律,在存储空间连续地存放数据的。 这
个排列规律是从最右边的下标开始,依次往左变化下标元素,例如,三维数组 Array23
2的存储顺序是这样的:
Array000起始内存地址、Array001、Array010、Array011、
Array020、Array021、Array100、Array101、Array110、Array
111、Array120、Array121终止内存地址。 31
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
2
25 结构
C++ 语言中提供的结构数据类型是一种复合数据类型,用于将某些相关的不同类型的数据
组织到一个新的数据结构中。结构数据类型的声明以关键字s t
rut开始,其一般形式为:
c
struct 结构名{ 数据成员说明列表 };
“结构名”是一个有效的C++ 标识符,“
数据成员说明列表”
是由变量说明语句构成的一个语
句序列,但不能在该列表中对变量进行初始化。下面的例子描述了某点平面坐标的结构:
struct double x;
double y;
s1, s2, s3;
C++ 允许在声明结构变量的同时进行初始化,例如:point s1 =15,50;
使用时,必须先把“结构名”作为类型名在程序中说明变量,下面的例子声明了两个结构
变量:point start,end;
对结构的数据成员进行处理时,必须用成员运算符 “.”来引用一个结构变量中的某个数
据成员,其一般形式为:
变量名.数据成员名
32 对结构变量中的数据成员进行初始化赋值和处理都可以采用这种方式,下面是计算两
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
点之间距离的示范代码:
float distance;
start.x = 100;
start.y = 20;
end.x = 0;
end.y = 0;
distance = sqrt(pow(start.xend.x,2)+ pow(start.yend.y,2));
由以上例子可以看出,结构的用法和其他变量没有太大区别 。 同时,结构还可以声明为
数组,或通过声明结构指针的方式方便地进行访问 。
2
26 枚举
枚举数据类型就是将变量的值都列出来的一种数据类型 。 它所定义的变量的值只能是
列出值之一。C++ 用关键字 enum 来定义枚举类型,其语法如下:
enum 枚举类型名{
<枚举标识符表> };
例如:
enum weekSunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday;
通过上面这个定义以后,C++ 编译器按定义时的顺序给枚举类型的每个标识符分配一个
整数值。如:Sunday = 0,Monday = l,Tuesday = 2 等等。 枚举类型中每个标识符必须是唯一
的,而它们的值可以不唯一。例如:
还可以对每个标识符显式地赋值和间断地赋值,例如:
2
27 联合
联合是一种特殊的结构,它的各个成员共享一个存储区。 联合的长度等于它的最大成
员的长度。C++ 用 union 来定义联合,其语法如下:
union 联合名
{ 成员列表 };
例如:
union mark
char name;
int page;
;
联合提供了快速数据转换的方法,使用联合可使几个变量共用一个存储区,这样可以节 33
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
省系统的开销。
枚举和联合对其数据成员的访问也都是使用操作符“.”来进行的。
2
28 指针
指针是C++ 语言中一种强大而有力的工具。 所谓指针是指包含另一变量内存地址的变
量。如果一个变量包含的是另一个变量的地址,可以称第一个变量指向第二变量。 如果一
个变量要存放另一个变量的地址,这个变量必须声明为指针类型的变量。 声明指针变量的
一般形式为:
int * pointer;
char * str;
float * px = &x;
#i
nclude<iostream.h>
man()
i
{ int num = 10; //声明一个整型变量 num
int * pointer = # //声明一个指针变量并把 num 所在的地址赋给 pointer
cout<< "num = "num<< "\n "; //输出 num 的值
cout<< "* pointer = "pointer<< "\n "; //输出 pointer 的值
cout<< "* pointer = "* pointer; //输出 * pointer 的值
}
程序运行结果如下:
num = 10
34 pointer = 0x0024DDEE
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
* pointer = 10
int X4;
int * pX;
pX = &X0; //等价于 pX = X;
pX 为指向 X数组的指针,表示第一个数组元素的地址。根据规则,Xi= * (pX+i)或
者(X+i)= pXi。
VC++ 支持指向结构的指针的声明和使用 。指针变量存储结构变量的地址。一旦指针拥
有结构变量的地址,就可以用操作符“- > ”来访问结构的成员,例如:
2
29 类型定义
C++ 提供关键字 typedef 来给已存在的数据类型命名一个新的名称,作为它的别名,其
语法如下:
typedef 已知类型名 新类型名;
例如:typedef int integer;
关键字 typedef 为一个已经存在的数据类型取一个别名,以便更好地说明该数据类型的用
途。读者可以取一个名字更短或者更熟悉和便于理解的名字,在程序编写、阅读时能提高效
率。经过类型定义后,新的类型名就可以在程序中使用了。例如,经过上面的类型定义,就可
以使用下面的语句来定义整型变量了,C++ 编译器将数据类型 integer 识别为 int 数据类型。
integer x,y;
2
210 运算符
C++ 语言中定义的运算符包括算术运算符 、赋值操作符、增量和减量运算符、
si
zef运算
o
符、关系和逻辑运算符以及位处理运算符等,下面将分别进行介绍。
(1)算术运算符
编译器是根据操作数的类型来执行相应类型的操作,如果两个操作数都是整型表达式, 35
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
编译器就产生整数操作的代码;如果操作数有一个或两个是浮点表达式,编译器产生浮点操
作的代码。表 2 3 列出了C++ 的算术运算符。
表2 3 算术运算符
运算符 功 能 数据类型 实 例
+ 单目加 数值 A = +B +2
- 单目减 数值 A = -B
+ 加 数值 C = A +B
- 减 数值 C = A -B
* 乘 数值 C = A*B
/ 除(取商的整数) 数值 C = A/B
% 模(取余数) 数值 C = A%B
算术表达式是一个语句中的一部分,代表的是一个数值,一般可以分为三类:
① 直接常量是最简单的算术表达式,这种只包含单一变量或常量的表达式是第一类算
术表达式。例如:
i 36
② 第二类算术表达式包含操作数和一个运算符,操作数可以是变量也可以是常量,例如:
4* x 123/543 PI+314
③ 更复杂的算术表达式包括多个运算符甚至函数,例如:
log(var)* 36
(3+y)* (8/x)- 5* y
(2)赋值操作符
赋值语句的一般形式为:变量名= 表达式,例中:
y = a*x+b;
C++ 允许在一条语句中使用多重赋值操作给多个变量赋一个值,例如:a = b = c = 50
C++ 还支持其他类型的操作符,如表 2 4 所示。
表2 4 算术赋值操作符
赋值操作符 等价的形式 举 例
x+= y x = x+y x+= 4
x-= y x = x-y x-= 10-a
x*= y x = x*y x*= 12
x/= y x = x/y x/= 5*a
x%= y x = x%y x%= 02
(3)增量和减量运算符
增量(++ )和减量 (- - )操作符是将变量中的值分别加 1 和减 1,其语法和应用举例见
36 表 2 5。
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
表2 5 增量和减量运算
举例 2(执行完下面每个独立的等式后
运算符 说 明 举例 1
的结果)(int a = 8; b = 4, d;)
变量++ 使用该变量后其值加 1 a ++ d = a ++;//d = 8,a = 9
++ 变量 使用该变量前其值加 1 ++a d = ++a;//d = 9,a = 9
变量- - 使用该变量后其值减 1 b -- d = b - -;//d = 4,b = 3
- - 变量 使用该变量前其值加 1 -- b d = - -b;//d = 3,b = 3
(4)sizeof 操作符
sizeof 操作符用于获得一种数据类型或一个变量的字节数,其语法如下:
sizeof({变量名/数据类型}) 或 sizeof变量名/数据类型}。
【例2 3】 Ex02_3 sizeof 操作符,见清单 2 3。
清单2 3 s
ize
of操作符
示范代码 运行结果显示(各类型数据字节数)
#inc
lude <iostream.h> Data type Memory Used
main() - - - - - - - - - - - - - - - - - - - -
{ short int aShort; short int 2
int anInt; integer 4
long along; long integer 4
char aChar; char 1
float aFloat; float 4
cout<< "Data type Memory Used \n ";
cout << "- - - - - - - - - - - - - - ";
cout<< "\n short int " <<sizeof(aShort);
cout<< "\n integer " <<sizeof(anInt);
cout<< "\n long integer " <<sizeof(aLong);
cout << "\n char " <<sizeof (aChar);
cout << "\n float " <<sizeof(aFloat);
cout << "\n \n \n ";
return 0;
(5)关系和逻辑运算符
关系和逻辑运算符指的是一个值与另一个值之间的关系,逻辑运算符指的是连接关系
的方式。C++ 中定义的关系和逻辑运算符如表 2 6 所示。
关系和逻辑运算符的优先级比算术运算符低 。 例如,6> 4+ 5 等价于 6> (4+ 5),其结果为
假。C++ 不支持预先定义的布尔标识符,而是把 0 当作假,把非 0 当作真。 在 VC 中可以用系
统预定义的布尔标识符 TRUE 和 FALSE 分别代表 1 和 0。
表2 6 关系和逻辑运算符
运算符 功能 举 例 运算符 功能 举 例
&& 逻辑与 if (i>1&&i<6) <= 小于等于 if(i<= 5)
|| 逻辑或 if (I = = 1||I = = 6) >= 大于等于 if(i>= 1)
! 逻辑非 if (!(i>1&&i<6) == 等于 if(i = = \o )
< 小于 if (i<6) != 不等于 if(i!= \n \)
> 大于 if (i>0) ?: 条件赋值 if(i<1)? 1:i
注意:不要误将赋值操作符“= ”当作关系操作符“= = ”使用。 37
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(6)位处理运算符
位处理运算符用来对一个字节或字的位进行开关、设置、查询和移位,具体如表 2 7
所示。
表2 7 位处理运算符
运算符 功 能 举 例 运算符 功 能 举 例
& 逐位与 i&12 ~ 逐位非 ~i
| 逐位或 i|58 << 逐位左移 i<<1
逐位异或 i 12 >> 逐位右移 i>>2
3 流程控制语句
2
语句是C++ 语言中最小的可执行 单 位,程 序 流 程 的 控 制 是 通 过 控 制 语 句 来 完 成 的,
主要的流程有分支和循环 。 C++ 的流程控制语句决 定 程 序 执 行 时 的 结 构 。 这 些 流 程 控
制语句主要包括:表达式和块语句 、选择语句 、switch 语句 、循环语句和辅助流程控制的
转移语句 。
2
31 表达式语句和块语句
大多数的C++ 语句是表达式语句。 一个简单的表达式语句由表达式后面加上分号组
成。例如:a = b+c;
块语句是以 {开始,以 }结 尾 的 语 句 。 在 {和 }之 间 封 装 着 一 系 列 的 语 句 。 这 些 语 句
可以是C++ 中的任何一 种 句 子 。 块 语 句 在 语 法 上 等 价 于 一 个 单 语 句,可 以 用 于 任 何 单
语句可以使用的地方 。 它与单语句的主要区别在于其后不加分号 。 下面是一个块语句
的例子:
{int A = 34,B = 35,sum;
sum = A+B;
2
32 选择语句
C++ 中提供了 if 选择语句,用于在条件满足时执行某些操作。 if 语句包括以下两种
形式:
(1)if 语句(单选条件判断语句)
其语法为:
if(条件表达式)
块语句;
如果 if 后面的条件表达式的逻辑值为真,则执行后面的块语句,否则不执行,例如:
if(i> 0)
i = j; //如果 i> 0 的话,将 j 的值赋给 i。
38 注意:如果需要处理的块语句只有一条语句的话,则可以不加大括号。
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
(2)if_else 语句(双选择的条件判断语句),其语法为:
if(条件表达式)
块语句 1;
else
块语句 2;
C++ 还允许条件判别语句的嵌套来产生一个多选择的判别语句,其语法为:
if(条件表达式 1)
块语句 1;
else if(条件表达式 2)
块语句 2;
else if(条件表达式 3)
块语句 3;
…
else if(条件表达式 n)
块语句 n;
else
块语句 n+1;
测试每一个条件表达式,哪一个的逻辑值为真,就执行哪一条块语句,若所有的逻辑值
都为假,则执行最后一个 else 下的块语句。
2
33 sw
ith分支语句
c
switch 语句可以根据当前的条件进行分支处理 。它将一个表达式的值与某些常量进行
连续比较,然后执行多分支程序的一支。switch 语句的一般形式是:
switch(表达式)
{
case 常量 1:
语句列表 1
break;
case 常量 2:
语句列表 2
break;
case 常量 n:
语句列表 n
break;
default:
语句列表 n+1 39
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
break;
在 switch 语句中,表达式的值类型和常量的类型必须一致。 当表达式的值与 case 中
指定的常量相等时,执行该 case 后的语句,直到遇到 break 语句或到达 switch 语句末尾。
如果不出现相等匹配的情况,则执行 default 后的语句。 无 default 时,如果所有匹配都失
败,程序直接执行 switch 语句之后的语句。
2
34 循环语句
循环语句是在给定条件成立的情况下,重复执行某个程序段,重复执行的程序段称为循
环体,循环体可以是单语句或块语句。 C++ 中的循环语句包括 wh
ile、
dowh
ile和f
or语句。
具体用法和区别见表 2 8。
表2 8 循环语句
2
35 转移语句
转移语句包括 break 语句、continue 语句和 goto 语句。具体用法和区别见表 2 9。
表2 9 转移语句
通 常 与 循 环 语 句 混 合 使 用。
可以用于 switch 语句中,还可
程序 遇 到 continue 语 句, 跳 过 不受限制地转向程序中
作用 用于循环语句的循环体中,以终
continue 语句后的其余语句,开 的任何地方
止、跳出循环体
始进入下一次循环
40
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
续 表
break 语句 continue 语句 goto 语句
4 函数
2
函数是由功能相关的语句序列所组成的独立模块 。描述一个函数所执行的算法的过程
称为函数定义,而使用一个函数的过程称为函数的调用 。 函数调用完成后,一般情况下要向
它的使用者返回一个值,这个值称为函数返回值。下面将介绍在C++ 程序中进行函数定义和
调用的方法。
2
41 函数定义
在程序中使用函数必须先进行函数定义 。函数定义为:
返回类型是表示返回值的类型,如果没有设置返回值的类型,系统自动设置返回值类型
为整型。函数的返回值是通过 return 语句获得的,如果在设计函数时不希望返回任何值
时,那么应将该函数的类型设置为 void 型。
参数 1,参数 2 这些量称为函数的形参,用于接受调用参数时传送给这个函数的数据。
参数表可以为空,但其后的圆括号不能省略。
函数体是一个语句系列,描述这个函数所要进行的操作。
【例2 4】 Ex02_4 函数定义,见清单 2 4。
清单2 4 函数定义
d
oub
led istane(
c d oubl
ex1d oubley1d oublex2d o
ubl
ey //计算平面上两点的距离
2)
{ double dis = 0;
dis = dis+sqrt(pow(x1-x2,2)+pow(y1-y2,2));
return dis;
2
42 函数的参数传递
在函数未被调用时,函数的形参并不占有实际的内存空间,也没有实际的值。 只有在函
数被调用时才分配形参的存储单元,并将实参与形参结合。 函数的参数传递指的就是形参 41
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
与实参结合(简称形实结合)的过程,形实结合的方式有值调用和引用调用 。
(1)值调用
值调用是指当发生了函数调用时,给形参分配内存空间,并用实参来初始化形参 (直接
将实参的值传递给形参)。这一过程是参数值的单向传递过程,一旦形参获得了值便与实参
脱离关系,此后无论形参发生了怎样的改变,都不会影响到实参。
(2)引用调用
值调用时参数是单向传递,那么如何使在子函数中对形参做的更改对主函数中的实参
有效呢? 这就需要使用引用调用。
引用是一种特殊类型的变量,可以被认为是另一个变量的别名,通过引用名与通过被引
用的变量名访问变量的效果是一样的 。
【例2 5】 Ex02_5a 值调用和 Ex02_5b 引用调用,见清单 2 5。
清单2 5 值调用和引用调用
2
43 局部变量和静态变量
函数的局部变量仅当函数被调用时才存在,一旦函数被终止,系统就把局部变量删除。
函数每次被调用时,系统会对局部变量进行初始化。例 2 4 中 dis 为局部变量。
函数的静态变量是在局部变量的数据类型前加上 static 关键字,它的初始化仅能执行
42 一次。当函数终止时将静态变量保存在独立的内存单元中,所以下一次调用该函数时,静态
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
变量能保持它原来的值。可以在不同的函数中使用相同的静态变量名,这不会造成编译器
的混淆,因为编译器一直跟踪每个函数的静态变量 。
【例2 6】 Ex02_6 静态变量,见清单 2 6。
清单2 6 静态变量
d
oubled istane(
c doublex 1doubl
ey 1doubl
ex 2doub
l 2)
ey //计算平面上两点的距离
{static double dis = 0;
dis = dis+sqrt(pow(x1-x2,2)+pow(y1-y2,2));
return dis;
2
44 内联函数
当程序调用内联函数时,是将该内联函数展开并直接插入到调用处,而一般函数的调用
是转到被调用函数体中,执行完函数后,退回到调用函数语句的下一句。 所以,内联函数可
以加快代码的运行速度,但是以增加程序的代码为代价。 因此,对于一些功能简单、规模较
小又使用频繁的函数,可以设计为内联函数。内联函数定义的语法为:
2
45 函数重载
函数重载是C++ 语言特有的。两个以上的函数取相同的函数名,但是形参的个数或者类
型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动调整调用哪一个函数,这就
是函数的重载。
【例2 7】 Ex02_7 函数重载,见清单 2 7。
清单2 7 函数重载
# include<iostream.h> //函数定义
//函数声明 in
tadd(intx i
n ty)
int add(int x,int y); {
double add(double x,double y); return x+y;
vo
i dma in()
{int a = 1; do
ublea dd(doubl
ex
doub
ley)
int b = 2; {
int result1; return 2*x+y;
double c = 31;
double d = 25;
double result2
43
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
//调用函数 执行该程序后的结果是:
result1 = add(a,b); After using the Function
result2 = add(c,d); result1 = 3
//显示执行函数后的值 result2 = 87
cout<< "After using the Function \n ";
cout<< "result1 = "<< result1<< "\n ";
cout<< "result2 = "<< result2<< "\n ";
2
46 函数模板
很多情况下,我们设计的算法可以处理多种数据类型,但是用函数实现算法时,即使设
计为重载函数也只是使用相同的函数名,函数体仍然要分别定义。 请看下面两个求绝对值
的函数:
int abs(int x)
{ return x<0?-x:x;
double abs(double x)
{ return x<0?-x:x;
这两个函数只是参数类型不同,功能完全一样,使用函数模板便可以避免函数体的重复
定义。函数模板可以用来创建一个通用功能的函数,以支持多种不同类型的形参,简化重载
函数的函数体设计。函数模板的定义形式是:
template<typename 标识符>
类型名 函数名(参数表)
{函数体定义}
【例2 8】 Ex02_8 定义一个求绝对值的函数模板,见清单 2 8。
清单2 8 函数模板
2
47 多态性和虚函数
44 对象在接收到同样的函数调用时,会产生不同的执行动作,这样的功能叫多态性。 C++
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
语言中,多态性具有两种形态:编译多态性和运行多态性。 使用重载函数可以获得编译多态
性,使用继承和虚函数可以获得运行多态性 。多态性的实现和联编有关。C++ 中有两种联编
方式:静态联编和动态联编。静态联编时,编译器可以根据调用时使用的实参,在编译时就
确定调用的函数;动态联编时,虚函数对多态性进行支持。 要获得多态性的对象,必须建立
一个类等级,然后在派生类中重新定义基函数,就可获得多态性对象。
虚函数定义的语法为:
v
oidma i n()//主函数 程序运行结果:
{ BaseC * pp; 情况一结果:两次都显示“Basec display”
BaseC x 情况二结果:第一次显示 “Basec display”第二
Derived y; 次显示“Derived display”
pp = &x; 原因:在情况一中,基类的 display()函数没有
pp- >display(); 定义为虚函数,所以基类的指针始终调用基类的
pp = &y; 函数。
pp- >display(); 在情况二中,基类的 display()函数定义为虚函
} 数,所以基类的指针可以调用派生类的函数
45
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
5 类和对象
2
2
51 类的定义和声明
类(class)是一种用户自定义的数据类型。它概括了一组对象的共同性,包括相同的数
据和函数组成。C++ 语言中,类的说明关键词是 class 开始,类说明的内容在花括号 {和 }之
间。C++ 说明类的一般方法见表 2 10。
表2 10 类的定义
c
las类名
s c
las
smy base
{ private: private:
私有数据和函数 float a;
protected: protected:
保护型数据和函数 int b;
public: public:
公有数据和函数 double c:
} float Geta();
int Getb();
}
在类中可以包含以下三种类型数据和函数,见表 2 11。
表2 11 访问权限
成 员 类 型 访问权限说明
公有类型(public) 声明为公有的成员可以被程序中的任何代码访问
类本身的成员函数可以访问
保护类型(protected) 友元类的成员函数可以访问
派生类的成员函数可以访问
类本身的成员函数可以访问
私有类型(private)
友元类的成员函数可以访问
在类中,数据成员可以像在C++ 语言那样进行说明,但不能在类中对数据成员进行初始
化,如赋值等操作。类说明的数据成员描述了对象的内部结构,其中的成员函数用于对这些
对象进行操作。但类中对这些成员函数进行了函数说明,还必须在程序中定义这些成员函
数的实现。定义成员函数的一般形式见表 2 12。
表2 12 成员函数的定义
定义成员函数的一般形式 成员函数说明举例
注意:“::”是作用域运算符,“类名::”用于表明其后的成员函数名是在“类名”中说明的。“函数体”中的成员函数描
46 述成员函数对数据成员的操作。
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
在类中定义的函数,都必须在定义类后给出定义,同时这些函数的操作数据只能是这个
类中的数据成员。在很多情况下,类必须具有构造函数和析构函数。 对类中数据成员的初
始化,是在类 中 的 构 造 函 数 中 完 成 的。 如 果 一 个 类 在 它 的 生 存 期 中 创 建 了 一 块 不 能 被
Windows 操作系统自动释放的内存,必须在类的析构函数中将这块内存释放掉,把资源还给
Windows。
2
52 对象
(1)对象的定义
与结构一样,类也是一种数据类型。 要想使用类,还必须声明对象。 对象是类的实例。
下面的语句声明了一个类变量(或类对象),其中 mybase 为类名(见表 2 10),而 A1 为类变量
(或对象):
mybase A1;//声明类对象 A1
对象声明后即可使用,使用方法为:
对象名.成员函数名 或 对象名.数据成员名
(2)类对象的访问权限
类的成员函数可以访问类的所有成员,没有任何限制,而类的对象对类的成员的访问是
受成员访问控制符制约的。如定义了一个表 2 10 的类 mybase。
#incl
ude<i
ost
ream.h>
//建立一个描述位置的类
cl
assl
oca
tion //lo
cat
ion类
{ private: 47
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
int x,y;
public:
void init(int i,int j){x = i; y = j;//内联函数
int getX();
int getY(){return y;
;
i
ntlocat
ione
gtX()
{return x;
//类应用举例
vo
idmai n()
{ int x,y;
location A,B;
A.init(5,8);
B.init(7,3);
x = A.getX();
y = B.getY();
cout<<x<< "\n" <<y<<endl;
例题输出结果为 5、3。
2
53 构造函数和析构函数
(1)构造函数
构造函数是类的一个特殊的公有成员函数,它与类同名,并且没有返回值。 构造函数的
作用就是在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态,使此对
象具有区别于彼对象的特征。C++ 在创建一个对象时,会自动调用类的“构造函数”完成初始
化成员变量的操作。
(2)析构函数
析构函数是类的一个公有成员函数,它的名称是由类名前加 “~ ”构成,也没有返回值。
析构函数与构造函数的作用几乎正好相反,它用来完成对象被删除前的一些清理工作,也就
是专门作扫尾工作的。一般情况下,析构函数是在对象的生存期即将结束的时刻由系统自
动调用的。它的调用完成之后,对象也就消失了,相应的内存空间也被释放。
2
54 继承和派生
自然界中,许多事物具有共同的特征,但普遍存在一些细微的差别。 使用层次分类的方
法来描述事物间的相同点和不同点是很方便的 。 C++ 语言中使用了这种层次分类的方法。
在面向对象的语言中,继承是一个重要的机制。它使用层次分类的方法,在一个一般类的基
础上建立新类。这种新类既具有继承下来的一般类的特征,也具有新的属于自己的特征。
在C++ 中,一个类从另一个类继承特征成为派生类,一般被继承的类称为基类。 这个过程可
以无限地进行下去,从而得出一个类的等级结构。
C++ 中有两种继承方式:单一继承和多重继承。在单一继承中,派生类只有一个基类,而
在多重继承中,派生类可以有多个基类。派生类继承了基类中所有的数据成员和成员函数,
48 同时又增加了新的数据成员和成员函数。 在这过程中,单一继承和多重继承有着不同的
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
特性。
(1)单一继承(单一继承中,派生类的一般形式见表 2 13)
表2 13 在单一继承中,派生类的一般形式
在单一继承中,派生类的一般形式 单一继承的例子
c
lass类名1:访问控制 类名2 c
lassextendpubl
icmy base
{private: private:
私有数据和函数 int extdata;
protected: public:
保护型数据和函数 int GetExtData();
public: }
公有数据和函数
}
public public
public protected protected
private private
public private
private protected private
private private
(2)多重继承
C++ 语言中允许一个类从多个类中派生出来,使这一个类具有多个基类的功能 。多重继
承的访问权限和单一继承的访问权限规则一样 。多重继承可以视为单一继承的扩展。 下面
是一个类从多个类派生的一般形式:
成员说明列表
}
2
55 t
his指针
一个类可以定义多个对象实例,每个对象都有自己的数据成员,但不论程序中定义多少
个对象实例,其成员函数只有一个。但编译程序是如何确定调用成员函数的对象,并处理其
相应的数据成员的呢? 这里就涉及 this 指针。
this 指针是一个特殊的隐含指针,它隐含于每一个类的成员函数中,也就是说,每个成
员函数都有一个 this 指针参数,指向调用该函数的对象。
this 指针只能在类的成员函数中使用,它指向该成员函数被调用的对象。 静态成员不
能访问 this 指针,因为静态成员函数不从属于任何对象 。this 指针一般用于返回当前对象
自身。
使用 this 指针时应该注意的问题是:this 指针是一个 const 指针,成员函数不能对其
进行赋值。
6 常类型 (
2 con
st)
常类型是指使用 const 声明的类型 。 变量或对象被声明为常类型后,其值就不能被更
新,因此声明常类型时必须初始化 。 虽然数据隐藏保证了数据的安全性,但各种形式的数
据共享却又不同程度地破坏了数据的安全性 。 因此,对于既要共享又需要防止改变的数
据应该声明为 常 量,以 利 于 保 护 。 常 类 型 主 要 有 常 对 象 、常 引 用 、常 成 员 函 数 和 常 数 据
成员 。
2
61 常引用
使用 const 关键字声明的引用被称为常引用 。 常引用所引用的对象不能被更新,常
引用声明时,必须同时进行初始化 。 常引用的声明如下:const < 类 型 标 识 符 > & < 引 用
名>
【例2 11】 Ex02_11 常引用例题,见清单 2 11。
50
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
清单2 11 常引用
# include<iostream.h> # include<iostream.h>
v
oi dma i n() v
oi dma in()
{ int n = 6; { int n = 6;
const int &m; const int &m = n;
m = 10; cout<< "m = "<<m<<endl;
cout<< "m = "<<m<<endl;
由此可见,常引用的值不能被更新。因此常引用声明时,必须同时进行初始化。
如果用常引用作形参,便不会发生对实参意外的更改。
【例2 12】 Ex02_12 常引用作形参例题,见清单 2 12。
清单2 12 常引用作形参
#inc
lude<i o
stre
am. h> 程序运行结果为:x = 75
v
oiddis l
pya (co
nst doube&x);
l //常引用作形参
v
oidma in()
{ double d = 75;
display(d);
}
v
oiddisplay(co
nstd oube&x)
l
{ cout<< "x = "<<x<<endl;
常引用作形参时,函数不能更新 x 所引用的对象,因此,对应的实参就不会被破坏。
2
62 常对象
使用 const 关键字声明的对象被称为常对象 。常对象的声明如下:
声明常对象的同时,也要进行初始化,声明常对象例题见清单 2 13。
清单2 13 常对象
c
lasA//类定义
s
{ private:
int x,y;
public:
A(int i = 0,int j = 0){x = i;y = i
//声明常对象
const A acobj(1,2);
A const bcobj(3,4)
51
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
2
63 常成员函数
使用 const 关键字声明的成员函数被称为常成员函数 。常成员函数的声明如下:
说明:
(1)const 是加在函数声明后面的类型修饰符,它是函数类型的一个组成部分,因此在
实现部分也要带 const 关键字。
(2)const 关键字可以被用于对重载函数的区分 。
(3)常成员函数不能更新对象的数据成员,也不能调用该类中没有用 const 修饰的成员
函数。
(4)如果将一个对象说明为常对象,则通过该对象只能调用它的常成员函数,而不能调
用其他成员函数。
【例2 13】 Ex02_13 常成员函数,见清单 2 14。
清单2 14 常成员函数
//常成员函数 程序运行结果:
#inc
lude<iostream. h> 成员函数: x = 1,y = 2
cla
ssA// 类定义 常成员函数:x = 3,y = 4
{ private:
int x,y;
public:
A(int i = 0,int j = 0){x = i;y = j;
vo
idf un() //成员函数
{ cout<< " 成员函数:x =" <<x<< " ,y =" <<y<<endl;
vo
idf un()c ons
t//常成员函数
{ cout<< " 常成员函数:x =" <<x<< " ,y = " <<y<<endl;
;
//主函数
voidma in()
{ A aobj(1,2);
aobj.fun();
const A bobj(3,4);
bobj.fun();
}
在 A 类中声明了两个公有成员函数 fun(),其中一个是常成员函数。在主函数中说明了
两个对象 aobj 和 bobj,其中对象 bobj 是常对象。 通过对象 aobj 调用的是一般的成员函
数,而通过对象 bobj 调用的是 const 修饰的常成员函数。
2
64 常数据成员
使用关键字 const 不仅可以说明成员函数,还可以说明数据成员 。 如果在一个类中
52 说明了常数据成员 (包括常引用 、常对象等 ),由于常数据成员不能被更新,因此,在类中
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
说明数据成员时,只 能 用 成 员 初 始 化 列 表 的 方 式 通 过 构 造 函 数 对 该 数 据 成 员 进 行 初
始化 。
【例2 14】 Ex02_14 常数据成员例题,见清单 2 15。
清单2 15 常数据成员
#incl
ude<iostream. h> 程序运行结果:
c
lasA//类定义
s x = 1,y = 5,z = 1
{ private: x = 3,y = 5,z = 3
const int x;
static const int y;//静态常数据成员
public:
const int &z;//常引用
A(int i):x(i),z(x){} //常数据成员通过初始化列表获得初值
vo
idf un()//成员函数
{ cout<< " x =" <<x<< " ,y =" <<y<< " ,z =" <<z<<endl;
;
co
n s
tin
tA y=5 //静态常数据成员的初始化
vo
idmain()//主函数
{ A aobj(1),bobj(3);
aobj.fun();
bobj.fun();
}
7 运算符重载
2
C++ 语言中已有的运算符允许重新定义(即重载)。 运算符重载是通过编写函数定义实
现的。函数定义虽然也包括函数头和函数体,但是函数名是由关键字 operator 和其后要重
载的运算符符号组成的。例如,函数名 Operator+ ()重载了运算符+ 。
【例2 15】 Ex02_15 重载复数的四则运算符。
复数由实部和虚部构造,可以定义一个复数类,然后再在类中重载复数四则运算的运算
符。见清单 2 16。
清单2 16 重载四则运算的运算符重载
#incl
ude<io
str
eam. h>
c
las
sc ompl
ex
public:
c
omplex ()
{ real = imag = 0;
c
ompl
ex( doubler do ublei)
{ real = r; imag = i;
complex operator+ (const complex &c);
complex operator- (const complex &c);
complex operator * (const complex &c);
53
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
v
oidma in()//主函数 该程序的运行结果为:
{complex c1(20,30),c2(40,- 20),c3; c1+ c2 = 6+ 1i
c3 = c1+ c2; c1- c2 = - 2+ 5i
cout<<"\nc1+ c2 =" ; c1* c2 = 14+ 8i
print(c3); c1/c2 = 045+ 08i
c3 = c1- c2; (c1+ c2)* (c1- c2)* c2/c1 = 961538+ 252308i
cout<<"\nc1- c2 =" ;
print(c3);
c3 = c1 * c2;
cout<<"\nc1* c2 =" ;
print(c3);
c3 = c1/c2;
cout<<"\nc1/c2 =" ;
print(c3);
c3 = (c1+ c2)* (c1- c2)* c2/c1;
cout<<"\n(c1+ c2)* (c1- c2)* c2/c1 =" ;
print(c3);
cout<<endl;
<类名> operator<运算符> (
<参数表> )
其中,operator 是定义运算符重载函数的关键字 。
程序中出现的表达式:
c1+c2
编译程序将给解释为:
54 c1.operator+ (c2)
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
2 /O(输入/输出 )流结构
8 I
C++ 的 I/O 是以字节流的形式实现的,流实际上就是一个字节序列 。 在输入操作中,
字节从输入设备 (如键盘 、磁盘 、网络连接等 )流向内存;在输出操作中,字节从内存流向输
出设备 (如显示器 、打印机 、磁盘 、网络连接等 )。 传统 C 语言的输入/输出函数中最常见的
是 printf 函数和 scanf 函数 。 其中,printf 函数是输出函数,scanf 函数是输入函数 。 在
C++ 语言中,仍然可以使用这种方法进行输入/输出 。 但是,C++ 语言提供了更为方便的输
入/输出处理功能,即输入/输出流 (I/O stream),Visual C++ 中,I/O 流的类结构如图 2 6
所示 。
图2 6 I
/O 流结构
的输出,称为流插入运算符;重载的右移运算符 ( >> )表示流的输入,称为流提取运算符。 这
两个运算符可以和标准对象 cin cout cerr 和 clog,以及用户自定义的流的对象一起使用 。
、 、
cout 是类 ostream_withassign 类的一个对象,它与标准输出设备 (通常指显示设备 )连
在一起,必须结合操作符<< 使用。 下面的语句用流插入运算符把整型变量 x 的值从内存输
出到标准输出设备上。
cout << x;
cout 最大的特点是输出的数据可以是各种数据类型,包括整型、实型、字符型等,在使用
该语句时不必考虑待输出数据的类型,也不必有相应的格式控制符 (如 printf 函数中的% d
和%c 等)。这样,无论输出变量的类型是否做了修改,cout 语句都完全一致。
cin 是派生类 istream_withassign 类的一个对象,它与标准输入设备 (通常指键盘 )连
在一起,必须结合操作符>> 使用。下面的语句用流提取运算符把整型变量 x 的值从 cin 输
入到内存中。
cin >> x;
在输入数据时,不论哪种数据类型,使用的格式都相同。
9 异常处理
2
异常(Exception)是程序可能检测到的、运行时刻不正常的情况。运行不正常的情况包
括被 0 除、数组越界和内存耗尽等。这样的异常独立于正常函数之外,并且需要程序立即停
止响应处理异常。
C++ 程序使用 try,throw 和 catch 关键字来处理异常。try 块用来标志运行时可能遇到
问题的代码。catch 块紧跟在 try 块后面,用来保存遇到异常时的处理代码。 throw 语句用
于将程序代码运行时遇到的异常通知异常处理代码 。
异常处理代码所使用的机制实际上相当简单 。先把要监督的有可能发生错误的代码放
在 try 程序块内部,接着构造 catch 程序块作为错误处理者。如果在 throw 块中的代码抛出
了一个异常,try 块立即停止执行,接着执行 catch 块中的部分。
2
91 异常处理的语法
(1)throw 语法
throw <表达式> ;
try
复合语句
}
因为被调用的函数可以抛出异常,所以主调函数必须能够响应这一事件 。 这种响应
56 通过捕获语句实现 。 为了捕获异常需要将代码放到 try 块中 。 try 块内的所有语句通常
第二章 C++语言基础
……………………………………………………………………………………………………………………………………………
catch(异常类型 1 参数 1)
{ 针对异常类型 1 的处理语句
}
catch(异常类型 2 参数 2)
{ 针对异常类型 2 的处理语句
}
…
catch(异常类型 n 参数 n)
{ 针对异常类型 n 的处理语句
}
2
92 异常处理的执行过程
异常处理的执行过程如下:
(1)通过正常的顺序执行到达程序的 try 语句,然后执行 try 块内的保护段。
(2)如果在执行 try 块内的保护段期间没有引起异常,那么跟在 try 块后的 catch 语句
就不执行,程序从异常抛掷的 try 块后跟随的最后一个 catch 语句后面的语句继续执行
下去。
(3)如果在保护段执行期间或保护段调用的任何函数中 (直接或间接的调用 )有异常被
抛掷,则从 throw 创建的对象中创建一个异常对象 。catch 处理程序按其在 try 块后出现的
顺序被检查。如果没有找到合适的处理程序,则继续检查下一个动态封闭的 try 块。 此处
理继续下去,直到最外层的封闭 try 块被检查完。
(4)如果找到了一个匹配的 catch 处理程序,catch 处理程序被执行,接下来程序跳转
到跟随在处理程序之后的语句。
(5)如果找不到一个匹配的 catch 处理程序,则程序失败,终止程序。
【例2 16】 Ex02_16 程序中的内存分配异常,见清单 2 17。
程序清单 2 17 给出了一个异常处理的程序,它分配了一些内存,接着又立即删除这些
内存。因为内存分配可能会失败,所以把内存分配的代码包含在 try 程序块中。 如果内存
分配返回的指针是 NULL,则 try 块抛出一个异常。在本例中异常对象是一个字符串 。
清单2 17 内存分配异常的处理 57
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
# include <iostream.h>
i
ntma in()
{ int * buffer;
try
buffer = new int256;
if(buffer = = NULL)
throw "内存分配失败! "
else delete buffer;
catch(char* exception)
{ cout<<exception<<endl;
return 0;
程序运行说明:
当程序运行时抛出了异常时,程序的执行将跳到 catch 块的第一行 (在 try 块中的剩余
代码不被执行),catch 块的第一行仅打印输出一条消息,然后执行函数的 return 行,结束
程序。
【例2 17】 Ex02_17 除数为 0 的异常处理,见清单 2 18。
清单2 18 除数为零的异常处理
# include<iostream.h>
int Divide(int x,int y) 程序运行后的结果为:
v
oi dma in() 8/2 = 4;
{ try 除数为零!
cout<< "8/2 = "<<Divide(8,2) <<endl; 这是最后一句!
cout<< "8/0 = "<<Divide(8,0)
<<endl;
cout<< "8/1 = "<<Divide(8,1)
<<endl;
catch(int)
{ cout<< "除数为零! "<<endl;
cout<< "这是最后一句! "<<endl;
i
ntD i
vide(i
ntx inty)
{ if(y = = 0)
throw y;
return x/y;
58
第三章 ndows应用程序
Wi
编程与 MFC
在 Windows 操作系统下,应用程序主要是以窗口的形式存在。 在不同的窗口间传递信
息,就成了 Windows 操作系统和应用程序间、不同的应用程序间互相交流的主要形式。 在
DOS 等控制台模式程序中,对诸如鼠标、键盘等的控制是通过轮询(分别查询这些设备的输入
请求)完成的。而在 Windows 环境中,这些控制是通过消息完成的,因此 Windows 也被称为
“基于事件的,消息驱动的”操作系统。
Visual C+ + (以 下 简 称 VC+ + )开 发 面 向 对 象 应 用 程 序 ,主 要 有 两 种 手 段 :一 种 是 使
用 Windows 提 供 的 Windows API 函 数 , 另 一 种 方 法 是 直 接 使 用 Microsoft 提 供 的 MFC
类库。
API 是应用程序编程接口 (Application Programming Interface)的缩写,Windows API
为 Windows 系统和 Windows 应用程序间的标准程序接口,它为应用程序提供系统的各种
特殊函数及数据 结 构 定 义,Windows 应 用 程 序 可 以 利 用 上 千 个 标 准 API 函 数 调 用 系 统
功能 。
MFC(Microsoft Foundation Class 微软基础类库)类库封装了大部分 Windows API 函数,
所包含的功能涉及整个 Windows 操作系统。它为 Windows 消息响应机制提供了比较完整的
处理功能,构成 MFC 类库的很多基类都具有常用消息的函数 。MFC 不仅提供了 Windows 环境
下应用程序的框架,而且提供了创建应用程序的组件。 使用 MFC 类库和VC++ 提供的高度可
视的应用程序开发工具,可以简化应用程序的设计,缩短开发周期,并且使代码的可靠性和
可重用性得到了很大提高。
本章要点:
* MFC 类库
* MFC 创建 Windows 程序的过程
* Windows 程序编程机制
* 消息映射与消息传递
1 MFC 类库
3
在VC++ 20 以上版本中,Microsoft 公司已经推出了 MFC 类库,并在 MFC 类库中提供了
一种用于基本 Windows 应用程序的结构。在面向对象的程序设计中,MFC 提供了功能完善的
应用程序框架,而且提供了以类为基础的代码块 。
MFC 完整地封装了 Windows API 函数;MFC 支持多线程;MFC 库具有同以 C 语言为基础的
使用 Windows API 开发的 Windows 应用程序的共存能力,在同一程序中,可以同时使用 MFC 59
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
3
11 MFC 基础类库
MFC 类基础类库如下所示。
(1)根类
MFC 类库中大部分类都是由 CObject 根类派生的。
(2)应用程序结构类
CCmdTarget 类是 CObject 的子类,它是 MFC 库中所有具有消息映射属性的基类。 消息
映射规定了当一对象接收到消息命令时,应调用哪一个函数对该消息进行处理。 程序员很
少需要从 CCmdTarget 类中直接派生出新类,往往都是从它的子类中派生出新类 。图 3 1 为
应用程序结构类的主要分层继承关系 。如:线程类 (CWinThread)、应用程序类 (CWinApp)、窗
口类(CWnd )、视 类 (CView )、框 架 窗 口 类 (CFrameWnd )、文 档 类 (CDocument )、文 档 模 板 类
(CDocTemplate)等。
图3 1 应用程序结构类
(3)图形处理类
(4)简单数据类型类
(5)数组、列表和映射类
(6)文件和数据库类
(7)Internet 和网络类
(8)OLE 类
(9)调试和异常处理类
MFC 各类的具体展开见图 3 2。
3
12 COb
j t类
ec
CObject 类是 MFC 类库的首要基础类,大部分的 MFC 类都是由 CObject 派生的。 由于
CObject 类描述了几乎所有 MFC 中其他类的一些共有特性,因此,有必要了解一下 CObject
类。CObject 类的声明在头文件 afx.h 中,该类的部分定义见清单 3 1。
60
第三章 Wi ows应用程序编程与 MFC
……………………………………………………………………………………………………………………………………………
nd
61
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
清单3 1 COb
jet类的定义
c
…
c
lassCo bject
public:
virtual CRuntimeClass* GetRuntimeClass()const;
public:
BOOL IsSerializable()const;
BOOL IsKindOf(const CRuntimeClass* pClass)const;
//Overridables
virtual void Serialize(CArchive& ar);
# if defined(_DEBUG)|| defined(_AFXDLL)
//Diagnostic Support
virtual void AssertValid()const;
virtual void Dump(CDumpContext& dc)const;
# endif
//Implementation
public:
static const AFX_DATA CRuntimeClass classCObject;
# ifdef _AFXDLL
static CRuntimeClass* PASCAL _GetBaseClass();
# endif
;
从以上代码可以看出,CObject 包含了支持一系列基本操作的成员函数,为派生类提供
了一些基础服务,主要包括以下几方面。
(1)串行化(串行类)
串行化是指对象将其状态保存在一个字节流中并用该字节流重建对象的能力 。 如果一
个对象支持串行化,那么它就可以保存到一个文件中 。具体见第 8 章。
(2)运行期的类型信息(Dynamic 和 DynCreate 类)
当一个类支持 MFC 的运行期类型信息 (RTTI)时,它能响应对它的名字和它的基类的请
求。CObject 的成员函数 IsKindOf 用来支持运行时类信息。 IsKindOf 用于判断对象是否
属于或派生于指定 的 类,参 数 为 CRuntimeClass 对 象,可 以 用 RUNTIME _ CLASS 宏 来 得 到。
RUNTIME_CLASS 宏的参数是类名。如果对象是指定类的成员或是指定类的派生类的成员,函
数返回 TRUE。
例: CMyPainterDoc * CMyPa
i nterView GetDocumen t()
{ ASSERT(m_pDocument- > IsKindOf(RUNTIME_CLASS(CMyPainterDoc)));
return (CMyPainterDoc* )m_pDocument;
若要支持一个 Dynamic 形式的运行期类型信息(RTTI)时,首先要从 CObject 类间接或直
接派生自己的类。然后在类的声明中使用 DECLARE_DYNAMIC 宏,而在类的实现中 (* .cpp 文
件)使用 IMPLEMENT_DYNAMIC 宏。
Dynamic 类的一个扩展类是 DynCreate。 若要支持动态类的创建,首先要从 CObject 类
62 间接或直接派生自己的类。 然后在类的声明中使用 DECLARE_DYNCREATE 宏,并定义一个无
第三章 Wi ows应用程序编程与 MFC
……………………………………………………………………………………………………………………………………………
nd
例://类的声明
class CMyPainterDoc: public CDocument
public:
# ifdef _DEBUG
virtual void AssertValid()const;
virtual void Dump(CDumpContext& dc)const;
# endif
//类的实现
# ifdef _DEBUG
void CMyPainterDoc::AssertValid()const
CDocument::AssertValid();
ASSERT(m_no!= 0)
}
例:
v
oidCMyPain
terDocDump( CDumpCon
tex c)c
t&d ons
t
CDocument::Dump dc ;
( )
dc << "姓名: "<< m_name << "\n "<< "学号: "<< m_no << "\n ";
Dump 的输出会被送到输出窗口。
除了使用成员函数 Dump 外,还可以使用 TRACE 宏将输出写入调试环境。 TRACE 宏可以
跟踪程序运行时变量的值。TRACE 宏接收包括位置占位符在内的字符串,使用与 C 程序中的
printf 函数相同的变量输出方式。 63
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
例:int i = 1;
char sz= "one "
TRACE("Integer = %d,string = %s\n ",i,sz);
2 利用 MFC 创建 Wi
3 ndows应用程序框架
使用 MFC AppWizard 创建基于 MFC 的 Windows 应用程序框架时,AppWizard 显示一系列
提示对话框,给出各种选项;程序员选择满足要求的选项。确定所有选项后,AppWizard 生成
应用程序文件,包括源文件、头文件、资源文件和工程文件等。
下面以建立一个多文档应用(MDI)程序为例说明操作步骤。
(1)启动 VC 60
(2)从 File 菜单中选择 New,打开 New 对话框
(3)确定创建的类型
选择 Projects 标签,从左边的工程类型列表框中选择要创建的工程类型 。可选择的应
用程序类型如表 3 1 所示。
表3 1 应用程序类型
项 目 名 称 含 义
ALT COM AppWizard 创建活动模板(ALT)项目工程,一般用于编写小的 ActiveX 控件
Cluster Resource Type Wizard 创建能够在微软群服务器上模拟和管理的项目工程
Custom AppWizard 以用户定制的模板向导创建工程
Database Project 创建数据库工程
DevStudio AddIn Wizard 创建一个向开发环境加入命令的项目工程框架
Extended Stored Proc Wizard 创建扩展存储过程的程序向导
ISAPI Extension 创建用于 Internet 的 DLL
Makefile 创建与内部 makefile 协同工作的项目工程
MFC ActiveX Control Wizard 创建基于 MFC 的 ActiveX 控件程序
MFC AppWizard(dll) 用 AppWizard 创建 MFC 动态链接库
MFC AppWizard(exe) 用 AppWizard 创建 MFC 可执行程序
New Database Wizard 创建网络数据库工程
Utility Project 创建不产生任何预定义文件输出工程
不使用 MFC 和 AppWizard,只是简单地为用户创建一个工程文件,用户
Win32 Application
必须自己生成所有的代码
Win32 Console Application 创建 Windows 控制台程序
Win32 DynamicLink Library 创建不需要模板且不利用 MFC 的 DLL,所有代码都必须由用户完成
Win32 Static Library 创建静态链接库的默认项目工程
图3 3 Pr
oje
cts标签
图3 4 MFCAppWi
zar
dS
tep1o
f6 图3 5 MFCAppWi
zar
dS
tep2o
f6
③ Dialog based:基于对话框的应用程序不支持文档/视图结构,而是仅仅显示一个简
单的对话框。这对于小的应用程序是有用的,但如果要用到菜单、工具栏和打印等功能,那
65
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
么就应该创建基于文档的应用程序 。
本例选中 Multiple documents(多文档 )单选按钮:资源语言为中文 (中国 )(APPWZCHS.
DLL)。
(8)单击 Next 按钮进入 MFC AppWizard Step 2 of 6
打开如图 3 5 所 示 的 对 话 框 ,从 中 选 择 数 据 库 支 持 。 各 种 选 择 的 具 体 作 用 见 表
3 2。
表3 2 MFCAppWi
zar
dS
te f6各选项的含义与作用
p2o
控件 选 项 含义与作用
不支持任何 ODBC 库。如果应用程序不使用数据库,那么选择该项将
None
建立比较小的应用程序
提供最低限度的数据库支持,包含数据库头文件(AFXDB.H)和链接库,
单 Header files only
但不创建任何与数据库相关的类,用户必须自己创建
选
按 包含所有的数据库头文件和链接,并创建与数据库相关的类。这个
Database view without
钮 选项创建的应用程序允许用户查看和更新记录集中的记录,并具有
file support
文档但不支持串行化
Database view with
与上一选项唯一不同之处在于不仅有文档支持还有串行化支持
file support
本例选择缺省选择 None,表示不带数据库支持。
(9)单击 Next 按钮,进入 MFC AppWizard Step 3 of 6
打开如图 3 6 所示的对话框。从中可以选择复合文档 (Compound document)支持选项。
各选项的含义与作用见表 3 3。
表3 3 MFCAppWi
zar
dS
te f6各选项的含义与作用
p3o
选 项 作 用
None 默认设置,不带复合文档支持
Container 应用程序作为容器,可以合并嵌入对象和链接对象到自己的文档中
应用程序可以创建和管理复合文档对象,但不能单独运行,仅支持嵌入
Mini_server
对象
应用程序既可创建和管理复合文档,又可单独运行,同时支持链接与嵌
Full_server
入对象
Both container and server 应用程序既作为容器,又作为服务器
使用复合文件格 式 来 串 行 化 容 器 的 文 档。复 合 文 件 格 式 能 在 一 个
Yes please 文件中保存 一 个 或 多 个 自 动 化 对 象,并 允 许 被 单 独 的 自 动 化 对 象
访问
不使用复合文档格式来串行化容器文档。该选项必须一次将包含自动
化对象的整个文件装入到内存中,不允许增量式保存单独的自动化对
No thank you
象。如果一个自动化对象被更改并加以保存,那么文件中所有的自动
化对象都必须保存
66
第三章 Wi ows应用程序编程与 MFC
……………………………………………………………………………………………………………………………………………
nd
续 表
选 项 作 用
使应用程序支持自动化,这样应用程序就可以操作其他应用程序创建
Automation
的对象,或者暴露自动化对象给自动化客户访问
使应用程序使用 ActiveX 控件。如果不选择该项,以后要插入 ActiveX
ActiveX Controls 控件到项目中,就必须在应用程序的 InitInstance 成员函数中添加对
AfxEnableControlContainer()函数的调用
本例选择缺省 None,表示不带复合文档支持。
图3 6 MFCAppWi
zar
dS
tep3o
f6 图3 7 MFCAppWi
zar
dS
tep4o
f6
单击 Advanced 按钮将显示如图 3
8 所示的对话框,其中包括两个标签。 在该对话框
中,AppWizard 为应用程序建立了一些名称和提示,可用于简化应用程序名。
图 3 9 为 Advanced Options 对话框的 Window styles 标签。各选项含义见表 3 5。选
择所需选项后,单击 Close 按钮返回到 AppWizard 第 4 步。 67
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
图3 8 Ad
van
cedOp
tio
ns对话框 图3 9 Ad
van
cedOp
tio
ns对话框之二
表3 5 Ad
van
cedOp
tio
ns对话框的 Wi
ndows
tyl
es标签各选项的含义与作用
选 项 含义与作用
Use split window AppWizard 将生成使用切分窗口的代码
Thick frame 框架将有一个可见的较粗的边缘,并且可以按通常的 Windows 方式调整尺寸
Minimize box 在框架的右上角有一个最小化框标志
Maximize box 在框架的左上角有一个最大化框标志
System menu 在框架的左上角有一个系统菜单
Minimized 当应用程序启动时,框架被最小化
Maximized 当启动应用程序时,最大化框架
图3 10 MFCAppWi
zar
dS
tep5o
f6 图3 11 MFCAppWi
zar
dS
tep6o
f6
文件(.cpp)的名称,可以修改。
表3 6 MFCAppWi
zar
dS
te f6各选项的含义与作用
p5o
选 项 含义与作用
设置工程风格
MFC Standard 缺省,标准风格
Windows Explorer Wndows 资源管理器风格
确定是否生成源文件注释
缺省,Appwzard 在源文件中插入相应注释以
Yes,please
指示在何处需要添加程序员自己的代码
No,thank you 不插入源文件注释
确定如何使用 MFC 库
As a shared DLL 缺省,应用程序在运行时调用 MFC 库
As a statically linked library 在创建应用程序时与静态 MFC 库链接
图3 12 S
etAc
tiv
ePr
oje
ctCo
nfi
gur
ati
on对话框 图3 13 应用程序运行窗口
3 程序中的文件和主要类
3
3
31 程序中的文件和主要类
AppWizard 实际为“MyPainter”应用程序创建了 19 个文件,这些文件存放在工程目录
MyPainter\下,如表 3 7 所示。
表3 7 AppWi
zad为 MyPa
r int
er应用程序创建的文件
文 件 名 描 述 文 件 名 描 述
ChildFrm.h 子窗口类头文件 MyPainter.rc 通用资源文件
ChildFrm.cpp 子窗口类源程序文件 Resource.h 应用程序资源头文件
MainFrm.h 主窗口类头文件 StdAfx.h 标准应用程序结构头文件
MainFrm.cpp 主窗口类源程序文件 StdAfx.cpp 标准应用程序结构源程序文件
MyPainter.h 应用类头文件 MyPainter.rc2 应用程序资源文件
MyPainter.cpp 应用类源程序文件 MyPainter.ico 应用程序图标文件
MyPainter Doc.h 文档类头文件 MyPainterDoc.ico 应用程序文档图标文件
MyPainter Doc.cpp 文档类源程序文件 Toolbar.bmp 工具按钮图形文件
MyPainter View.h 视图类头文件 ReadMe.txt 帮助文本文件
MyPainter View.cpp 视图类源程序文件
表3 8 AppWi
zad为程序自动生成的6个类
r
3
32 应用程序类(
CMyPa
int
erApp类)
CMyPainterApp 类是由 CWinApp 派生的,应用程序的初始化、运行和结束工作都是由应
用程序类来完成的。MyPainterApp.h 头文件代码见清单 3 2 所示。
在该头文件中声明的所有函数都是由 AppWizard 自动添加的。 以//Overrides 开始的
代码是用于覆盖由基类继承的函数,而以//Implementation 开始的代码是关于消息映射的
70 函数。需要指出的是,这些是由程序员自行处理,主要是通过 ClassWizard 来完成。
第三章 Wi ows应用程序编程与 MFC
……………………………………………………………………………………………………………………………………………
nd
清单3 2 MyPa
int
e h头文件
r.
c
lassCMyPa interApppub licCWi nApp
public:
virtual BOOL PreTranslateMessage(MSG* pMsg);
CMyPainterApp();
//Overrides
//ClassWizard generated virtual function overrides
//AFX_VIRTUAL(CMyPainterApp)
public:
virtual BOOL InitInstance();
//}}AFX_VIRTUAL
//Implementation
//AFX_MSG(CMyPainterApp)
afx_msg void OnAppAbout();
//NOTEthe ClassWizard will add and remove member functions here.
//DO NOT EDIT what you see in these blocks of generated code !
//AFX_MSG
DECLARE_MESSAGE_MAP()
};
应用程序类控制了执行程序的流程,程序运行流程见图 3 14。
图3 14 w
i ows应用程序运行机制
nd
在运行应用程序时,程序的基本框架首先取得控制权,做一些初始化的工作,构造应用程
序类的对象。每个 MFC 程序都必须有且只有一个由 CWinApp 派生的类的对象,在 MyPainter 程
序的 MyPainter.cpp 文件中,定义了 CMyPainterApp theApp,并把它声明为全局类型,构造对象时
要调用程序类的构造函数(声明为全局变量可以保证先调用构造函数)。
所有的 Windows 程序中都有一个主函数 WinMain, MFC 把 WinMain 函数隐藏在应用程序
的基本框架内部。创建完所有的静态和全局对象后,调用入口函数 WinMain。WinMain 函数首
先获得指向应用程序类对象的指针,然后进行初始化工作,即调用 InitInstance 函数进行初始
化。然后调用应用程序类的 Run 函数,运行程序的消息循环,该循环持续反复检测是否有用户 71
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
事件发生。每次检测到一个用户事件,程序就对之作出响应。这些事件包括移动鼠标指针、按
键、
单击或双击按钮等。当 Windows 接收到这些事件后,会产生一些相应的消息。 应用程序
接到这些消息后,按每一个消息的专门用途,产生一系列的执行动作。 这些执行动作称之为
响应。响应包括重画窗口、改变窗口大小、关闭窗口、打开一个位图、调用一个动态库等。 最
后调用应用程序类的 ExitInstance 函数结束程序,删除注册的窗口,释放内存等。
下面介绍应用程序类中的几个常用函数,它们分别完成程序初始化、 运行和结束等工作。
(1)InitInstance 函数
这个函数用来初始化一个应用程序实例,Windows 允许同时运行同一程序的多个副本或
实例。InitInstance 函数的源代码见清单 3 3。
清单3 3 I
nit
Ins
tan
ce()函数(在 MyPa
int
er.
cpp源文件中)
BOOLCMyPa interApp Ini
tInst
ance()
{ AfxEnableControlContainer();
# ifdef _AFXDLL
Enable3dControls();//Call this when using MFC in a shared DLL
# else
Enable3dControlsStatic(); //Call this when linking to MFC statically
# endif
SetRegistryKey(_T("Local AppWizardGenerated Applications" ));
LoadStdProfileSettings(); //Load standard INI file options (including MRU)
//Register the applications document templates. Document templates
//serve as the connection between documents, frame windows and views.
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_PAINTETYPE,
RUNTIME_CLASS(CMyPainterDoc),
RUNTIME_CLASS(CChildFrame),//custom MDI child frame
RUNTIME_CLASS(CMyPainterView));
AddDocTemplate(pDocTemplate);
//create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (! pMainFrame- >LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
//Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
//Dispatch commands specified on the command line
if (! ProcessShellCommand(cmdInfo))
return FALSE;
//The main window has been initialized, so show and update it.
pMainFrame- >ShowWindow(m_nCmdShow);
pMainFrame- >UpdateWindow();
return TRUE;
InitInstance 函数代码解释如下。
① InitInstance 函数首先调用 AfXEnableControlContainer 函数使应用程序能够包含
72 ActiveX 控件,然后调用 Enable3dControls/Enable3dControlsStatic 函数来允许 3D 控件,
第三章 Wi ows应用程序编程与 MFC
……………………………………………………………………………………………………………………………………………
nd
其 中 Enable3dControls 函 数 应 用 程 序 用 于 使 用 动 态 链 接 库 的 情 况, 而
Enable3dControlsStatic 函数则用于应用程序使用静态链接库的情况 。
② 调用 SetRegistryKey 函 数 通 知 MFC 使 用 注 册 表 而 不 是 INI 文 件 存 储 文 件 信 息。
LoadStdProfileSetting 函数加载标准 INI 文件设置和 MRU 文件列表 (最近使用过的文件名
称)。
③ 通过以下代码创建并注册文档模板 。
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_PAINTETYPE,
RUNTIME_CLASS(CMyPainterDoc),
RUNTIME_CLASS(CChildFrame),//custom MDI child frame
RUNTIME_CLASS(CMyPainterView));
AddDocTemplate(pDocTemplate);
# define RUNTIME_CLASS(class_name)(class_name::GetThisClass())
表3 9 Pr
oce
ssSh
ell nd函数的命令行参数
Comma
参 数 作 用
无 启动应用程序并打开新文件
文件名 启动应用程序并打开指定文件
/p 文件名 启动应用程序并在缺省打印机上打印指定文件
/pt 文件名打印机驱动程序端口 启动应用程序并在指定打印机上打印指定文件
/DDe 启动应用程序并等待 DDE 命令
/Automation 启动 OLE 自动化服务器
/Embedding 启动应用程序并编辑嵌入的 OLE 对象
⑤ InitInstance 使用如下语句显示并更新窗口。
m_pMainWnd- > ShowWindow(SW_SHOW);
m_pMainWnd- > UpdateWindow();
(2)Run 函数
初始化完成后,WinMain 函数调用 CWinApp 的成员函数 Run 处理消息循环。 Run 函数不
断地查询应用程序的消息队列,如果有消息,就把消息发给 Windows,由 Windows 调用处理消
息的窗口函数 (窗口函数同样被隐藏在应用程序的基本框架中 ),如果没有消息,就调用
CWinApp 的成员函数 OnIdle 作一些程序在空闲时做的工作,如果空闲时要做的工作也没有,
那么 应 用 程 序 就 一 直 等 待。 当 用 户 关 闭 应 用 程 序 的 实 例 时, Run 函 数 就 会 调 用
ExitInstance 函数,作一些退出前的处理工作。通常不要重载 Run 函数,因为它执行了缺省
的消息循环。
(3)ExitInstance 函数
当用户需要结束应用程序的运行实例时,就由 Run 调用 ExitInstance 函数,做一些专门
的扫尾清理工作。如果用户需要在程序退出前做专门的清理工作,如释放执行期间占用的
内存,可以重载 ExitInstance 函数。
(4)OnIdle 函数
OnIdle 函数在应用程序消息队列中没有消息时由 Run 函数调用。 默认时 OnIdle 用于
执行更新用户界面对象的状态及清理运行过程中的临时对象 。 用户可以重载它完成一些希
望在后台执行的操作。
3
33 程序的其他类
(1)文档类(CMyPainterDoc 类)
MyPainter 程 序 的 文 档 类 称 为 CMyPainterDoc, 该 类 是 从 CDocument 类 派 生 的。
CMyPainterDoc 类的头文件为 MyPainterDoc.h,源文件为 MyPainterDoc.cpp。文档类用于响
应数据文件的读写以及存储视图类所要查看和处理的信息 。
(2)视图类(CMyPainterView 类)
MyPainter 程 序 的 视 图 类 称 为 CMyPainterView, 该 类 是 从 CView 类 派 生 的。
CMyPainterView 类的头文件为 MyPainterView.h,源文件为 MyPainterView.cpp。 视图类指
定用户以什么方式见到文档数据,以及如何与其进行交互。
(3)主边框窗口类 (CMainFrame 类)
MyPainter 程 序 的 主 边 框 窗 口 类 为 CMainFrame, 该 类 是 从 CMDIFrameWnd 类 派 生 的。
CMainFrame 类的头文件为 MainFrm.h,源文件为 MainFrm.cpp。主边框窗口类用于管理应用
程序窗口,显示标题栏、菜单栏、工具栏、状态栏、控制菜单和控制按钮。 主边框窗口是所有
MDI 子窗口的包容器。
(4)子边框窗口类(CChildFrame 类)
MyPainter 程序的子边框窗口类为 CChildFrame,该类 是 从 CMDIChildWnd 类 派 生 的。
CChildFrame 类的头文件为 ChildFrm.h,源文件为 ChildFrm.cpp。 子边框窗口类用于管理
在 MDI 主边窗口中打开的各个文档。每个文档及其视图都有一个 MDI 子窗口。 MDI 子窗口
包含在主边框窗口中,而且没有自己的菜单栏、工具栏和状态栏,必须与包含它的主边窗口
共享。
(5)对话框类(CAboutDlg 类)
该对象是专门定义 About 对话框的内容和显示的,派生于 CDialog 类。 该类的定义和
74 实现函数都在 MyPainter.cpp 文件中,见清单 3 4。
第三章 Wi ows应用程序编程与 MFC
……………………………………………………………………………………………………………………………………………
nd
清单3 4 CAb
outD
lg类的定义和实现代码
c
lassCAb outDlgpub licCD ia
log
public:
CAboutDlg();
//Dialog Data
//AFX_DATA(CAboutDlg)
enum IDD = IDD_ABOUTBOX;
//AFX_DATA
//ClassWizard generated virtual function overrides
//AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);//DDX/DDV support
//AFX_VIRTUAL
//Implementation
protected:
//AFX_MSG(CAboutDlg)
//No message handlers
//AFX_MSG
DECLARE_MESSAGE_MAP()
};
CAbo u
tD lgCAb o utDg():CD
l ial
og(
CAb outDlgIDD)
{ //{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
v
oidCAboutDlgDoDat aEx
change(CDat
aEx
cha
nge pDX)
{ CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
//No message handlers
//AFX_MSG_MAP
END_MESSAGE_MAP()
//App command to run the dialog
v
oidCMyPa int
erApp OnAppAb out()
{ CAboutDlg aboutDlg;
AboutDlg.DoModal();
}
4 消息和消息处理
3
在 Windows 操作系统下,应用程序所做的事大部分是对消息驱动进行响应,而这些响应
几乎都是基于处理 Windows 消息的。 MFC 类库为窗口下的消息处理提供了框架 。 这些从
CCmdTarge 的类派生出来的类能够拥有自己的消息映射,这种功能使 MFC 类库的消息处理更
为简单,并能够加强类的功能性封装,避免了使用类时的重复性操作。
75
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
3
41 消息的分类
Windows 系统预定义了许多消息,每个消息都拥有一个宏定义,即用形象的字符串来标识
消息。可以在 Microsoft Visual Studio 所在的目录的 WinUser.h 文件中找到这些定义。
Windows 可以处理 3 种不同类型的消息。
(1)标准 Windows 消息
除了 WM_COMMAND 消息,所有以 WM_为前缀的消息都是标准的 Windows 消息。 例如 WM_
PAINT、WM_CHAR、WM_MOUSEMOVE 等。
标准的 Windows 消息由窗口和视图处理,CWnd 和它的派生类都可以接收标准的 Windows
消息。
(2)控件通知消息
控件通知消息包括按下按钮、编辑框等控件产生的消息。它以 WM_COMMAND 为消息名。同
标准 Windows 消息一样,控件通知消息由窗口和视图处理。例如,当用户对编辑控件中的文本
作出了修改后,编辑控件向其父窗口发送的 WM_COMMAND 消息中包含了 EN_CHANGE 控件通告码。
窗口的消息处理函数随即对该通告消息作出合适的处理,比如接收输入到控件中的文本。
(3)命令消息
与控件通知消息相同,命令消息也以 WM_COMMAND 为消息名。命令消息是由用户界面对
象产生的,如工具栏按钮、菜单或加速键等都是可以产生命令消息的用户界面对象 。
命令消息与其他消息的处理不同,它可被更广泛的对象 (如文档 、文档模块 、应用程
序模块 、窗口和视图等 )处理 。 如 果 某 条 命 令 直 接 影 响 到 某 个 对 象,则 由 该 对 象 来 处 理
这条命令 。
MFC 类库为了进一步扩展重复使用性,为大多数 Windows 应用程序的命令提供了默认操
作,这些默认操作中大部分同时也被包含在由 AppWizard 产生的默认菜单中,表 3 10 列出
了 MFC 类库中有默认操作的菜单命令。这些由 AppWizard 创建的菜单命令消息在 AFXRES.H
中定义,它们的命名规则一般是 ID_+ 菜单名+ 命令名。
如果想执行这些菜单命令,可以从应用程序中的任何一处发送一条预定义的命令消息,
MFC 类库就会进行自己的默认处理。程序员也可以对这些命令进行重载、编辑,以实现自己
定义的功能。
表3 10 MFC 类库中默认操作菜单命令
菜单项 默认菜单命令
文件菜单 New, Open, Close, Save, Save as, Page Setup, Print Setup, Print, Print Preview, Exit
编辑菜单 Clear, Clear All, Copy, Cut, Find, Paste, Replace, Select All, Undo, Redo
窗口菜单 New, Arrange, Cascade, Tile Horizontal, Tile Vertical, Splite
视图菜单 Toolbar, Status Bar
76 帮助菜单 Index, Using Help, About
第三章 Wi ows应用程序编程与 MFC
……………………………………………………………………………………………………………………………………………
nd
如果有必要的话,用户也可以自定义消息用于特定操作使用 。
不同的消息有不同的消息前缀,见表 3 11 所示。
表3 11 Wi
ndows消息前缀和对象类型
(4)消息标志符取值范围
消息标志符取值范围见表 3 12。
表3 12 消息标志的取值范围
消 息 类 型 取 值 范 围 消 息 类 型 取 值 范 围
系统定义消息 0x0000 到 0x03FF 系统定义消息 0x0800 到 0xBFF
用户定义内部消息 0x0400 到 0x07FF 用户定义外部消息 0xC000 到 0xFFFF
3
42 消息映射
MFC 应用程序通过一些宏将特定的消息映射到派生类中相应的成员函数上,这种方法被
称作消息映射机制。消息映射机制实质上就是消息及其处理函数的一一对应表以及分析、
处理这张表的应用程序内部的一些代码 。
在 MFC 中,凡是从 CCmdTArget 派生的类都可以有消息映射,如果要建立消息映射,需要
进行以下操作。
(1)在类的头文件(.h)中声明消息映射表
在通常情况下被写在一个类定义的最后 。
例:DECLARE_MESSAGE_MAP()
//清单 3 2 的最后一句 77
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(2)在类的头文件(.h)中声明消息函数
例:afx_msg void OnAppAbout();//清单 3 2
(3)在类的源文件(.cpp)中加入消息映射表
//消息映射表开始
BEGIN_MESSAGE_MAP(CMyPainterApp, CWinApp)
//{{AFX_MSG_MAP(CMyPainterApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout) //具体映射关系
//}}AFX_MSG_MAP
//Standard file based document commands
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
//Standard print setup command
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP() //消息映射表结束
在 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 之间的宏为消息映射表。在映射表中,每个
消息映射都是由一组预定义的宏组成,这些在消息映射内部的宏称为消息映射宏。 映射宏
有许多种,消息映射中使用的消息映射宏由所处理消息的类型决定,依照消息类型分类,相
应表中映射宏大致可分为:标准 Windows 消息映射宏、命令消息映射宏、控件通知消息映射
宏和其他映射宏。见表 3 13。
表3 13 预定义的映射宏
消 息 类 型 映 射 宏 宏 定 义
标准 Windows 消息 ON_WM_XXXX ON_WM_PAINT()
命令消息 ON_COMMAND ON_COMMAND(id,memberFxn)
命令标志范围 ON_COMMAND_RANGE ON_COMMAND_RANGE(id1.id2.,memberFxn)
扩展命令消息 ON_COMMAND_EX ON_COMMAND_EX(id,memberFxn)
扩展命令消息的范围 ON_COMMAND_EX_RANGE ON_COMMAND_EX_RANGE(id1.id2.,memberFxn)
更新命令消息 ON_UPDATE_COMMAND_UI ON_UPDATE_COMMAND_UI(id,memberFxn)
ON_ UPDATE _ COMMAND _ UI _ ON _ UPDATE _ COMMAND _ UI _ RANGE (id1. id2.,
要更新命令的消息范围
RANGE memberFxn)
控件通知消息 ON_XXXX ON_XXXX(wNotifyCode,id,memberFxn)
自定义控件通知消息 ON_CONTRO ON_CONTROL (wNotifyCode, id, memberFxn)
ON_CONTROL_RANGE
控件标志符的范围 ON_CONTROL_RANGE
(wNotifyCode, id1, id2,memberFxn)
用户自定义消息 ON_MESSAGE ON_MESSAGE(message,memberFxn)
ON_REGISTERED_MESSAGE
已注册 Windows 消息 ON_REGISTERED_MESSAGE
(nMessageVariable,memberFxn)
公共控件通知消息 ON_NOTIFY ON_NOTIFY(wNotifyCode,id,memberFxn)
ON_NOTIFY_RANGE
公共控件通知消息范围 ON_NOTIFY_RANGE
(wNotifyCode, id1, id2,memberFxn)
扩展公共控件通知消息 ON_NOTIFY_EX ON_NOTIFY_EX(wNotifyCode,id,memberFxn)
扩展 公 共 控 件 通 知 消 息 ON_NOTIFY_EX_RANGE
ON_NOTIFY_EX_RANGE
78 范围 (wNotifyCode,id1,id2,memberFxn)
第三章 Wi ows应用程序编程与 MFC
……………………………………………………………………………………………………………………………………………
nd
(4)在类的源文件(.cpp)中编写消息处理函数具体的代码
//具体代码}
例:void CMyPainterApp::OnAppAbout(){ //见清单 3 2
3
43 消息处理函数
由上可知消息映射主要由消息的 ID 与消息处理函数两部分组成的。
所有消息处理函数(也称消息响应函数,简称消息函数 )原型前面都有关键字 afx_msg
前缀,用于把 消 息 函 数 与 其 他 函 数 区 分 开 来。 依 照 消 息 分 类,可 将 消 息 函 数 分 为:标 准
Windows 消息函数、命令消息函数、控件通知消息函数。
消息函数可以用 ClassWizard 添加也可手工添加。
使用 犆犾
1 犪狊狊犠犻狕犪狉犱 添加消息处理函数
使用 ClassWizard 添加消息处理函数的步骤如下。
(1)选中 ClassWizard 对话框中的 Message Maps 标签。
(2)在 Project 中选择要操作的项目工程。
(3)在 Class name 下拉列表框中选择类。
(4)在 Object IDs 选择框中选择工程中的对象 ID,并在右边的 Messages 选择框中选择
消息,这时 Add Function 按钮会变黑,处于被激活状态。
(5)单击 Add Function,弹 出 一 个 函 数 生 成 的 确 认 对 话 框 Add Member Function,见
图 3 15。
(6)单击 Add Member Function 对话框中的 OK 按钮后,用 ClassWizard 添加函数的过程
就完成了。
图3 15 添加消息处理函数
标准 犠犻
2 狀犱狅狑狊消息函数
对于标准 Windows 消息,CWnd 类中已经预定义了默认的处理函数。 这些函数的名称以
On 开头,以它相应的 Windows 消息中不包括 WM_的部分作为函数名的后半部分。 例如,消息
函数 OnPaint 对应处理的是 WM_PAINT 消息。在 CWnd 的直接或间接派生类中如果定义了某
个标准 Windows 消息函数,则该函数重载基类中相应的消息函数。 主要的 Windows 消息处
理函数见表 3 14。
表3 14 主要的标准 Wi
ndows消息函数
序号 标准 Windows 消息 消 息 函 数 说 明
afx_msg void OnKeyDown
1 WM_KEYDOWN 键按下
(UINT nChar, UINT nRepCnt, UINT nFlags)
afx_msg void OnKeyUp
2 WM_KEYUP 键松开
(UINT nChar, UINT nRepCnt, UINT nFlags)
afx_msg void OnChar
3 WM_CHAR 按键
(UINT nchar,UINT nRepCnt,UINT nFlags)
afx _ msg void OnMouseMove (UINT nFlags, CPoint
4 WM_MOUSEMOVE 鼠标移动
point)
afx_msg void OnLButtonDown
5 WM_LBUTTONDOWN 鼠标左键按下
(UINT nFlags, CPoint point)
afx _ msg void OnLButtonUp (UINT nFlags, CPoint
6 WM_LBUTTONUP 鼠标左键松开
point)
afx_ msg void OnRButtonDown (UINT nFlags, CPoint
7 WM_RBUTTONDOWN 鼠标右键按下
point)
afx _ msg void OnRButtonUp (UINT nFlags, CPoint
8 WM_RBUTTONUP 鼠标右键松开
point)
afx_msg void OnLButtonDblClk
9 WM_LBUTTONDBCLICK 双击鼠标左键
(UINT nFlags, CPoint point)
afx_msg void OnRButtonDblClk
10 WM_RBUTTONDBCLICK 双击鼠标右键
(UINT nFlags, CPoint point)
11 WM_PAINT afx_msg void OnPaint() 窗口重绘
afx_msg void OnHScroll(UINT nSBCode, UINT nPos,
12 WM_HSCROLL 水平滚动栏
CScrollBar* pScrollBar)
afx_msg void OnVScroll(UINT nSBCode, UINT nPos,
13 WM_VSCROLL 垂直滚动栏
CScrollBar* pScrollBar)
14 WM_TIMER afx_msg void OnTimer(UINT nIDEvent) 时钟期结束
15 WM_CREATE afx_msg int OnCreate(
LPCREATESTRUCT lpCreateStruct) 创建窗口
80
第三章 Wi ows应用程序编程与 MFC
……………………………………………………………………………………………………………………………………………
nd
续 表
序号 标准 Windows 消息 消 息 函 数 说 明
16 WM_CLOSE afx_msg void OnClose() 关闭窗口
17 WM_QUERYENDSSION afx_msg BOOL OnQueryEndSession() 退出 Windows
关 闭 窗 口 后, 子
18 WM_DESTROY afx_msg void OnDestroy()
窗口被销毁前
关 闭 窗 口 后, 子
19 WM_NCDESTROY afx_msg void OnNcDestroy()
窗口被销毁后
表 3 14 中有关函数的具体解释如下。
(1)键盘消息函数
表中 1~3 为 键 盘 消 息 函 数。 其 中 OnChar 消 息 函 数 最 重 要,它 通 常 在 OnKeyDown 和
OnKeyUp 两个函数之间被调用。函数参数 NChar:表示所按键的字符代码值;nRepCnt:表示重
复击键次数;nFlags:表示扫描码、键的先前状态等,其具体含义见表 3 15。
表3 15 OnCh
ar函数中 nF
lag标志位的具体描述
位 说 明
0~15 表示用户重复单击键的次数,即 nPepCnt
16~23 表示扫描码,具体依赖于 OEM 厂商
24 如果是扩展码,则为 1;否则为 0
25~28 由 Windows 内部使用
29 如果 ALT 键被按下为 1;否则为 0
30 表示先前键的状态,如果消息发出前键是按下的,则为 1;否则为 0
31 表示键的转换状态,如果键被松开,则为 1,否则为 0
(2)鼠标消息函数
表中 4~10 为键盘消息函数。函数中的第一个参数 nFlags 用来表示鼠标事件发生时,
键盘上某些按键的状态,nFlag 对应的具体值见表 3 16。
表3 16 鼠标消息函数中 nF
lag取值
nFlag 取值 说 明 nFlag 取值 说 明
MK_MBUTTON 鼠标中键按下
体取值见表 3 17。
表3 17 nSBCo
de取值
OnHScroll OnVScroll
nSBCode 取值 说 明 nSBCode 取值 说 明
SB_LEFT 滚动到最左边 SB_BOTTOM 滚动到最底部
SB_ENDSCROLL 终止滚动 SB_ENDSCROLL 终止滚动
SB_LINELEFT 向左滚动 SB_LINEDOWN 向下滚动
SB_LINERIGHT 向右滚动 SB_LINEUP 向上滚动
SB_PAGELEFT 向左滚动一页 SB_PAGEDOWN 向下滚动一页
SB_PAGERIGHT 向右滚动一页 SB_PAGEUP 向上滚动一页
SB_RIGHT 滚动到最右边 SB_TOP 滚动到顶部
SB_THUMBPOSITION 滚动到 nPos 指定的位置 SB_THUMBPOSITION 滚动到 nPos 指定的位置
拖动滚动框到 nPos 拖动滚动框到 nPos
SB_THUMBTRACK SB_THUMBTRACK
指定的位置 指定的位置
控件通知消息函数
3
控件通知消息一般没有默认的消息函数,其函数名理论上可以随意,但最好遵循一定的
规则,例如以 On 开头。
当使用向导添加消息函数时,系统会提供一个建议的函数名,这个函数名主要是根据控
件通知消息的通知代码来命名的,用户最好采用此函数名。
例如:afx_msg void OnBNClickedOk()表明单击 OK 按钮要处理的消息函数。
afx_msg void OnBnDoubleClickedButtonApply()表明双击 ID 为 ID_BUTTON_APPLY 按钮
要处理的消息函数。
命令消息函数
4
命令消息也没有默认的消息函数 。命名原则与上相同。
例如:afx_msg void OnEditCut()表明处理命令标志符为 ID_EDIT_CUT 的命令消息函数。
由于 Windows 下的应用程序大多都有统一的界面风格,有一些命令在大多数应用程序
中都存在,如文件菜单下的新建、保存、退出,编辑菜单下的复制、粘贴、剪切等。 VC++ 已经对
这些常用的命令标志符(ID)进行了预定义,保存在 afxres.h 文件中。
3
44 消息传递
应用程序的构造过程被执行之后,CWinApp 类的 Run 函数就来检索消息,并把消息发送
到适当的窗口,在 MFC 程序中,非命令消息和命令消息采用两种不同的传递方式 。
(1)非命令消息的传递
标准 Windows 消息和控件通知消息为非命令消息,必须由窗口类的对象,也就是直接或
间接由 CWnd 类或其派生类的对象进行处理。 这个窗口可能是主框架窗口、标准控件、对话
框、视图或其他类型的子窗口。 在程序运行时,每个 Windows 窗口都与窗口对象联系在一
起。每个窗口对象都有自己的消息映射和处理函数 。 MFC 利用消息映射把消息与其对应的
处理函数相匹配。
(2)命令消息的传递 83
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
命令消息的处理与其他消息不同,命令消息可以被更广泛的对象处理,而不仅仅局限于
窗口类,所有派生自 CCmdTArget 的类,如文档类、文档模板类和应用程序类等都可以处理命
令消息。
既然这么多对象都可以处理命令消息,那么当命令消息被发送后,究竟选择谁来处理它
比较好呢? 理论上,使用何种对象处理它都是可以的,但一般情况下,为了增强程序的可维
护性,可以遵循就近原则。例如,如果某个命令是对文档内容进行操作,如复制、粘贴等命令
就可以把它交给文档对象处理;如果某个命令是进行绘图操作,就可以把它交给视图对象处
理;如果某个命令操作关系到程序的流程,如退出程序等,就可以把它交给应用程序类的对
象进行处理。但这种规则也不绝对,需要具体问题具体分析。
另外,还要解决一个问题,如果某个命令在多个对象中都有消息处理函数,那是不是调
用所有的处理函数呢? 显然不是,每次只有一个函数被调用。具体到哪个函数被调用,这就
涉及了命令消息的传递路径,表 3 18 列出了常用命令消息的详细传送路径 。
表3 18 命令消息传递路径
1 活动的 CMDIChildWnd
1 本视
MDI 主框架 2 本框架窗口 视 图
2 与本视图相连的文档
3 应用程序(CWinApp)
1 活动的视
1 文本档
MDI 子框架 2 本框架窗口 文 档
2 文档
3 应用程序
1 活动的视 1 本对话框
SDI 主框架 2 本框架窗口 对话框 2 拥有本对话框的窗口
3 应用程序 3 应用程序
当某个命令消息产生后,它被应用程序框架沿着命令消息的传递路径发送。 传递路径
上的每一个对象都按顺序检查自己的消息映射,看看是否能够处理这个命令消息。 如果消
息与处理函数相匹配,则调用处理函数处理该消息并且不再搜索后面的对象,反之就继续传
递下去。
在多文档的应用程序中,选择“编辑”菜单下的“查找”菜单项,将产生一条命令消息。 假
定其处理函数是应用程序文档模板类的成员函数 。那么消息传递路径如下:
① MDI 主框架窗口对象首先收到这个命令消息 。
② 主框架窗口在搜索自己的消息映射前把命令消息传给当前活动的子窗口 。
③ 当前活动的子窗口在检查自己的消息映射前把命令消息传给当前活动的视 。
④ 视图先搜索自己的消息映射,查找相应的处理函数,没找到,接着按照传递顺序把命
令消息发送给与视图相关联的文档 。
⑤ 关联的文档先搜索自己的消息映射,查找相应的处理函数,没找到,接着按照传递顺
序把命令消息发送给与文档相关联的文档模板 。
⑥ 文档模板找到与命令消息对应的消息处理函数,调用该函数,完成相应的操作并终止
命令消息的传递。
84
第四章 文档/视图结构及其编程
文档用以保存应用程序的数据,并提供与磁盘文件的交互以及与视的交互等。 视图以
可见的图形方式显示文档数据、提供用户界面、接受用户操作信息,并将与视图相关的操作
传递到文档,以及接受文档的更新视操作等。
MFC 的文档/视图结构将数据与用户实际显示和处理数据的方式分开来,图 4 1 显示文
档、视图和用户的关系。
图4 1 文档、视图与用户的关系
1 文档
4
4
11 使用文档管理数据的一般步骤
文档用于管理应用程序的数据,在使用文档类时的一般步骤为:
(1)CDocument 是所有文档类的基类,以 CDocument 类为基类派生出自己的文档类;
(2)添加用于保存数据的成员变量;
(3)编写实现读和修改文档数据的成员函数;
(4)重载文档类的成员函数 Serialize,实现将文档数据写入磁盘和从磁盘读出文档数
据的操作。
4
12 文档类(
CDoc t类)中的主要数据成员和成员函数
umen
(1)文档类的数据成员
文档数据必须用文档类的数据成员来实现。 在定义文档数据成员时,为了设置和获取 85
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
数据项,通常把成员函数添加到文档类中,并在成员函数上执行其他有用操作 。
视图通过指向文档的指针来访问文档对象,可以通过调用 CView::GetDocument 来获取
指向文档的指针。获得指向文档类的指针后,才能访问文档的成员。
(2)成员函数 OnNewDocument()
函数原型为:virtual BOOL OnNewDocument();
当用户选择“文件”菜单的 “新建”命令时,将调用这个函数对文档进行初始化。 如果是
多文档应用程序,将创建一个新文档并调用它完成初始化。 如果是单文档应用程序则调用
这个函数初始化已经存在的文档 。
当用 AppWizard 生成框架时,已定义了 OnNewDocument 的基本结构,见清单 4 2。
(3)成员函数 DeleteContens()
函数原型为:virtual void DeleteContens();
DeleteContens 函数用于在不删除文档对象的情况下清除文档中的数据。 这对于使用
同一文档对象的单文档应用程序特别重要 。
可以重载该函数以便实现 “编辑 ”菜单的 “全部清除 ”命令或类似命令来删除文档中的
数据。
(4)成员函数 OnOpenDocument()
函数原型为:virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
当用户选择“文件”菜单的 “打开”命令时,将调用 OnOpenDocument 来打开指定的文件,
并调用 DeleteContents 来清除文档中的数据,调用 Serialize 来从磁盘文件中加载新文档。
如果需要对文档进行初始化,那么可以重载 OnOpenDocument。
(5)成员函数 OnSaveDocument()
函数原型为:virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);
当用户选择“文件”菜单的“保存”或者“另存为”命令时,将调用该函数打开指定文件,并
调用 Serialize 函数向文件中写入文档数据。
(6)成员函数 OnCloseDocument()
函数原型为:virtual void OnCloseDocument();
当一个文档被关闭时,框架 就 会 调 用 这 个 函 数 。 函 数 的 默 认 操 作 是 关 闭 所 有 用 于
浏览文档的框架窗口和视图,清除文档的内容,并调用 DeleteContents 函数删除文档中
的数据 。
(7)成员函数 Serialize()
将对象写入永久性存储媒体(如磁盘文件)或从其中读取对象的过程叫做序列化 。 在文
档类中通常使用 Serialize 函数来完成序列化的操作。 MFC 应用程序向导重写 CDocument
的成员函数 Serialize 的主干部分并将其放入用户创建的文档类中 。 在实现应用程序的成
员变量后,一般需要自己编写 Serialize 函数,在应用程序向导生成的框架中,Serialize 函
数的基本结构见清单 4 2。
(8)成员函数 UpdateAllViews()
函数原型为:void UpdateAllViews (CView* pSender, LPARAM lHint = 0L, CObject*
pHint = NULL);
如果所有的视图都要更新,那么参数 pSender 为 NULL,否则为指向视图的指针;1Hint 包
86 含与修改有关的信息,可以传递任何数据;pHint 指向用于保存修改信息的对象,可以传递
第四章 文档/视图结构及其编程
……………………………………………………………………………………………………………………………………………
CObject 派生的任何对象的指针。
该函数将通知与文档对应的所有视图文档已经被更改了 。
如果在派生类文档类的成员函数中调用 UpdateAllViews 函数,调用形式为:
UpdateAllViews(NULL);
GetDocument()- > UpdateAllViews(this);
了解文档对象和视图对象在程序中是如何工作的是一件很重要的事,因为文档类在
VC++ 程序中控制着数据存储和显示的基本格式 。 文档对象是用来存储数据的,而视图对象
作为主窗口的一个子窗口,通常占据整个客户区,负责相应的数据显示。 在程序中这些主要
对象贯穿着所有函数调用的全过程 。
4
13 多文档类型
AppWizard 创建的应用程序框架开始只支持单个文档类。 如果应用程序要支持多文档
类型,那么必须创建新的文档类。 每种文档类型都有自己的文档类和视图类。 当用户选择
“文件”菜单的“新建”命令时,应用程序将显示对话框,列出所支持的文档类型,然后再按用
户选择的类型创建文档。每种文档类型都由自己的文档模板对象管理 。
要创 建 新 的 文 档 类, 请 打 开 ClassWizard, 用 ClassWizard 来 创 建 。 创 建 时, 选 择
CDocument 类作为派生新 类 的 基 类 并 提 供 必 要 的 文 档 信 息,然 后 再 实 现 这 个 新 类 的 数
据,最后在应用程 序 对 象 的 成 员 函 数 InitInstance 中 增 加 一 次 对 AddDocTemplate 函 数
的调用 。
2 视图
4
4
21 视图操作的一般步骤
视图用于显示文档内容并管理与用户的交互,视图的操作步骤一般为:
(1)以 CView 类为基类派生出自己的视类;
(2)通过派生类的 OnDraw 成员函数向视图提供文档数据;
(3)在派生视类中增加界面设计中涉及的各对象的 Windows 消息处理函数;
(4)编写相应的消息处理函数以响应用户操作;
(5)根据需要修改该派生视类的其他成员函数 。
4
22 视图类(
CVew 类)中的主要成员函数
i
(1)成员函数 OnDraw
函数原型为:virtual void OnDraw(CDC* pDC)= 0;
应用程序几乎所有的绘制工作 都 是 在 OnDraw 成 员 函 数 中 实 现 的,编 程 时 必 须 在 视
图类中重载此函数 。 在 AppWizard 生成的框架中,已经定义了 OnDraw 的基本结构,见清
单4 3。
OnDraw 通过调用文档成员函数获取文档数据,并传给 OnDraw 的设备文本对象,通过其 87
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
成员函数来显示数据。
当文档数据被更改时,视图必须重新绘制以反映数据的更改 。 例如,当用户通过视图更
改文档时,视图必须调用文档的成员函数 UpdateAllViews 来更新同一文档的所有视图。
(2)成员函数 GetDocument
函数原型为:CDocument* GetDocument()const;
该函数返回指向与视图相联的文档对象的 指 针,如 果 没 有 文 档 与 视 图 相 联,则 返 回
NULL。调用该函数可以获得一个指向当前视图所对应的文档类指针,利用它能够对文档类
的成员函数和公共数据成员进行访问 。
(3)成员函数 OnUpdate
函数 原 型 为: virtual void OnUpdate (CView * pSender, LPARAM lHint, CObject *
pHint);
调 用 该 成 员 函 数 可 以 使 视 图 反 映 文 档 数 据 的 变 化, 该 函 数 被 文 档 类 中 的
UpdateAllViews 函数所调用。不过用户也可以直接在派生视图类中调用该函数,通常派生
视图类的 OnUpdate 函数要访问文档、读取文档的数据,然后再更新视图的数据成员或控件,
以便反映出文档的变化。缺省情况下,OnUpdate 使整个视图窗口无效,同时触发 OnDraw 函
数,使用更新后的文档数据重新绘制窗口 。
(4)成员函数 OnInitialUpdate
函数原型为:virtual void OnInitialUpdate();
框架窗口在视图第一次与文档相联时调用该函数 。 在基类的 OnInitialUpdate 函数
中,除 了 调 用 OnUpdate 函 数 外, 不 作 其 他 任 何 事 情。 如 果 需 要 在 派 生 视 图 类 中 重 载
OnInitialUpdate 函数则一定要在其中调用基类的 OnInitialUpdate 函数,或者调用派生视
图类的 OnUpdate 函数。通过重载 OnInitialUpdate 函数可以完成视图类的初始化。应用程
序框架在创建视图时,首先调用 OnCreate 函数,然后调用 OnInitialUpdate 函数。 OnCreate
函数只能够被调用一次,而 OnInitialUpdate 函数可以被调用多次。
4
23 多视图
多数文档只要求单视图,但支持单个文档的多个视图是可能的。 每个文档对象保存有
该文档的视图列表,并提供用于添加和删除视图的成员函数,以及在文档数据发生变化时提
供 UpdateAllView 成员函数来更新所有视图。MFC 支持以下三种多视图模式:
(1)同一文档的多个视图对象,每个对象置于独立的 MDI 子窗口中。 例如,选择 “窗口”
菜单的“新建窗口”命令打开同一文档的多个 MDI 子窗口,然后可以在多个 MDI 子窗口中同
时查看文档的不同部分。
(2)同一文档边框窗口中有同一文档的多个视图对象 。例如,分割窗口可以将单个文档
边框窗口的视图空间分割成多个独立的视图,应用程序从同一视图类中创建多个视图对象 。
(3)单个边框窗口中有不同类的视图对象,多个视图共享单个边框窗口,每个视图从不
同类构造。例如,可以在一个视图显示文本文档,而在另一视图显示图形文档。
4
24 派生的视图类
为了增强应用 程 序 的 视 图 功 能,MFC 还 提 供 了 几 个 专 用 的 视 图 类,这 些 类 都 派 生 于
88 CView 类,这些派生的视图类见表 4 1。
第四章 文档/视图结构及其编程
……………………………………………………………………………………………………………………………………………
表4 1 CV
iew 的几个重要派生类
派生视类 说 明
CScrollView 为视图提供滚动和缩放显示功能
CFormView 提供可滚动的视图来显示由对话框控件组成的表单
CRecordView 在控件中显示数据库表中字段的表单视图
CDaoRecordView 在控件中显示数据库表中字段的表单视图
CCtrlView 控件视图类
3 框架 (边框窗口 )类
4
每个应用程序都有一个主边框窗口,主边框窗口标题栏中显示有应用程序的名称。 每
个文档都有一个文档边框窗口,一个文档边框窗口至少含有一个视图用于显示文档的内容 。
对于每个 SDI 应用程序,都有一个从 CFrameWnd 类派生的边框窗口,该窗口既是主边框
窗口,又是文档边框窗口。
对于每个 MDI 应用程序,包含有主边框窗口和子边框窗口 。MDI 主边框窗口中含有一个
称为 MDICLIENT 客户区窗口,该窗口管理主边框窗口的客户区并有自己的子窗 (文档边框窗
口)。图 4 2 为 MDI 主边框窗口、文档边框窗口 (子边框窗口 )和视图的关系。 利用 MFC
图4 2 MDI主边框窗口,文档边框窗口(子边框窗口)与视图的关系
89
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
4 文档模板类
4
为了管理创建文档及其关联的视图和框架窗口的复杂过程,框架使用了两个文档模板
类: 用 于 单 文 档 应 用 程 序 的 CSingleDocTemplate 和 用 于 多 文 档 应 用 程 序 的
CMultiDocTemplate,它们都是 CDocTemplate 的直接派生类。
在文档/视图结构中,实质上由应用程序对象创建文档模板,由文档模板创建文档和框
架窗口,而由框架窗口创建文档对应的视图 。应用程序类中 InitInstance 函数的一个主要
任务就是构造一个或多个适合的文档模板 。
下面的代码(见清单 3 3)阐述了在 InitInstance 函数中创建 CMultiDocTemplate 的过
程。
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_PAINTETYPE,
RUNTIME_CLASS(CMyPainterDoc),
RUNTIME_CLASS(CChildFrame),//custom MDI child frame
RUNTIME_CLASS(CMyPainterView));
AddDocTemplate(pDocTemplate);
首 先 声 明 一 个 指 向 CMultiDocTemplate 类 的 指 针, 并 使 用 new 操 作 符 和
CMultiDocTemplate 类的构造函数对其初始化。 构造函数的参数包括应用程序主框架的标
志符以及三个 CRuntimeClass 参数,这三个参数用于指定窗口框架类、文档类和视图类,它
们由文档模板在响应 “文件”菜单上 “新建”或多文档窗口菜单的 “新建窗口 ”等命令时动态
创建。
另外,为 了 使 RUNTIME _ CLASS 机 制 正 常 工 作, 在 派 生 的 框 架 窗 口 类 中 必 须 使 用
DECLARE_DYNCREATE 宏声明,这是因为框架需要使用 CObject 类的动态构造机制创建文档框
架窗口。
5 文档/视图结构各对象之间的关系
4
文档/视图结构各对象之间的关系见表 4 2。 文档保留该文档的视图列表以及指向创
建该文档的文档模板的指针;视图保留指向其文档的指针,视图窗口是文档框架窗口的子窗
口;文档框架窗口中保留指向活动视图的指针;文档模板保留其已打开文档的列表;Windows
跟踪所有打开的窗口,以便可以向这些窗口发送消息。 所有这些关系都是在文档/视图结构
的创建期间建立的,通过调用全局函数 AfxGetApp,任何对象均可以获得指向应用程序对象
的指针。
90
第四章 文档/视图结构及其编程
……………………………………………………………………………………………………………………………………………
表4 2 文档/视图结构各对象之间的关系
对 象 访问其他对象的方法
使 用 GetFirstViewPosition 和 GetNextView 访 问 文 档 的 视 图 列 表; 调 用
文档
GetDocTemplate 获取文档模板。文档更改后利用 UpDateAllViews 通知所有视图
视图 调用 GetDocument 获得指向文档的指针,调用 GetParentFrame 获取框架窗口
文档框架窗口 调用 GetActiveView 获取当前视图;调用 GetActiveDocument 获取当前视图文档
MDI 框架窗口 调用 MDIGetActive 获取当前活动的 CMDIChildWnd
6 文档/视图结构编程实例
4
4
61 单文档应用程序实例
【例4 1】 Ex04_1 创建一单文档应用程序,并显示字符串,具体说明见表 4 3。
表4 3 例4 1具体说明
项 目 图示和说明
程序界面
(1)启动 VC 60
(2)从 File 菜单中选择 New,打开 New 对话框
(3)选择 MFC AppWizard (exe)
(4)在 Project name 文本框中键入要创建的工程名(Ex04_1)
(5)单击 Location 文本框右边的“...”按钮,输入项目的保存路径
(6)单击 OK 按钮,进入 AppWizard Step 1 of 6
选中 Single documents(单文档)单选按钮,资源语言为中文(中国)(APPWZCHS.DLL)。
(7)AppWizard 的第 2 步~第 6 步,都为缺省选择
(8)单击 AppWizard Step 6 of 6 的 Finish 按钮
(9)单击 New Project Information 对话框中的 OK 按钮
(10)编译和运行程序
得到表 4 3 的应用程序界面,但界面上不显示字符串 Hello VC++ !This is my first
SDI program.
(11)显示字符串
在 Ex04_1View.cpp 文件的 OnDraw()函数中增添显示字符串的代码,见清单 4 1。 91
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
v
oidCEx 04_1View OnDraw( CDC pDC)
{ CEx04_1Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
//TODO: add draw code for native data here
pDC->Te xtOut(50,50,"He l
loVC++Th isismyf
irs
tSDIp
ror
gam. //在(50,50)的位置显示字
" );
//符串
}
(12)再次编译和运行程序
4
62 多文档应用程序实例
【例4 2】 Ex04_2 创建一多文档应用程序,显示字符串,具体说明见表 4 4。
表4 4 例4 2具体说明
项 目 图示和说明
程序界面
图4 4 AddMemb
erVa
ria
ble对话框 图4 5 AddMemb
erFun
cti
on对话框
CS
tri
n 04_
gCEx 2Do
c
Get
Str
in //自动添加成员函数框架
g()
{ }
04_
清单4 2 3个函数的源代码(在 Ex 2Do
c.pp文件中)
c
(4)在视图类实现字符串的显示,重载 OnDraw()函数(见清单 4 3)
93
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
vo
idCEx 04_2V i
ew OnDr aw(CDC pDC)
{ CEx04_2Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDC->TextOut(100,50,pDo c->Ge
tStr
in //在 x=100
g()); y=50的位置输出字符串
}
(5)编译和运行程序
4
63 多视图应用程序实例
【例4 3】 Ex04_3 创建一个具有多个视图的单文档应用程序,具体说明见表 4 5。
表4 5 例4 3具体说明
项 目 图示和说明
程序界面
(1)打开应用程序即显示上述界面,界面有 4 个视图
(2)在编辑控件中改变 x0、y0、x1 和 y1 的取值,单击确定按钮,椭圆位置和大
程序功能
小将随之改变
(3)选择文件- >保存即可实现实现图形数据的保存
(1)新建一单文档应用程序 Ex04_3
在 AppWizard 的 step 4 of 6 对话框中,单击 Advanced 按钮,选 Advanced Options 对话
框中的 Window styles 标签,选中 Use split window 复选框。见图 4 6。其余步骤按缺省选
择即可。
编译、链接和运行程序得到图 4 7 的界面。
(2)为工程添加 CEx04_FormView 类
选择菜单 Insert- > New Form,弹出图 4 8 所示的对话框。在 Name 编辑框中输入 CEx04
_FormView,在 Base Classes 下拉组合框中选择 CFormView。 这样将一个表单视图插入到项
目中。如果编译运行程序,会出现如图 4 9 所示的对话框,以确定程序采用哪个视图。
94
第四章 文档/视图结构及其编程
……………………………………………………………………………………………………………………………………………
图4 6 选中 Us
esl
pitw
ind
ow 图4 7 分割成4个视图的程序界面
图4 8 插入表单视图(
FormV
iew) 图4 9 选择视图
RUNTIME_CLASS(CEx04_3Doc),
RUNTIME_CLASS(CMainFrame), //main SDI frame window
RUNTIME_CLASS(CEx04_3View));
AddDocTemplate(pDocTemplate);
…
}
(4)将视图与切分窗口对应起来
打开 MainFrm.cpp 文件,重载 OnCreateClient 函数,见清单 4 5。
清单4 5 重载 OnCr
eat
eCl
iet函数(在 Ma
n inF
rm.
cpp文件中)
BOOLCMa inFr
ame OnCr e
ateCli
ent(LPCREATESTRUCT/l pcs/CCr eateContext pCo n
text)
{ //注释原有代码
/* return m_wndSplitter.Create(this,
2, 2, //TODO: adjust the number of rows, columns
CSize(10, 10), //TODO: adjust the minimum pane size
pContext);* /
//添加以下代码
VERIFY(m_wndSplitter.CreateStatic (this,2, 2));
VERIFY(m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CEx04_3View),CSize(400,200),pContext));
VERIFY(m_wndSplitter.CreateView(1,0,RUNTIME_CLASS(CEx04_3View),CSize(400,200),pContext));
VERIFY(m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CEx04_3View),CSize(400,200),pContext));
VERIFY(m_wndSplitter.CreateView(1,1,RUNTIME_CLASS(CEx04_FormView),CSize(400,200),pContext));
return true;
并在 MainFrm.cpp 文件加入三个头文件;
# include" Ex04_3Doc.h"
# include" Ex04_3View.h"
# include" Ex04_FormView.h"
(5)编译和运行程序
得到 4 个视图的界面。3 个视图为 CEx04_3View,1 个视图为 CEx04_FormView。 但界面
上没有任何显示的内容。
(6)在文档类中声明成员变量和初始化成员变量(见清单 4 6 和清单 4 7)
04_
清单4 6 声明成员变量(在 Ex 3Do
c.h文件中)
c
las
sCEx 04_ 3Docpub
licCDo
cume
nt
public:
int x0;
int y0;
int x1;
int y1;
96
第四章 文档/视图结构及其编程
……………………………………………………………………………………………………………………………………………
04_
清单4 7 初始化成员变量(在 Ex 3Do
c.pp文件中)
c
04_
CEx 3Do c 04_
CEx 3Do //构造函数
c()
{ x0 = 10;
y0 = 10;
x1 = 100;
y1 = 50;
(7)读写磁盘,保存数据(见清单 4 8)
清单4 8 S
eri
ali
z 04_
e函数(在 Ex 3Do
c.pp文件中)
c
v
oidCEx04_ 3DocSer
ial
ize(
CAr
chi
v r)
e&a
{ if (ar.IsStoring())
{ ar<<x0<<y0<<x1<<y1;
else
ar>>x0>>y0>>x1>>y1;
(8)绘图和显示图形,重载 OnDraw()函数(见清单 4 9)
清单4 9 OnDr 04_
aw 函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx04_3V i
ew OnDr aw(CDC pDC)
{ CEx04_3Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDC ->Ell
ipse(pDoc->x 0pDo c->y0 c->x
pDo 1 c->y
pDo //绘椭圆
1);
}
(9)编译和运行程序
得到在 3 个 CEx04_View 类视图中显示绘制的椭圆,但 CEx04_FormView 类视图上没有任
何显示的内容。
(10)编辑 CEx04_FormView 视图类对应的窗体资源
本例添加 4 个静态文本,4 个编辑框和 1 个下压按钮。
(11)添加与控件关联的成员变量和消息响应函数(见表 4 6)
表4 6 控件ID 号、成员变量和消息函数
控 件 标题 ID 号 变量名 类型 消息相应函数
静态文本 X0:
静态文本 Y0:
静态文本 X1:
静态文本 Y1:
编辑控件 IDC_X0 m_x0 int
编辑控件 IDC_Y0 m_y0 int
编辑控件 IDC_X1 m_x1 int
编辑控件 IDC_Y1 m_y1 int
下压按钮 确定 IDC_BUTTON_OK OnButtonOk 97
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(12)在文档和视图间建立数据通讯
① 在 Ex04_FormView.h 文件开头添加
# include "Ex04_3Doc.h"
② 在 Ex04_FormView.h 文件的类中声明 GetDocument 函数
Public:
CEx04_3Doc* GetDocument();
③ 在 Ex04_FormView.cpp 文件中,编写 GetDocument 函数,获取指向文档的指针,见清
单 4 10。
清单4 10 Ge
tDo
cume
n 04_
t函数(在 Ex FormV
iew.
cpp文件中)
04_
CEx 3Do c CEx 04_FormV iewGetDo c
ument()
{ ASSERT(m_pDocument- > IsKindOf(RUNTIME_CLASS(CEx04_3Doc)));
return (CEx04_3Doc* )m_pDocument;
v
oidCEx 04_ FormV i
ewOnIni
tialUpdae()
t
{ CFormView::OnInitialUpdate();
CEx04_3Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
m_x0 = pDoc- >x0;
m_y0 = pDoc- >y0;
m_x1 = pDoc- >x1;
m_y1 = pDoc- >y1;
UpdateData(false);
}
v
oidCEx 04_ FormV i
ewOnButtonOk()
{ CEx04_3Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
UpdateData(true);
pDoc- >x0 = m_x0;
pDoc- >y0 = m_y0;
pDoc- >x1 = m_x1;
pDoc- >y1 = m_y1;
pDoc- >UpdateAllViews(this);
}
(13)编译和运行
4
64 文档与视图结构之间相互作用关系分析实例
98 例 4 3 中文档与视图结构之间相互作用的关系分析如下 。
第四章 文档/视图结构及其编程
……………………………………………………………………………………………………………………………………………
(1)应用程序启动
·CEx04_3Doc 对象被创建
·CEx04_3View 对象被创建
·CEx04_FormView 对象被创建
·CEx04_3View::OnCreate 函数被调用
·CEx04_3View::OnInitialUpdate 函数被调用
·调用 CEx04_3View::OnUpdate 函数
·初始化视图
(2)用户执行视图命令
·CEx04_FormView 的 OnButtonOk()函数更新 CEx04_3Doc 数据成员
·调用 CDocument::UpdateAllViews
爛其他视图的 OnUpdate()函数被调用
(3)用户执行文档命令
·CEx04_3Doc 数据成员进行更新
·调用 CDocument::UpdateAllViews()
·调用 CEx04_3View::OnUpdate()
·其他视图的 OnUpdate()函数被调用
(4)用户退出应用程序
·视图对象被删除
·CEx04_3Doc 对象被删除
99
第五章 程序界面设计
应用程序的界面对用户的影响很大 ———无论程序代码如何高效、功能如何强大,如果用
户发现它太难于使用,那么这个程序就不会得到广泛的应用 。 菜单、工具栏为应用程序提供
了传递用户命令的界面,而状态栏提供了提示信息的输出区域 。 菜单、工具栏和状态栏共同
组成了 Windows 应用程序的友好界面。
本章要点:
* 菜单资源编辑器的使用
* 在应用程序中使用菜单的方法
* 工具栏资源编辑器的使用
* 在应用程序中使用工具栏的方法
* 在应用程序中使用状态栏的方法
1 界面设计原则
5
用户界面设计的一个总的原则就是用户至上 。 Windows 操作系统的一个重要优势在于
它为所有应用程序提供了相同的界面。 这样,一个有经验的用户就能很快掌握原先并没有
使用过的 Windows 应用程序。 菜单就是个很好的例子:大多数 Windows 应用程序遵循 “文
件”菜单在最左边,“编辑”、“工具”菜单紧接其后,“帮助”菜单在最右边的规则。 此外菜单命
令的位置也很重要。例如,用户希望在“编辑”菜单下找到“拷贝”、“剪切”和“粘贴”命令。 因
此,除非有充分的理由否则不应该改变公认的 Windows 界面规则。
5
11 界面布局原则
应用程序的界面布局不仅影响其外观,而且对其本身的易用性也有着举足轻重的作用 。
界面布局包括控件的位置、元素之间的协调性、空间的使用以及设计的简单性等等 。
(1)控件位置
在大多数界面设计中,并非所有的组成元素都具有同等的重要性,因此必须保证常用的
重要元素处于最明显的位置。
绝大多数语言都具有从左到右和从上到下的书写顺序 。 因此当用户观看屏幕时,会从
左上角开始,最重要的元素应该放置在那里。例如,如果要求在屏幕上显示包括消费者信息
的表单,则应该首先显示姓名字段。而诸如“确定”或“下一步”之类的按钮,则应该在屏幕的
左下方显示。这是因为,通常用户只是在完成了整个表单后,才会单击这些按钮。
将元素和控件分组也非常重要。 将信息功能相近或关联的控件分组排列,要比分散排
100 列效果好。分组排列大多是通过组框等控件完成的 。
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
(2)协调性
用户界面必须保证其协调性。 协调的外观将使应用程序看起来很舒服。 相反,缺乏协
调性,会使应用程序看起来很混乱,从而使用户低估其功能和稳定性 。
为了可视协调性,需要在开发前有一些大概的规划。 例如:控件的种类、尺寸以及字体
等等。这时先做一个模型将会有助于后续的开发 。
由于VC++ 提供了多种类型的控件,这很容易导致将各种类型的控件都加以使用的想法 。
在开发中必须注意避免出现此类想法,而应该仔细选择最适合应用程序的一些控件。 例如
列表框、组合框、列表视图控件和树视图控件都能用于显示一系列信息,但应该尽可能地选
择单一风格的控件。
此外,还需要注意所选用的控件是否合适。 例如将编辑框设置为只读用来显示文本不
如使用静态文本控件更合适。
(3)一致性
抽象地说一致性就是某个对象的可视化线索,如在 Windows 应用程序中,“打开”工具栏
按钮图标就是用于打开文件夹的;通俗地讲,一致性就是看到其外观就能猜到其功能 。
用户界面也需要使用一致性。例如,应用程序中的具有三维效果的按钮表示它们可以
被按下;那么同时又存在具有平坦风格的按钮,程序就失去了一致性。 在应用程序中保持一
致性是压倒一切的。
(4)空间的使用
应用程序中空间的使用也很重要,它有助于改善程序的外观和对某些元素的强调。 当
然空间并非一定要使用。然而过多的控件拥挤在一起,会增加寻找的时间而降低运行效率。
在设计时,需要综合考虑以确定最佳的空间分布 。
使控件间距一致,以及控件垂直和水平对齐也会提高应用程序的易用性 。
(5)简单性
界面设计中最重要的准则可能就是简单性了 。简单即美,这也是艺术中的一个准则。
测试应用程序简单性的最好方法就是实际使用该程序 。 如果在没有帮助的情况下,普
通用户不能很快完成某个操作,那么就需要考虑重新设计了。
(6)颜色
颜色的效果是千差万别的,用户的喜好也会因人而异。在设计应用程序时,需要注意不同文
化中颜色的差异。一般来说,在颜色的使用上应该持保守的态度,尽量选择软色调和中性颜色。
当然,对颜色的选择也需要考虑用户的情况 。例如,在设计儿童软件时,亮红色、绿色和
黄色是很好的选择而对于金融或银行应用程序,这些颜色会产生负面影响。 使用少量的亮
色能够有效地突出重要区域。作为原则,在应用程序中应该限制颜色的数量,而且应该保持
前后的一致性;应该尽可能使用标准 16 色调色板,这样会扩大应用程序的兼容性;此外,还
要考虑色盲用户。
(7)图片和图标
在应用程序中使用图片和图标,能提高应用程序的吸引力。 图片所传达的信息有时是
文字所远远不能及的,同样,其致命的弱点在于不同人的理解可能也会不同 。
工具栏按钮上的不同图标,形象地表达了不同的功能。 但是当用户不能确定某个图标
的功能时,就会产生反作用。 在设计工具栏图标时,应该参考其他应用程序以得到设计的标
准。例如绝大多数 Windows 应用程序,会将打开文件夹的图像作为 “打开”命令工具栏按钮图 101
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
标。如果你的应用程序违反了标准,就会给用户带来混乱。此外,还需要考虑文化的差异。
(8)字体
字体是用户界面的重要组成部分,在选择字体时,必须保证它能在各种不同分辨率的显
示器上都有良好的可读性。
除非计划将应用程序与字体一同发行,否则最好使用标准 Windows 字体,例如 Arial、
New Times Roman 或 System 字体。如果用户的系统不包括指定的字体,则系统将使用其他字
体,这就有可能造成实际的显示与预期不一致 。如果应用程序将在世界范围内发布,则需要
为每种语言选择合适的字体。此外还需要考虑字体所占的空间,例如中文比英文所占的空
间大 50%。为了保持一致性,类似功能的文本要使用相同的字体 。
5
12 用户帮助模型
帮助是应用程序的重要组成部分,它通常是用户寻找问题答案的首选。 用户帮助模型
的设计应该在开发前进行。模型的内容则与应用程序的复杂程度以及预期的用户有关 。
在创建帮助的主题和索引时,一定要从用户的角度出发。 例如, “应该如何格式化页
面?”要比“编辑、页面格式菜单”更容易定位问题。 此外,一定要保证上下文敏感性,也就是
说当用户选择了“格式”然后按下 F1 键,那么应该出现的是针对这个词的帮助,而不是出现
帮助主题。印刷或电子的帮助手册是另一种非常有用的工具,它们能提供在简捷帮助主题
中难于传递的信息。此外,工具提示、状态栏等也能够为用户提供很大的方便 。
2 菜单
5
菜单是 Windows 中的重要资源,它是一系列选项的列表,可以选中选项并产生一条消息
传递给窗口。根据菜单的外观和使用不同可分为:标准菜单、带有图标的菜单、右键菜单和
动态菜单等。
5
21 菜单资源编辑器
菜单的编辑是在菜单编辑器中完成的。 使用菜单资源编辑器,可以完成创建和编辑菜
单项和菜单命令。单击工程工作区的 Resource View 标签,进入资源视图,然后按照下面的
步骤进行菜单编辑:
(1)打开菜单资源编辑器:在 ResourceView 标签中单击 Menu 项左边的 “+ ”,然后双击
IDR_MYPAINTETYPE 打开菜单资源编辑器,见图 5 1。
102 图5 1 菜单资源编辑器 图5 2 Me
nuI
temPr
ope
rti
es对话框
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
(2)编辑菜单:选择菜单条上的新条目框上的一个空的矩形,单击右键弹出快捷菜单选
择 Properties,弹出如图 5 2 所示的对话框。在属性对话框的 Caption 文本框中输入菜单
标题如“菜单测试”,同时可以看到输入的文字菜单条的菜单项中 。 输入菜单中的名字时,在
一个符号前面加上一个“&”符号定义菜单加速键。
(3)填写菜单项的属性页:菜单项属性页中的各项属性说明见表 5 1。
表5 1 Me
nuI
temPr
ope
rti
es对话框中各属性说明
属性名称 说 明
Separator 复选框 分隔线,它的作用是把菜单中的选项分开以便查看。如果选择了此特性,则表示
加上的菜单项为一条分隔线。令此属性选择为空
弹出式菜单,如果选择了此选项,表示创建的这个菜单项为一个弹出式菜单的根
Popup 复选框
条目
非激活状态,如果下面的 Grayed 属性被选中,则此选项也同时被选中。如果单选
Inactive 复选框 此选项,而没有选择 Grayed 属性选项,则表示此菜单项初始化为非激活状态,即
它对鼠标的单击不响应。令此选项为空
选中标志,如果选中此属性,则表示创建的菜单项在初始化时为选中状态。令此
Checked 复选框
属性选择为空
加灰选项,如果选择了此选项,则表示创建的菜单项初始化为灰色非激活状态。
Grayed 复选框
令此属性选择为空
菜单项居右,如果选择了此选项,则表示创建的菜单项在运行时位于菜单的最右
help 复选框
边。令此属性选择为空
菜单项资源号,如果创建的菜单项不是一个弹出式菜单,就必须填写此菜单项资
ID 编辑 源 ID 号,由于前面选择了 Popup 选项,因此创建的菜单项是一个弹出式菜单项,
此文本输入会变灰,禁止输入
菜单项的标题。创建菜单项时必须为此菜单项输入一个名字,即为菜单项的
Caption 编辑框
标题
None 此选择为缺省值,表示不选中此属性
对于静态的菜单条目,这个值把创建的条目放置在一个新
的行上。对于弹出式菜中,这个值把创建的条目放置在一
此 属 性 Cloumn
个新的栏上。设置这个属性只是在应用程序运行时影响菜
Break 下拉列表框 有 三 个 单外观,这种外观的影响并不在菜单编辑器中显示出来
可选值
对于弹出式菜单,这个值使用一条垂直线,把新的片和旧的
Bar 栏分开。设置这个属性只是在应用程序运行时影响菜单的
外观,这种外观的影响并不在菜单编辑器中显示出来
提示性文本。这个属性包含了可以出现在状态条上的文本。当菜单项成为焦点
时,在此属性框里输入的文本会出现在状态栏上。如果输入的文本中有“ \n”则
Prompt 编辑框 “
\ n”后的文本将出现在工具提示中,而之前的文本将出现在状态栏中。这个属
性只在使用了 MFC 类库支持时起作用。由于前面选择了 Popup 选项,因此创建
的菜单项是一个弹出式菜单项,此文本输入框会变灰,禁止输入
5
2 u类
2 CMen
CMenu(菜单类)是 CObject 类的直接派生类,用于管理应用程序窗口中的菜单。 该类的
主要成员函数见表 5 2。 103
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
表5 2 CMe
nu类的主要成员函数
成 员 函 数 说 明
BOOL AppendMenu() 将一新菜单项增加到一个菜单尾部
BOOL Attach() 将一已存在的 Windows 菜单连接到一个 CMenu 对象
BOOL CreatMenu() 创建一菜单,并将该菜单连接到 CMenu 对象
BOOL CreatPopupMenu() 创建一弹出式菜单
HMENU Detach() 将一菜单与 CMenu 对象断开连接
BOOL DeleteMenu() 从菜单中删除一个菜单项
BOOL DestroyMenu() 销毁菜单及所有 Windows 资源
UINT GetMenuItemCount() 获得菜单所包含的项数
int GetMenuString() 拷贝指定菜单项标签到缓冲区
CMenu* GetSubMenu() 获得指定弹出式菜单的指针
BOOL InsertMenu() 在指定位置插入一个新菜单项
BOOL LoadMenu() 加载一个菜单资源
BOOL SetMenuItemBitmaps() 将指定位图与一菜单项相关联
BOOL TrackPopupMenu() 在指定位置处显示一个浮动的弹出式菜单
5
2 I类
3 CCmdU
该类没有基类,仅用于 CCmdTarget 派生类的 ON_UPDATE_COMMAND_UI 处理函数中。在应用
程序运行中,经常需要根据应用程序当前的状态对菜单项进行改变。例如对于 Edit|Copy 菜单
项,如果没有内容被选的话,该菜单项就呈灰色被禁止使用。这就是说,需要使用状态与菜单
项保持同步,在 MFC 类库中采用了如下方法:每当弹出式菜单第一次被显示时,都会发送特殊的
更新命令 UI 消息,该消息通常被传递给与菜单项相联系的对象。更新命令 UI 消息的控制函数
以一个 CCmdUI 对象作为其参数,而该 CCmdUI 对象中包含了一个指向相应菜单项的指针,于是
控制函数就可以利用该指针对菜单项进行修改。更新命令 UI 消息只适用于弹出式菜单的菜
单项和工具栏按钮,而对于始终显示的顶层菜单项则不适用。例如,不能利用更新命令 UI 消息
来禁止 Edit 菜单项。CCmdUI 类的数据成员及常用成员函数见表 5 3。
表5 3 CCmdUI类的数据成员及常用成员函数
数据成员及成员函数 说 明
m_nID 该数据成员中存储了用户接口对象的 ID
m_nIndex 该数据成员中存储了用户接口对象的索引
该数据成员为指向 CCmdUI 所代表的菜单的指针。如果该参数为 NULL,则表示
m_pMenu
该对象不是菜单
该数据成员为指向 CCmdUI 所代表的菜单中包含的第一个菜单项的指针。如
m_pSubMenu
果该参数为 NULL,则表示该对象不是菜单
m_pOther 该数据成员为发送通告消息的 Windows 对象
void Enable(
) 允许或禁止用户接口项,如菜单、
工具栏按钮等
void SetCheck(
) 设置用户接口项的检查状态
void SetRaido(
) 设置单选组的选择状态
void SetText(
) 设置用户接口项的文本
104 void ContinueRouting(
) 通知消息循环机制继续运行
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
5
24 标准菜单编程实例
以例 5 1 说明标准菜单的编辑、 菜单更新与菜单消息函数等有关内容。
例5 1】 Ex05_1 标准菜单的编辑、
【 消息响应与菜单更新,具体说明见表 5 4。
表5 4 例5 1具体说明
项 目 图示和说明
程序界面
图(
a)菜单 图(
b)消息对话框 图(
c)消息对话框 图(
d)更新菜单
( 消息对话框 1”
1)点击图(a)菜单“ 和“消息对话框 2”
分别弹出如图( 和(
b) 所示的消息对话框。
c)
程序功能 (2)点击图(a)菜单“连接”菜单出现如图(d)所示的菜单,“连接”菜单无效,且“连接”菜单前
有打勾标记。点击“断开”菜单则出现“断开”菜单无效,且“断开”菜单前有打勾标记
(1)新建一单文档应用程序 Ex05_1
(2)编辑菜单
① 创建“菜单测试”下拉菜单(见图 5 2)。
② 在“菜单测试”下拉式菜单下创建 “调用对话框 ”、“连接 ”和 “断开 ”三个菜单命令项。
注意创建“调用对话框”菜单要选中属性页中的 Popup 选项,创建“连接”和 “断开”菜单时不
要选中属性页中的 Popup 选项,并给出菜单 ID 号。
③ 在“调用对话框”的下拉子菜单下创建“消息对话框 1...”和“消息对话框 2...”,菜单
项有省略号表示单击该项将弹出对话框 。各菜单 ID 号见表 5 5。
表5 5 需要添加的菜单命令消息函数
菜单名称 菜单 ID 号 消 息 菜单消息函数
消息对话框 1... ID_TEST_MESSAGEDLG1 COMMAND OnTestMessagedlg1
消息对话框 2... ID_TEST_MESSAGEDLG2 COMMAND OnTestMessagedlg2
ID_TEST_CONNECT COMMAND OnTestBreak
连 接
UPDATE_COMMAND_UI OnUpdateTestBreak
ID_TEST_BREAK COMMAND OnTestConnect
断 开
UPDATE_COMMAND_UI OnUpdateTestConnect
(3)添加消息函数
通过 ClassWizard 为相应菜单命令增加命令消息处理(WM_COMMAND)函数,见图 5 3。
在 Class name 中选择 CEx05_1View;在 Object IDs 列表框中选择需要处理的菜单项 ID;
在 Message 列表框中选择 COMMAND 消息,再单击 Add Function 按钮即可。 添加的消息
响应函数见表 5 5。
(4)添加菜单更新消息函数
添加方法与上相同,只是在 Message 列表框中选择 UPDATE_COMMAND_UI 消息,再单击
Add Function 按钮即可。添加菜单更新消息函数见表 5 5。
(5)定义成员变量 105
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
图5 3 利用“
MFCC
las
sWi
zar
d"添加菜单命令消息函数
05_
清单5 1 定义两个 BOOL 变量(在 Ex 1Vi h文件中)
ew.
c
lassCEx 05_ 1V
iewpub l
icCV
iew
...
public:
BOOL bCheck;
BOOL bEnable;
...
(6)初始化成员变量(在构造函数中初始化,见清单 5 2)
(7)添加和编写菜单消息函数与菜单更新消息函数(见清单 5 2)
05_
清单5 2 初始化成员变量和消息函数(在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx05_1V i 05_
ewCEx 1Vi //构造函数,始化成员变量
ew()
{ bCheck = 1;
bEnable = 1;
//菜单消息函数
vo
idCEx05_1V iew
OnTe
stMess
age
d l
g //点击消息对话框 1 菜单的消息函数
1()
{ AfxMessageBox("这是消息对话框 1!");}
vo
idCEx05_1V iew
OnTe
stMess
age
d l
g //点击消息对话框 2 菜单的消息函数
2()
{ AfxMessageBox("这是消息对话框 2!");}
vo
idCEx05_1V iew
OnTe
stBr
eak()//点击断开菜单的消息函数
{ bCheck = 1;
bEnable = 1;
v
oidCEx05_1V iew
OnTe
stCo
nne
c //点击连接菜单的消息函数
t()
{ bCheck = 0;
bEnable = 0;
106
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
//菜单更新消息函数
vo
idCEx05_1V iewOnUpd ateTestConnet(
c //更新连接菜单
CCmdUI pCmdUI)
{ pCmdUI- >SetCheck(bCheck);//打勾或不打勾
pCmdUI- >Enable(!bEnable);//禁用或启用
}
vo
idCEx05_1V iewOnUpd ateTestBr
e k(
a CCmdUI pCmdUI)//更新断开菜单
{ pCmdUI- >SetCheck(!bCheck);//打勾或不打勾
pCmdUI- >Enable(bEnable);//禁用或启用
}
(8)编译和运行
5
25 带有图标的菜单编程实例
在许多 Windows 应用软件中,其菜单命令旁边带有一个图标,可使用 CMenu 类的成员函
数 SetMenuItemBitmaps,该函数原型如下:
表5 6 例5 2具体说明
项 目 图示和说明
程序界面
程序功能 点击图标菜单,上面两个图标交替出现
(1)新建一单文档应用程序 Ex05_2
(2)编辑菜单
在查看 菜 单 后 增 加 菜 单: 图 标 菜 单 测 试, 在 其 下 增 加 下 拉 菜 单 项: 图 标 菜 单 ID _
TEST_BMPMENU
(3)添加两个位图资源
选择 ResourceView 视图,将鼠标放在文件夹 Ex05_2 resources 上,单击鼠标右键,在弹
出的右键菜单中,点击 insert 弹出 Insert Resource 对话框,选中其中的 Bitmap,这样就
插入了一个 ID 号为 IDB_BITMAP1 的位图,编辑该位图。同样插入第 2 个位图。
位图 1:IDB_BITMAP1 ;位图 2:IDB_BITMAP2
(4)定义成员变量(见清单 5 3)
(5)初始化成员变量(在构造函数中初始化,见清单 5 4)
(6)编写 OnDraw()函数(见清单 5 4) 107
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(7)添加和编写菜单消息函数与菜单更新消息函数(见清单 5 4)
05_
清单5 3 定义成员变量(在 Ex 2Vi h文件中)
ew.
c
lassCEx 05_2Viewpub licCV i
ew
...public:
CBitmap bitmap1,bitmap2;
BOOL bCheck;
05_
清单5 4 函数的编写(在 Ex 2Vi
ew.
cpp文件中)
05_
CEx 2View 05_
CEx 2View()//构造函数,初始化成员变量
{ bitmap1.LoadBitmap(IDB_BITMAP1);
bitmap2.LoadBitmap(IDB_BITMAP2);
bCheck = 1;
v
oidCEx 05_2V iewOnDr aw( CDC pDC) //在 OnDraw()函数实现位图与菜单的关联
{ CMenu * m_pMenu = AfxGetMainWnd()- >GetMenu();
m_pMenu- > SetMenuItemBitmaps(ID_TEST_BMPMENU, MF_BYCOMMAND,
&bitmap1,&bitmap2);
}
v
oidCEx 05_2V iewOnTe s
tBmpme nu()//菜单消息函数
{ bCheck = !bCheck;
v
oidCEx05_ 2ViewOnUpd ateTes
t nu(
Bmpme //菜单更新消息函数
CCmdUI pCmdUI)
{ pCmdUI- >SetCheck(!bCheck);
}
(8)编译和运行
5
26 快捷菜单(上下文菜单,右键菜单)编程实例
用户单击鼠标右键时弹出的菜单被称为快捷菜单 。下面举例说明其创建过程。
【例5 3】 Ex05_3 快捷菜单,具体说明见表 5 7。
表5 7 例5 3具体说明
项目 图示和说明
程序菜单
图(
a) 快捷菜单 图(
b) 消息对话框
点击鼠标右键弹出图(a)所示的快捷菜单,再点击快捷菜单消息对话框,弹出图(b)的消
程序功能
108 息对话框
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
(1)新建一单文档应用程序 Ex05_3
(2)编辑菜单资源
① 创建一个新的菜单条。
选择 Insert Resource 菜单命令,在弹出的对话框中选择 Menu,然后单击 New 按钮,即可
向应用程序中添加一个新的菜单资源 。 添加操作也可通过右键单击 ResourceView 视图中
Menu 项,并 在 弹 出 的 快 捷 菜 单 中 选 择 Insert | Menu 命 令, 修 改 菜 单 资 源 的 ID 号 为
IDR_RIGHT_MENU。
② 添加 菜 单 项 和 菜 单 命 令, 步 骤 与 前 面 所 介 绍 的 完 全 类 似, 编 辑 完 成 后 的
IDR_RIGHT_MENU菜 单 资 源 如 图 5 4 所 示 。 其 中 “消 息 对 话 框 ... ”菜 单 ID 号 为
ID_SUB0_MSGDLG。
(3)添加消息函数
① OnContextMenu
VC60 专 门 为 使 用 快 捷 菜 单 提 供 了 WM _
CONTEXT_MENU 消息。利用 ClassWizard 添加,可以
添加到 CMainFrame 类或视类,本例添加到视类。
② OnSub0Messagedlg
利用 ClassWizard 添加点击 “消息对话框...”
图5 4 快捷菜单
菜单的消息响应函数 OnSub0Messagedlg。
(4)编写消息函数(见清单 5 5)
05_
清单5 5 消息函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx05_3V iewOnCo ntextMenu(CWnd pWndCPo in
tp oin //OnContextMenu 函数
t)
{ CRect rect;//定义 CRect 类对象
GetClientRect(&rect);//将客户区赋给 rect
CMenu menu;
menu.LoadMenu(IDR_CONTEXT_MENU);//载入快捷菜单资源
CMenu* pContextMenu = menu.GetSubMenu(0);//获得上下文菜单的第 0 号子菜单,
GetCursorPos(&point);//获得当前光标位置
pContextMenu- >TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON,
point.x, point.y, this,&rect);
menu.DestroyMenu();//释放菜单资源,以免浪费空间
}
v
oidCEx05_3V iewOnS ub0Me s
saged
lg()//菜单消息函数
{ AfxMessageBox(“您点击了快捷菜单中的消息对话框菜单!”);
}
(5)编译和运行
5
27 动态菜单编程实例
在应用程序中,可能需要动态的创建和改变菜单 。 这时用户可以定制另外的菜单资源,
并需要在应用程序中调用一些必要的函数,将菜单载入并激活。
【例5 4】 Ex05_4 动态菜单,具体说明见表 5 8。
109
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
表5 8 例5 4具体说明
项 目 图示和说明
程序界面
(1)新建一单文档应用程序 Ex05_4
(2)编辑菜单资源
① 增加一新菜单 IDR_MENU1,其中包含 color 菜单,
见图 5 5。
② 在 IDR_MENUFRAME 菜单条的查看菜单下增加 2
个菜单项,分别为:增加颜色菜单 ID_VIEW_ADD 和删除颜 图5 5 增加IDR_MENU1菜单
色菜单 ID_VIEW_DELETE。
(3)定义成员变量(见清单 5 6)
清单5 6 定义成员变量(在 Ma
inF h文件中)
rm.
c
lassCMa inFramepub
licCF
rameWnd
...
public:
BOOL bCheck;//是否选中
...}
(4)在构造函数中初始化 bCheck(见清单 5 7)
(5)添加和编写菜单消息函数与菜单更新消息函数(见清单 5 7)
清单5 7 编写构造函数与消息函数 (在 Ma
inF
rm.
cpp文件中)
CMainF
rame CMa i
nF rame()//在构造函数中初始化 bCheck
bCheck = 0;
//菜单消息函数
vo
idCMa i
nF r ameOnV iewAdd()//点击增加颜色菜单的消息函数
{ CMenu addmenu,* mainmenu;
if(!addmenu.LoadMenu(IDR_MENU1))
{MessageBox("菜单装入失败!");
return;//如装入失败,显示消息框,且返回
}
CString str =" 颜色(&C)";//要增加的菜单项的标签。
mainmenu = AfxGetMainWnd()- >GetMenu();//取得指向窗口菜单的 CMenu 对象的指针。
//将弹出式菜单插入到第 2 项菜单之前(菜单项从 0 开始计算)
mainmenu- >InsertMenu (1, MF_POPUP|MF_BYPOSITION|MF_STRING,
(UINT)addmenu.GetSubMenu(0)- > m_hMenu,str);
addmenu.GetSubMenu(0)- > m_hMenu;//是被装入菜单的第一个菜单项的弹出式菜单的菜单句柄。
110 mainmenu- >Detach();//将窗口菜单与 CMenu 对象分离。
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
(6)编译和运行
3 工具栏
5
工具栏中包含了一组用于执行命令的位图式按钮,其按钮被按下时与菜单选项和键盘
加速键作用相同,也会发送相应的命令消息。 工具栏一般
位于主框架窗口客户区域的上方,用户可以拖动工具栏将
其停靠在客户区的任何位置,也可以使用鼠标改动它的
大小。
5
31 工具栏资源编辑器
图5 6 为工具栏编辑器界面,使用工具栏编辑器可
以完成的功能包括:创建新的工具栏和按钮把位图转换为
工具栏资源,创建、移动和编辑工具栏按钮。
(1)创建工具栏
创建新的工具栏和按钮可以按下面的步骤完成:
① 选择 Insert |Resources 菜单命令; 图5 6 工具栏编辑器
② 在弹出的资源选择对话框中选择 Toolbar 选项; 111
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
③ 单击 New 按钮。
(2)创建、移动和编辑工具栏按钮
① 创建一个新的工具栏按钮
● 用鼠标选择工具栏最右端的空白按钮 。
● 在按钮上绘图。
● 双击该按钮,然后在弹出的按钮属性对话框图 5 7 中输入按钮 ID 号和提示。
图5 7 按钮属性对话框
按钮属性对话框各项说明见表 5 9。
表5 9 按钮属性对话框的各项说明
项 目 说 明
ID 按钮的 ID 号
Width 按钮中位图的宽度值,以像素为单位
Height 按钮中位图的高度值,以像素为单位
Prompt 按钮条目被选中时,\n 前面的文本将要显示在状态条上。\n 后面的文本为工具提示
② 移动一个工具栏按钮
首先用鼠标选择一个工具栏按钮,然后按住鼠标左键并移动此按钮到工具栏的相应位置。
③ 删除一个工具栏按钮
首先用鼠标选择一个工具栏按钮,使用鼠标把它拖到工具栏外。
④ 在工具栏上的两个按钮中插入一个空白区
● 在一个后面不是空白区的按钮前插入空白区时,只需要把此按钮用鼠标向后拖动,
直到此按钮的一半部分与后面的按钮重叠在一起为止 。
● 在一个后面是空白区的按钮前插入空白区时,并且仍然保持后面的空白区时,只需
要把此按钮用鼠标向后拖动,直到此按钮的一半部分与后面的按钮重叠在一起,或者此按钮
的右边边缘部分与下一个按钮刚好接触 。
● 在一个后面是空白区的按钮前插入空白区,并且删除后面的空白区,使前后两个按钮
相邻时,只需要把此按钮用鼠标向后拖动,直到此按钮的一半部分与下一个按钮重叠为止。
⑤ 删除工具栏按钮的空白区
● 选择工具栏中的一个空白区两边的其中一个按钮 。
● 使用鼠标把此按钮向空白区方向拖动,直到此按钮的一半部分与另一边的按钮的
一半重叠为止。
5
32 CToo
l r类
Ba
112 CToolBar 是工具栏类,用于管理应用程序窗口中的工具栏。 CToolBar 类的继承关系如
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
图 5 8 所示。
图5 8 CTo
ol r类的继承关系
Ba
成 员 函 数 说 明
int CommandToIndex() 获取工具栏中第一个有给定 ID 的按钮的索引
BOOL Create() 创建工具栏
void GetButtonInfo() 获取按钮的有关信息
UINT GetButtonStyle() 获取按钮的风格
CString GetButtonText() 获取按钮的文本
UINT GetItemID() 获取给定索引按钮的 ID
void GetItemRect() 获取给定索引项目的显示矩形
CTool BarCtrl&GetToolBarCtrl() 获取对 CToolBar 对象描述的 CToolBarCtrl 对象的引用
BOOL LoadBitmap() 加载工具栏的按钮图像
BOOL LoadToolBar() 加载工具栏资源
BOOL SetBitmap() 设置新的工具栏按钮位图
void SetButtonInfo() 设置按钮的 ID 风格和图像序号
BOOL SetButtons() 设置工具栏按钮的 ID
void SetButtonStyle() 设置按钮的风格
BOOL SetButtonText() 设置按钮的文本
void SetHeight() 设置工具栏的高度
void SetSizes() 设置按钮的大小
5
33 常规工具栏编程实例
常规工具栏的操作与应用主要包括工具栏创建、 载入、
显示与隐藏等,具体实例见例 5 5。
【例5 5】 Ex05_5 工具栏的创建、载入、显示与隐藏,具体说明见表 5 11。
表5 11 例5 5具体说明
项 目 图示和说明
程序界面
(1)程序启动时载入标准工具栏和绘图工具栏
程序功能
(2)点击查看下面的绘图工具栏菜单可以使绘图工具栏显示或隐藏
(1)新建一单文档应用程序 Ex05_5
(2)编辑工具栏资源
① 创建绘图工具栏:ID_DRAW_TOOLBAR;
② 在绘图工具栏中添加绘图按钮;
直线: ID_DRAW_LINE; 矩形: ID_DRAW_RECTANGLE
圆: ID_DRAW_CIRCLE; 文字: ID_DRAW_TEXT 113
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(3)将工具栏载入应用程序中
在应用程序中一般通过重载 CMainFrame 类中的 OnCreate 函数载入工具栏。 如果应用
程序中包括不同的子框架窗口类型,需要为每个子框架窗口创建不同的工具栏,这时也可在
CChildFrame 类的 OnCreate 函数中载入工具栏。OnCreate 函数是 WM_CREATE 消息映射的处
理函数,该消息处理函数将在框架窗口创建时被调用 。
① 定义 CToolBar 对象(见清单 5 8);
清单5 8 定义对象 (在 Ma
inF h文件中)
rm.
c
las
sCMa inFramepub li
cCF rameWnd
public:
CToolBar m_DrawToolBar;//定义 CToolBar 类的对象
}
② 工具栏载入(见清单 5 9)。
(4)工具栏的显示与隐藏
① 编辑查看菜单下的绘图工具栏菜单;
菜单名:绘图工具栏 ID 号:ID_VIEW_DRAWTOOLBAR
② 添加和编写菜单消息函数与菜单更新消息函数(见清单 5 9)。
清单5 9 函数(在 Ma
inF
rm.
cpp文件中)
//将工具栏载入应用程序中
in
tCMa inF r
ame OnCreae(
t LPCREATESTRUCTl pCre at
eStruct)
{...
//载入绘图工具栏
if (!m_DrawToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
|CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC)||
!m_DrawToolBar.LoadToolBar(IDR_DRAW_TOOLBAR))
{ TRACE0("绘图工具栏创建失败! \n" );
return - 1; //fail to create
m_DrawToolBar.SetWindowText("绘图工具栏");//给标准工具栏加上标题
//绘图工具栏停靠
m_DrawToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_DrawToolBar);
return 0;
//菜单消息函数
vo
idCMainFr ameOnV iewDrawb r()
a
{ BOOL bVisible = ((m_DrawToolBar.GetStyle()& WS_VISIBLE)!= 0);
ShowControlBar(&m_DrawToolBar,!bVisible, FALSE);
RecalcLayout();
}
//菜单更新消息函数
vo
idCMainFr ameOnUpd ateViewDr awbr(
a CCmdUI pCmdUI)
{ BOOL bVisible = ((m_DrawToolBar.GetStyle()& WS_VISIBLE)!= 0);
pCmdUI- >SetCheck(bVisible);
}
114
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
(5)编译和运行
5
34 下拉式工具栏按钮编程实例
使用 SetButtonlnfo 函数将工具栏按钮的风格设置为 TBSTYLE _DROPDOWN,即可创建下
拉式工具按钮。
在应用程序中,要求当用户按下缩放观察比例命令按钮时,就会打开一个菜单,该菜单
中包括视 图 能 够 被 缩 放 的 比 例。 由 于 此 时 按 钮 向 主 框 架 窗 口 传 递 的 并 非 一 般 按 钮 的
WM_COMMAND消息,而是通告下拉操作的 TBN_DROPDOWN 消息。 框架窗口所能处理的工具栏按
钮消息只是 WM_COMMAND 和 UPDATE_COMMAND_UI 消息,因此要想使框架窗口对按钮的下拉操
作作出处理的话,必须使用别的途径。
MFC 中提供了 ON_NOTIFY 宏用于处理复合对象消息,实际上,对对象的每个操作都会向
框架窗口发送 WM_NOTIFY 消息。WM_NOTIFY 消息包含了发送消息的控件 ID(该 ID 存放在其
消息处理函数的 wParam 参数中 )和一个存放在 lParam 中的结构指针。 该结构指针可以执
行一个 NMHDR 结构。
【例5 6】 Ex05_6 下拉工具栏的创建与应用,具体说明见表 5 12。
表5 12 例5 6具体说明
项 目 图示和说明
程序界面
(1)新建一单文档应用程序 Ex05_6
(2)编辑工具栏资源
在标准工具栏中添加放大按钮 ,该按钮的 ID 号为 ID_ENLARGE
(3)创建下拉式按钮(见清单 5 10)
清单5 10 在 OnCr
eae函数中创建下拉按钮(在 Ma
t inF
rm.
cpp文件中)
i
ntCMainFrame OnCreate(LPCREATESTRUCTl pCreateStruct)
{ //载入标准工具栏
m_wndToolBar.SetWindowText("标准工具栏");//给标准工具栏加上标题
//标准工具栏停靠
//在放大按钮旁显示三角箭头
m_wndToolBar.GetToolBarCtrl().SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS);
//在放大按钮 9(从 0 开始编号,所以第 9 个按钮编号为 8,其中空格也算一个按钮)旁创建下拉按钮
m_wndToolBar.SetButtonStyle(8,TBSTYLE_BUTTON | TBSTYLE_DROPDOWN);
} 115
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(4)编辑菜单资源
添加一个缩放菜单,ID 号为:IDR_ZOOM,下面带各种缩放比例的子菜单。
25% ID_ZOOM_25
50% ID_ZOOM_50
100% ID_ZOOM_100
200% ID_ZOOM_200
400% ID_ZOOM_400
(5)使用 ON_NOTIFY 宏显示下拉式按钮菜单
① 在视类的实现文件中添加 ON_NOTIFY 消息映射(见清单 5 12);
② 在视类的头文件中声明 OnDropDown 函数(见清单 5 11)。
(6)添加和编写 OnDropDown 消息函数(见清单 5 12)
(7)定义变量和赋初值
在 Ex05_6View.h 文件中定义 BOOL 型变量 bCheck25(见清单 5 11)。
在构造函数中赋初值 bCheck25 = 0(见清单 5 12)。
05_
清单5 11 声明变量与消息函数(在 Ex 6Vi h文件中)
ew.
c
lassCEx 05_6Viewpub li
cCV i
ew
...
public:
BOOL bCheck25;//定义 BOOL 型变量 bCheck25
protected:
//AFX_MSG(CEx05_6View)
...
//}}AFX_MSG
afx_ms gv oidOnDr opDown(NMHDR pNo t
ifySt
ruc
tLRESULT pRe
sul //手工添加
t);
DECLARE_MESSAGE_MAP()
}
(8)添加和编写不同放大比例的菜单消息响应函数和菜单更新消息响应函数 (见清单
5 12)
05_
清单5 12 函数(在 Ex 6Vi
ew.
cpp文件中)
...
BEGIN_MESSAGE_MAP(CEx05_6View, CView)
//{{AFX_MSG_MAP(CEx05_6View)
...
//}}AFX_MSG_MAP
//Standard printing commands
ON_ NOTIFY(TBN_ DROPDOWNAFX_ IDW_
TOOLBAROnDr
o //添加消息映射
pDown)
END_MESSAGE_MAP()
CEx 05_6ViewCEx05_ 6View() //构造函数
{ bCheck25 = 0;
//缩放菜单下拉工具按钮响应消息函数 OnDropDown
vo
idCEx 05_ 6View
OnDropDown( NMHDR pNo ti
fyStruc
tLRESULT pRe
sut)
l
{ NMTOOLBAR* pNMToolBar = (NMTOOLBAR* )pNotifyStruct;
116 CRect rect;
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
5 r 和 CDi
4 CReBa alogBa
r
5
4 r类
1 CReBa
ReBar 是一种工具栏,它的主要用途是作为子窗口、通用对话框控件、菜单和工具栏等的
容器。一个 ReBar 可以包含一个或多个带区 (band)。每一个带区可以包含拖动条 (gripper
bar)、位图、文本标签和子窗口的任意组合。
CReBar 类的继承关系如图 5 9 所示。
图5 9 CReBa
r类的继承关系
117
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
成 员 函 数 说 明
BOOL AddBar() 在 ReBar 中增加一个区段
BOOL Create() 创建一个 ReBar
CReBarCtrl& GetReBarCtrl() 允许直接访问在下面的公共控件
5
42 CD
ial r类
ogBa
DialogBar 是一个基于对话 框 模 板 的 控 件 栏。 它 与 对 话 框 有 些 类 似,可 以 包 含 标 准
Windows 通用控件和一些用户自定义的控件,并可以在这些控件之间进行转换 。它具有无模
式对话框的功能,使用对话框编辑器可以设计并创建对话框栏 。
CDialogBar 类的继承关系如图 5 10 所示。
图5 10 CD
ial
o r类的继承关系
gBa
5
43 编程实例
【例5 7】 Ex05_7 ReBar 创建与应用,具体说明见表 5 14。
表5 14 例5 7具体说明
项 目 图示和说明
程序界面
(1)新建一单文档应用程序 Ex05_7
(2)创建 ReBar
① 在 MinFrm.h 中定义 CReBar 类的对象(见清单 5 13);
② 在 CMainFrm.cpp 文件中用 CReBar 类的 Create 函数创建 ReBar(见清单 5 14)。
(3)创建一 DialogBar
118 ① 建立对话框资源:IDR_DLGBAR,见图 5 11;
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
图5 11 创建对话框IDR_
DLGBAR
② 在对话框中添加控件:组合框 IDC_ADDRESS;
③ 在 MinFrm.h 中定义 CDialogBar 类的对象(见清单 5 13);
清单5 13 定义对象(在 Ma
inF h文件中)
rm.
c
lassCMa inFramepubl
icCF rameWnd
...
public:
CReBar m_RebarToolBar;//定义一 CReBar 类对象
CDialogBar m_DlgToolBar;//定义一 CDialogBar 类对象
CButton m_check;//定义一 CButton 类对象
...}
i
ntCMa inFrame OnCreate(LPCREATESTRUCTl
pCr
eat
eSt
rut)
c
{...
//创建 ReBar
if(!m_ReBarToolBar.Create(this))
{ TRACE0("创建 ReBar 失败!\n" );
return-1;
//创建 CDialogBar
if (!m_DlgToolBar.Create(this, IDR_DLGBAR,CBRS_ALIGN_TOP, AFX_IDW_DIALOGBAR))
{ TRACE0("Failed to create dialogbar\n" );
return-1;//fail to create
//创建复选框
if(!m_check.Create("请选择",WS_CHILD|WS_VISIBLE|BS_AUTOCHECKBOX,
CRect(0,0,20,20),this,IDC_CHECK))
{ TRACE0("创建复选框失败!\n" );
return-1;
//将对话框条添加到 ReBar 上
m_RebarToolBar.AddBar(&m_DlgToolBar," 地址:",NULL,RBBS_GRIPPERALWAYS);
//将复选对话框添加到 ReBar 上
m_RebarToolBar.AddBar(&m_check," 复选框:",NULL,RBBS_BREAK|RBBS_GRIPPERALWAYS);
}
119
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(6)添加复选按钮的消息响应函数
① 添加单击复选按钮的 OnClick 消息函数声明(见程序清单 5 15);
清单5 15 OnC
lic 05_
k消息函数声明(在 Ex 7Vi h文件中)
ew.
c
lassCEx 05_ 7Viewpub l
icCV iew
...
protected:
//AFX_MSG(CEx05_7View)
//NOTEthe ClassWizard will add and remove member functions here.
//DO NOT EDIT what you see in these blocks of generated code !
//AFX_MSG
afx_ms gv o
idOnC li
ck();
DECLARE_MESSAGE_MAP()
};
② 添加单击复选按钮的消息映射说明(见清单 5 16);
③ 编写 OnClick 消息函数 (见清单 5 16);
④ 编写 OnDraw 函数 (见清单 5 16);
⑤ 在 CEx05_7View.cpp 文件开头添加# include" MainFrm.h" (见清单 5 16)。
05_
清单5 16 消息映射说明和函数编写(在 Ex 7Vi
ew.
cpp文件中)
#i nc
lude"Ma inF rm.h"
...
IMPLEMENT_DYNCREATE(CEx05_7View, CView)
BEGIN_MESSAGE_MAP(CEx05_7View, CView)
//{{AFX_MSG_MAP(CEx05_7View)
//NOTEthe ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//AFX_MSG_MAP
//Standard printing commands
...
ON_ BN_ CLICKED( IDC_ CHECK OnC li
ck)//复选按钮的消息映射说明
END_MESSAGE_MAP()
voi
dCEx 05_ 7ViewOnC l
ick()//单击复选框的消息响应函数
{ Invalidate();
}
voi
dCEx 05_ 7ViewOnDr aw(CDC pDC) //OnDr aw 函数
{ CEx05_7Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
//TODO: add draw code for native data here
CString clickmsg;
if(((CMainFrame*)(AfxGetApp()- >m_pMainWnd))- >m_check.GetCheck())
clickmsg =" 复选框被选中!";
else
clickmsg =" 复选框未被选中!";
pDC- >TextOut(10,10,clickmsg);
}
120
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
5 状态栏
5
在应用程序中的状态栏可以在用户不断工作的情况下为用户显示有用信息 。 它通常位
于窗口底部,既不接受用户输入也不产生命令消息。 状态栏的作用就是在程序的控制下在
窗格(pane)中显示一些文本。
5
51 CS
tat
u r类
sBa
CStatusBar 是状态栏类,用于管理应用程序窗口中的状态栏。 CStatusBar 类的继承关
系如图 5 12 所示。
图5 12 CS
tat
us r类的继承关系
Ba
表5 15 CS
tat
us r类的主要成员函数
Ba
成 员 函 数 说 明
int CommandToIndex() 获取给定 ID 的指示器索引值,第一个指示器的索引值为 0
BOOL Create() 创建状态栏
UINT GetItemID() 获取给定索引的指示器的 ID
void GetPaneInfo() 获取给定索引的指示器窗格样式和宽度
void GetPaneStyle() 获给定索引的指示器的样式
CString GetPaneText() 获给定索引的指示器的文本
CStatusBarCtrl& GetStatusBarCtrl() 获取 CStatusBarCtrl 类的成员函数的权限
void SetPaneInfo() 设置给定索引的指示器标窗格样式和宽度
void SetPaneStyle() 设置定索引的指示器的样式
BOOL SetPaneText() 设置定索引的指示器的文本
5
52 状态栏的创建
(1)创建一个状态栏的步骤
① 声明状态栏对象(见清单 5 17);
② 定义状态栏指示器字符串 ID 数组(见清单 5 18);
③ 创建状态栏(见清单 5 18)。
清单5 17 声明状态栏对象(在 Ma
inF h文件中)
rm.
c
lassCMa inFramepub li
cCF rameWnd
...
protected:
CStatusBar m_wndstatusBar;//声明状态栏对象
...}
121
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
...
//定义状态栏指示器字符串 ID 数组
stat
i cUINTi nd
icator
s=//指示器组
{
ID_SEPARATOR, //状态栏第一窗格,提示信息输出栏
ID_INDICATORCAPS, //状态栏第二窗格,显示 CapS Lock 键状态
ID_INDICATORNUM, //状态栏第三窗格,显示 Num Lock 键状态
IDINDICATORSCRL, //状态栏第四窗格,显示 Scroll Lock 键状态
};
intCMa inF rame OnCreate(LPCREATESTRUCTl pCr e
atest
ruct)
{...
//创建状态栏
if(! m _wndStatusBar.Create(this)||! m wndStatusBar.SetIndicators(indicators,
sizeof(Indicators)/sizeof(U INT)))
{ TRACEO("Failed to create status bar\n " );
return -l;//fail create
(2)把一个窗格添加到状态栏中的步骤
① 为窗格创建一个 ID 命令;
② 创建窗格的缺省字符串;
③ 添加窗格的命令 ID 到状态栏的指示器数组;
④ 为窗格创建一个处理程序。
5
53 编程实例
【例5 8】 Ex05_8 在状态栏显示鼠标的位置和时间,具体说明见表 5 16。
表5 16 例5 8具体说明
项 目 图示和说明
程序界面
(状态栏)
程序功能 在状态栏中动态显示鼠标的位置和系统时间
(1)新建一单文档应用程序 Ex05_8
(2)创建新的命令 ID 和缺省字符串
鼠标的命令 ID 为:ID_INDICATOR_MOUSE,缺省字符串为:鼠标位置。
时间的命令 ID 为:ID_INDICATOR_CLOCK,缺省字符串为:00:00:00。
创建和设置方法有两种:
一种是 View- > Resource Symbol,添加 ID;然后到 String table 中添加字符串。
另一种是 ID 号和字符串都在 String Table 中添加。 当用后一种方式操作时,若完成
后,时钟栏并不显示时间,则需要将此 New String 在 String Table 中对应的 Value 值加 1
122 (可在 Resource.h 中修改)。
第五章 程序界面设计
……………………………………………………………………………………………………………………………………………
...
//添加 ID 到指示器数组
stat
i cUINTi ndic
ators=
ID_SEPARATOR, //status line indicator
ID_INDICATOR_MOUSE, //鼠标位置状态栏
ID_INDICATOR_CLOCK, //时钟位置状态栏
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
;
...
intCMa inFrame OnCre a
te(LPCREATESTRUCTl pCr e a
teS
tru
ct)
{...
//载入状态栏到程序界面
if (!m_wndStatusBar.Create(this)||!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)
/sizeof(UINT)))
{TRACE0("Failed to create status bar\n" );
return-1; //fail to create
//设置显示鼠标和时间状态栏窗格的外观
int index = m_wndStatusBar.CommandToIndex(ID_INDICATOR_MOUSE);//获取鼠标指示器 ID 的索引
//设定鼠标指示器窗格的外观,SBPS_POPOUT 为外凸型
m_wndStatusBar.SetPaneInfo(index,ID_INDICATOR_MOUSE,SBPS_POPOUT,80);
int index1 = m_wndStatusBar.CommandToIndex(ID_INDICATOR_CLOCK);//获取时间指示器 ID 的索引
//设定时间指示器窗格的外观,SBPS_STRETCH 为下凹型
m_wndStatusBar.SetPaneInfo(index1,ID_INDICATOR_CLOCK,SBPS_STRETCH,80);
SetTimer(1,1000,NULL);//安装定时器,并将其时间间隔设为 1000 毫秒
...
}
v
oidCMa inF r
ame OnT imer(UINTn IDEvent)//OnTimer 函数,用 ClassWizard 添加
{ CTime time;
time = CTime::GetCurrentTime();//得到当前时间
Cstring s = time.Format("%H:%M:%S" );//转换时间格式
int index1 = m_wndStatusBar.CommandToIndex(ID_INDICATOR_CLOCK);
m_wndStatusBar.SetPaneText(index1,s);
CFrameWnd::OnTimer(nIDEvent);
}
v
oidCMa inF r
ame OnC lo
se()//OnClose 函数,用 ClassWizard 添加
{ KillTimer(1);
CFrameWnd::OnClose();
}
123
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
v
oidCEx05_8V i
ew OnMo useMo ve(UINTnFlagsCPo intp oint)
{ CStatusBar* pStatus = (CStatusBar* )
AfxGetApp()- >m_pMainWnd- >GetDescendantWindow(ID_VIEW_STATUS_BAR);
if(pStatus)
{ char mouseposition40;
sprintf(mouseposition," (
%d,%d) ",point.x,point.y);//将鼠标位置赋值给 mouseposition
pStatus- >SetPaneText(1,mouseposition,TRUE);//在状态条的第二个窗格中输出当前鼠标
//的位置
CView::OnMouseMove(nFlags, point);
}
}
124
第六章 对话框与控件
1 对话框基本知识
6
6
11 对话框的组成
对话框主要由两部分组成:
(1)一个指定了对话框中各控件及其相对位置的对话框模板资源;
(2)一个对话框类,通常都是从 CDialog 类派生。
6
12 对话框的类型
(1)按调用分类
Windows 应用程序中的对话框分为模式对话框和无模式对话框两类 。
模式对话框(Modal Dialog)就是在未关闭该对话框前,不能选择应用程序的其他功能。
例如在“Word”字处理软件中选择[格式][字体]菜单命令,在退出字体对话框前,无法继续进
行文本编辑。
无模式对话框(Modeless Dialog)则在关闭该对话框前,用户可以选择应用程序的其他
功能。例如“Word”字处理软件中的“Find”对话框。
(2)按创建分类
① 消息对话框 用 MessageBox()或 AfxMessageBox()显示消息对话框
② 通用对话框 字体、颜色、文件、查找等通用对话框
③ 一般对话框
125
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
6
13 编写对话框程序的流程
编写对话框程序的一般流程如下:
(1)创建对话框资源,并添加各种所需的控件;
(2)创建与对话框资源相关联的 CDialog 的派生类;
(3)对话框的显示;
(4)创建与控件相关的数据成员变量;
(5)创建对话框中相关控件的消息处理函数 。
2 消息对话框
6
6
21 消息对话框函数
最简单 的 对 话 框 是 显 示 消 息 的 消 息 框, 只 需 用 MFC 类 库 中 的 函 数 MessageBox 或
AfxMessageBox 来创建、显示和操作对话框。AfxMessageBox 的原型为:
表6 1 消息框中所用的图标
图 标 含 义 nType 可取的值
IDC_STATIC 警 告 MB_ICONEXCLAMATION
IDC_STATIC 信 息 MB_ICONINFORMATION
IDC_STATIC 问 题 MB_ICONQUESTION
IDC_STATIC 错 误 MB_ICONSTOP
表6 2 消息框中按钮的排列
表6 3 消息框的返回值
返 回 值 选择的按钮 返 回 值 选择的按钮
IDABORT 终止 IDOK 确定
IDCANCEL 取消 IDRETRY 重试
IDIGNORE 忽略 IDYES 是
IDNO 否
126
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
6
22 消息对话框编程实例
【例6 1】 Ex06_1 消息对话框,具体说明见表 6 4。
(1)新建一单文档应用程序 Ex06_1
(2)编辑菜单资源
(3)添加菜单消息函数(见表 6 5)
表6 4 例6 1具体说明
项 目 图示和说明
图(
a) 菜单 b) 警告消息对话框
( c) 信息消息对话框
(
程序界面
图(
d) 问题消息对话框 e) 错误消息对话框
( 图(
f) 缺省消息对话框
程序功能 点击消息对话框菜单下的各菜单项分别弹出以上消息对话框
表6 5 菜单消息函数
菜 单 ID 号 消 息 消 息 函 数
警告... ID_MSG_EXCLADLG COMMAND OnMsgExcladlg()
信息... ID_MSG_INFODLG COMMAND OnMsgInfodlg()
问题... ID_MSG_QUEDLG COMMAND OnMsgQuedlg()
错误... ID_MSG_STOPDLG COMMAND OnMsgStopdlg()
缺省... ID_MSG_DEFDLG COMMAND OnMsgDefDlg()
(4)编写菜单消息函数(见清单 6 1)
06_
清单6 1 菜单消息函数(在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx06_1V i
ew OnMs
gExcladg()
l
{ AfxMessageBox("警告", MB_YESNO|MB_ICONEXCLAMATION);
}
v
oidCEx06_1V i
ew OnMs
gInfodg()
l
{ AfxMessageBox("信息", MB_OKCANCEL|MB_ICONINFORMATION);
}
v
oidCEx06_1V i
ew OnMs
gQue d
lg()
{ AfxMessageBox("问题", MB_ABORTRETRYIGNORE|MB_ICONQUESTION);
}
v
oidCEx06_1V i
ew OnMs
gStopdg()
l
{ AfxMessageBox("错误!", MB_OK|MB_ICONSTOP);
}
127
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
v
oidCEx06_1V i
ew OnMs
gDefD
lg()
{ AfxMessageBox("缺省");
}
(5)编译和运行
3 对话框资源编辑器
6
对话框资源编辑器用于创建或编辑对话框资源或对话框模板。使用对话框编辑
器 能 够 向 对 话 框 中 添 加 控 件 的 布 局 和 测 试 对 话 框 运 行 ,图 6 1 为对话框资源编辑器
界面。
打开对话框资源编辑器时,同时还应出现图 6 2 所示的 Controls 工具栏和图 6 3 所
示的 Dialog 工具栏。如果 Controls 或 Dialog 工具栏没有出现,可将鼠标移到菜单栏位置,
单击 鼠 标 右 键, 在 出 现 的 快 捷 菜 单 中 选 择 Controls 命 令 或 Dialog 命 令 即 可。 利 用
“Controls”工具栏可以为对话框资源添加控件 。“Dialog”工具栏提供了一些对控件进行对
齐、均布和尺寸分配等功能。
图6 1 对话框资源编辑器窗口
图6 3 D
ial
og工具栏 图6 2 Co
ntr
ols工具栏
4 控件
6
在“Controls”工具栏中可以看到许多种类的控件,这些控件在 Windows 应用程序中得
到广泛的应用。各种控件类的继承关系如图 6 4 所示:
图6 4 控件类继承关系
控件资源的添加有两种方法,一种是使用对话框编辑器向对话框资源中添加控件;另一
128
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
CSliderCtrl 类进行支持。
热键控件 (Hot Key):热键控件看起来就像一个编辑框,但是用热键控件能够立刻反映
用户刚刚按下的键组合,这在设置程序的热键时特别有用 。 热键控件只是在 “视觉 ”上显
示了按键组合,设 置 热 键 的 工 作 还 需 要 用 户 添 加 代 码 完 成 。 MFC 提 供 了 CHotKey 类 进 行
支持 。
列 表 控 件 (List Control):按 一 定 的 排 列 顺 序 显 示 一 系 列 带 图 标 的 字 符 串 。 列 表
控 件 提 供 了 四 种 显 示 模 式 :大 图 标 、小 图 标 、列 表 和 详 细 信 息 。 用 户 可 以 向 列 表 控 件
中 添 加 新 的 项 , 也 可 以 选 择 列 表 控 件 的 显 示 模 式 。 MFC 提 供 了 CListCtrl 类 进 行
支持。
树形控件(Tree Control):用来显示一系列项目的层次关系,最典型的例子就是显示磁
盘上的文件和文件夹。如果有子项的话,单击树形控件中的项目可以展开或者收缩其子项 。
MFC 提供了 CTreeCtrl 类进行支持。
属性列表控件(Tab Control):属性列表控件用来包含大量的控件,可以满足用户显示或
获取大量数据的要求。每个属性表又可分为好几个属性页,这些属性页根据各自的标签进
行区分,这些属性页中都可以包容其他控件 。在显示属性列表的时候,一次只能够显示一个
属性页的全部内容,同时显示属性页的标签,用户通过单击标签打开相应的属性页。 MFC 提
供了 CTabCtrl 类进行支持。
动画控件(Animation):用来播放一段 AVI 格式的视频剪辑。用户可以控制视频剪辑的
播放、停止和定位,但仅限于这些功能。 动画控件甚至不能播放音频剪辑,如果用户需要更
高层次的视频或音频支持,请选用 MCIWnd 控件。MFC 提供了 CAnimateCtrl 类对动画控件进
行支持。
高级编辑框(Rich Edit):很显然,高级编辑框是编辑框控件功能的扩展 。在高级编辑框
中,除了简单地输入和编辑字符串外,用户还可以为字符或段落指定特定的格式,用户甚至
还可以向高级编辑框中插入 OLE 项。高级编辑框基本上实现了一个带格式的文本编辑器的
功能,而只需用户添加少量的接口。MFC 提供了 CRichEditCtrl 类进行支持。
日历控件 (Month Calender):日历控件看起来与一个真正的日历很相似,操作起来也是
类 似 的。 这 样 就 很 直 观 地 为 用 户 提 供 了 观 察 和 显 示 日 期 的 途 径 。 MFC 提 供 了
CMonthCalCtrl 类进行支持。
日期/时间选择器(Date Time Picker):日期/时间选择器向用户提供了一种直观的选择
日期和时间的方法。日期/时间选择器在外观上类似于一个组合框,但是当用户单击下拉箭
头时就会展开一个日历控件供用户进行选择,而一旦用户做出了选择,日期/时间选择器就
会自动显示新的日期/时间。MFC 提供了 CDateTimeCtrl 类进行支持。
IP 地址控件 (IP Address):IP 地址控 件 用 来 输 入 和 编 辑 IP 地 址 。 该 控 件 外 观 类 似
于一个编辑框,但是可以自动对输入的 字 符 按 三 个 一 组 进 行 分 区 和 加 间 隔 圆 点 。 IP 地
址控件为开发支持 Internet 技术的程 序 提 供 了 方 便 。 MFC 提 供 了 CIPAddressCtrl 类 进
行支持 。
扩展组合框(Extended Combo Box):扩展组合框在普通组合框的基础上还支持图像列
表。也就是说,可以在组合框中显示特定的图标表示相应的选择,而不仅仅是显示文本。
MFC 提供了 CComboBoxEx 类进行支持。
130 有关控件的应用将在程序实例中加以介绍 。
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
5 对话框类与对话框调用
6
6
51 对话框类
CDialog 类是所有对话框类的基类。 在 VC60 中,可利用对话框编辑器创建对话框资
源,并将其保存到资源中,然后利用 ClassWizard 创建 CDialog 派生类。
CDialog 类的继承关系见图 6 5。
图6 5 CD
ial
og类继承关系
CDialog 类的主要成员函数见表 6 6。
表6 6 CD
ial
og类的主要成员函数
成员函数 返回值 说 明
CDialog 构造 CDialog 对象
初始化 CDialog 对 象;创 建 非 模 式 对 话 框 并 将 其 连 接 到 CDialog
Create BOOL
对象
CreateIndirect BOOL 从内存中(非基于资源)的对话框模板中创建非模式对话框
DoModal int 调用(显示)模式对话框,完成后返回一整型数值
EndDialog void 关闭模式对话框
GotoDlgCtrl void 将焦点移至对话框中指定的对话框控件
GetDefID DWORD 获取对话框缺省按钮控件的 ID
从内存中(非基于资源)的对话框模板中创建模式对话框。保存参
InitModalIndirect BOOL
数直到调用 DoModal 函数
判断给定消息是否是非模式对话框所期望的消息,如果是,则处理
IsDialogMessage BOOL
此消息
MapzDialogRect void 将矩形的对话框单位转换为屏幕单位
NextDlgCtr void 将焦点移至对话框中下一对话框控件
重载该函数以实现“Cancel”按钮或 Esc 键的特定动作。缺省实现为
OnCancel void
关闭对话框,并且 DoModal 返回 IDCANCEL
OnInitDialog BOOL 重载该函数以进一步初始化对话框
重载该函数以实现“OK”按钮的特定动作。缺省实现为关闭对话框,
OnOk void
并且 DoModal 返回 IDOK
OnSetFont void 重载该函数以实现模式对话框控件绘制文本时使用的字体
PrevDlgCtrl void 将焦点移至对话框中上一对话框控件
SetDefID void 将对话框中缺省的按钮控件设置为指定按钮
SetHelpID void 设置对话框的与环境相关的帮助 ID
6
52 对话框调用及其编程实例
对模式对话框,调用 DoModal 函数加载对话框模板,显示对话框并管理与对话框对象的 131
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
交互,直到用户单击“确定”或“取消”按钮。
对非模式对话框,调用 Create 函数加载对话框模板。若对话框模板具有 WS_VISIBLE 风
格。则立即显示对话框,否则须调用 ShowWindow 成员函数显示对话框。
【例6 2】 Ex06_2 对话框模式与无模式调用,具体说明见表 6 7。
表6 7 例6 2具体说明
项 目 图示和说明
程序界面
图(
a) 菜单 图(
b) 模式对话框 图(
c) 模式对话框
点击菜单:对话框调用- >模式对话框...,弹出图(b),对话框关闭前不能进行其他操作
程序功能
点击菜单:对话框调用- >非模式对话框...,弹出图(c),对话框关闭前可以进行其他操作
(1)新建一单文档应用程序 Ex06_2
(2)添加模式对话框和非模式对话框资源
① 插入对话框
在 Workspace 窗口的 ResourceView 的资源列表中,鼠标放在 Dialog 文件夹,右击鼠标,在
快捷菜单中选择“ Insert Dialog”选项,此时在资源列表中新建名为“IDD_DIALOGl”的对话框资
源,对话框资源编辑器随之启动,将新对话框“ 调入其中,如图 6 6 所示。
IDD_DIALOGl”
图6 6 新建对话框资源
② 设置对话框属性
在新对话框的空白处 (注意不要在控件上 )单击鼠标右键,在弹出的快捷菜单中选择
“Properties ”命 令, 打 开 “Dialog Properties ”对 话 框, 如 图 6 7 所 示。 “Dialog
Properties”对话框中共有 5 个标签。本例将 General 标签中的 ID 号设置为 IDD_MODELDLG。
Caption 设置为“模式对话框”,对话框所使用的字体和字号,需要通过单击 “Font”按钮显示
的“Font”对话框来设置。
同样插入另一对话框,对话框 ID 号为:IDD_ MODELESSDLG;Caption 设置为 “非模式对
话框”。
132
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
图6 7 D
ial
ogPr
ope
rti
es对话框
(3)添加控件
在两个对话框中各添加一静态文本控件 。添加的步骤如下 (以 IDD_MODELDLG 对话框为
例说明):
① 鼠标移动到“Controls”工具栏的静态文本控件图标上,按下鼠标左键,将静态文本框
拖动到 IDD_MODELDLG 对话框模板的适当位置;
② 选中该静态文本框按下鼠标右键从弹出的快捷菜单中选择 Properties 选项可打开
图 6 8 所示的“Text Properties”对话框。
图6 8 Te
xtPr
ope
rti
es对话框
图6 9 Add
ingaC
las对话框
s 图6 10 NewC
las对话框
s
菜 单 ID 号 消 息 消息函数
模式对话框... ID_SHOWDLG_MODELDLG COMMAND OnShowdlgModeldlg()
非模式对话框... ID_SHOWDLG_MODELESSDLG COMMAND OnShowdlgModelessdlg()
(6)实现模式调用
① 编写模式调用消息处理函数 OnShowdlgModeldlg(见清单 6 2)。
② 在 Ex06_2View.cpp 文件开头中,添加# include" ModelDlg.h" (见清单 6 2)。
(7)实现非模式调用
① 编写非模式调用消息处理函数 OnShowdlgModelessdlg(见清单 6 2)。
② 在 Ex06_2View.h 文件开头,添加# include" ModelessDlg.h" (见清单 6 3)。
③ 在 Ex06_2View.h 文件中,定义 CModelessDlg 类的指针 pdlg(见清单 6 3)。
④ 在 Ex06_2View.cpp 构造函数中初始化 pdlg(见清单 6 2)。
⑤ 在 Ex06_2View.cpp 析构函数中删除 pdlg(见清单 6 2)。
06_
清单6 2 构造函数(在 Ex 2Vi
ew.
cpp文件中)
…
# include" ModelDlg.h"
CEx 06_2View CEx06_ 2V
i //构造函数
ew()
{ pdlg = NULL;
CEx06_2View~CEx 06_2V ew()
i //析构函数
{ delete pdlg;//删除 pdlg,释放内存
}
vo
idCEx 06_2View OnShowd l
gMo dedg()
l //点击“模式对话框...”菜单消息函数
{ CModelDlg modeldlg;//定义 CModelDlg 对话框类对象
modeldlg.DoModal();//显示模式对话框
}
vo
idCEx 06_2ViewOnSh owdlgMo d
eles
s d
l //点击“非模式对话框...
g() ”菜单消息函数
{ pdlg = new CModelessDlg(this);
pdlg- >Create(IDD_MODELESSDLG);
pdlg- >ShowWindow(SW_RESTORE);
}
06_
清单6 3 包含头文件和定义变量(在 Ex 2Vi h文件中)
ew.
…
# include" ModelessDlg.h"
c
lassCEx 06_ 2Viewpub l
icCV i
ew
private:
CModelessDlg * pdlg;
134
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
(8)编译和运行
6 对话框数据交换与验证机制
6
程序对话框数据交换(DDX, Dialog Data Exchange)用于初始化对话框中的控件并获取
用户的数据输入,而对话框数据验证(DDV, Dialog Data Validation)则用于验证对话框中数
据输入的有效性。MFC 在每个对话框类中提供了一个用于重载的虚函数 DoDataExchange 来
实现对话框数据交换和验证工作 。
6
61 对话框数据交换
如果使用 DDX 机制,则通常在 OnInitDialog 程序或对话框构造函数中设置对话框对象成员变
量的初始值。在对话框即将显示前,应用程序框架的 DDX 机制将成员变量的值传输给对话框中的
控件,当对话框响应 DoModal 或 Create 而被显示时,对话框控件将“ 显示”这些值。CDialog 类中的
OnInitDialog 函数默认时将调用 CWnd 类的 UpdateData 成员函数初始化对话框中的控件。
UpdateData 函数的原型如下:
BOOL UpdateData(BOOL bSaveAndValidate = TRUE);
参数 bSaveAndValidate 是一个标志位,如果 bSaveAndValidate 为 TRUE,表示将对话框
控件中界面数据传给程序代码的成员变量;如果 bSaveAndValidate 为 FALSE,表示将类中成
员变量数据状态传递给对话框及其控件界面 。
6
62 对话框数据验证
除了调用 DDX 参数指定数据交换外,用户还可以使用 DDV 函数进行对话框数据验证。在调
用控件的 DDX 函数后,必须立即调用该控件的 DDV 函数。大部分 DDV 函数的原型如下所示:
DDV_MinMaxCustom(pDX,Data,MinData,MaxData);
其中,参数 pDX 是一个指向 CDataExchange 对象的指针,参数 Data 中存放着即将被验证
的数据,后两个参数用于定制数据的范围 。
6
63 四则运算编程实例
【例6 3】 Ex06 3 四则运算的实现,具体说明见表 6 9。
表6 9 例6 3具体说明
项 目 图示和说明
图(
a) 菜单
程序包含的控件有:
程序界面
3 个编辑控件、6 个下压按钮、
3 个静态文本控件以及 2 个成组控件。
图(
b) 四则运算对话框
135
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
续 表
项 目 图示和说明
(1)点击菜单:对话框- >四则运算...,弹出四则运算对话框
程序功能
(2)在四则运算对话框的 num1、num2 编辑框中输入数据,然后点击+、-、*、
/按钮即进行相应的
计算,并将计算结果输出到 result 编辑框
(1)新建一单文档应用程序 Ex06_3
(2)添加对话框资源
插入对话框,打开 “
Dialog Properties”
对话框,将 General 标签中的 ID 号设置为
IDD_CALDLG。 Caption 设置为 “四 则 运 算
对话框”。
(3)添加控件 图6 11 Ed
itPr
ope
rti
es对话框
① 添加编辑控件;
添加 3 个编辑控件,分别用于 num1、num2 及 result 的文本说明。 打开图 6 11 所示的
“Edit Properties”对话框,对话框中的有关属性见表 6 10。
表6 10 编辑控件的属性
属性项 说 明
ID 指定控件的标识符
Visible 指定控件最初可见
Disable 指定控件最初被禁用
Group 指定基于 Tab 键顺序的一组控件中的第一个控件
TabStop 指定用户可以使用 TAB 键移动到该控件
ID Help 为控件分配基于资源 ID 的帮助 ID
Align Text 对齐文本,可以为左、居中、右
Multiline 如果控件文本对于控件的宽度而言太长,则将文本折为多行
Number 编辑框中只能输入数字
Horizontal Scroll 指定控件将具有水平滚动条
Auto HScroll 用户在行尾键入字符时,自动使文本滚动到左侧
Vertical Scroll 指定控件将具有垂直滚动条
Auto VScroll 当用户在最后一行按 Enter 键时,自动使文本向上滚动
Password 对输入到编辑控件中的每个字符显示星号
No Hide Selection 设定编辑控件将总是显示某选定内容,即使它没有焦点
OEM Convert 将输入到编辑框中的文本转换为 OEM 字符集
Want Enter 指定编辑控件要接收 Enter 键
Border 指定控件将具有窄边框
UpperCase 在编辑控件中输入字符时,将所有字符转换为大写
LowerCase 在编辑控件中输入字符时,将所有字符转换为小写
Read Only 防止用户在编辑框控件中输入或编辑文本
② 添加下压按钮控件;
136 添加 4 个下压按钮,分别用于+、-、*、
/的计算。 打开 “Push Button Properties”对话框,
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
图6 12 Pu
shBu
tto
nPr
ope
rti
es对话框
表6 11 下压按钮控件的属性
属性项 说 明
Default button 指定控件是对话框的默认命令按钮
Owner Draw 指定控件为所有者描述的按钮
Horizontal alignment 指定控件的水平对齐方式:默认、右、左、居中
Vertical alignment 指定控件的垂直对齐方式:默认、底、顶、中间
③ 添加静态文本控件;
添加 3 个静态文本控件,静态文本控件与前面控件不同的属性见表 6 12。
表6 12 静态文本控件的属性
属性项 说 明
No Prefix 防止“and”符()被解释为键盘助记键
No Wrap 指定文本不换行
Simple 指定控件显示单行左对齐文本
Sunken 指定控件具有半凹陷边框
④ 添加成组框控件。
添加 2 个成组框控件, 打开 “Group Box Properties”对话框,如图 6 13 所示。 无需改
变 General 标签中的 ID 号。
图6 13 Gr
oupBo
xPr
ope
rti
es对话框
(4)创建对话框类
在对话框编辑器中,双击“四则运算对话框 ”对话框,添加新类;在 “New Class”对话框的
类名 Name 编辑框中输入 CCalDlg。单击“OK"按钮。即创建了“CCalDlg”类。
(5)添加成员变量
添加成员变量步骤如下:
137
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
图6 14 利用 MFCC
las
sWi
zad添加成员变量
r 图6 15 AddMemb
erVa
ria
ble对话框
框数据交换和验证的代码,见清单 6 4。
清单6 4 对话框数据交换和验证(在 Ca
lDl
g.pp文件中)
c
CCa
lD l
g CCa lDlg(CWnd pPar
ent/=NULL/):CD
ial
og(
CCa
lDl
g
IDDpPa
ret)
n //构造函数
{
//{{AFX_DATA_INIT(CCalDlg)
m_num1 = 00;
m_num2 = 00;
m_result = 00;
//AFX_DATA_INIT
v
oidCCalDlgDoDataEx c
ha n
ge(CDa taExchang //对话框数据交换
e pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CCalDlg)
DDX_Text(pDX, IDC_EDIT_NUM1, m_num1);
DDV_MinMaxDouble(pDX, m_num1,-01, 100.);
DDX_Text(pDX, IDC_EDIT_NUM2, m_num2);
DDX_Text(pDX, IDC_EDIT_RESULT, m_result);
//}}AFX_DATA_MAP
(6)添加单击下压按钮的消息函数
本例需要添加 4 个单击按钮(+、-、*、
/)的消息函数。
使用如图 6 16 所 示 的 ClassWizard 的 Message Maps 标 签 添 加 消 息 函 数, 首 先 在
Object IDs 列表框中选择要添加消息处理的控件 (如 ID_BUTTON_ADD),然后在 Messages 列
表框中选择要添加的消息(如 BN_CLICKED 即单击按钮),再单击 Add Function 按钮弹出图中
Add Member Function 的对话框,消息函数名为 OnButtonAdd,点击“OK”按钮,即完成消息函数
OnButtonAdd ()的添加工作。按此方法添另 3 个消息函数,各消息函数见表 6 15。
图6 16 添加消息函数
139
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
表6 15 “四则运算对话框”消息函数
标题(Caption) ID 号 消 息 消息函数
+ IDC_BUTTON_ADD BN_CLICKED OnButtonAdd
- IDC_BUTTON_MINUS BN_CLICKED OnButtonMinus
* IDC_BUTTON_MULTIPLY BN_CLICKED OnButtonMultiply
/ IDC_BUTTON_DIVIDE BN_CLICKED OnButtonDivide
(7)编写消息函数
按上述方法添加完消息函数后,还要编写具体的函数代码。清单 6 5 列出了两个消息
函数的代码清单。
清单6 5 消息函数(在 Ca
lDl
g.pp文件中)
c
v
oidCCalDlgOnBu t
tonAdd()
{ UpdateData(true);//将界面数据传给成员变量
m_result = m_num1+ m_num2;
UpdateData(false);//将成员变量数据传给界面
}
v
oidCCalDlgOnBu t
tonMinus()
{ UpdateData(true);
m_result = m_num1- m_num2;
UpdateData(false);
}
v
oidCCalDlgOnBu t
tonMu l
til
py()
{ UpdateData(true);
m_result = m_num1* m_num2;
UpdateData(false);
}
v
oidCCalDlgOnBu t
tonDivi
de()
{ UpdateData(true);
if(m_num2 = = 0)
{ AfxMessageBox("除数不能为零,请重新输入 m_num2!" );
return;
m_result = m_num1/m_num2;
UpdateData(false);
}
(8)对话框调用(模式对话框)
对话框调用步骤如下:
① 定义菜单选项
在菜单编辑器中,创建“对话框”菜单,在“对话框”菜单中添加“四则运算...”菜单选项。
② 添加和编写菜单命令消息函数。
使用 ClassWizard 在“CEx06_3View”中添加菜单对象 “ID_DIALOG_CAL”的单击命令消息
控制函数 OnDialogCaldg(),见清单 6 6。
l
③ 添加 CalDlg.h 的说明。
在 Ex06_3View.cpp 文件中涉及 CCalDlg 类,因此需要将其头文件 CalDlg.h 的说明添加
140 到文件中。
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
06_
清单6 6 菜单调用对话框(在 Ex 3Vi
ew.
cpp文件中)
#inc
lude" Ex06_3View.h"
#inc
lude" CalDlg.h"//包含对话框头文件
…
v
oidCEx 06_3V i
ew OnDi a
logCa
ldg()
l
{ CCalDlg dlg;
dlg.DoModal();//模式调用四则运算对话框
}
(9)编译和运行
7 常用控件应用
6
6
71 常用控件编程实例一
【例6 4】 Ex06_4 常用控件应用一,具体说明见表 6 16。
(1)新建一单文档应用程序 Ex06_4
(2)添加对话框资源
插入对话框,打开“Dialog Properties”对话框,将 General 标签中的 ID 号设置为 IDD_
STUMSGDLG。Caption 设置为“学生信息”。
(3)添加控件
① 添加 5 个静态文本控件。
② 添加 3 个成组框控件。
③ 添加 3 个编辑控件,分别用于输入姓名、学号和电话。
④ 添加 1 个微调 CSpin 控件用于调整学号。
表6 16 例6 4具体说明
项目 图示和说明
对话框包括的控件有:
3 个编辑控件、
5 个静态文本控件、
1 个组合框控件、
程序 1 个列表框、
界面 2 个单选按钮、
2 个复选按钮、
2 个下压按钮
以及 3 个成组控件。
(1)点击菜单:登记- >学生信息...弹出学生信息对话框
程序
(2)在学生信息对话框中按图中信息进行输入和选择,然后点击“OK”按钮,程序将关闭对话框,并
功能
将对话框信息传递到视图中显示
141
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
图6 17 微调控件的属性选择
表6 17 微调控件的属性
属性项 说 明
Alignment 设置控件与其伙伴窗口的位置关系
Arrow 通过按箭头键增加或减少微调控件的位置
No Thousands 指定不在每三位十进制之间插入千位分隔符
Wrap 如果增加或减小位置时超出该范围,则使位置回绕
Auto Buddy 自动按照顺序选择上一个控件作为微调控件的伙伴窗口
Hot Track 当光标移过控件时,突出显示控件的箭头按钮
Set Buddy Integer 指定微调控件在它的位置改变时设置伙伴窗口的文本
⑤ 添加 1 个列表控件用于输入班级。
列表控件的属性选择见图 6 18,属性含义说明见表 6 18。
图6 18 列表控件的属性选择
表6 18 列表框的属性
属性项 说 明
Disable No Scroll 控件中一直显示滚动条
No Integral Height 指定组合框的大小正好是由应用程序指定的大小
No Redraw 指定在进行更改后不更新列表框的外观
Vertical Scrollbar 指定列表框将具有垂直滚动条
Has Strings 指定用户描述的组合框中包含由字符串组成的项
MultiColumn 指定水平滚动的多列列表框
No Data 指定所描述的列表框不包含任何数据
Selection 可以为单项、多项、扩展、无之中的一种
Sort 对添加到列表框的字符串进行自动排序
指定如果 在 列 表 框 具 有 焦 点 时 按 下 某 个 键, 列 表 框 的 所 有 者 将 接 到
Want Key Input
WM_VKEYTOITEM消息
⑥ 添加 1 个组合框控件用于输入学院。组合框控件的属性选择见图 6 19。
142
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
图6 20 单选按钮的属性选择
图6 19 组合框控件的属性选择 图6 21 复选按钮的属性选择
⑧ 添加 2 个 复 选 按 钮, 以 用 作 已 学 先 修 课 程 说 明 。 复 选 按 钮 的 属 性 选 择 见 图
6 21。
按钮可分为三类:下压按钮、单选按钮和复选按钮,这几个按钮的特性基本一致,见下压
按钮部分。
(4)创建对话框类
类的创建过程同前,在新类 “New Class”对话框中:输入类名为:CStumsgDlg;选择基类
为:CDialog;Dialog ID 为:IDD_STUMSGDLG。
(5)添加成员变量
用“MFC ClassWizard”添加成员变量, CStumsgDlg 类的成员变量见表 6 19。
表6 19 各控件的ID 号和成员变量
续 表
控 件 标题(Caption) ID 号 成员变量 变量类型
成组框 性别 IDC_STATIC
成组框 已学先修课 IDC_STATIC
(姓名)编辑框 IDC_NAME m_name CString
学号编辑框 IDC_NUMBER m_number CString
电话编辑框 IDC_TEL m_tel CString
微调控件 IDC_SPIN_NUMBER m_ctrlspin CSpinButtonCtrl
m_college CString
组合框 IDC_COLLEGE
m_ctrlcollege CComboBox(控制)
m_banji CString
列表框 IDC_BANJI
m_ctrlbanji CListCtrl(控制)
m_men int
单选按钮 男 IDC_MEN
m_ctrlmen CButton
单选按钮 女 IDC_WOMEN
复选按钮 C 语言程序设计 ID_CHECK_CPROGRAM m_bcprogram BOOL
复选按钮 软件基础 ID_CHECK_SOFTWARE m_bsoftware BOOL
下压按钮 OK IDOK
下压按钮 Cancel IDCANCEL
(6)添加消息函数
添加的改变组合框选项的消息函数 OnSelchangeCollege()(按图 6 22 所示选择添加)
和初始化对话框的函数 OnInitDialog()(按图 6 23 所示选择添加)。
图6 22 添加 OnS
elc
han
geCo
lle
ge()函数
(7)编写对话框类成员函数
① 重载初始化对话框函数(见清单 6 7)
144
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
图6 23 添加 On
InD
ial
og()函数
② 改变组合框选项的消息处理函数(见清单 6 7)
清单6 7 On
Ini
tDi
alg和 OnS
o elc
han
geCo
lle
ge函数(在 S
tums
gDl
g.pp文件中)
c
BOOLCS t
ums gDlgOn In
itDialg()
o //初始化对话框
{
CDialog::OnInitDialog();
m_ctrlcollege.SetCurSel(0);//将组合框的第一项,即机械工程学院显示在界面上
m_ctrlspin.SetRange(00001,10000);//设置微调控件的调节范围
//添加机械工程学院的班级到列表框
m_ctrlbanji.AddString("机设 021");
m_ctrlbanji.AddString("机设 022");
m_ctrlbanji.AddString("机设 023");
m_ctrlbanji.AddString("机设 024");
m_ctrlmen.SetCheck(1);//初始化性别为男
return TRUE;//return TRUE unless you set the focus to a control
//EXCEPTION: OCX Property Pages should return FALSE
v
oidCStumsgD l
gOnSelchangeCol
lege()//改变组合框选项
{
switch(m_ctrlcollege.GetCurSel())
{
case 0://机械工程学院
{ m_ctrlbanji.ResetContent();
m_ctrlbanji.AddString("机设 021");
m_ctrlbanji.AddString("机设 022");
m_ctrlbanji.AddString("机设 023");
m_ctrlbanji.AddString("机设 024");
break;
145
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
case 1://信息学院
{ m_ctrlbanji.ResetContent();
m_ctrlbanji.AddString("信息 011");
m_ctrlbanji.AddString("信息 012");
m_ctrlbanji.AddString("信息 013");
m_ctrlbanji.AddString("信息 014");
break;
case 2://化工学院
{ m_ctrlbanji.ResetContent();
m_ctrlbanji.AddString("化工 011");
m_ctrlbanji.AddString("化工 012");
m_ctrlbanji.AddString("化工 013");
m_ctrlbanji.AddString("化工 014");
break;
(8)调用学生信息对话框,并将学生信息对话框的数据在界面上显示
① 编辑菜单资源。
学生信息菜单 ID 号:ID_RECODE_STUMSG
② 在 Ex06_4Doc.h 头文件中定义成员变量(见清单 6 8)。
06_
清单6 8 定义成员变量(在 Ex 4Do
c.h头文件中)
c
las
sCEx 06_4Docpub licCDo
cume
nt
public:
BOOL m_bsoftware;
BOOL m_bCProgram;
int m_men;
CString m_banji;
CString m_college;
CString m_tel;
CString m_number;
CString m_name;
③ 添加和编写菜单消息处理函数,见清单 6 9。
④ 在 Ex06_4Doc.cpp 文件的开头添加 # include" StumsgDlg.h" (见清单6 9)。
清单6 9 OnRe
cod
eSt 06_
u 函数(在 Ex 4Do
c.pp文件中)
c
#inc
lude“StumsgD l
g.h”
…
v
oidCEx 06_4Do cOnRe codeStu()
{ CStumsgDlg dlg;
//将学生信息对话框的数据传给文档
if(dlg.DoModal()= = IDOK)
146
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
{ m_name = dlg.m_name;
m_number = dlg.m_number;
m_tel = dlg.m_tel;
m_college = dlg.m_college;
m_banji = dlg.m_banji;
m_bCProgram = dlg.m_bCProgram;
m_bsoftware = dlg.m_bsoftware;
m_men = dlg.m_men;
UpdateAllViews(NULL);
}
v
oidCEx 06_ 4ViewOnDr aw( CDCpDC)
{ CEx06_4Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
//TODO: add draw code for native data here
pDC- >TextOut(20,20,pDoc- >m_name);
pDC- >TextOut(80,20,pDoc- >m_number);
pDC- >TextOut(200,20,pDoc- >m_tel);
pDC- >TextOut(280,20,pDoc- >m_college);
pDC- >TextOut(400,20,pDoc- >m_banji);
switch(pDoc- >m_men)
{
case 0:
pDC- >TextOut(150,20+i," 男");
break;
case 1:
pDC- >TextOut(150,20+i," 女");
break;
if(pDoc- >m_bCProgram)
{ pDC- >TextOut(500,20+i," C 语言程序设计");
}
if(pDoc- >m_bsoftware)
{ pDC- >TextOut(650,20+i," 软件基础");
}
}
(9)编译和运行
6
72 常用控件编程实例二
【例6 5】 Ex06_5 控件的应用二,具体说明见表 6 20。
147
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
表6 20 例6 5的具体说明
项 目 图示和说明
对话框包括的控件有:
3 个静态文本控件、
程序界面 1 个组合框控件、
5 个单选按钮、
1 个进度条控件、
1 个滑块控件和
1 个滚动条控件。
(1)点击开始进度条按钮,进度条从 0 开始向前移动;点击停止进度条按钮,进度条停止向
前移动;点击继续进度条按钮,进度条从停止的位置开始继续向前移动;用文字显示
程序功能 进度
(2)拖动滑块条,用文字显示滑块位置
(3)拖动滚动条,用文字显示滚动位置
(1)建立一基于对话框的应用程序:Ex06_5
程序自动创建一 ID 号为 IDD_EX06_5_DIALOG 的对话框模板
(2)设置对话框属性
打开 IDD_Ex06_5_DIALOG 的对话框模板的“Dialog Properties”对话框,将 General 标签
的 Caption 设置为“Ex06_5(控件)”。
(3)添加控件
① 添加 1 个进度条控件,进度条控件的属性选择见图 6 24。
② 添加 1 个滑块条控件,滑块条控件的属性选择见图 6 25,属性含义见表 6 21。
图6 24 进度条控件属性 图6 25 滑块条控件属性
表6 21 滑块条的属性
属性项 说 明
Auto Ticks 指定滑块控件在其值范围内对于每个增量都有一个刻度
Enable Selection 指定滑块控件只显示选择范围
Orientation 指定滑块控件的方向:水平或垂直
Point 指定滑块箭头的指向
Tick Marks 指定滑块显示刻度线
Tooltips 滑块支持工具提示
③ 添加 1 个滚动条控件。
148 滚动条控件的属性选择见图 6 26。
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
④ 添加 3 个静态文本控件。
⑤ 添加 1 个成组框。
⑥ 添加 3 个下压按钮控制进度条移动、
停止和继续移动。
(4)定义成员变量(用 ClassWizard)
各控件 ID 号与成员变量见表 6 22。 图6 26 滚动条控件属性
表6 22 控件ID 号与成员变量
控 件 标 题 ID 号 成员变量 变量类型
静态文本 进度条 IDC_STATIC
静态文本 滑块条 IDC_STATIC
静态文本 滚动条 IDC_STATIC
成组框 控件 IDC_STATIC
进度条 IDC_PROGRESS1 m_ctrlProgress CProgressCtrl
m_ns int
滑块条 IDC_SLIDER1
m_ctrlSlider CSliderCtrl
滚动条 IDC_SCROLLBAR1 m_ctrlScrollbar CScrollBar
下压按钮 开始进度条 IDC_BUTTON_BEGIN
下压按钮 继续进度条 IDC_BUTTON_GOON
下压按钮 停止进度条 IDC_BUTTON_STOP
(5)重载初始化对话框的函数(见清单 6 11)
清单6 11 On
Ini
tDi
alo 06_
g()函数(在 Ex 5Dl
g.pp文件中)
c
(6)控制进度条
149
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
控 件 标题(Caption) ID 号 消息函数
下压按钮 开始进度条 IDC_BUTTON_BEGIN OnButtonBegin
下压按钮 继续进度条 IDC_BUTTON_GOON OnButtonGoon
下压按钮 停止进度条 IDC_BUTTON_STOP OnButtonStop
② 添加 OnTimer 函数。
OnTimer 函数的添加见图 6 27,OnTimer 函数由 SetTimer 函数确定时间间隔来触发,编
写的 OnTimer 函数见清单 6 12。
图6 27 OnT r函数的添加
ime
清单6 12 OnT
ime 06_
r函数(在 Ex 5Dl
g.pp文件中)
c
v
oidCEx 06_ 5DlgOnTimer( UINTn IDEvet)
n
{ int position = m_ctrlProgress.OffsetPos(1);
char s10;
wsprintf(s,"%d%% " , position);
CClientDC clientDC(this);
clientDC.SetTextColor(RGB(0,0,255));
clientDC.TextOut(200, 50, s);
CDialog::OnTimer(nIDEvent);
}
v
oidCEx 06_ 5DlgOnButtonBegin()
{ SetTimer(1, 100, NULL); //100 毫秒触发一次 OnTimer 函数
m_ctrlProgress.SetPos(0);
}
v
oidCEx 06_ 5DlgOnButtonGo on()
{ SetTimer(1, 100, NULL);
}
v
oidCEx 06_ 5DlgOnButtonStop()
{ KillTimer(1); //停止触发
}
150
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
清单6 13 OnHS
cro
l 06_
l函数(在 Ex 5Dl
g.pp文件中)
c
v
oidCEx06_ 5D l
g OnHS c
rol(
l UINTnSBCo deUINTnPo sCS
cro
ll rpS
Ba cro
ll r)
Ba
{ //滑块条
CSliderCtrl*slider = (CSliderCtrl*)pScrollBar;
if(slider = = &m_ctrlSlider)
{ int position = slider- >GetPos();
char s10;
wsprintf(s," %d%% " , position);
CClientDC clientDC(this);
clientDC.SetTextColor(RGB(0,255,0));
clientDC.TextOut(200, 90, s);
}
//滚动条
int step;
step = 1;
CScrollBar* scrollbar = (CScrollBar* )pScrollBar;
m_ctrlScrollbar.GetScrollPos();
if(scrollbar = = &m_ctrlScrollbar)
{switch(nSBCode)
{ case SB_THUMBPOSITION:
scrollbar- >SetScrollPos(nPos);
break;
case SB_LINEUP:
if((m_ns-step)> 0)
{ m_ns = m_ns- step;
else
m_ns = 0;
scrollbar- >SetScrollPos(m_ns);
break;
case SB_LINEDOWN:
if((m_ns+step)< 100)
{ m_ns = m_ns+step;
else
m_ns = 100;
scrollbar- >SetScrollPos(m_ns);
break;
int position = scrollbar- >GetScrollPos();
char s10;
151
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
wsprintf(s,"%d%% " , position);
CClientDC clientDC(this);
clientDC.SetTextColor(RGB(0,0,255));
clientDC.TextOut(200, 150, s);
}
CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
(8)编译和运行
8 图像列表控件 、列表控件与树形控件的应用
6
6
81 图像列表控件
有些控件依赖于图像列表,如列表控件和树形控件。 下面主要讲述图像列表控件的创
建和初始化。
(1)创建图像列表
用 CImageList::Create()函数创建图像列表,该函数原型为:
BOOL Create(int cx, int cy, UINT nFlags, int nInitial, int nGrow);
参数:cx,cy 表示图像宽度与高度,以像素为单位。
nFlag:图像列表的类型,可以取 ILC_COLOR、ILC_COLOR4、ILC_COLOR8、ILC_COLOR16、ILC_
COLOR24、ILC_COLOR32、ILC_COLORDDB、ILC_MASK,除 ILC_COLOR 只能单独使用外,其余可以组
合使用。
nInitial:图像列表初始化时包含的图像数 。
nGrow:系统需要改变大小时,图像列表允许扩大到的图像数,为 0 表明列表在运行时不
能增加。
创建举例:
(2)初始化图像列表
在图像列表创建完成后,需要向列表中增加图像。 增加图像最简单的方法是把图像包
含在应用程序的资源文件中,作为它的一部分,并从资源文件中加载。 向列表中增加图像可
用 CImageList::Add 函数,该函数的原型为:
① 资源文件为位图的加载;
首先编辑位图(假设为 2 个),资源 ID 号为:IDB_BITMAP1,IDB_BITMAP2,然后添加代码将
152 位图加载到每个图像列表中。
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
CBitmap bitmap;//定义位图类对象
bitmap.LoadBitmap(IDB_BITMAP1);//加载位图 1
pImageList- > Add(&bitmap,(COLORREF)0x000001);//添加位图 1 到图像列表
bitmap.DeleteObject();//释放位图
bitmap.LoadBitmap(IDB_BITMAP2);//加载位图 2
pImageList- > Add(&bitmap,(COLORREF)0x000001);//添加位图 2 到图像列表
bitmap.DeleteObject();//释放位图
② 资源文件为图标的加载。
首先编辑 2 个图标,资源 ID 号为:IDI_ICON1,IDI_ICON2,然后添加代码将图标加载到每
个图像列表中。
6
82 列表控件 CL
ist
Ctr
l
CListCtrl 类封装了列表视控件的功能。例如 Windows 资源管理器中的显示文件名称、
文件长度和最近的修改日期等,应用的就是 CListCtrl 控件。
(1)创建列表控件
用 CListCtrl::Create()函数创建列表控件,该函数原型为:
BOOL Create(DWORD dwStyle, const RECT & rect, CWnd*pParentWnd, UINT nID);
参数 dwStyle:列表控件风格,可取值见表 6 24。
表6 24 dwS
tye取值(列表控件风格)
l
风 格 说 明
LVS_ALIGNLEFT 在大图标和小图标视图中左对齐项目
LVS_ALIGNTOP 在大图标和小图标视图中上对齐项目
LVS_AUTOARRANGE 在大图标和小图标视图中自动排列项目
LVS_EDITLABELS 允许用户编辑项目标签
LVS_ICON 设置控件为大图标视图
LVS_LIST 设置控件为列表视图
LVS_NOCOLUMNHEADER 在报告视图中不显示列标题
LVS_NOITEMDATA 仅保存每个项目的状态
LVS_NOLABELWRAP 不允许项目标签的多行显示
LVS_NOSCROLL 关闭滚动
LVS_NOSORTHEADER 不显示列标题的按钮
LVS_OWNERDRAWFIXED 在报告视图中允许实现所有者自画项目
LVS_REPORT 设置控件为报告视图
153
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
续 表
风 格 说 明
LVS_SHAREIMAGELISTS 在控件不需要图像列表时,不允许控件删除图像列表
LVS_SINGLESEL 不允许选择多个项目
LVS_SMALLICON 设置控件为小图标视图
LVS_SORTASCENDING 按升序排列项目
LVS_SORTDESCENDING 按降序排列项目
rect:说明列表控件的大小和位置。
pParentWnd:指向控件父窗口的指针,由于控件必定有父窗口,因此这个参数一定不能
为 NULL。
NID:定制列表控件的 ID。
创建举例:
CListCtrl m_ctrlList;
m_ctrlList.Create(WS_VISIBLE|WS_CHILD|WS_BORDER|LVS_REPORT|LVS_NOSORTHEADER
|LVS_EDITLABELS,CRect(300,10,700,420),this,IDC_LISTCTRL);
(2)列表控件的列和项目
创建列表视图 对 象 后,需 要 创 建 列 表 视 的 列 和 项 目。要 创 建 列,首 先 必 须 申 明
LV_COLUMN 结 构 ,利 用 该 结 构 可 以 和 系 统 交 换 信 息 。 MFC 定 义 的 LV_ COLUMN 结 构 见 清
单6 14 。
清单6 14 LV_
COLUMN 结构定义
t
yped
e fstruct_LV_ COLUMN
UINT mask; //指示有效域的标志
int fmt; //列对齐方式
int cx; //列宽度
LPSTR pszText; //字符串缓冲区地址
int cchTextMax; //字符串缓冲区的大小
int iSubItem; //该列的子项目索引
}LV_COLUMN
t
yped
e fstruct_LV_ ITEM
UINT mask; //指示有效域的标志
int iItem; //项目索引
int iSubItem; //子项目索引
UINT state; //项目的当前状态
UINT stateMask; //有效项目状态
LPSTR pszText; //字符串缓冲区地址
int cchTextMax; //字符串缓冲区的大小
int iImage; //该项目的图像索引
LPARAM lParam; //附加信息(32 位)
}LV_ITEM
6
83 树形控件 CT
reeC
trl
(1)创建树形控件
用 CTreeCtrl::Create 函数创建树形控件,该函数原型为:
参数:dwStyle:树形控件风格,取值见表 6 25。
表6 25 dwS
tye取值(树形控件风格)
l
风 格 说 明
TVS_DISABLEDRAGDROP 禁止拖动操作
TVS_EDITLABELS 允许用户编辑标签
TVS_HASBUTTONS 为每个父项目分配一个按钮
TVS_HASLINES 树中两个项目之间加上一根线
TVS_LINESATROOT 在根和子项目之间加上一根线
TVS_SHOWSELALWAYS 在失去焦点时仍强制一个选项保持选中状态
rect:说明树形控件的大小和位置
pParentWnd:指向控件父窗口的指针,由于控件必定有父窗口,因此这个参数一定不能
为 NULL
NID:要创建的树形控件的 ID
创建举例:
CTreeCtrl m_ctrlTree;
m_ctrlTree.Create (WS_VISIBLE|WS_CHILD|WS_BORDER|TVS_HASLINES|TVS_LINESATROOT|
TVS_HASBUTTONS|TVS_EDITLABELS,CRect(20,10,280,420),this,IDC_TREECTRL);
(2)树形控件的项目
MFC 中定义的 TV_ITEM 结构见清单 6 16。
155
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
清单6 16 TV_
ITEM 结构定义
Ty
pe d
efs t
ruct_ TV_ ITEM
UINT mask; //指示有效域的标志
HTREEITEM hItem; //项目句柄
UINT state; //项目的当前状态
UINT stateMask; //有效项目状态
LPSTR pszText; //字符串缓冲区地址
Int cchTextMax; //字符串缓冲区的大小
int iImage; //该项目的图像索引,未选中状态
int iSelectedImage; //该项目的图像索引,选中状态
int cChilren; //项目是否有与其相关的子项目
LPARAM lParam; //附加信息(32 位)
}TV_ ITEM
状 态 状 态 状 态
TVIS_BOLD TVIS_CUTE TVIS_DROPHILLED
TVIS_EXPANDED TVIS_EXPANDONC TVIS_FOCUSED
TVIS_OVERLAYMASK TVIS_SELECTED TVIS_STATEIMAGEMASK
TVIS_USERMASK
(3)插入结构的定义
TV_INSERTSTRUCT 保存如何将结构 TV_ITEM 插入到树形控件中的信息,其结构定义见清
单 6 17。
清单6 17 TV_
INSERTSTRUCT结构定义
Ty
pe d
efstructTV_ INSERTSTRUCT
HTREEITEM hParent; //父树形控件项目的句柄
HTREEITEM hInsertAfter; //指定在其后插入新项目的项目句柄
TVITEM item; //包含插入树中的项目的信息的 TVITEM 结构
}TV_ INSERTSTRUCT
6
84 编程实例
【例6 6】 Ex06_6 图像列表控件、列表控件与树形控件的应用编程,具体说明见表
6 28。
表6 28 例6 6具体说明
项目 图示和说明
程序
界面
图(
b) 菜单
图(
a) 主界面
包括的控件有:
1 个树形控件、1 个列表控件
(1)图(a):在华东理工大学下面为学院,学院下面为年级,点击年级将在列表控件中显示年级中
程序
的班级名称、人数和班导师
功能
(2)图(b):点击查看菜单下大图标、小图标、列表和报告,列表控件将按所选方式显示
(1)建立一单文档应用程序:Ex06_6
(2)编辑 5 个图标(见表 6 29)
表6 29 5个图标
ICON 的 ID 号 图 标 说 明
IDI_ICONTREE1 用于树形控件中的学校
IDI_ICONTREE2 用于树形控件中的学院
IDI_ICONTREE3 用于树形控件中的年级
IDI_ICONB 用于列表控件中大图标
IDI_ICONS 用于列表控件中小图标
(3)初始化视图
初始化视图一般重载 OnInitialUpdate 函数,重载的该函数见清单 6 18。 157
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
清单6 18 On
Ini
ti
alUpd
at 06_
e函数(在 Ex 6Vi
ew.
cpp文件中)
v
oidCEx 06_6V i
ew On I
nit
ial
Upda
te()
{ CView::OnInitialUpdate();
CreateListCtrl();//创建列表控件
CreateTreeCtrl();//创建树形控件
}
(4)创建树形控件
① 定义一树形控件对象、图像列表对象和创建树形控件函数(见清单 6 19)。
② 编写创建树形控件函数(见清单 6 20)。
③ 定义 IDC_TREECTRL。
操作菜单 View- >Resource Symbols 弹出 Resource Symbols 对话框(
见图 6 28)
,点击 New 弹
出 New Symbol 对话框,在 Name 编辑框中输入 IDC_TREECTRL, Value 编辑框的值取系统自动给定的
值 101。点击 New Symbol 对话框中的 OK 按钮和 Resource Symbols 对话框中的 Close 按钮。
图6 28 定义IDC_
TREECTRL
(5)创建列表控件
① 定义列表控件对象、图像列表对象和创建列表控件的函数(见清单 6 19)。
06_
清单6 19 定义成员变量和函数(在 Ex 6Vi h头文件中)
ew.
c
las
sCEx 06_ 6Viewpub l
icCV iew
public:
CImageList m_ctrlSImageList; //定义图像列表对象,用于列表控件小图标
CImageList m_ctrlBImageList; //定义图像列表对象,用于列表控件大图标
CImageList m_ctrlTreeImageList; //定义图像列表对象,用于树形控件
CTreeCtrl m_ctrlTree; //定义树形控件对象
void CreateTreeCtrl(); //创建和初始化树形控件函数
CListCtrl m_ctrlList; //定义树形控件对象
void CreateListCtrl(); //创建和初始化列表控件函数
}
② 编写创建列表控件的函数(见清单 6 20)。
③ 定义 IDC_LISTCTRL。
158 操作过程同树形控件。
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
06_
清单6 20 创建树形控件和列表控件的函数(在 Ex 5Vi
ew.
cpp文件中)
v
oidCEx 06_ 5V i
ew CreateTre
eC t
rl()//创建树形控件函数
{ //创建图像列表
m_ctrlTreeImageList.Create(13,13,false,3,0);
HICON hIcon = ::LoadIcon(AfxGetResourceHandle(),MAKEINTRESOURCE(IDI_ICONTREE1));
m_ctrlTreeImageList.Add(hIcon);
hIcon = ::LoadIcon(AfxGetResourceHandle(),MAKEINTRESOURCE(IDI_ICONTREE2));
m_ctrlTreeImageList.Add(hIcon);
hIcon = ::LoadIcon(AfxGetResourceHandle(),MAKEINTRESOURCE(IDI_ICONTREE3));
m_ctrlTreeImageList.Add(hIcon);
//创建树形控件
m_ctrlTree.Create(WS_VISIBLE|WS_CHILD|WS_BORDER|TVS_HASLINES|TVS_LINESATROOT|
TVS_HASBUTTONS|TVS_EDITLABELS,CRect(20,10,220,300),this,IDC_TREECTRL);
//将图像列表与树形控件相联系
m_ctrlTree.SetImageList(&m_ctrlTreeImageList,LVSIL_NORMAL);
//为根目录初始化 TVITEM 结构
TVITEM tvItem;
tvItem.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
tvItem.pszText =" 华东理工大学";
tvItem.cchTextMax = 16;
tvItem.iImage = 0;
tvItem.iSelectedImage = 0;
TVINSERTSTRUCT tvInsert;
tvInsert.hParent = TVI_ROOT;
tvInsert.hInsertAfter = TVI_FIRST;
tvInsert.item = tvItem;
HTREEITEM hRoot = m_ctrlTree.InsertItem(&tvInsert);
//将各学院插入到学校的根目录下:
tvItem.pszText =" 机械学院";
tvItem.cchTextMax = 12;
tvItem.iImage = 1;
tvItem.iSelectedImage = 1;
tvInsert.hParent = hRoot;
tvInsert.hInsertAfter = TVI_FIRST;
tvInsert.item = tvItem;
HTREEITEM hCollege1 = m_ctrlTree.InsertItem(&tvInsert);
tvItem.pszText =" 信息学院";
tvItem.cchTextMax = 12;
tvItem.iImage = 1;
tvItem.iSelectedImage = 1;
tvInsert.hParent = hRoot;
tvInsert.hInsertAfter = TVI_FIRST;
tvInsert.item = tvItem;
HTREEITEM hCollege2 = m_ctrlTree.InsertItem(&tvInsert);
//将年级插入到学院的根目录下:
tvItem.pszText =" 机械 01 级";
tvItem.cchTextMax = 12;
tvItem.iImage = 2;
tvItem.iSelectedImage = 2;
tvInsert.hParent = hCollege1;
tvInsert.hInsertAfter = TVI_FIRST;
159
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
tvInsert.item = tvItem;
HTREEITEM hClass = m_ctrlTree.InsertItem(&tvInsert);
tvItem.pszText =" 机械 02 级";
tvItem.cchTextMax = 12;
tvItem.iImage = 2;
tvItem.iSelectedImage = 2;
tvInsert.hParent = hCollege1;
tvInsert.hInsertAfter = TVI_LAST;
tvInsert.item = tvItem;
hClass = m_ctrlTree.InsertItem(&tvInsert);
tvItem.pszText =" 机械 03 级";
tvItem.cchTextMax = 12;
tvItem.iImage = 2;
tvItem.iSelectedImage = 2;
tvInsert.hParent = hCollege1;
tvInsert.hInsertAfter = TVI_LAST;
tvInsert.item = tvItem;
hClass = m_ctrlTree.InsertItem(&tvInsert);
tvItem.pszText =" 机械 04 级";
tvItem.cchTextMax = 12;
tvItem.iImage = 2;
tvItem.iSelectedImage = 2;
tvInsert.hParent = hCollege1;
tvInsert.hInsertAfter = TVI_LAST;
tvInsert.item = tvItem;
hClass = m_ctrlTree.InsertItem(&tvInsert);
}
v
oidCEx 06_ 6V i
ew CreateLi
stCtr //创建列表控件函数
l()
{ //创建图像
m_ctrlSImageList.Create(16,16,false,1,0);
m_ctrlBImageList.Create(32,32,false,1,0);
//添加图像到图像列表中
HICON hIcon = ::LoadIcon(AfxGetResourceHandle(),MAKEINTRESOURCE(IDI_ICONS));
m_ctrlSImageList.Add(hIcon);
hIcon = ::LoadIcon(AfxGetResourceHandle(),MAKEINTRESOURCE(IDI_ICONB));
m_ctrlBImageList.Add(hIcon);
//创建列表控件
m_ctrlList.Create(WS_VISIBLE|WS_CHILD|WS_BORDER|LVS_REPORT|LVS_NOSORTHEADER|
LVS_EDITLABELS,CRect(250,10,500,300),this,IDC_LISTCTRL);
//将图像列表与列表控件关联
m_ctrlList.SetImageList(&m_ctrlSImageList,LVSIL_SMALL);
m_ctrlList.SetImageList(&m_ctrlBImageList,LVSIL_NORMAL);
//创建列表控件列
LV_COLUMN lvColumn;
lvColumn.mask = LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM;
lvColumn.fmt = LVCFMT_CENTER;
lvColumn.cx = 75;
lvColumn.iSubItem = 0;
160
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
lvColumn.pszText =" 班级";
m_ctrlList.InsertColumn(0,&lvColumn);
lvColumn.iSubItem = 1;
lvColumn.pszText =" 人数";
m_ctrlList.InsertColumn(1,&lvColumn);
lvColumn.iSubItem = 2;
lvColumn.pszText =" 班导师";
m_ctrlList.InsertColumn(2,&lvColumn);
}
(6)实现列表控件的不同显示
① 编辑菜单资源。
② 添加菜单消息函数和菜单更新消息函数(见表 6 30)。
表6 30 菜单ID 号和所添加消息函数
菜 单 ID 号 消息处理函数
OnViewBicon()
大图标 ID_VIEW_BICON
OnUpdateViewBicon(CCmdUI*pCmdUI)
OnViewSicon()
小图标 ID_VIEW_SICON
OnUpdateViewSicon(CCmdUI*pCmdUI)
OnViewList()
列表 ID_VIEW_LIST
OnUpdateViewList(CCmdUI*pCmdUI)
OnViewReport()
报告 ID_VIEW_REPORT
OnUpdateViewReport(CCmdUI*pCmdUI)
c
las
sCEx 06_ 6Viewpub li
cCV
iew
protected:
BOOL m_bBIcon;
BOOL m_bSIcon;
BOOL m_bList;
BOOL m_bReport;
v
oidCEx 06_6V i 06_
ewEx 6Vi //构造函数
ew()
{ m_bBIcon = 0;
m_bSIcon = 0;
m_bList = 0;
m_bReport = 1;
161
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
//列表控件的不同显示
vo
idCEx 06_6V i
ewOnV i
ewB i
con()//显示为大图标
{ SetWindowLong(m_ctrlList.m_hWnd,GWL_STYLE,WS_VISIBLE|WS_CHILD| WS_BORDER
|LVS_ICON|LVS_EDITLABELS);
m_bBIcon = 1;
m_bSIcon = 0;
m_bList = 0;
m_bReport = 0;
v
oidCEx 06_6V i
ewOnV i
ewSicon()//显示为小图标
{ SetWindowLong(m_ctrlList.m_hWnd,GWL_STYLE,WS_VISIBLE|WS_CHILD| WS_BORDER|
LVS_SMALLICON|LVS_EDITLABELS);
m_bBIcon = 0;
m_bSIcon = 1;
m_bList = 0;
m_bReport = 0;
v
oidCEx 06_6V i
ewOnV i
ewL i
st()//显示为列表形式
{ SetWindowLong(m_ctrlList.m_hWnd,GWL_STYLE,WS_VISIBLE|WS_CHILD| WS_BORDER|
LVS_LIST|LVS_EDITLABELS);
m_bBIcon = 0;
m_bSIcon = 0;
m_bList = 1;
m_bReport = 0;
v
oidCEx 06_6V i
ewOnV i
ewRe por //显示为报告形式
t()
{ SetWindowLong(m_ctrlList.m_hWnd,GWL_STYLE,WS_VISIBLE|WS_CHILD| WS_BORDER|
LVS_REPORT|LVS_EDITLABELS);
m_bBIcon = 0;
m_bSIcon = 0;
m_bList = 0;
m_bReport = 1;
v
oidCEx06_ 6ViewOnUpd ateVi
ewB icon(CCmdUIpCmdUI)
{ pCmdUI- >SetCheck(m_bBIcon);
}
v
oidCEx06_ 6ViewOnUpd ateVi
ewS ic
on( CCmdUIpCmdUI)
{ pCmdUI- >SetCheck(m_bSIcon);
}
v
oidCEx06_ 6ViewOnUpd ateVi
ewL it(
s CCmdUIpCmdUI)
{ pCmdUI- >SetCheck(m_bList);
}
v
oidCEx06_ 6ViewOnUpd ateVi
ewRe port(CCmdUIpCmdUI)
{ pCmdUI- >SetCheck(m_bReport);
}
(7)实现改变树形控件的操作
① 在 Ex06_6View.h 头文件中声明 OnSelchangedTree 函数(见清单 6 23)。
162
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
清单6 23 声明 OnS
elc
han
gedTr
e 06_
e函数(在 Ex 6Vi h文件中)
ew.
c
las
sCEx 06_ 6Vi
ewpub licCView
protected:
//AFX_MSG(CEx06_6View)
…
//}}AFX_MSG
afx_ms gvoidOnS el
changedTr
ee(
NMHDRpNMHDRLRESULTpRe
sul //手工添加
t);
DECLARE_MESSAGE_MAP()
}
v
oidCEx 06_ 6V i
ew OnS el
changedTre(
e NMHDRpNMHDRLRESULTpRe
sut)
l
{ NM_TREEVIEW *pNMTreeView = (NM_TREEVIEW *)pNMHDR;
//TODO: Add your control notification handler code here
*pResult = 0;
CString strText;
HTREEITEM hTreeItem;
hTreeItem = m_ctrlTree.GetSelectedItem();
strText = m_ctrlTree.GetItemText(hTreeItem);
if(strText = =" 机械 01 级")
{m_ctrlList.DeleteAllItems();
Add01toList();
}
else if(strText = =" 机械 02 级")
{ m_ctrlList.DeleteAllItems();
Add02toList();
}
else if(strText = =" 机械 03 级")
{ m_ctrlList.DeleteAllItems();
Add03toList();
}
else (strText = =" 机械 04 级")
{ m_ctrlList.DeleteAllItems();
Add04toList();
}
}
163
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
④ 声明 Add01toList()—Add04toList()函数(见清单 6 26)。
清单6 26 声明 Add
01t
oLi
st()—Add
04t
oLi
s 06_
t()函数(在 Ex 6Vi h文件中)
ew.
c
las
sCEx 06_6Viewpub li
cCVi
ew
public:
void Add01toList();
void Add02toList();
void Add03toList();
void Add04toList();
}
⑤ 编写 Add01toList()—Add04toList()函数(见清单 6 27)。
清单6 27 Add
01t
oLi
st()—Add
04t
oLi
s 06_
t()函数(在 Ex 6Vi
ew.
cpp文件中)
vo
idCEx 06_6V iew Add01 t
oLit()
s
{ //创建列表控件项目
LV_ITEM lvItem;
lvItem.mask = LVIF_TEXT|LVIF_IMAGE|LVIF_STATE;
lvItem.state = 0;
lvItem.stateMask = 0;
lvItem.iImage = 0;
lvItem.iItem = 0;
lvItem.iSubItem = 0;
lvItem.pszText =" 机械 011";
m_ctrlList.InsertItem(&lvItem);
m_ctrlList.SetItemText(0,1,"30");
m_ctrlList.SetItemText(0,2,"李老师");
lvItem.iItem = 1;
lvItem.iSubItem = 0;
lvItem.pszText =" 机械 012";
m_ctrlList.InsertItem(&lvItem);
m_ctrlList.SetItemText(1,1,"31");
m_ctrlList.SetItemText(1,2,"张老师");
lvItem.iItem = 2;
lvItem.iSubItem = 0;
lvItem.pszText =" 机械 013";
m_ctrlList.InsertItem(&lvItem);
m_ctrlList.SetItemText(2,1,"32");
m_ctrlList.SetItemText(2,2,"刘老师");
}
//Add02toList()- Add04toList()略
(8)编译和运行
9 属性单 、属性页和向导
6
属性单(Property Sheet)是带标签的对话框。每个属性单至少带有两个属性页,每个属
164 性页 (Property Page )都 基 于 对 话 框 模 板, 出 现 在 属 性 单 的 某 一 标 签 中。 MFC 类 的
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
6
91 CP
rope
rtyPage类
CPropertyPage 类是 CDialog 类的一个派生类,继承关系见图 6 29。
图6 29 CPr
ope
rtyPa
ge类的继承关系
表6 31 CPr
ope
rtyPa
ge类的主要成员函数
成员函数 返回值 说 明
CPropertyPage 构造 CpropertyPage 对象
构造 CpropertyPage 对象。若需要在运行时指定参数,或者使
Construct void
用数组指定参数,则使用构造函数 Construct
在对模式属性单中的某一页进行了不可恢复的改变后,调用该
CancelToClose void
函数将 OK 按钮改为 Close 按钮,将 Cancle 按钮灰化处理
SetModified void 激活或禁止 Apply 按钮
向属性单的每一个属性页依次发送消息,如果某属性页返回非
QuerySiblings LRESULT
零值,就停止发送
OnCancel void 当单击 Cancel 按钮时由框架调用此函数
OnKillActive BOOL 当前页不再是活动页时由框架调用此函数
OnOK void 当单击 OK、Apply 或 Close 按钮时由框架调用此函数
OnSetActivate BOOL 当属性页被激活时,由框架调用此函数
OnApply BOOL 当单击 Apply 按钮时,由框架调用此函数
OnReset void 当单击 Cancel 按钮时,由框架调用此函数
当单击 Cancel 按钮,但相关操作还没有执行时由框架调用此
OnQueryCancel BOOL
函数
在向导类型的属性表中,在单击上一页按钮时由框架调用此
OnWizardBack LRESULT
函数
在向导类型的属性表中,在单击下一页按钮时由框架调用此
OnWizardNext LRESULT
函数
在向导类型的属性 表 中,在 单 击 完 成 按 钮 时 由 框 架 调 用 此
OnWizardFinish void
函数
6
92 CP
rope
rt t类
yShee
CPropertySheet 类是 CWnd 类的一个直接派生类,继承关系见图 6 30。
图6 30 CPr
ope
rtSh
y e
et类的继承关系
165
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
成员函数 返回值 说 明
CPropertySheet 构造 CpropertySheet 对象
构造 Cpropertysheet 对象。当定义了 CpropertySheet 类对象
Construct void 而没有调用构造函数时,如定义 了 CpropertySheet 对 象 数 组
时,必须显示地调用 Construct
GetActiveIndex int 获取属性单中当前活动页的索引
GetPageIndex int 获取属性单中指定属性页的索引
GetPageCount int 获取属性单中属性页的数量
GetPage CPropertyPage* 获取指向指定属性页的指针
GetActivePage CPropertyPage* 返回当前活动页对象的指针
SetActivePage BOOL 设置活动页对象
SetTitle void 设置属性单的标题
GetTabControl CTabCtrl* 获取指向 tab 控件的指针
SetFinishText void 设置 Finish 按钮的文本
SetWizardButtons void 设置向导中 Set 按钮的显示模式
SetWizardMode void 设置为向导模式
DoModal int 显示模式属性单
Create BOOL 显示非模式属性单
AddPage void 在属性单中增加一个属性页
RemovePage void 从属性单中删除一个属性页
PressButton BOOL 模拟属性单中指定按钮的单击
EndDialog void 关闭属性单
6
93 编程实例
【例6 7】 Ex06_7 属性单、属性页的应用,具体说明见表 6 33 所示。
表6 33 例6 7的具体说明
项目 图示和说明
程序
界面
图(
a) 属性单 图(
b) 向导
166
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
续 表
项目 图示和说明
(1)点击菜单:查看- >属性单...,将弹出图(a)
程序
(2)点击菜单:查看- >向导...,将弹出向导的第一页(见图(b)),按下一步到第二页,再按一次到
功能
第三页
(1)建立一单文档应用程序:Ex06_7
(2)插入 3 个对话框
对话框 ID 号为 IDD_DIALOG1、IDD_DIALOG2、IDD_DIALOG3。
在对话框属性选择中将 style 选为 Child,Border 选为 Thin,选中 Disabled 复选按钮,
删除 OK 和 Cancel 按钮。
(3)为 3 个对话框分别建类
类名 分 别 为: CPage1Dlg、CPage2Dlg 和 CPage3Dlg; 基 础 类 选 为: CPropertyPage; 见 图
6 31。
图6 31 CPa
ge1D
lg类的建立 图6 32 CPa
geD
lgSh
eet类的建立
(4)属性单的实现
① 创建属性单类。
点击 Insert 菜单下的 NewClass,在 Name 中输入 CPageDlgSheet,在 Base class 中选
择 CPropertySheet,见图 6 32。单击 OK 按钮即建立了 CPageDlgSheet 类。
② 在 PageDlgSheet.h 头文件中,声明属性页对象和成员函数(见清单 6 28)。
③ 添加 3 个包含属性页类头文件的语句(见清单 6 28)。
清单6 28 Pa
geD
lgSh
ee h头文件
t.
//CPageDlgSheet
//添加 3 个包含属性页的语句
# include" Page1Dlg.h"
# include" Page2Dlg.h"
# include" Page3Dlg.h"
classCPa geDlgSheetpub l
icCPr ope
rtSh
y e
et
DECLARE_DYNAMIC(CPageDlgSheet)
167
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
…
public:
CPageDlgSheet(UINT nIDCaption, CWnd*pParentWnd = NULL, UINT iSelectPage = 0);
CPageDlgSheet(LPCTSTR pszCaption, CWnd*pParentWnd = NULL, UINT iSelectPage = 0);
…
public:
//声明 3 个属性页的对象
CPage1Dlg m_page1dlg;
CPage2Dlg m_page2dlg;
CPage3Dlg m_page3dlg;
//声明成员函数
void AddPageToSheet();
};
CPageDlgShee
tCPageDlgShet(
e UINTn IDCa
p t
ionCWndpPare
ntWndUINTiSe
lec
tPage)
:CProper
t Sh
y eet(
nIDCa t
pionpPa r
entWndiSel
ectPae)
g
{ AddPageToSheet();
}
CPageDlgShee
tCPageDlgShet(
e LPCTSTRp szCapt
ionCWndpPar
entWndUINTiS
ele
ctPa
ge)
:CProper
t Sh
y eet(
pszCapti
o npParentWndi
S e
lec
tPage)
{ AddPageToSheet();
}
CPageDlgShee
t~CPa geDlgShet()
e
{
}
vo
idCPa geDlgShe
etAddPa eTo
g Sheet()
{ AddPage(&m_page1dlg);
AddPage(&m_page2dlg);
AddPage(&m_page3dlg);
}
(5)向导的实现
① 创建向导类。
点击 Insert 菜单下的 NewClass,在 NewClass 对话框的类名中输入 CPageDlgWizard,
在 Base class 中选择 CPropertySheet。单击 OK 按钮即建立了 CPageDlgWizard 类。
② 在 PageDlgWizard.h 文件中,声明属性页对象和成员函数(见清单 6 30)。
③ 添加 3 个包含属性页类头文件的语句(见清单 6 30)。
清单6 30 Pa
geD
lgWi
zar
d.h头文件
//CPageDlgWizard
//添加 3 个包含属性页的语句
# include" Page1Dlg.h"
# include" Page2Dlg.h"
168
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
# include" Page3Dlg.h"
c
lassCPa geD lgWizardpublicCPr o
per
tySh
eet
public:
//声明 3 个属性页的对象
CPage1Dlg m_page1dlg;
CPage2Dlg m_page2dlg;
CPage3Dlg m_page3dlg;
//声明成员函数
void AddPageToWizard();
};
CPaeD
g lgWizardCPa geDl
gWizad(
r UINTnIDCapt
ionCWndpPare
ntWndUINTiSe
lec
tPage)
:CPr
o pert
yShee (
tn IDCa pt
ionpPare
n tWndi
Sel
ectPa
ge)
{ AddPageToWizard();
}
CPaeD
g lgWizardCPa geDl
gWizad(
r LPCTSTRpszCapt
ionCWndpPar
entWndUINTiS
ele
ctPa
ge)
:CPr
o pert
ySheet(pszCapti
onpParentWndiSe
lec
tPage)
{ AddPageToWizard();
}
vo
idCPa geDlgWizar dAddPaeToWi
g za
rd()
{ AddPage(&m_page1dlg);
AddPage(&m_page2dlg);
AddPage(&m_page3dlg);
SetWizardMode();
}
(6)调用属性单和向导
① 编辑菜单。
② 添加菜单消息函数(见表 6 34)和编写菜单消息函数(见清单 6 32)。
表6 34 添加菜单消息函数
菜 单 ID 号 消 息 函 数
属性单... ID_VIEW_SHEET OnViewSheet()
向导... ID_VIEW_WIZARD OnViewWizard()
# include" Ex06_7View.h"
# include" PageDlgSheet.h"//包含属性页头文件
# include" PageDlgWizard.h"//包含向导类头文件
v
oi dCEx 06_7V i
ew OnV iewShe
e //点击菜单“属性单...”消息处理函数
t()
{ CPageDlgSheet dlgsheet("属性单",this,0);//显示属性单
169
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
dlgsheet.DoModal();
}
v
oidCEx06_7View OnViewWizar //点击菜单“向导...”消息处理函数
d()
{ CPageDlgWizard dlgWizard("向导",this,0);
dlgWizard.DoModal();//显示向导
}
(7)编译和运行
10 通用对话框类
6
通用对话框类用于各种 Windows 程序中,执行各种标准操作。 它们由 CDialog 类派生
而来,其资源全部包含在\Windows\SYSTEM 目录下的 COMMDLG.DLL 动态链接库中。 表 6 35
列出了通用对话框类的名称及其作用 。
表6 35 通用对话框类的名称及其作用
类 名 作 用 数据成员及其结构名称
CFontDialog 从提供的字体列表中选择一种字体 m_cf:CHOOSEFONT
CFileDialog 打开或保存一个存在或新建的文件 m_ofn:OPENFILENAME
CColorDialog 选择或创建颜色 m_cc:CHOOSECOLOR
设置打印 机 及 与 打 印 有 关 的 参 数,打 印
CPrintDialog m_pd:RRWTDLG
文档
CFindReplaceDialog 在文本文件中查找替换指定字符串 m_fr:FINDREPLACE
CPageSetupDialog 设置打印机页面 m_psd:PAGESETUPDLE
COleDialog 派生了多种 OLE 控制对话框类 m_io 等
6
10
1 CFo
ntD
iaog类
l
该类为字体对话框类,主要成员函数见表 6 36,具体应用见第 7 章。
表6 36 CFo
ntD
ial
og类
成 员 函 数 作 用 使 用 方 法
GetColor 获得当前颜色
GetCurrentFont 获得当前字体
GetFaceName 获得字体的字体名称
(1)构造 CFontDialg 类的对象
GetStyleName 获得字体的风格名称 (2)通过对象的数据成员 m_cf 初始化字
GetSize 获得字体的点大小 体对话框中各控件的值或状态
GetWeight 获得字体的磅数 (3)调用成员函数 DoModal 显示对话框
IsStrikeOut 确定字体是否带删除线 (4)应用程序通过 CFontDialog 类的成员
函数获得各种信息
IsUnderline 确定字体是否带下划线
IsBold 确定字体是否是粗体
IsItalic 确定字体是否斜体
170
第六章 对话框与控件
……………………………………………………………………………………………………………………………………………
6
10
2 CF
ileD
iaog类
l
该类为文件对话框类,主要成员函数见表 6 37,具体应用见第 8 章。
表6 37 CF
ileD
ial
og类
成员函数 作 用 使 用 方 法
GetFileName 返回所选文件名 (1)构造 CFileDialog 类的对象
GetPathName 返回所选文件路径 (2)通过对象的数据成员 m_ofn 初始
GetFileExt 返回所选文件扩展名 化文 件 对 话 框 中 各 控 件 的 值 或
状态
GetFileTitle 返回所选文件标题
(3)调 用 成 员 函 数 DoModal 显 示 对
GetNextPathName 返回下一个所选文件标题 话框
GetReadOnlyPref 返回文件的只读属性 (4)应用程序通过 CFileDialog 类的
GetStartPosition 返回文件列表中的第一个元素的位置 成员函数获得各种信息
6
10
3 CCo
lorD
iaog类
l
该类为颜色对话框类,主要成员函数见表 6 38,具体应用见第 7 章。
表6 38 CCo
lorD
ial
og类
成 员 函 数 作 用 使 用 方 法
返回 一 个 COLORREF 结 构,其 中 包 (1)构造 CColorDialog 类的对象
GetColor
含所选的颜色 (2)通过对象的数据成员 m_cc 初始化颜
色对话框中各控件的值或状态
GetSavedCustomColors 获得用户创建的定制颜色
(3)调用成员函数 DoModal 显示对话框
(4)应用程序通过 CColorDialog 类的成
GetCurrentColor 将当前颜色设定为指定颜色 员函数获得各种信息
6
10
4 CP
rin
tDi
alog类
该类为打印对话框类,主要成员函数见表 6 39。
表6 39 CPr
intD
ial
og类
成 员 函 数 作 用 使 用 方 法
GetCopies 获得打印份数
GetDefaults 获得未在对话框中显示的设备缺省值
(1)构造 CPrintDialog 类的对象
GetDeviceName 获得当前所选的打印设备名称 (2)通过对象的数据成员 m_pd 初始化
GetDevMale 获得一个 DEVMODE 结构 打印对话框中各控件的值或状态
GetDriverName 获得当前所选的打印驱动名称 (3)调 用 成 员 函 数 DoModal 显 示 对
GetFromPage 获得打印队列中的开始页 话框
(4)应用程序通过 CPrintDialog 类的
GetTopPage 获得打印队列中的结束页
成员函数获得各种信息
GetPortName 获得当前所选的打印端口名称
GetPrinterDC 获得一个打印设备关联的控制
6
10
5 CF
indRep
laceD
iaog类
l
该类为查找替换对话框类,主要成员函数见表 6 40。 171
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
表6 40 CF
indRe
pla
ceD
ial
og类
成 员 函 数 作 用 使 用 方 法
决定用户是否查找字符串的下一 (1)构造 CPrintDialog 类的对象
FindNext
目标 (2)通过对象的数据成员 m_fr 初始化查
GetFindString 返回当前查找字符串 找替换对话框中各控件的值或状态
GetReplaceString 返回当前替换字符串 (3)调用成员函数 DoModal 显示对话框
(4)应用程序通过 CFindReplaceDialog 类
用户是否需要替换所有匹配字
Replace All 的成员函数获得各种信息
符串
172
第七章 绘 图
1 设备环境类
7
为了支 持 GDI 绘 图,MFC 提 供 了 两 种 重 要 的 类:设 备 环 境 类 (CDC 类 )和 绘 图 对 象 类
(CGdiObject 类)。设备环境类用于设置绘图属性和绘制图形;绘图对象类封装了各种 GDI
绘图对象和方法。
7
11 CDC 类
CDC 类(设备环境类)的基类是 CObject,CDC 类封装了所有绘图函数。用法如下:
CDC * pDC;//定义一个 CDC 类的指针对象
pDC- > TextOut(100,100, "Hello V C++");//绘文字(文字也是一种图形)
CClientDC dc(this);
dc.TextOut(100,100, "Hello V C++");
(2)CPaintDC 类
该类是为 OnPaint 函数进行重画时所提供的显示描述表,该类的构造函数自动调用
BeginPaint 函数,而析构函数自动调用 EndPaint 函数。 在 PaintDC 上绘图直到下次重画时 173
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
才会显示。在应用程序运行时,改变窗口大小或移动覆盖在窗口 上 的 窗 口 或 对 话 框 时,
Windows 会发送 WM_PAINT 消息以通知客户区的变动,同时发送 WM_PAINT 消息通知非客户区
的变化。一般来说,对 WM_PAINT 消息的处理在 OnPaint 消息处理函数中进行;而在非客户区
的重画由系统完成。
CPaintDC dc(this);
OnPrepareDC(&dc);
dc.TextOut(100,100, "Hello V C++");
(3)CWindowDC 类
该类用于应用程序窗口包括客户区与非客户区(包括窗口边框、标题栏、控制按钮等 )的
绘制。
(4)CMetaFileDC 类
该类用于图源文件的绘制。图源文件记录一组 GDI 命令,可以通过这一组 GDI 命令重
建图形输出。使用 CMetaFileDC 时,所有的图形输出命令会自动记录到一个与 CMetaFileDC
相关的图源文件中。
7
12 映射模式
在绘图中所用的坐标都是按逻辑单位(像素)给出的。 1 个逻辑单位代表多少实际的长
度,这个关系模式称为映射模式(map mode)。共有三类八种映射模式,见表 7 1。
表7 1 Wi
ndows中的映射模式
映 射 模 式 说 明
逻辑点 像素点 MM_TEXT 1 个逻辑点= 1 个设备像素。正 X 在右,正 Y 在上
MM_TWIPS 1 个逻辑点 twip = 1/1 440 英寸,正 X 在右,正 Y 在上
MM_HIENGLISH 1 个逻辑点= 0001 英寸,正 X 在右,正 Y 在上
逻辑点 固定长度
MM_HIMETRIC 1 个逻辑点= 0001 厘米,正 X 在右,正 Y 在上
(约束映射)
MM_LOENGLISH 1 个逻辑点= 001 英寸,正 X 在右,正 Y 在上
MM_LOMETRIC 1 个逻辑点= 001 厘米,正 X 在右,正 Y 在上
MM_ANISOTROPIC 将逻辑单位换算为应用程序定义的值
逻辑点 非固定长
与 MM_ANISOTROPIC 类似,但将 X 轴上的某个单位转换为 Y 轴上
度(非约束映射) MM_ISOTROPIC
相同距离的单位
CDC * pDC;
pDC- > SetMapMode(MM_LOMETRIC);
2 GDI对象与 CGd
7 iOb
j t类
ec
GDI 对象是 Windows 图形设备接口的抽象绘图工具,包括画笔、画刷、位图、调色板、字体
和区域等。这些工具在绘图中起着不同的作用,例如画笔用来指定所绘制图形的线条类型,
174 画刷用来指定填充图形内部的模式,而字体用来指定输出文本的字体等 。
第七章 绘 图
……………………………………………………………………………………………………………………………………………
7
21 画笔(
CPen)
CPen 类是从 CGdiObject 类中派生出来的类,是 Windows GDI 提供的绘制线条和图形的
工具,它可以以多种颜色和线形绘制各种直线 、曲线或图形边框。
(1)画笔的分类
画笔可分为堆画笔和自定义画笔,自定义画笔可分为修饰画笔和几何画笔。 堆画笔是
系统已定义好的画笔,包括 BLACK_PEN(黑色画笔)、WHITE_PEN(白色画笔)和 NULL_PEN(空画
笔即不画线或不画边框)三种画笔。自定义画笔对画笔的属性要进行编程设定,修饰画笔用
在对绘图有较高要求的场合;几何画笔用于绘制一般的直线、曲线等。
(2)几何画笔的三种属性
几何画笔具有下列三种属性:类型、宽度和颜色。 画笔的宽度以像素为单位,默认的宽
度为 1 个像素单位。在 Windows 中常用的画笔类型如表 7 2 所示。
表7 2 画笔类型
画 笔 类 型 描 述 画 笔 类 型 描 述
PS _SOLID 实 线 PS_NULL 空 线
PS_DASH 画 线 PS_INSIDEFRAME 内框线
PS_DOT 点 线 PS_GEOMETRIC 几何笔
PS_DASHDOT 点画线 PS _COSMETIC 修饰笔
(3)画笔的构造函数
CPen 类有三种构造函数,其原型分别为:
CPen();
CPen(int nPenStyle,int nWidth,COLORREF crColor);
CPen(int nPenStyle,int nWidth, const LOGBRUSH * pLogBrush,int nStyleCount = 0,
const DWORD * lpStyle = NULL);
前两个用于构造几何画笔,后一个用于构造修饰画笔。
参数:nPenStyle 为画笔类型见表 7 2;nwidth 为画笔宽度;crColor 为画笔颜色。
如果使用未带参数的构造函数,一般使用 CreatePen 成员函数来构造 CPen 对象。 如果
使用带参数的构造函数,则无需初始化。
构造画笔举例:创建了一个黑色、笔宽为 2 个像素点、绘制虚线的画笔。
① 采用一步构造法
一步构造法是指在定义画笔对象的同时就设定画笔的特性 。如:
② 采用二步构造法
二步构造法是指先定义画笔对象,然后用 CreatePen 函数设定画笔的特性。如:
CPen newPen;
NewPen.CreatePen (PS_DASH,2,RGB(255,255,255)); 175
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
7
22 画刷(
CBr
ush)
画刷是应用程序用颜色填充控件 、窗口或其他区域的 GDI 对象。
(1)画刷的分类
画刷可分为:堆画刷、实心画刷、阴影画刷和图案画刷。堆画刷包括 6 种,见表 7 3。
表7 3 堆画刷的类型
堆画刷类型 说 明
BLACK_BRUSH 黑色画刷
WHITE_BRUSH 白色画刷
DKGRAY_BRUSH 暗灰色画刷
GRAY_BRUSH 灰色画刷
LTGRAY_BRUSH 淡灰色画刷
NULL_BRUSH 空画刷,内部不填充
(2)画刷的三种属性
画刷有下列三种属性:颜色、图案和阴影类型。阴影类型见表 7 4。
表7 4 画刷的阴影类型
阴影类型 说 明 索引号
HS_HORIZONTAL 阴影线为水平线 0
HS_VERTICAL 阴影线为垂直线 1
HS_BDIAGONAL 阴影线为+45°斜线(从左到右) 2
HS_FDIAGONAL 阴影线为-45°斜线(从右到左) 3
HS_CROSS 阴影线为交叉的水平线和垂直线 4
HS_DIAGCROSS 阴影线为斜向交叉的直线 5
(3)画刷的构造函数
CBrush 类有四种构造函数,其原型分别为:
CBrush();
CBrush(COLORREF crColor);
CBrush(int nIndex,COLORREF crColor);
CBrush(CBitmap*pBitmap);
参数:nIndex 代表阴影的类型,见表 7 4。
(4)创建画刷
① 一步法创建画刷
CBrush newBrush(RGB(0,0,255));//创建绿色实心画刷
//创建具有 45 度向上斜线阴影的绿色画刷
CBrush newBrush(HS_FDIAGONAL, RGB(0,0,255));
//创建位图图案的画刷
CBitmap pBitmap;
176 CBrush newBrush(&pBitmap);
第七章 绘 图
……………………………………………………………………………………………………………………………………………
② 两步法创建画刷
● 用 CreateSolidBrush(DWORD crColor)函数创建一个实心画刷。
例:创建一个黑色实心画刷。
CBrush newBrush;
newBrush. CreateSolidBrush(RGB(255,255,255));
● 用 CreateHatchBrush (int nIndex,DWORD crColor )函 数 创 建 一 个 带 阴 影 的 画 刷,
nIndex 取值见表 7 4。
例:创建一个红色水平线填充画刷。
CBrush newBrush;
newBrush. CreateHatchBrush(HS_HORIZONTAL,RGB(255,0,0));
● 用 CreatePatternBrush(CBitmap * pBitmap)函数创建一个图案画刷。
7
23 字体(
CFo
nt)
文本以某种字体方式显示输出,字体是一种 GDI 对象。 文本输出之前,首先创建字体,
然后用所创建的字体输出文本。
(1)字体的分类
Windows 使用两种字体族:固定宽度字体和可变宽度字体。
Windows 定义了三种不同的字体类型,分别是以下三种。
光栅字体:也称位图字体,是一种与设备有关的字体,其显示与屏幕分辨率有关。
向量字体:由图形线段组成的字体,与设备无关,但显示速度较慢。
True Type 字体:由图形线段定义字符外形的字体,因其显示效果好,故应用最广。
(2)字体的属性
字体一般有 14 个属性,用 CreatFont 函数创建字体的 14 个属性,见清单 7 1。
清单7 1 字体的属性
BOOLCr eat
eFont(
Int nHeight, //字符高度
Int nWidth, //字符宽度
Int nEscapement, //行倾斜度
Int nOrientation, //字符旋转度
Int nWeight, //字符线宽
BYTE bItalic, //斜体
BYTE bUnderline, //下画线
BYTE cStrikeOut, //删除线
BYTE nCharSet, //字符集
BYTE nOutPrecision, //输出匹配
BYTE nClipPrecision, //区域裁剪
BYTE nQuality, //属性匹配
BYTE nPitchAndFamily, //字体间距
LPCTSTR lpszFacename //字体名称
);
t
ype
de fstructtagLOGFONT
LONG lfHeight; //以逻辑单位指定字体的高度
LONG lfWidth; //以逻辑单位指定字体中字符的平均宽度
LONG lfEscapement; //以十分之一度为单位指定以该字体写出的一行文字的角度
LONG lfOrientation; //以十分之一度为单位指定每个字符的基线的角度
LONG lfWeight; //字体线宽(0~1 000)
BYTE lfItalic; //若设置为 TRUE,则指定为斜体字体
BYTE lfUderline; //若设置为 TRUE,则指定为下画线字体
BYTE lfStrikeOut; //若设置为 TRUE,则指定为删除线字体
BYTE lfCharSet; //指定字符的字体集
BYTE lfOutPrecision; //指定输出精度
BYTE lfClipPrecision; //指定裁剪精度
BYTE lfQuality; //指定输出质量
BYTE lfPitchAndFamily; //指定字体的字距和字体族
CHAR lfFaceNameLF_FACESIZE; //指向指定字体的字样名以 NULL 结束的字符串,长度不得超过
32 个字符。
LOGFONT;
}
在使用逻辑字体前,先将其选入设备环境,选入后逻辑字体成为物理字体。
(4)TEXTMETRIC 数据结构
TEXTMETRIC 结构可以设置或获取文本字符的有关信息 。其定义见清单 7 3。
清单7 3 TEXTMETRIC 结构定义
t
ype
de fstructtagTEXTMETRIC
int tmHeight; //字符高度
int tmAscent; //字符基线以上高度
int tmDescent; //字符基线以下高度
178
第七章 绘 图
……………………………………………………………………………………………………………………………………………
(5)字体的创建
CFont 类有四种字体创建函数,其原型分别为:
BOOL CreateFont(14 个参数),见清单 7 1。
BOOL CreateFontIndirect(const LOGFONT * lpLogFont );参 数 * lpLogFont 为 逻 辑 字 体
指针。
BOOL CreatePointFont(int nPointSize, LPCTSTR lpszFaceName, CDC*pDC = NULL);
BOOL CreatePointFontIndirect(const LOGFONT*lpLogFont, CDC*pDC = NULL);
(6)使用字体对话框
使用字体对话框的程序段见清单 7 4。
清单7 4 使用字体对话框
7
24 位图(
CBtmap)
i
CBitmap 位图类封装了 Windows 的图形设备接口位图,提供管理位图的成员函数。 要使
用该对象,首先构造该对象,然后通过初始化成员函数连接位图句柄到该对象,最后调用该
对象的成员函数。 179
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(1)位图结构
位图结构主要定义位图的有关信息,其定义见清单 7 5。
清单7 5 位图结构
t
ypedefstructtagBITMAP
int bmType;//位图类型
int bmWidth;//位图宽度
int bmHeight;//位图高度
int bmWidthBytes;//
BYTE bmPlanes;//位图的位面数
BYTE bmBitsPixel;//像素位数
LPVOID bmBits;//
BITMAP;
(2)创建位图
① 用 CreateBitmap 函数创建位图
函数原型:CreateBitmap (int nWidth,int nHeight,int nPlanes,int nBits,const *
volid lpBits);
参数:nWidth 和 nHeight:以像素为单位的位图的宽度与高度;
nPlanes:指定位图的位面数;
nBits:各个像素的位数;
lpBits:在创建位图时,位图图案通常不被指定,因此在这种情况下一般为 NULL。
例:创建一个宽 50、高 80、1 个位面、每个像素 24 位并且没有初始化位图。
CBitmap bitmap;
bitmap. CreateBitmap(50,80,1,24,NULL);
② 用 CreateBitmapIndirect 函数创建位图
例:创建一个宽 150、高 180、1 个位面、每个像素 16 位的位图。
BITMAP bm;
bm. bmType = 0;
bm. bmWidth = 150;
bm. bmHeight = 180;
bm. bmWidthBytes = 200;
bm. bmPlanes = 1;
bm. bmBitsPixel = 16;
bm. bmBits = NULL;
bitmap.CreateBitmapIndirect(&bm);
(3)加载位图
LoadBitmap 函数可以加载位图。函数原型如下:
BOOL LoadBitmap(LPCTSTR lpszResourceName);
180 BOOL LoadBitmap(UINT nIDResource);
第七章 绘 图
……………………………………………………………………………………………………………………………………………
参数:lpszResourceName:资源文件名;
nIDResource:资源 ID 号。
使用 LoadBitmap 函 数 加 载 位 图 无 需 使 用 CreateBitmap 或 其 他 位 图 创 建 函 数,
LoadBitmap 函数创建与当前视频系统兼容的位图,同时不考虑资源位图的格式。 以下是一
个具有 IDB_BITMAP1 资源 ID 的位图加载到 CBitmap 对象中的例子:
CBitmap Bitmap;
Bitmap.LoadBitmap(IDB_BITMAP1);
7
3 CPo
int、
CSze 和 CRec
i t
CPoint,CSize 和 CRect 分别表示点类、矩形大小类及矩形类。 这些为简单的数据类,没
有基类,这些类分别封装了标准结构 POINT,SIZE 和 RECT。
7
31 CPo
int类
CPoint 类封装了标准结构 POINT,结构 POINT 表示一个屏幕上的二维点,它的定义如下:
CPoint point1,point2;
point1.x = 100;
point1.y = 100;
point2.x = 200;
point2.y = 200;
7
32 CS
ize类
CSize 类封装了标准结构 SIZE,结构 SIZE 表示一个矩形的长度和宽度,只确定了矩形的
大小,没有确定具体的位置,它的定义如下:
size1.cx = 100;
size1.cy = 100;
或 CSize size(100,100)
7
3 t类
3 CRec
CRect 类封装了标准结构 RECT,结构 RECT 表示一个矩形,与 SIZE 不同的是它既确定了
大小又确定了位置,它的定义如下:
CRect rect1(point1,point2);
CRect rect2(10,10,50,100);
4 常见的绘图任务
7
7
41 绘制图形的一般步骤
(1)定义 CDC 类或其派生类的对象
(2)定义 GDI 对象
(3)创建 GDI 对象
(4)用 CDC::SelectObject 函数选择新的 GDI 对象,保存老的 GDI 对象
(5)做一些绘图工作
(6)还原老的 GDI 对象
绘图步骤举例见清单 7 6。
清单7 6 绘图步骤举例
182
第七章 绘 图
……………………………………………………………………………………………………………………………………………
7
42 绘制文本
使用设备环境选中的字体、文本颜色和背景颜色可以绘制文本 。绘制文本的步骤如下。
(1)字体大小计算
通过调用 GetTextMetric()函数返回当前使用字体的尺寸描述 。
(2)文本属性的设置
设置文本颜色:CDC::SetTextColor(int nColor)
设置背景颜色:CDC::SetBkColor(int nColor)
设置背景模式:CDC::SetBkMode(intnBkMode)
参数:nBkMode 为背景模式,可取 OPAQUE (不透明,用设置的背景色填充文本范围 )和
TRANSPARENT(透明,背景色不起作用)
(3)输出文本函数
GDI 提 供 了 5 个 文 本 输 出 函 数: DrawText ()、ExtTextOut ()、GrayString ()、
TabbedTextOut()、TextOut()。
其中最常用的是 TextOut(),其函数原型支持两种参数方式:
参数 x,y:输出文本行的左上角坐标值。 LpszString:指向输出文本的指针,nCount:输
出字符数,str:输出字符串本身。
BOOL 型返回值返回文本是否正常输出 。
绘制文本举例:
CClientDC dc(this);
dc.SetTextColor(RGB(255,0,0));//设置文字颜色为红色
dc.SetBkColor(RGB(0,0,255));//设置文字背景色为蓝色
dc.TextOut(50,50, "输出文本 ");
7
43 绘点
SetPixel 函数的功能是在指定坐标处按指定颜色绘一点 。
(1)绘点函数
(2)绘点举例
CClientdc dc(this);
dc.SetPixel(200,100,RGB(128,0,128));
7
44 绘直线
MoveTo 函数的功能是将直线起点移动到指定坐标处,LineTo 函数的功能是从起点开始 183
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
画直线到终点。
(1)绘直线函数
MoveTo 函数原型如下:
LineTo 函数原型如下:
CClientdc dc(this);
dc.MoveTo(100,100);
dc.LineTo(200,300);
7
45 绘矩形
(1)绘矩形函数
Rectangle 函数的功能是使用当前选定的画笔绘制一个矩形,并使用当前选定的画刷填
充矩形。函数原型如下:
参数:x1、y1 和 x2、y2:分别指定了矩形的左上和右下点 。
LpRect:指向矩形的指针,定义了矩形的四个角。
(2)绘矩形举例
CClientdc dc(this);
dc. Rectangle (100,100,200,300);
7
46 绘椭圆
(1)绘椭圆函数
Ellipse 函数的功能是使用当前选定的画笔绘制一个椭圆,并使用当前选定的画刷填充
椭圆。函数原型如下:
BOOL Ellipse (int x1, int y1, int x2, int y2);
BOOL Ellipse(LPCRECT lpRect);
参数:x1、y1 和 x2、y2:分别指定了包围椭圆的矩形的左上和右下点;
lpRect:定义包围椭圆的矩形区域。
(2)绘椭圆举例
CClientdc dc(this);
184 dc. Ellipse (100,100,200,300);
第七章 绘 图
……………………………………………………………………………………………………………………………………………
7
47 绘弧线
(1)绘弧线函数
Arc 函数的功能是绘弧线,绘弧线其实是先绘一个椭圆,然后取其中的一部分,即为圆
弧。函数原型如下:
BOOL Arc(nit xl,int y1,int x2,int y2,int x3,int y3,int x4,int y4);
BOOL Arc(LPCRECT lpRect,POINT ptStart,POINT ptEnd);
参数:x1、y1 和 x2、y2:分别指定了外接矩形的左上和右下点;
x3、y3 和 x4、y4:指定了弧的起点和弧的终点;
lpRect:指定了弧的外接矩形;
ptstart、ptEnd:指定了弧的起点和终点。
(2)绘弧线举例
CClientdc dc(this);
dc.Arc(100,100,400,200,80,120,300,150);
7
48 绘位图
BitBlt 函数从源设备环境拷贝一个位图到当前设备环境 。 StretchBilt 函数是将位图
从源矩形拷贝到目标矩形中,并按需要拉伸或压缩位图使其适应目标矩形的大小 。
(1)绘位图函数
BOOL BitBlt (int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int
ySrc, DWORDdwRop);
参数:x、y:目的矩形左上角的逻辑 x 和 y 坐标;
nWidth、nHeight:目的矩形和源位图的宽度和高度(按逻辑单位);
pSrcDC:指向一个 CDC 对象的指针,用于标识从中拷贝位图的源设备环境;
xSrc、ySrc:源位图左上角的逻辑 x 和 y 坐标
dwRop:要执行的光栅操作码,其操作码定义了图形设备接口 (GDI)在输出操作中如何组
合颜色,可取的值见表 7 5。
表7 5 dwRo
p取值
光栅操作码 说 明
BLACKNESS 使所有输出变成黑色
DSTINVERT 将目标位图反相
MERGECOPY 用 AND 操作符将模式与源位图结合
MERGEPAINT 用 OR 操作符将反相的源位图与目标位图结合
NOTSRCCOPY 将反相的源位图拷贝到目标位图
NOTSRCERASE 将目标和源位图的 OR 操作结果反相
PATCOPY 将模式拷贝到目标位图
PATINVERT 用 XOR 操作符将目标位图与模式结合
PATPAINT 用 OR 操作符将反相的源位图与模式结合后使用 OR 操作符将结果与目标位图结合 185
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
续 表
光栅操作码 说 明
SRCAND 用 AND 操作符将目标位图像素与源位图像素结合
SRCCOPY 将源位图拷贝到目标位图上
SRCERASE 使目标位图反相,并使用 AND 操作符将此结果与源位图结合
SRCINVERT 用 XOR 操作符将目标位图与源位图结合
SRCPAINT 用 OR 操作符将目标位图像素与源位图像素结合
WHITENESS 使所有输出变成白色
BOOL StretchBlt (int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc,
int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop);
参数:nSrcWidth、nSrcHeight 表示源矩形的逻辑宽度和逻辑高度;
其余参数同 BilBlt 函数中的参数。
(2)显示位图举例
显示位图完整的过程见清单 7 11。
5 绘图编程实例
7
7
5 I对象和基本绘图函数的应用编程实例
1 GD
【例7 1】 Ex07_1GDI 对象和基本绘图函数的应用,具体说明见表 7 6。
表7 6 例7 1具体说明
项 目 图示和说明
图(
a) 画笔 图(
b) 画刷 图(
c) 字体
程序界面
186 图(
d) 位图 图(
e) 字体对话框 图(
f) 再单击字体菜单
第七章 绘 图
……………………………………………………………………………………………………………………………………………
续 表
项 目 图示和说明
(1)没有点击过菜单字体对话框的操作:
点击菜单“画笔”将出现图(a);点击菜单画刷将出现图(b);点击菜单字体将出现图(c)点
击菜单“位图”将出现图(d);重复上述操作重复出现图(a)~图(d)
(2)点击过菜单“字体”对话框的操作:
程序功能
点击菜单“画笔”将出现图(a);点击菜单“画刷”将出现图(b);点击菜单“字体”将出现图
(f);点击菜单“位图”将出现图(d);重复上述操作重复出现图(a)(b)(f)(d)
(3)改变字体对话框字体的选择,图(f)中“CFontDialog 对 话 框”将 根 据 选 择 的 字 体 重 新
改变
(1)新建一单文档应用程序 Ex07_1
(2)资源准备
① 编辑菜单资源与添加菜单消息函数(见表 7 7)
表7 7 菜单消息函数
菜 单 名 ID 号 消 息 消 息 函 数
字体对话框... ID_FONTDLG COMMAND OnFontdlg();
COMMAND OnGdiPen()
画笔 ID_GDI_PEN
UPDATE_COMMAND_UI OnUpdateGdiPen(CCmdUI*pCmdUI)
COMMAND OnGdiBrush()
画刷 ID_GDI_BRUSH
UPDATE_COMMAND_UI OnUpdateGdiBrush(CCmdUI*pCmdUI)
COMMAND OnGdiFont()
字体 ID_GDI_FONT
UPDATE_COMMAND_UI OnUpdateGdiFont(CCmdUI*pCmdUI)
位图 ID_GDI_BITMAP COMMAND OnGdiBitmap()
② 位图资源的准备
选择 Resource View 选项卡,鼠标放在 Ex07_1 resources 文件夹上,然后单击鼠标右键,
弹出图 7 1 所示的右键菜单;点击“Insert...”,弹出如图 7 2 所示的对话框;选择 Bitmap,
将 ID 号为 IDB_BITMAP1 的资源插入项目中;编辑位图,见图 7 3。
图7 1 右键菜单 图7 2 “
Ins
ertRe
sou
rce”对话框 图7 3 编辑好的位图
(3)字体的设置
① 在 Ex07_1View.h 中 定 义 变 量 m_ logfont,m_ logfont1 和 m_ TextColor (见 清 单 7
6)
187
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
07_
清单7 6 声明变量和函数(在 Ex 1Vi h文件中)
ew.
c
las
sCEx 07_ 1Viewpub licCVi ew
protected:
LOGFONT m_logfont,m_logfont1; //逻辑字
COLORREF m_TextColor; //文字颜色
enumPens,Brushes,Fonts,Bitmapsm_GdiShow; //声明枚举变量,在(4)① 步添加
//声明 4 个函数,在(3)① 步要求定义
void ShowFonts();
void ShowBrushes();
void ShowPens();
void ShowBitmaps(CDC * pDC);
//声明 4 个 BOOL 变量,在(4)① 步添加
BOOL bPens;
BOOL bBrushes;
BOOL bFonts;
BOOL bBitmaps;
② 编写菜单消息函数 OnFontdlg()设置字体(见清单 7 7)
07_
清单7 7 字体设置 (在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx07_1View OnFo ntdlg()//字体设置
{ CFontDialog dlg;
if(dlg.DoModal()= = IDOK)
{ dlg.GetCurrentFont(&m_logfont1);
m_TextColor = dlg.GetColor();
}
}
(4)画笔、画刷、字体和位图的变换显示
① 手工添加枚举变量的声明(见清单 7 6)
② 在构造函数中给枚举变量 m_GdiShow 赋初值(见清单 7 8)
07_
清单7 8 构造函数(在 Ex 1Vi
ew.
cpp文件中)
07_
CEx 1View CEx07_
1Vi //构造函数
ew ()
{ m_GdiShow = Pens;//给枚举变量赋初值
//给 4 个 BOOL 变量赋初值,在(4)② 步添加
bPens = 1; //初始显示画笔
bBrushes = 0;
bFonts = 0;
bBitmaps = 0;
③ 在 OnDraw()函数中使用显示变量(见清单 7 9)
188
第七章 绘 图
……………………………………………………………………………………………………………………………………………
v
oidCEx07_1V iewOnDr aw (CDC pDC )
{ CEx07_1Doc*pDoc = GetDocument();
ASSERT_VALID(pDoc);
switch(m_GdiShow)
{ case Pens:
ShowPens();
break;
case Brushes:
ShowBrushes();
break;
case Fonts:
ShowFonts();
break;
case Bitmaps:
ShowBitmaps(pDC);
break;
(5)添加和编写 4 个函数
① 添加 4 个函数:ShowPens(
)、ShowBrushes(
)、ShowFonts(
)和 ShowBitmaps(
CDC * pDC)
在 ClassView 中鼠标放在 CEx07_1View 类上,点击右键,选择 Add Member Function,在
Function Type 中输入 void,在 Function Declaration 中输入 ShowFonts,访问模式选择为
protected,单击 OK。按同样的方法定义其余三个函数,见清单 7 6。
② 编写 ShowPens()函数(见清单 7 10)
清单7 10 Sh
owPe
n 07_
s()函数(在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx07_ 1View ShowPens()
{
CClientDC dc(this); //定义对象
CPen newPen1,newPen2,newPen3; //定义 3 个新画笔
CPen * pOldPen; //定义老画笔指针
newPen1.CreatePen(PS_SOLID,6,RGB(255,0,0)); //创建实线 6 个点宽的红色画笔
pOldPen = dc.SelectObject(&newPen1); //保存原画笔,选中新画笔
dc.MoveTo(20,20); //将画笔移动到(20,20)点
dc.LineTo(200,20); //画直线
dc.SelectObject(pOldPen); //恢复原画笔
newPen2.CreatePen(PS_DASH,1,RGB(0,255,0)); //创建画线 1 个点宽的绿色画笔
pOldPen = dc.SelectObject(&newPen2); //保存原画笔,选中新画笔
dc.MoveTo(20,80); //将画笔移动到(20,80)点
dc.LineTo(200,80); //画直线
dc.SelectObject(pOldPen); //恢复原画笔
newPen3.CreatePen(PS_DASHDOT,1,RGB(0,0,255)); //创建点画线 1 个点宽的蓝色画笔
pOldPen = dc.SelectObject(&newPen3); //保存原画笔,选中新画笔
dc.MoveTo(20,140); //将画笔移动到(20,140)点
dc.LineTo(200,140); //画点画线
dc.SelectObject(pOldPen); //恢复原画笔
} 189
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
③ 编写 ShowBrushes()函数(见清单 7 11)
清单7 11 Sh
owBr
ush
e 07_
s()函数(在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx07_ 1V i
ew ShowBr u
shes()//画刷
{ int y = 0;
CClientDC dc(this);
CPen newPen(PS_SOLID,1,RGB(0,0,0)); //创建新画笔
CPen * pOldPen;
pOldPen = dc.SelectObject(&newPen); //保存原画笔,选中新画笔
for(UINT i = 0;i<7;++i) //循环创建 7 个画刷,绘 7 个矩形
{ CBrush newBrush;
CBrush * pOldBrush;
if(i = = 6)
newBrush.CreateSolidBrush(RGB(255,0,0)); //创建实心画刷
else
newBrush.CreateHatchBrush(i,RGB(0,0,255));//创建带阴影的画刷
pOldBrush = dc.SelectObject(&newBrush); //保存原画刷,选中新画刷
y+ = 40;
dc.Rectangle(20,y,200,y+30); //绘矩形
dc.SelectObject(pOldBrush); //恢复原画刷
}
dc.SelectObject(pOldPen); //恢复原画笔
}
④ 编写 ShowFonts()函数(见清单 7 12)
清单7 12 Sh
owFo
nt 07_
s()函数(在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx07_1V iew ShowFon ts()//字体
{ CClientDC dc(this);
CFont newFont1,newFont2,newFont3;
CFont* pOldFont;
//由 CreateFont 函数创建字体
newFont1.CreateFont(25, 0, 0, 0,400,
false,false,false,
ANSI_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH|FF_SWISS,
" 宋体");
pOldFont = dc.SelectObject(&newFont1); //保存原字体,选中新字体
dc.SetTextColor(RGB(0,0,0)); //设置字体颜色
dc.SetBkColor(RGB(255,255,0)); //设置背景颜色
dc.TextOut(10,20,"这是由 CreateFont 函数创建的字体"); //在 x = 10,y = 20 处输出文字
dc.SelectObject(pOldFont); //恢复老字体
//由 CreateFontIndirect 函数创建字体
m_logfont.lfHeight = 20;
m_logfont.lfWidth = 0;
m_logfont.lfEscapement = 0;
m_logfont.lfOrientation = 0;
190 m_logfont.lfWeight = FW_NORMAL;
第七章 绘 图
……………………………………………………………………………………………………………………………………………
m_logfont.lfItalic = FALSE;
m_logfont.lfUnderline = FALSE;
m_logfont.lfStrikeOut = FALSE;
m_logfont.lfCharSet = GB2312_CHARSET;
m_logfont.lfOutPrecision = OUT_STROKE_PRECIS;
m_logfont.lfClipPrecision = CLIP_STROKE_PRECIS;
m_logfont.lfQuality = DRAFT_QUALITY;
m_logfont.lfPitchAndFamily = VARIABLE_PITCH|FF_MODERN;
lstrcpy(m_logfont.lfFaceName, _T("楷体_GB2312" ));
newFont2.CreateFontIndirect(&m_logfont);
pOldFont = dc.SelectObject(&newFont2); //保存老字体,选择新字体
dc.SetTextColor(RGB(0,0,255)); //设置字体颜色
dc.SetBkColor(RGB(100,255,0)); //设置背景色
dc.TextOut(10,80,"这是由 CreateFontIndirect 函数创建的字体");//在 x = 10,y = 80 处输出文字
dc.SelectObject(pOldFont); //恢复老字体
//由字体对话框设置的字体
newFont3.CreateFontIndirect(&m_logfont1);
pOldFont = dc.SelectObject(&newFont3); //保存老字体,选择新字体
dc.SetTextColor(m_TextColor); //设置字体颜色
dc.SetBkColor(RGB(0,255,255)); //设置背景色
dc.TextOut(10,120,"CFontDialog 对话框"); //在 x = 10,y = 120 处输出文字
dc.SelectObject(pOldFont); //恢复老字体
}
⑤ 编写 ShowBitmaps()函数(见清单 7 13)
清单7 13 Sh
owB
itma
p 07_
s()函数(在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx07_ 1ViewShowB itma ps(CDC pDC) //位图
{ CBitmap Bitmap; //定义位图类对象
Bitmap.LoadBitmap(IDB_BITMAP1); //调用位图
CDC MemDC; //定义 CDC 类对象
MemDC.CreateCompatibleDC(pDC); //创建一个与指定设备一致的内存设备描述表
CBitmap * pOldBitmap = MemDC.SelectObject(&Bitmap);//保存老位图,选择新位图
BITMAP bm;
Bitmap.GetObject(sizeof(BITMAP),&bm); //获得位图的结构
pDC- >BitBlt(10,10,bm.bmWidth,bm.bmHeight,&MemDC,0,0,SRCCOPY); //显示位图
MemDC.SelectObject(pOldBitmap); //恢复老位图
}
(6)用菜单实现变换显示
① 在 CEx07_1View.h 中声明 4 个 BOOL 变量,用于更新菜单的操作(见清单 7 6)
② 在构造函数中给 4 个 BOOL 变量赋初值(见清单 7 8)
③ 编写菜单消息函数和菜单更新消息函数(见清单 7 14)
07_
清单7 14 菜单消息函数和菜单更新消息函数(在 Ex 1Vi
ew.
cpp文件中)
//菜单消息处理函数
vo
idCEx07_ 1V iew
OnGd
iPe //点击画笔菜单
n()
{ m_GdiShow = Pens;
bPens = 1;
191
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
bBrushes = 0;
bFonts = 0;
Invalidate();
}
vo
idCEx07_ 1V iew
OnGdi Bru
sh()//点击画刷菜单
{ m_GdiShow = Brushes;
bPens = 0;
bBrushes = 1;
bFonts = 0;
Invalidate();
}
vo
idCEx07_ 1V iew
OnGdi Fot()
n //点击字体菜单
{ m_GdiShow = Fonts;
bPens = 0;
bBrushes = 0;
bFonts = 1;
Invalidate();
}
vo
idCEx07_ 1V iew
OnGdi Bi
tmap()//点击位图菜单
{ m_GdiShow = Bitmaps;
bPens = 0;
bBrushes = 0;
bFonts = 0;
bBitmaps = 1;
Invalidate();
}
//菜单更新消息函数
vo
idCEx07_ 1V iew
OnUpd at
eGdiPen(CCmdUI pCmdUI)//更新画笔菜单
{ pCmdUI- >SetCheck(bPens);
}
vo
idCEx07_ 1V iew
OnUpd at
eGdiBrush(CCmdUI pCmdUI)//更新画刷菜单
{ pCmdUI- >SetCheck(bBrushes);
}
vo
idCEx07_ 1V iew
OnUpd at
eGdiFont(CCmdUI pCmdUI)//更新字体菜单
{ pCmdUI- >SetCheck(bFonts);
}
vo
idCEx07_ 1V iew
OnUpd at
eGdiBitmap(CCmdUI pCmdUI)//更新位图菜单
{ pCmdUI- >SetCheck(bBitmaps);
}
(7)编译和运行
7
52 鼠标绘图编程实例
【例7 2】 Ex07_2 鼠标绘图的实现,具体说明见表 7 8。
(1)新建一单文档应用程序 Ex07_2
(2)编辑绘图工具栏并将工具栏载入应用程序界面
① 绘图工具栏按钮的 ID 号依次为:ID_MDRAW_LINE、ID_MDRAW_RECTANGLE、ID_MDRAW_
ELLIPSE;
192
第七章 绘 图
……………………………………………………………………………………………………………………………………………
项 目 图示和说明
程序界面
(1)点击菜单:鼠标绘图- >直线或工具栏的直线按钮,可在客户区绘制出直线
点击菜单:鼠标绘图- >矩形或工具栏的矩形按钮,可在客户区绘制出矩形
点击菜单:鼠标绘图- >椭圆或工具栏的椭圆按钮,可在客户区绘制出椭圆
程序功能
从键盘可以直接输入字符
(2)点击“保存”菜单或工具栏保存按钮,打开保存对话框,输入文件名可将绘制的图
形保存到磁盘
i
ntCMa inF rame OnCreate(LPCREATESTRUCTl pCrea
teStruct)
{...
//绘图工具栏载入
if (! m_DrawToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
|CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC)||
! m_DrawToolBar.LoadToolBar(IDR_DRAW_TOOLBAR))
{TRACE0("Failed to create toolbar\n" );
return-1;//创建失败
}
//绘图工具栏浮动
m_DrawToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_DrawToolBar);
return 0;
(3)添加和编写点击绘图工具栏按钮的消息函数
用 ClassWizard 添 加 3 个 工 具 栏 按 钮 消 息 函 数 和 3 个 更 新 消 息 函 数 (见 清
单 7 16 )。
193
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
07_
清单7 16 绘图工具栏按钮消息函数和更新消息函数(在 Ex 2Vi
ew.
cpp文件中)
v
oi 07_
dCEx 2View OnMd rawLine()//绘直线
{ m_DrawType = 1;//m_DrawType 表示绘图类型,在 Ex07_2View.h 文件中定义
}
v
oi 07_
dCEx 2View OnMd rawRectanl
g //绘矩形
e()
{ m_DrawType = 2;
v
oi 07_
dCEx 2View OnMd
rawE
lli
ps //绘椭圆
e()
{ m_DrawType = 3;
v
oi 07_
dCEx 2View OnMd
rawAn //任意
y()
{ m_DrawType = 4;
v
oi 07_
dCEx 2V i
ew OnUpd at
eMd rawL in e(CCmdUI pCmdUI)
{ pCmdUI- >SetCheck(m_DrawType = = 1);
}
v
oi 07_
dCEx 2V i
ew OnUpd at
eMd rawRe ctange(
l CCmdUI pCmdUI)
{ pCmdUI- >SetCheck(m_DrawType = = 2);
}
v
oi 07_
dCEx 2V i
ew OnUpd at
eMd rawE llipse(CCmdUI pCmdUI)
{ pCmdUI- >SetCheck(m_DrawType = = 3);
}
(4)鼠标操作与光标形状
① 在 CEx07_2View.h 中添加成员变量(见清单 7 17);
07_
清单7 17 添加成员变量(在 Ex 2Vi h文件中)
ew.
c
las
sCEx 07_2Viewpub li
cCS crol
lView
public:
//添加以下 5 个变量
UINT m_DrawType; //表示绘图类型
BOOL m_bMouseDown; //鼠标是否被按下
CPoint m_StartPt,m_OldPt; //m_StartPt 表示开始按下鼠标左键的位置,也即鼠标的初始位置。
//m_OldPt 用来表示鼠标移动过程中“上一次”的位置。
HCURSOR m_hCursor; //鼠标句柄类型,设置鼠标的形状。
//添加以下 2 个变量
CString m_strShow; //表示当前行字符串,
int m_nLine; //显示行数
...
}
② 在构造函数中初始化成员变量(见清单 7 18);
07_
清单7 18 构造函数(在 Ex 2Vi
ew.
cpp文件中)
07_
CEx 2View CEx07_2Vi ew ()
{ m_DrawType = 1;
m_bMouseDown = false; //鼠标未按下
m_hCursor = AfxGetApp()- >LoadStandardCursor(IDC_CROSS); //鼠标形状为十字形
m_nLine = 0; //手动添加
194 }
第七章 绘 图
……………………………………………………………………………………………………………………………………………
AfxGetApp()为全局函数用来得到指向主应用程序对象的指针 。
LoadStandardCursor()函数是 CWinApp 类的成员函数,其作用是取得鼠标形状句柄,其
函数原型为:
HCURSOR LoadStandardCursor(LPCTSTR lpszCursorName)const;
参数:lpszCursorName 是鼠标形状名称字符串,可取值见表 7 9。
表7 9 l
pszCu
rsrName可取值
o
lpszCursorName 取值 鼠标形状说明
IDC_ARROW 标准箭头形状
IDC_IBEAM I 形鼠标,用于标志文本编辑状态的鼠标形状
IDC_WAIT 用于标志程序等待状态的鼠标形状
IDC_CROSS 十字形
IDC_UPARROW 上箭头形
IDC_SIZEALL 四个方向都有箭头的十字形,用来标志移动
IDC_SIZENWSE 从左上到右下的双箭头
IDC_SIZENESW 从右上到左下的双箭头
IDC_SIZEWE 水平方向的双箭头
IDC_SIZENS 竖直方向的双箭头
vo
idCEx 07_2V iew OnLBut
tonDown(UINTnFlasCPo
g intp
oit)
n //鼠标左键按下
{ m_bMouseDown = true;
m_StartPt = point; //存放画线的起始位置
m_OldPt = point; //存放光标的当前位置
SetCapture(); //捕获鼠标
CRect rect; //定义一矩形类数据对象
GetClientRect(&rect); //获取视图窗口客户区的坐标
ClientToScreen(&rect); //转换客户区坐标为屏幕坐标
ClipCursor(&rect); //将光标限定在视图窗口客户区内
CScr
ollVi
ew OnLBu t
t nDown(
o nFla
gspo
int);
}
OnMouseMove()函数的实现代码见清单 7 20。
清单7 20 OnMo
useMo
v 07_
e()函数(在 Ex 2Vi
ew.
cpp文件中)
v
oidCEx07_2V i
ew OnMo us
eMoe(
v UINTnF
lasCPo
g intp
oit)
n //鼠标移动
{ SetCursor(m_hCursor);//设置鼠标形状为十字形
if(m_bMouseDown)
{ CClientDC dc(this);
switch(m_DrawType)
{ case 1:
195
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
dc.SetROP2(R2_NOT);
//以下两行代码擦除从起点(鼠标按下点)到上次鼠标移动到的位置之间的临时线
dc.MoveTo(m_StartPt);
dc.LineTo(m_OldPt);
//以下两行代码从起点到鼠标当前位置画线
dc.MoveTo(m_StartPt);
dc.LineTo(point);
break;
case 2:
dc.SetROP2(R2_NOTXORPEN);
dc.Rectangle(m_StartPt.x,m_StartPt.y,m_OldPt.x,m_OldPt.y);
dc.Rectangle(m_StartPt.x,m_StartPt.y,point.x,point.y);
break;
case 3:
dc.SetROP2(R2_NOTXORPEN);
dc.Ellipse(m_StartPt.x,m_StartPt.y,m_OldPt.x,m_OldPt.y);
dc.Ellipse(m_StartPt.x,m_StartPt.y,point.x,point.y);
break;
m_OldPt = point;//鼠标当前位置为下一次移动的旧位置
}
CScrollView::OnMouseMove(nFlags, point);
}
nDrawMode 取值 含 义
R2_BLACK 像素点颜色总是黑色
R2_WHITE 像素点颜色总是白色
R2_NOP 像素点颜色不改变
R2_NOT 像素点颜色为屏幕颜色的反色
R2_COPYPEN 像素点颜色为画笔颜色
R2_NOTCOPYPEN 像素点颜色为画笔颜色的反色
R2_MERGEPENNOT 像素点颜色为画笔颜色和屏幕颜色反色的或
R2_MASKPENNOT 像素点颜色为画笔颜色和屏幕颜色反色的与
R2_MERGENOTPEN 像素点颜色为画笔颜色反色和屏幕颜色的或
R2_MASKNOTPEN 像素点颜色为画笔颜色反色和屏幕颜色的与
R2_MERGEPEN 像素点颜色为画笔颜色和屏幕颜色的或的反色
R2_NOTMERGEPEN 像素点颜色为画笔颜色和屏幕颜色的或
R2_MASKPEN 像素点颜色为画笔颜色和屏幕颜色的与
R2_NOTMASKPEN 像素点颜色为画笔颜色和屏幕颜色的与的反色
R2_XORPEN 像素点颜色为画笔颜色和屏幕颜色的异或
R2_NOTXORPEN 像素点颜色为画笔颜色和屏幕颜色的异或的反色
OnLButtonUp()函数的实现代码见清单 7 21。
196
第七章 绘 图
……………………………………………………………………………………………………………………………………………
清单7 21 OnLBu
tto 07_
nUp()函数(在 Ex 2Vi
ew.
cpp文件中)
v
oidCEx07_2View OnLBu ttonUp(UINTnF lasCPo
g intp oi
nt)//释放鼠标
{ if(m_bMouseDown)
{
m_bMouseDown = false;
ReleaseCapture();//释放鼠标捕获并恢复正常输入
ClipCursor(NULL);//使光标可以移动到屏幕任意位置
CClientDC dc(this);
switch(m_DrawType)
{
case 1:
dc.SetROP2(R2_NOT);//设置绘图模式,以屏幕反转色为绘图颜色,即擦除“旧线”
dc.MoveTo(m_StartPt);
dc.LineTo(m_OldPt);
dc.SetROP2(R2_COPYPEN);//设置绘图模式,以当前画笔绘图,即绘“新线”
dc.MoveTo(m_StartPt);
dc.LineTo(point);
break;
case 2:
dc.SetROP2(R2_NOT);
dc.Rectangle(m_StartPt.x,m_StartPt.y,m_OldPt.x,m_OldPt.y);
dc.SetROP2(R2_COPYPEN);
dc.Rectangle(m_StartPt.x,m_StartPt.y,point.x,point.y);
break;
case 3:
dc.SetROP2(R2_NOT);
dc.Ellipse(m_StartPt.x,m_StartPt.y,m_OldPt.x,m_OldPt.y);
dc.SetROP2(R2_COPYPEN);
dc.Ellipse(m_StartPt.x,m_StartPt.y,point.x,point.y);
break;
//以下 4 行程序在第(8)步添加,其作用为把信息添加到文档类数据成员 m_LineList 中
CEx07_2Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CLine* pLine = new CLine(m_StartPt,point,m_DrawType);
pDoc- >m_LineList.AddTail((void*)pLine);
}
CScrollView::OnLButtonUp(nFlags, point);
}
(5)在视类中响应键盘输入与显示字符
用户可以用键盘输入字符并 在 视 图 窗 口 内 显 示 出 来,最 先 输 入 的 字 符 显 示 在 视 图
窗口最上面一行,按 Enter 键则换行输入,每行最多容纳 64 个字符,通过以下步骤完成 。
① 在 Ex07_2View.h 中添加 2 个成员变量 m_strShow 和 m_nLine(见清单 7 14)
② 在构造函数中初始化成员变量 m_nline(见清单 7 15)
③ 在 CEx07_2View 类中添加 WM_CHER 键盘消息函数
所添加的键盘消息函数名为:OnChar();具体代码见清单 7 22。
07_
清单7 22 键盘消息处理(在 Ex 2Vi
ew.
cpp文件中) 197
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
v
oidCEx07_2V i
ew OnCh ar(UINTnCharUINTnRe
pCn
tUINTnF
las)
g
{ //回车
if(nChar = = VK_RETURN)
{ m_strShow.Empty();//置空
m_nLine ++ ;//表示以后输入的字符应该在下一行
}
else if(m_strShow.GetLength()
<64)
{ m_strShow+= nChar;//将按下的键值赋给变量 m_strShow
CClientDC dc(this);
TEXTMETRIC tm;
dc.GetTextMetrics(&tm);
int nLineHeight = tm.tmHeight+tm.tmExternalLeading;//计算每一行字的高度
dc.TextOut(0,m_nLine* nLineHeight,m_strShow);//输出字符
CScrollView::OnChar(nChar, nRepCnt, nFlags);
}
图7 4 新建 CL
ine类
清单7 23 L
in h文件
e.
c
lassCLinepub licCOb jec
t
DECLARE_SERIAL(CLine)
public:
CPoint m_StartPt,m_EndPt;//分别表示直线的起点和终点
public:
CLine();
CLine(int x1,int y1,int x2,int y2,int type);
CLine(CPoint pt1,CPoint pt2,int type);
virtual ~CLine();
void Draw(CDC* pDC);//绘图函数
virtual void Serialize(CArchive &ar);//系列化函数
};
v
oidCLine
Seria
lie(
z CAr ch
ive&a //序列化函数
r)
{ if(ar.IsStoring())
ar<<m_StartPt<<m_EndPt<<m_nGraphType;
else
ar>>m_StartPt>>m_EndPt>>m_nGraphType;
(7)在文档类中处理数据实现数据的磁盘操作
① 在 Ex07_2Doc.h 中声明成员变量和成员函数(见清单 7 25)
07_
清单7 25 声明成员变量和成员函数(在 Ex 2Do
c.h文件中)
#incl
ude"L i
ne.h"
c
lassCEx 07_2Docpub li
cCDo cume
nt
public:
CPtrList m_LineList;//直线链表
CStringList m_strList;//保存除最后一行文字之外的各行文字
CString m_strLastLine;//最后一行
void ReInit();//重新初始化文档数据变量
void DrawLine(CDC*pDC);
void DrawText(CDC*pDC);
...}
② 编写 ReInit()函数(见清单 7 26)
清单7 26 Re
Ini 07_
t()函数(在 Ex 2Do
c.pp文件中)
c
v
oidCEx 07_2Do c ReInit()
{ CLine * pLine;
POSITION pos = m_LineList.GetHeadPosition();
for(;pos!= NULL;m_LineList.GetNext(pos))
{ pLine = (CLine* )m_LineList.GetAt(pos);
delete pLine;
m_LineList.RemoveAll();
m_strList.RemoveAll();
m_strLastLine.Empty();
}
新建的文档应该是空的,它的数据内容不应该受到前一文档的干扰和影响,为此新建文
档要初始化。
④ 程序结束时数据空间的释放,在析构函数中进行(见清单 7 28)
07_
清单7 28 析构函数(在 Ex 2Do
c.pp文件中)
c
07_
CEx 2Do 07_
c~CEx c()
2Do
{ ReInit();
}
⑤ 编写 serialize()函数,实现数据的磁盘读写(见清单 7 29)
清单7 29 S
eri
ali
z 07_
e()函数(在 Ex 2Do
c.pp文件中)
c
v
oidCEx07_2Do cSerial
ize( CArchiv
e&a r)
{
if (ar.IsStoring())
{ UINT nCount;
//保存直线
nCount = m_LineList.GetCount();
ar<<nCount;
CLine * pLine;
POSITION pos = m_LineList.GetHeadPosition();
for(; pos!= NULL; m_LineList.GetNext(pos))
{ pLine = (CLine*)m_LineList.GetAt(pos);
pLine- >Serialize(ar);
}
//保存文本
m_strList.Serialize(ar);//从 CObject 派生的链表类支持链表中各元素的序列化
ar<<m_strLastLine;
else
UINT nCount;
ar>>nCount;
while(nCount - - )
{ CLine * pLine = new CLine;
pLine- >Serialize(ar);
m_LineList.AddTail(pLine);
}
m_strList.Serialize(ar);
ar>>m_strLastLine;
数据的磁盘读写有关内容见第 8 章的 83 节。
⑥ 重绘图形和文字(见清单 7 30)
清单7 30 Dr
awL
ine()和 Dr
awTe
x 07_
t()函数(在 Ex 2Do
c.pp文件中)
c
v
oidCEx 07_ 2DocDrawL
ine( //重绘图形
CDCpDC)
{ //取得需要重绘的区域
CRect rectClip; 201
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
pDC- >GetClipBox(&rectClip);
CLine * pLine;
POSITION pos = m_LineList.GetHeadPosition();
for(;pos!= NULL;m_LineList.GetNext(pos))
{ pLine = (CLine* )m_LineList.GetAt(pos);
if(rectClip.PtInRect(pLine- >m_StartPt)||rectClip.PtInRect(pLine- >m_EndPt))
{ pLine- >Draw(pDC);
}
}
}
v
oidCEx07_2Do cDrawTe xt(CDCpDC) //重绘文字
{ CRect rectClip,rect;
pDC- >GetClipBox(&rectClip);//取得需要重绘的区域
CSize size;
TEXTMETRIC tm;
pDC- >GetTextMetrics(&tm);
int nLineHeight = tm.tmHeight+tm.tmExternalLeading;
CString str;
int line = 0;
POSITION pos = m_strList.GetHeadPosition();
for(;pos!= NULL;m_strList.GetNext(pos))
{ str = m_strList.GetAt(pos);
size = pDC- >GetTextExtent(str);
rect.SetRect(0,line* nLineHeight,size.cx,line* nLineHeight+size.cy);
if(!(rect&rectClip).IsRectEmpty())pDC- >TextOut(0,line* nLineHeight,str);
line ++ ;
str = m_strLastLine;
size = pDC- >GetTextExtent(str);
rect.SetRect(0,line* nLineHeight,size.cx,line* nLineHeight+size.cy);
if(!(rect&rectClip).IsRectEmpty())
pDC- >TextOut(0,line* nLineHeight,str);
}
v
oidCEx07_ 2Vi
ew OnDr aw(CDC pDC)
{ CEx07_2Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDoc- >DrawLine(pDC);
pDoc- >DrawText(pDC);
}
(10)编译和运行
7
53 动态绘图编程实例
202 【例7 3】 Ex07_3 动态绘图的实现,具体说明见表 7 10。
第七章 绘 图
……………………………………………………………………………………………………………………………………………
表7 10 例7 3具体说明
项 目 图示和说明
图(
b) 设置菜单
程序界面
图(
a) 主界面 图(
c) 动态菜单 图(
d) 颜色对话框
(1)点击菜单:设置- >背景颜色,可弹出图(d)所示的颜色对话框,选择颜色,然后点击确定,
可设置主界面的背景颜色
(2)点击菜单:设置- >画笔颜色,可弹出图(d)所示的颜色对话框,选择颜色,然后点击确定,
可设置画笔颜色
程序功能
(3)点击菜单:动态→动态绘正弦曲线,动态绘制正弦曲线
(4)点击菜单:动态→开始绘余弦曲线,动态绘制余弦曲线
点击菜单:动态→停止余弦曲线绘制,即停止余弦曲线绘制;如果又点击开始绘余弦曲
线,程序将继续绘制余弦曲线
(1)新建一单文档应用程序 Ex07_3
(2)编辑菜单和添加消息函数(见表 7 11)
表7 11 菜单消息函数
菜 单 名 ID 号 消 息 消 息 函 数
背景颜色... ID_SETUP_BKCOLOR COMMAND OnSetupBkcolor()
画笔颜色... ID_SETUP_PENCOLOR COMMAND OnSetupPencolor()
动态绘正弦曲线 ID_ADRAW_SIN COMMAND OnAdrawSin()
开始绘余弦曲线 ID_ADRAW_COS COMMAND OnAdrawCos()
停止余弦曲线绘制 ID_ADRAW_STOP COMMAND OnAdrawStop()
(3)背景颜色的设置与显示
① 定义变量 m_BKColor (见清单 7 32)
07_
清单7 32 定义变量(在 Ex 3Vi h文件中)
ew.
c
las
sCEx 07_3Viewpub licCView
public:
COLORREF m_BKColor;//背景颜色
COLORREF m_PenColor;//画笔颜色,在(4)①标题处要求定义
CPen newPen,*pOldPen;//定义画笔对象,在(5)①标题处要求定义
void ShowBKColor();//显示背景颜色,在(4)④标题处要求定义
}
203
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
07_
CEx 3View CEx 07_3V ew()
i
{ m_BKColor = RGB(255,255,255);//背景颜色初始为白色
m_PenColor = RGB(0,0,0);//画笔颜色初始为黑色,在(4)②标题处要求赋初值
}
③ 编写背景颜色设置菜单的消息函数(见菜单 7 34)
清单7 34 OnS
etupBk
col
o 07_
r()函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx07_3View OnSetupBk colr()
o //单击“背景颜色...
”菜单
{ CColorDialog colordlg;
if(colordlg.DoModal()= = IDOK)
{ m_BKColor = colordlg.GetColor();
}
Invalidate();
}
v
oidCEx07_3V i
ew ShowBKCo lo //显示背景颜色
r()
{ CClientDC dc(this);
CRect rect;
GetClientRect(&rect);
CBrush newBrush,*pOldBrush;
newBrush.CreateSolidBrush(m_BKColor);
pOldBrush = dc.SelectObject(&newBrush);//保存原画笔,选中新画笔
dc.Rectangle(rect);
dc.SelectObject(pOldBrush);//恢复原画笔
}
⑤ 重载 OnDraw()函数(见清单 7 36)
清单7 36 OnDr 07_
aw()函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx07_3View OnDraw(CDC pDC)
{ CEx07_3Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
ShowBKColor();//显示背景颜色
}
(4)画笔颜色的设置
① 定义变量 m_PenColor (见清单 7 32)
② 初始化变量 m_PenColor (见清单 7 33)
③ 编写背景颜色设置菜单的消息函数(见菜单 7 37)
204
第七章 绘 图
……………………………………………………………………………………………………………………………………………
清单7 37 OnS
etupPe
nco
lo 07_
r()函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx07_3View OnSetupPen col
or()//单击“背景颜色...
”菜单
{ CColorDialog colordlg;
if(colordlg.DoModal()= = IDOK)
{ m_PenColor = colordlg.GetColor();
}
}
(5)动态绘制正弦曲线
其绘制动画的实现方法是:绘一段直线,用 Sleep()函数停止一下,再继续绘制。
① 定义变量 newPen、*pOldPen(见清单 7 32)
② 编写 OnAdrawSin()函数(见清单 7 38)
清单7 38 OnAd
rawS
i 07_
n()函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx07_ 3V i
ew OnAd rawSi //单击“动态绘正弦曲线”菜单
n()
{ CClientDC dc(this);
newPen.CreatePen(PS_SOLID,3,m_PenColor);//创建新画笔
pOldPen = dc.SelectObject(&newPen);//选择新画笔,保存老画笔
dc.MoveTo(100,100);//移动到点(100,100)处
//动态绘直线
for(int i = 0;i<100;i ++ )
{ Sleep(100);
dc.LineTo(100+i,100);
}
dc.MoveTo(100,100);//移动到点(100,100)处
for(i = 0;i<100;i ++ )//动态绘正弦曲线
{ Sleep(100);
dc.LineTo(100+ i*1,(int)(100+ 50*sin(20*628*i)));
}
dc.SelectObject(pOldPen);//还原老画笔
}
(6)动态绘制余弦曲线
其绘制动画的实现方法是:利用 SetTimer()、OnTimer()函数。
① 编写 OnAdrawCos()函数(见清单 7 39)
清单7 39 OnAd
rawCo 07_
s()函数(在 Ex 3Vi
ew.
cpp文件中)
v
oi 07_
dCEx 3View OnAd r
awCo s()//单击“开始绘余弦曲线”菜单
{ SetTimer(1,50,0);//设置定时器
} newPen.CreatePen(PS_SOLID,3,m_PenColor);
v
oi 07_
dCEx 3View OnTimer(
UINTn
IDEv
ent)
{ CClientDC dc(this);
205
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
pOldPen = dc.SelectObject(&newPen);
static int i = 0;//设置静态变量
//动态绘水平直线
dc.MoveTo(100+i,300);
dc.LineTo(101+i,300);
//动态绘余弦曲线
dc.MoveTo(100+i,(int)(300+ 50*cos(20*628*(i))));
dc.LineTo(101+i,(int)(300+ 50*cos(20*628*(i+1))));
++i;
dc.SelectObject(pOldPen);
CView::OnTimer(nIDEvent);
}
(7)停止余弦曲线绘制(见清单 7 41)
清单7 41 OnAd
rawS
to 07_
p()函数(在 Ex 3Vi
ew.
cpp文件中)
v
oi 07_
dCEx 3View OnAd
rawS
to //单击“停止余弦曲线绘制”菜单
p()
{ KillTimer(1);//停止定时器
}
(8)编译和运行
206
第八章 文 件 操 作
在任何一个应用程序中都不希望其文档数据随着应用程序的关闭而全部消失 。 文档数
据的永久保存有两类方法,其一是保存在数据库中,其中的数据库可以随机存取。 其二是保
存为应用程序直接读写的磁盘文件 。本章将介绍采用第二类方法如何将文档数据存入磁盘
的永久性介质中,以及如何从磁盘文件中读取数据 。
CFile 是所有文件类的基类,它提供无缓冲二进制文件的输入输出服务。 CArchive 类
用来存储二进制字节流,对文件 I/O 提供缓冲,它广泛用于对象的序列化,它还提供了一些
函数使得对文件的操作更加容易 。
本章要点:
* 文件的读与写
* 文档数据的保存与打开
8
1 CF
ile类
在 MFC 中,使用 CFile 类来处理正常的文件 I/O 操作。 CFile 类直接提供了无缓冲、二
进制磁盘输入输出服务,并且它还通过它的派生类间接地支持文本文件和内存文件的操作 。
CFile 类经常和 CArchive 类共同来实现 MFC 对象的序列化支持。
CFile 类用于打开与关闭文件、读写文件、在文件中定位和管理文件。 其主要函数见表
8 1。
表8 1 CF
ile类主要函数
函 数 说 明
Constructor() 创建 CFile 对象。如果传递的是一个文件名,就打开该文件
Denstructor() 释放超出范围的 CFile 对象。如果文件已打开,则关闭它
Abort() 立即关闭文件而不管是否出错
Close() 关闭文件
Duplicate() 创建一个可复制的文件对象
Flush() 清除流中数据
GetFilename() 获取文件名
GetFilePath() 获取文件的全路径
GetFileTitle() 获取文件标题(不带扩展名的文件名)
GetLength() 获取文件长度
GetPosition() 获取文件当前的位置
GetStatus() 获取文件的状态
207
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
续 表
函 数 说 明
LockRange() 锁定文件的一部分
Open() 打开文件
Read() 从文件中读取数据
Remove() 删除文件
Rename() 重命名文件
Seek() 设置文件中位置
SeekToBegin() 设置至文件头的位置
SeekToEnd() 设置至文件尾的位置
SetFilePath() 设置文件路径
SetLength() 设置文件长度
SetStatus() 设置文件状态
UnlockRange() 对文件的一部分进行解锁
Write() 向文件中写数据
8
11 打开文件
可以直接通过 CFile 的构造函数来打开磁盘文件,同时可以用标志位指定打开方式 (只
读、只写、读写等)。
参数lpszFileName:指定文件名称;
nOpenFlag:文件状态标志,定制文件打开方式的标志符;一次可以使用多个状态标
志,可以简单地将它们“或”在一起。这些状态标志描述了如何打开文件、何种操作
是有效的,其取值和含义见表 8 2。
表8 2 nOp
enF
lag(文件状态标志)
标 记 说 明
CFile::modeCreate 创建新的文件或把已存在的文件长度截为 0
CFile::modeNoInherit 不允许子过程继承
CFile::modeNoTruncate 创建文件时,如果文件已经存在则不对文件截断
CFile:: modeRead 只允许读操作
CFile:: modeReadWrite 允许读写操作
CFile:: modeWrite 只允许写操作
CFile::shareCompat 允许其他过程打开文件
CFile:: shareDenyNone 允许其他过程对文件进行读写
CFile:: shareDenyRead 不允许其他过程对文件进行读操作
CFile:: shareDenyWrite 不允许其他过程对文件进行写操作
CFile:: shareExclusive 不允许其他过程访问
CFile::typeBinary 设置文件为二进制模式
CFile:: typeText 设置文件为文本模式
式打开当前目录下的 readme.txt;
也可采用先构造一 CFile 类对象,然后用 Open 函数打开,Open 函数原型如下:
8
12 关闭文件
函数 CFile::Close()关闭一个文件,一般一个文件使用完毕就应该关闭它。 当文件对
象被销毁时,析构函数会自动关闭文件;但通过 new 在堆中分配一个文件对象时,在 delete
这个对象之前必须关闭文件。
8
13 文件读写
可以用 CFile::Read()和 CFile:Write()对文件进行顺序读写,它们都从文件当前位置
开始,文件刚刚打开时,当前位置为文件头,文件当前位置随文件读写的字节数而移动。 函
数 Read()和 Write()的声明如下:
Read 函数原型:virtual UINT Read(void* lpBuf,UINT nCount);
参数:lpBuf:指向缓冲区的指针
nCount:要读取文件的字节数
Write 函数原型:virtual void Write(const void* lpBuf,UINT nCount);
参数:含义同上
例:对于一个已经打开的文件对象 file,下面的语句从文件中读 100 个字节存入字数组
pbuf 中(nBytesRead 是实际读到的字节数):
8
14 文件定位
函数 Read()和 Write()只能按顺序读写文件,如果想对文件数据进行随机存取,要用到
文件定位。通用的文件定位函数是 CFile::Seek(),它的作用是把文件位置指针相对于一个 209
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
基准移动一个偏量(即改变文件的当前位置)。
Seek 函数原型:virtual LONG Seek (LONG lOff,UINT nForm);
参数 nForm:指定移动的模式,可以在 CFile::begin,CFile::current 和 CFile::end 之
间取值,分别表示从文件的开始位置,指针的当前位置,以及文件的末尾位置开始移动指针 。
lOff:偏移量,其值可正可负,负值表示向前移;
如果指定的位置是合法的,函数将返回当前文件指针距离文件开头的字节偏移量;否则
将触发一个 CFileException 异常。另外,如果一个文件已经被打开,文件指针被定位于文
件的开始位置(偏移量为 0)。
例:下面的语句把文件位置指针相对于开头向后移动 1 000 个字节,而不论文件当前位
置在哪儿。
2 文件流f
8 str
eam
文件 流 允 许 对 文 件 或 具 有 文 件 行 为 的 外 部 设 备 进 行 输 入 或 输 出,如 果 采 用 文 件 流
fstream 处理 文 件 I/O, 程 序 中 必 须 包 含 fstream. h 文 件。 它 定 义 的 类 包 括 ifstream、
ofstream 和 fstream。流分为三类:输入、输出和输入/输出。 要创建一个输入流,必须说明
它为 ifstream;要创建一个输出流,必须说明它为类 ofstream;执行输入和输出操作的流必
须说明为 fstream。
8
21 打开文件
文件打开可以先构造一个流,然后用 open()函数打开文件,open()函数原型为:
void open(const char* szName, int nMode, int nProt = filebuf::openprot);
参数:szName 为文件名,它可以包含路径说明符;
nMode 决定文件的打开方式,取值见表 8 3;
nProt 决定存取文件的方式,缺省值为 filebuf::openprot。
表8 3 nMo
de的取值
nMode 取值 含 义
Ios::app 把文件的输出添加在文件尾,它只能用于输出文件
Ios::ate 文件打开时定位于文件尾
Ios::in 文件有输入能力
Ios::out 文件有输出能力
Ios::nocreate 导致函数 open()在文件不存在时失败
Ios::noreplace 导致函数在文件存在时失败
Ios::trun 导致已存在的同名文件的内容被破坏且长度被截断为 0
210 例:打开一个供输入/输出的流
第八章 文 件 操 作
……………………………………………………………………………………………………………………………………………
fstream mystream;
mystream.open(“readme.txt”,ios::in|ios::out);
如果 open()失败,则 mystream 为 0。
虽然使用 open ()函 数 打 开 一 个 文 件 完 全 合 适,但 在 大 多 数 情 况 下,由 于 ifstream、
ofstream、fstream 类包含自动打开文件的构造函数,所以还可用下面的方式打开文件 。
例:ifstream mystream("myfile ");
8
22 关闭文件
要关闭 一 个 文 件, 使 用 成 员 函 数 close (), 函 数 close ()不 带 任 何 参 数。 例 如 用
mystream.close()的语句关闭关于流 mystream 的文件。
8
23 编程实例
【例8 1】 Ex08_1 编写一程序,利用 CFile 类实现文件的读操作,利用文件流 fStream
类实现文件的写操作,具体说明见表 8 4。
表8 4 例8 1具体说明
项 目 图示和说明
程序界面
图(
b) “另存为”对话框
图(
a) 程序主界面
图(
c) 消息对话框 图(
d) 消息对话框
(1)新建一基于对话框的应用程序 Ex08_1
(2)添加控件、成员变量和消息函数(见表 8 5)
表8 5 控件ID 号、成员变量和消息函数 211
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(3)写入文件
① 定义变量(见清单 8 1)
08_
清单8 1 定义变量(在 Ex 1Vi h文件中)
ew.
c
las
sCEx 08_1DlgpublicCD ialo
g
public:
CString strFileName;//文件名
CString strFilePath;//文件路径
BOOL IsTextFile(CString& rFile);//判断文件类型是否正确(5)①标题处要求定义
}
② 编写下压按钮“写入文件”的消息处理函数(见清单 8 2)
清单8 2 OnBu
tto
nWr
it 08_
e()函数(在 Ex 1Dl
g.pp文件中)
c
v
oidCEx 08_ 1DlgOnBu ttonWr i
te()//单击"写入到文件…"按钮
{ UpdateData(true);
CFile file;
CfileDialog filedlg(0,//1-文件打开, 0-文件另存为
".txt|* .* " ,
NULL,
OFN_OVERWRITEPROMPT,
" 文本文件(*.txt)|*.txt|All Files (*.* )|* .* ||",
NULL);
if(filedlg.DoModal()= = IDOK)
strFileName = filedlg.GetFileName();//获得文件名
if(strFileName = ="" )
{ AfxMessageBox("请输入文件名!");
return;
file.Open(strFileName,CFile::modeCreate|CFile::modeWrite);//将数据写入文件
int length = m_strWrite.GetLength();//获取文件长度
212
第八章 文 件 操 作
……………………………………………………………………………………………………………………………………………
f
il
e.Write((LPCTSTR) m_s
trWri
te
length);//获取有关文件的信息 CS
tri
ng
AfxMessageBox(已保存到文件:+s t
rFil
eName+ //保存结束提示
);
s
trFi
lePath=f il
e.Ge
tFi
lePa
t //获得文件的路径
h();
f
il
e.Cloe();
s //关闭文件
}
(4)查看文件路径
编写下压按钮“查看文件路径”的消息函数(见清单 8 3)
清单8 3 OnBu
tto
nFi
lea
pt 08_
h()函数(在 Ex 1Dl
g.pp文件中)
c
v
oidCEx 08_1D l
g OnButtonFi
lepat //单击“查看文件路径”按钮
h()
{ m_strFilePath = strFilePath;
UpdateData(false);
}
(5)读入文件
① 定义判断文件类型是否正确的函数(见清单 8 1)
② 编写 IsTextFile 函数(见清单 8 4)
清单8 4 I
sTe
xtF
il 08_
e()函数(在 Ex 1Dl
g.pp文件中)
c
BOOLCEx 08_1D l
gIsText
Fie(
l CS
tring &rFile)//判断文件类型
{ CStringList strList;
CString str(rFile);
strList.AddHead(".TXT" );
strList.AddHead(".SYS" );
strList.AddHead(".BAT" );
strList.AddHead(".DAT" );
str = str.Right(4);//文件名的右边四位字符
str.MakeUpper();//转换成大写
return (strList.Find(str))? TRUE:FALSE;
{ UpdateData(t
rue);
s
trFi
leName=f il
edlg.GetFi
leName();//获得文件名
i(!
f I sTe xtFi
l (
estrFi
leName ))//判断文件类型是否正确
{ Af xMe ssa
geBox("文件类型不正确!");
return
f
1.open(strFi
leNamei os
in|i
osnocr
eae);
t
whi
l (
e f! 1.eof())
{ f 1.getli
ne(s255);
ms_trRead=m_ strRe
ad+"\ r\n" +s//添加文件中的文本到编辑框
UpdateData(f
alse);
}
AfxMe ssa
geBox( s
trFi
leName+ " 文件读入完毕!");//保存结束提示
f1.close();//关闭文件流
}
}
(6)编译和运行
8
3 CAr
chve 类与序列化
i
CArchive(档案)类与C++的 iostream 类有些类似,不过 CArchive 类用来存储二进制字
节流,而 iostream 类只用于格式化的文本。
序列化(Serialize)是指将对象写入永久性存储媒体 (如磁盘文件 )或从其中读取对象
的过程。Serialize 成员函数并不直接使用 CFile 对象,而是通过 CArchive 类 的对象与
CFile 对象交互。MFC 将 CArchive 类的对象用作被序列化的对象和存储媒体之间的中介物。
如果使用序列化功能,首先要进行两项工作:创建可序列化的类和序列化对象 。
8
31 创建可序列化的类
(1)从 Object 类派生
一个类支持序列化是指此类能够用 Serialize()函数实现对类对象的流式存取。 要让
一个类支持序列化,它必须是 CObject 类或其派生类。
(2)使用 SERIAL 宏
为了支持序列化,在类的头文件说明中必须包含 DECLAR_SERIAL 宏调用。 在类的执行
文件(cpp 文件)中必须包含 IMPLEMENT_SERIAL 宏调用。这两个宏的格式为:
DECLAR_SERIAL(class_name)
IMPLEMENT_SERIAL(class_name,base_class_name)
其中 class_name 是要序列化的类名,base_class_name 是其基类的名字,注意类名不带
双引号。
(3)重载 Serialize 成员函数
(4)在类中定义不带参数的构造函数
从磁盘上加载序列化对象后,MFC 重新创建这些对象时,需要使用一个默认的不带参数
的构造函数。如果没有在使用 SERIAL 宏的类中定义不带参数的构造函数,编译器发出没有
214 可用的默认构造函数的警告。
第八章 文 件 操 作
……………………………………………………………………………………………………………………………………………
8
32 序列化对象
拥有了序列化的类后,就可以通过 CArchive 对象将该类的对象序列化到文件中或者从
文件序列化该类的对象。
(1)创建 CArchive 对象
创建 CArchive 对象,首先构造 CFile 对象,然后将 CFile 对象传递到 CArchive 类的构
造函数中。
例:构造一 CArchive 类的对象。
CFile MyFile;
MyFile.Open(...,CFile::ModeWrite);
CArchive MyArchive(&MyFile,Carchive::store);
可以采用下面的代码关闭 Carchive 对象。
MyArchive.Close();
MyFile.Close();
(2)使用运算符
CArchive 类提供了“ << ”和“
>> ”两个运算符来实现简单的串行化操作,“ << ”运算符用于
向文件中写入数据,“ >> ”用于向文件中读取数据。 MFC 类库将 CArchive 对象作为运算符的
第一操作数,第二操作数见表 8 6。
表8 6 CAr
chi
ve类的“
<<”和“>>”运算符支持的数据类型
例:运算符的应用
CArchive ar;
int myint;
BYTE myByte;
RECT myRect;
ar << myint << myByte << myRect;
8
33 CA
rch
ive类的数据成员和成员函数
CArchive 类的数据成员和成员函数见表 8 7。
表8 7 CAr
chi
ve类的主要数据成员和成员函数
数据成员和成员函数 作 用
m_pDocument 指向被序列化的 CDocument 对象的指针
Abort() 不发送异常的情况下直接关闭文档
Close() 关闭文档
215
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
续 表
数据成员和成员函数 作 用
Flush() 将等待的数据写入数据流
Read/Write() 读取/写入字节数据
Read/WriteString() 读取/写入一行文本字符
GetFile() 获取文档对象的 CFile 对象的指针
Get/SetObjectSchema() 读取/设置对象的版本号
IsBufferEmpty() 确定在套接字接受过程中是否清空缓冲区
IsLoading() 确定是否载入文档
IsStoring() 确定是否存储文档
8
34 Se
ria
li
ze函数串行化处理数据
文档数据的串行化是由文档类的成员函数 Serialize 处理的,在 AppWizard 创建的基本
框架中在文档类的* Doc.cpp(如 Ex07_2Doc.cpp)文件中已经定义了清单 8 6 的 Serialize
的基本结构。
清单8 6 S
eri
ali
ze()函数
v
oidCEx07_ 2DocSer
ial
ize(CAr chiv r)
e&a
{ if (ar.IsStoring())
//TODO:add storing code here
{
else
//TODO:add loading code here
v
oid 类名:: Seri
alie(
z CAr
chi
ve&a r)
{ 基类名::Serialize(CArchive& ar)
if (ar.IsStoring())
{ //TODO:add storing code here
ar<<成员变量 1<<成员变量 2...;//存
}
else
216
第八章 文 件 操 作
……………………………………………………………………………………………………………………………………………
其 中, 基 类 是 指 执 行 序 列 化 处 理 的 派 生 类 的 基 类, 但 当 基 类 是 CObject 或 者 是
CDocument 类时,因为其 Serialize 函数不执行任何操作,所以通常不予以调用。
(2)当要序列化的类的成员变量是非标准的对象时,其 Serialize 函数按清单 8 8
编写。
清单8 8 S
eri
ali
ze()函数中序列化非标准类的对象
v
oid 类名:: Seri
alie(
z CAr
chi
ve&a r)
{ 基类名::Serialize(CArchive& ar)
if (ar.IsStoring())
{ //TODO:add storing code here
ar<<成员变量 1<<成员变量 2...;//存
}
else
//TODO:add loading code here
ar>>成员变量 1>>成员变量 2...;//取
}
非标准类对象.Serialize(ar);//非标准类对象的序列化
}
v
oid 类名:: Seri
alie(
z CAr
chi
ve&a r)
{ 基类名::Serialize(CArchive& ar)
if (ar.IsStoring())
{ //TODO:add storing code here
ar<<成员变量 1<<成员变量 2...;//存
}
else
//TODO:add loading code here
非标准类对象的指针= new 非标准类;
ar>>成员变量 1>>成员变量 2...;//取
}
//类中包含的不是非标准类的对象,而是其指针。因此必须在 CArchive 处理读写文档时,首先定义新
//的非标准类的对象,然后调用新建对象的 Serialize 成员函数。
//非标准类对象的指针- >Serialize(ar);
}
//另外,对于这种情况下的 Serialize 函数还可以用下面的简便写法
v
oid 类名:: Seri
alie(
z CAr
chi
ve&a r)
{ 基类名::Serialize(CArchive& ar)
if (ar.IsStoring())
217
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
vo
id 类名:: Seri
alie(
z CArch
ive&a r)
{ 基类名::Serialize(CArchive& ar)
if (ar.IsStoring())
{ //TODO:add storing code here
ar<<成员变量 1<<成员变量 2...;//存
}
else
//TODO:add loading code here
ar>>成员变量 1>>成员变量 2...;//取
}
集合类的对象.Serialize(ar);//集合类对象的序列化
}
//说明:其中集合类对象的 Serialize 函数依次调用该对象中每个类 2 对象的 Serialize 函数,完成类 2
//对象组的存取。相应地,需在类 2 中定义其 Serialize 函数。
vo
id 类2名:: Seria
lie(
z CArch
ive&a r)
{ if (ar.IsStoring())
{ //TODO:add storing code here
ar<<类 2 成员变量 1<<类 2 成员变量 2...;//存
}
else
//TODO:add loading code here
ar>>类 2 成员变量 1>>类 2 成员变量 2...;//取
}
}
218
第九章 打 印
1 MFC 的基本打印和打印预览
9
利用 V C++实现打印和打印预览功能时,有时甚至不需要加入任何代码,下面以一个例
题来说明 V C++所带的打印功能。
9
11 缺省打印实例
【例9 1】 Ex09_1 建立一单文档应用程序,实现打印和打印预览文本与图形文件。 具
体说明见表 9 1。
表9 1 例9 1具体说明
项目 图示和说明
程序
界面
图(
a) 主界面 图(
b) 预览
程序 (1)启动程序,其界面见图(a)
功能 (2)点击菜单:文件- >打印预览,其界面见图(b)
219
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(1)新建一单文档应用程序 Ex09_1
1~5 步按缺省选择,在 step 6 of 6 选择视图类型为 CScrollView 类。
(2)编写 OnDraw 函数(见清单 9 1)
v
oidCEx 09_ 1ViewOnDr aw( CDC pDC)
{ CEx09_1Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDC- >TextOut(100,10,"这是 MFC 的打印功能!");//输出一行文字
pDC- >Rectangle(30,100,330,400);//绘制一个矩形
}
(3)编译和运行
9
12 视类中的打印函数
视类中自动添加与打印有关的函数见清单 9 2。
09_
清单9 2 视类中的打印函数(在 Ex 1Vi
ew.
cpp文件中)
BOOLCEx 09_1V i
ew OnPreparePrint
in g(
CPr
int
Inf
o p
Ino)
f
{ return DoPreparePrinting(pInfo);
}
vo
idCEx09_ 1ViewOnBe i
gnPrint
ing(CDC/pDC/CPr i
ntInf
o/pIn
fo/)
{
}
vo
idCEx09_ 1ViewOnEndPr int
ing(CDC/pDC/CPr intIn
fo/pI
nfo/)
{
}
除了清单 9 2 中的 3 个函数外,还有下面两个函数。
(1)OnPreparePrinting()
在一个打 印 任 务 开 始 时, 应 用 框 架 首 先 要 调 用 OnPreparePrinting ()成 员 函 数,
OnPreparePrinting()接 收 一 个 指 向 CPrintInfo 类 对 象 的 指 针 参 数。 表 9 2 列 出 了
CPrintInfo 类中重要的成员数据和成员函数 。
单击菜单:文 件 - > 打 印 时 将 显 示 如 图 9 1 所 示 的 打 印 对 话 框,图 9 1 的 显 示 是
DoPreparePrinting(
)函数实现的,用户可通过该对话框设置 CPrintInfo 类的许多数据成员。
表9 2 CPr
int
Ino类的成员
f
成员数据和成员函数 说 明
m_pPD 指向作为 Print 对话框的 CPrint Dialog 对象的指针
m_bDirect 指示文档是否正在直接打印的标志,它旁路 Print 对话框
m_bPreview 指示文档是否正在预览的标志
m_bContinuePrinting 指示 MFC 是否应该保持在打印循环内的标志
m_nCurPage 当前正在打印的页数
m_nNumPreviewPages 确定多少页应该显示在打印预览窗口:1 或 2
220 m_lpUserData 指向应用程序定义结构的指针
第九章 打 印
……………………………………………………………………………………………………………………………………………
续 表
成员数据和成员函数 说 明
m_rectDraw 定义打印机 DC 上的当前可用页区域的矩形框
m_strPageDesc 含有页号显示信息的格式字符串
SetMinPage() 指定文档的第一个页数(通常为 1)
SetMaxPage() 指定文档的最末页码
GetMinPage() 获取文档的第 1 页码
GetMaxPage() 获取文档的最末页码
GetFromPage() 获取待打印的第 1 页码
GetToPage() 获取待打印的最末页码
(2)OnBeginPrinting()
在“打印”对话框确定退出后,应用框架调用 OnBeginPrinting()成员函数。 该函数中可
以分配打印所需的 GDI 资源。
(3)OnPrepareDC()
应用程序框架在显示或打印之前调用 OnPrepareDC()成员函数,在该函数中可重新设置
显示或打印的逻辑映射模式,使之在随后的显示或打印中生效 。
(4)OnPrint ()
OnPrint()成员函数的参数中一个参数为指向设备环境的指针,另一个为指向整个打印
过程中均使用到的打印信息对象 (CPrintInfo)的指针。 在打印或打印预览之前,应用框架
创建打印信息对象。打印结束后,删除该对象,释放资源。 在打印信息对象中记录了打印页
码范围,打印作业的当前状态以及打印份数等信息 。在打印过程中,应用框架和视类之间交
换打印操作的信息主要通过处理同一打印信息对象,进而相互传递的。 因此控制了打印过
程中的打印信息对象,也就控制了打印。
图9 1 打印对话框 图9 2 打印过程函数的调用
(5)OnEndPrinting()
在打印文档最后一页结束后调用 OnEndPrinting()成员函数,用于释放打印时分配的资
源。如果在重写 OnBeginPrinting()时创建的 GDI 对象,也需要重写 OnEndPrinting()删除
该 GDI 对象。整个打印过程函数的调用见图 9 2。
9
13 打印控制过程
应用程序框架可自动在程序中实现简单的打印及打印预览功能,在控制上由应用程序 221
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
框架和视类共同完成,控制过程如下:
(1)视类设置“打印”对话框初始值
(2)应用框架调用 Windows 标准“打印”对话框,获取有关参数
(3)视类分配用于打印的 GDI 对象
(4)应用框架创建用于打印的设备环境 CDC 对象
(5)应用框架调用 CDC 类的 StartDoc()成员函数
(6)视类通知应用程序需打印文档的页数
(7)应用框架调用 CDC 类的 StartPage()成员函数
(8)应用框架通知视类将要打印的页号
(9)视类按通知准备该页内容并发送 escape 代码设置打印机模式
(10)视类打印指定页
(11)应用框架调用 CDC 类的 EndPage()结束该页打印
(12)返回第 7 步打印下一页,如此循环直到打印完全部文档
(13)应用框架调用 EndDoc()结束本次打印过程
(14)视类释放 GDI 资源
2 打印缩放 、映射模式选择及其编程实例
9
从表 9 1 中的图(a)和图(b)可以看出,所打印的文档与在屏幕上显示的不同,尽管屏幕
上的矩形占据了应用程序窗口相当大的一部分,打印出来却十分的小。 这是因为屏幕上的
像素和打印机上的点尺寸不同。 尽管在两处的矩形都是 300 点阵,但较小的打印机点打出
的矩形看起来更小。这是 Windows 缺省的图像映射模式 MM_TEXT 的工作原理。如果想把被
打印的图像缩放到一指定尺寸,就需要选择一种不同的映射模式(见表 7 1)。
例如选择 MM_LOMETRIC 模式,即一个像素点= 01 毫米长。
【例9 2】 Ex09_2 功能见例 9- 1,采用 MM_LOMETRIC 模式。
程序的建立过程同例 9 1,重新编写的 OnDraw 函数见清单 9 3。
清单9 3 OnDr 09_
aw 函数(在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx 09_ 2ViewOnDr aw(CDC pDC)
{ CEx09_1Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDC- >TextOut(100,10,"下面绘制矩形的映射模式为 MM_LOMETRIC" );//输出一行文字
pDC- >SetMapMode(MM_LOMETRIC);//设置映射模式
pDC- >Rectangle(30,-100,330,-400);//绘制矩形
}
222
图9 3 MM_
LOMETER
缺省坐标系
第九章 打 印
……………………………………………………………………………………………………………………………………………
3 多页打印及其编程实例
9
如果上述例题中的矩形很多,不能在一页中完成,就要打印多页,要实现多页打印仅仅
用缺省的程序是不够的,为此要通过如下的步骤来实现。
(1)通知 MFC 有多少页需要打印(或预览)
在 OnBeginPrinting()函数中用 SetMaxPage()函数来实现。
(2)重载 OnPrepareDC()函数,设置原点
【例9 3】 Ex09_3 多页打印的实现,具体说明见表 9 3。
表9 3 例9 3具体说明
项 目 图示和说明
程序
菜单
与界面
图(
a) 屏幕显示界面 图(
b) 打印预览界面
(1)启动程序,界面上共有 6 个矩形
程序功能 (2)单击鼠标左键向下增加一个矩形,数字增加 1。如单击 3 次,共有 9 个矩形,见图(a)
(3)点击菜单:文件- >打印预览,其界面见图(b)
(1)新建一单文档应用程序 Ex09_3
1~5 步按缺省选择,在 step 6 of 6 选择视图类型为 CScrollView 类。
(2)绘制矩形
① 在文档类头文件中添加记录矩形个数的变量
public:
int m_iNumRects;//矩形数
② 在构造函数中给变量赋初值,见清单 9 4。
09_
清单9 4 在构造函数中给变量赋初值(在 Ex 3Do
c.pp文件中)
c
09_
CEx 3Doc CEx 09_ c()
3Do
{ m_iNumRects = 9;
③ 添加和编写单击鼠标左键的消息函数(见清单 9 5) 223
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
清单9 5 OnLBu
tto 09_
nDown ()函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx 09_ 3ViewOnLBu t t
onDown(UINTnF lagsCPo
intp
oit)
n
{ CEx09_3Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDoc- >m_inumRects ++;
Invalidate();
CScrollView::OnLButtonDown(nFlags, point);
}
④ 重写 OnDraw()函数来绘矩形(见清单 9 6)
清单9 6 OnDr 09_
aw()函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx 09_ 3ViewOnLBu t t
onDown(UINTnF lagsCPo
intp
oit)
n
{ CEx09_3Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDoc- >m_inumRects ++;
Invalidate();
CScrollView::OnLButtonDown(nFlags, point);
}
(3)编译和运行程序
① 选择文件- > 打印预览,6 个矩形在第一页上正确显示,第二页为空白,见图 9 4。
② 在窗口内单击 1 次,变成 7 个矩形
③ 选择文件- >打印预览,7 个矩形在第一页上不能完全显示,而第二页为空白,见图 9 5。
为此要解决多页显示与打印的问题 。
图9 4 6个矩形能正确显示 图9 5 7个矩形不能正确显示
(4)实现多页正确预览第一步———获取多少页需要预览或打印
重写 OnBeginPrinting()函数,注意去掉对函数的两个参数的注释,见清单 9 7。
清单9 7 OnBe
ginPr
int
in 09_
g()函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx 09_ 3View OnBe ginPri
nting(CDC pDCCPr intI
nfo p
Ino)
f
{ CEx09_3Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
int pageHeight = pDC- >GetDeviceCaps(VERTRES);
int logPixelsY = pDC- >GetDeviceCaps(LOGPIXELSY);
224
第九章 打 印
……………………………………………………………………………………………………………………………………………
(5)编译和运行程序
单击一次鼠标左键,然后操作菜单:文件- > 预览,其界面见图 9 6。 可以看出,第二页
显示与第一页完全相同,这是不正确的。第二页应该显示第一页剩余的部分,而不是从开始
处重新显示相同的数据。
图9 6 两页显示完全相同 图9 7 显示正确
(6)实现多页正确预览第二步———设置原点
为保证 第 二 页 和 后 继 页 打 印 正 常, 必 须 改 变 MFC 确 认 的 页 顶 部, 重 载 视 类 的
OnPrepareDC()函数,见清单 9 8。
清单9 8 OnPr
epa
r 09_
eDC()函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx09_3V iewOnPr epa eDC(
r CDC pDCCPr intInfo p
Ino)
f
{ if(pDC- >IsPrinting()) //应用程序是否正在调用设备环境类的成员函数
{ int pageHeight = pDC- >GetDeviceCaps(VERTRES);//获得打印页的高度
//新原点的 Y 轴坐标为当前页码减 1 乘以 1 页的大小
int originY = pageHeight* (pInfo- >m_nCurPage- 1);
pDC- >SetViewportOrg(0,- originY);//设置新原点
}
//CScrollView::OnPrepareDC(pDC, pInfo);
}
(7)编译和运行程序
单击一次鼠标左键,然后操作菜单:文件- > 预览,其界面见图 9 7。可以看出多页打印
可正确预览了。
225
第十章 异 常 处 理
在 程 序 运 行 中 总 会 发 生 各 种 各 样 的 不 可 预 知 的 情 形 ,如 除 数 为 0,不 能 够 分 配 内
存 ,本 应 该 打 开 的 文 件 现 在 无 法 打 开 等 。 一 个 好 的 软 件 不 仅 要 保 证 软 件 的 正 确 性 ,而
且 应 该 具 有 一 定 的 容 错 能 力 。 在 程 序 设 计 时 要 做 到 允 许 用 户 排 除 错 误 ,继 续 运 行 程
序 ,至 少 要 给 出 适 当 的 提 示 信 息 ,不 能 轻 易 死 机 ,更 不 能 出 现 灾 难 性 的 后 果 。 因 此 ,在
设 计 程 序 时 ,要 充 分 考 虑 各 种 可 能 出 现 的 意 外 情 况 ,并 给 予 适 当 的 处 理 ,这 就 叫 做 异
常处理。
本章要点:
* CException 类及其派生类
* CFileException 类的应用
10
1 CExcep
tion 类
10
11 CEx
cep
tin类的函数
o
MFC 异常类的基类是 CException 类,CException 类除了构造函数外,只包括三个函数,
具体见表 10 1。
表10 1 CEx
cet
pin类的函数
o
函 数 作 用
void Delete() 删除一个异常类对象
10
12 CEx
cep
tin类的派生类
o
CException 类的派生类见表 10 2。
表10 2 CEx
cet
pin类的派生类
o
派 生 类 作 用
CMemoryException 内存不够异常
CNotSupportedException 请求一个不支持的操作
CArchiveException 档案专用异常
CFileException 文件专用异常
CResourceException Windows 资源未找到或不可创建
COleException OLE 异常
CDBException 数据库异常(由 ODBC 的数据库类引发)
COleDispatchException OLE 分配(自动化)异常
CUserException 指示某个资源不可发现的异常
CDaoException 数据访问对象异常(由 DAO 类引起)
CInternetException Internet 异常
2 文件异常 CF
10 ileExcep
tion 类
CFileException 用于通知应用程序出现了与文件系统有关的错误 。 CFileException
包含几个公共数据成员和方法,有助于判断哪个文件出了问题引起的异常事件,从而扩展了
CException 的基本功能。
10
21 CF
ileEx
cep
tin类数据成员
o
CFileException 类有三个数据成员:m_strFileName、m_cause、m_IosError。
公有成员 m_strFileName 包含与异常相联系的文件名。在故障产生的一刻把它们联系
起来是一种较好的解决方案。
公有成员 m_cause 提供一个独立于操作系统的错误代码,其取值见表 10 3。
表10 3 CF
ileEx
cet
pio m_
n c
aue的取值
s
m_cause 取值 含 义
CFileException::none 没有遇到错误
CFileException::generic 遇到未指定的错误
CFileException::fileNotFound 文件未能找到
CFileException::badPath 所有或部分路径不合法
CFileException::tooManyOpenFiles 打开文件超过最大量
CFileException::accessDenied 不能访问此文件
CFileException::invalidFile 试图使用一个无效句柄
CFileException::removeCurrentDir 不能删去当前工作目录
CFileException::directoryFull 不能建立任何附加目录项
CFileException::badSeek 不能设置文件指针
227
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
续 表
m_cause 取值 含 义
CFileException::hardIO 遇到一个软件错误
CFileException::sharingViolation 不能加载 SHARE.EXE,或者共享区被锁
CFileException::lockViolation 试图锁住一个已经加锁的区域
CFileException::diskFull 试图写整个盘
CFileException::endOfFile 到达文件结束
10
22 CF
ileEx
cep
tin类成员函数
o
CFileException 类成员函数见表 10 4。
表10 4 CF
ileEx
cet
pin类成员函数
o
成员函数名 作 用
构造一个 CFileException 对象,用于保存原因代码和操作
CFileException:: CFileException()
系统错误
将一给定运行库错误值转换为一个 CFileException 枚举错
CFileException:: ErrnoToException()
误值
返回给 定 参 数 值 所 对 应 的 一 个 枚 举 符。若 未 知,则 返 回
CFileException::OsErrnoToException()
CFileException::generic
首先构造一个对应给定 nErrno 值的 CFileException 对象,
CFileException::ThrowErrno
然后发布一个异常
发布一个对应于给定参数值的 CFileException 对象。若未
CFileException::ThrowOsErrno
知,则发布一个码值为 CFileException::generic 的异常
CFileException 构造函数,原型如下:
参数:cause———一个枚举类型变量,用于表示异常的原因;
IosError———指示异常操作的操作系统专用原因(若可用)。
10
23 编程实例
【例10 1】 将文件异常应用于文件操作中,将清单 8 2 的 OnButtonRead 函数进行修
改,采用文件异常来对文件操作进行处理,修改后的 OnButtonRead 函数见清单 10 1。请读
者按此方法修改清单 8 1 中的 OnButtonWrite 函数。
清单10 1 修改后的 OnBu
tto
nRe
a 08_
d函数(在 Ex 1Dl
g.pp文件中)
c
v
oidCEx08_1D lg
OnBu
tto
nRe
ad()
228 { fstream f1;//定义文件流对象
第十章 异 常 处 理
……………………………………………………………………………………………………………………………………………
char s200;
CFileDialog filedlg(1,//1-文件打开, 0-文件另存为
".txt|* .* " ,
NULL,
OFN_OVERWRITEPROMPT,
" 文本文件(* .txt)|* .txt|All Files (* .* )|* .* ||",
NULL);
if(filedlg.DoModal()= = IDOK)
{ UpdateData(true);
TRY
strFileName = filedlg.GetFileName();
if(! IsTextFile(strFileName))//判断文件类型是否正确
{ AfxMessageBox("文件类型不正确!");
return;
AfxThrowFileException(CFileException::generic);
f1.open(strFileName, ios::in|ios::nocreate);
strFilePath = filedlg.GetPathName();
if(! f1.good())
AfxThrowFileException(CFileException::fileNotFound);
while(! f1.eof())
{ f1.getline(s, 255);
m_strRead = m_strRead+ "\r\n" + s;//添加文件中的文本到编辑框
UpdateData(false);
}
AfxMessageBox(strFileName+ " 文件读入完毕!");//保存结束提示
}
CATCH(CFileException, e)
{ CString msg;
switch(e- >m_cause)
{ case CFileException::generic:
msg =" 不能打开此类文件: "+ strFileName;
break;
case CFileException::fileNotFound:
msg =" 文件打开错!"+ strFileName;
break;
default:msg =" 系统捕捉的错误!";
}
MessageBox(msg," 文件异常");
}
END_CATCH
f1.close();
}
}
3 数据库异常类
10
CDBException 类为数据库异常类,由 CException 类派生,以 3 个继承的成员变量反映
对数据库操作时的异常: 229
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
有关数据库异常的应用见第十一章数据库编程 。
230
第十一章 数据库编程
简而言之,数据库就是一组记录的集合。 VC++提供了多种多样的数据库访问技术;如
ODBC(Open DataBase Connectivity )、DAO (Data Access Object )、OLE DB (Object Link and
Embedding DataBase)、ADO(ActiveX Data Object)等。 这些技术各有自己的特点,但都提供
了简单、灵活、访问速度快、可扩展性好的开发技术。 利用这些技术可以从程序中直接操作
各种各样的数据库,如 SQL Server,Microsoft Access,Microsoft FoxPro 和 Paradox 等。
本章要点:
* 数据库基本知识
* ODBC 数据库编程
* ADO 数据库编程
1 数据库概述
11
数据库主要包括两种模型:平面数据库模型和关系数据库模型。 平面数据库模型包括
一系列记录,每个记录由若干字段组成。关系数据库就是若干个平面数据库的连接,在关系
数据库中用户不仅能像在平面数据库中那样查询记录,而且能够建立一个记录集合和另一
个记录集合之间的关系,这样大大提高了数据库的效率。 在关系数据库中,每一个记录集合
称为一张表,而表之间的连接是通过关键字段来实现的 。
11
11 数据库对象
数据库对象是数据表中的物理现象。 这些对象拥有唯一的名字,并保存数据和数据关
系信息。
(1)表、字段和记录
表类似于日常生活中的表格,它按行和列方式将相关信息排列成逻辑组。 表中的每一
行称为记录,每一列称为字段。如表 11 1 为学生信息数据表。
表11 1 学生信息数据表
(2)主键
一个记录中,可以代表整个记录的字段就可以将其设为主键。 该字段需要具备下面两 231
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
个条件:
① 字段中的每一个值都是唯一的(即不能重复)。
② 在意义上具有代表性。
如在表 11 1 中,“学号”———StuID 字段比“姓名”———StuName 字段更适合作为主键。
11
12 ODBC
ODBC(Open Database Connectivity)即开放式数据库链接,是客户应用程序访问关系数
据库时提供的一个统一的接口。ODBC 已经成为一种标准,目前所有的关系数据库都提供了
ODBC 驱动程序,这使 ODBC 的应用非常广泛,基本上可用于所有的关系数据库。 但是 ODBC 只
能访问各种关系数据库,另一端为应用程序和开发工具提供了一种统一的接口。 ODBC 是基
于 SQL 而设计的,而且它还定义了 C 语言与 SQL 数据库之间的程序设计接口。 在程序执行
过程中,ODBC32.DLL 会调用特定数据库的驱动程序,因而它允许单个程序同时访问多个数据
库管理系统中的数据。
11
13 DAO
和 ODBC 不同,DAO 通过数据库引擎实现对数据库的访问,DAO 具有比 ODBC 更多更强的功
能。
(1)ODBC 使用一组动态链接库来实现而 DAO 通过 OLE 对象来实现,因此 DAO 具有更新的
结构。
(2)DAO 类是 ODBC 类的超集,它包含了 ODBC 类的大部分功能并增加了许多自己的功能,
它提供了一个功能更强的方法集 。
(3)MFC ODBC 类对 ODBC API 的封装并不完全,它并不能对 ODBC 实现底层控制,是一种高
级用户接口。而 MFC DAO 类对 DAO 的封装已经相当完全,能够对 DAO 进行绝大多数的底层操
作,是一种底层用户接口。
11
14 OLEDB
OLE DB 是 Visual C++开发数据库应用中提供的新技术,它基于 COM 接口。 因此,OLE DB
对所有的文件系统包括关系数据库和非关系数据库都提供了统一的接口。 这些特性使得
OLE DB 技术比传统的数据库访问技术更加优越 。
11
15 ADO
ADO 技术是基于 OLE DB 的访问接口,它继承了 OLE DB 技术的优点,并且,ADO 对 OLE DB
的接口作了封装,定义了 ADO 对象,使程序开发得到简化。 ADO 技术属于数据库访问的高层
接口。
类 名 作 用
CDatabase 主要功能是建立与数据源的连接
CRecordset 代表从数据源选择的一组记录(记录集)
提供了一个表单视图与某个记录集直接相连,利用对话框数据交换机制(DDX)在
CRecordView
记录集与表单视图的控件之间传输数据
支持记录字段数据交换(DFX),即记录集字段数据成员与相应的数据库的表的字
CFileExchange
段之间的数据交换
CDBException 代表 ODBC 类产生的异常
(1)CDatabase 类
CDatabase 对象表示到一数据源的连接,通过它可以操作数据源。该类的成员函数见表
11 3。
表11 3 CDa
tab
ase类常用成员函数
成 员 函 数 函 数 功 能
BeginTrans() 开始事务
BindParameters() 允许在调用 CDatabase::ExecuteSQL 前绑定参数
Cancel() 删除一次异步操作
CanTransact() 检测数据库是否支持事务,如果支持则函数返回非零值
CanUpdate() 检测 CDaoDatabase 对象是否可更新,如果可更新则返回非零值
CDatabase() 构造一个 CDatabase 对象
Close() 关闭数据库连接
CommitTrans() 执行事务
ExecuteSQL() 执行 SQL 语句,不返回记录
GetBookmarkPersistence() 确定通过记录集对象中的哪一个书签操作
GetConnect() 获得数据库的连接参数
GetCursorCommitBehavior() 确定在一个打开的记录集对象中提交一项事务的效果
GetCursorRollbackBehavior() 确定在一个打开的记录集对象中退回一项事务的效果
GetDatabaseName() 获得当前使用的数据库的名称
IsOpen() 检测 CDatabase 对象是否正连接到数据库上,如果已连接则返回非零值
Open() 通过一个 ODBC 驱动程序创建到数据源的连接
OpenEx() 通过一个 ODBC 驱动程序创建到数据源的连接
Rollback() 回滚事务,数据源返回先前的状态
SetLoginTimeout() 设置数据库连接的超时数(秒为单位)
SetQueryTimeout() 设置数据库查询操作的超时数(秒为单位)
(2)CRecordset 类
CRecordset 对象表示从数据源中选择的一组记录即 “记录集”,通过该类的方法实现对
数据库中记录的各种操作。该类的数据成员和常用成员函数见表 11 4。
表11 4 CRe
cor
dSt类的数据成员以及常用成员函数
e
数据成员和成员函数 函 数 功 能
m_hstmt() 包含记录集的 ODBC 陈述句柄,类型为 HSTMT
m_nFields() 从数据源中选入的记录集类的字段数目,类型为 UNIT
m_nParams() 包含记录集类中参数数据成员数量,类型为 UNIT
m_pDatabase() 包含一个 CDatabase 对象指针,通过它访问数据源
m_strFilter() 用于构造 SQL WHERE 语句的字符串
m_strSort() 用于构造 ORDER BY 语句的字符串
AddNew() 准备向记录集中添加一条新记录
CRecordset() 构造一个记录集
Close() 关闭记录集
Delete() 删除记录集中的当前记录
Open() 从一个表、动态集或快照创建一个新记录集
CanAppend() 如果新记录可通过 AddNew 成员函数增加到记录集中,则返回非零
CancelUpdate() 取消所有由 AddNew 或 Edit 操作引发的待决的更新操作
CanScroll() 如果可以在记录中滚动,返回非零
CanTransact() 如果数据源支持事务,返回非零
CanUpdate() 返回非零则记录集可以更新
DoFiledExchange() 在记录集的字段数据成员和数据源对应记录间交换数据(双向)
Edit() 准备改变记录集中的当前记录
GetDefaultConnect() 获取缺省连接串
GetDefaultSQL() 获取要执行的缺省 SQL 串
GetRecordCount() 获得记录集对象中记录数
GetSQL() 得到用于从记录集选择记录的 SQL 字符串
GetTableName() 获取记录集所基于的表名
IsBOF() 返回非零则当前记录为第一条记录
IsDeleted() 返回非零则当前记录被删除,如果被删除则函数返回 TRCE
IsEOF() 返回非零则当前记录为最后一条记录
IsFieldDirty() 返回非零则当前记录中指定字段发生变化
IsOpen() 返回非零则记录集已经被打开
Move() 移动到记录集中指定的记录号
MoveFirst() 在记录集中把当前记录置为第一条记录
MoveLast() 在记录集中把当前记录置为最后一条记录
MoveNext() 在记录集中把当前记录置为下一条记录
MovePrev() 在记录集中把当前记录置为上一条记录
Update() 在数据源存储新的或编辑过的数据,以完成 AddNew 或 Edit 操作
(3)CFieldExchange 类
CFieldExchange 类支持数据库类所使用的记录集字段交换 (RFX)程式。 如果使用自己
234 定义的数据类型写数据交换程式,会使用这个类,否则不会直接使用此类。 RFX 在记录集对
第十一章 数 据 库 编 程
……………………………………………………………………………………………………………………………………………
象的字段数据成员与数据源中当前记录的相应字段之间交换数据 。
(4)CRecordView 类
CRecordView 类的对象用于在控件中显示数据库记录的视图。 这种视图是一种直接连
接到一个 CRecordset 对象格式视图,它从一个对话框模板资源创建,并将 CRecordset 对象
的字段显示在对话框模板的控件里 。
11
22 注册数据库
创建数据库应用程序前,首先应将数据库注册为可由 ODBC 驱动程序访问的数据库。 例
如将 Access 数据库 Stumsg.mdb 注册为 ODBC 数据源 StuMsg 的操作步骤如下:
(1)根据需要创建文件夹,例如 d:\ExDatabase,将 StuMsg.mdb 文件拷贝到其中。
(2)打开控制面板- > 性能和维护- > 管理工具- > 数据源 (ODBC)。 注意不同的操作系统
数据源 ODBC 所在的位置略有区别。
(3)双击数据源(ODBC)图标。显示如图 11 1 所示的 ODBC 数据源管理器对话框。
图11 3 ODBC Mi
cro
sof
tAc
ces安装对话框
s 图11 4 选择数据库对话框
库文件 StuMsg.mdb。
(7)单击图 11 4 的确定按钮、单击图 11 3 的确定按钮图、单击图 11 1 的确定按钮。
通过上述步骤,完成了设置使用 Microsoft Access ODBC 驱动程序访问 StuMsg.mdb 数据
库文件的特殊机制。
11
23 ODBC 数据库编程实例
【例11 1】 Ex11_1ODBC 应用程序,具体说明见表 11 5。
项目 图示和说明
程 图(
b) 图(
c) 图(
d)
序 记录菜单 排序菜单 过滤菜单
界
面
图(
a) 主界面 图(e) 图(f) 图(g)
过滤对话框 对话框 对话框
(1)程序带有一后台数据库 StuMsg.mdb
程 (2)通过操作工具栏和记录菜单可以实现对数据库的浏览
序 (3)通过操作记录菜单中的增加记录和删除记录可实现对数据库记录的增加和删除
功 (4)通过操作排序菜单下的各选项,可分别按学号、姓名、籍贯、班级和学院排序
能 (5)通过操作过滤菜单下的各选项,弹出查找对话框,可分别按学号、姓名、籍贯、班级和学院过滤
(或查找)。如果没有找到记录,则弹出图(f);如果找到记录,则弹出图(g)
第一部分:数据库部分
(1)用 Access 建立数据库 StuMsg.mdb,并建立数据库表 studenttab。
数据库表的设计见表 11 6。
表11 6 s
tud
ent
tab表
(2)按上述步骤注册数据库
第二部分:VC++部分
(1)新建一单文档应用程序 Ex11_1,并建立与 studenttab 表交互的记录集类。
① 在 MFC AppWizard 的 step1 中,选中 single document。
② 在 MFC AppWizard 的 step2 中,选中 Database view without file support,如图 11 5
所示。
③ 单击 Data Source 按钮,建立应用程序与先前建立的数据库之间的连接,在打开的
Database Options 对话框的 ODBC 下拉列表框中选择“StuMsg”数据源,如图 11 6 所示。
图11 5 MFCAppWi
zar
dSt
ep2o
f6 图11 6 数据库选择对话框
11_
清单11 1 Ex 1Se
t.c
pp
c
lassCEx 11_ 1Se
tpub licCRe cords
et
public:
CEx11_1Set(CDatabase* pDatabase = NULL);
DECLARE_DYNAMIC(CEx11_1Set)
//Field/Param Data
//AFX_FIELD(CEx11_1Set, CRecordset)
CString m_StuID;
CString m_StuName;
BOOL m_StuSex;
CTime m_StuBirthday;
CString m_StuNativePlace;
CString m_StuClass;
CString m_StuCollege;
//AFX_FIELD
//Overrides
//ClassWizard generated virtual function overrides
//AFX_VIRTUAL(CEx11_1Set)
public:
virtual CString GetDefaultConnect(); //Default connection string
virtual CString GetDefaultSQL(); //default SQL for Recordset
virtual void DoFieldExchange(CFieldExchange* pFX); //RFX support
//AFX_VIRTUAL
;
GetDefaultConnect()函数主要返回所连接的数据库名;GetDefaultSQL()函数主要返回
数据表名;DoFieldExchange()函数主要表明 CEx11_1Set 类中定义的 7 个变量与数据表中 7
个字段交换的对应关系。这些函数体见清单 11 2。
11_
清单11 2 Ex 1Se
t.c
pp
# include" stdafx.h"
# include" Ex11_1.h"
# include" Ex11_1Set.h"
# ifdef _DEBUG
# define new DEBUG_NEW
# undef THIS_FILE
static char THIS_FILE= __FILE__;
# endif
/////////////////////////////////////////////////////////////////////////////
//CEx11_1Set implementation
IMPLEMENT_DYNAMIC(CEx11_1Set, CRecordset)
CEx 11_1S e
tCEx 11_1Set(CDa
tab
asepdb):CRecord
set(pdb)
{ //{{AFX_FIELD_INIT(CEx11_1Set)
m_StuID = _T("");
m_StuName = _T("");
238
第十一章 数 据 库 编 程
……………………………………………………………………………………………………………………………………………
m_StuSex = FALSE;
m_StuBirthday = 0;
m_StuNativePlace = _T("");
m_StuClass = _T("");
m_StuCollege = _T("");
m_nFields = 7;//AFX_FIELD_INIT
m_nDefaultType = snapshot;
CSt
rin
gCEx 11_1SetGetDefault
Co nne t()
c
{ return _T("ODBC;DSN = StuMsg" );
}
CSt
rin
gCEx 11_1SetGetDefaulSQL()
t
{ return _T("[studenttab" );
}
vo
idCEx 11_1SetDoFieldExchange( CFiel
dEx changepFX)
{ //{{AFX_FIELD_MAP(CEx11_1Set)
pFX ->SetFieldType(CFieldExchange::outputColumn);
RFX_Text(pFX, _T("[StuID" ), m_StuID);
RFX_Text(pFX, _T("[StuName" ), m_StuName);
RFX_Bool(pFX, _T("[StuSex" ), m_StuSex);
RFX_Date(pFX, _T("[StuBirthday" ), m_StuBirthday);
RFX_Text(pFX, _T("[StuNativePlace" ), m_StuNativePlace);
RFX_Text(pFX, _T("[StuClass" ), m_StuClass);
RFX_Text(pFX, _T("[StuCollege" ), m_StuCollege);
//}}AFX_FIELD_MAP
② Ex11_1View 类。
该类通过定义一个 CEx11_1Set 类的指针实现与 CEx11_1Set 类的交互,见清单 11 3。
清单11 3 定义指针 m_
pSe 11_
t(在 Ex 1Vi h文件中)
ew.
c
las
sCEx 11_1V iewpub li
cCRe cor
dV i
ew
protected:// create from serialization only
CEx11_1View();
DECLARE_DYNCREATE(CEx11_1View)
public:
//AFX_DATA(CEx11_1View)
enum IDD = IDD_EX11_1_FORM;
CEx 11_1Set m_ pSt //定义记录集类的指针
e
//}}AFX_DATA
(3)编辑界面,添加控件资源和成员变量
本例中添加 5 个编辑框显示数据表中的学号 、姓名 、籍贯 、班级和学院等 5 个字段,代
表学号的编辑框在属性设置时设置为 “只读 ”,即将图 11 9 中的 Read Only 选中 。 5 个编
辑框的 ID 号和成员变量见表 11 7。 用 “MFC ClassWizard”添加成员变量,添加方法见图
11 10。 239
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
控 件 ID 号 成 员 变 量 变量类型
编辑框(学号) IDC_STUID m_pSet ->m_StuID CString
编辑框(姓名) IDC _STUNAME m_pSet ->m_StuName CString
编辑框(籍贯) IDC _STUNATIVEPLACE m_pSet ->m_StuNativePlace CString
编辑框(班级) IDC _STUCLASS m_pSet ->m_StuClass CString
编辑框(学院) IDC _STUCOLLEGE m_pSet ->m_StuCollege CString
图11 9 选中 Re
adOn
ly复选框 图11 10 添加成员变量
图11 11 数据交互关系
v
oidCEx 11_1View DoDataExchange( CDataEx c
h an
ge pDX)
{ CRecordView::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CEx11_1View)
DDX_FieldText(pDX, IDC_STUID, m_pSet ->m_StuID, m_pSet);
DDX_FieldText(pDX, IDC_STUNAME, m_pSet ->m_StuName, m_pSet);
DDX_FieldText(pDX, IDC_STUNATIVEPLACE, m_pSet ->m_StuNativePlace, m_pSet);
DDX_FieldText(pDX, IDC_STUCLASS, m_pSet ->m_StuClass, m_pSet);
DDX_FieldText(pDX, IDC_STUCOLLEGE, m_pSet ->m_StuCollege, m_pSet);
//}}AFX_DATA_MAP
(4)编译和运行
得到如表 11 4 的界面,应用程序已具有修改记录的功能 。方法是修改记录中的任意字
240 段(除 STUID),然后移动到另一记录。应用程序记录菜单也提供了与工具栏功能相同的移动
第十一章 数 据 库 编 程
……………………………………………………………………………………………………………………………………………
记录选项。
(5)添加和删除记录
① 在记录菜单下增加两个子菜单,并添加相应的消息函数(见表 11 8)。
表11 8 增加的菜单ID 号和消息处理函数
菜单标题 ID 号 菜单消息函数
增加记录 ID_RECORD_ADD OnRecordAdd
删除记录 ID_RECORD_DELETE OnRecordDelete
v
oidCEx11_1V i
ew OnRe cordAdd()//增加记录
{ m_pSet ->AddNew();//建立一个空记录
m_bAdding = true;
CEdit * pCtrl = (CEdit* )GetDlgItem(IDC_EDIT_STUID);//取得学号编辑控件的指针
int result = pCtrl ->SetReadOnly(false);//学号编辑框只读状态改为 false
UpdateData(false);//显示新的空白记录
}
清单11 6 OnMo
v 11_
e函数(在 Ex 1Vi
ew.
cpp文件中)
向数据库中增加一个新的记录,就必须移动一个新的记录,这一操作强制调用视窗的
OnMove()成员函数。
⑥ 编写 OnRecordDelete 函数(见清单 11 7)。
清单11 7 OnRe
cor
dDe
let 11_
e函数(在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx11_1V i
ew OnRe cor
dDelee()
t //删除记录
{ m_pSet ->Delete();//删除记录集
m_pSet ->MoveNext();//移动到要显示的下一个记录
if(m_pSet ->IsEOF())//记录指针是否在数据库的末尾
m_pSet ->MoveLast(; /定位于记录集的最后一个记录
) /
if(m_pSet ->IsBOF())//记录指针是否在数据库的开头
m_pSet ->SetFieldNull(NULL);//设置当前记录字段为 NULL
UpdateData(FALSE);
}
到此程序可执行添加、删除和更新的操作。
(6)排序
① 增加一个“排序”菜单,它包含 5 个子菜单,并添加相应的消息函数(见表 11 9)。
表11 9 在排序菜单中增加5个菜单选项
菜单标题 ID 号 菜单消息函数
学 号 ID_SORT_STUID OnSortStuid()
姓 名 ID_SORT_STUNAME OnSortStuname()
籍 贯 ID_SORT_STUNATIVEPLACE OnSortStunativeplace()
班 级 ID_SORT_STUCLASS OnSortStuclass()
学 院 ID_SORT_STUCOLLEGE OnSortStucollege()
11_
清单11 8 排序菜单下的5个菜单选项的消息函数(在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx11_1V i
ew OnSortStuid()//按学号排序
{ m_pSet ->Close();
m_pSet ->m_strSort =" StuID" ;
m_pSet ->Open();
UpdateData(false);
}
v
oidCEx11_1V i
ew OnSortStuname() //按姓名排序
{ m_pSet ->Close();
m_pSet ->m_strSort =" StuName" ;
m_pSet ->Open();
UpdateData(false);
}
v
oidCEx11_1V i
ew OnSortStunativep lac //按籍贯排序
e()
{ m_pSet ->Close();
m_pSet ->m_strSort =" StuNativePlace" ;
m_pSet ->Open();
UpdateData(false);
}
v
oidCEx11_1V i
ew OnSortStuclass() //按班级排序
{ m_pSet ->Close();
m_pSet ->m_strSort =" StuClass" ;
m_pSet ->Open();
UpdateData(false);
}
v
oidCEx11_1V i
ew OnSortStucoll
e ge() //按学院排序
{ m_pSet ->Close();
m_pSet ->m_strSort =" StuCollege" ;
m_pSet ->Open();
UpdateData(false);
}
(7)过滤(查找)
① 增加一个“过滤”菜单,它包含 5 个选项,并添加相应的消息函数(见表 11 10)。
表11 10 在过滤菜单中增加5个菜单选项
菜单标题 ID 号 菜单消息函数
学 号 ID_FILTER_STUID OnFilterStuid()
姓 名 ID_FILTER_STUNAME OnFilterStuname()
籍 贯 ID_FILTER_STUNATIVEPLACE OnFilterStunativeplace()
班 级 ID_FILTER_STUCLASS OnFilterStuclass()
学 院 ID_FILTER_STUCOLLEGE OnFilterStucollege()
② 建立查找对话框资源与类。
查找对 话 框 界 面 如 表 11 5 的 图 所 示, 对 话 框 ID 为 IDD _ FILTERDLG, 类 名 为
CFilterDlg,控件 ID 号与变量见表 11 11。
243
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
表11 11 过滤对话框各控件和成员变量
③ 定义和函数
在 Ex11_1View.h 文件中定义的变量和函数见清单 11 9。
11_
清单11 9 定义变量和函数(在 Ex 1Vi
ew.
cpp文件中)
c
lassCEx11_ 1Viewpub l
icCRe co
rdV i
ew
CString strStaticFilter;
void DoFilter(CString strFiltertype);//执行查询函数
};
v
oidCEx 11_1V iew DoFi
lter(CStr
ings tr
Fil
tertype)
{ CFilterDlg dlg;
dlg.m_strStaticFilter = strStaticFilter;
nt RecordCount;
char strRecordCount10;
if(dlg.DoModal()= = IDOK)
{ CString str = strFiltertype+ " = " + dlg.m_strEditFilter+ " ";
m_pSet ->Close();
m_pSet ->m_strFilter = str;
m_pSet ->Open();
RecordCount = m_pSet ->GetRecordCount();
if(RecordCount = = 0)
{ AfxMessageBox("没有此记录!");
m_pSet ->Close();
m_pSet ->m_strFilter ="" ;
m_pSet ->Open();
}
else
sprintf(strRecordCount," 共有 %d 条记录", RecordCount);
AfxMessageBox(strRecordCount);
}
UpdateData(false);
}
④ 编写过滤菜单下的四个选项的消息函数(见清单 11 11)。
11_
清单11 11 过滤菜单下的四个菜单选项的消息函数(在 Ex 1Vi
ew.
cpp文件中)
v
oi 11_
dCEx 1V i
ew OnF il
ter
Stu
i //按学号查询
d()
244 { strStaticFilter =" 输入要查询的学号:";
第十一章 数 据 库 编 程
……………………………………………………………………………………………………………………………………………
DoFilter("StuID" );
}
v
oidCEx11_1View OnF i
lterSt
un ame() //按姓名查询
{ strStaticFilter =" 输入要查询的姓名:";
DoFilter("StuName" );
}
v
oidCEx11_1View OnF i
lterSt
un at
iveplac //按籍贯查询
e()
{ strStaticFilter =" 输入要查询的籍贯:";
DoFilter("StuNativePlace" );
}
v
oidCEx11_1View OnF i
lterSt
u c
lass()//按班级查询
{ strStaticFilter =" 输入要查询的班级:";
DoFilter("StuClass" );
}
v
oidCEx11_1View OnF i
lterSt
u c
oll
e e()
g //按学院查询
{ strStaticFilter =" 输入要查询的学院:";
DoFilter("StuCollege" );
}
3 ADO 数据库编程
11
11
31 ADO 结构
ADO 的结构模型包含了 7 种对象和 4 种集合,其说明见表 11 12。
表11 12 ADO 模型包含的7种对象和4种集合
对象和集合名称 说 明
连接对象(Connection) 表示与数据源的连接,以及处理一些命令和事务
命令对象(Command) 用于处理传递给数据源的命令
记录集对象(RecordSet) 用于处理数据的表格集,如获取和修改数据
域对象(Field) 用于表示记录集中的列信息,包括列值以及其他信息
参数对象(Parameter) 用于对传送给数据源的命令赋参数值
属性对象(Property) 用于操作在 ADO 中使用的其他对象的详细属性
错误对象(Error) 用于获得连接对象所发生的错误的详细信息
Connection 对象具有 Errors 集合。包含为响应与数据源有关的单一错误
错误集合(Errors)
而创建的所有 Error 对象
Command 对象具有 Parameters 集合。包 含 了 应 用 于 命 令 对 象 的 所 有 的
参数集合(Parameters)
Parameters 对象
Recordset 对 象 具 有 Fields 集 合。包 含 了 所 有 代 表 记 录 集 中 每 列 的
域集合(Fields)
Fields 对象
Connection、Command、Recordset 和 Field 对 象 都 具 有 Properties 集 合。
属性集合(Properties)
Properties 对象的集合中包含了这些对象的所有特性 245
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
11
32 连接对象
Connection 对象代表了打开的、与数据源的连接,在使用 ADO 访问数据库之前,必须先
创建一个连接对象,然后通过它打开到数据库的连接。 其常用的函数和属性分别见表 11
13 和表 11 14。
表11 13 连接对象的函数
函 数 说 明
Open() 在创建了一个连接对象后,使用该函数打开到数据源的连接
打开了到数据源的连接后,使用该函数执行指定的查询、SQL 语句、存储过程或提
Execute()
供特定指定的文本等内容
Close() 关闭 Connection 对象以便释放所有关联系统的资源
BeginTrans() 启动新的事务
CommitTrans() 保存所有更改并结束当前事务。它也可以启动新事务
RollbackTrans() 取消当前事务中所做的任何更改并结束事务。它也可以启动新事务
表11 14 连接对象的属性
属 性 说 明
Attributes 可以读写,并且其值可能为 AdXactCommitRetaining 或为 AdXactAbortRetaining
指示在终止尝试和产生错误之前执行命令所等待的时间,为长整型,单位为秒(默
ConnectionTimeout
认值为 15 秒),连接关闭时该属性为读/写,而打开时为只读
ConnectionString 包含用来建立到数据源的连接的信息
DefaultDatabase 指示 Connection 对象的默认数据库
使用该属性可以设置 Connection 对象的隔离级别。该属性为读/写,直到下次调
IsolationLevel
用 BeginTrans 方法时,该设置才可以生效
使用该属性可设置或返回当前连接上提供者正在使用的访问权限。Mode 属性只
Mode
能在关闭 Connection 对象时方可设置
Provider 使用该属性可设置或返回提供连接者的名称
Version 使用该属性返回 ADO 执行的版本号
11
33 命令对象
Command 对象定义了将对数据源执行的指定命令。 其常用的函数和属性分别见表 11
15 和表 11 16。
表11 15 命令对象的函数
函 数 说 明
Execute() 执行在 CommandText 属性中指定的查询、SQL 语句或存储过程
用指定的名称、类型、方向、大小和值创建新的 Parameter 对象,在参数中传递的
CreateParameter()
所有值都将写入相应的 Parameter 属性
使用 Cancel 方法终止执行异步 Excute 方法调用。如果在试图终止的方法中没
Cancel()
有使用 adRunAsync,则 Cancel 将返回运行错误
246
第十一章 数 据 库 编 程
……………………………………………………………………………………………………………………………………………
表11 16 命令对象的属性
属 性 说 明
CommandText 可设置或返回 Command 对象的文本
ConnectionTimeout 指示在终止尝试和产生错误之前执行命令所等待的时间
CommandType 指定命令类型,可以是文本命令、表格名或者是一个存储过程
ActiveConnection 指示指定的 Command 对象当前所属的 Connection 对象
State 指定对象的当前状态
Prepared 指定执行前是否保存命令的编译版本
11
34 记录集对象
Recordset 对象表示的是来自基本表或命令执行结果的记录全集。 其常用函数和属性
分别见表 11 17 和表 11 18。
表11 17 记录集对象的函数
函 数 说 明
MoveFirst() 移动记录集到第一条记录处
MoveLast() 移动记录集到最后一条记录处
MovePrevious() 移动记录集到当前记录的前一条记录处
MoveNext() 移动记录集到当前记录的后一条记录处
Move() 移动记录集到指定的当前记录处
使用 NextRecordset 方法返回复合命令语句中下一条命令的结果,或者是返回多
NextRecordset()
个结果的已存储过程结果
Open() 直接打开一个记录集,而不是作为执行命令或者连接命令产生的记录集
Close() 关闭记录集
Delete() 删除记录集中的当前记录
Update() 将当前对记录集的改动保存到数据源
CancelUpdate() 取消 Update 更新前所做的改变
表11 18 记录集对象的属性
属 性 说 明
AbsolutePage 指定当前记录集所在的页
使用该属性可根据其在 Recordset 中的序号位置移动到记录,或确定当前记录的
AbsolutePosition
序号位置
ActiveConnection 指示指定的 Recordset 对象当前所属的 Connection 对象
指示当前记录位置是否位于 Recordset 对象的第一个记录之前。如果当前记录
BOF 位于第一个对象之前,BOF 属性返回 TRUE,如果当前记录为第一个记录或位于其
后,将返回 FALSE
续 表
属 性 说 明
Filter 指定一个在行集中移动时使用的过滤器
LockType 获得或设置当前的访问状态
获得或设置一次操作中的记录集对象中返回的最大行的数目,默认值为 0,表示
MaxRecord
无限制
PageCount 获得记录集中使用的页数
PageSize 获得或者指定一页中的行数
RecordCount 获得记录集中包含的记录的数目
Source 获得记录集中记录的来源
Status 返回当前行的状态
11
35 域对象
Field 对象代表使用普通数据类型的数据列 。其常用的函数和属性分别见表 11 19 和
表 11 20。
表11 19 域对象的函数
函 数 说 明
可将长二进制或字符数据填写到域对象中。在系统内存有限的情况下,可以使
AppendChunk()
用 AppendChunk 方法对长整型值进行部分而非全部的操作
检索域对象部分或全部长二进制或字符数据。在系统内存有限的情况下,可以
GetChunk()
使用 GetChunk 方法处理部分而非全部的长整型
表11 20 域对象的属性
属 性 说 明
ActualSize 指示字段值的实际长度
Attribute 域的属性集合,可以用来判断此域是否有固定长度或是否可以为空
DefinedSize 确定 Field 对象的数据容量
Name 列的名称
NumericalScale 在浮点数中用于指出小数点右边多少位用于表示这个数
OriginalValue 发生任何更改前已在记录中存在的 Field 的值
Precision 表示数字 Field 对象值的精度
Type 列中的值的数据类型
UderlyingValue 反映了数据源中列的当前值,当在事务的同步时被使用
Value 列的值,用来查询或设置此列的值
11
36 参数对象
Parameter 对象代表参数与基于参数化查询或存储过程的 Command 对象的相关参数,代
表使用普通数据类型的数据列。其常用的函数和属性分别见表 11 21 和表 11 22。
248
第十一章 数 据 库 编 程
……………………………………………………………………………………………………………………………………………
表11 21 参数对象的函数
函 数 说 明
可将长二进制或字符数据填写到域对象中。在系统内存有限的情况下,可以使
AppendChunk()
用 AppendChunk 方法对长整型值进行部分而非全部的操作
表11 22 参数对象的属性
属 性 说 明
表示参数的特性,这个属性包含了几个字节标志的组合。标志用于指示参数是
Attribute
否接收有符号,0 或者长数据值
指示 Parameter 所指的是输入参数、输出参数还是既输入又输出参数,及该参数
Direction
是否为存储过程返回的值
Name 参数对象的名称
NumericalScale 在浮点数中用于指出小数点右边多少位用于表示这个值
Precision 表示参数对象值的精度
Type 列中的值的数据类型
Size 表示 Parameter 对象的最大长度(按字节或字符)
Value 包含分配给参数的实际值
11
37 错误对象
错误随时可在应用程序中发生,常见的有无法建立连接、无法执行命令或对某些状态的
对象无法进行操作(例如,试图使用没有初始化的记录集 )。 每当错误出现时,一个或多个
Error 对象将被放到 Connection 对象的 Errors 集合中。当另一个错误产生时,Errors 集合
将被清空,并在其中放入新的 Errors 对象集。其常用的属性见表 11 23。
表11 23 参数对象的属性
属 性 说 明
Description 包含错误的文本
NativeError 使用该属性可对特殊 Error 对象检索特定的错误信息
Number 包含错误常量的长整型整数值
标识产生错误的对象。在向数据源发出请求之后,Errors 集合中如有多个 Error
Source
对象,该属性将十分有用
使用该属性读取由提供者在处理 SQL 语句过程中出现错误返回的 5 个字符的错
SQLState
误代码
Value 包含分配给参数的实际值
11
38 集合
ADO 提供“集合”,这是一种可方便地包含其他特殊类型对象的对象类型 。使用集合方法
可按名称(文本字符串)或序号(整型数)对集合中的对象进行检索。 ADO 提供的四种集合分
述如下:
(1)Errors 集合
Errors 集合包含为响应涉及提供者的单个错误而创建的所有 Error 对象。 任何涉及 249
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
函 数 说 明
对 Errors 集合使用 Clear 方法,来删除集合中现有的全部 Error 对象。发生错误
Clear()
时,ADO 将自动清空 Errors 集合,并以基于新错误的 Error 对象填充集合
Item() 返回集合中的 Error 对象
函 数 说 明
Append() 添加新的参数对象到集合
Delete() 从集合中删除参数对象
Refresh() 用于进程或者参数化命令中,从有关参数的提供程序那里收集信息
Item() 返回集合中的参数对象
函 数 说 明
Append() 添加新的 Field 对象到集合
Delete() 从集合中删除 Field 对象
Refresh() 更新集合中的 Field 对象
Item() 返回集合中的 Field 对象
特性相对应。
Properties 集合的常用函数见表 11 27。
表11 27 Pr
ope
rti
es集合的函数
函 数 说 明
Refresh() 更新集合中的 Property 对象
Item() 返回集合中的 Property 对象
11
39 ADO 数据库编程实例
【例11 2】 Ex11_2 后台采用 ACCESS 建立 StuMsg.mdb 数据库(同例 11 1)。 使用 ADO
技术访问数据库,实现对数据库的浏览。具体说明见表 11 28。
表11 28 例11 2具体说明
项 目 图示和说明
程序界面
(1)程序带有一后台数据库 StuMsg.mdb
程序功能
(2)通过操作工具栏可以实现对数据库的浏览
(1)新建一单文档应用程序 Ex11_2
一直按“下一步”,到 step 6 of 6 选择 FormView 视图。
(2)编辑界面,添加控件资源(见例 11 1 的表 11 7)
(3)引入 ADO 动态链接库
在VC++中通过在程序中使用预编译指令 # import 来告诉编译器将此指令中指定的动
态链接库引入工 程 中,并 从 动 态 链 接 库 中 取 出 其 中 的 对 象 和 信 息,产 生 msado15.tlh 和
ado15.tli两个头文件来定义 ADO 库 。 在应用程序的文件 stdafx.h 中加入的语句见清单
11 12。
清单11 12 引入 ADO 动态链接库(在s
tda
f h文件中)
x.
ADO 库是一组 COM 动态库,在调用 ADO 前,必须初始化 OLE/COM 库环境。 在 MFC 应用程
序里,一个比较好的方法是在应用程序类的 InitInstance 成员函数中初始化 OLE/COM 库环
境。见清单 11 14。
(5)创建 ADO 与数据源的连接
ADO 与 数 据 源 的 连 接 是 通 过 连 接 智 能 指 针 来 创 建 的。 首 先 需 要 添 加 一 个 指 向
Connection 对象的指针,然后调用 CreateInstance ()来创建一个连接对象的实例,再调用
Open()函数来创建与数据源的连接。
① 定义变量 pADOConnect(见清单 11 13)。
11_
清单11 13 定义变量(在 Ex 2.h文件中)
c
lassCEx 11_2Apppub l
icCWi nApp
public:
CEx11_2App();
_ConnectionPtr m_pADOConn; //定义 ADO 数据库连接对象指针
_RecordsetPtr m_pADOSet; //在(6)②中使用
CString VariantToCString(VARIANT var); //数据类型转换函数,在(6)②中使用
};
extern CEx11_2App theApp; //在(6)②中使用
② 创建连接对象(见清单 11 14)。
11_
清单11 14 初始化 OLE/COM 库环境(在 Ex 2.pp文件中)
c
252 (6)获得记录集,在界面上显示数据库数据
第十一章 数 据 库 编 程
……………………………………………………………………………………………………………………………………………
11_
清单11 15 数据类型转换函数(在 Ex 2.pp文件中)
c
CS
tri
n gCEx 11_2V i
ew Varian
tToCS tr
ing(VARIANTv a //数据类型转换
r)
{ CString strValue;
_variant_t var_t;
_bstr_t bst_t;
time_t cur_time;
CTime time_value;
COleCurrency var_currency;
switch(var.vt)
{case VT_EMPTY:strValue = _T("");break;
case VT_UI1:strValue.Format ("%d" ,var.bVal);break;
case VT_I2:strValue.Format ("%d" ,var.iVal);break;
case VT_I4:strValue.Format ("%d" ,var.lVal);break;
case VT_R4:strValue.Format ("%f" ,var.fltVal);break;
case VT_R8:strValue.Format ("%f" ,var.dblVal);break;
case VT_CY:
var_currency = var;
strValue = var_currency.Format(0);
break;
case VT_BSTR:
var_t = var;
bst_t = var_t;
strValue.Format ("%s" ,(const char* )bst_t);
break;
case VT_NULL:strValue = _T("");break;
case VT_DATE:
cur_time = var.date;
time_value = cur_time;
strValue = time_value.Format("%A,%B%d,%Y" );
break;
case VT_BOOL:strValue.Format ("%d" ,var.boolVal);break;
default:strValue = _T("");break;
return strValue;
11_
清单11 16 定义函数(在 Ex 2Vi h文件中)
ew.
c
las 11_
sCEx 2Viewpub l
icCFormV
iew
protected:
void ShowTable();//显示表中的数据
}
清单11 17 在界面上显示数据库数据(
Ex11_
2Vi
ew.
cpp文件中) 253
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
v
oidCEx 11_
2V iew ShowTa be()
l //显示表中的数据
{ _variant_t vstrSQL;
vstrSQL =" SELECT * FROM Studenttab" ;//从数据表中读数据
try
HRESULT hTRes;
hTRes = theApp.m_pADOSet.CreateInstance(_T("ADODB.Recordset" ));
if (SUCCEEDED(hTRes))
{ hTRes = theApp.m_pADOSet ->Open(vstrSQL,
_variant_t((IDispatch * )(((CEx11_2App* )AfxGetApp())->m_pADOConn),
true),adOpenDynamic,adLockPessimistic,adCmdText);
if(SUCCEEDED(hTRes))
{ TRACE(_T("连接成功! \n" ));
if(theApp.m_pADOSet ->BOF)
{ return;
if(theApp.m_pADOSet ->MoveFirst()= = S_OK)
{ m_strStuID = theApp.VariantToCString( theApp.m_pADOSet ->GetCollect("StuID" ));
m_strStuName = theApp.VariantToCString( theApp.m_pADOSet ->GetCollect("StuName" ));
m_strStuNativePlace = theApp.VariantToCString( theApp.m_pADOSet ->GetCollect( "StuNativePlace" ));
m_strStuClass = theApp.VariantToCString( theApp.m_pADOSet ->GetCollect("StuClass" ));
m_strStuCollege = theApp.VariantToCString( theApp.m_pADOSet ->GetCollect("StuCollege" ));
UpdateData(false);
}
}
}
}end try
catch(_com_error e) //捕捉异常
{ CString errormessage;
MessageBox("ShowTable 创建 db 记录集失败 1! + strSQL" );
}
}
v
oidCEx11_2V i
ew OnIni
tial
Upd a
te()//初始化视图
{ CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
ShowTable();//显示数据表中的记录
}
工具栏 ID 号 工具栏按钮消息函数
ID_RECORD_FIRST OnRecordFirst()
ID_RECORD_PREV OnRecordPrev()
254
第十一章 数 据 库 编 程
……………………………………………………………………………………………………………………………………………
续 表
工具栏 ID 号 工具栏按钮消息函数
ID_RECORD_NEXT OnRecordNext()
ID_RECORD_LAST OnRecordLast()
② 编写工具栏按钮消息函数(见清单 11 18)。
11_
清单11 18 遍历记录集(在 Ex 2Vi
ew.
cpp文件中)
v
oidCEx 11_ 2ViewOnRe cordF i
rst()//首记录
{ theApp.m_pADOSet ->MoveFirst();
try
m_strStuID = theApp.VariantToCString(theApp.m_pADOSet ->GetCollect("StuID" ));
m_strStuName = theApp.VariantToCString(theApp.m_pADOSet ->GetCollect("StuName" ));
m_strStuNativePlace = theApp.VariantToCString(theApp.m_pADOSet ->GetCollect
("StuNativePlace" ));
m_strStuClass = theApp.VariantToCString(theApp.m_pADOSet ->GetCollect("StuClass" ));
m _strStuCollege = theApp.VariantToCString(theApp.m_pADOSet ->GetCollect
("StuCollege" ));
UpdateData(false);
}
catch(_com_error*e)
{AfxMessageBox(e ->ErrorMessage());
}
}
v
oidCEx 11_ 2ViewOnRe cordPr ev()//前一记录
{ theApp.m_pADOSet ->MovePrevious();
if(theApp.m_pADOSet ->BOF)
{ AfxMessageBox("已经是首记录!");
theApp.m_pADOSet ->MoveLast();
}
try
//以下同 OnRecordFirst()
}
v
oidCEx 11_ 2ViewOnRe cordNe xt()//下一记录
{ theApp.m_pADOSet ->MoveNext();
if(theApp.m_pADOSet ->EOF)
{ AfxMessageBox("已经是末记录!");
theApp.m_pADOSet ->MoveLast();
}
try
//以下同 OnRecordFirst()
}
v
oidCEx 11_ 2ViewOnRe cordLa st()//末记录
{ theApp.m_pADOSet ->MoveLast();
try
//以下同 OnRecordFirst()
}
(8)编译和运行
【例11 3】 Ex11_3 后台采用 SQL Server2000 建立 StuMsg 数据库,数据库表同例 11 1。 255
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
使用 ADO 技术访问数据库,实现对数据库的浏览。其余同例 11 2。
第一部分:数据库部分
(1)用 SQL Server 建立数据库 StuMsg
① 启动 SQL Server 服务管理器(见图 11 13)。
② 运行 SQL Server 企业管理器,并新建数据库 StuMsg(见图 11 14)。
图11 13 SQLS
erv
er服务管理器 图11 14 SQLS
erv
er企业管理器
11_
清单11 19 定义变量(在 Ex 3.h文件中)
c
lassCEx 11_2Apppub l
icCWi nApp
public:
CEx11_2App();
_ConnectionPtr m_pADOConn; //定义 ADO 数据库连接对象指针
_RecordsetPtr m_pADOSet; //在(6)②中使用
Bool ADOExecute(_RecordsetPtr &pADOSet, _variant_t &strSQL); //在(6)②中使用
CString VariantToCString(VARIANT var); //数据类型转换函数,在(6)②中使用
};
extern CEx11_2App theApp; //在(6)② 中使用
② 创建连接对象(见清单 11 20)。
11_
清单11 20 初始化 OLE/COM 库环境和创建连接对象(在 Ex 3.pp文件中)
c
图11 16 用 C
las
sWi
zad添加 Ex
r itI
nst
ane函数
c
清单11 21 Ex
itI
nst
anc 11_
e函数(在 Ex 3.pp文件中)
c
Bo
olCEx11_ 3AppEx itInst
ance()
{ if(adStateOpen = = m_pADOConn ->State)
m_pADOConn ->Close(); //释放 ADO Connection
m_pADOConn.Release();
if(adStateOpen = = m_pADOSet ->State)
m_pADOSet ->Close(); //释放 ADO RecordSet
m_pADOSet.Release();
return CWinApp::ExitInstance();
}
(6)获得记录集,在界面上显示数据库数据 257
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
① 编写数据类型转换函数(同例 11 2)。
② 定义和编写 ADOExecute 函数(定义见清单 11 13,函数体见清单 11 22)。
清单11 22 ADOEx
ecu
t 11_
e函数(在 Ex 3.pp文件中)
c
Bo
olCEx 11_
3App ADOEx ecute(_Recordse
tPtr&pADOS et_variant_t&s t
rSQL)
{ if (pADOSet ->State = = adStateOpen)pADOSet ->Close();
try
pADOSet ->Open (strSQL, m_pADOConn.GetInterfacePtr(), adOpenStatic,
adLockOptimistic, adCmdUnknown);
return true;
catch(_com_error &e)
{ CString err;
err.Format("ADO Error: %s" ,(char* )e.Description());
AfxMessageBox(err);
return false;
c
las 11_
sCEx 3Viewpub l
icCFormV
iew
protected:
void ShowTable();//显示表中的数据
}
清单11 24 在界面上显示数据库数据(
Ex11_
3Vi
ew.
cpp文件中)
v
oidCEx11_ 3View ShowTa be()
l //显示表中的数据
{ __variant_t vstrSQL;
vstrSQL =" Select * from studenttab" ;
theApp.ADOExecute(theApp.m_pADOSet,vstrSQL);
theApp.m_pADOSet ->MoveFirst();
m_strStuID = theApp.VariantToCString(theApp.m_pADOSet ->GetCollect("StuID" ));
m_strStuName = theApp.VariantToCString(theApp.m_pADOSet ->GetCollect("StuName" ));
m_strStuNativePlace = theApp.VariantToCString(
theApp.m_pADOSet ->GetCollect(
"StuNativePlace" )
);
m_strStuClass = theApp.VariantToCString(theApp.m_pADOSet ->GetCollect("StuClass" ; ))
m_strStuCollege = theApp.VariantToCString(theApp.m_pADOSet ->GetCollect("StuCollege" ));
UpdateData(false);
}
v
oidCEx11_ 3View OnInitialUpd ae()
t //初始化视图
{ CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
ShowTable();//显示数据表中的记录
}
258
第十一章 数 据 库 编 程
……………………………………………………………………………………………………………………………………………
259
第十二章 动态链接库
1 动态链接库的分类与创建
12
12
11 动态链接库分类
VC++可以建立以下四种类型的动态链接库:
(1)win32DLL,也称非 MFC
(2)使用“MFC DLL 向导”生成静态链接到 MFC 的常规 DLL
静态链接到 MFC 的 Regular DLL 是内部使用 MFC 的 DLL, DLL 中的导出函数可以被 MFC
和非 MFC 可执行程序调用。这种类型的 DLL 在建立时使用的是 MFC 静态链接库,导出函数使
用的是标准 C 接口。例如:
其中,ExportedFunction 是导出函数名。
(3)使用“MFC DLL 向导”生成动态链接到 MFC 的常规 DLL
动态链接 MFC 的 Regular DLL 是内部使用 MFC 的 DLL,DLL 中的导出函数可以被 MFC 和
非 MFC 可执行程序调用。这种类型的 DLL 在建立时使用的是 MFC 动态链接库。 导出函数使
用的是标准 C 接口,但必须使用宏 AFX_MANAGE_STATE 来设置 MFC 模块状态。 为此,必须在
所有 DLL 导出函数的开始添加如下代码行:
AFX_MANAGE_STATE(AfxGetstaticModuleState());
加对 AfxTermExtensionModule 的 一 次 调 用。 每 个 进 程 从 扩 展 DLL 分 离 时, 必 须 调 用
AfxTermExtensionModule 函数来释放扩展 DLL。如果扩展 DLL 是隐式链接到可执行程序中,
则没有必要调用 AfxTermExtensionModule 函数。
如果显示链 接 到 扩 展 DLL 的 应 用 程 序 是 多 线 程 的,那 么 应 该 使 用 AfxLoadLibrary/
AfxFreeLibrary 代替 LoadLibrary/FreeLibrary 来加载/卸载扩展 DLL。
12
12 使用 AppWi
zad创建 MFCDLL
r
使用 Appwizard 来创建 MFC DLL 的基本结构,其步骤如下:
(1)从菜单“File”中选择“New”命令,弹出“New”对话框
(2)切换到“Projects”选项卡,选择项目类型为“MFC Appwizard(dll)”(见图 12 1)
(3)在“Project name”编辑框中键入名字,如“myDll”
(4)单击“OK”按钮,弹出图 12 2 所示的对话框
(5)选择要创建动态库的类型
单 选 按 钮,表 示 创 建 静 态 链 接 到 MFC 的
“Regular DLL with MFC statically linked ”
Regular DLL。
“Regular DLL using shared MFC DLL”单选按钮,表示创建动态链接到 MFC 的 Regular DLL。
“MFC Extension DLL(using shared MFC DLL)”单选按钮,表示要创建动态链接到 MFC 的
扩展 DLL。
(6)选择类型后,单击“Finish”按钮
(7)在随后的对话框中单击“OK”按钮,完成动态链接库框架的创建
图12 1 新建 MFCAppWi
zad(
r d
ll) 图12 2 “
MFCAppWi
zar
dS
te f1”对话框
p1o
2 从 DLL 中导出函数
12
DLL 文件与可执行文件非常相似,不同点在于 DLL 包含有导出表 (Export Table)。 导出
表包含 DLL 中每个导出函数的名字,这些函数是进入 DLL 的入口点。 只有导出表中的函数
可以被外部程序调用。
从 DLL 中导出函数有三种方法:
(1)创建模块定义文件(.DEF),并在建立 DLL 时使用此文件。
262 (2)在导出函数的定义中使用关键字_declspec(dllexport)。
第十二章 动 态 链 接 库
……………………………………………………………………………………………………………………………………………
12
21 使用.
DEF文件导出函数
模块定义文件(Export Definition File,DEF)是由一个或多个用于描述 DLL 属性的模
块语句组成的文本文件。每个.DEF 文件必须至少包含以下模块定义语句 。
(1)第一个语句必须是 LIBRARY 语句,这个语句指出 DLL 的名字,链接器将这个名字放
到 DLL 导入库(import library)中。DLL 导入库中包含了指向外部 DLL 的函数索引指针。
(2)EXPORTS 语句列出导出函数的名字,以及导出函数的序数值 (由@ 号与数字构成 )。
序数值可以省略,编译器(Compiler)会为每个导出函数指定一个,但这样指定的值不如自己
指定的明确。
(3)使用 DESCRIPTION 语句描述 DLL 的用途,这个语句可以省略。
例如:
LIBRARY BTREE
DESCRIPTION" 实现二叉搜索树"
EXPORTS
Insert@ 1
Delete@ 2
Member@ 3
Min @ 4
12
22 使用关键字_
dec
lspec(
dl
lexpo
rt)
可以使用关键字_declspec(dllexport)从 DLL 导出数据、函数、类或类的成员函数。 如
果使用关键字_declspec(dllexport),则不需要.DEF 文件。
例如,可以使用以下方法导出函数:
void _declspec(dllexport)Function(void);
又如,可以使用以下方法导出类中的所有公有成员变量和成员函数:
...类定义...}
12
23 使用 AFX_
EXT_
CLASS 导出和导入
MFC 扩展 DLL 使用宏 AFX_EXT_CLASS 来导出类,而链接到扩展 DLL 的可执行文件使用这
个宏来导入类。使用宏 AFX_EXT_CLASS,扩展 DLL 和链接到扩展 DLL 的可执行文件可以使用
相同的头文件。
如果要导出整个类,那么可以在 DLL 的头文件中添加 AFX_EXT_CLASS 关键字到类的声
明中。例如:
12
24 调用约定
默认时,VC++使用C++调用约定。如果要从 C 语言模块中使用C++编写的 DLL 函数,那么
应该在函数声明中用 extern" C" 来指定。例如:
3 链接 DLL 到可执行程序
12
与应用程序不同,DLL 必须与可执行文件链接后才能使用 。链接 DLL 到可执行程序有两
种方式:隐式链接(Implicit linking)和显式链接(Explicit linking)。
12
31 隐式链接
隐式链接时,使用 DLL 的可执行文件链接到该 DLL 导入库 (.LIB 文件)中。 当加载使用
DLL 的可执行文件时,操作系统将加载 DLL。 客户端可执行文件调用 DLL 的导出函数,就好
像这些函数包含在可执行文件内部一样 。为了隐式链接 DLL,可执行文件需要从 DLL 提供者
获取以下三个文件。
(1)包含导出函数(或C++类)声明的头文件(.H)
264 (2)导入库文件(.LIB 文件)
第十二章 动 态 链 接 库
……………………………………………………………………………………………………………………………………………
12
32 显式链接
显式链接时,使用 DLL 的可执行文件在运行时通过函数调用的方式来显式加载或卸载
DLL,并通过函数指针来调用 DLL 的导出函数。
要显式链接 DLL,应用程序必须:
(1)调用 LoadLibrary 或 AfxLoadLibrary 函数来加载 DLL 并获取模块句柄。
(2)调用 GetProcessAddress 来获取应用程序要调用的导出函数的指针。由于应用程序通
过指针调用 DLL 导出函数,因此编译器不用生成外部引用,从而不用链接到 DLL 的导入库中。
(3)使用完 DLL 后,调用 FreeLibrary 或 AfxFreeLibrary 函数来卸载 DLL。
4 动态链接库编程实例
12
12
41 编程实例一———采用关键字导出、隐式链接
【例12 1】 Ex12- 1 建 立 一 动 态 链 接 到 MFC 的 常 规 DLL, 采 用 关 键 字 _ declspec
(dllexport)导出,使用隐式链接方式,具体说明见表 12 1。
表12 1 例12 1功能说明
项目 图 示 和 说 明
程序
界面
本例创建两个工程:
工程 1:创建动态链接库 Ex12_01Dll,在动态库中实现两个数的平方和及两个数的平方差
程序
计算
功能
工程 2:使用动态链接库 Ex12_01App,点击使用动态链接库对话框中的平方和,则调用动态库
中的平方和计算函数;点击对话框中的平方差,则调用动态库中的平方差计算函数
工程 1:创建动态链接库
(1)动态链接库工程项目 Ex12_01Dll
选择 File - > New,然 后 单 击 Project 选 项,选 择 MFC AppWizard (dll ),注 意 不 是 MFC
AppWizard(exe),如图 12 1 所示,输入工程名 Ex12_01Dll。点击 OK,弹出创建 MFC 常规 DLL 265
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
12_
清单12 2 动态链接库库函数(在 Ex 01D
ll.
cpp文件中)
...
CEx12_01DllApp theApp;
//以下为添加的程序
# define DllExport __declspec(dllexport)
ex
t e
rn"C"D l
l Exportd oubeMyAdd(
l doub
lexdo
ubey)
l //平方和计算函数
{ AFX_MANAGE_STATE(AfxGetStaticModuleState());
return x* x+ y* y;
e
xte
rn"C"D l
l Exportd oubl
eMyMi
nus(doublexdoubley) //平方差计算函数
{ AFX_MANAGE_STATE(AfxGetStaticModuleState());
return x* x- y* y;
表12 3 成员变量和消息函数
266 (4)编写消息函数(见清单 12 3)
第十二章 动 态 链 接 库
……………………………………………………………………………………………………………………………………………
12_
清单12 3 消息函数(在 Ex 01AppD
lg.
cpp文件中)
v
oidCEx12_01AppD lgOnAdd() //平方和
{ UpdateData(true);
m_result = MyAdd(m_x,m_y);
UpdateData(false);
}
v
oidCEx 12_01AppD lgOnMi nus()//平方差
{ UpdateData(true);
m_result = MyMinus(m_x,m_y);
UpdateData(false);
}
(5)导入动态链接库函数的说明(见清单 12 4)
12_
清单12 4 导入动态链接库库函数 (在 Ex 01AppD
l h文件中)
g.
(6)编译设置
选择 Project - > Settings,在弹出的
对话框 Project Settings 对话框 中 选 择
标签 link,在 Object/library modues 中
输入 Ex12_01Dll.lib,见图12 3。
(7)拷贝文件
将 Ex12_ 01Dll.dll、Ex12_ 01Dll.lib
文件拷贝到系统目录 (windows\system 或
windows\system32)下,也可以选择其他目
录,如使用该动态链接库的 Ex12_01App 工
图12 3 Pr
oje
ctS
ett
ins对话框
g
程目录下 (建议在做习题时选择此方法)。
(8)编译、链接和运行 Ex12_01App.exe
编译、链接 EX12_01App 工程生成 Ex12_01App.exe 文件,运行 Ex12_01App.exe,即可进行
平方和及平方差的计算。
12
42 编程实例二———采用.
DEF导出、显式链接
【例12 2】 建立一动态链接到 MFC 的常规 DLL,采用.DEF 导出,显式链接方式。 具体
功能与例 12 1 相同。
工程 1:创建动态链接库
(1)创建动态链接库工程项目 Ex12_02Dll
创建 Regular DLL using shared MFC 动态链接库。
(2)编写动态链接库中的函数(见清单 12 5)
267
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
12_
清单12 5 动态链接库库函数(在 Ex 02D
ll.
cpp文件中)
CEx12_02DllApp theApp;
//以下为添加的程序
doub
leMyAdd( do ub
lex do
ubl //平方和计算函数
ey)
{ AFX_MANAGE_STATE(AfxGetStaticModuleState());
return x*x+y*y;
do
ubl
eMyMi nus(doub
l ex
doubey)
l //平方差计算函数
{ AFX_MANAGE_STATE(AfxGetStaticModuleState());
return x*x-y*y;
(3)编写导出函数说明的.DEF 文件(见清单 12 6)
12_
清单12 6 导出函数说明(在 Ex 02D
ll.
def文件中)
(4)编译、链接和生成
编译、链接 Ex12_02Dll 工程项目,生成 Ex12_02Dll.dll 和 Ex12_02Dll.lib 文件。
工程 2:使用动态链接库
(1)新建一基于对话框的可执行程序 Ex12_02App
(2)~(3)步同例 12 1 工程 2
(4)编写消息函数(见清单 12 7)
12_
清单12 7 消息函数(在 Ex 02AppD
lg.
cpp文件中)
v
oidCEx 12_02AppD lg OnAdd()//平方和
{ UpdateData(true);
//加载动态链接库
HINSTANCE hInstance = NULL;
hInstance = LoadLibrary("Ex12_02Dll.dll" );
if(hInstance = = NULL)
{ MessageBox("加载动态库失败!");
return;
//得到函数地址
typedef double(* MYADD)(double,double);
MYADD SquareAdd = NULL;
SquareAdd = (MYADD)GetProcAddress(hInstance," MyAdd" );
//计算平方和
m_result = SquareAdd (m_x,m_y);
UpdateData(false);
//卸载动态链接库
if(hInstance != NULL)
268
第十二章 动 态 链 接 库
……………………………………………………………………………………………………………………………………………
FreeLibrary(hInstance);
}
v
oidCEx 12_01AppD lg OnMinus()//平方差
{ UpdateData(true);
//加载动态链接库
HINSTANCE hInstance = NULL;
hInstance = LoadLibrary("Ex12_02Dll.dll" );
if(hInstance = = NULL)
{ MessageBox("加载动态库失败!");
return;
//得到函数地址
typedef double(* MYMINUS)(double,double);
MYMINUS SquareMinus = NULL;
SquareMinus = (MYMINUS)GetProcAddress(hInstance," MyMinus" );
//计算平方差
m_result = SquareMinus (m_x,m_y);
UpdateData(false);
//卸载动态链接库
if(hInstance != NULL)
FreeLibrary(hInstance);
}
(5)拷贝文件
将 Ex12_02Dll.dll 拷贝到 Ex12_02App 工程目录下。
(6)编译、链接和运行 Ex12_02App.exe
编译、链接 Ex12_02App 工程,生成 Ex12_02App.exe 文件;运行 Ex12_02App.exe,即可进
行平方和及平方差的计算。
12
43 编程实例三———扩展 MFC 的 DLL
【例12 3】 建立一扩展 MFC 的 DLL,采用 AFX_EXT_
CLASS 导出类,隐式链接方式。具体功能与例 12 1 相同。
工程 1:创建动态链接库 MFC AppWizard(dll)
(1)创建动态链接库工程项目 Ex12_03Dll
创建 MFC Extension DLL 动态链接库。
(2)新 建 编 写 动 态 平 方 和 及 平 方 差 的 计 算 函 数
类 CSquareCal
选择 Insert - > NewClass ...,新建 SquareCal 类,见图
12 4。
图12 4 新建类对话框
(3)在 CSquareCal 类导出与函数定义(见清单 12 8)
清单12 8 类导出与函数定义(在 Squ
areCa
l.h文件中)
c
lasAFX_
s EXT_ CLAS SCSqu
areCa
lpub
licCo
bje
ct//导出整个类
{ public:
CSquareCal();
269
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
virtual ~CSquareCal();
double MyAdd(double x,double y);//定义平方和计算函数
double MyMinus(double x,double y);//定义平方差计算函数
};
(4)编写动态链接库中的函数(见清单 12 9)
# include" stdafx.h"
# include" SquareCal.h"
# ifdef _DEBUG
# undef THIS_FILE
static char THIS_FILE= __FILE__;
# define new DEBUG_NEW
# endif
CSquareCal CSquareCal()//计算类构造函数
{ }
CSquareCal~CSqu a r
eCal()//计算类析构函数
{ }
doubleCSqu areCal MyAdd(d o
ubl
ex do
ubl //平方和计算函数
ey)
{ return x* x+ y* y;
do
ubl
eCSquareCal MyMi s(
nu d
oub
lex
doub
l //平方差计算函数
ey)
{ return x* x- y* y;
(5)编译、链接和生成
编译、链接项目,如果成功,则生成 Ex12_03Dll.dll 和 Ex12_03Dll.lib 文件。
工程 2:使用动态链接库
(1)新建一基于对话框的可执行程序 Ex12_03App
(2)~(3)同例 12 1
(4)编写消息函数(见清单 12 10)
12_
清单12 10 消息函数(在 Ex 03AppD
lg.
cpp文件中)
v
oidCEx12_03AppD lgOnAdd() //平方和计算
{ UpdateData(true);
CSquareCal cal;
m_result = cal.MyAdd(m_x,m_y);
UpdateData(false);
}
v
oidCEx12_03AppD lgOnMi nus()//平方差计算
{ UpdateData(true);
CSquareCal cal;
m_result = cal.MyMinus(m_x,m_y);
UpdateData(false);
}
# include" SquareCal.h"
(6)编译设置(方法同例 12 1)
选择 Project - > Settings,在 弹 出 的 Project Settings 对 话 框 中 选 择 标 签 link,在
Object/library modues 中输入 Ex12_03Dll.lib。
(7)拷贝文件
将 Ex12_03Dll.dll 和 Ex12_03Dll.lib 拷贝到 Ex12_03App 工程目录下。
(8)编译、链接和运行 Ex12_03App.exe
编译、链接 Ex12_03App 工程,生成 Ex12_03App.exe 文件,运行 Ex12_03App.exe,即可进
行平方和及平方差的计算。
12
44 编程实例四———动态链接库嵌套
【例12 4】 动态链接库的嵌套调用。 建立一扩展 MFC 的 DLL,采用 AFX_EXT_CLASS 导
出对话框类,隐式链接方式,具体说明见表 12 4。
表12 4 例12 4 功能说明
项目 图 示 和 说 明
程序
界面
图(
a) 可执行程序界面 图(
b) 动态链接库界面
本例创建两个工程:
工程 1:创建动态链接库 Ex12_04Dll,在动态库中实现动态库嵌套调用,实现两个数的平方和
及两个数的平方差计算,界面见图(b)
程序
工程 2:调用动态链接库 Ex12_04App,界面见图(a)。点击“调用动态库...”按钮,则显示右图
功能
的对话框,在对话框的 x 和 y 中输入数据,然后点击“平方和”或“平方差”按钮即可将
计算的结果显示在结果对编辑框中,按“确定”按钮,可将右边对话框中的结果导入左
边对话框的结果编辑框中
工程 1:创建动态链接库
(1)创建动态链接库工程 Ex12_04Dll
创建 MFC Extension DLL 动态链接库。
(2)添加对话框资源和创建对话框类
① 插入对话框资源 IDD_SQUARECALDLLDLG 到 Ex12_04Dll 工程中。
② 添加控件(按表 12 4 中图(b)所包含的控件添加)。
③ 为 IDD_SQUARECALDLLDLG 对话框创建 CSquareCalDllDlg 类,基类为 CDialog。
(3)平方和与平方差的实现
① 添加控件变量和消息函数(见例 12 1 表 12 3)。
② 编写消息函数(见清单 12 11)。 271
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
# include" stdafx.h"
# include" resource.h"
# include" SquareCalDllDlg.h"
# include" SquareCal.h"//包含动态库计算类头文件
v
oi dCSqu areCalD l
lDlgOnAdd() //单击平方和按钮
{ UpdateData(true);
CSquareCal cal;
m_result = cal.MyAdd(m_x,m_y);
UpdateData(false);
}
v
oi dCSqu areCalD l
lDlgOnMi nus()//单击平方差按钮
{ UpdateData(true);
CSquareCal cal;
m_result = cal.MyMinus(m_x,m_y);
UpdateData(false);
}
(5)编译设置
选择 Project - > Settings,在 弹 出 的 Project Settings 对 话 框 中 选 择 标 签 link,在
Object/library modues 中输入 Ex12_03Dll.lib。
(6)拷贝文件
将 Ex12_03Dll.dll、
Ex12_03Dll.lib 和 SquareCal.h 文件拷贝到 Ex12_04Dll 工程目录下。
(7)编译、链接和生成
编译、链接 EX12_04Dll 工程项目,生成 Ex12_04Dll.dll 和 Ex12_04Dll.lib 文件。
工程 2:使用动态链接库
(1)新建一基于对话框的可执行程序 Ex12_04App
272 (2)动态链接库 Ex12_04Dll 的调用
第十二章 动 态 链 接 库
……………………………………………………………………………………………………………………………………………
① 添加控件、控件成员变量和消息函数(见表 12 5)。
表12 5 成员变量和消息函数
② 编写消息函数(见清单 12 13)。
12_
清单12 13 消息函数(在 Ex 04AppD
l h文件中)
g.
…
#inc
lud
e"Squ areCalDl
lDlg. h"
v
oidCEx12_04AppD lgOnLo add
l //单击“调用库...”按钮
l()
{ CSquareCalDllDlg dlg;
if(dlg.DoModal()= = IDOK)
{ m_result = dlg.m_result;
UpdateData(false);
}
}
12
45 编程实例五———带数据库的动态链接库
【例12 5】 在上题中增加数据库,实现在动态链接库中使用数据库,具体见表 12 6。
表12 6 例12 5 功能说明
项目 图 示 和 说 明
程序
界面
图(
a) 可执行程序界面 图(
b) 带数据库的动态链接
273
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
续 表
项目 图 示 和 说 明
本例创建两个工程:
工程 1 为创建动态链接库 Ex12_04Dll,界面见图(b),有两个功能;功能一:在动态库中实现动
态库嵌套调用,实现两个数的平方和及两个数的平方差计算。功能二:动态库与数据库相结
程序 合,界面中 x、y 和结果编辑框可显示数据库中的数据
功能 工程 2 为调用动态链接库 Ex12_04App,界面见图(a),点击“调用动态库...”按钮,则显示右图
的对话框,在对话框的 x 和 y 中输入数据,然后点击平方和或平方差按钮即可将计算的结果
显示在结果对编辑框中,按确定按钮,可将右边对话框中的结果导入左边对话框的结果编辑
框中
第一部分:数据库的建立与注册
在 Access 平台下建立 Squaredb.mdb 数据库,创建 Squaredbtab 数据表,数据表的设计
见表 12 7。
表12 7 Squ
are
dbt
ab数据表
字段名 ID x y result
数据类型 int double double double
数据库建立完成后,注册数据库,注册数据库名为 Squaredb。
第二部分:VC++实现部分
工程 1:创建动态链接库
动态链接库与数据库采用 ODBC 链接方式。
(1)界面设计
修改上题动态链接库中的对话框资源,添加控件 (见表 12 8,修改后的界面见表 12 6
中的图(b))。
(2)添加单击 4 个下压按钮的消息函数(见表 12 8)
表12 8 各控件和成员变量
控件 标题(Caption) ID 号 消息函数
下压按钮 首记录 IDC_RECORD_FIRST OnRecordFirst()
下压按钮 上一条 IDC_RECORD_PRV OnRecordPre()
下压按钮 下一条 IDC_RECORD_NEXT OnRecordNext()
下压按钮 末记录 IDC_RECORD_LAST OnRecordLast()
(3)创建数据库记录集类
① 选择菜 单 Insert - > New Class ..., 弹 出 New Class 对 话 框 (见 图 12 5 ), 创 建
CSquaredbSet 类, 在 Name 编 辑 框 中 填 写 CSquaredbSet, 在 Base class 组 合 框 中 选 择
CRecordset,然后点击 OK。
② 在弹出的 Database Options 对话框(见图 12 6)中选择 ODBC 中的 Squaredb,然后点
击 OK。
③ 在弹出的 Select DataBase Tables 对话框(见图 12 7)中选择 Squaretab,然后点击
OK。这样就完成 CSquaredbSet 的建立。
274
第十二章 动 态 链 接 库
……………………………………………………………………………………………………………………………………………
图12 5 新建记录集类对话框
(4)类导出的声明,定义记录集类的指针对象(见清单 12 14)
清单12 14 类导出的声明,定义记录集类的指针对象(在 Squ
areCa
lDl
lDl
g.h文件中)
# include" SquaredbSet.h"//包含记录集的头文件
c
lassAFX_ EXT_ CLAS SCSqu areCal
Dll
Dlgpub
licCD
ial
og
...
public:
CSquaredbSetm_ pSet//定义记录集类指针对象
...
}
(5)添加对话框初始化的函数、编写消息函数(见清单 12 15)
清单12 15 On
Ini
tDi
alg()(在 Squ
o areCa
lDl
lDl
g.pp文件中)
c
m_pSet ->MoveFirst();
m_x = m_pSet ->m_x;
m_y = m_pSet ->m_y;
UpdateData(false);
return TRUE;//return TRUE unless you set the focus to a control
v
oidCSqua reCalDl
l D
lgOnAdd()
{ UpdateData(true);
CSquareCal cal;
m_result = cal.MyAdd(m_x,m_y);
UpdateData(false);
}
v
oidCSqua reCalDl
l D
lgOnMi nus()
{ UpdateData(true);
CSquareCal cal;
m_result = cal.MyMinus(m_x,m_y);
UpdateData(false);
}
v
oidCSqua reCalDl
l D
lgOnRe cordFi
rst()
{ m_pSet ->MoveFirst();
m_x = m_pSet ->m_x;
m_y = m_pSet ->m_y;
UpdateData(false);
}
v
oidCSqua reCalDl
l D
lgOnRe cordPre()
{ m_pSet ->MovePrev();
if(m_pSet ->IsBOF())
{ AfxMessageBox("已是首记录!");
m_pSet ->MoveFirst();
}
m_x = m_pSet ->m_x;
m_y = m_pSet ->m_y;
UpdateData(false);
}
v
oidCSqua reCalDl
l D
lgOnRe cordNext()
{ m_pSet ->MoveNext();
if(m_pSet ->IsEOF())
{ AfxMessageBox("已是末记录!");
m_pSet ->MoveLast();
}
m_x = m_pSet ->m_x;
m_y = m_pSet ->m_y;
UpdateData(false);
}
v
oidCSqua reCalDl
l D
lgOnRe cordLast()
{ m_pSet ->MoveLast();
m_x = m_pSet ->m_x;
m_y = m_pSet ->m_y;
UpdateData(false);
}
276
第十二章 动 态 链 接 库
……………………………………………………………………………………………………………………………………………
(6)编译、链接和生成
编译、链接 Ex12_04Dll 工程项目,生成 Ex12_04Dll.dll 和 Ex12_04Dll.lib 文件。
工程 2:使用动态链接库
与上题相同。但要注意将 SquareCalDllDlg.h 文件拷贝到 Ex12_04App 工程目录下后,
打开该 文 件 然 后 注 释 # include " SquaredbSet. h" 和 “CSquaredbSet * m _ pSet", 见 清 单
12 16。
清单12 16 注释#includ
e"Squa
redbS
e h" 和 CSqu
t. are
Setm_
pSe
t
(在 Squa
reCa
lDl
lDlg.h文件中)
//#include"Squ
aredbSe
t.h"//包含记录集的头文件
cl
assAFX_ EXT_CLAS SCSqu areCalDllDl
gpub
licCD
ial
og
...
public:
//CSquaredbSet * m_pSet;//定义记录集类指针对象
...
}
277
第十三章 Ac
tieX 控件
v
13
1 Ac
tiveX 的基本概念
为了方便快捷 地 在 同 时 运 行 的 多 个 应 用 程 序 中 交 换 信 息,Microsoft 发 展 了 OLE 与
ActiveX 技术。OLE 最初是对象的链接与嵌入(Object Linking and Embedding)技术,后来发
展为复合文档技术,不同类型的文件包括文字、图片、声音、动画和视频等可以共同存在于一
个文档中,它们可以由不同的应用程序产生,同时也可以在该文档中编辑。 ActiveX 技术来
源于 OLE 技术,ActiveX 技术主要是指在网络环境下基 于 组 件 对 象 模 型 (COM: Component
Object Model)的软件模块之间的通信与交互。OLE 和 ActiveX 的基本技术是 COM。
13
11 组件对象模型 COM
COM(Component Object Model)是 Windows 对象的一个二进制标准,是一种跨应用和语言
共享二进制代码的方法。COM 是一种协议,建立了一个软件模块同另一个软件模块之间的连
接,然后将其描述出来。 当这种连接建立起来后,两个软件模块之间就可以通过一个称为
“接口”的机制来进行通讯。
COM 对象也称为 COM 组件,它是 COM 类的特殊实例,它可以包含许多通过其接口来访问
的函数 (即 方 法 ), COM 对 象 至 少 必 须 拥 有 IUnknown 接 口。 CoClass (Component Object
Class)即 COM 类是包含在一个 DLL 或者一个 EXE 中,其代码隐藏在一个或多个接口背后,
CoClass 是接口的实现。
(1)接口(Interface)
接口就是两个不同对象间的一种连接方式。 计算机程序是通过一组函数进行连接的,
这组函数定义了程序中不同部分的接口 。 DLL 的接口就是它所输出的那些函数。 C++类的
接口就是该类的成员函数集。COM 中的接口是一组由组件实现的提供给客户使用的函数,这
些函数称为方法,用于控制应用程序对 COM 对象的访问。 接口名称带 “I ”字母前缀,例如
278 Iunknown、IShellLink 等等。C++中,接口类只包含纯虚函数,接口可以继承于其他接口,和
第十三章 Act
iveX 控件
……………………………………………………………………………………………………………………………………………
C++普通类的继承类似,但不允许多重继承。
GUID(Global Unqiue Identity Code,全局统一标识符 )是一个 128 位数字 (16 字节),它
作为 COM 语言 无 关 性 的 一 个 标 识,是 一 个 具 有 全 球 唯 一 性 的 标 识 符。 有 时 会 遇 到 UUID
(Universally Unique Identifier Code),作用和 GUID 是一样的。
在 Windows 平台上,GUID 应用非常广泛:类、接口标识、数据库甚至自动生成的机器名、
目录名等。Class ID(或者称 CLSID)是 CoClass 的 GUID;Interface ID(或者称 IID)是一个接
口的 GUID。
(2)IUnknown 接口
COM 技术规范规定,每个 COM 组件都必须支持 IUnknown 接口,为 COM 组件定义的每个接
口都必须是由 IUnknown 派生而来的。 IUnKnown 的定义在 WIN32 SDK 中的 UNKNOWN 头文件
中。IUnKnown 的定义见清单 13 1。
清单13 1 IUnKn
own的定义
i
nte
rfaceIUnKn own
virtual HRESULT __stdcall QueryInterface(const IID& iid,void * * ppv)= 0;
virtual ULONG __stdcall AddRef()= 0;
virtual ULONG __stdcall Release()= 0;
}
IUnKnown 有三个方法(三个函数):
① AddRef()———告诉 COM 对象增加其引用计数,如果获取一个接口指针的副本,应该调
用这个方法增加引用记数;
② Release()———告诉 COM 对象减少其引用计数;
③ QueryInterface()———客户可以通过此函数来查询组件是否支持某个特定的接口 。
QueryInterface 函数返回一个指向组件支持的接口的指针 。 如果 QueryInterface 函数没
有找到组件支持的接口则返回的指针是 NULL。
QueryInterface()的原型是:
HRESULT IUnKnown::QueryInterface(REFIID iid, void* * ppv)
参数:iid,它是一个结构,接口标识符结构,iid(IID)标识了客户所需的接口,每一个接
口都有一个唯一的接口标识符,所以某个与 iid 相对应的接口绝对不会发生变化;
ppv,指向接口地址的指针。
13
12 对象链接与嵌入
对象的链接与嵌入是存储由另一个应用程序产生的 OLE 文档项目的两种方法。
(1)对象链接
如在 Word 中操作:菜单插入- > 对象- > 选定由文件创建选项卡- > 输入文件名 (VC 成绩
.xl)- > 选中复选框链接到文件,单击确定。整个文件将出现在 Word 文档中。
如果对磁盘上的文件“VC 成绩.xls”进行了修改,那么,其变化就会反映到 Word 文档中;
在 Word 文档中双击这个文件,将打开 Excel 来编辑 “VC 成绩.xls”。 如果在磁盘上删除了
“VC 成绩.xls”,那么 Word 文档仍然保持上次的结果,但是不能再编辑它。
链接并不动态地增加文档的大小,因为只有文件的位置和少量的描述信息需要保留在
文档中。 279
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(2)对象嵌入
如在 Word 中操作:菜单插入- > 对象- > 选定由文件创建选项卡- > 输入文件名 (VC 成绩
.xls)- > 不选中复选框链接到文件,单击“确定”。
整个文件将出现在 Word 文档中,那么与上述有什么区别呢? 当在 Word 文档中双击这
个对象时,Word 的菜单和工具栏消失并由 Excel 等同的菜单和工具栏替换,在这里修改不会
影响到“VC 成绩.xls”文件。
嵌入后的文档比其原始的文件要大得多,但是,如果出现空间不足的问题可以删除嵌入
的原始文件。
13
13 自动化服务器与自动化控制器
(1)容器和服务器
如果要将对象嵌入或链接到另一个之中,需要一个容器和一个服务器。 容器是嵌入或
链接对象的应用程序,如上述的 Word。
服务器是创建对象并且当对象被双击时,可以被启动的应用程序,如上述的 Excel。
(2)自动化服务器与自动化控制器
自动化技术(Automation)允许一个应用程序驱动另外一个应用程序 。驱动程序称为自
动化控制器(Automation Controller)或称自动化客户,另一个为自动化服务器 (Automation
Server)。一个自动化服务器至少包含一个,也可能多个 Idispatch 接口,其他应用程序 (驱
动程序)可以创建该接口或连接到该接口上 。
13
14 Ac
tiveX 控件
ActiveX 是在进程中装入的极小自动化服务器,ActiveX 由容器应用程序 (自动化控制
器)使用。ActiveX 控件的扩展名一般是 OCX。
ActiveX 控件是 32 位控件,等同于 OLE 控件。典型的控件由一个用户界面、一个单一的
IDispatch 接口和一个单一的 IConnectionPoint 接口组成,其中用户界面既表示设计时的
又表示运行时的,IDispatch 接口定义该控件的所有方法和属性,而 IConnectionPoint 接口
是用于该控件可以启动的事件的 。
ActiveX 控件通过事件告诉容器对该控件进行了操作 。 ActiveX 控件触发事件与它的
容器进行通信,容器也通过方法和属性与控件进行通信 。
ActiveX 控件的设计主要包括控件界面(外观)设计,控件的属性、方法和事件设计。
VC60 中创建 ActiveX 控件的方法一般有以下 3 种:
(1)使用 MFC ActiveXControlWizard
(2)使用 ActiveX 模板类库(ATL)
(3)使用 ActiveX 开发工具(BaseCtl Framework)
下面以 一 个 实 例 讲 述 利 用 MFC ActiveXControlWizard 开 发 ActiveX 控 件 的 方 法 和
步骤。
13
2 Ac
tiveX 控件编程实例
280 【例13 1】 开发 ActiveX 控件,具体说明见表 13 1。
第十三章 Act
iveX 控件
……………………………………………………………………………………………………………………………………………
项目 图 示 和 说 明
图(
a) 时钟控件界面
程
图(
b) 一般属性设置 图(
c) 是否显示数字时钟 图(
d) 颜色设置
图(
e) 字体设置 图(
f) 图片设置 图(
g) Al
l
13
21 Ac
tiveX 控件框架的创建
利用 MFC ActiveXControlWizard 创建
ActiveX 控件的步骤如下:
(1)从菜单 “File”中选择 “New ”命令,
弹出“New”对话框,见图 13 1。
(2)切换到“ 选项卡,选择项目
Projects”
类型为“ MFC ActiveX ControlWizard”
。
(3)在“Project name”编辑框中键入名
字,如“Ex13_01ActiveX”。 图13 1 New 对话框
281
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(4)单击“OK”按钮,弹出图 13 2 所示的对话框。
(5)单击“Next”按钮,弹出图 13 3 所示的对话框,单击“Finish”即可。
图13 2 MFCAc
tiv
eXCo
ntr
olWi
zar
ds
tep1o
f2 图13 3 MFCAc
tiv
eXCo
ntr
olWi
zar
ds
tep2o
f2
13
22 控件的类
向导共产生 3 个类,12 个文件,其中 7 个实现文件,5 个头文件。3 个类分别为:控件模块
CEx13_01ActiveXApp 类、
控件 CEx13_01ActiveXCtrl 和属性页 CEx13_01ActiveXPropPage 类,文件
见表 13 2。
表13 2 AppWi
zar 13_
d为 Ex 01Ac
tieX 创建的文件
v
文 件 名 描 述 文 件 名 描 述
Ex13_01ActiveX.cpp 控件模块类源文件 Ex13_01ActiveX.h 控件模块类头文件
Ex13_01ActiveXCtl.cpp 控件类源文件 Ex13_01ActiveXCtl.h 控件类头文件
Ex13_01ActiveXPpg.cpp 属性页类源文件 Ex13_01ActiveXPropPage.h 属性页类头文件
Ex13_01ActiveX.rc 通用资源文件 Resource.h 资源头文件
Ex13_01ActiveX.def 模块定义文件 标准 应 用 程 序 结 构
StdAfx.h
Ex13_01ActiveX.odl 对象描述语言文件 头文件
标准 应 用 程 序 结 构
StdAfx.cpp
源程序文件
(1)控件模块类
控件 模 块 类 CEx13 _ 01ActiveXApp 类 从 COleControlModule 类 派 生, 而 不 是 常 见 的
CWinApp 类。在该类的源文件中实现了 InitInstance ()函数和 ExitInstance ()函数,在源
文件中有一个随机的数字串,称做控件标识。 该数字串唯一地表示了 “Ex13_ 01ActiveX ”
控件。
(2)控件类
控件类 CEx13_01ActiveXCtrl 类由 COleControl 类派生,代表了 “Ex13_01ActiveX”控件
对象,这是用户工作的重点,用户将在该类中完成了大部分的代码添加和修改工作 。
在 CEx13_01ActiveXCtrl 类中,除了必要的构造函数和析构函数外,另外还实现了其他
的几个函数:OnDraw()函数完成控件的自我绘制工作;DoPropExchange()函数实现控件的初
282 始化和串行化;OnResetState()函数重新设置控件的状态,即重新初始化;AboutBox()函数则
第十三章 Act
iveX 控件
……………………………………………………………………………………………………………………………………………
显示控件的版权信息对话框。
在 CEx13_01ActiveXCtrl 类中,更重要的是一些映射入口 (消息映射、调度映射、事件映
射)和属性页的设置。
(3)属性页类
属性页类 CEx13_01ActiveXPage 类由 COlePropertyPage 类派生,而 COlePropertyPage
类同时又是 CDialog 类的派生类,因此 COlePropertyPage 类的表现完全类似于一个对话框
类。用户将修改 COlePropertyPage 类的资源,并添加合适的成员变量以完成设置控件属性
的工作。
13
23 Ac
tiveX 控件的测试
在控件框架建 立 后,即 可 对 控 件 进 行 测 试,控 件 测 试 可 在 控 件 容 器 中 进 行,其 步 骤
如下:
(1)选择菜单 Tools - > ActiveX Control Test Container,显示图 13 4 所示的界面
(2)插入指定的 ActiveX 控件
选择菜单 Edit - > Insert New Control,弹出 Insert Control 对话框(见图 13 4),选中
CEx13_01ActiveX control,然后单击“OK”按钮,控件出现在测试界面中,见图 13 5。
图 13 5 可以看出,控件缺省的图形是一个椭圆,这一点阅读清单 13 2 的 OnDraw 函数
可见。
清单13 2 OnDr 13_
aw()函数 (在 Ex 01Ac
tiv
eXC
trl.
cpp文件中)
v
oidCEx 13_
01Ac t
iveXCtrlOnDraw(CDC pd cc ons
tCRe ct&r cBound sconstCRe c
t&r cI
nva l
id)
{ pdc ->FillRect(rcBounds, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));
pdc ->Ellipse(rcBounds);
}
13
24 控件的外观设计
(1)定义时间成员变量和画时、分、秒钟的成员函数(见清单 13 3)
283
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
13_
清单13 3 定义有关成员变量和成员函数(在 Ex 01Ac
tiv
eXC
tr h文件中)
l.
# define PI 31415926536
c
lassCEx 13_01ActiveXCtrlpub l
icCO leControl
...
public:
CEx13_01ActiveXCtrl();
...
protected:
//声明画时、分、秒钟的成员函数
void MySec(CDC* pDC, double angle);//绘秒钟
void MyMin(CDC* pDC, double angle);//绘分钟
void MyHour(CDC* pDC, double angle);//绘时钟
//定义时间成员变量
double m_sec;//时间成员变量—秒
double m_min;//时间成员变量—分
double m_hour;//时间成员变量—时
char CurrentTime30;
long lTime;
long m_sethour;
long m_setmin;
long m_setsec;
BOOL m_IsTimer;
UINT m_timer;
~CEx13_01ActiveXCtrl();
...}
(2)在构造函数中初始化时间成员变量(见清单 13 4)
13_
清单13 4 构造函数(在 Ex 01Ac
tiv
eXC
trl.
cpp文件中)
13_
CEx 01Activ eXCt
r l
CEx13_ 01Acti
veXCtrl()//构造函数
{ InitializeIIDs(&IID_DEx13_01ActiveX, &IID_DEx13_01ActiveXEvents);
m_hour = 0;
m_min = 0;
m_sec = 0;
strcpy(CurrentTime," YYYY- MM- DD 00:00:00" ); //[0]= 0;
m_selectDispNumClock = TRUE;//见 1343
m_IsTimer = FALSE;
m_sethour = 25;
m_setmin = 0;
m_setsec = 0;
SetInitialSize(200, 200);//设定时钟初始大小
}
(3)编写画时、分、秒函数代码(见清单 13 5)
13_
清单13 5 画时、分、秒的函数(在 Ex 01Ac
tiv
eXC
trl.
cpp文件中)
v
oidCEx13_ 01Ac
tiveXC
trlMyHo
ur(
CDCpDCd
oub
lea
nge)
l //画时钟
{ CRect rect;
GetClientRect(&rect);
284 int r;
第十三章 Act
iveX 控件
……………………………………………………………………………………………………………………………………………
int x_center;
int y_center;
r = (rect.Width()> rect.Height())? (rect.Height()
/2- 20): (rect.Width()/2- 20);
x_center = rect.right/2;
y_center = rect.bottom/2;
pDC ->SetBkMode(TRANSPARENT);
pDC ->MoveTo(x_center, y_center);
pDC ->LineTo(x_center+ (r- 38)* sin(angle), y_center- (r- 38)* cos(angle));
}
v
oidCEx 13_01Ac t
iveXCtrlMyMi n(CDCpDCd o ub
lea n
gle)//画分钟
{ CRect rect;
GetClientRect(&rect);
int r;
int x_center;
int y_center;
r = (rect.Width()> rect.Height())? (rect.Height()
/2- 20): (rect.Width()/2- 20);
x_center = rect.right/2;
y_center = rect.bottom/2;
pDC ->SetBkMode(TRANSPARENT);
pDC ->MoveTo(x_center, y_center);
pDC ->LineTo(x_center+ (r- 30)* sin(angle), y_center- (r- 30)* cos(angle));
}
v
oidCEx 13_01Ac t
iveXCtrlMy Sc(
e CDCpDCd oubl
ea nl
ge)//画秒钟
{ CRect rect;
GetClientRect(&rect);
int r;
int x_center;
int y_center;
r = (rect.Width()> rect.Height())? (rect.Height()
/2- 20): (rect.Width()/2- 20);
x_center = rect.right/2;
y_center = rect.bottom/2;
pDC ->SetBkMode(TRANSPARENT);
pDC ->MoveTo(x_center, y_center);
pDC ->LineTo(x_center+ (r- 20)* sin(angle), y_center- (r- 20)* cos(angle));
}
(4)激活和撤消定时器
利用 ClassWizard 添加标准的 Windows 消息函数 OnCreate()函数和 OnDestroy()函数,
在 OnCreate()函数中用 SetTimer()函数激活定时器;在 OnDestroy()函数中添加 KillTimer
()函数撤消定时器(见清单 13 6)。
13_
清单13 6 激活和销毁定时器(在 Ex 01Ac
tiv
eXC
trl.
cpp文件中)
i
ntCEx13_01Ac tiveXCtrlOnCr eae(
t LPCREATESTRUCTl pCr
eat
eSt
rut)
c
{ if (COleControl::OnCreate(lpCreateStruct)= = - 1)
return- 1;
m_timer = SetTimer(1,1000, NULL);//激活定时器
return 0;
285
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
v
oidCEx13_01ActiveXCtrl
OnDe s
tro //销毁定时器
y()
{ COleControl::OnDestroy();
KillTimer(m_timer);
}
(5)映射定时器标识的函数 OnTimer()(见清单 13 7)
清单13 7 OnT
ime 13_
r()函数(在 Ex 01Ac
tiv
eXC
trl.
cpp文件中)
v
oidCEx13_ 01Ac t
iveXC t
rlOnT imer(UINTn IDEvent)
{ struct tm* osTime;
CTime t = CTime::GetCurrentTime();
time(&lTime);
osTime = localtime(&lTime);
strcpy(CurrentTime, asctime(osTime));
CurrentTime24= ;
CurrentTime25= 0;
osTime = t.GetLocalTm(NULL);
m_hour = PI* (osTime ->tm_hour+osTime ->tm_min/600)
/60;
m_min = PI* (osTime ->tm_min+osTime ->tm_sec/600)/30;
m_sec = PI* (osTime ->tm_sec)/30;
if(osTime ->tm_sec % 30 = = 0)
FireIsTime();//见 13262
if(osTime ->tm_hour = = m_sethour&& osTime -> tm_min = = m_setmin&& osTime -> tm_sec = = m_
setsec)
FireIsTime();
m_IsTimer = TRUE;
InvalidateControl();
COleControl::OnTimer(nIDEvent);
}
(6)编写 OnDraw()函数画时钟,显示数字时钟(见清单 13 8)
v
oidCEx13_ 01Ac t
iveXC t
rlOnDr aw( CDC pd cc ons
tCRe ct&r cBoundsconstCRe ct&r c
Inva
li d)
{ //设置,见 1342 和 1343
CBrush brush(TranslateColor(GetBackColor()));
pdc ->FillRect(rcBounds, &brush);
pdc ->SetTextColor(TranslateColor(GetForeColor()));
pdc ->SetBkMode(TRANSPARENT);
CFont* oldFont = SelectStockFont(pdc);
if(m_selectDispNumClock)
pdc ->ExtTextOut(rcBounds.left, rcBounds.top, ETO_CLIPPED, rcBounds, CurrentTime,
strlen(CurrentTime),NULL);
int r;
int x_center;
int y_center;
r =(rcBounds.Width(> rcBounds.Height(
) ))?(rcBounds.Height(
)/2- 20)
:(rcBounds.Height(
)/2- 20)
;
x_center = rcBounds.right/2;
y_center = rcBounds.bottom/2;
286
第十三章 Act
iveX 控件
……………………………………………………………………………………………………………………………………………
(7)在构造函数中设定时钟初始大小(见清单 13 4)
13
25 设置属性
环境属性和时钟的高、宽已在构造函数中设置。 下面主要说明库存属性和自定义属性
的设置。
定义库存属性
1
(1)添加库存属性
利用 ClassWizard 的 Automation 标签,如图 13 6 所示。 单击 “Add Property”按钮,弹
出如图 13 7 所示的对话框。
① 添加背景颜色 BkColor
在图 13 7 的 External name 中输入 BackColor,在 Implementation 中选择“Stock”。点
击“OK”。用同样的方法添加下面两项。
图13 6 Au
toma
tin标签
o 图13 7 添加库存属性
② 添加前景颜色 ForeColor
③ 添加字体 Font
(2)利用控件的属性页类来设置库存属性
在 Ex13_01ActiveXCtrl.cpp 文件中,定义容器所包含的页数(见清单 13 9)。 287
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
13_
清单13 9 属性页(在 Ex 01Ac
tiv
eXC
trl.
cpp文件中)
BEGIN_PROPPAGEIDS(CEx13_01ActiveXCtrl, 4)
PROPPAGEID(CEx13_01ActiveXPropPage::guid)
PROPPAGEID(CLSID_CColorPropPage)//手工添加
PROPPAGEID(CLSID_CFontPropPage)//手工添加
PROPPAGEID(CLSID_CPicturePropPage)//手工添加
END_PROPPAGEIDS(CEx13_01ActiveXCtrl)
(3)响应属性,在 OnDraw()函数中实现(见清单 13 8)
字体:字体颜色应用于数字显示时钟;
背景颜色:用于时钟背景。
自定义属性
2
用 Check Box 复选按钮选择是否显示数字时钟 。
(1)设计自定义属性资源(见图 13 8)
(2)定义自定义属性:BOOL selectDispNumClock
利用 Add Property 对话框来定义,见图 13 9。
(3)利用 ClassWizard 定义 IDC_CHK 控件变量
变量名 m_chk,变量类型 BOOL。
(4)设置默认的初始值,在构造函数中(见清单 13 10)
13_
清单13 10 构造函数(在 Ex 01Ac
tiv
eXPr
opPa
ge.
cpp文件中)
13_
CEx 01Ac tiveXPropPa
ge
CEx13_01Act
iveXPropPage()
:CO
lePr oe
p rtyPage(IDDIDS_EX13_01ACTIVEX_ PPG_ ION)
CAPT
{ //{{AFX_DATA_INIT(CEx13_01ActiveXPropPage)
m_chk = FALSE;
//AFX_DATA_INIT
(5)实现自定义属性(见清单 13 11)
13_
清单13 11 实现自定义属性 (在 Ex 01Ac
tiv
eXC
trl.
cpp文件中)
v
oi 13_
dCEx 01ActiveXCtrl
OnS
ele
ctD
ispNumC
loc
kCh
ane
gd()
{ SetModifiedFlag();}
288
第十三章 Act
iveX 控件
……………………………………………………………………………………………………………………………………………
13
26 设置事件
事件包括库存事件和自定义事件 。
添加库存事件
1
库存事件由 COleControl 类自动添加,COleControl 类包含常用行动( 如单击和双击控件、键盘
事件等)的驱动事件的预定义的成员函数。添加库存事件 Click,在 ClassWizard 的 ActiveX Events
标签中,单击“ 按钮(
Add Event” 见图 13 10)
,在 External name 组合框中选择“Click”
。
自定义事件
2
自定义事件为当时钟达到某一时刻时发送标识 。
(1)定义自定义事件:事件名 IsTime(见图 13 11)
图13 10 添加库存事件 C
lic
k 图13 11 自定义事件I
sTime
(2)编写触发事件的代码
编写定时映射函数 OnTime(),见清单 13 7。在 OnTime()函数中调用 FireIstime()。
13
27 设置方法
方法包括库存方法和自定义方法 。
定义库存方法
1
库存方法已经由 COleControl 实现,COleControl 支持 DoClick 和 Refresh 两个库存方
法。库存方法 DoClick 使用 Click 事件触发,选择 ClassWizard 的 Automation,单 击 Add
Method ...弹出 AddEvent 对话框,如图 13 12 所示定义库存方法。
289
图13 12 定义库存方法 图13 13 自定义方法
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
自定义方法
2
(1)自定义方法
定义一个函数 SetTime(),能外部调用,定时触发 IsTime 事件,添加方法见图 13 13。
(2)实现参数设置
编写内部函数 SetTime()代码,见清单 13 12。
清单13 12 S
etT 13_
ime()函数 (在 Ex 01Ac
tiv
eXC
trl.
cpp文件中)
v
oidCEx13_01Ac t
iveXCtrl
Set
Time(
lon
gst_
e h
ourl
ongs
et_mi
nl
ongs
et_
sec)
{ m_sethour = set_hour;
m_setmin = set_min;
m_setsec = set_sec;
(3)自定义方法实现
在指定的时、分、秒 内 触 发 IsTime 事 件,完 善 OnTime ()函 数。 编 译 项 目 生 成 Ex13_
01ActiveX.ocx 文件,然后在 ActiveX 控件测试容器中测试。
完成上述测试后,点击菜单 Build - > Ex13_01ActiveX.ocx,系统自动编译项目生成Ex13_
01ActiveX.ocx 文件。
13
3 Ac
tiveX 控件的注册
ActiveX 控件必须注册后才能使用,可以采用下面两种方法注册。
方法一:将 ActiveX 控件的文件* .ocx 放入 windows/system 目录下。
方法二:菜单 Tools - > Register Control
13
4 Ac
tiveX 控件应用编程实例
下面主要讲述 ActiveX 控件的应用。
【例13 2】 使用 ActiveX 控件,具体说明见表 13 3。
表13 3 例13 2具体说明
程 序 界 面 程 序 功 能
图13 14 插入 Ac
tieX 控件到项目
v 13_
图13 15 插入 Ex 01Ac
tieX 控件到界面
v
图13 16 添加 On
IsT
imeAc
tiv
ex()函数
② 编写 OnIsTimeActivex()函数(见清单 13 13)。
清单13 13 On
IsT
imeAc
tiv
e 13_
x()函数 (在 Ex 01Ac
tiv
eXApp.
cpp文件中)
v
oi 13_
dCEx 01ActiveXAppV i
ewOnI
sTimeAc
tiv
ex()
{ MessageBeep((WORD)- 1); //时间到发出一声“嘟”}
① 添加控件到界面。
添加 3 个静态文本控件、3 个编辑控件、1 个下压按钮和 1 个成组框。
② 添加成员变量和消息函数(见表 13 4)。
表13 4 各控件和成员变量
③ 编写单击设置报时的消息函数(见清单 13 14)。
清单13 14 OnS
etC
loc 13_
k()函数(在 Ex 01Ac
tiv
eXAppV
iew.
cpp文件中)
v
oidCEx13_01ActiveXAppV i
ew OnSetClock()
{ UpdateData();
m_ctrlClock.SetTime(m_sethour, m_setmin, m_setsec);//设置报时时间}
(6)编译和运行
运行 Ex13_01ActiveXApp.exe 程序,即可显示表 13 3 中所示的界面。
292
第十四章 多媒体技术
多媒体技术是综合图像、文字、
声音、动画和视频等多种媒体手段,用于传递和表达计算机
信息的技术。目前的计算机应用程序越来越多地将多媒体技术应用于应用程序设计之中。
开发多媒体应用程序,根据开发目的大致可分为三类:一是简单地调用媒体文件;二是
编辑处理媒体文件;三是控制多媒体硬件设备。本章主要讲述第一类,即调用媒体文件。
本章要点:
* 多媒体文件格式
* 多媒体播放器的制作
1 多媒体文件格式
14
多媒体文件中最具代表性的是音频文件和视频文件 。
音频文件主要格式有:波形音频文件 (.wav)、MIDI 音频文件 (.mid)、CD 音频和 MP3 文件
(.mp3)。
波形音频文件(.wav)是以数字化形式存储声音的文件,可以使用专用的工具软件编辑
序列化的波形文件,也可以使用过滤器修改。波形音频文件比较复杂,所占磁盘空间也比较
大,但几乎可以表达任何声音。
MIDI 音频文件即乐器数字接口,MIDI 文件实质上存储了一组指令,这些指令用于操作
微机的声音合成器(声卡的一部分),使用约定的乐器发出指定的声音。 因为 MIDI 文件中只
有一组指令,因此容易编辑,所占磁盘空间也较小。
CD 音频即通过计算机的 CD
ROM 驱动器来播放的音乐格式。 CD 音频代表高品质的数
字音频信息,但所需的存储空间大。
MP3 文件是一种压缩格式的声音文件,其结构复杂,所占磁盘空间较小,也可表达几乎所
有声音。
视频文件 的 文 件 格 式 也 有 多 种, 最 常 用 的 是 AVI (AudioVideo Interleaved )文 件
(.avi),它将视频信号以一系列的位图信息存储,文件中除了视频信号外还包括以波形声音
形式存储的数字化声音。
2 播放多媒体文件
14
在应用程序中播放多媒体文件时,必须在所有调用了多媒体函数的源文件中声明相应
的头文件。多媒体头文件包括:DIGITALV.H、MCIAVI.H、NMSYSTEM.H、MSACM.H、VCR.h、VFW.H。
此外,还需链接 MSACM32.LIB、WINMM.LIB 和 VFW32.LIB 等动态链接库。 在开发应用程序时, 293
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
可通过引入多媒体控件的方法在程序中自动添加所需要的声明 。
完成上述工作后,即可在程序中使用多媒体函数播放多媒体文件或操作多媒体设备 。
在程序中调用多媒体文件的主要方法有:
(1)使用 MCIWND 窗口类控控制波形文件、音频 CD 和视频等等。
(2)使用 MCI(Multimedia Control Interface,多媒体控制接口)。
(3)使用 API 函数。
此外,还可使用 PlaySound ()、SndPlaySound ()、MessageBeep ()等 函 数 控 制 播 放 音 频
文件。
在以上方法中,使用 API 函数需要程序员编写大量代码,所以比较简单有效的方法是使
用 MCIWND 窗口类或直接发送 MCI 命令。以下主要介绍 MCI 方法。
14 I控制方法
3 MC
14
3 I设备类型
1 MC
MCI(Multimedia Control Interface)是微软 Windows 定义的多媒体接口标准,提供了强
大的函数库来处理各种多媒体设备,而且由于 MCI 是一个可扩充库,所以如果出现了新的多
媒体设备,也能很方便地将其加入库中。表 14 1 列出了 MCI 所支持的设备类型。
表14 1 MCI支持的设备类型
设备类型 说 明 设备类型 说 明
animation 动画设备 scanner 图形扫描设备
caudio CD 音频设备 sequencer MIDI 设备
dat 数字音频设备 videodisc 影碟播放设备
digitalvideo 数字视频设备 vcr 录像机设备
other 未定义设备 waveaidio 波形音频设备
overlay 叠加视频设备
14
3 I函数接口
2 MC
MCI 为用户提供了控制不同多媒体设备的函数库,这些函数库允许用户以不同的方式访
问设备。表 14 2 列出了 MCI 主要函数接口。
表14 2 MCI主要函数接口
函数名称 说 明
续 表
函数名称 说 明
14
33 常用的 MC
I命令消息
常用的 MCI 命令消息,见表 14 3。
表14 3 常用的 MCI命令消息
命令消息 说 明 命令消息 说 明
MCI_OPEN 打开 MCI_STATUS 获取设备信息
MCI_SET 设置设备信息 MCI_PLAY 播放媒体文件 295
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
4 多媒体文件调用编程实例
14
本节以一个实例来说明多媒体文件的调用 。
【例 14 1】 Ex14 _ 1 设 计 一 个 简 易 的 Windows 媒 体 播 放 器。 利 用 动 画 控 件
ActiveMovie,实现媒体文件的播放,具体说明见表 14 4。
表14 4 例14 1具体说明
程序界面 程 序 功 能
(1)单击“OPEN”按钮,弹出打开文件对话框,选
择要打开的媒体文件。
(2)单击“PLAY”按钮,即可播放。
(3)单击“PAUSE”按钮,暂停播放;单击“PLAY”按
钮继续播放。
(4)单击“FULLSCREEN”按钮,全屏显示。
(5)单击“STOP”按钮,停止播放;单击“PLAY”按
钮从头开始播放
(1)新建一基于对话框的应用程序 Ex14_1
(2)添加控件
按照表 14 4 中的图添加控件,控件 ID 号等见表 14 5。
表14 5 控件ID 号、添加成员变量和消息处理函数
控 件 标 题 控件 ID 号 成员变量 消 息 消息函数名
按钮 OPEN IDC_OPEN BN_CLICKED OnPlay()
按钮 PLAY IDC_PLAY BN_CLICKED OnOpen()
按钮 PAUSE IDC_PAUSE BN_CLICKED OnPause()
按钮 STOP IDC_STOP BN_CLICKED OnStop()
按钮 FULLSCREEN IDC_FULLSCREEN BN_CLICKED OnFullScreen()
按钮 EXIT IDC_CANCEL
ActiveMovie m_ctrlActiveMovie
ActiveMovie 控件的插入方法如下:
在菜单中依次选择“Project/Add To Project/Components And Controls”菜单项,在出现
的“Components And Controls Gallery ”对话框中打开 “Registered Active Controls”文件
夹,选 中 “ActiveMovie Control Object ”选 项, 单 击 “Insert ”按 钮 后 关 闭 该 对 话 框,
ActiveMovie 控件便出现在控件面板中,调整好控件在对话框中的位置。为了能够控制控件
的操作,打开 ClassWizard,点击标签 Member Variable 选择 IDC_ACTIVEMOVIECONCROLl,然后
单击“Add Variable ...”按钮为它添加变量 m_ActiveMovie。
(3)编写消息函数(见清单 14 1)
296
第十四章 多 媒 体 技 术
……………………………………………………………………………………………………………………………………………
14_
清单14 1 消息函数 (在 Ex 1Dl
g.pp文件中)
c
v
oidCEx 14_1D l
g OnOp en() //显示“打开”对话框,选择要播放的文件
{ char szFileFilter=
" Mp3 File(*.mp3)|*.mp3|"
" Wma File(*.wma)|*.wma|"
" Video File(*.dat)|*.dat|"
" Wave File(*.wav)|*.wav|"
" AVI File(*.avi)|*.avi|"
" Movie File(*.mov)|*.mov|"
" Media File(*.mmm)|*.mmm|"
" Mid File(*.mid;* ,rmi)|*.mid;*.rmi|"
" MPEG File(*.mpeg)|*.mpeg|"
" All File(*.*)|*.* ||";
CFileDialog dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY,szFileFilter);
if(dlg.DoModal()= = IDOK)
{ CString PathName = dlg.GetPathName();
PathName.MakeUpper();
m_ActiveMovie.SetFileName(PathName);
}
}
v
oidCEx 14_1D l
g OnPlay() //播放文件
{ m_ActiveMovie.Run(); //播放文件
SetTimer(0,20,NULL); //设置定时器
}
v
oidCEx 14_1D l
g OnPa use()//暂停播放
{ m_ActiveMovie.Pause();
}
v
oidCEx 14_1D l
g OnStop() //停止播放
{ m_ActiveMovie.Stop();//停止播放文件
KillTimer(0); //关掉定时器
}
v
oidCEx 14_1D l
g OnFullscreen()//全屏显示
{ m_ActiveMovie.Pause (); //暂停播放
m_ActiveMovie.SetFullScreenMode(true); //设置满屏模式
m_ActiveMovie.SetMovieWindowSize(SW_SHOWMAXIMIZED); //设置窗口为最大
m_ActiveMovie. Run(); //继续播放
}
(4)编译和运行
297
第十五章 多进程与多线程编程
1 多进程编程
15
15
11 进程
进程(Process)是当前操作系统下一个被加载到内存的 、正在运行的应用程序实例。 每
一个进程都是由内核对象和地址空间所组成的,内核对象(内核对象在系统中相当于一种数
据结构,里面包含维护该对象的各种信息)可以让系统在其内存放有关进程的统计信息并使
系统能够以此来管理进程,而地址空间则包括了所有程序模块的代码和数据以及线程堆栈 、
堆分配空间等动态分配的空间。进程仅仅是一个存在,是不能独自完成任何操作的,必须拥
有至少一个在其环境下运行的线程,并由其负责执行在进程地址空间内的代码。 在进程启
动的同时即启动了一个线程,该线程被称做主线程或是执行线程,由此线程可以继续创建子
线程。如果主线程退出,那么进程也就没有存在的可能了,系统将自动撤消该进程并完成对
其地址空间的释放。
加载到进程地址空间的每一个可执行文件或动态链接库文件的映像都会被分配一个与之
相关联的全局唯一的实例句柄( 。该实例句柄实际是一个记录有进程加载位置的基
Hinstance)
本内存地址。 进 程 的 实 例 句 柄 在 程 序 入 口 函 数 WinMain ()中 通 过 第 一 个 参 数 HINSTANCE
hinstExe 传递,其实际值即为进程所使用的基本地址空间的地址。对于VC++链接程序所链接
产生的程序,其默认的基本地址空间地址为 0x00400000,如果没有必要一般不要修改该值。在
程序中,可以通过 GetModuleHandle(
)函数得到指定模块所使用的基本地址空间。
15
12 创建进程
298 (1)子进程的创建与 CreateProcess()函数
第十五章 多进程与多线程编程
……………………………………………………………………………………………………………………………………………
BOOL CreateProcess(
LPCTSTR lpApplicationName, //可执行模块名
LPTSTR lpComma ndLine, //命令行字符串
LPSECURITY_ATTRIBUTES lpProcessAttributes, //进程的安全属性
LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程的安全属性
BOOL bInheritHandles, //句柄继承标志
DWORD dwCreationFlags, //创建标志
LPVOID lpEnvironment, //指向新的环境块的指针
LPCTSTR lpCurrentDirectory, //指向当前目录名的指针
LPSTARTUPINFO lpStartupInfo, //指向启动信息结构的指针
LPPROCESS_INFORMATION lpProcessInformation //指向进程信息结构的指针
);
在进程中通过 CreateProcess()函数去创建一个子进程,子进程在全部处理过程中只对
父进程地址空间中的相关数据进行访问,从而可以保护父进程地址空间中与当前子进程执
行任务无关的全部数据。子进程可以看成是父进程在运行期间的一个过程 。
父进程来掌握子进程的活动包括启动 、执行和退出。首先通过 CreateProcess()创建子
进程,子进程启动后父进程通过 WaitForSingleObject ()函数等待其执行的结束,在子进程
没有退出前父进程一直处于阻塞状态,一旦子进程退出,WaitForSingleObject ()函数所等
待的 pi. hProcess 对 象 将 得 到 通 知, 父 进 程 将 得 以 继 续, 如 有 必 要 可 以 通 过
GetExitCodeProcess()来获取子进程的退出代码。具体编程见清单 15 3。
(2)进程的互斥运行
正常情况下,一个进程的运行一般是不会影响到其他正在运行的进程的 。 但是对于某
些有特殊要求的如以独占方式使用串行口等硬件设备的程序就需要在其进程运行期间不允
许其他试图使用此端口设备的程序运行,同时此类程序通常也不允许运行同一个程序的多
个实例。这就引出了进程互斥的问题。
实现进程互斥的核心思想比较简单:进程在启动时首先检查当前系统是否已经存在
此进程的实例,如果没有,进程将成功创建并设置标识实例已经存在的标记 。 此后再创建
进程时将会通过该标记知晓实例已经存在,从而保证进程在系统中只存在一个实例 。 具
体可以采取内 存 映 射 文 件 、有 名 事 件 量 、有 名 互 斥 量 以 及 全 局 共 享 变 量 等 多 种 方 法 来
实现 。
CreateMutex()函 数 可 用 来 创 建 一 个 有 名 或 无 名 的 互 斥 量 对 象, 其 函 数 原 型 见 清
单15 2。 299
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
清单15 2 Cr
eat
eMu
tex()函数的原型
HANDLECreat
eMu
tex( LPSECURITY_ATTRIBUTES lpMutexAttributes,//指向安全属性的指针
BOOL bInitialOwner, //初始化互斥对象的所有者
LPCTSTR lpName //指向互斥对象名的指针
LPSECURITY_ATTRIBUTES lpMutexAttributes,//指向安全属性的指针
);
如果函数成功执行,将返回一个互斥量对象的句柄。如果在 CreateMutex()执行前已经
存在 有 相 同 名 字 的 互 斥 量, 函 数 将 返 回 这 个 已 经 存 在 互 斥 量 的 句 柄, 并 且 可 以 通 过
GetLastError()得 到 错 误 代 码 ERROR _ ALREADY _ EXIST。 可 见, 通 过 对 错 误 代 码 ERROR _
ALREADY_EXIST 的检测可以实现 CreateMutex()对进程的互斥。
使用全局 共 享 变 量 的 方 法 则 主 要 是 在 MFC 框 架 程 序 中 通 过 编 译 器 来 实 现 的。 通 过
# pragma data_seg预编译指令创建一个新节,在此节中可用 volatile 关键字定义一个变量,而
且必须对其进行初始化。volatile 关键字指定了变量可以为外部进程访问。最后,为了使该
变量能够在进程互斥过程中发挥作用,还要将其设置为共享变量,同时允许其具有读、写访问
权限。这可以通过# pragma comment 预编译指令来通知编译器,具体见清单 15 7。
15
13 结束进程
进程只是提供了一段地址空间和内核对象,其运行是通过在其地址空间内的主线程来
实现的。当主线程的进入点函数返回时,进程也就随之结束。 这种进程的终止方式是进程
的正常退出,进程中的所有线程资源都能够得到正确的清除。 除了这种进程的正常退出方
式外,有时还需要在程序中通过代码来强制结束本进程或其他进程的运行,如使用下面的两
个函数。
(1)使用 ExitProcess()函数结束进程
函数原型为:void ExitProcess(UINT uExitCode);
参数 uExitCode 为进程设置了退出代码。 该函数具有强制性,在执行完毕后进程即已
经被结束,因此位于其后的任何代码将不能被执行 。虽然 ExitProcess()函数可以在结束进
程的同 时 通 知 与 其 相 关 联 的 动 态 链 接 库, 但 是 由 于 它 的 这 种 执 行 的 强 制 性, 使 得
ExitProcess()函数在使用上存在安全隐患。 例如,如果在程序调用 ExitProcess ()函数之
前曾用 new 操作符申请过一段内存,那么将会由于 ExitProcess()函数的强制性而无法通过
delete 操作符将其释放,从而造成内存泄漏。 由于 ExitProcess ()函数的强制性和不安全
性,在使用时一定要引起注意。
(2)使用 TerminateProcess()函数结束进程
函数原型为:BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode);
参数 hProcess 和 uExitCode 分别为进程句柄和退出代码。 如果被结束的是本进程,可
以通过 GetCurrentProcess()获取到句柄。
ExitProcess()只能强制执行本进程的退出,如果要在一个进程中强制结束其他的进程
就要用 TerminateProcess()来实现。与 ExitProcess()不同,TerminateProcess()函数执行
后,被终止的进程是不会得到任何关于程序退出的通知的 。 也就是说,被终止的进程是无法
在结束运行前进行退出前的收尾工作的。 所以,通常只有在其他任何方法都无法迫使进程
300 退出时才会考虑使用 TerminateProcess()去强制结束进程。
第十五章 多进程与多线程编程
……………………………………………………………………………………………………………………………………………
TerminateProcess (
)是异步执行的,在调用返回后并不能确定被终止进程是否已经真的退
出,如果调用 TerminateProcess (
)的进程对此细节关心,可以通过 WaitForSingleObject()来等
待进程的真正结束。
15
14 多进程编程实例
【例15 1】 Ex15_1 多进程编程,具体说明见表 15 1。
表15 1 例15 1具体说明
项目 图 示 和 说 明
程序界面
图(
a) 程序主界面 图(
b) 查看当前进程对话框
启动程序 Ex15_1.exe 后,如果程序不关闭,不能启动 Ex15_1.exe 第二个进程,也就是进程
互斥
程序功能 (1)点击菜单:进程->创建子进程->调用记事本,将打开记事本程序
(2)点击菜单:进程->创建子进程->调用 Word,将打开 Word 程序
(3)点击菜单:进程->查看当前进程,将显示当前进程的对话框
(1)新建一单文档应用程序 Ex15_1
(2)编辑菜单与添加菜单消息函数(见表 15 2)
表15 2 菜单消息函数(添加到视类)
菜 单 名 ID 号 消息函数
打开记事本 ID_PROCESS_CREAT_NOTPAD OnProcessCreatNotpad()
打开计算器 ID_PROCESS_CREAT_CAL OnProcessCreatCal()
查看当前进程… ID_PROCESS_NOWPROCESS OnProcessNowprocess()
(3)打开记事本菜单消息函数(见清单 15 3)
15_
清单15 3 打开记事本菜单消息函数 (在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx15_1V i
ew OnPr o
cessCreatNotpa //打开记事本
d()
{ CString sCommandLine;//临时变量,记事本程序的路径+记事本程序名
char cWindowsDirectoryMAX_PATH;//windows 路径
char cCommandLineMAX_PATH;//记事本程序的路径+记事本程序名
DWORD dwExitCode;
PROCESS_INFORMATION pi;
STARTUPINFO si =sizeof(si)};
GetWindowsDirectory(cWindowsDirectory, MAX_PATH);//得到 Windows 目录
//记事本程序的路径+记事本程序名 301
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(4)打开计算器菜单消息函数(见清单 15 4)
15_
清单15 4 打开计算器菜单消息函数 (在 Ex 1Vi
ew.
cpp文件中)
v
oidCEx 15_1V iew OnPr oc
essCreat
Ca l()//打开计算器
{... //前 7 行见清单 15 3
//计算器程序的路径+ 记事本程序名
sCommandLine = CString(cWindowsDirectory)+ "\\system32" + "\\NotePad.exe" ;
::strcpy(cCommandLine, sCommandLine);
//启动"计算器"作为子进程
BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (ret)
{...见清单 15 3
}
else
AfxMessageBox( "打开计算器失败! \r\n 请确认在 windows\system32 目录下有 Calc.exe 文件。 ",
MB_OK|MB_ICONERROR);
}
}
(5)查看当前进程
① 编辑查看当前进程对话框的资源 。
对话框 ID 号为:IDD_PROCESSVIEWDLG;
添加控件:添加 1 个列表框,其 ID 号为:IDC_LISTB_PROCESS
② 为对话框建类,类名:CProcessViewDlg。
③ 用 ClassWizard 添加 IDC_LISTB_PROCESS 的成员变量:CListBox m_ctrlProcessList。
④ 用 ClassWizard 添加初始化对话框函数(见清单 15 5)。
清单15 5 On
Ini
tDi
alg()函数 (在 Pr
o oce
ssV
iewD
lg.
cpp文件中)
BOOLCPr oce
ssViewDlgOn In
itDial
og()
{ CDialog::OnInitDialog();
m_ctrlListProcess.ResetContent();
//创建快照句柄
302
第十五章 多进程与多线程编程
……………………………………………………………………………………………………………………………………………
HANDLE hSnapshot;
hSnapshot = CreateToolhelp32Snapshot(TH32 CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
Process32First(hSnapshot, &pe);//先搜索系统中第一个进程的信息
//下面对系统中的所有进程进行枚举,并保存其信息
do
//把进程对应的文件路径名填入列表框
int index = m_ctrlListProcess.AddString(pe.szExeFile);
//设置列表框中该项的 Data 相应的进程的 ID 号,利于以后终止该进程
m_ctrlListProcess.SetItemData(index, pe.th32ProcessID);
}while (Process32Next(hSnapshot, &pe));
CloseHandle(hSnapshot);//关闭快照句柄
return TRUE;//return TRUE unless you set the focus to a control
v
oidCEx15_1View OnProcessNowp
roc
es //查看当前进程
s()
{ CProcessViewDlg dlg;
dlg.DoModal();
}
(6)进程互斥运行(见清单 15 7)
15_
清单15 7 进程互斥运行(在 Ex 1.pp文件中)
c
# pragma data_seg("Shared" )
int volatile g_lAppInstance = 0;
# pragma data_seg()
# pragma comment(linker,"/section:Shared,RWS" )
BOOLCEx 15_1App Init
Instance()
{ //下面两行被注释的代码是使用编译器来实现进程的互斥运行,本例不用此方法
//if(+ +g_nAppInstance> 1)
//return FALSE;//通知进程结束
HANDLE m_hMutex = CreateMutex(NULL, FALSE," Sample05" );//创建互斥量
//检查错误代码
if (GetLastError()= = ERROR_ALREADY_EXISTS)
{ //如果已有互斥量存在则释放句柄并复位互斥量
CloseHandle(m_hMutex);
m_hMutex = NULL;
return FALSE;//程序退出
}
AfxEnableControlContainer();
…
}
(7)编译和运行
303
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
2 多线程编程
15
线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,
相当于一个子程序,它对应于VC++中的 CWinThread 类对象。 单独一个执行程序运行时,缺
省地包含一个主线程,主线程以函数地址的形式出现,提供程 序 的 启 动 点,如 main ()或
WinMain()函数等。当主线程终止时,进程也随之终止。 根据实际需要,应用程序可以分解
成许多独立执行的线程,每个线程并行的运行在同一进程中 。
一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统
资源。操作系统给每个线程分配不同的 CPU 时间片,在某一个时刻,CPU 只执行一个时间片
内的线程,多个时间片中的相应线程在 CPU 内轮流执行,由于每个时间片时间很短,所以对
用户来说,仿佛各个线程在计算机中是并行处理的。 操作系统是根据线程的优先级来安排
CPU 的时间,优先级高的线程优先运行,优先级低的线程则排队等待。
线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来
处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程 CWinAPP 对象就是一个
用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进
程终止。工作线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和
用户界面线程的区别是它不用从 CWinThread 类派生来创建,对它来说最重要的是如何实现工
作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版
本;一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。
15
21 线程的创建与结束
(1)线程的创建
创建线程有多种方法,具体创建函数见表 15 3。
(2)线程的结束
线程结束可以采用线程函数返回的方法(这种方法较好)
,这是确保所有线程资源被正确地清
除的唯一办法。如果线程能够返回,就可以确保下列事项的实现:在线程函数中创建的所有 C 对
象均将通过它们的撤消函数被正确地撤消。操作系统将正确地释放线程堆栈使用的内存。系统
将线程的退出代码设置为线程函数的返回值。系统将递减线程内核对象的使用计数。
线程的结束函数,见表 15 4。
表15 3 线程的创建函数
函数来源 函 数 原 型
//创建工作者线程的函数:
CWinTh read Af xBe i
gnTh r
e ad(
AFX_THREADPROC pfnThreadProc,//指向工作者线程函数的指针
LPVOID pParam,//待传递给线程函数的参数
MFC 提供的
int nPriority = THREAD_PRIORITY_NORMAL,
函数
UINT nStackSize = 0,//线程栈 大小的指针
DWORD dwCreateFlags = 0,//线程栈创建标志的指针
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL//线程栈安全属性的指针
);
304
第十五章 多进程与多线程编程
……………………………………………………………………………………………………………………………………………
续 表
函数来源 函 数 原 型
//创建用户界面线程的函数:
CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,//指向 CruntimesClass 类的指针
MFC 提供的 int nPriority = THREAD_PRIORITY_NORMAL,
函数 UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpSecurityAttrs,//指向安全属性的指针,如为 NULL
//则将使用默认的安全属性
DWORD dwStackSize,//线程空间的实际大小
Win32 API
LPTHREAD_START_ROUTINE lpStartAddress,//指线程的地址
函数
LPVOID lpParameter,//新线程参数
DWORD dwCreationFlags,//线程的创建方式,为 0 时创建的线程将立即运行,
//为 CREAT_SUSPENDED 时创建的线程将立即被挂起
LPDWORD lpThreadId//指向的地址将用来存放系统分配给新线程的 ID 号
);
表15 4 线程的结束函数
函 数 来 源 函 数 原 型
MFC 提供的函数 void AfxEndThread(UINT nExitCode);//nExitCode 是待结束线程的退出码
void ExitThread(DWORD dwExitCode);//dwExitCode 是待结束线程的退出码
BOOL TerminateThread(
Win32 API 函数 HANDLE hThread,//待结束线程的句柄
DWORD dwExitCode//待结束线程的退出码
);
15
22 线程的调度和优先级
(1)Windows 对线程的调度管理流程
Windows 操作系统是一种抢先式多任务的操作系统 。 这种操作系统虽然可以同时执行
多个线程,但实际上在一个极短的时间片上一个 CPU 只对开启的多个线程中的一个线程进
行处理。调度程序是操作系统的一个组成部分,它决定哪一个线程在什么时候运行以及运
行多长时间,它的目标是尽可能高效率地在执行的线程中间分配 CPU 时间,以便造成一种假
象,好像所有的线程都是立即运行的 。
线程是由线程内核对象和线程栈组成。 线程内
核对象的一个作用即是供系统存放线程的统计信息 。
图 15 1 为 Windows 对线程管理的流程图。
Windows 每隔 20 毫秒左右就要检查当前存在的
所有线程内核对象,并从可调度的一个线程内核对象
中加载环境结构中的值到 CPU 的寄存器。 此内核对
象中的环境结构保存了在上次运行线程时的 CPU 寄
存器状态。CPU 只对当前的处理线程操作 20 毫秒,20
毫秒之后 Windows 将把当前 CPU 的寄存器状态再次
保存到线程的环境结构中,停止本线程的执行,并以 图15 1 Wi
ndows对线程的调度管理
同样方式继续对下一个可调度线程进行相同处理 。
Windows 系统只能调度那些挂起计数为 0 的线程对象,对于那些暂停记数大于 0 的线程
或正在等待某些事情发生的线程将不为其分配 CPU 时间。线程在刚创建时其暂停计数均初
始化为 1,也就是刚创建的线程不可调度。 之后线程将花一定的时间去完成初始化工作,并
在初始化完成后查看是否使用了 CREATE_SUSPENDED 标志。如果该标志存在,则挂起计数依
然为 1,线程处于不可调度的挂起状态。如果未使用该标志,那么 CreateThread()函数将会
把计数器数设置为 0,使线程处于可调度状态。
(2)挂起和继续执行线程
一个运行的线程可以用 CWinThread::SuspendThread ()来挂起,并可用 CWindowThread::
ResumeThread(
)来使它重新执行。 一个线程可以在它自身上调用 SuspendThread (),或者让
另一个线程来调用它的 SuspendThread()。然而一个被挂起的线程不能用 ResumeThread()
来唤醒自己;必须有别的线程为它调用 ResumeThread()。 一个被挂起的线程几乎不占用处
理器的时间,并且基本上不增加系统的开销。
306 Windows 对每个线程保持一个挂起计数,每调用一次 SuspendThread (),就给该计数加
第十五章 多进程与多线程编程
……………………………………………………………………………………………………………………………………………
优先级范围 说 明 备 注
THREAD_PRIORITY_HIGHEST 线程在标准优先级上两级运行
THREAD_PRIORITY_ABOVE_NORMAL 线程在标准优先级上一级运行
THREAD_PRIORITY_NORMAL 线程在标准优先级类运行
THREAD_PRIORITY_BLOW_NORMAL 线程在标准优先级下一级运行
THREAD_PRIORITY_LOWEST 线程在标准优先级下两级运行
在 15 优先级上运行,如果进程优先级类
SetThreadPriority()函
THREAD_PRIORITY_TIME_CRIT_ICAL 别为 REALTIME_PRIORITY_CLASS 则在优
数设置
先级 31 上运行
在 1 优先级上运行,如果进程优先级类
SetThreadPriority()函
THREAD_PRIORITY_IDLE 别为 REALTIME_PRIORITY_CLASS 则在优
数设置
先级 16 上运行
相同的优先级范围在不同的进程优先级类别下将会有不同的优先级取值,具体的优先
级映射关系由操作系统来负责。
需要注意的是,在改变线程优先级时必须先将线程挂起,设置完毕后再将其恢复。 具体
的,可以在开始创建线程时将其挂起,在设置完优先级后再将其恢复,也可将正在运行的线
程先通过 SuspendThread()挂起,然后再更改优先级设置。
(5)线程优先级设置举例
见例 15 2 的第(5)步。
15
23 线程间通信
由于进程本身隐含地创建有一个主线程,因此在程序中显式创建了线程后,实际上是对
多线程的处理与应用。在主线程中新创建的子线程一般是用来完成某种特定的任务,在执
行过程中总是或多或少地需要同主线程通信。 实现线程间通信的方法有很多,常用的主要
是通过全局变量、自定义消息函数和事件对象等来实现 。
(1)使用全局变量
将全局变量作为线程监视的对象,并通过在主线程对此变量值的改变而实现对子线程
的控制。由于这里的全局变量需要在使用它的线程之外对其值进行改变,这就需要通过
volatile(volatile 意思是不稳定的,用它修饰的成员变量在每次被线程访问时,都强迫从 307
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
共享内存中重读该成员变量的值)关键字对此变量进行说明。
(2)利用自定义消息
全局变量在线程通信中的应用多用在主线程对子线程的控制上,而从子线程向主线
程的信息反馈则多采用自定义消息的方式来进行 。 这里对自定义的消息的使用同使用普
通自定义消息非常相似,只不过消息的发送是在子线程函数中进行的 。 该方法的主体是
自定义消息函数,应首先定义自定义消息函数并添加对应的消息函数响应代码对此变量
进行说明 。
在子线程函数需要向主线程发送消息的地方调用 PostMessage()或 SendMessage()消
息传递函数将消息发送给主线程即可。 由于消息发送函数是在线程中被调用,因此需要指
出接收窗口句柄,可通过线程参数将其传递给线程函数 。
(3)使用事件内核对象
利用事件内核对象对线程的通信要复杂些,主要通过对事件对象的监视来实现线程间
的通信。事件对象 由 CreateEvent ()函 数 创 建,具 有 两 种 存 在 状 态:置 位 与 复 位,分 别 由
SetEvent ()和 ResetEvent ()来 产 生。 事 件 的 位 置 将 通 过 WaitForSingleObject ()或
WaitForMultpleObjects()之类的通知等函数继续执行。
(4)线程间通信举例
见例 15 2 第(6)~(8)步。
15
24 多线程编程实例
【例15 2】 Ex15_ 2 线程 创 建、线 程 管 理 和 线 程 间 通 信 的 应 用 与 编 程,具 体 说 明 见
表15 6。
表15 6 例15 2具体说明
项目 图示和说明
程
序
界
面 图(
a) 主界面和线程创建菜单 图(
b) 线程管理菜单 图(
c) 线程间通信菜单
图(
d) 消息对话框 图(
e) 消息对话框
308
第十五章 多进程与多线程编程
……………………………………………………………………………………………………………………………………………
续 表
项目 图示和说明
序 图(
f) 消息对话框
图(
i) 消息对话框
界
图(
h) 使用自定义消息
面
图(
g) 消息对话框 图(
j) 消息对话框
(1)点击菜单:线程创建->使用 AfxBeginThread(),弹出图(d)
(2)点击菜单:线程创建->使用 CreateThread(),弹出图(e)
(3)点击菜单:线程管理->线程启动时设置优先级,弹出图(e)
程
(4)点击菜单:线程管理->线程运行起来后改变优先级,弹出图(e)
序
(5)点击菜单:线程间通信->使用全局变量->执行,弹出图(f)
功
(6)点击菜单:线程间通信->使用全局变量->停止,弹出图(g)
能
(7)点击菜单:线程间通信->使用自定义消息,弹出图(h)
(8)点击菜单:线程间通信->使用事件内核对象->执行,弹出图(i)
(9)点击菜单:线程间通信->使用事件内核对象->停止,弹出图(j)
(1)新建一单文档应用程序 Ex15_2
(2)编辑菜单与添加菜单消息函数(见表 15 7)
表15 7 菜单消息函数(添加到视类)
菜 单 名 ID 号 消 息 函 数
使用 AfxBeginThread() ID_USE_AFXBEGINTHREAD OnUseAfxbeginthread()
使用 CreateThread() ID_USE_CREATETHREAD OnUseCreatethread()
线程启动时设置优先级 ID_SET_START_PRIORITY OnSetStartPriority()
线程运行起来后改变优先级 ID_SET_AFTERRUN_PRIORITY OnSetAfterrunPriority()
使用全局变量->执行 ID_COMMUNICATE_GLOBAL_START OnCommunicateGlobalStart()
使用全局变量->停止 ID_COMMUNICATE_GLOBAL_END OnCommunicateGlobalEnd()
使用自定义消息 ID_COMMUNICATE_USE_MESSAGE OnCommunicateUseMessage()
使用事件内核对象->执行 ID_COMMUNICATE_EVENT_START OnCommunicateEventStart()
使用事件内核对象->停止 ID_COMMUNICATE_EVENT_END OnCommunicateEventEnd()
(3)编写线程函数 (见清单 15 8)
15_
清单15 8 线程函数 (在 Ex 2Vi
ew.
cpp文件中)
UINTTh re
a dFun
c2( LPVOIDpPa ram)//线程函数
{ AfxMessageBox("AfxBeginThread()函数创建的线程!");//显示消息对话框
return 0;
(4)创建线程
编写菜单消息函数(见清单 15 9)。
15_
清单15 9 创建线程,编写菜单消息函数 (在 Ex 2Vi
ew.
cpp文件中)
v
oidCEx 15_2V i
ew OnUs eAf
xbeginthread()//使用 Af xbe
ginthread()创建
{ //创建工作者线程,并将视指针作为参数传递给线程
CWinThread* pWinThread = AfxBeginThread(ThreadFunc1, this);
}
v
oidCEx 15_2V i
ew OnUs eCr
eat
et hr
ead() //使用 Cr ea
tet
hread()创建
{ HANDLE hThread = CreateThread(NULL,//无安全属性
0,//使用默认线程栈大小
ThreadFunc2,//线程函数地址
this,//将视指针作为参数传递给线程
0,//线程创建后立即执行
NULL //不关心线程 ID
);
CloseHandle(hThread);//释放线程句柄
}
(5)线程优先级设置
编写菜单消息函数(见清单 15 10)。
15_
清单15 10 线程优先级设置菜单消息函数 (在 Ex 2Vi
ew.
cpp文件中)
v
oidCEx15_2V i
ew OnS etStar
tPrior
ity() //线程启动时设置优先级
{ //创建线程
HANDLE hThread = CreateThread (NULL,//无安全属性
0,//使用默认线程栈大小
ThreadFunc2,//线程函数地址
this,//将视指针作为参数传递给线程
CREATE_SUSPENDED,//线程创建后立即挂起
NULL);//不关心线程 ID
SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST);//设置线程优先级
ResumeThread(hThread);//恢复线程执行
CloseHandle(hThread);//关闭句柄
}
v
oidCEx15_2V i
ew OnS etAfter
runPr iori
ty()//线程运行起来后改变优先级
{ //创建线程
HANDLE hThread = CreateThread (NULL,//无安全属性
0,//使用默认线程栈大小
ThreadFunc2,//线程函数地址
this,//将视指针作为参数传递给线程
0,//线程创建后立即执行
NULL);//不关心线程 ID
310
第十五章 多进程与多线程编程
……………………………………………………………………………………………………………………………………………
SuspendThread(hThread);//挂起线程
SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST);//设置线程优先级
ResumeThread(hThread);//恢复线程执行
CloseHandle(hThread);//关闭句柄
}
(6)线程间通信—使用全局变量
① 编写线程函数(见清单 15 11)
15_
清单15 11 线程间通信的线程函数 (在 Ex 2Vi
ew.
cpp文件中)
② 编写菜单消息函数(见清单 15 12)
15_
清单15 12 使用全局变量进行线程间通信的菜单消息函数 (在 Ex 2Vi
ew.
cpp文件中)
v
oidCEx15_2V i
ew OnCommun icateGlobalSt
art()//使用全局变量 执行
{ g_bDo = true;//通过全局变量通知线程执行
AfxBeginThread(ThreadFunc3, NULL);//启动线程
}
v
oidCEx15_2V i
ew OnCommun icateGlobalEnd()//使用全局变量 停止
{ g_bDo = false;//通过全局变量通知线程结束
}
(7)线程间通信—利用自定义消息
① 编写线程函数(见清单 15 13)
② 自定义消息(见清单 15 13)
15_
清单15 13 利用自定义消息进行线程间通信的线程函数(在 Ex 2Vi
ew.
cpp文件中)
#def
ineWM_ USER_MSG WM_ USER+101//自定义消息
UINTThreadFun 4(
c LPVOIDpPa ram)//线程函数
{ Sleep(3000);//延迟 3 秒
::PostMessage((HWND)pParam, WM_USER_MSG, 0, 0);//向主线程发送自定义消息
return 0;
清单15 14 使用全局变量进行线程间通信的菜单消息函数
15_
(在 Ex 2Vi
ew.
cpp文件中)
v
oidCEx15_2View OnUs eMessage(WPARAM wPa ramLPARAMl
Par //自定义消息函数
am)
{ AfxMessageBox("使用自定义消息 线程已退出!");//报告消息
}
v
oidCEx15_2View OnCommun icat
eUseMe ss
ag //菜单消息函数
e()
{ HWND hWnd = GetSafeHwnd();//获取窗口句柄
AfxBeginThread(ThreadFunc4, hWnd);//启动线程
}
(8)线程间通信—使用事件内核对象
① 编写线程处理函数(见清单 15 15)
清单15 15 使用事件内核对象进行线程间通信的线程处理函数
15_
(在 Ex 2Vi
ew.
cpp文件中)
Wa
itFo
rSi
ngl
eOb
jet(HANDLE hHandle,
c //等待对象的句柄
DWORD dwMilliseconds//超时时间间隔
);
② 编写菜单消息函数(见清单 15 16)
清单15 16 使用事件内核对象进行线程间通信的菜单消息处理函数
15_
(在 Ex 2Vi
ew.
cpp文件中)
v
oidCEx15_2V i
ew OnCommun i
cateEventStart()//使用事件内核对象 执行
{ hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//创建事件
AfxBeginThread(ThreadFunc5, NULL);//启动线程
}
v
oidCEx15_2V i
ew OnCommun i
cateEventEnd() //使用事件内核对象 停止
{ SetEvent(hEvent);//事件置位
}
312
第十五章 多进程与多线程编程
……………………………………………………………………………………………………………………………………………
3 线程同步
15
15
31 线程同步的概念
因为各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题
是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。 保证各
个线程可以在一起适当的协调工作称为线程之间的同步 。
前面一节介绍的事件对象实际上就是一种同步形式 。 Visual C++中使用同步类来解决
由操作系统的并行性而引起的数据不安全问题,MFC 支持的七个多线程的同步类可以分成两
大类:同步对象 (CSyncObject、CSemaphore、CMutex、CCriticalSection 和 CEvent)和同步访
问对象(CMultiLock 和 CSingleLock)。本节主要介绍临界区 (Critical Section)、事件内核
对象(Event)、互斥(Mutex)、信号量 (Semaphore),这些同步对象使各个线程协调工作,程序
运行起来更安全。
15
32 临界区
临界区是一段以独占方式对某些共享资源访问的代码,在任意时刻只允许一个线程对
共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他
所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开 。
线程在使用临界区时,一般不允许其运行时间过长。 只要进入临界区的线程没有离开,
其他所有试图进入此临界区的线程都会被挂起而进入到等待状态 。 这在一定程度上会影响
程序的运行性能。需要注意的是不要将等待用户输入或是其他一些需要外界干预的操作包
含到临界区中。如果线程进入了临界区却一直没有释放,同样也会引起其他线程的长时间
等待。
(1)CCriticalSection 类
MFC 为临界区提供有一个 CCriticalSection 类,使用该类进行线程同步处理是非常简
单的,只需在线程处理函数中用 CCriticalSection 类成员函数 Lock()和 UnLock()标定出被
保护代码片段即可。
(2)临界区应用举例
见例 15 3 第(3)步。
15
33 事件内核对象
在前面讲述线程通信时曾提过使用事件内核对象来进行线程间的通信,除此之外,事件
内核对象也可以通过通知操作的方式来保持线程的同步 。使用临界区只能同步同一进程中
的线程,而使用事件内核对象则可以对进程外的线程进行同步,其前提是得到对此事件内核
对象的访问权。
(1)CEvent 类
MFC 为事件处理提供 了 一 个 CEvent 类,共 包 含 有 除 构 造 函 数 外 的 4 个 成 员 函 数,见
表15 7。
313
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
表15 7 CEv
ent类成员函数
函数原型和参数说明 作 用
将事件设置为可用,释放任意等待的线程后再将
BOOL PulseEvent()
其重置为不可用
将事件设置为不可用,直到显示地调用成员函数
BOOL ResetEvent()
SetEvent 时设置为可用
BOOL SetEvent() 将事件设置为可用,并释放任意等待的线程
由当前拥有自动事件的线程调用,且在使用后释
Virtual BOOL UnLock()
放此事件对象
15
34 互斥内核对象
互斥(Mutex)是一种用途非常广泛的内核对象 。 互斥与临界区很相似,但是使用时相对
复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同
步,从而实现资源的安全共享。
(1)CMutex 类
MFC 为互斥提供了一个 CMutex 类,互斥与 CMutex 类的对象相对应,使用 CMutex 类的方
法是使用互斥对象,在构造 CMutex 类对象的同时可以指明待查询的互斥对象的名字,在构
造函数返回后即可访问此互斥变量 。该对象的 Lock()函数用于占有互斥,Unlock()用于释
放互斥。CMutex 类构造函数原型为:
CMutex(BOOL bInitiallyOwn = FALSE,
LPCTSTR lpszName = NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute = NULL);
(2)互斥内核对象举例,见例 15 3 第(5)步。
15
35 信号量内核对象
314 信号量 (Semaphore)内核对象的用法和互斥的 用 法 很 相 似,不 同 的 是 它 可 以 同 一 时
第十五章 多进程与多线程编程
……………………………………………………………………………………………………………………………………………
刻允许多个线程 访 问 同 一 个 资 源,但 是 需 要 限 制 在 同 一 时 刻 访 问 此 资 源 的 最 大 线 程
数目 。
(1)CSemaphore 类
MFC 为互斥提供了一个 CSemaphore 类,信号量需要用 CSemaphore 类声明一个对象,一
旦创建了一个信号量对象,就可以用它来实现对资源的访问技术 。 为实现计数处理,先创建
一个 CSingleLock 或 CMltiLock 对象,然后用该对象的 Lock ()函数减少这个信号量的计数
值,Unlock()反之。
CSemaphore 类构造函数原型为:
构造函数可以构造一个信号量对象,并对初始资源计数、最大资源计数、对象名和安全
属性等进行初始化。
(2)信号量内核对象举例,见例 15 3 第(6)步。
15
36 线程同步编程实例
【例15 3】 Ex15_3 线程同步编程,具体说明见表 15 8。
表15 8 例15 3具体说明
项目 图 示 和 说 明
序
图(
a) 程序主界面 图(
b) 消息对话框
界
图(
c) 消息对话框 图(
d) 消息对话框 图(
e) 消息对话框
点击菜单:线程同步->临界区,弹出图(b)
程
序 点击菜单:线程同步->事件内核对象,弹出图(b)
功 点击菜单:线程同步->互斥内核对象,弹出图(b)
能
点击菜单:线程同步->信号量内核对象,弹出图(c)和(d),关闭(c)或(d),将弹出(e)
(2)编辑菜单与添加菜单消息函数(见表 15 9)
表15 9 菜单消息函数(添加到视类)
菜 单 名 ID 号 消 息 函 数
(3)临界区
① 编写线程函数(见清单 15 17)
15_
清单15 17 采用临界区的线程处理函数(在 Ex 3Vi
ew.
cpp文件中)
char cArray10;//共享资源
CCriticalSection criticalSection;//MFC 临界区类对象
UINTTh readCri
ticalFun c1( LPVOIDp a
ram)//线程函数
{ criticalSection.Lock();//进入临界区
//对共享资源进行写入操作
for (int i = 0; i < 10; i+ + )
{ cArrayi= a ;
Sleep(1);
}
criticalSection.Unlock();//离开临界区
return 0;
UINTThreadCr i
ticalFun c2( LPVOIDp a
r //线程函数
am)
{ criticalSection.Lock();//进入临界区
//对共享资源进行写入操作
for (int i = 0; i < 10; i+ + )
{ cArray10 i 1= b ;
Sleep(1);
}
criticalSection.Unlock();//离开临界区
return 0;
② 编写临界区菜单消息函数(见清单 15 18)
15_
清单15 18 临界区菜单消息函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx15_3V i
ew OnS ynCriti
calse
ction()//临界区
{ //启动线程
AfxBeginThread(ThreadCriticalFunc1, NULL);
AfxBeginThread(ThreadCriticalFunc2, NULL);
Sleep(300);//等待计算完毕
//报告计算结果
CString sResult = CString(cArray);
AfxMessageBox(sResult);
}
316
第十五章 多进程与多线程编程
……………………………………………………………………………………………………………………………………………
(4)事件内核对象
① 编写线程函数(见清单 15 19)
15_
清单15 19 采用临界区的线程处理函数(在 Ex 3Vi
ew.
cpp文件中)
CEvent event;//事件内核对象
UINTTh re
adEv ent Func 1(LPVOIDp a
r //线程函数
am)
{ //对共享资源进行写入操作
for (int i = 0; i < 10; i+ + )
{ cArrayi= a ;
Sleep(1);
}
event.SetEvent();//设置事件置位
return 0;
UINTThreadEv ent Func 2(LPVOIDp a
r //线程函数
am)
{ event.Lock();//等待事件
//对共享资源进行写入操作
for (int i = 0; i < 10; i+ + )
{ cArray10 i 1= b ;
Sleep(1);
}
return 0;
② 临界区菜单消息函数编写(见清单 15 20)
15_
清单15 20 临界区菜单消息函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx15_3V i
ew OnS y nEven //事件内核对象
t()
{ //启动线程
AfxBeginThread(ThreadEventFunc1, NULL);
AfxBeginThread(ThreadEventFunc2, NULL);
Sleep(300);//等待计算完毕
//报告计算结果
CString sResult = CString(cArray);
AfxMessageBox(sResult);
}
(5)互斥内核对象
① 线程函数编写(见清单 15 21)
15_
清单15 21 采用互斥内核对象的线程处理函数(在 Ex 3Vi
ew.
cpp文件中)
mutex.Unlock();//释放互斥对象
return 0;
UINTThreadMu te xFun c2( LPVOIDpa
r //线程函数
am)
{ mutex.Lock();//等待互斥对象通知
//对共享资源进行写入操作
for (int i = 0; i < 10; i++)
{ cArray10 i 1= b ;
Sleep(1);
}
mutex.Unlock();//释放互斥对象
return 0;
② 互斥内核对象菜单消息函数编写(见清单 15 22)
15_
清单15 22 临界区菜单消息函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx15_3V i
ew OnS y nEven //事件内核对象
t()
{ //启动线程
AfxBeginThread(ThreadMutexFunc1, NULL);
AfxBeginThread(ThreadMutexFunc2, NULL);
Sleep(300);//等待计算完毕
//报告计算结果
CString sResult = CString(cArray);
AfxMessageBox(sResult);
}
(6)信号量内核对象
① 编写线程函数(见清单 15 23)
15_
清单15 23 采用信号量内核对象的线程处理函数(在 Ex 3Vi
ew.
cpp文件中)
② 编写信号量内核对象菜单消息函数(见清单 15 24)
15_
清单15 24 信号量菜单消息函数(在 Ex 3Vi
ew.
cpp文件中)
v
oidCEx15_3View OnSynSema phoe()
r //信号量内核对象
{ //启动线程
AfxBeginThread(ThreadSemaphoreFunc1, NULL);
AfxBeginThread(ThreadSemaphoreFunc2, NULL);
AfxBeginThread(ThreadSemaphoreFunc3, NULL);
}
(7)编译和运行
319
第十六章 网络通信编程
16
1 TCP/
IP 协议
16
11 TCP/
IP 协议的体系结构
TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)
是一个四层协议,它的结构如图 16 1 所示。
链路层:包括系统中的设备驱动程序和网络接口
卡,它们和具体的物理媒介 (如电缆)一起,高层提供物
理链路。
网络层:主要任务是做出数据分组的路由选择 。
传输层:为相互通信的两主机提供端到端的通信
能力。其中,TCP 协议向应用层提供可靠的数据连接,
它保证进程间数据传输的正确、有序和不重复。UDP 协
议仅仅为应用层提供数据报的分组发送服务,数据传
输的可靠性只能通过应用层来保证 。
应用层:负责处理具体的 TCP/IP 协议的细节。 图16 1 TCP/
IP 体系结构图
16
1 P 地址和通信端口
2 I
(1)IP 地址
IP 地址的全称是互联网地址,它是唯一标识的网络接口,即一台主机。 IP 地址是一个
320 32 位二进制数,整个 IP 地址空间分为 5 类地址,用 A、B、C、D、E 表示。其中 A、B、C 为基本类,D
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
类型 范 围 类型 范 围
A类 00.00~127255255255 D类 224000~239255255255
B类 128000~191255255255 E类 240000~247255255255
C类 192000~223255255255
此外,还需要特别注意以下几个特殊的 IP 地址:
网络地址:IP 中主机地址为 0 的地址表示网络地址。如 12821100
广播地址:网络号后跟一个所有位全是 0 或全是 1 的后缀,就是直接广播地址。 使用广
播地址可以向网络内的所有主机发送数据 。
组播地址:224000~239255255255
回送地址:12700.0~127255255255,用于测试,一般用 127001
(2)端口号
主机之间的通信实际上是主机之间进程的通信 。 与如上所述相同,IP 地址可以唯一地
确定互联网上的主机,而在系统内部,为了区分不同的进程,TCP/IP 引入了协议端口的概念,
用它来表示主机内的不同进程。
端口是一种抽象的软件结构,进程通过系统调用和端口绑定后,对通信端口的操作类似
于对本地文件的操作。 可以说端口是进程和运输层之间 I/O 操作的桥梁。 TCP/IP 协议用
16 位二进制数 (一个 WORD )表示端口号,因此理论上系统可以分配利用的端口号有 216 =
65 536个。端口号由 IANA(Internet Asigned Number Authority)控制和分配,端口可划分为
三类:
知名端口(wellknown ports):0~ 1023,它们由 IANA 分配,为固定服务保留。 如 FTP 协
议端口号为 21,Telnet 端口号为 23,Http 协议端口号为 80 等。
16
13 客户机/服务器模式
在 TCP/IP 网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式
(Client/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的
服务。客户/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软硬件资源、
运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客
户请求服务这一非对等关系。其次,网间进程通信完全是异步的,相互通信的进程间既不存 321
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二
者的数据交换提供同步,这就是基于客户/服务器模式的 TCP/IP。
16 t概念与 Wi
2 Socke ndowsSocke
tAP
I
Socket 的英文原意是“孔”或 “插座 ”,中文译为套接字,起源于美国加州大学伯克利分
校,是伯克利开发的 BSD UNIX(Berkeley Software Distribution)操作系统中的网络通信接
口之一。可以将套接字看作不同主机间进程进行双向通信的端点 。 Windows Socket 是一套
开发的、支持多种协议的 Windows 下网络编程接口,是 Windows 网络编程事实上的标准。
Windows Socket 有 WinSock11 提供的原始 API 函数和 WinSock20 提供的一组扩展函数。
这两套函数虽有重复,但是 20 提供的函数功能更强大,函数数量也更多。两套函数可以灵
活混 用,分 别 包 含 在 头 文 件 Winsock.h,Winsock2.h,分 别 需 要 引 入 库 wsock32.lib、Ws2_
32.lib。
16
21 Soc
ket的类型
TCP/IP 的 Socket 提供下列三种类型套接字。流套接字、数据报套接字和原始套接字。
(1)流套接字(SOCK_STREAM)
其提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复地发送,且按发送顺
序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。 文件传送协
议(FTP)即使用流式套接字。
(2)数据报套接字(SOCK_DGRAM)
它提供了一个无连接服务。数据包以独立包形式被发送,不提供无错保证,数据可能丢
失或重复,并且接收顺序混乱。网络文件系统(NFS)使用数据报套接字。
(3)原始套接字(SOCK_RAW)
该接口允许对较低层协议,如 IP、ICMP 直接访问。 常用于检验新的协议实现或访问现
有服务中配置的新设备。常用的 DOS 程序 ping 可以用原始套接字实现。
16
22 阻塞和非阻塞
Socket 有阻塞(或同步)和非阻塞(或异步 )两种使用方式,阻塞和非阻塞往往都是针对
一个函数来说的,“阻塞”就是函数直到其要执行的功能全部完成时才返回;而 “非阻塞”则是
函数仅仅做一些简单的工作,然后马上返回,而它所要实现的功能留给别的线程或者函数去
完成。例如,SendMessage 就是“阻塞”函数,它不但发送消息到消息队列,还需要等待消息被
执行完才返回;相反 PostMessage 就是个非阻塞函数,它只管发送一个消息,而不管这个消
息是否被处理,就马上返回。
16
23 Wi
ndowsSoc
ke I主要函数
tAP
Windows Socket API 中大部分函数是在原有的 Berkeley Socket 基础上建立的,除此之
外,Windows 增加了一些套接字函数,这些函数以字母 WSA 开头。
322 (1)创建套接字函数 socket()
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
符。否则返回值 INVALID_SOCKET。
服务器程序调用 accept 函数从处于侦听的流套接字的客户连接请求队列中取出最靠
前的一个客户请求,并创建一个新的套接字来与客户套接字之间创建连接。 对于客户机后
续的操作,都应使用这个套接字,至于原来那个侦听套接字,它仍然用于接受其他客户机连
接,而且仍处于侦听模式。
(6)流套接字数据传输的函数 send()
/recv()
/recv()用于在参数 s 指定的已连接的流套接字上发送输出/接收输入数据。
send()
函数原型:int send(SOCKET s,const char FAR * buf,int len,int flags);
int recv(SOCKET s,char FAR * buf,int len, int flags);
参数:s 为已连接的套接字描述符。
buf 指向接收输入数据缓冲区的指针,其长度由 len 指定。
flags 指定传输控制方式,如是否接收带外数据等。
如果没有错误发生,send()
/recv()返回实际发送/接收的字节数。 如果连接被关闭,返
回 0。否则它返回 SOCKET_ERROR。
(7)数据报数据传输的函数 sendto()
/recvfrom()
sendto()的使用与 send()相同,recvfrom()的使用与 recv()相同。
函数原型:int sendto(SOCKET s,const char buf,int len,int flags,
const struct sockaddr FAR* to,int tolen);
参数:buf 为发送数据的缓冲,len 为发送数据的字节,to 参数为一个指向 sockaddr 结
构的指针,带有接收数据的套接字目标地址信息,其余参数同 send()
/recv()函数。
函数原型:int recvfrom(SOCKET s,char FAR * buf,int len,int flags,
struct sockaddr FAR* from,int * fromlen);
参数:from 是一个 sockaddr 结构指针,fromlen 参数是带有指向地址结构的长度的指
针。当它返回数 据 时,sockaddr 结 构 内 便 填 入 发 送 数 据 端 的 地 址,其 余 参 数 同 send ()
/
recv()函数。
(8)关闭套接字 shutdown()、closesocket()
在应用 closesocket()之前,应调用 shutdown()函数从容地中断连接,即发送端通知接
收端“不再发送数据”或接收端通知发送端“不再接收数据”。
函数原型:int shutdown(SOCKET s,int how);
int closesocket(SOCKET s);
参数:s 为要关闭的套接字号。
how 为关闭的方式,取值有 0(仅关闭数据接收),1(仅关闭数据发送),2(二者都关闭)
shutdown()调用成功返回 0,否则返回 SOCKET_ERROR(即返回 1)。
(9)WSAStartup()
函数原型:int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData);
参数:wVersionRequested 用于指明应用程序请求使用的 Socket 版本。
lpWSAData 用于返回请求的 Socket 的版本信息。
当一个应用程序调用 WSAStartup 函数时,操作系统利用请求的 Socket 版本来搜索相应
324 的 Socket 库,然后 将 找 到 的 Socket 库 绑 定 到 应 用 程 序 中。 此 后,应 用 程 序 就 可 以 调 用
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
Socket 库中的成员函数。
(10)WSACleanup()
函数原型:int WSACleanup(void);
应用 程 序 在 完 成 对 请 求 的 Socket 库 使 用 后, 需 要 调 用 WSACleanup 函 数 来 解 除 与
Socket 库的绑定并且释放 Socket 库所占用的系统资源。
16
24 s
tru
cts
oc r结构
kadd
sockaddr 结构表示一个套接字地址,它定义如下:
struct sockaddr
unsigned short sa_family;//表示地址族,对于 TCP/IP 应用而言,它只能取 AF_INET
char sa_data14; //表示地址、端口等信息
};
struct sockaddr_in
short sin_family;//表示地址族,必须是 AF_INET
unsigned short sin_port; //表示端口号
struct in_addr sin_addr; //表示 32 位 IP 地址,用 in_add 结构表示
char sin_zero8;//表示全部填充 0,保证和 sockaddr 大小
//相同
};
struct in_addr
union struct u_char s_b1,s_b2,s_b3,s_b4; S_un_b;
struct u_short s_w1,s_w2; S_un_w;
u_long s_addr;
S_un;
16
25 Wi
ndowsSoc
ke I辅助函数
tAP
端口号和 IP 地址都是以网络字节存放的。这是由于不同主机的字节顺序可能不同,当
它们通过网络互联时,需要以相同的字节顺序来通信。以 32 位的 IP 地址为例,它在网络上
的传输顺序是:0~7bit,8~15bit,16~23bit 和 24~ 31bit。 这种字节顺序被称为大头 (big
endian)字节序。对一些以小头(little endian)字节序存储的系统,必须进行相应的字节序
转换。
(1)字节序转换函数
字节序转换函数共有 4 个,具体见表 16 2。
325
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
表16 2 字节序转换函数
字节序转换函数 说 明
u_short htons (u_short hostshort) 主机转换成网络字节顺序(短整型)
u_long htonl (u_long hostlong) 主机转换成网络字节顺序(长整型)
u_long ntohl (u_long netlong) 网络转换成主机字节顺序(长整型)
u_short ntohs (u_short netshort) 网络转换成主机字节顺序(短整型)
表16 3 I
P 地址转换函数
IP 地址转换函数 说 明
unsigned long inet_addr (const char FAR * cp) 点分十进制转换成 32 位无符号整型
char FAR * inet_ntoa (struct in_addr in) 32 位无符号整型转换成点分十进制
struct hostnet
char * h_name; //主机的正式名称
char * * h_aliases; //主机的别名
int h_addrtype; //主机地址类型,对于 TCP/IP 协议而言取
//值为 AF_INET
int h_length; //地址长度,对于 TCP/IP 协议而言取值为 4
char * * h_addr_list; //存储主机地址的数组
# define h_addr h_addr_list0 //向后兼容
};
16
3 Wi t编程流程与编程实例
ndowsSocke
16
31 流套接字编程流程
326 流套接字的服务器进程和客户进程在通信前必须创建各自的套接字并建立连接,然后
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
才能对相应的套接字进行“读”、“写”操作,实现数据的传输。图 16 1 为流套接字编程流程
的时序图。
服务器端和客户端编程主要步骤如下:
(1)服务器进程创建套接字(socket)
服务进程总是先于客户进程启动,服务进程首先调用 socket 函数创建一个流套接字。
(2)将服务器本地地址绑定到所创建的套接字上以使在网络上标识该套接字 (bind)
调用 bind()函数来完成。通过 bind 函数,套接字就和 IP 地址及端口联系在一起了。
(3)开始监听连接(listen)
将套接字置于监听模式并准备接收连接请求,调用 listen(
)让一个套接字等候进入连接。
(4)准备接受来自客户端的连接请求(accept)
通过调用 accept()函数使套接字作好接收客户连接的准备 。 accept()函数返回后,其
中参数 addr 中会包含发出连接请求的那个客户机的 IP 地址信息,并返回一个新的套接字
描述字,它对应于已经接收的那个客户机的连接 。对于该客户机后续的所有操作,都应使用
这个新套接字,至于原来那个监听套接字,它仍然用于接收其他客户机连接,而且仍处于监
听模式。如无连接请求,服务进程被阻塞。
(5)创建客户机套接字(socket)
(6)与远程服务器进行连接(connect)
调用 connect()函数可以建立一个端的连接,在 connect()函数中,参数 s 标识一个未连
接的套接字的描述字。name 标识服务进程 IP 地址信息。
(7)连接请求
当连接请求到来后,被阻塞服务进程的 accept()函数生成一个新的套接字与客户套接
字建立连接,并向客户返回接收信号。
图16 1 流套接字编程时序图
327
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
(8)数据收发(send、recv)
一旦客户机的套接字收到服务器的接收信号,则表示客户机与服务器已实现连接,则可
以进行数据传输了。用 send()、recv()函数进行数据收发。
(9)关闭套接字
调用 shoudown()、closesocket()函数。
16
32 数据报套接字编程流程
数据报套接字是无连接的,它的编程流程要简单些,见图 16 2。
图16 2 数据报套接字编程时序图
16
33 Wi
ndowsSoc
ke I编程实例
tAP
【例16 1】 利 用 Windows Socket API 实 现 服 务 器 与 客 户 端 的 通 信, 具 体 说 明 见
表16 4。
服务器端:
(1)新建一基于对话框的应用程序 Ex16_1Server
(2)在 Ex16_1ServerDlg.h 文件中添加语句
# include" winsock.h"
# pragma comment(lib," wsock32" )
(3)添加控件、控件成员变量和消息函数(见表 16 5)
328
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
项目 图示和说明
程 图(
c 客户端主界面 图(
d 登录成功消
息对话框
序
界 图(
a 服务器端主界面
面
图(
e 服务器未启动或端口不对的消息对话框
图(
b 服务器已启动
消息对话框
图(
f 用户名不正确的 图(
g 用户密码不正确
消息对话框 的消息对话框
程 (1)启动程序 Ex16_1Server.exe,见图(a),点击“设定并启动服务器”按钮,弹出图(b)
(2)启动程序 Ex16_1Client.exe,见图(c),点击“登录”按钮,弹出图(d);若服务器没有启动则弹出
序
图(e);若服务器已启动,用户名错弹出图(f);若服务器已启动,用户密码错弹出图(g)
功 (3)若登录成功,在客户端输入要发送的信息,然后点击“发送”按钮,在服务器端点击“接收”按
能 钮,客户端的发送信息的编辑框的信息显示在服务器端的接收信息编辑框中
16_
清单16 1 定义变量和函数(在 Ex 1Se
rve
rDl
g.h文件)
c
lassCEx16_ 1ServerD
lgpub li
cCD
ial
og
public:
void CleanUp();//解除与 Socket 库的绑定并且释放 Socket 库所占用的系统资源
void StartUp();//搜索相应的 Socket 库,然后将找到的 Socket 库绑定到应用程序中
...
protected:
int ret;
int error;
SOCKET m_hSocket;//定义套接字对象,接收创建的套接字
SOCKET s;//定义套接字对象
SOCKADDR_IN m_addr;//定义结构对象
};
(5)在构造函数中初始化变量(见清单 16 2)
16_
清单16 2 构造函数(在 Ex 1Se
rve
rDl
g.pp文件)
c
CEx16_ 1S er
v erD
lgCEx16_1S
erverDg(
l CWnd pPar
en/=NULL/)
t
:CD ia
lo g(CEx16_1S
erv
erDlgIDDpPa
ren
t )
{...
m_hSocket = NULL;
s = NULL;
ret = 0;
error = 0;
(6)服务器启动、监听等功能的实现(见清单 16 3)
服务器启动、监听等功能是在消息函数 OnOK()中实现的,其编程过程按图 16 1 中服务
器的编程流程进行。
16_
清单16 3 OnOK()函数(在 Ex 1Se
rve
rDl
g.pp文件)
c
v
oidCEx16_1ServerDlg OnOK()
{ //创建服务器端的 socket
if(m_hSocket != NULL)
{ closesocket(m_hSocket);//如果已创建了套接字先关闭原来的
m_hSocket = NULL;
if(m_hSocket = = NULL)
{ m_hSocket = socket(AF_INET,SOCK_STREAM,0);
ASSERT(m_hSocket != NULL);
}
UpdateData();
//将服务器本地地址绑定到所创建的套接字
m_addr.sin_family = AF_INET;
330 m_addr.sin_addr.S_un.S_addr = INADDR_ANY;//任何 IP 地址
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
m_addr.sin_port = htons(m_nPort);
ret = bind(m_hSocket,(LPSOCKADDR)&m_addr,sizeof(m_addr));
if(ret = = SOCKET_ERROR)
{ TRACE("Bind Error: %d \n" , (error = WSAGetLastError()));
return;
AfxMessageBox("服务器已启动!");
//开始监听
ret = listen(m_hSocket, 2);
if(ret = = SOCKET_ERROR)
{ TRACE("Listen Error: %d \n" , (error = WSAGetLastError()));
return;
//使套接字作好接受客户连接的准备
s = accept(m_hSocket, NULL, NULL);
if(s = = SOCKET_ERROR)
{ TRACE("Accept Error: %d \n" , (error = WSAGetLastError()));
return;
//一旦有用户连接,就等待用户发来的请求信息,接收客户端的数据
char buff256;
ret = recv(s,buff,256,0);
if(ret = = 0 || ret = = SOCKET_ERROR)
{ TRACE("Recv data error: %d\n" , WSAGetLastError());
return;
//下面是对客户发来的信息进行处理,属字符处理
char*name = NULL;
char*pass = NULL;
int len = 0;
len = buff0;
name = new charlen+1;
for(int i = 0; i < len; i++)
namei= buffi+1;
int len2 = bufflen+1;
pass = new charlen2+1;
for(i = 0; i < len2; i++)
passi= buffi+2+len;
passlen2= \0 ;
namelen= \0 ;
CString str;
if(strcmp(name," ware" )! = 0)
{ str = _T("用户名不正确!");
TRACE(_T("用户名不正确! \n" ));
}
else
if(strcmp(pass," 11111" )! = 0)
{ str = _T("用户密码不正确!");
TRACE(_T("用户密码不正确! \n" ));
}
}
331
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
if(str.IsEmpty())
str = _T("登录成功!");
//将“登录成功!”字符串发送到客户端
char*buf = str.GetBuffer(0);
ret = send(s, buf, str.GetLength(), 0);
if(ret != str.GetLength())
{ TRACE("Send data error: %d\n" , WSAGetLastError());
return;
delete name;
delete pass;
v
oidCEx16_ 1Ser verDlgOnRe cv()
{ char buff256;
ret = recv(s,buff,256,0);
if(ret = = 0 || ret = = SOCKET_ERROR)
{ TRACE("Recv data error: %d\n" , WSAGetLastError());
return;
char*strsend = NULL;
int len = 0;
len = buff0;
strsend = new charlen+1;
for(int i = 0; i < len; i+ + )
strsendi= buffi+1;
strsendlen= \0 ;
m_strRecvClient = strsend;
UpdateData(false);
delete strsend;
16_
清单16 5 其他函数(在 Ex 1Se
rve
rDl
g.pp文件)
c
v
oidCEx16_1S erverDlgStatUp()
r
{ WSADATA wsaData;
WORD version = MAKEWORD(2, 0);
int ret = WSAStartup(version, &wsaData);
if(ret != 0)
TRACE("Initialize Error!\n" );
}
v
oidCEx16_1S erverDlgCleanUp()
{ if (WSACleanup()! = 0)
332
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
(9)初始化对话框函数(见清单 16 6)
清单16 6 On
Ini
tDi
alo 16_
g()函数(在 Ex 1Se
rve
rDl
g.pp文件)
c
BOOLCEx 16_1ServerD
lgOnInitDia
log()
{ CDialog::OnInitDialog();
...
StartUp();
return TRUE; //return TRUE unless you set the focus to a control
(10)编译与运行
客户端:
(1)新建一基于对话框的应用程序 Ex16_1Client
(2)在 Ex16_1ClinetDlg.h 文件中添加语句
# include" winsock.h"
# pragma comment(lib," wsock32" )
(3)添加控件、控件成员变量和消息函数(见表 16 6)
表16 6 控件ID 号、成员变量和消息函数
16_
清单16 7 定义变量和函数(在 Ex 1Se
rve
rDl
g.h文件)
c
lassCEx16_ 1Clie
n tD
lgpubl i
cCD i
alog
public:
CEx16_1ClientDlg(CWnd*pParent = NULL); //standard constructor
void CleanUp();
void StartUp();
...
protected:
SOCKET m_hSocket;
SOCKADDR_IN m_addr;
int ret;
int error;
;
(5)在构造函数中初始化变量(见清单 16 8)
16_
清单16 8 构造函数(在 Ex 1Cl
ien
trD
lg.
cpp文件)
CEx16_1C li
e n
tD l
g
CEx 16_1Cl
ien
tDlg(CWnd pPa
ren
t/=NULL/)
:CDi
alo (
g CEx _
161C l
ien
tDl
gIDDpPa
ret)
n
{ m_hSocket = NULL;
ret = 0;
error = 0;
16_
清单16 9 OnOK()函数(在 Ex 1Cl
ien
tDl
g.pp文件)
c
v
o i
dCEx16_1Clien
tD lgOnOK()
{//创建客户端套接字
if(m_hSocket != NULL)
{ closesocket(m_hSocket);//如果已创建了套接字先关闭原来的
m_hSocket = NULL;
if(m_hSocket = = NULL)
{ m_hSocket = socket(AF_INET,SOCK_STREAM,0);
ASSERT(m_hSocket != NULL);
}
UpdateData();
//主动连接服务器
m_addr.sin_family = AF_INET;
m_addr.sin_addr.S_un.S_addr = inet_addr(m_strServerIP.GetBuffer(0));
m_addr.sin_port = htons(m_nPort);
ret = connect(m_hSocket, (LPSOCKADDR)&m_addr, sizeof(m_addr));
if(ret = = SOCKET_ERROR)
{ TRACE("Connect Error: %d \n" , (error = WSAGetLastError()));
if(error = = 10061)
334
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
AfxMessageBox(_T("请确认服务器已打开,并工作在同样的端口!"));
return;
//准备向服务器发送请求的数据包
CString str;
str+= char(m_strUser.GetLength());
str+= m_strUser;
str+= char(m_strPass.GetLength());
str+= m_strPass;
char*buf = str.GetBuffer(0);
ret = send(m_hSocket, buf, str.GetLength(), 0);//发送数据到服务器
if(ret != str.GetLength())
{ TRACE("Send data error: %d\n" , WSAGetLastError());
return;
char buff256;
ret = recv(m_hSocket, buff, 256, 0);//等待服务器的响应,该函数将堵塞
if(ret = = 0)
{ TRACE("Recv data error: %d\n" , WSAGetLastError());
return;
buffret= \0 ;
AfxMessageBox(buff);//显示收到的服务器回传的信息
}
v
oidCEx16_ 1ClientDl
g OnSend()
{ UpdateData(true);
CString str;
str+= char(m_strSend.GetLength());
str+= m_strSend;
char*buf = str.GetBuffer(0);
ret = send(m_hSocket, buf, str.GetLength(), 0);
if(ret != str.GetLength())
{ TRACE("Send data error: %d\n" , WSAGetLastError());
return;
4 MFC 中的 Wi
16 nsock
MFC 提供了两个类用于网络编程,即 CAsyncSocket 类和 CSocket 类。 CAsyncSocket 类 335
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
16
41 CAs
yncSoc
ket类及其主要成员函数
CAsyncSocket 类直接封装了 WinSock API。
(1)创建套接字函数 Create()
创建一个 CAsyncSocket 类对象,并调用 Create()函数创建套接字。
函数原型:BOOL Create (UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long
lEvent = FD _ READ | FD _ WRITE | FD _ OOB | FD _ ACCEPT | FD _
CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL);
参数 nSocketPort 为套接字指定一个端口;lEvent 用于指定要生成的事件通知。 默认
时,所有事件都会生成通知。
lpszSocketAddress 是套接字的网络地址,如“12856228”或 ftp.microsoft.com;默认
值为 NULL,表示套接字地址限定为本地机。 对于服务器而言,调用 Create ()应该指明服务
端口号;而对于客户端,使用 Create()的默认值就行了,因为调用 Connect()时会给出端口号
的。
例一:CAsyncSocket socket;
socket.Create();
例二:CAsyncSocket * pSocket = new CAsyncSocket;
pSocket - > Create(nPort,SOCK_DGRAM);
(2)套接字与服务器连接函数 Connect()
客户机套接字可以调用 CAsyncSocket::Connect 来与服务器连接。如果连接成功,返回
TRUE,否则返回 FALSE。连接失败时,可以调用 GetLastError 来得到详细的错误报告。
无论成功与否,当 Connect 调用完成后将调用 OnConnect 消息函数来通知正在连接的套
接字。OnConnect 可以在 CAsyncSocke 的派生类中重载。
函数原型为:virtual void OnConnect(int nErrorCode);
参数:nErrorCode 为错误代码,如果 nErrorCode 为 0,表示连接成功,此时套接字可以准
备发送或接收数据。如果发生错误,那么将在 nErrorCode 中包含一个特定的错误码。
(3)接受与服务器连接函数 Listen()
如果创建了 服 务 器 套 接 字,那 么 可 以 调 用 CAsyncSocket::Listen 来 侦 听 连 接 请 求。
Listen 带有一个后备参数,用于确定等待队列中有多少个连接请求。 当套接字接受队列中
的连接请求后,将调用 OnAccept。可以在 CAsyncSocket 的派生类中重载 OnAccept。为了接
受客户机的请求,需要从 OnAccept 中调用 Accept。
函数原型为:BOOL Listen(int nConnectionBacklog = 5);
参数:nConnectionBacklog 表示请求连接队列的最大长度,有效值为 1 到 5。
(4)发送和接收流式数据函数 Send() /Receive()
要从流套接字中发送数据,可以调用 CAsyncSocket::Send。当套接字的缓存是空的,并
且套接字可以进行另一次发送时,将调用 OnSend 来通知套接字现在可以调用 Send 来发送
336 数据。
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
函数原型:virtual int Send(const void* lpBuf, int nBufLen, int nFlags = 0);
参数:lpBuf 指向接收输入数据缓冲区的指针,其长度由 nBufLen 指定。nFflags 指定传
输控制方式,如是否接收带外数据等。
套接字 接 收 到 数 据 后, 将 调 用 OnReceive。 可 以 在 CAsyncSocket 的 派 生 类 中 重 载
OnReceive 并调用 Receive 从套接字缓存中读取数据。 套接字接收的数据将一直保存在套
接字缓存中,除非调用 Receive 将其读走。
函数原型:virtual int Receive(const void* lpBuf, int nBufLen, int nFlags = 0);
参数含义同上。
若发送和接收数据报数据可用函数 SendTo()和 ReceiveFrom()。
(5)关闭套接字 Close()
使用完套接 字 后, 要 调 用 CAsyncSocket:: Close 来 释 放 与 套 接 字 有 关 的 系 统 资 源。
Close 通 常 在 套 接 字 析 构 函 数 中 调 用, 必 须 删 除 之; 不 必 关 心 套 接 字 的 关 闭, 因 为
CAsyncSocket 对象析构时会调用 Close()关闭套接字句柄。
16
42 CAs
yncSoc
ket类编程实例
【例16 2】 利用 CAsyncSocket 类实现信息的发送与接收,具体说明见表 16 6。
服务器端和客户端用同一程序,所以只要编写一个应用程序。
(1)新建一基于对话框的应用程序 Ex16_2
在 step 2 of 4 选中 Windows Sockets 复选按钮。
(2)添加控件、控件成员变量和消息函数(见表 16 7)
表16 6 例16 2具体说明
项目 图 示 和 说 明
序 图(
a) 选中服务器 图(
b) 消息对话 图(
c) 服务器端数据的发送与接收
图(
d) 选中客户端 图(
e) 消息对话 图(
f) 客户端数据的发送与接收
337
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
续 表
项目 图 示 和 说 明
(1)启动程序 Ex16_2.exe,选中服务器,见图(a):点击“启动服务器开始侦听”按钮,弹出图(b),图
(a)上半部分不可用
程 (2)启动程序 Ex16_2.exe,选中客户端,见图(d):点击“连接”按钮,弹出图(e),图(a)和图(d)变成
图(c)和图(f)
序
(3)在客户端:在发送的消息编辑框中输入文本,点击“发送”按钮,客户端的信息发送到服务器的
功 接收的信息列表框中
能 (4)在服务器端:在发送的消息编辑框中输入文本,点击“发送”按钮,服务器的信息发送到客户端
的接收的信息列表框中
(5)在客户端:点击“断开”按钮,客户端界面回到图(d),服务器界面回到图(a)
(3)编写点击两个单选按钮的消息函数(见清单 16 11)
16_
清单16 11 点击两个单选按钮的消息函数(在 Ex 1Se
rve
rDl
g.pp文件)
c
v
oidCEx16_2DlgOnC li
enttype()//单击客户类型单选按钮
{ UpdateData(true);
m_ctrlConnect.SetWindowText("连接");
GetDlgItem(IDC_BCLOSE)->EnableWindow(true);//断开按钮可用
}
v
oidCEx16_2DlgOnS er
vertype()//单击服务器类型单选按钮
{ UpdateData(true);
m_ctrlConnect.SetWindowText("启动服务器开始侦听");
GetDlgItem(IDC_BCLOSE)->EnableWindow(false);//断开按钮不可用
}
(4)创建新类 CNewScoket
CNewScoket 类从 CAsyncSocket 类派生,点击菜单 Insert - > New Class。 在 New Class
对话框中,类的类型为 MFC Class;类名中输入 CNewSocket;基类选择 CAsyncSocket;单击 OK,
338 即可把这个新类添加到应用程序 CEx16_2 中。
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
c
las
sCNewS ocketpublicCAs yncSocke
t
public:
CNewSocket();
virtual ~CNewSocket();
public:
void SetParent(CDialog *pWnd); //手工添加
...
protected:
void OnClose(int nErrorCode); //手工添加
void OnConnect(int nErrorCode); //手工添加
void OnAccept(int nErrorCode); //手工添加
void OnReceive(int nErrorCode); //手工添加
void OnSend(int nErrorCode); //手工添加
private:
CDialog* m_pWnd; //手工添加
};
# include" Ex16_2Dlg.h"
v
oi dCNewS ocketSetParet(
n CD
ial
ogpWnd)
{ m_pWnd = pWnd;
v
oidCNewSocketOnAc c e
pt(in
tnErrorCo e)
d
{ if(nErrorCode = = 0)
((CEx16_2Dlg* )m_pWnd)->OnAccept();
}
v
oidCNewSocketOnCo nnet(
c in
tnEr r
orCo e)
d
{ if(nErrorCode = = 0)
((CEx16_2Dlg* )m_pWnd)->OnConnect();
}
v
oidCNewSocketOnC lose(i
ntnErrorCode)
{ if(nErrorCode = = 0)
((CEx16_2Dlg* )m_pWnd)->OnClose();
}
v
oidCNewSocketOnRe c e
ive(
intnErrorCode)
{ if(nErrorCode = = 0)
((CEx16_2Dlg* )m_pWnd)->OnReceive();
}
v
oidCNewSocketOnSe nd(intnErr
orCo de)
{ if(nErrorCode = = 0)
((CEx16_2Dlg* )m_pWnd)->OnSend();
}
(7)在对话框类的头文件中定义变量和函数(见清单 16 14)
16_
清单16 14 在对话框类的头文件中定义变量和函数(在 Ex 2Dl
g.h文件中)
# include" NewSocket.h"
c
lassCEx 16_ 2DlgpublicCD ia
log
public:
void OnAccept(); //手工添加
void OnConnect(); //手工添加
void OnSend(); //手工添加
void OnReceive(); //手工添加
void OnClose(); //手工添加
...
private:
CNewSocket m_sListenSocket; //手工添加,用于连接请求的侦听
CNewSocket m_sConnectSocket; //手工添加,用于来回发送消息
...
};
(9)连接应用程序,编写单击连接按钮的消息函数(见清单 16 16)
清单16 16 OnBCo
nne
c 16_
t()函数 (在 Ex 2Dl
g.pp文件中)
c
v
oidCEx16_2DlgOnBc onnec //单击连接按钮
t()
{ UpdateData(true);
GetDlgItem(IDC_BCONNECT)->EnableWindow(false);
GetDlgItem(IDC_SERVERIP)->EnableWindow(false);
GetDlgItem(IDC_PORT)->EnableWindow(false);
GetDlgItem(IDC_STATICIP)->EnableWindow(false);
GetDlgItem(IDC_STATICPORT)->EnableWindow(false);
GetDlgItem(IDC_STATICTYPE)->EnableWindow(false);
340 GetDlgItem(IDC_CLIENTTYPE)->EnableWindow(false);
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
GetDlgItem(IDC_SERVERTYPE)->EnableWindow(false);
//客户端
if(m_iType = = 0)
{ m_sConnectSocket.Create();//客户端产生一个缺省的套接字
m_sConnectSocket.Connect(m_strServerIP,m_uPort);//客户端与服务器端连接
}
else//服务器端
{ m_sListenSocket.Create(m_uPort);//服务器端产生一个绑定端口的套接字
m_sListenSocket.Listen();//服务器监听
AfxMessageBox("服务器已启动,开始侦听");
}
}
16_
清单16 17 CEx 2Dl 16_
g类中手工添加的函数(在 Ex 2Dl
g.pp文件中)
c
v
oidCEx16_2D l
g OnAccept()
{ m_sListenSocket.Accept(m_sConnectSocket);
GetDlgItem(IDC_EMSG)->EnableWindow(true);
GetDlgItem(IDC_STATICMSG)->EnableWindow(true);
GetDlgItem(IDC_BSEND)->EnableWindow(true);
GetDlgItem(IDC_BCONNECT)->EnableWindow(false);
GetDlgItem(IDC_SERVERIP)->EnableWindow(false);
GetDlgItem(IDC_PORT)->EnableWindow(false);
GetDlgItem(IDC_STATICIP)->EnableWindow(false);
GetDlgItem(IDC_STATICPORT)->EnableWindow(false);
GetDlgItem(IDC_STATICTYPE)->EnableWindow(false);
GetDlgItem(IDC_CLIENTTYPE)->EnableWindow(false);
GetDlgItem(IDC_SERVERTYPE)->EnableWindow(false);
}
v
oidCEx16_2D l
g OnConnect()
{ GetDlgItem(IDC_EMSG)->EnableWindow(true);
GetDlgItem(IDC_STATICMSG)->EnableWindow(true);
GetDlgItem(IDC_BSEND)->EnableWindow(true);
GetDlgItem(IDC_BCLOSE)->EnableWindow(true);
AfxMessageBox("已连接! 可以发送消息了");
}
(11)编译、运行与测试
编译完成后,同时启动程序的两份拷贝,一个是在服务器端,单击启动服务器开始侦
听按钮,另一个在客户机端,单击连接按钮 。 连接后,连接控件被禁用,消 息 发 送 控 件 被
启用 。
(12)发送和接收
应用程序连接后,还需要添加消息收发功能,使用户能在对话框窗口的编辑框中输入文
本消息,然后单击发送按钮将消息发送到对方程序的列表框中。 编写单击发送按钮的消息
函数 OnBSend(),见清单 16 18。 341
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
清单16 18 OnBS
e 16_
nd()消息函数(在 Ex 2Dl
g.pp文件中)
c
v
oidCEx16_ 2Dlg OnB send()
{ int iLen;
int iSent;
UpdateData(true);
if(m_strMessage!="" )
{ iLen = m_strMessage.GetLength();
iSent = m_sConnectSocket.Send(LPCTSTR(m_strMessage),iLen);
if(iSent = = SOCKET_ERROR)
{ AfxMessageBox("不能发送!");
}
else
v
oidCEx16_ 2D l
g OnRe ceie()
v
{ char *pBuf = new char1205;
int iBufSize = 1024;
int iRcvd;
CString strRecvd;
iRcvd = m_sConnectSocket.Receive(pBuf,iBufSize);
if(iRcvd = = SOCKET_ERROR)
{ }
else
pBufiRcvd= NULL;
strRecvd = pBuf;
m_ctrlRecvd.AddString(strRecvd);
UpdateData(false);
}
}
(13)终止连接
为关闭两个程序之间的连接,客户机端的用户应单击断开按钮终止这次连接。 服务器
端的应用程序接着就会收到 OnClose Socket 事件。 OnClose(函数和 OnBClose)函数见清单
16 20。
清单16 20 OnC
loe()函数和 OnBC
s los 16_
e()消息函数(在 Ex 2Dl
g.pp文件中)
c
v
oidCEx16_2DlgOnC loe()
s
{ m_sConnectSocket.Close();
UpdateData(true);
GetDlgItem(IDC_EMSG)->EnableWindow(false);
GetDlgItem(IDC_STATICMSG)->EnableWindow(false);
GetDlgItem(IDC_BSEND)->EnableWindow(false);
342 GetDlgItem(IDC_BCLOSE)->EnableWindow(false);
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
GetDlgItem(IDC_BCONNECT)->EnableWindow(true);
GetDlgItem(IDC_SERVERIP)->EnableWindow(true);
GetDlgItem(IDC_PORT)->EnableWindow(true);
GetDlgItem(IDC_STATICIP)->EnableWindow(true);
GetDlgItem(IDC_STATICPORT)->EnableWindow(true);
GetDlgItem(IDC_STATICTYPE)->EnableWindow(true);
GetDlgItem(IDC_CLIENTTYPE)->EnableWindow(true);
GetDlgItem(IDC_SERVERTYPE)->EnableWindow(true);
}
v
oi 16_
dCEx 2DlgOnBc
loe()
s
{ OnClose();}
(14)编译和运行
16
43 CSoc
ket类
CSocket 类 派 生 自 CAsyncSocket 类, 它 实 现 了 对 WinSock API 的 更 高 层 的 抽 象。
CSocket 类通常和 CSocketFile 类、CArchive 类结合使用,通过 CArchive 类的序列化接口完
成数据的收发功能。CSocket 改写了 CAsyncSocket 的多个函数以支持同步操作。 它们包括
Accept()、Receive()、Send()等。实际上,这些函数由原来的一调用就返回改写成等待直到
调用完成才返回。
使用 CSocket 编程可以参照下面的步骤。
(1)参考 CAsyncSocket 的使用方法完成客户端和服务器端 Socket 的创建和监听 (或是
连接)工作。
(2)不要利用 Send ()、Receive ()、SendTo ()、ReceiveFrom ()发送和接收数据,而是以
Csocket 对象创建一个 CSocketFile 对象。
例如在服务器端:
CSocket servSocket;
...
CSocketFile servSocketFile(&servSocket);
(3)根据 CSocketFile 对象创建一个与之关联的 CArchive 对象,然后便可以在这个
CArchive 对象上收发数据了:
CArchive servSocketArchiveIn(&servSocketFile,CArchive::load);
CArchive servSocketArchiveIn(&servSocketFile,CArchive::store);
(4)调用 CAchive 的“<< ”和“>> ”方法收发数据。
(5)通信结束,清除 CSocket、CSocketFile 和 CArchive 对象数据。
5 串行端口通信编程
16
串行端口通常指的是计算机提供的 COM1、COM2、COM3、COM4 等 RS232 C 端口。串行端口的
本质是作为 CPU 与外部串行设备间的编码转换设备。即当数据从 CPU 经过串行端口发送出去
时,字节数据将被转换为串行的位;在接收数据时,串行的位将由串行端口转换成字节数据。 343
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
在进行串行通信编程时,其步骤如下:
(1)操作系统提出资源申请要求(打开串口)
(2)对端口进行参数配置
(3)同串口进行数据交换并完成数据从串口的发送与接收
(4)在通信完成时应及时释放资源(关闭串口)
用VC++开发串行通信目前通常有如下几种方法:一是利用 Windows API 通信函数;二是
利用VC++的标准通信函数_inp、_inpw、_inpd、_outp、_outpw、_outpd 等直接对串口进行操
作;三是使用 Microsoft Visual C++的通信控件(MSComm)。
16
51 Wi
n I串行端口通信函数及编程实例
dowsAP
与通信有关的 Windows API 函数共有 26 个,本节主要介绍下面几个主要函数:
CreateFile()用“comn”(n 为串口号)作为文件名就可以打开串口;SetCommState()函数
负责完成串口的普通设置;ReadFile()读串口;WriteFile()写串口;CloseHandle()关闭串口
句柄。
(1)CreateFile()函数打开串口
函数原型:HANDLE CreateFile(
LPCTSTR lpFileName, //指向一个通信资源的对象名,如“COM1”
DWORD dwDesiredAccess, //串口的存取方式
DWORD dwShareMode, //串口的共享方式,设为 0
LPSECURITY_ATTRIBUTES lpSecurityAttributes,//串口的安全属性结构
DWORD dwCreationDisposition, //创建方式,在此只能取 OPEN_EXISTIN
DWORD dwFlagsAndAttributes, //文件属性,取 FILE_FLAG_OVERLAPPED
HANDLE hTemplateFile //设置为 NULL
);
函数成功返回一个句柄,该句柄标识了由 lpFileName 指定的串口。在此后对串口的所
有操作均通过该句柄来进行。 在串口不使用时,应通过 CloseHandle()函数将其关闭,以释
放资源。
(2)SetCommState()函数设置串口
函数原型:BOOL SetCommState(
HANDLE hFile, //串口句柄
LPDCB lpDCB //指向 DCB 结构对象的指针
);
(3)I/O 读写数据
如果用 CreateFile()打开串口时,没有使用 FILE_FLAG_OVERLAPPED 标志,则表示此端
口进行的是同 步 I/O 读 写, 在 同 步 方 式 下 进 行 数 据 发 送 与 接 收 分 别 由 WriteFile ()和
ReadFile()函数完成。其函数原型为:
发送:BOOL WriteFile(
HANDLE hFile, //串口句柄
LPCVOID lpBuffer, //发送缓存
344 DWORD nNumberOfBytesToWrite, //要发送的字节
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
函 数 作 用
BOOL GetCommTimeOuts() 获得当前的超时设置
BOOL SetCommTimeOuts() 完成超时设置
BOOL GetCommState() 获得串口的设置
BOOL SetupComm() 缓冲区大小的设置
BOOL SetCommMask() 建立事件屏蔽
BOOL GetCommMask() 查询得到串口当前的时间屏蔽设置情况
BOOL GetCommProperties() 获取系统支持的各项串口设置
BOOL ClearCommError() 确定缓冲区中处于等待的字节数
BOOL WaitCommEvent() 监视发生在串行口中的各种串口通信事件
BOOL GetOverlappedResult() 后台异步执行的操作是否已经完成
HANDLE CreatEvent() 创建事件句柄
BOOL ResetEvent() 复位
(6)有关结构(见表 16 9)
表16 9 有关结构
结 构 作 用
该结构包括 28 个成员,这些成员是对串口的各基本控制参数
的描述,其中 BaudRate、ByteSize、Parity 和 StopBits 这 4 个
typedef struct_DCBDCB;
成员最基本,通过这几个成员即指定了串口通信时所采取的
波特率、数据位、奇偶校验方式和停止位 345
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
续 表
结 构 作 用
typedef struct _ COMMTIMEOUTS
该结构包括 5 个参数,为超时设置结构
COMMTIMEOUTS,*LPCOMMTIMEOUTS;
Typedef Struct_COMMPROP OMMPROP 该结构包括 18 个成员,各成员记载了系统支持的各项配置
typedef struct _ COMM _ CONFIG 该结构包括 8 个成员,其中包含有一个 DCB 结构对象,各成员
COMMCONFIG,*LPCOMMCONFIG; 主要是关于通信设备的配置信息
typedef struct _ OVERLAPPED 该结构包括 5 个成员,对于串口通信只有 hEvent 成员是有效
OVERLAPPED; 的,其他 4 个成员必须设置为 0
typedef struct_COMSTATCOMSTAT, 该结构包括 10 个成员,对于串口通信只有 hEvent 成员是有效
*LPCOMSTAT; 的,其他 4 个成员必须设置为 0
项目 图 示 和 说 明
程序
菜单
与
界面
图(
a) 程序主界面 图(
b) 串口设置 图(
c) 接收到数据
(1)新建一基于单文档的应用程序 Ex16_3
在 step 4 of 6 选中 Windows Sockets 复选按钮,在 step 6 of 6 中选择 FormView 视图。
(2)添加串口设置对话框和有关控件,并建立串口设置对话框类
对话框 ID 号:IDD_COMSETDLG。串口设置对话框类名:CComsetDlg。有关控件 ID 号和控
件成员变量见表 16 11。
表16 11 串口设置对话框控件ID 号和控件成员变量
控 件 类 型 ID 号 成员变量 成员变量类型
组合框(端口) IDC_COMBO_COM m_nPort int
组合框(波特率) IDC_COMBO_BAUD m_nBaud int
组合框(数据位) IDC_COMBO_DATABIT m_nDataBit int
346 组合框(停止位) IDC_COMBO_STOPBIT m_nStopBit int
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
续 表
控 件 类 型 ID 号 成员变量 成员变量类型
组合框(校验) IDC_COMBO_CHECK m_nCheck int
静态文本(流控制) IDC_STATIC
(3)编辑调用串口设置对话框的菜单,添加和编写该菜单的消息函数
菜单 ID 号:ID_COMM_SET;消息函数:OnCommSet(),消息函数见清单 16 21。
清单16 21 OnCommS
e 16_
t()消息函数(在 Ex 2Vi
ew.
cpp文件中)
v
oidCEx16_ 3V i
ew OnCommS et()//串口设置菜单消息函数
{ //超时结构对象
COMMTIMEOUTS CommTimeOuts;
//设备控制块结构对象
DCB dcb;
//配置串口参数
CComsetDlg dlg;
if (dlg.DoModal()= = IDOK)
{ //打开串口
m_hCom = CreateFile(m_ComPortdlg.m_nPort,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if (m_hCom = = INVALID_HANDLE_VALUE)
{ AfxMessageBox("打开通讯口失败!",MB_OK);
return;
//得到当前串口配置
dcb.DCBlength = sizeof(dcb);
GetCommState(m_hCom, &dcb);
//更改串口配置
//基本配置
dcb.BaudRate = m_ComBauddlg.m_nBaud;
dcb.ByteSize = m_ComDataBitdlg.m_nDataBit;
dcb.StopBits = m_ComStopBitdlg.m_nStopBit;
dcb.Parity = m_ComParitydlg.m_nCheck;
//硬件流控制
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fOutxCtsFlow = FALSE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
//软件流控制
dcb.fInX = FALSE;
dcb.fOutX = FALSE;
dcb.XonChar = (char)0xFF;
dcb.XoffChar = (char)0xFF;
dcb.XonLim = 100;
dcb.XoffLim = 100;
dcb.EvtChar = 0x0d;
347
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
dcb.fBinary = TRUE;
dcb.fParity = TRUE;
//配置串口设置
SetCommState(m_hCom, &dcb);
//超时设置
CommTimeOuts.ReadIntervalTimeout = MAXDWORD;
CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
CommTimeOuts.ReadTotalTimeoutConstant = 0;
CommTimeOuts.WriteTotalTimeoutMultiplier = 2 *9600/dcb.BaudRate;
CommTimeOuts.WriteTotalTimeoutConstant = 25;
SetCommTimeouts(m_hCom, &CommTimeOuts);
//设置缓冲区大小
SetupComm(m_hCom, 4096, 4096);
//开启数据接收线程
AfxBeginThread(ReadProc, this);
}
}
(5)编写接收数据线程函数(见清单 16 23)
清单16 23 接收数据线程函数 Re
adPr
o 16_
c()(在 Ex 2Vi
ew.
cpp文件中)
ov.OffsetHigh = 0;
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
//设置事件驱动的类型
if (!SetCommMask(m_hCom, EV_RXCHAR))
return 1;
while (true)
{ WaitCommEvent(m_hCom, &dwEvtMask, NULL);//等待接收事件
if ((dwEvtMask & EV_RXCHAR)= = EV_RXCHAR)
{ClearCommError(m_hCom, &dwErrorMask, &comstat);//确定接收缓冲中处于等待的字节数
if (comstat.cbInQue> 0)
{ //异步读取数据
if (!ReadFile(m_hCom, Buf+dwPoint, comstat.cbInQue, &dwActRead, &ov))
{ if (GetLastError()= = ERROR_IO_PENDING)
{ //检测数据是否接收完毕
while (!GetOverlappedResult(m_hCom, &ov, &dwActRead, FALSE))
{ //数据尚未接收完毕
if (GetLastError()= = ERROR_IO_INCOMPLETE)
continue;
//对接收到的数据进行处理
if (dwActRead> 0)
{ //判断一条数据是否接受完毕
if (BufdwPoint+dwActRead 1= = 13)
{ //将接收到的数据插入到列表控件
BufdwPoint+dwActRead 1= 0;
pView ->m_ctrlListRecvText.InsertString(
0, CString(
Buf)
);
memset(Buf, 0, sizeof(Buf));//复位
dwPoint = 0;
else
dwPoint+= dwActRead;//修正接收缓存指针
}
}
}
}
else
//对接收到的数据进行处理
if (dwActRead> 0)
{ //判断一条数据是否接受完毕
if (BufdwPoint+dwActRead 1= = 13)
{ //将接收到的数据插入到列表控件
BufdwPoint+dwActRead 1= 0;
pView ->m_ctrlListRecvText.InsertString(0, CString(Buf));
memset(Buf, 0, sizeof(Buf));//复位
dwPoint = 0;
else
dwPoint+= dwActRead;//修正接收缓存指针
}
}
ResetEvent(ov.hEvent);//手动复位
349
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
}
}
}
}
return 1;
v
oidCEx 16_ 3V i
ew OnBS end()//按发送数据按钮
{ if (m_hCom = = INVALID_HANDLE_VALUE);//确保串口已打开
return;
UpdateData(TRUE);
OVERLAPPED ov;//异步结构对象
ov.Internal = 0;
ov.InternalHigh = 0;
ov.Offset = 0;
ov.OffsetHigh = 0;
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
//发送数据
DWORD dwActWrite;
! WriteFile(
if( m_hCom, m_strSendText+ CString(
13), m_strSendText.GetLength(
)+ 1,&dwActWrite, &ov)
)
{
if (GetLastError()= = ERROR_IO_PENDING)
{ //检测数据是否发送完毕
while (!GetOverlappedResult(m_hCom, &ov, &dwActWrite, FALSE))
{ //数据尚未发送完毕
if (GetLastError()= = ERROR_IO_INCOMPLETE)
continue;
//手动复位异步事件
ResetEvent(ov.hEvent);
//清除发送数据
m_strSendText = "" ;
UpdateData(FALSE);
}
(8)编译和运行
350
第十六章 网络通信编程
……………………………………………………………………………………………………………………………………………
16
52 利用端口函数实现串行端口通信编程
这种方式主要是采用两个端口函数_inp(), _outp()实现对串口的读写,
读端口的函数原型为:int _inp(unsigned shot port)
该函数从端口读取一个字节,端口号为 0~65535。
写端口的函数原型为:int _outp(unsigned shot port, int databyte)
该函数向指定端口写入一个字节。 不同的计算机串口地址可能不一样,通过向串口的
控制及收发寄存器进行读写,可以实现灵活的串口通信功能,由于涉及具体的硬件电路讨论
比较复杂,在此不加赘述。
16
53 MSComm 控件及其编程实例
MSComm 控件是微软开发的专用通信控件,封装了串口的所有功能,使用很方便。 在工程
中通过操作菜单“Project - > Add To Project - > Components and Controls”可以弹出一个组
件和控件 的 对 话 框,进 入 “Registered ActiveX Controls ”目 录 下 并 选 择 添 加 “Microsoft
Communications Control,Version60”控件到工程。
(1)MSComm 控件的属性(表 16 13)
属性名称 说 明
CommPort 设置端口号,类型 short:1 comm1 2 comm2
设置串口通信参数,类型 CString:B 波特率,P 奇偶性(N 无校验,E 偶校验,O 奇校验),D
Settings
字节有效位数,S 停止位
PortOpen 设置或返回串口状态,类型 BOOL:TRUE 打开,FALSE 关闭
InputMode 设置从接收缓冲区读取数据的格式,类型 long: 0 Text 1 Bin
Input 从接收缓冲区读取数据,类型 VARIANT
InBufferCount 接收缓冲区中的字节数,类型:short
InBufferSize 接收缓冲区的大小,类型:short
Output 向发送缓冲区写入数据,类型:VARIANT
OutBufferCount 发送缓冲区中的字节数,类型:short
OutBufferSize 发送缓冲区的大小,类型:short
InputLen 设置或返回 Input 读出的字节数,类型:short
CommEvent 串口事件,类型:short
(2)MSComm 控件对文本信息的传输
在使用时,每个 MS Comm 控件只能对应一个串口。 如果需要访问多个串口,就必须相应
地使用多个 MS Comm 控件。
① 设置与打开端口
该类提供了 SetCommPort()函数设置要操作的端口号,用 SetSetting()函数配置串口,
最后通过 SetPortOpen()函数打开端口。
② 发送数据
用 SetOutput()函数发送数据,该函数需要将要发送的数据以 VARIANT 类型参数传入。 351
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
③ 接收数据
用 GetInput()函数从串口接收到数据,并将其保存到一个 COleSafeArray 类型的临时
变量。
【例16 4】 利用 MS Comm 控件实现串口通信,具体功能同例 16 3。
(1)项目名为 Ex16_4,其余同上题(1)
(2)同上题(1)
(3)编辑调用串口设置对话框的菜单,添加和编写该菜单的消息函数
菜单 ID 号:ID_COMM_SET;消息函数:OnCommSet(),消息函数见清单 16 25。
清单16 25 OnCommS
e 16_
t()消息函数(在 Ex 4Vi
ew.
cpp文件中)
v
oidCEx16_4V i
ew OnCommS et()//串口设置菜单消息函数
{ CComSetDlg dlg;
if (dlg.DoModal()= = IDOK)
{ m_ctrlComm.SetCommPort(m_ComPortdlg.m_nPort); //设置端口号
CString sSetting;
sSetting.Format( "%d,%s,%d,%s" , m_ComBauddlg.m_nBaud, m_ComParitydlg.m_nCheck,
m_ComDataBitdlg.m_nDataBit, m_ComStopBitsdlg.m_nStopBit);
m_ctrlComm.SetSettings(sSetting); //配置串口
m_ctrlComm.SetPortOpen(TRUE); //打开串口
}
}
(4)定义串口设置的全局变量(见清单 16 26)
16_
清单16 26 串口设置全局变量的定义(在 Ex 4Vi
ew.
cpp文件中)
END_MESSAGE_MAP()//结束消息映射后添加以下定义
short m_ComPort=1, 2, 3, 4; //串口
int m_ComBaud=1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600; //波特率
int m_ComDataBit=7, 8; //数据位
CString m_ComStopBit=" 1" ," 15" ," 2"; //停止位
CString m_ComParity=" n" ," o" ," e"; //校验方式
16_
清单16 27 变量的定义(在 Ex 4Vi h文件中)
ew.
c
las
sCEx 16_4V i
ewpub l
icCFo
rmV
iew
public:
DWORD m_dwPoint;
char m_Buf4096;
清单16 28 OnBS
e 16_
nd()消息函数(在 Ex 4Vi
ew.
cpp文件中)
v
oidCEx16_4View OnBS e //按发送数据按钮
nd()
{ if (m_ctrlComm.GetPortOpen()= = FALSE)
return;
UpdateData(TRUE);
m_ctrlComm.SetOutput(COleVariant(m_strSendText+CString(13))); //发送数据
m_strSendText ="" ; //清空发送数据
UpdateData(FALSE);
}
(8)添加和编写 OnCommMscomm1()函数接收数据
用 ClassWizard 添加 OnComm 消息函数 OnCommMscomm1(),该函数程序见清单 16 29。
清单16 29 OnCommMs
c 16_
omm1()消息函数(在 Ex 4Vi
ew.
cpp文件中)
v
oidCEx16_4V i
ewOnCommMs comm1()
{ //接收到数据
if (m_ctrlComm.GetCommEvent()= = 2)
{ COleSafeArray oleInput = m_ctrlComm.GetInput(); //从串口读取数据
long dwActRead = oleInput.GetOneDimSize(); //得到数据长度
for (long i = 0; i < dwActRead; i++)
oleInput.GetElement(&i, m_Buf+m_dwPoint+i);
oleInput.Clear(); //释放 OLE 对象
//判断一条数据是否接收完毕
if (m_Bufm_dwPoint+dwActRead-1= = 13)
{ //将接收到的数据插入到列表控件
m_Bufm_dwPoint+dwActRead-1= 0;
m_ctrlListRecvText.InsertString(0,"dfg" );
m_ctrlListRecvText.InsertString(0, CString(m_Buf));
memset(m_Buf, 0, sizeof(m_Buf)); //复位
m_dwPoint = 0;
else
m_dwPoint+= dwActRead; //修正接收缓存指针
}
}
}
(9)编译和运行
353
第十七章 项目开发实例 ——— 学生
管理信息系统
管理信息系统 MIS(Management Information System)是一个由人、计算机等组成的能进
行信息的收集、传送、储存、维护和使用的系统。
本章以学生管理信息系统的开发为例,说明项目的开发过程、项目帮助文件的建立、项
目的集成与打包等内容。本系统使用 ACCESS 或 SQLServer 为后台数据库,提供学生信息管
理的一些常用基本功能。
1 管理信息系统设计原则
17
管理信息系统的设计与开发是一项系统工程,为了保证系统的质量,设计人员必须遵守
共同的原则,尽可能地提高系统的各项指标。具体包括的设计原则如下:
(1)实用性原则
(2)可扩展性与可维护性原则
(3)安全可靠性原则
(4)用户界面设计原则
MIS 人机界面必须遵循以下一些基本原则:
① 以通信功能作为界面的设计核心 ;
② 界面必须始终一致;
③ 界面必须使用户随时掌握任务的进展情况 ;
④ 界面必须能够提供帮助;
⑤ 界面友好,使用方便;
⑥ 输入画面尽可能接近实际;
⑦ 具有加强的容错性。
(5)数据库设计原则
2 需求分析
17
17
21 系统主要功能
在该系统中主要对学生的基本信息 、学生成绩等进行管理,系统主要功能如下:
(1)学生信息管理
354 用来管理学生基本信息,包括浏览、查询、添加、修改和删除。 学生基本信息包括:学号、
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
姓名、性别、出生年月、籍贯、自然班级、学院。
(2)课程信息管理
一门课程有一个上课班级,也可能有几个上课班级,因此课程信息里的授课教师是不确
定的,所以把授课教师添加到了班级信息中。 课程信息管理包括:浏览、查询、添加、修改和
删除。课程基本信息包括:课程编号、课程名称、课程类型、课程学时、课程学分、所属院系。
(3)班级信息管理
对于学分制管理下的选课而言,学生选择上课教师,上课不再采用自然班上课,而是由学
生选择教师上课后组成上课班级。因此,这里的上课班级和自然班级是不同的。这里主要是
对班级基本信息进行管理,包括浏览、
查询[按班级号查询,按课程号查询]、添加、修改和删除。
班级基本信息包括:班级编号、
所属课程、授课教师、
开始日期、结束日期、班级人数。
(4)成绩信息管理
用来管理学生成绩,包括浏览、查询[按成绩自动编号、课程号、班级号查询 ]、添加、修改
和删除。成绩基本信息包括成绩自动编号 、学生学号、上课班级编号、课程成绩。
(5)成绩统计分析
统计一个上课班级的成绩分段百分比 、平均成绩、均方差。 可以分别统计每个课程班级
的成绩和整个课程的成绩。
(6)权限管理
对于不同层次的使用者应该开放不同的权限 。
管理者:维护学生基本信息,管理院系、课程、成绩各种数据。
教师:可以录入学生成绩信息,查询学生基本信息和成绩统计信息 。
学生:可以查询成绩信息和成绩统计信息 。
(7)使用要求
需要提供方便灵活的数据查询功能,友好的人机界面,满足繁杂、多样的用户需求。
总之,通过该系统的建设来提高学生的管理效率,使得学校的发展能够适应当前教育信
息化建设的总体发展趋势。
17
22 数据流
数据流图是一种面向数据流的分析方法,主要采用自顶向下逐层分解的分析思想和原
则。图 17 1(a)为顶层数据流图,由于成绩管理数据流较为复杂,因此再给出成绩管理数
(
a)系统数据流顶层图 (
b)成绩管理数据流图
图17 1 数据流图 355
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
据流图,见图 17 1(b),部分数据流向将在数据库中的关系中有所体现,限于篇幅其他数据
流程模块略。
3 系统设计
17
通过上述需求分析,出于对执行部门的单一性和系统的安全考虑,此系统初步设计成单
机、多机环境两种工作模式。 单机模式推荐使用 ACCESS 数据库;分布式多机环境下使用
SQLServer 数据库,并且所有客户端和数据库都必须在一个局域网内 。
17
31 系统的功能模块
根据需求分析的结果,系统的总体功能模块如图 17 2 所示:
图17 2 系统功能图
356
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
17
32 业务流程设计
业务流程是为实现业务的某一特定目的所采取的一系列有控制的步骤 。 如不能给某门
不存在的课程添加班级,不能删除下挂有授课班级的课程等 。
图 17 3 为本系统的业务流程图,它是建立关系数据库的又一重要依据 。
图17 3 业务流程图
17
33 数据库设计
针对学生管理信息系统的功能图、数据流图以及业务流程图,采用 ACCESS 或 SQLServer
作为后台数据库,数据库命名为 StuMIS,整个系统包括 6 张数据表,分别如下:
表17 1 系统管理员表:
Use
rTa
b
表17 2 院系信息表:
Col
leeTa
g b
表17 3 课程表:
Cou
rseTa
b
表17 4 课程班级表:
Cla
ssTa
b
表17 5 学生表:
Stud
ent
Tab
表17 6 成绩表:
Sco
reTa
b
外键也称为关系,外键约束是关系数据库的一个重要特征,建立外键是减少数据冗余的
重要手段,可以有效地保证数据库的完整性与有效性 。比如在 ScoreTab 表 ScoreStuID 字段
中,不能输入 StudentTab 表 StuID 字段中没有的值;同样如果 StudentTab 表 StuID 字段中
的某个值被引用了,则不能随意地更改或删除它 (取决于在设计数据库关系时的设置 )。 在
ACCESS 中建立外键的方法非常简单:
(1)点击进入数据库,在表视图的空白区域内点击鼠标右键
(2)点击选择[关系]
(3)单击右键选择[显示表],选择要设计的外键 [关
系]的表
(4)单击选中需要受到约束的字段,拖放到外键查
询的字段,此时弹出图 17 4
实施参照完整性有两个选项:
① 级联更新相关字段:即如果外键中的值发生了改
变,则受它约束的相关字段会相应地更新。 实际操作中
图17 4 编辑关系[设计外键]
我们一般选择此项,以简化数据库更新操作。
358 ② 级联删除相关记录:即如果外键中的某个值被删除,则受它约束的相关字段会自动地
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
删除。为了确保数据安全,防止误操作,一般不选此项。
选择完这项即完成了一个外键[关系]的制作。
在 SQLServer 中操作大同小异,这里不再叙述。 在 SQLServer 中制作完成的关系如图
17 5 所示。
图17 5 数据库关系图(
SQLS
erv
er)
关系的引用,在减小了数据库的冗余的同时,也带来了查询与更新上的麻烦,比如要查
询 ScoreTab 中的某项成绩的课程是哪个院系开的,就需要依据外键进行三次连接查询,即:
ScoreTab - > ClassTab - > CourseTab - > CollegeTab。 为了降低这种查询所带来的书写 SQL
语句的难度和提高执行效率,在 SQLServer 中一般会采用通过建立视图或存储过程的方法
来实现,但为了确保能支持 ACCESS 数据库,本项目使用了直接写 SQL 语句的方法。
4 详细设计
17
依据系统需求分析和功能模块的设计结果,采用 Visual C++60 来实现各个模块的功
能。本系统采用基于对话框的设计方法来实现,在刚开始会尽可能详细地介绍每一步的实
现,到后面对已经介绍过的内容则会简化,只给出关键代码,请读者注意。
先使用 MFC AppWizardexe创建一个新项目,命名为 ExMIS,单击确定后在创建类型中
选择 Dialog based (基于对话框)的应用程序,其他选默认即可。
17
41 主体框架模块
主程序界面是应用程序与其他功能模块的连接平台,并且包含了权限控制,根据实际使
用的需要,学生管理信息系统采用了传统的 “菜单/工具栏/状态栏 ”风格。 下面分别介绍菜
单、工具栏与状态栏的添加与制作。
1 添加菜单
(1)选择菜单中 Insert - > Resource - > Menu 项,然后单击 New 按钮,创建一个新菜单,
将其 ID 更改为 IDR_MENU。 359
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
图17 6 主窗体界面
(2)在菜单中创建如下菜单选项后 再 保 存,之 后 在 合 适 的 地 方 添 加 菜 单 分 隔 符 (见
表17 7)。
(3)在资源视图中打开 Dialog 中 IDD_EXMIS_DIALOG 对话框,在窗体上单击右键后选择
Properties 弹出属性设置对话框,在 MENU 的下拉选项中选择 IDR_MENU。
表17 7 菜单项,及其ID
菜 单 菜 单 项 菜单 ID 号
重新登录 ID_MENUITEM_RELOGIN
登录管理
退出系统 ID_MENUITEM_QUIT
学生基本信息数据维护 ID_MENUITEM_STUDENT
院系数据维护 ID_MENUITEM_COLLEGE
数据管理
课程数据维护 ID_MENUITEM_COURSE
班级数据维护 ID_MENUITEM_CLASS
成绩管理 ID_MENUITEM_SCORE
成绩管理
成绩统计 ID_MENUITEM_STAT
用户管理 系统用户维护 ID_MENUITEM_USER
记事本 ID_MENUITEM_NOTEPAD
附属工具
计算器 ID_MENUITEM_CAL
关于 帮助文档 ID_MENUITEM_HELP
添加 犆犜狅狅
2 犾犅犪狉犆狋
狉犾工具栏
在 MFC 中,提供了两个工具栏类:CToolBar 和 CToolBarCtrl。 CToolBar 具有代码编写
简单、可装载工具栏资源等优点,但它只支持 16 色图标,而 CToolBarCtrl 却可以弥补这个缺
点,但 CToolBarCtrl 需配合 CImageList,代码也略为复杂,为了综合美观和实效,本文在主
360 窗体中使用 CToolBarCtrl,而在其他模块中使用 CToolBar 实现,读者也可以先参看后文,先
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
protected:
CToolBarCtrl m_ToolBar;
CImageList m_ImageList;
(2)选择菜单中 Insert - > Resource - > Import 按钮,导入先制作好的 12 个 ICON 图标,
为减小编写代码的工作量,注意此时必须按顺序依次导入,以保证它们的 ID 值连续 [会自动
命名为 ICON1~ICON12。然后在 String Table 中添加图标下的提示文字,同样也必须依次
输入,以使它们 ID 的值连续,分别命名为 IDS_STRING102~IDS_STRING113。
(3)在 OnInitDialog()初始化函数的 return TRUE 代码前添加代码,见清单 17 1。
清单17 1 On
Ini
tDi
alg()函数(在 ExMI
o SDl
g.pp文件中)
c
button8.idCommand = ID_MENUITEM_NOTEPAD;
button9.idCommand = ID_MENUITEM_CAL;
button10.idCommand = ID_MENUITEM_HELP;
button11.idCommand = ID_MENUITEM_QUIT;
m_ToolBar.AddButtons(12,button);//装载入 12 个制作好的工作栏按钮
button0.fsStyle = TBSTYLE_SEP;
//添加分隔条,这里从后向前添加,这样不用考虑添加后对序号的影响
m_ToolBar.InsertButton(10,&button0);
m_ToolBar.InsertButton(7,&button0);
m_ToolBar.InsertButton(6,&button0);
m_ToolBar.InsertButton(4,&button0);
m_ToolBar.InsertButton(1,&button0);
m_ToolBar.AutoSize;
m_ToolBar.SetStyle(TBSTYLE_FLAT|CCS_TOP);//更改风格
添加状态栏
3
添加状态栏的方法较为简单:
(1)在 CExMISDlg 类的声明文件中添加一个状态栏的成员变量
CStatusBar m_wndStatusBar;
(2)在指示器数组中添加状态栏窗格的 ID 号,见清单 17 2。
清单17 2 i
ndi
cat
ors(在 ExMI
SDl
g.pp文件中)
c
清单17 3 On
Ini
tDi
alg()函数(在 ExMI
o SDl
g.pp文件中)
c
//状态栏,获取客户区大小
CRect rect;
GetClientRect(rect);
//定义状态栏
m_wndStatusBar.Create(this);
m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT));
//在底部显示状态栏
m_wndStatusBar.MoveWindow(0,rect.bottom- 20,rect.right,20);
m_wndStatusBar.SetPaneStyle(2,SBPS_STRETCH);
362
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
主窗体布局
4
完成上述 3 个步骤后,再为主窗体添加欢迎画面,并调整窗体大小布局,以使菜单、工具
栏和状态栏以及欢迎画面显得比较紧凑 。
(1)在资源视图中虽然其自带的画图板只支持 256 色(8 位),但依然可以导入 24 位以上
的图片。再次选择菜单 Insert - > Resource - > Import 按钮,在文件类型中选择选“所有文件
(* .* )”,然后导入事先制作好的真彩色位图,此时会弹出因位图多于 256 色而无法编辑的
英文提示,即表示导入成功。
(2)删除 IDD_EXMIS_DIALOGCExMISDlg对话框上的所有项目,添加三个 Picture 控件,
一个用来装载图片,另外两个用来做分隔线。将要做成分隔线的两个 Picture 控件属性更改
为,Type:Frame; Color: Etched。然后分别拉伸成一条线即可。将用来装载图片的 Picture
控件的 Type 属性更改为 Bitmap 然后在 Image 选项中选择刚才导入位图的 ID 即可。
(3)最后调整窗口的大小和三个 Picture 控件的位置,上下留出菜单、工具栏、状态栏的
显示空间,可反复编译调整到最佳位置,最后得到的效果如图 17 7 所示。
图17 7 调整后的主窗体界面
17
42 登录权限验证模块
登录窗体设计
1
登录窗体如图 17 8 所示,并在主窗体中调用它,其制作过程如下:
(1)界面制作
① 单击菜单中的 Insert - > New Form 选项,在新建窗
体对 话 框 中, 输 入 类 名 称 CLoginDlg, 基 类 选 择 默 认 的
CDialog,此时 会 自 动 生 成 对 话 框 ID 为 IDD _ LOGINDLG _
DIALOG,并导入登录位图 IDB_BITMAP_LOGIN。
② 单击资源视图中新生成的 IDD_LOGINDLG_DIALOG
图17 8 登录窗体
对话框窗体,在窗体上单击右键,在 Caption 属性中输入
“学生管理信息系统_登录”更改标题,之后添加控件,见表 17 8。
363
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
表17 8 登录框控件列表
控件类型 控件 ID 控件变量 控 件 属 性
Picture IDC_STATIC Type: Bitmap, Image: IDB_BITMAP_LOGIN
Static Text IDC_STATIC Caption:用户名:
Static Text IDC_STATIC Caption:密 码:
Static Text IDC_STATIC Caption:数据库:
Edit Box IDC_EDIT_USERNAME m_strUsername
Edit Box IDC_EDIT_PASSWORD m_strPassword 勾选 Styles 中的 Password 属性
Data: ACCESS, SQLServer
Combo Box IDC_COMBO_TYPE m_strDataType
分成两行,Ctrl+ Enter 换行
Button IDC_BTN_LOGIN Caption:登录
Button IDC_BTN_CANCEL Caption:取消
注:其中控件变量 m_str 代表是 CString 类型的。下文中还会出现的一些常用表示:m_ctrl 代表是 Control 类型的,
m_b代表是布尔型的,m_n 代表是 int 型的,m_f 代表是 float 型的。
public:
int m_nPower; //指示登录用户的权限
(3)在 ExMisDlg.cpp 源文件中添加# include" LoginDlg.h"
(4)使用 MFC ClassWizzard(创建类向导),为 CExMisDlg 的 ID_MENUITEM_RELOGIN(重新
登录菜单选项),添加消息响应函数 OnMenuitemRelogin(),并添加代码,见清单 17 4。
清单17 4 OnMe
nui
temRe
loi
gn()菜单消息函数(在 ExMI
SDl
g.pp文件中)
c
v
oidCExMI SDlg OnMe nuit
emRe l
ogn()
i
{ //打开登录窗口,以下为添加
m_nPower = 0; //重新或第一次登录时,初始化权限为零
CLoginDlg dlg;
dlg.DoModal();
m_nPower = dlg.m_nPower; //获取登录用户的权限
}
(5)在主窗体显示之前弹出登录窗口,如图 17 8 所示。
使用 犛犙犔 语言
2
接下来就要直接操作数据库,SQL 是英文 Structured Query Language 的缩写,意思为结
构化查询语言。SQL 语言的主要功能就是同各种数据库建立联系,进行沟通。 在实际应用
中,用的最多的无非是增、删、改、查四种操作,其中以 SELECT 语句最为灵活,下面简要举例
介绍一下其用法。
(1)INSERT 命令:在数据库中创建新行。用法如下:
以必须加上方框号,power 为数值型,所以其值加单引号:
UPDATE tablename
SET fieldname1 = fieldvalue1, fieldname2 = fieldvalue2
WHERE condition
如将刚才添加的 zcl 用户的密码更改为“123”:
SELECT fieldname1,fieldname2
FROM tablename1,tablename2
WHERE search_conditions
ORDER BY order_conditions
例 1:查询密码为“1”的用户,并按用户名排序
例 2:查询课程编号为“10001”的所有成绩记录
例 3:查询汇总用户表的权限均值和权值总和,并将总和重命名为 total
例 4:连接查询所有班级,并显示这些班级所属的课程名称 。
使用 犃犇犗 连接数据库
3
ADO 是基于 OLE DB 接口而设计的,是一个易于使用,速度快,内存支出小的应用程序接
口。使用 ADO 通过 SQL 语言连接 ACCESS 数据库实现用户登录。 365
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
if(! AfxOleInit())
{ //初始化 OLE 环境
AfxMessageBox("OLE 初始化失败");
}
v
oidCLo ginD l
g OnB
tnLog n()
i
{ //检查用户名是否输入
UpdateData(true);
if(m_strUsername.IsEmpty()||m_strPassword.IsEmpty()||m_strDataType.IsEmpty())
{ AfxMessageBox("请将资料填写完整再登录!");
return;
_variant_t Holder,SQL; //添加的
//建立连接
m_pConn.CreateInstance(__uuidof(Connection));
m_pConn ->Open( "Provider = Microsoft.Jet.OLEDB.40;Data Source = StuMIS.mdb" ,"" ,"" ,- 1);
//建立数据集
m_pRst.CreateInstance(__uuidof(Recordset));
//声明对象
m_strSQL = " SELECT password,power FROM UserTab WHERE username= "+
m_strUsername+ " " ;//以上两行为一行,因为 username 是数据库中保留字,所以加上方框号
_variant_t sql = m_strSQL;
m_pRst ->Open( sql,m_pConn.GetInterfacePtr( ),adOpenDynamic,adLockOptimistic,adCmdText) ;
bool r = false;//判断是否登录成功
if(! m_pRst ->adoEOF)
{ _variant_t Holder = m_pRst ->GetCollect((long)0);
CString password = (char* )_bstr_t(Holder);
if(password = = m_strPassword)
{ //获得用户权限
Holder = m_pRst ->GetCollect((long)1);
m_nPower = (int)V_I2(&Holder);
366
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
r = true;
m_pRst ->Close();
}
if(! r) AfxMessageBox("错误的用户名或密码!");
else CDialog::OnOK();//相当于单击了对话框的 OK 按钮,返回一个模态,结束调用。
}
至此,完成了登录模块的设计与制作。
17
43 重新封装 ADO
由上文中的示例可以看出,ADO 的使用还是比较复杂的,而且极易发生异常,因为 VC 中
使用 ADO 的本质还是调用外部的 DLL(动态链接库 )来实现的,所以时序很重要,如果打乱了
装载 DLL、初始化 OLE 环境、建立连接接口的顺序或给的时间间隔不够,就会引发异常,所以
通常的解决方法是加上 trycatch异常处理块。 而且 ADO 的返回类型是_variant_t(多
种类型的集合),并不能直接使用,因此对 ADO 的重新封装就显得十分必要。
1 创建 犕狔犚犲犮狅狉犱犛犲狋类
(1)在工程中添加一个新类 MyRecordSet,并在头文件中添加成员函数和方法,见清单
17 6。
清单17 6 MyRe
cor
dSe
t.h文件
c
las
sMyRecordSe
t
public:
CString GetFieldName(int nCol);
bool ADOEOF();
void ADOConnectionClose();
//数据集的列数,字段数
int nFieldCols;
//数据集的行数
int nFieldRows;
//返回数据集中的某个列值
CString GetFieldString(int nCol); //以字符串形式返回值
CString GetFieldString(CString strFieldName); //以字符串形式返回值
int GetFieldNumber(int nCol); //以整型返回值,参数为列序号
int GetFieldNumber(CString strFieldName); //1 次重载,参数为字段名
float GetFieldFloat(int nCol); //以浮点返回值,参数为列序号
float GetFieldFloat(CString strField); //以浮点返回值,参数为字段名
CString VariantToCString(VARIANT var); //类型转换
//类型转换
CString TimeToString(CTime time);
CTime StringToTime(CString s);
//数据集指针操作
HRESULT MoveNext();
HRESULT MovePrev();
HRESULT MoveLast();
HRESULT MoveFirst();
367
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
MyRecordSet(); //构造函数
//数据集操作
bool ADOOpen(CString strDataType);
bool ADOOpen();
bool ADOExcute(); //执行 SQL 语句:不带参数
bool ADOExcute(CString strSQL); //执行 SQL 语句:带参数
//执行 SQL 语句,返回受影响的行数,主要针对增删改
int ADOExcuteNoQuery(CString strSQL);
void ADOClose(); //手动关闭数据集
_ConnectionPtr m_pConn; //定义一个连接对象
_RecordsetPtr m_pRst; //定义一个数据集
CString m_strSQL; //查询语句
CString m_strDBType; //数据库类型
bool isConn; //是否已经打开连接
bool isOpen; //是否已经打开数据集
virtual ~MyRecordSet(); //析构造函数
};
(2)MyRecordSet 类的源文件,清单 17 7。
清单17 7 MyRe
cor
dSe
t.pp文件
c
MyReco r
d S etMyRe cor
dSet()
{ isConn = false;
isOpen = false;
nFieldRows = 0;
nFieldCols = 0;
try
m_pConn.CreateInstance(__uuidof(Connection));
m_pRst.CreateInstance(__uuidof(Recordset));
}
catch(...)
{ }
}
MyReco r
d S et~MyRe cor
dSet()//析构函数
{ if(isOpen)m_pRst ->Close(); //如果数据集打开则关闭
if(isConn)m_pConn ->Close(); //如果连接打开则关闭
}
//建立数据库连接
boo
lMyRe cord S
et ADOOpen(CS t
rings
trDa
taType)
{ m_strDBType = strDataType;
return ADOOpen();
}
boo
lMyRe cord S
et ADOOpen()
{ ADOConnectionClose();
isConn = false;
try
//选择不同的数据库连接,ACCESS 或 SQLServer
if(m_strDBType = =" ACCESS" )
{m_pConn ->Open("Provider = Microsoft.Jet.OLEDB.40;Data Source = StuMIS.mdb" ,"
" ,"" ,-1);
368 }
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
else
m_pConn ->Open( "Provider = SQLOLEDB.1;Password = ;Persist Security Info = True;User
;//此处为一行
ID = sa;Initial Catalog = StuMIS;Data Source = MICROSOF 87DA1D" ,"" ,"" ,- 1)
}
//使游标在客户端,才能取出受影响的行列数
m_pRst ->CursorLocation = adUseClient;
isConn = true; //连接成功
}
catch(...)
{AfxMessageBox("与数据建立连接失败!");}
return isConn;
boolMyRe cordSetADOEx cute(CStringstrSQL) //执行 SQL 语句,返回数据集,主要针对查询
{ m_strSQL = strSQL;
return ADOExcute();
}
//执行 SQL 语句,返回受影响的行数,主要针对增删改
in
tMyRe c o
rdSe tADOEx cuteNoQu ery(CStringstrSQL)
{ _variant_t var;
_bstr_t sql = strSQL;
try
m_pConn ->Execute(sql,&var,adCmdText);
return (int)V_I2(&var);
}
catch(...)
{return 0;
boolMyRe cordSetADOEx cute()//执行 SQL 语句,必须先对 m_ strSQL 赋值
{ //声明对象
_variant_t sql = m_strSQL;
nFieldRows = 0;
nFieldCols = 0;
ADOClose();
isOpen = false;
try
m_pRst ->Open( sql,m_pConn.GetInterfacePtr(
),adOpenDynamic,adLockOptimistic,adCmdText)
;
if(m_pRst ->State)
{ nFieldRows = m_pRst ->RecordCount;
nFieldCols = m_pRst ->GetFields()->GetCount();
isOpen = true;
catch(...)
{ }
return isOpen;
voi
dMyRe co
r dSetADOC lose() //关闭数据集
{ if(isOpen)
{ m_pRst ->Close();
isOpen = false;
369
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
v
oidMyRe c
ordSetADOCo nne
ctio
nCl
ose() //关闭数据连接
{ //只有先关闭数据集才能再关闭连接
ADOClose();
if(isConn)
{ m_pConn ->Close();
isConn = false;
bo
olMyRe c
o rdSe
tADOEOF() //返回是否数据集结尾
{ if(isConn&&isOpen)
return m_pRst ->adoEOF;
return true;
HRESULTMyRe c ordSetMoveFir
st()
{ return m_pRst ->MoveFirst(); }
HRESULTMyRe c ordSetMoveLast()
{ return m_pRst ->MoveLast(); }
HRESULTMyRe c ordSetMovePrev()
{ return m_pRst ->MovePrevious(); }
HRESULTMyRe c ordSetMoveNext()
{ return m_pRst ->MoveNext();}
CSt
ringMyRe cord Se
t GetFi
eldName(intnCo l) //返回字段名称,参数为列序号
{ CString sValue;
_variant_t vValue;
vValue = m_pRst ->GetFields()->GetItem((long)nCol)->Name;
if(vValue.vt = = VT_EMPTY||vValue.vt = = VT_NULL)
sValue ="" ;
else
sValue = (char* )(_bstr_t)vValue;
sValue.TrimLeft();
sValue.TrimRight();
return sValue;
//以字符串类型返回数据集中的值,参数是字段名称
CSt
ringMyRe cordSetGetFie
ldStri
ng( CSt
ringstrF
iedName)
l
{ _variant_t strField = strFieldName;
_variant_t var;
try
var = m_pRst ->GetCollect(strField);
}
catch(...)
{AfxMessageBox("读取数据失败");
return"" ;
return VariantToCString(var);
}
//以字符串类型返回数据集中的值,传入的是列的序号
CSt
ringMyRe cordSetGetFie
ldStr
ing(intnCol)
{ _variant_t var;
try
var = m_pRst ->GetCollect((long)nCol);
370
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
}
catch(...)
{ AfxMessageBox("读取数据失败");
return"" ;
return VariantToCString(var);
}
i
ntMyRe cordSet Ge
tFi
eldNumber(i
ntnCo l)//以整型类型返回数据集中的值,传入的是字段序号
{ _variant_t var;
try
var = m_pRst ->GetCollect((long)nCol);
}
catch(...)
{ AfxMessageBox("读取数据失败");
return 0;
return (int)V_I2(&var);
}
//以整型类型返回数据集中的值,传入的是字段名
in
tMyRe cordSetGet
Fie
ldNumb er(CStringst
rF i
eldName)
{ _variant_t strField = strFieldName;
_variant_t var;
try
var = m_pRst ->GetCollect(strField);
}
catch(...)
{ AfxMessageBox("读取数据失败");
return 0;
return (int)V_I2(&var);
}
f
loa
tMyRe cordSetGet
FieldFl
oat(intnCol)//以浮点类型返回数据集中的值,传入的是字段序号
{ _variant_t var;
try
var = m_pRst ->GetCollect((long)nCol);
}
catch(...)
{AfxMessageBox("读取数据失败");
return 0;
return (float)V_R4(&var);
}
f
loa
tMyRe cord Se
tGe tF
ieldFl
oat(CStri
ngs t
rFie
l //以浮点类型返回数据集中的值,传入的是字段名
d)
{ _variant_t vField = strField;
_variant_t var;
try
var = m_pRst ->GetCollect(vField);}
catch(...)
{AfxMessageBox("读取数据失败");
return 0;
371
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
return (float)V_R4(&var);
}
//类型转换
CSt
ringMyRe cordSe
t Va r
iantToCString(VARIANTv a //将数据库中的返回值变为字符串输出
r)
{ CString strValue;
_variant_t var_t;
_bstr_t bst_t;
time_t cur_time;
CTime time_value;
COleCurrency var_currency;
switch(var.vt)
{ case VT_EMPTY:strValue = _T("");break;
case VT_UI1:strValue.Format ("%d" var.bVal);break;
case VT_I2:strValue.Format ("%d" var.iVal);break;
case VT_I4:strValue.Format ("%d" var.lVal);break;
case VT_R4:strValue.Format ("%f" var.fltVal);break;
case VT_R8:strValue.Format ("%f" var.dblVal);break;
case VT_CY:
var_currency = var;
strValue = var_currency.Format(0);
break;
case VT_BSTR:
var_t = var;
bst_t = var_t;
strValue.Format ("%s" (const char*)bst_t);
break;
case VT_NULL:strValue = _T("");break;
case VT_DATE:
cur_time = var.date;
time_value = cur_time;
strValue = time_value.Format("%Y-%m-%d" );
break;
case VT_BOOL:strValue.Format ("%d" var.boolVal);break;
default:strValue = _T("");break;
return strValue;
CSt
ringMyRe c
ordSetTimeTo
Str
ing(
CTimet //时间到字符串
ime)
{
CString str;
str = time.Format("%Y-%m-%d" );
return str;
CTimeMyRe cordSetStri
n ime(
gToT CSt
rin
gsr)
t //字符串到时间
{ if(str.IsEmpty())
str = " 1990-1-1" ;
int ymd;
y = atoi(str);
int ij;
i = str.Find(- ,0);
j = str.Find(- ,i+1);
372
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
CString temp;
while(i<j-1)
temp+= str.GetAt(++i);
m = atoi(temp);
temp.Empty();
i = str.GetLength();
while(j<i-1)
temp+= str.GetAt(++j);
d = atoi(temp);
CTime tmp(ymd000);
return tmp;
由代码可看出此封装可实现以下内容:
① 用 ACCESS 和 SQLServer 两种数据库连接
② 使用时只需声明一个实例,指定连接类型就能初始化数据连接 ,执行 SQL 语句时无需
打开或关闭数据库。
③ 有多种返回值可供选择,封装了一些常用的函数和操作。
使用此类可极大地简化对数据库的操作,加快开发速度,在使用此类前,同样需要引用
ADO 的 DLL 和初始化 OLE 环境。
使用 犕狔犚犲犮狅狉犱犛犲狋类
2
下面说明如何使用 MyRecordSet 对数据库进行操作,以登录验证为例,过程如下。
(1)在 CLoginDlg 类的头文件中添加 MyRecordSet 对象的实例:
MyRecordSet m_pRS; //定义数据集
(2)在 CLoginDlg 类 的 源 文 件 中 改 写 登 录 按 钮 IDC _ BTN _ LOGIN 的 消 息 响 应 函 数
OnBtnLogin(),见清单 17 8。
清单17 8 OnB
tnLo
gin()消息函数(在 Lo
ginD
lg.
cpp文件中)
v
oidCLoginDlgOnB tnLo i
gn()
{ //检查用户名是否输入
UpdateData(true);
if(m_strUsername.IsEmpty()||m_strPassword.IsEmpty()||m_strDataType.IsEmpty())
{ AfxMessageBox("请将资料填写完整再登录!");
return;
m_pRS.ADOOpen(m_strDataType); //打开所选数据连接
m_strSQL =" SELECT passwordpower FROM UserTab WHERE username= "+m_strUsername+ " " ;
m_pRS.ADOExcute(m_strSQL);
if(m_pRS.nFieldRows = = 1) //判断是否找到 1 个用户
{ if(m_pRS.GetFieldString(0)= = m_strPassword) //判断密码是否正确
{ m_nPower = m_pRS.GetFieldNumber(1); //取得权限
CDialog::OnOK();
}
else
AfxMessageBox("错误的密码!"); }
} 373
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
else
AfxMessageBox("错误的用户名!");}
}
这样就完成了登录查询和取得权限的操作,为了将登录时选的数据库类型通过主窗体
传给其他模块,有必要在主窗体中添加如下操作:
(3)在 CExMISDlg 类的头文件中添加 MyRecordSet 对象的实例:
dlg.DoModal();//在登录窗口取得返回值后添加
m_pRS.m_strDBType = dlg.m_pRS.m_strDBType;//将登录时选择的数据库类型传到主
//窗体
17
44 院系数据管理模块
因为院系数据管理模块涉及控件较少 ,操作的数据库字段也少,所以先行介绍。 院系管
理模块其实就是对 CollegeTab 表的管理,这个
表不受外键约束,处在控制的最高层,从关系图
可以看出,除用户表外,其他表都直接或间接受
他的约束。
界面设计
1
图 17 9 即为院系管理界面,其实现步骤
如下:
(1)新建一个名为 CCollegeDlg 的对话框
类 (继 承 自 CDialog 类 ),ID 自 动 命 名 为 IDD_
COLLEGEDLG_ DIALOG并 更 改 标 题 (Caption )为
图17 9 院系管理界面
“院系管理”。
(2)按图 17 9 为院系管理界面添加控件如下表:
表17 9 院系管理窗口控件列表
控 件 类 型 控件 ID 控件变量 控 件 属 性
List Control IDC_LIST m_ctrlList
Static Text IDC_STATIC Caption:院系名称:
Static Text IDC_STATIC Caption:院系备注:
Edit Box IDC_EDIT_ID m_strID
Edit Box IDC_EDIT_MEMO m_strMemo 勾选 Styles 中的 Multiline 属性
Button IDC_BUTTON_CLEAR Caption:清空输入
Button IDC_BUTTON_ADD Caption:保存新增
Button IDC_BUTTON_EDIT Caption:保存修改
Button IDC_BUTTON_DEL Caption:删除记录
374
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
院系浏览与显示
2
(1)在 CCollegeDlg 头文件中添加 MyRecordSet 的实例和成员函数
MyRecordSet m_pRS;//数据库类型会在主窗体调用时被主窗体指定
void ShowListItems();//在列表中显示院系
(2)为 CCollegeDlg 类添加(重载)WM_INITDIALOG 的消息映射 OnInitDialog(),编写成
员函数实现代码,以便显示院系列表,见清单 17 9。
清单17 9 On
Ini
tDi
alg()和 Sh
o owL
ist
Items()函数(在 Co
lle
geD
lg.
cpp文件中)
BOOLCCo l
legeDlgOnI n
itDia
log()
{ CDialog::OnInitDialog();
//添加的代码
m_ctrlList.InsertColumn(0,"院系名");
m_ctrlList.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);//设备风格
m_ctrlList.SetColumnWidth(0,160);
m_pRS.ADOOpen();//打开 ADO 的连接
ShowListItems();//在列表中显示院系
return TRUE;
v
oidCCol
legeD lg
ShowList
Items()
{ m_ctrlList.DeleteAllItems();//清空列表项
m_pRS.ADOExcute("SELECT CollegeID FROM CollegeTab" );
int i = 0;
while(!m_pRS.ADOEOF())
{ m_ctrlList.InsertItem(i++m_pRS.GetFieldString(0));
m_pRS.MoveNext();
}
m_ctrlList.SetRedraw(TRUE);
}
CCo
llegeDlg OnC l
ickLit()
s
{ CString strSQL;
int i = m_ctrlList.GetSelectionMark();
CString str = m_ctrlList.GetItemText(i0);
strSQL.Format("SELECT * FROM CollegeTab WHERE CollegeID = %s " str);
m_pRS.ADOExcute(strSQL);
if(m_pRS.nFieldRows!= 1)
{ AfxMessageBox("数据库查找错误!");
return;
m_strID = m_pRS.GetFieldString("CollegeID" );
m_strMemo = m_pRS.GetFieldString("CollegeMemo" );
UpdateData(false);
}
375
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
院系数据操作
3
(1)清空输入:为 IDC_BUTTON_CLEAR 控件添加 OnButtonClear()事件,见清单 17 11
(2)保存添加:为 IDC_BUTTON_ADD 控件添加 OnButtonAdd()事件,见清单 17 11
(3)保存修改:为 IDC_BUTTON_EDIT 控件添加 OnButtonEdit()事件,见清单 17 11
(4)删除记录:为 IDC_BUTTON_DEL 控件添加 OnButtonDel()事件,见清单 17 11
清单17 11 院系数据操作消息函数(在 Co
lle
geD
lg.
cpp文件中)
CCo l
leg eD lg OnBu ttonClear()//清空输入
{ m_strID ="" ;
m_strMemo ="" ;
UpdateData(false);
}
CCo l
leg eD lg OnBu ttonAdd() //保存添加
{ UpdateData(true);
CString sql;
sql.Format( "INSERT INTO CollegeTab(
CollegeID
CollegeMemo)Values(%s %s )
",m_strID
m_strMemo)
;
if(m_pRS.ADOExcuteNoQuery(sql)= = 1)
{ AfxMessageBox("增加记录成功!");
ShowListItems();//增加完记录后,马上刷新显示;
}
else
AfxMessageBox("增加记录失败,可能是重复的院系名,可选择保存修改!");
}
CCo l
leg eD lg OnBu ttonEdit()//保存修改
{ UpdateData(true);
CString sql;
sql.Format("UPDATE CollegeTab SET CollegeID = %s CollegeMemo = %s WHERE
CollegeID = %s " m_strIDm_strMemom_strID);//此处为一行
if(m_pRS.ADOExcuteNoQuery(sql)= = 1)
{ AfxMessageBox("编辑记录成功!");
ShowListItems(); //编辑完记录后,马上刷新显示;
}
else
AfxMessageBox("编辑记录失败!");
}
CCo l
leg eD lg OnBu ttonDel()//删除记录
{ if(m_ctrlList.GetSelectionMark() <0)
{ AfxMessageBox("请先选中要删除的项!");
return;
//此操作不能恢复,一般都需要确认一下
if(AfxMessageBox("您确认要删除吗?",MB_OKCANCEL)= = IDOK)
{ UpdateData(true);
CString sql;
sql.Format("DELETE * FROM CollegeTab WHERE CollegeID = %s " m_strID);
if(m_pRS.ADOExcuteNoQuery(sql)= = 1)
{ AfxMessageBox("删除记录成功!");
ShowListItems();//刷新显示;
}
else
376
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
AfxMessageBox("删除记录失败,当其他表存在有依赖此记录的项时则不能删除!");
}
}
}
调用院系管理窗口
4
下面说明在主窗体中调用院系窗体,设定只有权限值为 “3”的管理员才能调用,因为权
限验证代码会被多次使用,所以设计了一个权限判断函数来实现:
(1)在 CExMISDlg 头文件中添加函数声明。
# include "CollegeDlg.h"
boolCExMI SDlgPowe rVal
ida
te(i
ntnPowe //权限管理
r)
{ if(m_nPower<nPower) //m_nPower 已经在登录时被赋用户权限值
{ AfxMessageBox("对不起,您的权限不够,无法执行此项操作!");
return false;
return true;
v
oidCExMI SDlgOnMe nui
temCo l
le ge()//院系管理,三级以上权限可进
{ if(PowerValidate(3))
{ CCollegeDlg dlg;
//将使用的数据库类型传给学院管理子窗体
dlg.m_pRS.m_strDBType = m_pRS.m_strDBType;
dlg.DoModal();
}
}
17
45 学生数据管理模块
由前面数据库关系图可以看出 ,因为学生表 [StudentTab只 受 院 系 表 [CollegeTab
的约束 ,所以制作完院系模块 ,并 录 入 一 些 院 系 信 息 后 ,就 可 以 开 始 制 作 学 生 数 据 管 理
模块 。
界面设计
1
设计好的学生数据管理界面如图 17 10。
此窗体的设计略有些复杂,同样使用了包含菜单、工具栏和状态栏的窗体,因为数据管
理的功能项都大同小异,因此我们设计了两个通用的菜单栏和工具栏 ,只是在不同的窗口中
响应不同的消息响应而已。窗体制作过程如下。 377
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
图17 10 学生数据管理界面
(1)添加窗体和控件
新建一对话框窗体,ID 号为 IDD_DATADLG_DIALOG,添加控件如下,并按图 17 10 布局以
便给菜单、工具、状态栏显示空间。
表17 10 学生数据管理窗口控件列表
控 件 类 型 控件 ID 控件变量 控 件 属 性
Group Box IDC_STATIC Caption:详细信息:
Static Text IDC_STATIC Caption:姓名:
Static Text IDC_STATIC Caption:籍贯:
Static Text IDC_STATIC Caption:性别:
Static Text IDC_STATIC Caption:院系:
Static Text IDC_STATIC Caption:出生年月:
Static Text IDC_STATIC Caption:班级:
Static Text IDC_STATIC Caption:学号:
Edit Box IDC_EDIT_NAME m_strName
Edit Box IDC_EDIT_NATIVEPLACE m_strNativePlace
Edit Box IDC_EDIT_CLASS m_strClass
Edit Box IDC_EDIT_ID m_strID
Combo Box IDC_COMBO_SEX m_strSex Data:男、女;分两行
Combo Box IDC_COMBO_COLLEGE m_strCollege 不赋初值,由程序填充
Data Time Picker IDC_DATE_BIRTHDAY m_timeBirthday
Button ID_ITEM_SEARCH Caption:查找
Button ID_ITEM_DELETE Caption:删除记录
Button ID_ITEM_SAVE Caption:保存添加
Button ID_ITEM_EDIT Caption:保存编辑
勾选 Styles 中:Has buttons;
Tree Control IDC_TREE m_ctrlTree
Has lines; Lines at root;
List Control IDC_LIST m_ctrlList
378
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
(2)建类并定义控件变量
为新建的对话框建类,类名为 CDataDlg基类为 CDialog;添加的控件变量见表 17 10。
添加菜单栏和状态栏
2
因为菜单里部分菜单项和界面上的四个按钮的功能相同 ,功能相同的菜单项和按钮以
相同的 ID 命名,这样一来,触发它们就会响应同一个消息函数 ,从而减小代码编写量。
(1)添加菜单
在 Insert Resource 中添加一个新的 Menu设置菜单 ID 号为 IDR_MENU_DATA并添加表
17 11 的菜单项。
表17 11 通用菜单项列表
(2)关联菜单
在学生数据维护窗体 IDD_DATADLG _DIALOG 上点击右键,
更选 MENU 为 IDR_MENU_DATA 即可。
(3)添加状态栏 状态栏的添加见 17413
添加 犆犜狅狅
3 犾犅犪狉工具栏
下面将使用比较简单的 CToolBar 来实现在对话框窗体上显示工具栏 。
(1)添加工具栏资源
在 Insert Resource 中添加一个新的 Toolbar 并将工具栏的 ID 重命名为:IDR_TOOLBAR_
并添加如图 17 11 所示的图标,其 ID 从左至右依次为:ID_ITEM_ADD、ID_ITEM_SAVE、ID
DATA
_ITEM_SEARCH、ID_ITEM_EDIT、ID_ITEM_DELETE、ID_ITEM_REFRESH、ID_ITEM_HELP、ID_ITEM_
QUIT。
图17 11 学生数据管理窗口工具栏
(2)装载工具栏资源
工具栏的显示并不能像菜单那样直接在窗口上通过可视化的操作实现 ,通过下面的步
骤可实现在界面的显示。
① CDataDlg 的头文件中添加变量声明
CToolBar m_wndToolBar;
② CDataDlg 类添加 OnInitDialog 初始化消息函数,见清单 17 13
清单17 13 On
Ini
tDi
alg()函数(在 Da
o taD
lg.
cpp文件中)
v
oidCDataD
lgOn I
nitD
ial
o //初始化对话框函数
g()
{ ...
CRect rect;
379
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
GetClientRect(rect);
m_wndToolBar.Create(this);
m_wndToolBar.LoadToolBar(IDR_MENU_DATA); //装载工具栏资源
m_wndToolBar.MoveWindow(0,0,rect.right48);//调整工具栏位置
}
添加数据到树形及列表控件
4
树形控件(Tree Control)和列表控件(List Control)是程序中常用的,也是比较难使用
的两个控件,下面将使用树形控件来制作一个索引目录,并使用列表控件来实现对数据的
浏览。
(1)首先在 CDataDlg 的头文件中添加一些成员数据和函数的声明 ,见清单 17 14。
清单17 14 CDa
taD
lg类的成员数据和函数声明(在 Da
taD
l h文件中)
g.
Cl
a s
sCDa taDlg
CImageList m_TreeBootImage;//树形控件图标
CString m_strSQL; //保存上一次操作的 SQL 语句,以便刷新或排序用
void AddTreeNodes(); //添加数据到树形控件
void AddListItems(); //添加数据到列表控件
HTREEITEM hRootItem; //根节点
//如果该值[strValue在指定节点[hParent的子节点中不存在,则添加一个为此值的节点;
HTREEITEM AddDistinctNode(HTREEITEM hParentCString strValue);
};
BOOLCDa t
aDlgOnInitDi
alog()//初始化对话框函数
{ ......
m_ctrlTree.SetImageList(&m_TreeBootImageTVSIL_NORMAL); //初始化树形控件,设置图标
//设置列表控件的样式
m_ctrlList.ModifyStyle(0,LVS_REPORT|LVS_SHOWSELALWAYS|LVS_SINGLESEL);
m_ctrlList.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES|
LVS_EX_HEADERDRAGDROP);
380 m_pRS.ADOOpen();//打开数据连接
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
CDataDlg::OnItemRefresh();//调用刷新记录事件全部重新填充
return TRUE;
v
oidCDataDlgOn ItemRe f
resh()//刷新所有记录,主要是让树控件刷新
{ m_strSQL =" SELECT * FROM StudentTab" ; //暂存 SQL 语句,供排序时使用
m_pRS.ADOExcute(m_strSQL);
AddTreeNodes();//添加院系、班级到树控件中
AddListItems();//添加学生到列表中
//往院系下拉框中填充院系表已经有的院系,以便用户选择。
MyRecordSet rs;//为院系控件添加院系列表,此处建立了一个临时的 RecordSet 实例
rs.ADOOpen(m_pRS.m_strDBType);//打开数据连接
rs.ADOExcute("SELECT CollegeID FROM CollegeTab" );//提取数据集
while(!rs.ADOEOF())
{ m_ctrlCollege.AddString(rs.GetFieldString(0));
rs.MoveNext();
}
rs.ADOConnectionClose();//释放资源
}
(4)向控件中添加数据,见清单 17 17。
AddTreeNodes()为 向 树 形 控 件 中 添 加 数 据,在 填 充 树 形 控 件 时 编 写 了 一 个
AddDistinctNode()函数。这个函数十分巧妙,只遍历了一遍,即完成了分类的功能,减小了
很多代码编写量,读者可仔细体会。AddListItems()为向列表控件中添加数据。
清单17 17 向控件中填充数据的函数(在 Da
taD
lg.
cpp文件中)
vo
idCDataDlgAddTr eeNo des()
{ m_ctrlTree.DeleteAllItems();//删除所有节点控件中的项目
//添加根节点即学生表的信息
hRootItem = m_ctrlTree.InsertItem("学生基本信息",0,2,TVI_ROOTTVI_LAST);
m_pRS.MoveFirst(); //指向第一条
while(!m_pRS.ADOEOF())
{ //如此院系不在根节点中,则添加院系到根节点中
HTREEITEM hCollege = AddDistinctNode(hRootItem
m_pRS.GetFieldString(
"CollegeID" )
);
//如此院的班级不在院系节点的子节点中,则添加
HTREEITEM hClass = AddDistinctNode(hCollegem_pRS.GetFieldString("StuClass" ));
m_pRS.MoveNext();
}
}
//如果该值[strValue在指定节点的子节点中不存在,则将此值添加到其子节点中;
HTREEITEM CDa taDlgAddD is
tinctNode( HTREEI TEMhPa rentCS t
rings t
rValue)
{ HTREEITEM hItem = m_ctrlTree.GetChildItem(hParent);
while(hItem)
{ CString strItemText = m_ctrlTree.GetItemText(hItem);
if(strItemText = = strValue)
return hItem; //已经存在该值的子节点,则返回
hItem = m_ctrlTree.GetNextSiblingItem(hItem);
}
//遍历了所有子节点,没有找到该值,则添加并返回
return m_ctrlTree.InsertItem(strValue12hParentTVI_LAST);
} 381
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
v
oidCDataDlg AddL is
tIems()
t
{ //清列表框的头,列,行
int nCount = 0;
CHeaderCtrl *pHeaderCtrl = m_ctrlList.GetHeaderCtrl();
if(pHeaderCtrl!= NULL)
nCount = pHeaderCtrl ->GetItemCount();
for(int i = 0;i<nCount;i++)
m_ctrlList.DeleteColumn(0);
m_ctrlList.DeleteAllItems();
//取出字段名显示到列表头中
for(i = 0;i< m_pRS.nFieldCols;i++)
m_ctrlList.InsertColumn(im_pRS.GetFieldName(i),LVCFMT_LEFT110);
//取出字段值放到列表行中
int nItem = 0;
while(!m_pRS.ADOEOF())
{ m_ctrlList.InsertItem(nItemm_pRS.GetFieldString(0));
for(int i = 1;i<m_pRS.nFieldCols;i++)
m_ctrlList.SetItemText(nItemim_pRS.GetFieldString(i));
m_pRS.MoveNext();
}
}
添加事件到树形及列表控件
5
下面将添加一些事件到树形及列表控件中 ,如单击树形控件的某个节点,会在列表控件
中显示对应的数据;单击列表控件的某个行,会有详细信息框中显示;单击列表控件的某列
会按列 值 重 新 排 序 等。 在 CDataDlg 中 添 加 IDC _ TREE 的 TVN _ SELCHANGED 消 息 映 射
OnSelchangedTree (); 添 加 IDC _ LIST 的 NM _ CLICK 和 LVN _ COLUMNCLICK 的 消 息 映 射
OnClickList()和 OnColumnclickList();见清单 17 18。
清单17 18 向控件中填充数据的函数(在 Da
taD
lg.
cpp文件中)
//单击树形控件的某个节点后
v
oidCDa taDl
g OnS e
lch
a n
gedTr e(
e NMHDR pNMHDRLRESULT pRe s
ult)
{ NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW* )pNMHDR;
* pResult = 0;
HTREEITEM hSelItem = m_ctrlTree.GetSelectedItem(); //取得当前选中的节点
HTREEITEM hParentItem = m_ctrlTree.GetParentItem(hSelItem); //取得选中节点的父节点
//通过父节点判断选中的的是根节点,还是学院二级子节点,按不同情况写 SQL 语句的条件
CString strSqlstrstrCondition; //SQL 语句
strSql = " SELECT *FROM StudentTab" ;
if(hParentItem!= NULL)
{ HTREEITEM hOldParent = m_ctrlTree.GetParentItem(hParentItem);
if(hOldParent!= NULL)
{ //选中的是班级节点,添加院系条件和班级条件
strCondition.Format(" Where CollegeID = %s AND StuClass = %s "
m_ctrlTree.GetItemText(hParentItem),m_ctrlTree.GetItemText(hSelItem));
}
else
//选中的是院系节点,添加院系条件
382
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
strCondition.Format(
" Where CollegeID = %s "
m_ctrlTree.GetItemText(
hSelItem)
);
}
}
else
strCondition ="" ; //选中的是根节点,没有过滤条件
}
strSql+= strCondition; //为 SQL 加上筛选条件,
m_strSQL = strSql; //将 SQL 语句暂存,以供排序(单击列表控件列时)使用;
m_pRS.ADOExcute(strSql); //提取数据集
AddListItems(); //重新填充列表控件
}
//当单击列表控件某一行后,在详细信息中显示
vo
idCDa t
aD l
g OnC l
ickL
ist(NMHDR pNMHDRLRESULT pRe su
lt)
{ int i = m_ctrlList.GetSelectionMark(); //取得该行
//将值赋给控件变量
m_strID = m_ctrlList.GetItemText(i0);
m_strName = m_ctrlList.GetItemText(i1);
m_strSex = m_ctrlList.GetItemText(i2);
m_timeBirthday = m_pRS.StringToTime(
m_ctrlList.GetItemText(
i3)); //字符类型转化为时间类型
m_strNativePlace = m_ctrlList.GetItemText(i4);
m_strClass = m_ctrlList.GetItemText(i5);
m_strCollege = m_ctrlList.GetItemText(i6);
UpdateData(false); //将变量显示到界面上
*pResult = 0;
//当单击列表控件某一列后按列排序
vo
idCDa t
aD l
g OnCo l
umncli
ckL i
st(NMHDRpNMHDRLRESULTpRe su
lt)
{ NM_LISTVIEW*pNMListView = (NM_LISTVIEW*)pNMHDR;
* pResult = 0;
//单击表头列时排序,可有两种方法,一种是比较调整所有对应行的值冒泡排序,较为麻烦
//这里使用了获取列名并重新填充的方法
//获取表头列中的文字,由于没有直接的函数,所以需要分两步
//取得选中的列索引:Info.iSubItem行索引为:Info.iItem
CPoint pt;
::GetCursorPos(&pt);
m_ctrlList.ScreenToClient(&pt);
LVHITTESTINFO Info;
Info.pt = pt;
Info.flags = LVHT_ABOVE;
m_ctrlList.SubItemHitTest(&Info);
//取得列头中的值即:col.pszText
LVCOLUMN col;
col.mask = LVCF_TEXT;
col.cchTextMax = 20;
char buff20;
col.pszText = buff;
m_ctrlList.GetColumn(Info.iSubItem&col);
//取得值之后,为 SQL 语句增加排序条件,并重新填充
CString strSQL = m_strSQL+ " ORDER BY" +col.pszText;
m_pRS.ADOExcute(strSQL);//提取数据集
AddListItems(); //重新填充列表控件
}
383
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
6 学生信息数据管理
下面将对学生信息数据进行增 、删 、改 、查的操作 ,与院系数据操行的步骤相同 ,先用
ClassWizard 为 CDataDlg 的各个菜单项添加事件 。 在新增和编辑记录中使用了两种方法
实现对列表控件的刷新显示 ,一种是通过暂存的 SQL 语句重新填充数据集刷新显示列表 ,
这种方法操作简单 ,但需查询数据库;一种是直接对列表进行操作 ,更改列表中的数据 ,这
种操作不需要查询数据库 ,但在字段较多时方法略显复杂 ,读者可自行体会 。 具体实现代
码见清单 17 19。
清单17 19 学生数据库增、删、改、查的操作(在 Da
taD
lg.
cpp文件中)
v
oidCDa taDlg OnItemS ea
r h()
c //查找数据事件
{ UpdateData(true);
//查找指定学号的项,并放入列表框中
m_pRS.ADOExcute("SELECT * FROM StudentTab WHERE StuID = "+ m_strID+" ");
AddListItems(); //刷新列表数据
UpdateData(false);
}
v
oidCDa taDlg OnItemAdd() //清空输入框的值,准备增加记录
{ m_strID ="" ;
m_strName ="" ;
m_strSex ="" ;
m_strNativePlace ="" ;
m_strClass ="" ;
m_strCollege ="" ;
UpdateData(false);
}
v
oidCDa taDlg OnItemS ave()//保存增加的记录
{ UpdateData(true);
CString sql;
sql.Format("INSERT INTO StudentTab(StuIDStuNameCollegeIDStuNativePlace
StuClassStuSex
StuBirthday)Values(%s %s %s %s %s %s %s ) ",
m_strIDm_strNamem_strCollegem_strNativePlacem_strClassm_strSex
m_pRS.TimeToString(m_timeBirthday)); //从 sql 开始到此处为一行
if(m_pRS.ADOExcuteNoQuery(sql)= = 1)
{ AfxMessageBox("增加记录成功!");
//增加完记录后,马上在列表中显示;
int n = m_ctrlList.GetItemCount();
n = m_ctrlList.InsertItem(nm_strID);
m_ctrlList.SetItemText(n1m_strName);
m_ctrlList.SetItemText(n2m_strSex);
m_ctrlList.SetItemText(n3m_pRS.TimeToString(m_timeBirthday));//需类型转换
m_ctrlList.SetItemText(n4m_strNativePlace);
m_ctrlList.SetItemText(n5m_strClass);
m_ctrlList.SetItemText(n6m_strCollege);
}
else
AfxMessageBox("增加记录失败,可能是重复的学号!");
}
}
v
oidCDa taDlg OnItemEd it()//保存编辑事件
384 { UpdateData(true);
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
CString sql;
sql.Format("UPDATE StudentTab SET StuID = %s StuName = %s CollegeID = %s
StuNativePlace = %s
StuClass = %s
StuSex = %s
StuBirthday = %s WHERE StuId = %s "
m_strIDm_strNamem_strCollegem_strNativePlacem_strClassm_strSex
m_pRS.TimeToString(m_timeBirthday),m_strID);//此处为一行
if(m_pRS.ADOExcuteNoQuery(sql)= = 1)
{ AfxMessageBox("编辑记录成功!");
m_pRS.ADOExcute(m_strSQL);//编辑完记录后,用暂存的 SQL 的语句刷新显示
AddListItems();
}
else
AfxMessageBox("编辑记录失败!");
}
}
v
oidCDataDlgOnItemDelee()
t //删除事件,由于删除操作不可恢复,因此要谨慎实现
{ if(m_ctrlList.GetSelectionMark()
<0)
{ AfxMessageBox("请先选中要删除的项!");
return;
if(AfxMessageBox("您确认要删除吗?",MB_OKCANCEL)= = IDOK)
{ UpdateData(true);
CString sql;
sql.Format("DELETE * FROM StudentTab WHERE StuID = %s " m_strID);
if(m_pRS.ADOExcuteNoQuery(sql)= = 1)
{ AfxMessageBox("删除记录成功!");
//删除列表项中造中的记录
m_ctrlList.DeleteItem(m_ctrlList.GetSelectionMark());
}
else
AfxMessageBox("删除记录失败!");
}
}
}
调用学生管理窗口
7
调用学生管理窗口与在主窗体中调用其他模块的方法差不多 ,仅仅是权限的不同,设置
2 级以上权限可管理,具体过程略,关键代码见清单 17 20。
清单17 20 调用学生管理窗口(在 ExMI
SDl
g.pp文件中)
c
v
oidCExMI SDlgOnMe nuitemS
tuden t()
{ if(PowerValidate(2))//学生管理,二级以上权限可进
{ CDataDlg dlg;//准备打开数据管理窗口
dlg.m_pRS.m_strDBType = m_pRS.m_strDBType;//将数据库类型传到数据管理窗体
dlg.DoModal();
}
}
385
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
17
46 课程成绩管理模块
由于数据管理模块间的功能和形式都差不多 ,所以这里不再详细介绍,现在只简要介绍
一下与学生数据管理区别较大的课程成绩管理模块 。 由于使用了关系模块,并且要与功能
相对较弱的 ACCESS 数据库相兼容 (无法创建储存过程与视图 ),因而数据的查询就略显复
杂。因此有时在设计数据库时也会增加一些冗余字段来简化查询操作 。
17
4 1 成绩的分类显示
6
成绩数据维护界面见图 17 12。 这里使用了按院系- > 课程- > 课程班级的分级来分类
显示分库。由于课程名称可能出现重复,所以使用 “课程编号:课程名称 ”的格式显示,需要
使用时只需做一下分离即可(以便查询使用),其关键代码见清单 17 21。
图17 12 课程成绩管理窗口
清单17 21 树型控件和列表控件显示有关成绩内容(在 S
cor
eDl
g.pp文件中)
c
v
oidCScor
eD l
g On It
emRe f
resh()
{ //利用查询新组建一个包含院系、课程、班级的表,使用了连接查询
m_pRS.ADOExcute("SELECT ClassIDCourseTab.CourseIDCourseTab.CourseName
CollegeTab.CollegeID FROM ClassTabCourseTabCollegeTab
WHERE ClassTab.CourseID = CourseTab.CourseID AND
CourseTab.CollegeID = CollegeTab.CollegeID" );//此处为一行
AddTreeNodes();
m_strSQL =" SELECT * FROM ScoreTab" ;//添加分数到列表中,并暂存 SQL 语句
m_pRS.ADOExcute(m_strSQL);
AddListItems();
}
v
oidCScor
eD l
g AddTr eeNo ds()
e
{ m_ctrlTree.DeleteAllItems(); //删除所有节点控件中的项目
hRootItem = m_ctrlTree.InsertItem("学生成绩基本信息",0,2,TVI_ROOTTVI_LAST);
m_pRS.MoveFirst(); //指向第一条
386 CString nodeCourse; //声明一个临时字符串变量
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
while(! m_pRS.ADOEOF())
{ //添加院系到根节点中
HTREEITEM hCollege = AddDistinctNode(hRootItemm_pRS.GetFieldString("CollegeID" ));
//添加课程名称到院系节点中, 因为可能有重复的课程名, 所有将课程编号也加到节点值中
nodeCourse = m_pRS.GetFieldString(
"CourseID" )
+ ":"+ m_pRS.GetFieldString("CourseName" )
;
HTREEITEM hCourse = AddDistinctNode(hCollegenodeCourse);
HTREEITEM hClass = AddDistinctNode(hCoursem_pRS.GetFieldString("ClassID" ));
m_pRS.MoveNext();
}
}
虽然图中的树形控件中的部分节点值在成绩表找不到相应字段 ,不过因为有关系的存
在,所以可以使用连接查询来实现树形控件的点击刷新 ,关键代码见清单 17 22。
清单17 22 改变树形控件选项的消息函数(在 S
cor
eDl
g.pp文件中)
c
v
oidCScoreDlg OnS e
lchan ge
dTr ee( NMHDR pNMHDRLRESULT pRe sult)
{ NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW* )pNMHDR;
* pResult = 0;
HTREEITEM hSelItem = m_ctrlTree.GetSelectedItem();//取得当前选中的节点
HTREEITEM hParentItem = m_ctrlTree.GetParentItem(hSelItem);//取得选中节点的父节点
CString strSqlstrstrCondition;//SQL 语句
strSql =" SELECT * FROM ScoreTab" ;//
m_strClassID ="" ;//清空班级,防止误操作
if(hParentItem!= NULL)
{ HTREEITEM hOldParent = m_ctrlTree.GetParentItem(hParentItem);
if(hOldParent!= NULL)
{ HTREEITEM hOlder = m_ctrlTree.GetParentItem(hOldParent);
if(hOlder!= NULL)
{ //选中的是班级节点,添加班级条件即可
strCondition.Format (" WHERE ClassID = %s " m_ctrlTree.GetItemText(hSelItem)
);
m_strClassID = m_ctrlTree.GetItemText(hSelItem);
}
else
//选中的是课程节点,添加课程条件即可(选先分离出课程编号)
CString s = m_ctrlTree.GetItemText(hSelItem);//提取课程编号
int nPos;
nPos = s.Find(_T(":"),0);
if(nPos>0)
{ str = s.Mid(0,nPos);
strCondition.Format( " WHERE ClassID IN (SELECT ClassID FROM
ClassTab WHERE CourseID = %s ) ",str);//生成 SQL 语句的 Where 条件
}
}
}
else
//选中的是院系节点,添加院系条件
strCondition.Format(" WHERE ClassID IN (SELECT ClassID FROM ClassTab WHERE
CourseID IN (SELECT CourseID FROM CourseTab WHERE CollegeID = %s ))",
m_ctrlTree.GetItemText(hSelItem));//院系的连接查询较复杂
}
387
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
}
else
strCondition ="" ;//选中的是根节点,没有过滤条件
}
UpdateData(false);
strSql+ = strCondition;//加上筛选条件,
//将 SQL 语句暂存,以供排序使用;
m_strSQL = strSql;
m_pRS.ADOExcute(strSql);//提取数据集
AddListItems();//刷新列表数件
}
17
4 2 成绩的多种查询
6
在学生成绩查询的界面上,提供了三种查询分数的方式,按
学号、班级号、课程号,以方便教师对学生的管理。 学号可在界面
上直接输入,但班级号和课程号却必须依靠一个弹出的对话框
(见图 17 13)来实现,具体步骤如下: 图17 13 输入参数窗口
(1)首先新建一个名为 CInputDlg 的对话框,并将出现的两
个默认的按钮上的文字分别更名为确认和取消 ,然后添加一个编辑 ID 为 IDC_EDIT_INPUT
的编辑控件。
(2)在 CInputDlg 类的头文件中添加一个公共的变量:
v
oidCInputDl
g OnOK()
{ //将窗口上的变量传给 m_strValue 后返回 IDOK 的模态值。
GetDlgItem(IDC_EDIT_INPUT)->GetWindowText(m_strValue);
CDialog::OnOK();
}
这样就完成了参数输入对话框的制作。 下面再以按课程号查询为例,说明如何使用这
个对话框。
(4)首先在 CScoreDlg 的源文件中添加输入参数对话框的引用 。
# include" InputDlg.h"
v
oidCScor
eD l
g OnB t
nCo urse()
{ //弹出输入框,按输入的课程号查找所有成绩
CInputDlg dlg;
if(dlg.DoModal()= = IDOK)
388
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
成绩的统计分析
3
这里的成绩统计分析是针对班级的 ,其实现和算法都比 1747 的课程的成绩统计分析
要简单。不过在调用之前需要将班级编号传递过去,即在班级成绩统计分析中添加一个公
共的字符型传递变量即可,具体实现不再累述。
17
47 课程成绩统计模块
课程成绩统计模块与院系、用户管理
模块的难度相当,不过增加了很多与窗口
的数据交互传递工作。
界面设计
1
图 17 14 为 课 程 成 绩 统 计 界 面 ,将
课程 成 绩 统 计 类 命 名 为: CReportDlgID
号为:IDD_ REPORTDLG_ DIALOG。 课 程 成 绩
统计模 块 的 左 侧 是 一 个 树 形 控 件 ,右 侧
是各种统计信息 ,分隔线是用 Picture 控
件制作的 ,外框是 Group Box表 17 12 是
图17 14 课程成绩统计窗口
关键控件列表 ,其中所有的 EDIT 控件都
是只读的 。
表17 12 课程成绩统计窗口控件列表
控件类型 控件 ID 控件变量 控 件 说 明
Edit Box IDC_EDIT_NAME m_strName 课程名称
Edit Box IDC_EDIT_CREDIT m_fCredit 课程学分
Edit Box IDC_EDIT_COLLEGE m_strCollege 所属院系
Edit Box IDC_EDIT_COUNT m_nCount 课程人数
Edit Box IDC_EDIT_HIGH m_fHigh 最高分
Edit Box IDC_EDIT_LOW m_fLow 最低分
Edit Box IDC_EDIT_AVG m_fAvg 平均分
Edit Box IDC_EDIT_DELTA m_fDelta 标准差
Edit Box IDC_EDIT_50 m_n50 50 分以下人数
389
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
续 表
控件类型 控件 ID 控件变量 控 件 说 明
Edit Box IDC_EDIT_R50 m_str50 50 分以下百分比
Edit Box IDC_EDIT_60 m_n60 50—59 分人数
Edit Box IDC_EDIT_R60 m_str60 50—59 分百分比
Edit Box IDC_EDIT_70 m_n70 60—69 分人数
Edit Box IDC_EDIT_R70 m_str70 60—69 分百分比
Edit Box IDC_EDIT_80 m_n80 70—79 分人数
Edit Box IDC_EDIT_R80 m_str80 70—79 分百分比
Edit Box IDC_EDIT_90 m_n90 80—89 分人数
Edit Box IDC_EDIT_R90 m_str90 80—89 分百分比
Edit Box IDC_EDIT_100 m_n100 90—100 分人数
Edit Box IDC_EDIT_R100 m_str100 90—100 分百分比
选 Styles 中:Has buttons;Has lines;
Tree Control IDC_TREE m_ctrlTree
Lines at root;
课程分类显示
2
课程成绩统计模块的课程分类显示与 1746 的成绩显示模块差不多,甚至更加容易,
因为没有了班级一级的子节点,关键代码见清单 17 25。
清单17 25 课程分类显示(在 Re
por
tDl
g.pp文件)
c
BOOLCRe portDlgOnIni
tDialog()
{ CDialog::OnInitDialog();
//初始化树形控件,设置图标
m_ctrlTree.SetImageList(&m_TreeBootImageTVSIL_NORMAL);
m_pRS.ADOOpen();//打开数据集
m_pRS.ADOExcute("SELECT * FROM CourseTab" );//添加院系、课程到树控件中
AddTreeNodes();//增加树节点
return TRUE;
v
oidCReportD l
g AddTreeNo des()
{ //删除所有节点控件中的项目
m_ctrlTree.DeleteAllItems();
//添加根节点即学生表的信息
hRootItem = m_ctrlTree.InsertItem( "所有课程列表", 0,2,TVI_ROOTTVI_LAST);
m_pRS.MoveFirst();//指向第一条
while(!m_pRS.ADOEOF())
{ //添加院系到根节点中
HTREEITEM hCollege = AddDistinctNode(
hRootItemm_pRS.GetFieldString("CollegeID" )
);
//添加课程名称到院系节点中, 因为可能有重复的课程名, 所有将课程编号也加到节点值中
CString nodeCourse = m_pRS.GetFieldString("CourseID" )+ ":"+
m_pRS.GetFieldString("CourseName" );
HTREEITEM hClass = AddDistinctNode(hCollegenodeCourse);//此函数前文清单已列出
m_pRS.MoveNext();
}
}
390
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
课程成绩统计
3
下面说明树形控件的单击节点事件和成绩统计函数 ,在成绩统计中要计算标准差,标准
2
差的数学公式为:标本标准差= ∑(样本值-样本平均值)。
槡
样本个数-1
使用树形控件的单击节点事件来触发执行统计计算函数 ,关键代码见清单 17 26,注意
在程序开头要添加# include" Math.h" 。
清单17 26 改变树形控件选项得到课程成绩统计(在 Re
por
tDl
g.pp文件)
c
# include" Math.h"//引用数学函数库
v
oi dCRe por
tD lgOnS el
ch angedTr e(
e NMHDR pNMHDRLRESULT pRe s u
lt)
{ NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW* )pNMHDR;
* pResult = 0;
HTREEITEM hSelItem = m_ctrlTree.GetSelectedItem();//取得当前选中的节点
HTREEITEM hParentItem = m_ctrlTree.GetParentItem(hSelItem);//取得选中节点的父节点
//判断选中的是根节点,还是学院二级子节点。并取回根节点
CString strstrSqlstrCondition;//SQL 语句
//只有当选中课程节点时才开始计算
if(hParentItem!= NULL)
{ HTREEITEM hOldParent = m_ctrlTree.GetParentItem(hParentItem);
if(hOldParent!= NULL)
{ //选中的是课程节点,添加课程条件即可
//先分离出课程编号
CString s = m_ctrlTree.GetItemText(hSelItem);
int nPos;
nPos = s.Find(_T(":"),0);
if(nPos>0)
{ str = s.Mid(0,nPos);
//查询课程信息的 SQL 命令
strSql.Format("SELECT * FROM CourseTab WHERE CourseID = %s " str);
m_pRS.ADOExcute(strSql);
m_strName = m_pRS.GetFieldString("CourseName" );
m_fCredit = m_pRS.GetFieldFloat("CourseCredit" );
m_strCollege = m_pRS.GetFieldString("CollegeID" );
Calculate(str);//调用统计函数
UpdateData(false);//更新界面数据
}
}
}
}
v
oi dCRe por
tD lgCa l
culate(CStringc our
seID)
{ //先清空所有
m_fLow = 00f;
m_fHigh = 00f;
m_fDelta = 00f;
m_fCredit = 00f;
m_fAvg = 00f;
m_n90 = 0;
m_n80 = 0;
m_n70 = 0;
m_n60 = 0; 391
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
m_n50 = 0;
m_n100 = 0;
m_nCount = 0;
CString sql;
//先提出某门课程的平均分,使用了 SQL 中的 Avg 关键字。
sql.Format("SELECT Avg(Score)FROM ScoreTab WHERE ClassID IN (SELECT ClassID FROM
ClassTab WHERE CourseID = %s )",courseID);//此处为一行
m_pRS.ADOExcute(sql);
//因为 ACCESS 中提取的平均数是字符类型,需要转化为浮点型
m_fAvg = atof(m_pRS.GetFieldString(0));
m_fHigh = m_fAvg;//初始化最高分和最低分
m_fLow = m_fAvg;
//找出最高分、最低分,并计算出标准差
float sumscore;
sum = 0;
sql.Format("SELECT score FROM ScoreTab WHERE ClassID IN (SELECT ClassID FROM
ClassTab WHERE CourseID = %s )",courseID);//此处为一行
m_pRS.ADOExcute(sql);
m_nCount = m_pRS.nFieldRows;
while(!m_pRS.ADOEOF())
{ score = m_pRS.GetFieldFloat(0);
if(score>m_fHigh) m_fHigh = score;
if(score<m_fLow) m_fLow = score;
sum+ = (scorem_fAvg)* (scorem_fAvg); //方差的和
//各个分数段人数统计
if(score<50) m_n50+ + ;
else if(score<60) m_n60+ + ;
else if(score<70) m_n70+ + ;
else if(score<80) m_n80+ + ;
else if(score<90) m_n90+ + ;
else if(score<= 100) m_n100+ + ;
m_pRS.MoveNext();
}
if(m_pRS.nFieldRows>1)
{ m_fDelta = sqrt(sum/(m_pRS.nFieldRows-1));//计算标准差
}
else
m_fDelta = 0;
//计算出各分数段所占人数比例,因为整型相除还是整型,所以先把其中一个转化为浮点型即可
if(m_nCount>0)
{ m_str100.Format("%31f%%" (float)m_n100/m_nCount* 100);
m_str90.Format("%31f%%" (float)m_n90/m_nCount* 100);
m_str80.Format("%31f%%" (float)m_n80/m_nCount* 100);
m_str70.Format("%31f%%" (float)m_n70/m_nCount* 100);
m_str60.Format("%31f%%" (float)m_n60/m_nCount* 100);
m_str50.Format("%31f%%" (float)m_n50/m_nCount* 100);
}
else
m_str100 =" 0%" ;
m_str90 =" 0%" ;
392
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
m_str80 =" 0%" ;
m_str70 =" 0%" ;
m_str60 =" 0%" ;
m_str50 =" 0%" ;
17
48 帮助的制作
在一个软件项目中帮助文件是必不可少的 ,帮助文件一般有.chm 和.hlp 两种类型。
VC++使用的 MSDN 联机帮助,是一个非常典型的 HtmlHelp 联机帮助系统,HtmlHelp 文件的扩
展名为.chm。在VC++安装盘中的 HtmlHelp 目录下可以找到该工具的软件安装包,但在安装
VC++时 HtmlHelp Workshop 工 具 并 不 会 被 一 同 安 装。 因 此 首 先 要 在 VC++ 安 装 盘 中 的
HtmlHelp 目录下找到 Setup.exe 文件安装 HtmlHelp Workshop 工具。
另外 Microsoft Visual Studio 60 自 带 了 一 个 制 作 系 统 帮 助 文 档 的 工 具 Help
Workshop一般可以在安装目录下的 Microsoft Visual Studio 60 Tools 中找到。 用 Help
Workshop 制作的帮助文件的扩展名为.hlp并且可以将 RTF 文件制作成帮助。 因此可以先
用 WORD 将帮助写好,再另存为 RTF 文件格式。 下面主要说明用 Help Workshop 制作帮助文
件的过程。
制作系统帮助的具体步骤
1
(1)新建一个帮助工程
打开主界面后,选择 File - > New新建,弹出如图 17 15 所示
的界面,选择 Help Project 新建一个帮助工程,按 OK 按钮,然后在
另存为对话框中输入工程保存的目录 ,保存文件名为 Help。
图17 15 选择帮助类型
(2)导入 RTF 帮助文件
接下来弹出如图 17 16 所示的窗口。单击 Fiels 按钮,将准备要制作成帮助的 RTF 文
件导入工程。如图 17 17 所示,单点 ADD 按钮以后,在文件对话框中选择要导入的文件,
然后单击 OK 即可。
(3)设定帮助窗口的属性 393
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
调用帮助及附属工具
2
帮助的调用和计算器/记事本的调用一样,我们分别使用了两种方式实现,读者可比较
二者的区别。首先将 help.hlp 文件拷贝到项目的文件夹中,然后编写在主界面上调用帮助
文件及计算器的消息函数,见清单 17 27。
清单17 27 帮助及附属工具(计算器)的调用(在 ExMI
SDl
g.pp文件)
c
v
oidCExMI SDlgOnMe nuitemHelp()//调帮助文档
{ ShellExecute(NULLNULL_T("help.hlp" ),NULLNULLSW_SHOW);
}
v
oidCExMI SDlgOnMe nuitemCal()
{ //调用计算器
WinExec("calc.exe" SW_SHOW);
}
5 项目包装和项目打包
17
17
51 项目包装
图标的改变
1
AppWizard 会为用户生成一个默认的 MFC 图标。如何修改此图标呢?
在 ResourceView 中,单击 Icon 项左边的“+ ”号,然后将 AppWizard 为程序生成的 IDR_
394 MAINFRAME 删除,再插入用户所选择的新图标,该图标的 ID 号仍然设置为 IDR_MAINFRAME绘
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
制新图标即可。
启动闪屏的制作
2
为应用程序添加闪屏启动封面可选
择菜 单 Project - > Add To Project - >
Components and Controls这 时 弹 出
Components and Controls Gallery 对话
框。 双 击 其 中 的 Develper Studio
Components 文件夹,选择 SplashScreen
组件,单击 Insert 按钮即可。 如图 17
20 所示。
这 时,VC++ 会 为 应 用 程 序 添 加
CSplahWnd 类,该类专门用于管理闪屏。
同时还 会 向 应 用 程 序 中 插 入 ID 号 为
IDB_SPLASH 的位图 (在项目目录下自动
图17 20 插入 Sp
las
hSc
ren组件
e
生成了 Splh16.bmp 位图文件 ),以作为
闪屏窗口的显示封面。用户可以编辑 IDB_SPLASH 位图,也可用其他专用图形处理软件设计
好一幅图形,将文件名保存为 Splsh16.bmp然后将其拷贝到应用程序目录下以替换原来的
Splsh16.bmp 文件。本项目没有将闪屏加入,读者可自行加入。
17
52 项目打包
项目开发后要进行打包和发布 (也可以说是安装盘的制作 ),VC++自带 InstallShield
for VC(60)来打包并发布程序。InstallShield 其实是一款第三方工具,以其功能强大、灵
活性好、容易扩展和强大的网络支持而著称 。这个软件一般放在 VC 安装盘的 ISHIELD 目录
下,直接安装即可使用。下面说明安装盘的制作。
建立安装项目
1
(1)打开的 InstallShield 启动画面如图 17
21 所示,右击鼠标,然后点击 Project Wizard 即可新
建一个安装项目。
(2)接着出现如图 17 22 所示的 Welcome 向导
页,按要求输入应用程序的一些基本信息。 这里输
入的内容也可以通过工作区窗口进行修改,单击下
一步。
(3)接下来是 Choose Dialogs 向导页。 用来让
用户从列表中选择将要在安装过程中 出 现 的 对 话
图17 21 I
nst
all
Shi
eld启动界面
框。单击向导页的左下角的 Preview 按钮可预览该
对话框,选择默认设置(图略),然后单击下一步。
(4)出现如图 17 23 所示的 Choose Target Platforms 向导页。 该向导页让用户选择
一个或多个操作系统平台,用鼠标全面选中,然后单击下一步。 395
V
isu
alC++编程与项目开发 ……………………………………………………………………………………………………
图17 22 We
lcome界面 图17 23 选择平台界面
2 完善安装工程
完成项目创建的 InstallShield 的工程界面如图 17 26 所示。 框架左侧为工程工作
区,由 7 个选项卡构成。右侧显示的视图与工作区中当前选中的选项卡有关。 下方则是信
息输出区。
396 下面主要对工程工作区中的选项卡进行介绍 。
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
Scripts 页: 该 选 项 卡 页 面 用 来
管理安装程序的脚本文件。
Components 页:管理安装程序的
各组件项。
Setup Types 页:用来管理提供给
用户的安装类型。
Setup Files 页:该页面用来管理
在安装过程中将要用到的安装文件 。
File Groups 页:负责管理安装工
程所需要的文件组。
Resources 页: 用 来 管 理 安 装 工
17 26 I
nst
all
Shi
eld工程界面
程所需要的安装资源。
Media 页:用来管理安装程序的发布媒介。
下面介绍如何修改安装工程以便完成安装 。
(1)为安装工程添加文件
切换到 File Groups 页,如图 17 27 所示。 在 File Groups 中添加文件,在右侧空白区
域点击鼠标右键然后选 Insert Files 即可,如图所示将主程序和数据库添加到 Program
Executable Files 中,将帮助添加到 Help Files 中。
图17 27 F
ileGr
o s选项卡
up
图17 28 Comp
one
nts选项卡
图17 29 添加快捷方式
(4)发布制作好的安装工程
398
第十七章 项目开发实例———学生管理信息系统
……………………………………………………………………………………………………………………………………………
399
参 考 文 献
400