Visual C++与 Oracle数据库


Visual C++与 Oracle 数据库 编程案例 徐 武 周启涛 葛卉娟 等编著 内 容 提 要 本书通过多个具体的案例,详细介绍了使用 Visual C++和 Oracle 开发基于数据库技术管理信息系统的 多种方法。管理信息系统是应用软件中最为重要的一种,读者能从本书中学会开发管理信息系统的方法。 本书由两部分组成。第一部分介绍了 Oracle 数据库基础和 Visual C++开发数据库应用的多种方法,包 括 MFC ODBC、ADO,以及利用 Oracle 提供的 OO4O 开发数据库的方法等。第二部分以多个管理系统为 例,按照系统设计、数据库设计与实现、系统的实现三个步骤详细介绍了每个管理信息系统的开发过程, 读者能从案例中真正学会 Visual C++开发数据库的多种方法。 本书浓缩了作者多年的项目开发经验和技巧,力图让读者能从更专业的角度掌握 Visual C++开发 Oracle 数据库系统的方法。 本书内容丰富,实用性强,讲解透彻,易于掌握,适合于有一定 Visual C++应用基础和对数据库有初 步了解的编程人员阅读,也可作为相关专业大学生项目设计时的参考书。 未经许可,不得以任何方式复制或抄袭本书之部分或全部内容。 版权所有,侵权必究。 图书在版编目(CIP)数据 Visual C++与 Oracle 数据库编程案例/徐武等编著.—北京:电子工业出版社, 2004.11 ISBN 7-121-00528-X Ⅰ.V... Ⅱ.徐... Ⅲ.①C 语言-程序设计②关系数据库-数据库管理系统,Oracle-程序设计 Ⅳ. ①TP312②TP311.138 中国版本图书馆 CIP 数据核字(2004)第 111704 号 责任编辑: 祁玉芹 印 刷: 北京天竺颖华印刷厂 出版发行: 电子工业出版社 北京市海淀区万寿路 173 信箱 邮编 100036 经 销: 各地新华书店 开 本: 787×1092 1/16 印张:24.25 字数:573 千字 印 次: 2004 年 11 月第 1 次印刷 印 数: 5000 册 定价:35.00 元 凡购买电子工业出版社的图书,如有缺损问题,请向购买书店调换。若书店售缺,请与本社发行部 联系。联系电话:(010)68279077。质量投诉请发邮件至 zlts@phei.com.cn,盗版侵权举报请发邮件至 dbqq@phei.com.cn。 前前 言言 Visual C++是 Microsoft Visual Studio 开发组件中最为强大的可视化应用程序开发工具, 是计算机界公认的最优秀的应用开发工具之一。Oracle 数据库是当今应用最广泛的大型数 据库系统,在性能和可靠性方面一直被视为业界的先驱。因而优秀的开发工具 Visual C++ 和强大的数据库产品 Oracle,是很多企业开发管理信息系统的首选。 Visual C++提供了多种数据库开发技术,几乎能访问所有的数据库系统,如 Oracle、SQL Server、DB2、Sybase、FoxPro、Access 等。这些开发技术主要包括 ODBC API、MFC ODBC、 DAO、OLE DB 和 ADO。Visual C++提供了良好的开发环境,支持数据库厂商提供的数据 库开发库,如本书要介绍的 OO4O(Oracle objects for OLE),它是 Oracle 厂商提供的专门针 对 Oracle 数据库的开发库,利用这些库可以更好和更快捷地访问 Oracle 数据库。Visual C++ 提供的多种数据库开发技术中,微软推荐使用 MFC ODBC 和 ADO。目前,业界也主要使 用这两种开发技术以及专门针对 Oracle 产品的 OO4O 数据库开发技术。本书将重点介绍这 3 种开发技术,并从实用性的角度提供了 8 个管理信息系统的案例,详细透彻地讲述了这 些开发技术。即使对数据库系统不甚了解的读者,也能通过这 8 个案例的学习掌握数据库 应用系统的开发方法,成为项目开发的核心主力。 本书共 10 章,通过理论基础和案例分析详细介绍了 Visual C++和 Oracle 开发数据库应 用的方法。下面分别介绍各部分的内容。 第 1 章讲述了 Oracle 数据库基础。首先介绍了 Oracle 数据库的一些优点,然后介绍 Oracle 的实用工具以及它们的使用方法,包括数据库配置助手、网络配置助手、网络管理 器、企业控制台、SQL*Plus、SQLPlus Worksheet。这些都是 Oracle 数据库常用的工具。 第 2 章介绍了 Visual C++数据库开发技术。详细介绍了 MFC ODBC 数据库开发技术、 ADO 数据库开发技术、ADO 数据绑定技术、ActiveX 数据绑定控件开发技术和 OO4O 数据 库开发技术。ADO 数据绑定技术是利用 Visual C++ Extensions 进行 ADO 编程,而 ActiveX 数据绑定控件开发技术主要是采用 ADO 数据控件和 ADO 数据绑定控件,因而也属于 ADO 数据库绑定技术。本章对每种技术都辅以实例讲解,避免枯燥、空洞的理论讲述,易于读 者掌握。另外,本章在每种技术的介绍中都给出了针对这些数据库进行记录添加、修改和 删除的多种处理方法,读者可以对比各种开发技术下的这些数据库的基本操作,快速了解 Visual C++的各种数据库开发技术。 第 3 章~第 5 章通过 3 个案例详细介绍了利用 MFC ODBC 开发管理信息系统的方法, 包括家庭备忘录管理系统、企业设备管理系统和人脉资源管理系统。读者可以学会利用 CDatabase 数据库类和 CRecordset 记录集类操作数据库,以及 CDBException 异常类处理数 据库异常的方法。另外,还介绍了在数据库系统中使用存储过程和事务处理的方法,存储 过程的使用能够大大提高数据库的处理速度,而事务处理能提高系统的安全性。 第 6 章~第 9 章通过 4 个案例详细介绍了利用 ADO 开发管理信息系统的方法,包括客户资源管理系统、行业监管系统、人才储备管理系统和家庭账务管理系统。客户资源管理 系统采用了 ADO 的数据库连接对象(Connection Object)、命令对象(Command Object)和记录 集对象(RecordSet Object)来操作数据库。行业监管系统采用连接对象和记录集对象来操作 数据库,并主要介绍了利用记录集对象实现记录的添加、修改和删除的操作。人才储备管 理系统使用了 ADO 数据绑定技术,介绍了数据绑定类的编写方法,以及利用这些绑定类 访问数据库中的数据的方法。家庭账务管理系统利用 ADO 数据控件和数据绑定控件开发 数据库管理系统,通过这些控件,给程序提供了友好的界面,而且还增加了程序的功能, 如利用 MS Chart 控件显示统计信息数据,能使很多信息通过图形而一目了然。 第 10 章通过会议纪要管理系统介绍了 OO4O 数据库开发技术。系统中利用数据库类 ODatabase 和记录集类 ODynaset 来操作数据库,两个类都可以实现记录的添加、修改和删 除操作,可见利用 OO4O 编程也是非常灵活的。 本书通过实例进行阐述,讲解透彻,易于掌握。由于许多大型关系型数据库的通用性, 对于开发人员来说,开发这些数据库的方法是一样的,因而本书介绍的方法不仅适用于 Oracle的OO4O数据库开发技术,也同样适用于其他数据库的开发,如SQL Server、Informix、 DB2、Sysbase 等等。 本书浓缩了作者多年的项目开发经验和技巧,力图让读者能从更专业的角度掌握 Visual C++开发 Oracle 数据库系统的方法。本书由徐武、周启涛和葛卉娟主持编写,参加 编写的人员还有蓝荣香、王昊亮、喻波、马天一、魏勇、郝荣福、孙明、李大宇、武思宇 和邵蕴秋等。由于时间仓促,加之水平有限,书中难免有缺点和不足之处,敬请读者批评 指正。 我们的 E-mail 地址:qiyuqin@phei.com.cn 作者 2004 年 10 月 · 3· 目 录 第 1 章 Oracle 数据库基础............................................................................1 1.1 概述................................................................................................................................1 1.2 实用工具........................................................................................................................1 1.2.1 Oracle 数据库配置助手 .................................................................................... 1 1.2.2 Oracle 网络配置助手 ........................................................................................ 3 1.2.3 Oracle 网络管理器 ............................................................................................ 7 1.2.4 Oracle 企业管理控制台 .................................................................................. 11 1.2.5 Oracle SQL* Plus............................................................................................. 14 1.2.6 Oracle SQLPlus Worksheet.............................................................................. 16 1.3 Oracle 数据库基本操作 ..............................................................................................17 1.3.1 创建表空间 ...................................................................................................... 17 1.3.2 创建用户 .......................................................................................................... 20 1.3.3 创建数据表和索引 .......................................................................................... 21 1.4 本章小结......................................................................................................................24 第 2 章 Visual C++数据库开发技术介绍 .....................................................25 2.1 概述..............................................................................................................................25 2.1.1 Visual C++开发数据库的优势........................................................................ 25 2.1.2 Visual C++数据库开发技术............................................................................ 26 2.2 MFC ODBC 数据库开发技术.....................................................................................27 2.2.1 MFC ODBC 主要类介绍................................................................................. 27 2.2.2 ODBC 数据源的配置...................................................................................... 27 2.2.3 数据库的连接 .................................................................................................. 30 2.2.4 查询记录 .......................................................................................................... 31 2.2.5 添加记录 .......................................................................................................... 33 2.2.6 修改记录 .......................................................................................................... 34 2.2.7 删除记录 .......................................................................................................... 34 2.2.8 CRecordView 类的运用 .................................................................................. 34 · 4· 2.3 ADO 数据库开发技术................................................................................................ 45 2.3.1 ADO 主要对象介绍......................................................................................... 46 2.3.2 _bstr_t 和_variant_t 类..................................................................................... 46 2.3.3 引入 ADO 库.................................................................................................... 46 2.3.4 数据库的连接 .................................................................................................. 47 2.3.5 查询记录 .......................................................................................................... 48 2.3.6 添加记录 .......................................................................................................... 50 2.3.7 修改记录 .......................................................................................................... 51 2.3.8 删除记录 .......................................................................................................... 51 2.4 ADO 数据绑定技术.................................................................................................... 52 2.4.1 IADORecordBinding 接口介绍...................................................................... 52 2.4.2 绑定单元介绍 .................................................................................................. 53 2.4.3 创建数据绑定类 .............................................................................................. 54 2.4.4 查询记录 .......................................................................................................... 55 2.4.5 添加记录 .......................................................................................................... 56 2.4.6 修改记录 .......................................................................................................... 57 2.5 ActiveX 数据绑定控件开发技术............................................................................... 57 2.5.1 创建一个对话框程序 ...................................................................................... 58 2.5.2 添加 ADO 数据控件........................................................................................ 59 2.5.3 添加 Microsoft DataGrid 控件......................................................................... 62 2.5.4 添加 Microsoft DataCombo 控件 .................................................................... 63 2.5.5 添加 Microsoft DataList 控件.......................................................................... 65 2.5.6 添加 Microsoft Chart 控件............................................................................... 66 2.6 OO4O 数据库开发技术.............................................................................................. 68 2.6.1 OO4O 主要类介绍........................................................................................... 68 2.6.2 引入库文件 ...................................................................................................... 69 2.6.3 数据库的连接 .................................................................................................. 69 2.6.4 查询记录 .......................................................................................................... 70 2.6.5 添加记录 .......................................................................................................... 71 2.6.6 修改记录 .......................................................................................................... 72 2.6.7 删除记录 .......................................................................................................... 72 2.7 本章小结 ..................................................................................................................... 73 第 3 章 家庭备忘录管理系统.......................................................................75 3.1 系统设计 ..................................................................................................................... 75 3.1.1 功能描述 .......................................................................................................... 75 3.1.2 功能模块设计 .................................................................................................. 76 3.2 数据库设计与实现 ..................................................................................................... 76 · 5· 3.2.1 数据库需求设计 .............................................................................................. 76 3.2.2 数据库表的设计 .............................................................................................. 76 3.2.3 数据库表的创建 .............................................................................................. 77 3.3 系统的实现 ..................................................................................................................82 3.3.1 创建应用程序 .................................................................................................. 83 3.3.2 创建主对话框的界面 ...................................................................................... 83 3.3.3 显示数据到界面上 .......................................................................................... 86 3.3.4 家庭成员基本信息的管理 .............................................................................. 92 3.3.5 备忘录配置信息的管理 .................................................................................. 97 3.3.6 备忘录信息的管理 ........................................................................................ 100 3.3.7 查询备忘录信息 ............................................................................................ 107 3.4 本章小结....................................................................................................................118 第 4 章 企业设备管理系统 ........................................................................119 4.1 系统设计....................................................................................................................119 4.1.1 功能描述 ........................................................................................................ 119 4.1.2 功能模块设计 ................................................................................................ 120 4.2 数据库设计与实现 ....................................................................................................120 4.2.1 数据库需求设计 ............................................................................................ 120 4.2.2 数据库表的设计 ............................................................................................ 120 4.2.3 数据库表的创建 ............................................................................................ 121 4.3 系统的实现 ................................................................................................................122 4.3.1 创建应用程序 ................................................................................................ 122 4.3.2 创建主对话框的界面 .................................................................................... 122 4.3.3 显示数据到界面上 ........................................................................................ 125 4.3.4 设备库存管理 ................................................................................................ 131 4.3.5 设备借出归还管理 ........................................................................................ 137 4.3.6 统计信息管理 ................................................................................................ 145 4.4 本章小结....................................................................................................................156 第 5 章 人脉资源管理系统 ........................................................................157 5.1 系统设计....................................................................................................................157 5.1.1 功能描述 ........................................................................................................ 157 5.1.2 功能模块设计 ................................................................................................ 158 5.2 数据库设计与实现 ....................................................................................................158 5.2.1 数据库需求设计 ............................................................................................ 158 5.2.2 数据库表的设计 ............................................................................................ 159 · 6· 5.2.3 数据库表的创建 ............................................................................................ 160 5.3 系统的实现 ............................................................................................................... 165 5.3.1 创建应用程序 ................................................................................................ 165 5.3.2 创建主对话框的界面 .................................................................................... 165 5.3.3 显示数据到界面上 ........................................................................................ 168 5.3.4 系统配置信息管理 ........................................................................................ 176 5.3.5 人脉信息管理 ................................................................................................ 180 5.3.6 人脉查询管理 ................................................................................................ 188 5.4 本章小结 ................................................................................................................... 197 第 6 章 客户资源管理系统 ........................................................................199 6.1 系统设计 ................................................................................................................... 199 6.1.1 功能描述 ........................................................................................................ 199 6.1.2 功能模块设计 ................................................................................................ 199 6.2 数据库设计与实现 ................................................................................................... 200 6.2.1 数据库需求设计 ............................................................................................ 200 6.2.2 数据库表的设计 ............................................................................................ 200 6.2.3 数据库表的创建 ............................................................................................ 201 6.3 系统的实现 ............................................................................................................... 203 6.3.1 创建应用程序 ................................................................................................ 203 6.3.2 创建主对话框的界面 .................................................................................... 203 6.3.3 显示数据到界面上 ........................................................................................ 206 6.3.4 客户信息管理 ................................................................................................ 212 6.3.5 客户合同管理 ................................................................................................ 219 6.3.6 合同客户信息管理 ........................................................................................ 224 6.4 本章小结 ................................................................................................................... 232 第 7 章 行业监管系统 ...............................................................................233 7.1 系统设计 ................................................................................................................... 233 7.1.1 功能描述 ........................................................................................................ 233 7.1.2 功能模块设计 ................................................................................................ 233 7.2 数据库设计与实现 ................................................................................................... 234 7.2.1 数据库需求设计 ............................................................................................ 234 7.2.2 数据库表的设计 ............................................................................................ 234 7.2.3 数据库表的创建 ............................................................................................ 235 7.3 系统的实现 ............................................................................................................... 236 7.3.1 创建应用程序 ................................................................................................ 236 · 7· 7.3.2 创建系统登录界面 ........................................................................................ 236 7.3.3 创建主对话框的界面 .................................................................................... 238 7.3.4 系统登录管理 ................................................................................................ 240 7.3.5 显示数据到界面上 ........................................................................................ 246 7.3.6 企业基本信息管理 ........................................................................................ 254 7.3.7 企业质量信誉档案管理 ................................................................................ 262 7.4 本章小结....................................................................................................................265 第 8 章 公司人才储备管理系统 .................................................................267 8.1 系统设计....................................................................................................................267 8.1.1 功能描述 ........................................................................................................ 267 8.1.2 功能模块设计 ................................................................................................ 267 8.2 数据库设计与实现 ....................................................................................................268 8.2.1 数据库需求设计 ............................................................................................ 268 8.2.2 数据库表的设计 ............................................................................................ 269 8.2.3 数据库表的创建 ............................................................................................ 270 8.3 系统的实现 ................................................................................................................271 8.3.1 创建应用程序 ................................................................................................ 272 8.3.2 创建主对话框的界面 .................................................................................... 272 8.3.3 显示数据到界面上 ........................................................................................ 274 8.3.4 人才信息管理 ................................................................................................ 286 8.3.5 教育经历信息管理 ........................................................................................ 296 8.3.6 职位变更信息管理 ........................................................................................ 300 8.3.7 工作成果信息管理 ........................................................................................ 303 8.4 本章小结....................................................................................................................306 第 9 章 家庭账务管理系统 ........................................................................307 9.1 系统设计....................................................................................................................307 9.1.1 功能描述 ........................................................................................................ 307 9.1.2 功能模块设计 ................................................................................................ 307 9.2 数据库设计与实现 ....................................................................................................308 9.2.1 数据库需求设计 ............................................................................................ 308 9.2.2 数据库表的设计 ............................................................................................ 308 9.2.3 数据库表的创建 ............................................................................................ 310 9.3 系统的实现 ................................................................................................................314 9.3.1 创建应用程序 ................................................................................................ 314 9.3.2 创建主对话框的界面 .................................................................................... 314 · 8· 9.3.3 显示数据到界面上 ........................................................................................ 317 9.3.4 收支类型管理 ................................................................................................ 324 9.3.5 收入信息管理 ................................................................................................ 326 9.3.6 支出信息管理 ................................................................................................ 330 9.3.7 统计查询管理 ................................................................................................ 333 9.4 本章小结 ................................................................................................................... 350 第 10 章 会议纪要管理系统 ......................................................................351 10.1 系统设计 ................................................................................................................. 351 10.1.1 功能描述 ...................................................................................................... 351 10.1.2 功能模块设计 .............................................................................................. 351 10.2 数据库设计与实现 ................................................................................................. 351 10.2.1 数据库需求设计 .......................................................................................... 352 10.2.2 数据库表的设计 .......................................................................................... 352 10.2.3 数据库表的创建 .......................................................................................... 353 10.3 系统的实现 ............................................................................................................. 354 10.3.1 创建应用程序 .............................................................................................. 354 10.3.2 创建主对话框的界面 .................................................................................. 355 10.3.3 显示数据到界面上 ...................................................................................... 357 10.3.4 会议基本信息的管理 .................................................................................. 364 10.3.5 议题信息的管理 .......................................................................................... 372 10.4 本章小结 ................................................................................................................. 378 第1章 Oracle数据库基础 Oracle 数据库是当今应用最广泛的大型数据库。Oracle 公司作为全球第一大数据库厂 商,其旗舰产品 Oracle 数据库在国内外获得了诸多成功的应用,全球几乎每个行业都在使 用 Oracle 技术。Oracle 数据库降低了企业运营成本,同时提供了高质量的服务,一直以来 成为各大、中、小型企业青睐的数据库产品之一。 1.1 概述 企业选择 Oracle 数据库的主要原因是它功能强大、性能稳定、使用灵活和易于管理。 Oracle 数据库在这些重要性能方面一直保持着领先的地位,被视为业界的先驱。随着商业 环境的竞争日益激烈,今天的企业面临着一个重要的挑战,即如何以最低的成本和最高质 量的管理来建立企业的管理信息系统。一方面系统功能日益强大,另一方面系统的维护成 本越来越昂贵。一个高效、可靠的信息系统对企业成功的重要性是不言而喻的,系统某个 部分的临时故障可能会危及企业的生存,因为企业收益和客户会因此而丢失,而且会支付 相关赔偿,由此产生的不良公众效应更有可能对企业的信誉和股票的价格带来灾难性的后 果。因此,对企业的信息系统的有效管理是现代企业制胜的一个法宝。正是认识到这样的 需求,很多企业选择了以 Oracle 数据库为后台的管理信息系统,为企业提供了一个完整、 高级和有效的解决方案。 1.2 实用工具 本节详细介绍 Oracle 9i 的一些重要实用工具,读者将从它们的使用方法中逐渐了解这 个强大而又复杂的 Oracle 数据库系统。 1.2.1 Oracle 数据库配置助手 Oracle 数据库配置助手用于创建数据库、在现有的数据库中配置数据库选项、删除数 据库和管理数据库模板。 如图 1-1 所示,选择“开始”|“程序”|Oracle - OraHome90|Configuration and Migration Tools|Database Configuration Assistant 命令,弹出 Oracle Database Configuration Assistant 欢 迎使用窗口,单击“下一步”按钮,进入数据库配置助手的操作界面,如图 1-2 所示。 图 1-1 数据库配置助手菜单 — 2 — 图 1-2 数据库配置助手步骤 1 的操作界面 一般在安装 Oracle 数据库服务端的时候,都会默认地建立一个数据库。在创建这个默 认数据库过程中,会提示用户输入需要创建的数据库的全局数据库名称和系统标识符 (SID)。本书示例中在安装 Oracle 9i 的时候,默认安装的数据库的全局数据库名称为 ORADB,系统标识符(SID)也为 ORADB(读者在使用本书实例的时候,只需将书中用到 Oracle 数据库的全局数据库名称 ORADB 换成自己的 Oracle 全局数据库的名称就可以了), 数据库系统管理员的账号为 system,密码为 james(系统管理员的初始密码为 manager)。 如果在安装 Oracle 数据库服务端的时候没有安装数据库,可利用 Oracle 数据库配置助 手来创建数据库。方法如下: 在图 1-2 中选中“创建数据库”单选按钮,单击“下一步”按钮,进入“数据库模板” 窗口,如图 1-3 所示。 图 1-3 选择数据库模板窗口 — 3 — 选中 New Database 单选按钮,单击“下一步”按钮,进入“数据库标识”窗口,如图 1-4 所示。 图 1-4 创建数据库标识窗口 在“全局数据库名称”文本框中输入 ORADB,在 SID 文本框中输入 ORADB,单击 “完成”按钮,全局数据库名称为 ORADB 的数据库创建成功。 1.2.2 Oracle 网络配置助手 Oracle 网络配置助手提供了监听程序配置、命名方法配置、本地网络服务名配置和目 录使用配置的功能。在进行 Oracle 数据库开发的时候,都需要配置一个本地服务名。利用 Oracle 网络配置助手能够实现本地服务名的配置。方法如下: 如图 1-5 所示,选择“开始”|“程序”|Oracle - OraHome90|Configuration and Migration Tools|Net Configuration Assistant 命令,弹出“Oracle Net Configuration Assistant:欢迎使用” 对话框,如图 1-6 所示。 图 1-5 网络配置助手菜单示意图 选中“监听程序配置”单选按钮,单击“下一步”按钮,进入“网络服务名配置”对 话框,选中“添加”单选按钮,如图 1-7 所示。 单击“下一步”按钮,选中“Oracle8i 或更高版本数据库或服务”单选按钮,如图 1-8 — 4 — 所示。 图 1-6 “Oracle Net Configuration Assistant:欢迎使用”对话框 图 1-7 网络服务名配置对话框 图 1-8 选择 Oracle 版本 — 5 — 单击“下一步”按钮,在“服务名”文本框中输入 ORADB(Oracle 数据库的全局数据 库名称),如图 1-9 所示。 图 1-9 输入服务名 单击“下一步”按钮,进入“网络服务名配置,请选择协议”对话框,选择 TCP 协议, 如图 1-10 所示。 图 1-10 选择协议 单击“下一步”按钮,进入“网络服务名配置,TCP/IP 协议”对话框,在“主机名” 文本框中输入 Oracle 数据库服务端的主机名 ghj(如果不知道主机名称,可以如图 1-11 所示, 在 Oracle 数据库服务端的机器上的命令窗口的命令行中输入 hostname,按 Enter 键获取主 机名 ghj),选中“使用标准端口号 1521”单选按钮,如图 1-12 所示。 图 1-11 获取 Oracle 安装机器主机名对话框 — 6 — 图 1-12 设置主机名与端口号 单击“下一步”按钮,进入“网络服务名配置,测试”对话框,选中“是,进行测试” 单选按钮,如图 1-13 所示。 图 1-13 选择是否测试 单击“下一步”按钮,进入“网络服务名配置,正在连接”对话框,如图 1-14 所示。 图 1-14 连接测试结果对话框 在“详细信息”文本框中列出了数据库连接信息。如果显示图 1-14 中“正在连接…测 — 7 — 试成功”的信息,表明可以连接数据库 ORADB。单击“下一步”按钮,进入“网络服务 名配置,网络服务名”对话框,如图 1-15 所示。 图 1-15 输入网络服务名 在“网络服务名”文本框中输入要配置的本地服务名 ORADB(此名称可任意),单击“下 一步”按钮,进入“网络服务名配置,是否配置另一个网络服务名?”对话框。在“是否 配置另一个网络服务名?”选项组中选中“否”单选按钮,然后单击“下一步”按钮,进 入“网络服务名配置完毕!”对话框,如图 1-16 所示。 图 1-16 网络服务名配置成功 单击“下一步”按钮,进入“Oracle Net Configuration Assistant 欢迎使用”对话框,单 击“完成”按钮,完成本地服务名 ORADB 的配置。 1.2.3 Oracle 网络管理器 Oracle 网络管理器主要用来配置本地网络服务名,本地监听程序和名称服务器。通过 Oracle 网络管理器也可以创建一个本地服务名。方法如下: 如图 1-17 所示,选择“开始”|“程序”|Oracle - OraHome90|Configuration and Migration — 8 — Tools|Net Manager 命令,弹出 Oracle Net Manager 窗口,如图 1-18 所示。 图 1-17 网络管理器菜单 图 1-18 网络管理器窗口 如图 1-19 所示,选择“编辑”|“创建”命令,弹出“网络服务名向导:欢迎使用” 对话框,如图 1-20 所示。 图 1-19 选择创建服务名 — 9 — 图 1-20 “网络服务名向导:欢迎使用”对话框 在“网络服务名”文本框中输入需要配置的网络服务名称 ORADB2(名称可任意),如 图 1-21 所示。 图 1-21 输入网络服务名 单击“下一步”按钮,进入“协议”对话框,从协议列表中选择“TCP/IP(Internet 协 议)”选项,如图 1-22 所示。 图 1-22 选择协议 单击“下一步”按钮,进入“协议设置”对话框,在“主机名”文本框中输入 ghj, 在“端口号”文本框中输入 1521,如图 1-23 所示。 — 10 — 图 1-23 协议设置 单击“下一步”按钮,进入“服务”对话框,选择“(Oracle8i 或更高版本)服务名”单 选按钮,在“服务名”文本框中输入 ORADB,选择“连接类型”列表框中的“数据库默 认”选项,如图 1-24 所示。 图 1-24 服务配置 单击“下一步”按钮,进入“测试”对话框,如图 1-25 所示。 图 1-25 “测试”对话框 — 11 — 单击“测试”按钮,弹出“连接测试”对话框,显示数据库连接信息,如图 1-26 所示。 图 1-26 连接测试结果 单击“关闭”按钮,退出“连接测试”对话框,并返回到“测试”对话框。单击“测 试” 对话框中的“完成”按钮,网络服务名 ORADB2 配置成功。选择 Oracle Net Manager 主窗口中“Oracle Net 配置”节点下的“本地”选项,单击“本地”选项左边的“+”号, 展开“本地”选项,双击“服务命名”选项,可以看到已配置的本地服务 ORADB2 以及上 一节配置的本地服务 ORADB 的信息,如图 1-27 所示。 图 1-27 查本地服务命名信息 1.2.4 Oracle 企业管理控制台 Oracle 企业管理控制台是 Oracle 数据库实用工具中最重要的一个工具。Oracle 运行的 一些重要运行参数可利用这个工具进行修改,还提供了对数据库例程、方案、安全性和存 — 12 — 储等的管理。 如图 1-28 所示,选择“开始”|“程序”|Oracle - OraHome90|Enterprise Manager Console 命令,弹出 Oracle Enterprise Manager Console 对话框,如图 1-29 所示。 图 1-28 企业管理控制台菜单 图 1-29 Oracle Enterprise Manager Console 对话框 选中“独立启动”单选按钮,单击“确定”按钮,进入“Oracle Enterprise Manager Console 独立”窗口,如图 1-30 所示。 图 1-30 企业管理控制台 单击数据库左边的“+”号,展开“数据库”节点,列出企业管理控制台管理的数据 — 13 — 库名称。如果展开的数据库树中没有看到 ORADB 数据库,则需要把 ORADB 数据库添加 到“数据库”树中,选择“导航器”|“将数据库添加到树”命令,弹出“将数据库添加到 树”对话框。可以采用以下两种方式将数据库添加到“数据库”树中。 1. 手动添加数据库 这种方式是直接输入数据库的信息。选中“手动添加数据库”单选按钮,在“主机名” 文本框中输入 ghj,在“端口号”文本框中输入 1521,在 SID 文本框中输入 ORADB,在 “网络服务名”文本框中输入 ORADB,如图 1-31 所示。 图 1-31 手动添加页面 单击“确定”按钮,ORADB 数据库成功添加到数据库树中,如图 1-32 所示。 图 1-32 添加 ORADB 之后数据库树窗口 2. 从本地的 tnsnames.ora 文件夹中添加已选数据库 这种方式是从本地配置的网络服务名中获取数据库信息,然后添加到数据库树中。在 图 1-31 所示的“将数据库添加到树”对话框中,选中“从本地的 tnsnames.ora 文件夹中添 — 14 — 加已选数据库(A)位于 f\oracle\oraqo\NETWORK\ADMIN 中”单选按钮,从服务名列表中选 择已配置的网络服务名 ORADB,单击“确定”按钮,ORADB 数据库就可以添加到数据库 树中。 单击 ORADB 节点,弹出“数据库连接信息”对话框,在“用户名”文本框中输入 system, 在“口令”文本框中输入 james,选择“连接身份”为 Normal,如图 1-33 所示。 图 1-33 “数据库连接信息”对话框 单击“确定”按钮,ORADB 数据库成功连接,并展开了 ORADB 数据库节点,如图 1-34 所示。 图 1-34 ORADB 数据库信息页面 Oracle 企业管理控制台的其他重要的操作将在后续章节中介绍。 1.2.5 Oracle SQL* Plus Oracle SQL* Plus 提供了一个执行 SQL 语句的工具,比如我们可以利用这个工具获取 数据库的信息或修改数据库的信息。 如图 1-35 所示,选择“开始”|“程序”|Oracle - OraHome90|Application Development|SQL Plus 命令,弹出“注册”对话框。在“用户名”文本框中输入 system,在“口令”文本框 中输入 james,在“主机字符串”文本框中输入 ORADB(此处的主机字符串就是前面配置 — 15 — 的本地服务名),如图 1-36 所示。 图 1-35 Oracle SQL*Plus 菜单示意图 图 1-36 Oracle SQL*Plus 注册界面 单击“确定”按钮,进入 Oracle SQL*Plus 窗口,在窗口的编辑区内显示 Oracle 数据 库连接成功的信息,如图 1-37 所示。 图 1-37 Oracle SQL*Plus 登录后的窗口 在闪烁的光标处可以输入希望执行的 SQL 语句,SQL 语句要以分号“;”作为结束符 标志,然后按回车键 Enter 执行这条 SQL 语句。如输入显示系统日期的 SQL 语句“select sysdate from dual;”,如图 1-38 所示。 — 16 — 图 1-38 执行 SQL 语句 按 Enter 键之后,将执行这条 SQL 语句显示系统的当前日期。 1.2.6 Oracle SQLPlus Worksheet Oracle SQLPlus Worksheet 也是一个可以执行 SQL 语句的工具。 如 图 1-39 所 示 , 选 择 “ 开 始 ” | “ 程 序 ” |Oracle-OraHome90|Application Development|SQLPlus Worksheet 命令,弹出“Oracle Enterprise Manager 登录”对话框。选 中“直接连接到数据库”单选按钮,在“用户名”文本框中输入 system,在“口令”文本 框中输入 james,在“服务”文本框中输入 ORADB,选择“连接身份”Normal,如图 1-40 所示。 图 1-39 SQLPlus Worksheet 菜单示意图 图 1-40 “Oracle Enterprise Manager 登录”对话框 — 17 — 单击“确定”按钮,进入“SQL*Plus 工作单”窗口,显示成功的连接信息,如图 1-41 所示。 图 1-41 Oracle SQL*Plus 登录成功窗口 在编辑框中输入要执行的 SQL 语句,然后按 F5 键执行这条 SQL 语句。如输入显示系 统日期的 SQL 语句“select sysdate from dual”,执行这条语句后将显示系统的当前日期, 如图 1-42 所示。 图 1-42 SQL 语句执行界面 1.3 Oracle 数据库基本操作 在 Oracle 数据库开发之前,要学会使用 Oracle 实用工具配置本地服务名(已经在前面 配置了)、创建表空间、添加用户和创建数据库表等一些数据库基本操作。 1.3.1 创建表空间 在创建数据库用户之前,首先需要创建数据库表空间,表空间是数据库表存放的位置。 利用企业管理控制台能很方便地创建表空间。 打开企业管理控制台 Enterprise Manager Console,双击“数据库”节点下的 ORADB 数据库节点,弹出“数据库连接信息”窗口。在“用户名”文本框中输入 system,在“口 令”文本框中输入 james,选择连接身份为 Normal,然后单击“确定”按钮,连接 ORADB 数据库。从展开的 ORADB 数据库树中,双击“存储”节点,在其子树中双击“表空间” — 18 — 节点,在右侧页面列出 Oracle 数据库所有的表空间信息。占用率是数据表所占用表空间的 大小与表空间大小的百分数比值,如图 1-43 所示。 图 1-43 所有表空间列表窗口 右击“表空间”节点,在弹出的快捷菜单中选择“创建”命令,弹出“创建表空间” 对话框。输入名称 DB1,修改数据文件名称为 DB1a.ora,设置数据文件的大小为 10 MB, 如图 1-44 所示。 图 1-44 “创建表空间”对话框 数据文件是表空间存放数据的文件名称。10 MB 表示文件的最大容量,如果数据表中 的数据超过 10 MB 的时候,数据表文件会自动增长。选择“存储”选项卡,保持默认值, — 19 — 即选中“本地管理”、“自动分配”和“是–生成重做日志并可恢复”单选按钮,如图 1-45 所示。 图 1-45 “存储”选项卡 单击“创建”按钮,弹出“表空间创建成功”的信息提示框,单击“确定”按钮,DB1 表空间创建成功,可以在表空间列表中看到已创建的 DB1 表空间。双击表空间 DB1 树节 点,然后再双击“数据文件”树节点,在右侧页面显示数据表文件的信息,如图 1-46 所示。 图 1-46 DB1 表空间数据文件信息页面 — 20 — 从图 1-46 中可以看到,数据文件 DB1a.ora 的路径、大小为 10 MB、使用了 0.063 M、 占用率 0.63%(因为表空间还没有数据,所以占用率比较小)。 1.3.2 创建用户 在创建数据库表之前,必须创建数据库用户,数据表隶属于数据库用户。利用企业管 理控制台能很方便地创建用户。 利用前面已经打开的企业管理控制台 Enterprise Manager Console(确保是以系统管理员 system 的身份登录的,只有系统用户才有权限创建数据表和用户)。在已经打开的 ORADB 数据库树中,双击 ORADB 数据库树下的“安全性”树节点,然后再双击“用户”节点, 列出系统的所有用户。右击“用户”树节点,在弹出的快捷菜单中选择“创建”命令,弹 出“创建用户”对话框,在用户“名称”文本框中输入 DB1,在“输入口令”文本框中输 入 DB1,在“确认口令”文本框中输入 DB1,从“默认值”表空间列表中选择表空间 DB1, 如图 1-47 所示。 图 1-47 “创建用户”对话框 在“创建用户”对话框中选择“角色”选项卡,如图 1-48 所示。 从可用角色列表中双击 RESOURCE 选项,RESOURCE 角色添加到已授予角色的列表 中,如图 1-49 所示。 在“创建用户”对话框中选择“系统权限”选项卡,从可用系统权限列表中双击 SELECT ANY DICTIONARY 和 UNLIMITED TABLESPACE 选项,这两个系统权限就添加到已授予 的系统权限列表中,如图 1-50 所示。 — 21 — 图 1-48 “角色”选项卡 图 1-49 已授予角色页面 图 1-50 已授予系统权限页面 单击“创建”按钮,弹出“用户 创建成功”信息提示框,单击“确定”按钮,DB1 用户创建成功,此时可以在用户列表中看到已添加的用户 DB1。 1.3.3 创建数据表和索引 数据库用户创建成功之后,可以在该数据库用户下创建数据表,以及添加、修改和删 除数据表。利用 Oracle SQLPlus Worksheet 工具能非常方便地创建数据表和索引,也可以 利用其他可以执行 SQL 语句的工具,如 Oracle SQL* Plus。 1. 创建数据表 打开 Oracle SQLPlus Worksheet,进入“Oracle Enterprise Manager 登录”对话框,在 “用户名”文本框中输入 db1,在“口令”文本框中输入 db1,在“服务”文本框中输入 ORADB,选择连接方式 Normal,如图 1-51 所示。 — 22 — 图 1-51 “Oracle Enterprise Manager 登录”对话框 单击“确定”按钮,进入“SQL*Plus 工作单”窗口。在编辑框中输入创建学生信息表 Student_info_tab 的 SQL 语句。SQL 语句如下: CREATE TABLE Student_info_tab ( Student_id INTEGER NOT NULL, Student_name VARCHAR2(24) NULL, Student_sex INTEGER NOT NULL, Student_old INTEGER NOT NULL ); 按 F5 键,执行创建表 Student_info_tab 的 SQL 语句,如图 1-52 所示。 从运行的结果信息中可以看到“表已创建”,表明 Student_info_tab 表创建成功。 图 1-52 创建表窗口 2. 创建索引 索引是提高数据库性能的常用方法。索引可以令数据库服务器比没有索引快得多的速 度查找特定的行,不过索引也在总体上增加了数据库系统的负荷,索引占用磁盘空间,并 且降低添加、删除和更新数据的速度。在多数情况下,索引所带来的数据检索速度的优势 远远超过它的不足之处。现在创建一个非常重要的索引-主键索引,一方面它保证了记录行 的惟一性,另一方面又兼有索引排序的功能。在 Student_info_tab 表中创建主键索引的 SQL 语句如下: — 23 — ALTER TABLE Student_info_tab ADD ( PRIMARY KEY (Student_id) ) ; 选择“编辑”|“全部清除”命令,将清除编辑框中的信息和运行结果信息。按 F5 键, 执行添加主键索引的 SQL 语句,如图 1-53 所示。 图 1-53 创建索引页面 从运行结果信息中可以看到“表已更改”,表明主键索引创建成功。 打开企业管理控制台 Enterprise Manager Console。双击 ORADB 数据库下的“方案” 树节点,然后双击“表”树节点,从展开的用户中双击 DB1 节点,可以看到刚才添加的表 STUDENT_INFO_TAB。单击表 STUDENT_INFO_TAB 树节点,在右侧页面显示表的详细 信息。选择“一般信息”选项卡,如图 1-54 所示。 图 1-54 “一般信息”选项卡 — 24 — 选择“约束条件”选项卡,可以看到刚才添加的主键索引 SYS_C002718,约束类型 PRIMARY,约束条件为表列 STUDENT_ID,因为主键索引也是一个约束,保证表中 Student_id 字段的惟一性,如图 1-55 所示。 图 1-55 表约束条件信息页面 主键索引 SYS_C002718 还可以在“方案”树节点下的索引列表中看到,如图 1-56 所示。 图 1-56 索引页面 1.4 本章小结 本章首先介绍了 Oracle 数据库的一些优点,然后是 Oracle 实用工具的使用方法,包括 数据库配置助手、网络配置助手、网络管理器、企业控制台、SQL*Plus 和 SQLPlus Worksheet。 这些都是今后在 Oracle 数据库开发的时候常用的工具。理解这部分的内容将为后续章节内 容的了解打下良好的基础。 总的来说,Oracle 实用工具提供了友好的界面,随着后续章节的介绍和实例演练,读 者将更加得心应手地使用这些工具。 第 2 章 Visual C++数据库开发技术介绍 Visual C++是 Microsoft Visual Studio 开发组件中最为强大的编程工具。一方面,它是 当今最为流行的系统级开发语言,另一方面,它能和 Microsoft 的操作系统无缝结合,开发 出高性能的 Windows 应用程序。 Visual C++提供了多种数据库开发技术,几乎能访问所有的数据库系统,如 Oracle、 SQL Server、DB2、Sybase、FoxPro 和 Access 等。而且还提供了良好的开发环境,支持数 据库厂商提供的数据库开发库。如本章要讲到的 OO4O(Oracle objects for OLE),它是 Oracle 厂商提供的专门针对 Oracle 数据库的开发库,利用这些库可以更好和更快捷地访问 Oracle 数据库。 利用 Visual C++可以开发出功能强大、性能优良和界面友好的数据库应用程序,这正 是许多公司长期以来采用 Visual C++开发数据库系统的重要原因。 2.1 概述 Visual C++提供了多种数据库访问技术,这些技术各有特色,给开发者提供了更多的 选择。本节首先介绍 Visual C++开发数据库的一些优势,然后介绍这些数据库开发技术的 种类。 2.1.1 Visual C++开发数据库的优势 目前能够开发数据库系统的工具有很多,除了 Visual C++,还有 Delphi、PowerBuilder 和 Visual Basic 等。相比而言,Visual C++拥有许多优势,主要表现在以下方面。 1. 简单性 Visual C++中提供了 MFC 类库、ATL 模板类以及开发向导工具(如应用程序向导 AppWizard 和创建类向导 ClassWizard 等一系列工具),方便开发者快速建立自己的应用程 序,把更多的精力放在数据库系统的设计和开发中。 2. 灵活性 Visual C++提供的开发环境可以使开发者根据自己的需要设计应用程序的界面和功 能,其丰富的类库和控件,为开发者提供了更多的选择。 3. 访问速度快 为了解决 ODBC 开发数据库应用程序访问数据库速度慢的问题,Visual C++提供了新 的访问技术——OLE DB 和它的高层接口 ADO,OLE DB 和 ADO 都是基于 COM 接口的技 术,使用这种技术可以直接对数据库的驱动程序进行访问,大大提高了访问速度。 4. 可扩展性 Visual C++提供了 OLE 技术和 ActiveX 技术,这种技术可以增强应用程序的能力。使 — 26 — 用 OLE 技术和 ActiveX 技术可以使开发者利用 Visual C++中提供的各种组件、控件以及第 三方开发者提供的组件来创建自己的程序,从而实现应用程序的组件化。使用这种技术可 以使应用程序具有良好的可扩展性。 5. 访问多种数据源 传统的 ODBC 技术只能访问关系型数据库。在 Visual C++中,OLE DB 访问技术不仅 可以访问关系型数据库,还可以访问非关系型数据库。 2.1.2 Visual C++数据库开发技术 Visual C++提供了多种数据库开发技术,主要包括 ODBC API、MFC ODBC、DAO、 OLE DB 和 ADO。 1. ODBC(Open DataBase Connectivity)API ODBC 是为数据库应用程序访问关系型数据库时提供的一个统一接口,对于不同的数 据库,ODBC 提供了一套统一的 API,使应用程序可以访问任何一种提供了 ODBC 驱动程 序的数据库。而且,ODBC 已经成为一种标准,所以,目前所有的关系型数据库都提供了 ODBC 驱动程序,这使 ODBC 的应用非常广泛,但 ODBC 只能用于关系型数据库,使得 利用 ODBC 很难访问对象数据库及其他非关系型数据库。ODBC 是一种底层的访问技术, 因此,ODBC API 可以使客户应用程序能够从底层设置和控制数据库,完成一些高层数据 库开发技术无法完成的功能。 2. MFC ODBC(Microsoft Foundation Classes ODBC) 由于直接使用 ODBC API 编写应用程序要编制大量代码,开发者往往需要花费很多的 精力在非核心代码的维护上,而 Visual C++中提供了 MFC ODBC 类,封装了 ODBC API, 这使得利用 MFC 来创建 ODBC 的应用程序非常简便。 3. DAO(Data Access Object) DAO 提供了一种通过程序代码创建和操纵数据库的机制。多个 DAO 构成一个体系结 构,在这个结构中,各个 DAO 对象协同工作。MFC DAO 是微软提供的用于访问 Microsoft Jet 数据库文件(*.mdb)的强有力的数据库开发工具,它通过 DAO 的封装,向程序员提供了 DAO 丰富的操作数据库手段。 4. OLE DB(Object Link and Embedding DataBase) OLE DB 是 Visual C++开发数据库应用中提供的新技术,它基于 COM 接口。因此, OLE DB 对所有的文件系统包括关系型数据库和非关系型数据库都提供了统一的接口。这 些特性使得 OLE DB 技术比传统的数据库访问技术更加优越。与 ODBC 技术相似,OLE DB 属于数据库访问技术中的底层接口。直接使用 OLE DB 来设计数据库应用程序需要大量的 代码,通常使用下面将要介绍的 ADO 数据访问接口。 5. ADO(ActiveX Data Object) ADO 技术是基于 OLE DB 的访问接口,它继承了 OLE DB 技术的优点,并且,ADO 对 OLE DB 的接口作了封装,定义了 ADO 对象,使程序开发得到简化。ADO 技术属于数 据库访问的高层接口。 — 27 — Visual C++提供了上述几种开发技术,但是微软推荐使用 MFC ODBC 和 ADO 数据库 开发技术。其中 DAO 开发技术已经不再被 Visual C++.Net 支持,而且在实际的项目开发中, 也主要使用这两种开发技术,本书将重点介绍这两种开发技术。 2.2 MFC ODBC 数据库开发技术 ODBC 是一个应用广泛的数据库访问应用编程接口,使用标准的 SQL 作为其数据库访 问语言。ODBC 的设计是建立在客户机/服务器体系结构基础之上的。ODBC 使开发者避免 了与数据源连接的复杂性。几乎所有的关系型数据库系统都提供 ODBC 驱动程序,例如 SQL Server、Oracle、Informix、DB2 和 Sysbase 等。ODBC 数据库访问接口是用 C 语言编 写的,充分体现了 C 语言的通用性,从而可以更好地访问多种关系型数据库系统。开发者 只需安装该数据库系统的 ODBC 驱动程序,就可以建立应用程序和数据库系统的通信接 口,从而使应用程序快速访问和操作数据库。 2.2.1 MFC ODBC 主要类介绍 Visual C++中提供了MFC ODBC类,封装了ODBC API,这使得利用MFC来创建ODBC 的应用程序非常简便。在 MFC ODBC 类中,常用的类有 CDatabase 数据库类、CRecordset 记录集类、CRecordView 可视记录集类和 CDBException 异常类。 CDatabase 类对象建立了和数据库数据源的一个连接,通过它可以对数据库进行操作。 例如使用 ExecuteSQL 函数实现对数据库记录的添加、修改和删除操作。 CRecordset 类对象代表一个从数据源中获取的记录集。CRecordset 记录集有两种重要 的打开方式,分别为动态集(Dynaset)和快照集(Snapshot)。快照集(Snapshot)提供了对数据 的静态访问,就像对数据源的某些数据进行拍照一样,是静态的数据,别的用户对数据源 中的数据进行添加、修改和删除的时候,快照集里面的记录不受影响,只有在调用了 CRecordset 的 Requery 函数重新查询的时候,快照集才会产生变化。动态集提供了对数据 的动态访问,和其他用户所做的更改保持同步,当别的用户修改或删除了记录集中的记录 时,会在动态集中反映出来。 CRecordView 类对象提供了一个表单视图来显示和操作记录集,因为 CRecordView 类 派生于 CFormView 类,同时又绑定了一个记录集,从而可以利用表单视图方便地显示、修 改、添加和删除记录。CRecordView 采用对话框数据交换技术(DDX)和记录字段交换技术 (RFX)自动实现表单控件中的数据和数据库中的字段数据的关联。在 2.2.8 节中将详细介绍 CRecordView 的使用方法。 CDBException 类对象处理 MFC ODBC 访问类在操作数据库的时候发生的异常,它继 承于 CException 类。MFC ODBC 类在操作数据库的时候会抛出 CDBException 异常,这些 异常一定要处理,否则程序会不正常中止,给用户带来不便甚至带来巨大的损失。 2.2.2 ODBC 数据源的配置 在利用 MFC ODBC 开发数据库应用程序的时候,需要配置 ODBC 数据源。可以利用 ODBC 数据源管理器添加、修改和删除数据源。 如图 2-1 所示,选择“开始”|“程序”|“管理工具”|“数据源(ODBC)”命令,或者 — 28 — 打开控制面板中管理工具栏下的数据源(ODBC),弹出“ODBC 数据源管理器”对话框。选 择“系统 DSN”选项卡,如图 2-2 所示。 图 2-1 ODBC 数据源菜单示意图 图 2-2 “ODBC 数据源管理器”对话框 单击“添加”按钮,弹出“创建新数据源”对话框,从驱动程序列表中选择 Oracle in OraHome90 选项,如图 2-3 所示。 图 2-3 选择驱动程序对话框 — 29 — 单击“完成”按钮,进入 Oracle ODBC Driver Configuration 对话框,在 Data Source Name 文本框中输入要配置的数据源名称 ORADB(名称可任意),在 TNS Service Name 下拉列表 框中输入 ORADB(就是本地服务名,也可以单击 TNS Service Name 下拉列表框右边的下三 角按钮,从中选取本地服务名 ORADB),如图 2-4 所示。 图 2-4 Oracle ODBC 数据源配置对话框 单击 Test Connection 按钮,弹出 Oracle ODBC Driver Connect 对话框。Service Name 为 ORADB,在 User Name 文本框中输入 System,在 Password 文本框中输入 james,如图 2-5 所示。 图 2-5 ODBC 数据源连接测试对话框 单击 OK 按钮,如果弹出 Connection successful 信息提示框,表明一个 ODBC 数据源 配置成功,数据源的名称为 ORADB。单击信息提示框的“确定”按钮,返回到 Oracle ODBC Driver Connect 对话框,单击 OK 按钮,返回到“ODBC 数据源管理器”对话框。从系统数 据源列表中可以看到已配置的 ORADB 数据源,如图 2-6 所示。 单击“确定”按钮,退出 ODBC 数据源管理器。 — 30 — 图 2-6 系统 DSN 列表对话框 2.2.3 数据库的连接 在进行 MFC ODBC 数据库开发的时候,首选利用 CDatabase 类对象建立一个和数据源 的连接,通过它可以对数据库进行操作。 在使用 MFC ODBC 数据库开发的时候,需要引入 MFC ODBC 数据库类的定义文件, 可以在系统工程下的 StdAfx.h 头文件中使用 include 语句引入 afxdb.h 文件,方法如下: #include 对数据源进行操作之前需要定义一个 CDataBase 对象。代码如下: CDatabase m_db; 利用 CDatabase 类的 OpenEx 函数建立和数据库的连接。函数的原型如下: virtual BOOL OpenEx( LPCTSTR lpszConnectString, DWORD dwOptions = 0 ); throw( CDBException, CMemoryException ); lpszConnectString 是 ODBC 的连接字符串,连接字符串包括数据源名称、用户名和用 户密码。格式为“DSN=OracleServer_Source;UID=123;PWD=abc123”,DSN 代表数据源名 称,UID 代表数据库用户名,PWD 代表数据库用户密码。对于本书中的数据源名称为 ORADB,数据库用户名和数据库用户密码都为 DB1 的 连 接 字 符 串 为 “DSN=ORADB;UID=DB1;PWD=DB1”。在构造连接字符串的时候字符与字符(包括等号和 分号)之间不能有空格字符,如字符串“DSN= ORADB ;UID= DB1;PWD=DB1”中包含了 空字符,就不能建立正确的连接。连接数据库的代码如下: TRY{ m_db.OpenEx(“DSN=ORADB;UID=DB1;PWD=DB1”,CDatabase::noOdbcDialog); } CATCH(CDBException,ex) — 31 — { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH 其中的 TRY、CATCH、AND_CATCH、END_CATCH 都是异常处理宏。从 OpenEx 函 数的原型中可以看出 OpenEx 函数可能会抛出 CDBException 和 CMemoryException 异常, 因而必须对这些异常进行处理。 2.2.4 查询记录 查询数据库中的记录需要使用 CRecordset 类对象,它提供了从数据源中提取记录集, 并对记录集进行操作的方法。如果查询的记录是多条的,可以利用 CRecordset 类的 Move, MoveNext,MovePrev,MoveFirst 和 MoveLast 方法移动记录到指定的位置。 在操作记录集之前,需要定义一个 CRecordset 类对象,并传入 CDatabase 类的指针, 方法如下: CRecordset rs( &m_db ); 或者在定义 CRecordset 类对象之后,再把数据连接指针的地址赋给 rs 的数据库连接指 针变量,方法如下: CRecordset rs; rs.m_pDatabase = &m_db; 然后调用 CRecordset 的 Open 方法打开记录集,Open 函数的原型如下: virtual BOOL Open( UINT nOpenType = AFX_DB_USE_DEFAULT_TYPE, LPCTSTR lpszSQL = NULL, DWORD dwOptions = none ); throw( CDBException, CMemoryException ); nOpenType 为记录集的打开方式,包括 dynaset、snapshot、dynamic 和 forwardOnly 方 式,具体的含义可以查看 MSDN。 lpszSQL 可以是要打开记录集的 SQL 语句,也可以是一个表名或者是一个存储过程。 dwOptions 一般情况下为空,更多的参数可以查看 MSDN。 在查询记录之前,在数据表 STUDENT_INFO_TAB 中添加一条学生 ID 为 2,姓名为 “李四”,性别为 1,年龄为 28 的一条记录。查询表 STUDENT_INFO_TAB 的所有记录的 — 32 — 代码如下: TRY{ CString sql = “Select * from STUDENT_INFO_TAB”; rs.Open(CRecordset::dynaset, sql); int nId,nSex,nOld; CString strName; int i = 0 ; while (!rs.IsEOF()) { i++; CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) nId = var.m_ lVal; var.Clear(); rs.GetFieldValue(1, strName); rs.GetFieldValue(2, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) nSex = var.m_lVal; var.Clear(); rs.GetFieldValue(3, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) nOld = var.m_lVal; var.Clear(); CString strField; strField.Format("The Record %d VALUES: %d, %s, %d, %d \r\n”, i,nId, strName ,nSex,nOld); TRACE(strField); rs.MoveNext(); } } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH 利用 Open 方法打开记录集之后,就可以遍历打开的记录集和获取记录集中的字段值。 — 33 — 遍历记录的时候,利用 CRecordset 类的 IsEOF 方法判断记录集是否到达末尾,如果没有, 可以继续访问记录集,否则退出 While 循环。在获取字段数据的时候,有一个非常重要的 类 CDBVariant,它是 MFC ODBC 中的一个处理数据库的变量类型类,能方便地存取数据, 而不用担心数据类型的转换。使用 GetFieldValue 函数获取字段的值,当访问完一条记录的 时候,利用 MoveNext 方法移动到下一条记录从而可以继续访问。 2.2.5 添加记录 利用 MFC ODBC 类添加新记录非常方便,只需构造添加新记录的 SQL 语句,然后调 用 CDatabase 类的 ExecuteSQL 方法执行 SQL 语句,就能将新记录添加到数据库中。 ExecuteSQL 函数的原型如下: void ExecuteSQL( LPCSTR lpszSQL ); throw( CDBException ); lpszSQL 代表一个 SQL 语句,函数的返回类型是空,即 ExecuteSQL 函数不返回数据 库操作结果。在表 STUDENT_INFO_TAB 中添加一条学生 ID 为 1 的记录的 SQL 语句如下: Insert into STUDENT_INFO_TAB(STUDENT_ID,STUDENT_NAME,STUDENT_SEX, STUDENT_OLD) VALUES(1,’张三’,1,22) 利用 CDatabase 类的 ExecuteSQL 方法在数据库中添加新记录的代码如下: TRY{ CString sql = “Insert into STUDENT_INFO_TAB(STUDENT_ID,” “STUDENT_NAME,STUDENT_SEX,” “STUDENT_OLD) ” “VALUES(1,’张三’,1,22)”; m_db.ExecuteSQL(sql); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH 查看数据库表 STUDENT_INFO_TAB 中的数据,可以发现数据库表中增加了一条 STUDENT_ID 为 1,STUDENT_NAME 为“李四”,STUDENT_SEX 为 1,STUDENT_OLD 为 27 的新记录。 — 34 — 2.2.6 修改记录 修改记录的方法和添加记录的处理方法是一样的,只需构造一个修改数据库记录的 SQL 语句。如要修改学生 ID 为 1 的记录,使该学生的年龄增加 1,SQL 语句如下: Update STUDENT_INFO_TAB SET STUDENT_OLD = STUDENT_OLD +1 Where STUDENT_ID = 1 使用 CDataBase 类的 ExecuteSQL 方法来修改学生记录的代码如下: TRY{ CString sql = “Update STUDENT_INFO_TAB ” “SET STUDENT_OLD = STUDENT_OLD +1 ” “Where STUDENT_ID = 1 ”; m_db.ExecuteSQL(sql); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH 查看数据库表 STUDENT_INFO_TAB 中的数据,学生 ID 为 1 的年龄已经修改为 28。 2.2.7 删除记录 删除记录的方法和添加记录的处理方法也是相似的,只需构造一个删除数据库记录的 SQL 语句。如要删除学生 ID 为 1 的记录,SQL 语句如下: DELETE FROM STUDENT_INFO_TAB WHERE STUDENT_ID = 1 然后利用 CDatabase 的 ExecuteSQL 方法删除记录,代码不再列出,只需替换 SQL 语 句就可以了。 利用 CRecordset 类也可以实现记录的添加、修改和删除,不过必须利用 CRecordset 的继承类,在下面一节 CRecordView 类的运用中读者将学会它的使用方法。 2.2.8 CRecordView 类的运用 CRecordView 类派生于 CFormView(表单视图)类,同时又绑定了一个记录集,提供了 较为方便的以表单视图的方式显示记录集,以及对数据库记录的添加、修改和删除的功能。 读者可以利用 CRecordView 类创建简单的数据库应用程序。 下面将利用应用程序向导(AppWizard)创建一个单文档视图的程序,读者将会更加清晰 — 35 — 了解 CRecordView 类的使用方法,以及了解 CRecordView 是如何采用对话框数据交换技术 (DDX)和记录字段交换技术(RFX)自动实现表单控件中的数据和数据库中的字段数据的关 联。 1. 创建一个单文档视图的程序 运行 Visual C++,选择 File|New 命令,弹出 New 对话框。从 Projects 列表中选择 MFC AppWizard(exe)向导,在 Location 文本框中选择项目工程的目录“D:\VCSAMPLE\”,在 Project name 文本框中输入工程文件的名称 StudentInfo,如图 2-7 所示。 图 2-7 创建新项目对话框 单击 OK 按钮,进入 MFC AppWizard-Step 1 对话框,从应用程序的类型中选中 Single document 单选按钮,选中 Document/View architecture support?复选框,从语言列表中选择 “中文(中国)(APPWZCHS.DLL)”,如图 2-8 所示。 图 2-8 向导第一步对话框 — 36 — 单击Next按钮,进入MFC AppWizard-Step 2 of 6 对话框,选中Database view without file support 单选按钮,如图 2-9 所示。 图 2-9 向导第二步对话框 单击 Data Source 按钮,弹出 Database Options 对话框。选择 Datasource(数据源的连接 方式)为 ODBC,单击 ODBC 下拉列表框右边的下三角按钮,将列出所有的 ODBC 数据源, 从中选择 ORADB。选择 Recordset type(打开数据集的类型)选项组中的 Dynaset(动态集)单 选按钮,如图 2-10 所示。 图 2-10 选择数据源对话框 单击 OK 按钮,弹出 Oracle ODBC Driver Connect 对话框,在 User Name 文本框中输 入 DB1,在 Password 文本框中输入 DB1,如图 2-11 所示。 — 37 — 图 2-11 数据源连接对话框 单 击 OK 按 钮 , 弹 出 Select Database Tables 对话框。从数据表列表中选择 DB1.STUDENT_INFO_TAB 数据表,如图 2-12 所示。 图 2-12 选择数据表对话框 单击 OK 按钮,返回向导的第二步对话框,并在 Data Source 按钮下显示数据源和数据 表的信息,如图 2-13 所示。 图 2-13 数据源和表信息对话框 — 38 — 单击 Finish 按钮,弹出 New Project Information 对话框,显示程序向导自动创建的文 件信息。单击 OK 按钮,一个支持数据库的单文档视图应用程序创建完成。 3. 在表单上添加数据显示控件 在工作视图中选择 ResourceView 选项卡,选择 Dialog 下的 IDD_STUDENTINFO_ FORM 对话框模板。添加 4 个标签框和 4 个编辑框,对应 STUDENT_INFO_TAB 表的 4 个 字段,控件类型、ID 及说明见表 2-1。 表 2-1 控件列表 控件类型 ID 说 明 控件类型 ID 说 明 Label IDC_STATIC 学生 ID Label IDC_STATIC 姓名 Label IDC_STATIC 性别 Label IDC_STATIC 年龄 Edit Box IDC_EDIT_ID 学生 ID 文本框 Edit Box IDC_EDIT_NAME 姓名文本框 Edit Box IDC_EDIT_SEX 性别文本框 Edit Box IDC_EDIT_OLD 年龄文本框 添加完表 2-1 中的控件,编译并运行之后的界面如图 2-14 所示。 图 2-14 添加控件后的主窗口 4. 关联控件变量和数据库中的字段数据 应用程序向导自动创建一个表单视图类 CStudentInfoView(继承于 CRecordView 类), 它绑定了一个记录集,记录集类的名称为 CStudentInfoSet(继承于 CRecordset)。要建立控件 变量和数据库中的字段数据的关联需要两个步骤,一个是在 CStudentInfoView 类中利用对 话框数据交换技术(DDX)建立表单上的控件变量和数据集中的成员数据的关联,另外一个 是在 CStudentInfoSet 类中利用记录字段交换技术(RFX)实现数据集中的成员数据和数据库 中的字段数据的关联。 可以利用 ClassWizard 实现第一个步骤的关联,即在 CStudentInfoView 类中添加编辑 框控件的成员变量,并使这些变量与 CStudentInfoSet 记录集的成员变量关联起来。选择 — 39 — View|ClassWizard 命令,或者按 Ctrl+W 组合键,弹出 MFC ClassWizard 对话框。选择 Member Variables 选 项 卡 , 从 Project 列 表 中 选 择 StudentInfo, 从 Class Name 列 表 中 选 择 StudentInfoView,在 Controls IDs 列表框中将会列出前面 4 个编辑框的 ID。选择 IDC_EDIT_ID,单击 Add Variable 按钮,弹出 Add Member Variable 对话框。单击 Member variable name 列表框中的下三角按钮,从列出的成员参数变量中选择 m_pSet->m_STUDENT_ID,如图 2-15 所示。 图 2-15 添加成员变量对话框 单 击 OK 按钮 ,学 生 ID 的控件成员变量添加完毕,如此就建立了编辑控件 IDC_EDIT_ID 和数据集中的成员数据 m_pSet->m_STUDENT_ID 的关联。以同样的方式添 加编辑框控件 IDC_EDIT_NAME 的成员变量 m_pSet->m_STUDENT_NAME,添加编辑框 控 件 IDC_EDIT_SEX 的 成 员 变 量 m_pSet->m_STUDENT_SEX , 添 加 编 辑 框 控 件 IDC_EDIT_OLD 的成员变量 m_pSet->m_STUDENT_OLD。单击 MFC ClassWizard 对话框 中的 OK 按钮,退出 ClassWizard 对话框。这 4 个编辑控件都建立了和数据集的关联,可 以在 CStudentInfoView 类的 DoDataExchange 函数中,看到刚才添加的成员变量,这是 ClassWizard 自动生成的代码,代码如下: void CStudentInfoView::DoDataExchange(CDataExchange* pDX) { CRecordView::DoDataExchange(pDX); //{{AFX_DATA_MAP(CStudentInfoView) DDX_FieldText(pDX, IDC_EDIT_ID, m_pSet->m_STUDENT_ID, m_pSet); DDX_FieldText(pDX, IDC_EDIT_NAME, m_pSet->m_STUDENT_NAME, m_pSet); DDX_FieldText(pDX, IDC_EDIT_OLD, m_pSet->m_STUDENT_OLD, m_pSet); DDX_FieldText(pDX, IDC_EDIT_SEX, m_pSet->m_STUDENT_SEX, m_pSet); //}}AFX_DATA_MAP } 第 2 个步骤建立数据集中的成员数据和数据库中的字段数据的关联是程序向导自动生 成的,可以查看 StudentInfoSet.cpp 文件中的 DoFieldExchange 函数,了解数据库字段和记 — 40 — 录集是如何关联起来的,代码如下: void CStudentInfoSet::DoFieldExchange(CFieldExchange* pFX) { //{{AFX_FIELD_MAP(CStudentInfoSet) pFX->SetFieldType(CFieldExchange::outputColumn); RFX_Text(pFX, _T("[STUDENT_ID]"), m_STUDENT_ID); RFX_Text(pFX, _T("[STUDENT_NAME]"), m_STUDENT_NAME); RFX_Text(pFX, _T("[STUDENT_SEX]"), m_STUDENT_SEX); RFX_Text(pFX, _T("[STUDENT_OLD]"), m_STUDENT_OLD); //}}AFX_FIELD_MAP } 应用程序向导在 StudentInfoDoc.h 文件中添加了记录集 CStudentInfoSet 的一个成员变 量,代码如下: public: CStudentInfoSet m_studentInfoSet; 从 2.2.4 节中 CRecordset 对象的定义可知,定义与一个 CRecordset 对象需要指定数据 库连接的指针,作为 CRecordset 类的继承类 CStudentInfoSet 也需要数据库的连接指针,而 在 CStudentInfoSet 类的 CstudentInfoSet 构造函数中,构造函数默认传入了一个空的数据库 连接的指针,在 StudentInfoSet.h 文件中的定义如下: public: CStudentInfoSet(CDatabase* pDatabase = NULL); 在 StudentInfoDoc.h 文件中定义的 CStudentInfoSet 变量 m_studentInfoSet 没有带参数, 说明CStudentInfoSet 会自动调用 GetDefaultConnect 函数返回的数据源连接字符串创建一个 数据源的连接,使记录集类的数据库连接指针变量不为空。 CStudentInfoSet 中 的 GetDefaultConnect 函数代码如下: CString CStudentInfoSet::GetDefaultConnect() { return _T("ODBC;DSN=ORADB"); } 可以看出返回的字符串中没有数据库用户和密码信息,那么程序在每次运行的时候都 会要求用户输入数据库的用户名称和数据库的密码,这样给用户带来不便,可以修改 GetDefaultConnect 函数中返回的字符串,修改后的代码如下: CString CStudentInfoSet::GetDefaultConnect() { return _T("ODBC;DSN=ORADB;UID=DB1;PWD=DB1"); } CStudentInfoSet 类还要确定打开什么样的记录集,CStudentInfoSet 对象会自动根据 GetDefaultSQL 函数返回的字符串打开数据库记录集。CStudentInfoSet 类 中 的 GetDefaultSQL 代码如下: — 41 — CString CStudentInfoSet::GetDefaultSQL() { return _T("[DB1].[STUDENT_INFO_TAB]"); } 代码中的字符串为打开数据表 STUDENT_INFO_TAB 所有记录的 SQL 语句。编译并 运行程序,利用工具栏上的导航按钮可以查看数据表中的记录,如图 2-16 所示。 图 2-16 程序运行窗口 5. 添加、修改和删除记录 利用 CRecordset 的继承类 CStudentInfoSet 的 AddNew、Update 和 Delete 方法能够实现 对数据库记录的添加、修改和删除的操作。 在 IDD_STUDENTINFO_FORM 对话框模板中再添加 3 个按钮,分别为“添加”、“删 除”和“刷新”按钮。因为如果要修改记录,只需修改界面上的数值,然后利用导航按钮 移动到另外一条记录,修改后的记录会自动更新到数据库中,所以不用增加“修改”按钮。 而“刷新”按钮用来取消添加和修改的记录。这 3 个按钮的控件类型、ID、说明及响应函 数见表 2-2。 表 2-2 按钮控件列表 控件类型 ID 说 明 函 数 Button IDC_BTN_ADD 添加 OnBtnAdd 函数处理添加新记录的操作 Button IDC_BTN_DEL 删除 OnBtnDel 函数处理删除记录的操作 Button IDC_BTN_REFRESH 刷新 OnBtnRefresh 函数处理刷新记录的操作 为了更好地处理添加、修改和删除记录的操作,需要重载 OnMove 函数来处理滚动命 令。在 CStudentInfoView 视图中添加 OnMove 的消息映射函数。选择 View|ClassWizard 命 令,弹出 MFC ClassWizard 对话框,选择 Message Maps 选项卡,从 Project 列表中选择 StudentInfo,从 Class Name 列表中选择 CStudentInfoView,从 Object IDs 列表中选择 — 42 — CStudentInfoView,从 Messages 列表中选择 OnMove,单击 Add Function 按钮,添加 OnMove 函数,如图 2-17 所示。 图 2-17 添加 OnMove 函数对话框 单击 OK 按钮,OnMove 函数添加成功。在处理 OnMove 滚动函数的时候,需要添加 一个 BOOL 类型的添加标志变量,来标识当前的操作是否为添加记录,在 StudentInfoView.h 文件中定义这个变量,代码如下: private: BOOL m_bAddFlag; 在 CStudentInfoView 的构造函数中设置添加标志变量 m_bAddFlag 的初始值为 FALSE, 代码如下: CStudentInfoView::CStudentInfoView() : CRecordView(CStudentInfoView::IDD) { //{{AFX_DATA_INIT(CStudentInfoView) m_pSet = NULL; //}}AFX_DATA_INIT m_bAddFlag = FALSE; } 修改后的 OnMove 函数的代码如下: BOOL CStudentInfoView::OnMove(UINT nIDMoveCommand) { if (m_bAddFlag) { if (!UpdateData()) return FALSE; TRY — 43 — { m_pSet->Update(); } CATCH(CDBException, e) { AfxMessageBox(e->m_strError); return FALSE; } END_CATCH m_pSet->Requery(); //重新查询记录 UpdateData(FALSE); m_bAddFlag = FALSE; return TRUE; } else { return CRecordView::OnMove(nIDMoveCommand); } } 可以看出 OnMove 函数对添加状态下的滚动命令进行了重新处理,调用 UpdataData 函 数获取界面上的数据。然后调用 CRecordset 的 Update 方法将数据更新到数据库中,最后 调用 Requery 函数,重新查询记录,并将数据显示到界面上,同时设置添加标志为 FALSE。 单击“添加”按钮的处理函数为 OnBtnAdd,代码如下: void CStudentInfoView::OnBtnAdd() { //如果已处于添加状态,则完成添加操作 if (m_ bAddFlag) OnMove(ID_RECORD_FIRST); m_pSet->AddNew(); m_pSet->SetFieldNull(NULL); m_ bAddFlag = TRUE; UpdateData(FALSE); } 从 OnBtnAdd 函数可以看出,如果已处于添加状态(即在单击“添加”按钮之前,已经 单击过一次“添加”按钮,这时第 2 次单击“添加”按钮,就应该把界面上的数据保存到 数据库中),调用 CStudentInfoView 的 OnMove 方法将新记录保存到数据源中。如果不是添 加状态(即第 1 次单击“添加”按钮),将编辑框中的数据清空,以添加新的数据,并把添 加标志设为 TRUE。 编译并运行 StudentInfo 程序。单击“添加”按钮,所有编辑框的信息变为空,然后在 “学生 ID”文本框中输入 3,在“姓名”文本框中输入“王五”,在“性别”文本框中输 入 1,在“年龄”文本框中输入 26,如图 2-18 所示。 再次单击“添加”按钮,新的学生信息将添加到数据库中。利用导航按钮可以查看到 这条新添加的记录。 — 44 — 图 2-18 添加学生信息窗口 如果要修改记录,只需修改界面上的数值,然后利用导航按钮移动到另外一条记录, 修改后的记录会自动更新到数据库中。如把王五的年龄从 26 改为 28,只需修改“年龄” 文本框中的数据为 28,如图 2-19 所示。 图 2-19 修改年龄窗口 利用导航按钮或者记录菜单跳到另外一条记录,王五的年龄就从 26 改为 28 了,因为 移动记录的时候,OnMove 函数会自动将修改的记录保存到数据库中。移动导航按钮,将 记录定位在学生 ID 为 3 的王五上,可以看出年龄已经变为 28。 单击“刷新”按钮的处理函数为 OnBtnRefresh,代码如下: void CStudentInfoView::OnBtnRefresh() { if (m_bAddFlag) { //取消添加模式 m_pSet->Move(AFX_MOVE_REFRESH); — 45 — m_bAddFlag = FALSE; } //更新表单视图 UpdateData(FALSE); } 如果是添加状态(即已经单击过一次“添加”按钮),OnBtnRefresh 函数调用 OnMove 函数(需传入刷新操作 AFX_MOVE_REFRESH 参数)来撤销添加记录的操作,并把记录定位 在添加之前的记录上。如果是做了修改记录的操作,单击“刷新”按钮的时候,修改后的 数据不会保存到数据库中,只需调用 UpdateData 函数把数据从控件成员变量中恢复到界面 上。 单击“删除”按钮的处理函数为 OnBtnDel,代码如下: void CStudentInfoView::OnBtnDel() { TRY{ m_pSet->Delete(); } CATCH(CDBException, e) { AfxMessageBox(e->m_strError); return; } END_CATCH //滚动到下一个记录 m_pSet->MoveNext(); //如果滚出了记录集的边界,则滚动到最后一个记录 if (m_pSet->IsEOF()) m_pSet->MoveLast(); //如果记录变空了,则清除数据成员的值 if (m_pSet->IsBOF()) m_pSet->SetFieldNull(NULL); //更新界面上的显示数据 UpdateData(FALSE); } OnBtnDel 调用 Delete 方法删除记录,删除之后滚动记录到下一条记录。如果滚出记录 集的边界,则跳到最后一个记录;如果记录为空,则清除数据成员的值,界面上的数据也 显示为空。 2.3 ADO 数据库开发技术 ADO 是目前在 Windows 环境中比较流行的数据库编程技术。ADO 是建立在 OLE DB 底层技术之上的高级编程接口,因而它兼具有强大的数据处理功能和简单易用的编程接口, 因而得到广泛的应用。 ADO 的底层是 OLE DB,不仅能访问关系型数据库,也可以访问非关系型数据库,是 现在最快速的数据库访问中间层了。ADO 对 OLE DB 的包装可以说相当成功的,对象模型 — 46 — 简明扼要,功能远超 DAO(Data Access Object)、RDO(Remote Data Object)。 2.3.1 ADO 主要对象介绍 ADO 对象包括:连接对象(Connection Object)、命令对象(Command Object)、记录集对 象(RecordSet Object)、字段对象(Field Object)、记录对象(Record Object)、错误对象(Error Object)、参数对象(Parameter Object)、属性对象(Property Object)和流对象(Stream Object)。 其中最为重要的 3 个对象是连接对象、命令对象和记录集对象。 连接对象(Connection Object)代表一个和数据源的连接,以后的数据库操作都是建立在 这个连接上的。这和 MFC ODBC 中创建数据源的连接的 CDatabase 数据库类非常相似,其 他功能也非常相似。连接对象提供了对数据库的操作,但是它不返回数据库操作之后的记 录集,这是和命令对象不同的一点。 命令对象(Command Object)用来处理数据库的一些操作,在某些方面具有和连接对象 相同的功能。例如都可以执行标准的 SQL 语句以及存储过程,不过命令对象可以返回带有 记录集的结果。 记录集对象(RecordSet Object)代表了一个记录集,和 MFC ODBC 中的 CRecordset 记 录集类有些相似,用来访问记录集。记录集对象能方便地实现记录的添加、修改和删除操 作。 在使用这 3 个对象的时候,需要定义与之对应的 3 个智能指针,分别为:_ConnectionPtr、 _CommandPtr 和_RecordsetPtr,然后调用它们的 CreateInstance 方法实例化,从而创建这 3 个对象的实例。 2.3.2 _bstr_t 和_variant_t 类 在利用 ADO 进行数据库开发的时候,_bstr_t 和_variant_t 两个类很有用,省去了许多 BSTR 和 VARIANT 类型转换的麻烦。 COM 编程不使用 CString 类,因为 COM 必须设计成跨平台,它需要一种更普遍的方 式来处理字符串以及其他数据类型,这也是 VARIANT 变量数据类型的来历。BSTR 类型 也是如此,用来处理 COM 中的字符串。VARIANT 是一个巨大的 union 联合体,几乎包含 了所有的数据类型,简单来说,_variant_t 是一个类,封装了 VARIANT 的数据类型,并允 许我们简单地对之进行强制类型转换。同样,_bstr_t 是对 BSTR 进行了封装的类。有了这 两个类,开发 ADO 程序将得到很大的方便。 2.3.3 引入 ADO 库 在 Visual C++中使用 ADO 开发数据库之前,需要引入 ADO 库。可以在 StdAfx.h 文件 末尾处引入 ADO 库文件,方法如下: #import "C:\Program Files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") 使用预处理指令 import 使程序在编译过程中引入 ADO 动态库(msado15.dll)。 no_namespace 表明不使用命令空间。“rename("EOF","adoEOF")”表明把 ADO 中用到的 EOF 改为 adoEOF,防止发生命名冲突。 — 47 — 利用应用程序向导进行 ADO 数据库开发的时候,需要在程序向导的第二步,选择 Automation 选项,使应用程序能够支持自动化。 2.3.4 数据库的连接 建立数据库的连接需要使用连接对象(Connection Object)。首先定义一个_ConnectionPtr 类型的指针,代码如下: _ConnectionPtr m_pConnection; 然后调用 CreateInstance 方法实例化,代码如下: m_pConnection.CreateInstance(_uuidof(Connection)); 调用 Connection 对象的 Open 方法创建数据库的连接,Open 函数的原型如下: HRESULT Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options ); 其中 ConnectionString 是一个包含连接信息的字符串,UserID 是访问数据库的用户名 称,Password 是访问数据库的密码,Options 为可选参数。如果在连接字符串 ConnectionString 中包含了数据库用户名和密码,UserID 和 Password 值可以为空。 ADO 可以连接许多数据库供应商提供的数据源,尽管这些供应商有自己不同的特点, 但是 ADO 使用相同的编程模型,这也是 ADO 强大和灵活的一个地方。现在构造一个可以 连接 Oracle 数据库的连接字符串 ConnectionString,利用 Oracle 提供的 OLE DB 数据供应 商(Oracle Provider for OLE DB),其典型的连接语句为“Provider= OraOLEDB.Oracle.1;Data Source=serverName;User ID=MyUserID; Password= MyPassword”。其中 DataSource 是本地 服务名。数据库本地服务名为 ORADB,数据库用户名为 db1,密码为 db1 的连接数据源的 代码如下: m_pConnection.CreateInstance(_uuidof(Connection)); m_pRecordset.CreateInstance(_uuidof(Recordset)); m_pCommand.CreateInstance("ADODB.Command"); try { _bstr_t strConnect="Provider=OraOLEDB.Oracle.1;Password=db1;User ID=db1;" "Data Source=oradb;Persist Security Info=True"; //连接参数可以不需要用户 ID 或密码 m_pConnection->Open(strConnect,"","",-1); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } — 48 — 代码中使用了 try 和 catch 来处理 COM 异常,如果不处理,ADO 的异常有可能使程序 崩溃,所以一定要记得捕捉_com_error 异常。代码中使用的 OLEDB 供应商为 Oracle Provider for OLE DB,如果采用微软提供的供应商 Microsoft OLEDB Provider for Oracle 的驱动程序, 即“Provider=MSDAORA.1”,其他参数不变,有的功能不能实现。 2.3.5 查询记录 利用 ADO 查询数据库的记录,需要使用记录集对象(RecordSet Object)。首先定义一个 _ RecordsetPtr 类型的指针,代码如下: _RecordsetPtr m_pRecordset; 然后调用 CreateInstance 方法实例化,代码如下: m_pRecordset.CreateInstance(_uuidof(Recordset)); 再调用 RecordSet 对象的 Open 方法打开记录集,Open 函数的原型如下: HRESULT Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ); 其中 Source 是一个变体类型,可以是一个 Command 对象,也可以是一个 SQL 语句或 者存储过程。ActiveConnection 是一个连接对象,也可以是一个连接数据源的字符串。光 标类型 CursorType 是 CursorTypeEnum 枚举结构中的一个值。枚举结构如下: enum CursorTypeEnum { adOpenUnspecified = -1,//不作特别指定. adOpenForwardOnly = 0,//前滚静态光标.这种光标只能向前浏览记录集,比如用 MoveNext 向前滚动,这种方式可以提高浏览速度. adOpenKeyset = 1,//采用这种光标的记录集看不到其他用户的新增、删除操作. adOpenDynamic = 2,//动态光标.所有数据库的操作都会立即在各用户记录集上反应出来. adOpenStatic = 3//静态光标.产生一个静态的记录集,但其他用户的新增、删除、更新操作是 不可见的. }; 锁定类型 LockType 是 LockTypeEnum 枚举结构中的一个值。枚举结构如下: enum LockTypeEnum { adLockUnspecified = -1,//未指定. adLockReadOnly = 1,//只读记录集. adLockPessimistic = 2,//悲观锁定方式.数据在更新时锁定其他所有动作,这是最安全的锁 定机制. adLockOptimistic = 3,//乐观锁定方式.只有在调用 Update 方法时才锁定记录.在此之前仍 然可以做数据的更新、插入、删除等动作. adLockBatchOptimistic = 4,//乐观分批更新.编辑时记录不会锁定,更改、插入及删除是在 — 49 — 批处理模式下完成. }; Options 可以取如下值: adCmdText:表明 Source 是一个文本命令,如打开记录的 SQL 语句 adCmdTable:表明 Source 是一个表名 adCmdProc:表明 Source 是一个存储过程 adCmdUnknown:未知 利用 Open 方法打开记录集之后,就可以遍历打开的记录集和获取记录集中的字段值。 遍历记录的时候,利用 adoEOF 函数判断记录集是否到达末尾,如果没有,可以继续访问 记录集,否则退出 While 循环。有 3 种方式获取记录的字段值,一种是采用 GetCollect 函 数获取字段值,如要获取某一字段的值,只需传入字段的名称或者字段的序号(从零开始)。 另外两种方式相同,都是使用了字段对象(Field Object),并且调用字段对象的 GetItem 方法 获取需要操作的字段,如要获取某一字段,只需传入字段的名称或者字段的序号(从零开 始),然后取字段的 Value 值或从 GetValue 函数返回的值得到数据表中某一字段的值。查询 表 STUDENT_INFO_TAB 的所有记录的代码如下: try { CString strSql="select * from STUDENT_INFO_TAB"; BSTR bstrSQL = strSql.AllocSysString(); m_pRecordset->Open(bstrSQL,(IDispatch*)m_pConnection,adOpenDynamic,adLo ckOptimistic,adCmdText); int nId,nSex,nOld; CString strName; int i = 0 ; //遍历所有记录 while(!m_pRecordset->adoEOF) { //记录条数增加 i++; _variant_t TheValue; //取记录字段值方式之一 //获取学生 ID 字段值. TheValue = m_pRecordset->Fields->GetItem("STUDENT_ID")->Value; if(TheValue.vt!=VT_NULL) nId = TheValue.iVal; //取记录字段值方式之二 //获取学生姓名字段值. TheValue = m_pRecordset->GetCollect("STUDENT_NAME"); if(TheValue.vt!=VT_NULL) strName= (char*)_bstr_t(TheValue); //取记录字段值方式之三 //获取学生年龄字段值. TheValue = m_pRecordset->Fields->GetItem("STUDENT_OLD")->GetValue(); if(TheValue.vt!=VT_NULL) — 50 — nOld = TheValue.iVal; CString strField; strField.Format("The Record %d VALUES: %d, %s, %d \r\n", i,nId, strName,nOld); AfxMessageBox(strField); m_pRecordset->MoveNext();//转到下一条记录 } m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } 2.3.6 添加记录 ADO 数据库开发技术提供了很多操作数据库的方法,给开发者很多的选择。对于添加 记录,既可以使用连接对象(Connection Object)的 Execute 方法,也可以使用命令对象 (Command Object)的 Execute 方法,只需传入添加记录的 SQL 语句就可以了,这两种方式 类似于 CDataBase 的 ExecuteSQL 方法。另外,利用记录集对象(RecordSet Object)也可以实 现记录的添加操作,首先使用记录集的 AddNew 方法告知数据库要添加新的数据,然后利 用 GetItem 方法获取要操作记录的字段,并设置字段对象的 Value 值,或者利用 PutValue 方法设置字段值,最后使用记录集的 Update 方法将数据更新到数据库中。如添加一条学生 ID 为 4,姓名为“王涛”,性别为 1,年龄为 26 的记录的代码如下: try { CString strSql="select * from STUDENT_INFO_TAB"; BSTR bstrSQL = strSql.AllocSysString(); m_pRecordset->Open(bstrSQL,(IDispatch*)m_pConnection, adOpenDynamic,adLockPessimistic,adCmdText); //添加记录 m_pRecordset->AddNew(); m_pRecordset->Fields->GetItem("STUDENT_ID")->Value = (short)4; //修改字段的值得第一种方式 m_pRecordset->Fields->GetItem("STUDENT_NAME")->Value = _bstr_t(" 王涛 "); m_pRecordset->Fields->GetItem("STUDENT_SEX")->Value = (short)1; //修改字段值的第二种方式 m_pRecordset->Fields->GetItem(_variant_t("STUDENT_OLD"))->PutValue((sho rt)26); //更新数据库 m_pRecordset->Update(); m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); — 51 — } 尽管 Value 是一个变体类型,但是它可以根据赋值数据的类型自动保存数据,省去类 型转换的麻烦。运行这段代码,将在数据库中添加一条 STUDENT_ID 为 4,姓名为“王涛”, 性别为 1,年龄为 26 的一条记录。 2.3.7 修改记录 ADO 提供多种修改记录的方法,和添加记录一样,既可以使用连接对象(Connection Object)的 Execute 方法,也可以使用命令对象(Command Object)的 Execute 方法,只需传入 修改记录的 SQL 语句就可以了。另外,利用记录集对象(RecordSet Object)也可以实现记录 的修改操作,首先打开记录集,然后修改记录集中相应的字段值,最后调用 Update 方法, 修改后的数据就更新到数据库中。如对数据库表 STUDENT_INFO_TAB 中的 STUDENT_ID 为 2 的记录进行修改。修改其姓名为“李小燕”,性别为 0,年龄为 29,利用记录对象修改 记录的方法如下: try { CString strSql="select * from STUDENT_INFO_TAB where STUDENT_ID = 2"; BSTR bstrSQL = strSql.AllocSysString(); m_pRecordset->Open(bstrSQL,(IDispatch*)m_pConnection, adOpenDynamic,adLockPessimistic,adCmdText); //修改第一条记录的值 if(!m_pRecordset->adoEOF) { //修改字段的值的第一种方式 m_pRecordset->Fields->GetItem("STUDENT_NAME")->Value = _bstr_t(" 李小燕"); m_pRecordset->Fields->GetItem("STUDENT_SEX")->Value = (short)0; //修改字段值的第二种方式 m_pRecordset->Fields->GetItem(_variant_t("STUDENT_OLD"))->PutValue((sho rt)29); //更新数据库 m_pRecordset->Update(); } m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } 添加记录的赋值方法和修改记录是一样的,不同的是,在添加记录的时候需要首先调 用 AddNew 方法告知数据库,后面的语句是要添加一条新的记录。 2.3.8 删除记录 删除记录的方法和添加记录的方法一样,既可以使用连接对象(Connection Object)的 Execute 方法,也可以使用命令对象(Command Object)的 Execute 方法,还可以利用记录集 — 52 — 对象(RecordSet Object)的 Delete 方法删除记录。现在给读者介绍一下利用命令对象 (Command Object)实现记录的删除操作。 在 使 用 命 令 对 象 (Command Object) 实现数据库的操作之前,需要定义一个 _ CommandPtr 类型的指针,代码如下: _CommandPtr m_pCommand; 然后调用 CreateInstance 方法实例化,代码如下: m_pCommand.CreateInstance("ADODB.Command"); 设置命令对象(Command Object)的活动连接属性和命令文本属性。设置活动连接 ActiveConnection 使之指向已打开的数据库连接,设置命令文本 CommandText 为要删除记 录的 SQL 语句,最后调用 Execute 方法实现数据库的删除操作。如删除 STUDENT_ID 为 3 的代码如下: try { _variant_t vNULL; vNULL.vt = VT_ERROR; vNULL.scode = DISP_E_PARAMNOTFOUND;//定义为无参数 m_pCommand->ActiveConnection = m_pConnection;//将建立的连接赋值给它 m_pCommand->CommandText = "delete STUDENT_INFO_TAB where STUDENT_ID = 3";//命令字串 m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命 令,取得记录集 } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } 2.4 ADO 数据绑定技术 ADO 数据绑定技术就是利用 Visual C++ Extensions 进行 ADO 编程,提供了把数据库 中的数据直接读取到本地变量的一个接口(IADORecordBinding 接口),从而绕开复杂的数 据类型转换。ADO 数据绑定技术将会使我们的编程工作变得轻松和高效。 2.4.1 IADORecordBinding 接口介绍 Visual C++ Extensions for ADO 把 RecordSet 记录集中的字段绑定到 C/C++变量中。一 旦当前行的数据发生改变,数据将被立即拷贝到绑定的 C/C++变量中。根据需要,数据被 转换到指定的 C/C++数据类型。 IADORecordBinding 接口的 BindToRecordset 方法用来实现数据库字段到本地 C/C++ 变量之间的绑定。如果要新增一条记录,可以使用 AddNew 方法。而 Update 方法把绑定的 C/C++变量数据更新到数据库中。 — 53 — 2.4.2 绑定单元介绍 Visual C++ Extensions for ADO 把 Recordset 对象的字段类型映射到本地的 C/C++变量 中,把这种从一个数据库字段映射到一个 C/C++变量之间的过程定义称为一个绑定单元 (Binding Entries)。绑定单元由宏来完成,可以绑定的类型包括数值型、定长数据类型和可 变长的数据类型。绑定的基本流程是:定义派生自 CADORecordBinding(CADORecordBinding 类本身其实也是一组宏定义)的类,在类中使用 特定的宏来实现数据绑定,然后在类中声明相应的 C/C++变量。 绑 定 单 元 定 义 了 Recordset 字 段 和 C++ 变 量 之 间 的 联 系 。 宏 BEGIN_ADO_ BINDING(Class)(开始宏)和 END_ADO_BINDING()(结束宏)界定了一组绑定单元,其中 Class 是数据绑定类的名称。 绑定单元中的宏提供了对定长类型数据 ADO_FIXED_LENGTH_ENTRY、数值型数据 ADO_NUMERIC_ENTRY、变长类型数据 ADO_VARIABLE_LENGTH_ENTRY 的支持,不 同类型的数据使用不同的绑定宏。这 3 种类型数据的绑定宏格式如下。 1. 定长类型的数据绑定格式 定长类型的数据绑定格式有两种: ADO_FIXED_LENGTH_ENTRY(Ordinal, DataType, Buffer, Status, Modify) ADO_FIXED_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Modify) 这两种绑定格式用来处理诸如 adSmallInt、adBinary、adBigInt、adUnsignedInt 等定长 数据类型的绑定。 2. 数值型数据绑定格式 数值型数据绑定格式有两种格式: ADO_NUMERIC_ENTRY(Ordinal, DataType, Buffer, Precision, Scale, Status, Modify) ADO_NUMERIC_ENTRY2(Ordinal, DataType, Buffer, Precision, Scale, Modify) 这两种绑定格式用来处理诸如 adDouble、adDecimal 等浮点性的数据非常方便,对于 定长类型的 adInteger 等也可以处理。 3. 变长类型的数据绑定格式 变长类型的数据绑定格式有 4 种格式: ADO_VARIABLE_LENGTH_ENTRY(Ordinal, DataType, Buffer, Size, Status, Length, Modify) ADO_VARIABLE_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Size, Status, Modify) ADO_VARIABLE_LENGTH_ENTRY3(Ordinal, DataType, Buffer, Size, Length, Modify) ADO_VARIABLE_LENGTH_ENTRY4(Ordinal, DataType, Buffer, Size, Modify) 这两种绑定格式用来处理变长类型的数据非常方便。能非常方便地处理字符串类型, 如 adChar, adVarChar 以及 adVarBinary。 在上述的 3 种数据绑定格式中,用到了一些参数,它们的含义如下。 — 54 — Class:绑定单元和 C/C++变量定义所在的类,派生自 CADORecordBinding 的类。 Ordinal:序数类型,对应数据库中字段的系号(从 1 开始计数)。 DataType:ADO 的数据类型,如 adVarChar、adSmallInt 等。 Buffer:用来存储字段值的 C++变量。 Size:变量的最大字节数,对于变长类型的字符串,需要有保存零结束符‘\0’的空 间(通常的处理方式是比数据库字段的长度多 2 个字节,见表 STUDENT_INFO_TAB 中的 STUDENT_NAME 字段在数据库中定义了 24 个字符,那么可以设置 Size 的值为 26)。 Status:状态位。指示 Buffer 内容的有效性和转换是否成功。有两个非常重要的值, 一个是 adFldOK,表明转换是成功的;另一个是 adFldNull, 表明字段值为 NULL。 Modify:布尔类型。如果为 TRUE,表明 ADO 允许更新绑定数据;如果为 FALSE, 表明数据是只读的。 Precision:数值类型数据的精度。 Scale:数值类型的小数位数。 Length:一个四字节的变量,保存数据的实际长度。 2.4.3 创建数据绑定类 在使用 Visual C++ Extensions for ADO 进行数据库开发的时候,需要引入 icrsint.h 头文 件,此文件包含了 Visual C++ Extensions for ADO 的定义。方法如下: #import "C:\Program Files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") #include 在实际项目开发的时候,为了使用方便,可以把这两个 include 语句放在 StdAfx.h 文 件的末尾处。这样在创建数据库绑定类的时候,只需引用 StdAfx.h 头文件就可以了。由于 Visual C++ Extensions 的实现是建立在记录集 RecordSet 对象上的,正如记录集对象调用 AddNew 方法添加新记录和 Update 方法更新记录一样,IADORecordBinding 接口也提供了 相应的方法,使用 AddNew 方法添加新记录,以及 Update 方法修改记录。 定义一个访问 STUDENT_INFO_TAB 表的数据绑定类,代码如下: #include "stdafx.h" _COM_SMARTPTR_TYPEDEF(IADORecordBinding, __uuidof(IADORecordBinding)); inline void TESTHR(HRESULT _hr) { if FAILED(_hr) _com_issue_error(_hr); } class CStudentInfoRs : public CADORecordBinding { BEGIN_ADO_BINDING(CStudentInfoRs) ADO_FIXED_LENGTH_ENTRY(1, adInteger, m_n_id, m_ul_idStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_ch_name, sizeof(m_ch_name),m_ul_nameStatus, true) ADO_FIXED_LENGTH_ENTRY(3, adInteger, m_n_sex, m_ul_sexStatus, true) ADO_FIXED_LENGTH_ENTRY(4, adInteger, m_n_old, m_ul_oldStatus, true) END_ADO_BINDING() — 55 — public: int m_n_id; CHAR m_ch_name[26]; int m_n_sex; int m_n_old; ULONG m_ul_idStatus; ULONG m_ul_nameStatus; ULONG m_ul_sexStatus; ULONG m_ul_oldStatus; }; 表 STUDENT_INFO_TAB 中有 3 个字段是整型数据,利用 adInteger 类型的定长数据 绑定格式就可以了,姓名是字符串类型,就采用变长类型数据绑定格式。每绑定一个字段 值,需要一个绑定格式宏、存储字段值的 C++变量,以及这个变量值的状态位。如对于学 生 ID 字段利用定长类型数据绑定格式 ADO_FIXED_LENGTH_ENTRY、存储字段值的 C++ 变量 m_n_id 和状态位 m_ul_idStatus。利用这个数据绑定类 CStudentInfoRs 就可以实现对 表 STUDENT_INFO_TAB 的访问,以及添加和修改的操作。TESTHR 宏用来抛出异常,如 果某些执行语句返回的结果不成功,可以利用 TESTHR 宏抛出异常,而程序代码也需要捕 捉这个异常,从而跳过下面要执行的语句。 2.4.4 查询记录 利用数据绑定类能非常方便地获取已打开记录集的字段值。在利用数据绑定类查询记 录的时候,需要用到记录集对象打开需要查询的记录集,然后利用 IADORecordBinding 接 口的 BindToRecordset 方法将打开的记录集和数据绑定类绑定起来,这样就可以通过访问数 据绑定类成员的方法来访问记录集中的字段值。如查询表 STUDENT_INFO_TAB 所有记录 的代码如下: try { CStudentInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); m_pRecordset->Open("SELECT * FROM STUDENT_INFO_TAB", _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //数据绑定 TESTHR (picRs->BindToRecordset(&rs)); int i = 0 ; int nId,nOld; CString strName; //遍历记录集合 while (!m_pRecordset->adoEOF) { i++; //获取学生 ID 字段值. nId = rs.m_ul_idStatus== adFldOK ?rs.m_n_id:0; — 56 — //获取学生姓名字段值。 strName = rs.m_ul_nameStatus == adFldOK ? rs.m_ch_name: ""; //获取学生年龄字段值。 nOld = rs.m_ul_oldStatus== adFldOK ?rs.m_n_old:0; CString strField; strField.Format("The Record %d VALUES: %d, %s, %d \r\n", i,nId, strName,nOld); TRACE(strField); m_pRecordset->MoveNext();//转到下一条记录 } } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } 在代码中,首先定义一个 CStudentInfoRs 的实例 rs,和一个 IADORecordBinding 的接 口对象 picRs,并传入 m_pRecordset 记录集对象,实现接口和记录集对象的关联,用记录 集对象的 Open 方法打开要查询的记录集,使用IADORecordBinding 接口的 BindToRecordset 方法建立记录集和数据绑定类实例rs的绑定,也就可以遍历记录集和访问记录的字段数据。 此时记录集中的数据已经保存在 rs 的成员变量中,但是在访问之前,需要判断一下成员变 量对应的状态值。如果状态值为 adFldOK,说明数据转换成功,是有效的,可以获取这个 数据值。 2.4.5 添加记录 ADO 数据绑定技术是利用 IADORecordBinding 接口的 AddNew 方法将新数据添加到 数据库中的。首先利用记录集对象打开需要操作的数据表,并利用 IADORecordBinding 接 口的 BindToRecordset 方法将打开的记录集和数据绑定类绑定起来,然后设置数据绑定类成 员的值,最后调用 IADORecordBinding 接口的 AddNew 方法将数据添加到数据库中。如在 表 STUDENT_INFO_TAB 中添加一条记录的代码如下: try { CStudentInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); m_pRecordset->Open("SELECT * FROM STUDENT_INFO_TAB", _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //绑定记录集 TESTHR (picRs->BindToRecordset(&rs)); //给新添加的记录赋值 rs.m_n_id = 6; rs.m_n_old = 30; rs.m_n_sex = 1; strcpy (rs.m_ch_name,"李伟",24); — 57 — //调用 IADORecordBinding 接口的 AddNew 方法添加新数据 TESTHR (picRs->AddNew(&rs)); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } 运行这段代码之后,将在数据库表 STUDENT_INFO_TAB 中添加一条学生 ID 为 6, 姓名为“李伟”,年龄为 30,性别为 1 的记录。 2.4.6 修改记录 ADO 数据绑定技术是利用 IADORecordBinding 接口的 Update 方法把数据更新到数据 库中的。首先利用记录集对象打开需要修改的记录集,调用 IADORecordBinding 接口的 BindToRecordset 方法把打开的记录集和数据绑定类绑定起来,然后修改数据绑定类成员的 值,调用 IADORecordBinding 接口的 Update 方法将数据更新到数据库中。修改表 STUDENT_INFO_TAB 中学生 ID 为 6 的记录的代码如下: try { CStudentInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); m_pRecordset->Open("SELECT * FROM STUDENT_INFO_TAB where STUDENT_ID = 6", _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //绑定记录集 TESTHR (picRs->BindToRecordset(&rs)); //更新记录值 rs.m_n_old = 31; memcpy(rs.m_ch_name,"李文峰",24); //调用 IADORecordBinding 接口的 Update 方法更新数据 TESTHR (picRs->Update(&rs)); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } 代码运行之后,学生 ID 为 6 的姓名就改为“李文峰”,年龄为 31。 2.5 ActiveX 数据绑定控件开发技术 ActiveX 数据绑定控件开发技术使用了两种 ActiveX 控件,一种是数据控件,一种是 数据绑定控件。 — 58 — 1. 数据控件 数据库控件负责数据库的连接和记录集的获取。Visual C++ 提供了两种数据控件技 术:ADO 数据控件和 RDO 数据控件。ADO 是 OLE DB 的一个封装,而 RDO 是 ODBC 的 一个封装。 2. 数据绑定控件 数据绑定控件负责显示数据。每一个数据绑定控件可以绑定一个数据控件,从而显示 该数据控件中的数据。ADO 数据绑定控件有很多,包括 Microsoft DataCombo、Microsoft DataGrid、Microsoft DataList、Microsoft DataRepeater、Microsoft Hierarchical FlexGrid、 Microsoft Chart,其中 Microsoft DataCombo、Microsoft DataGrid、Microsoft DataList、 Microsoft Chart 是常用的控件。这些数据绑定控件提供了多种数据显示方式,既丰富了程 序的界面,又增强了程序的功能。 下面将利用应用程序向导(AppWizard)创建一个对话框程序,读者将从中了解 ADO 数 据控件和 ADO 数据绑定控件的基本使用方法。 2.5.1 创建一个对话框程序 运行 Visual C++,选择 File|New 命令,弹出 New 对话框。从 Projects 列表中选择 MFC AppWizard(exe)向导,在 Location 文本框中选择项目工程的目录“D:\VCSAMPLE\”,在 Project name 文本框中输入工程文件的名称 ActiveXShow,单击 OK 按钮,进入 MFC AppWizard-Step 1 页面,从应用程序的类型中选择 Dialog based 选项,并选择语言“中文(中 国)(APPWZCHS.DLL)”。单击 Next 按钮,进入 MFC AppWizard-Step 2 of 4 页面,选择 Automation 复选框,如图 2-20 所示。 图 2-20 ActiveXShow 向导生成对话框 单击 Finish 按钮,ActiveXShow 对话框应用程序创建完毕。 — 59 — 2.5.2 添加 ADO 数据控件 在 工 作 视 图 中 选 择 ResourceView 选 项 卡 , 选 择 Dialog 下 的 IDD_ACTIVEXSHOW_DIALOG 主对话框模板,修改标题为“利用 ActiveX 控件显示学生 信息”。如图 2-21 所示,在对话框模板的空白处,右击,选择 Insert ActiveX Control 命令, 弹出 Insert ActiveX Control 对话框,从 ActiveX Control 列表中选择 Microsoft ADO Data Control 6.0 选项,如图 2-22 所示。 图 2-21 添加 ActiveX 控件对话框 图 2-22 添加 ADO 数据控件对话框 单击 OK 按钮,会在对话框模板上显示新添加的 ADO 数据控件,控件 ID 为 IDC_ADODC1。打开数据控件 IDC_ADODC1 的属性框,选择 Control 选项卡,选择 Source of Connection(数据源连接方式)选项组中的 Use Connection String 单选按钮,如图 2-23 所示。 — 60 — 图 2-23 ADO 数据控件 Control 属性对话框 单击 Build 按钮,弹出“数据链接属性”对话框。从希望连接的数据列表中选择 Oracle Provider for OLE DB,如图 2-24 所示。 图 2-24 选择数据链接提供者对话框 单击“下一步”按钮,进入“数据链接属性”对话框的“连接”选项卡,在“数据源” 文本框中输入 ORADB,在“用户名称”文本框中输入 db1,在“密码”文本框中输入 db1, 选择“允许保存密码”选项,如图 2-25 所示。 单击“测试连接”按钮,可以检验数据库连接字符串配置是否正确。如果弹出“测试 连接成功”的信息提示框,表明配置正确。 打开 ADO 数据控件 IDC_ADODC1 的属性框,选择 RecordSource 选项卡,在 Command Type 列表框中选择 2-adCmdTable,在 Table or Stored Procedure Name 列表框中选择表名 STUDENT_INFO_TAB,如图 2-26 所示。 — 61 — 图 2-25 设置连接属性对话框 图 2-26 设置 RecordSource 选项卡 也可以在 Command Type 列表框中选择 1 - adCmdText,在 Command Text 文本框中输 入“select * from STUDENT_INFO_TAB”,如图 2-27 所示。 图 2-27 设置 RecordSource 选项卡 — 62 — 这两种方式是等价的。为了使 ADO 数据绑定控件在用到字段名称的时候,避免英文 字段名称,可以给表 STUDENT_INFO_TAB 的每一个字段名称取一个中文别名,如输入打 开所有记录的 SQL 语句“select STUDENT_IDAS 学生 ID,STUDENT_NAME AS 姓名, STUDENT_SEX AS 性别,STUDENT_OLD AS 年龄 from STUDENT_INFO_TAB”,如图 2-28 所示。 图 2-28 设置 RecordSource 选项卡 也可以利用数据绑定控件的函数设置显示列的名称,将在第 9 章中的实例中详细介绍 这种方法。数据绑定控件不需要显示出来,因此清除 IDC_ADODC1 列表框中 General 选项 卡下的 Visible 复选框,如图 2-29 所示。 图 2-29 General 选项卡 2.5.3 添加 Microsoft DataGrid 控件 Microsoft DataGrid 常用来显示一个数据表的字段信息。如图 2-30 所示,从 ActiveX Control 列表中选择 Microsoft DataGrid Control 6.0 选项。 — 63 — 图 2-30 添加 DataGrid 控件对话框 单击 OK 按钮,DataGrid 控件添加到对话框模板上,控件 ID 为 IDC_DATAGRID1。打 开 IDC_DATAGRID1 列表框,选择 All 选项卡,单击 DataSource 选项框,从列表中选择 IDC_ADODC1,在 Caption 文本框中输入“学生信息”,如图 2-31 所示。 图 2-31 设置 DataGrid 控件 All 属性对话框 编译并运行程序,DataGrid 控件列出所有的学生信息记录,如图 2-32 所示。 图 2-32 DataGrid 控件显示所有数据对话框 2.5.4 添加 Microsoft DataCombo 控件 Microsoft DataCombo 和普通列表框控件的样式很相似,可以用来显示数据表的某一个 — 64 — 字段的所有数据。如图 2-33 所示,从 ActiveX Control 列表中选择 Microsoft DataCombo Control 选项。 图 2-33 添加 DataCombo 控件对话框 单击 OK 按钮,DataCombo 控件添加到对话框模板上,控件 ID 为 IDC_DBCOMBO1, 要利用这个数据绑定控件显示数据,也需要指定一个 ADO 数据控件。可以按照 2.5.2 节中 添加 ADO 数据控件的方法再添加一个 ADO 数据控件,控件 ID 为 IDC_ADODC2。打开 ADO 数据控件 IDC_ADODC2 的属性框,选择 RecordSource 选项卡,在 Command Type 列 表框中选择 2–adCmdTable 选项,在 Table or Stored Procedure Name 列表框中选择表名 STUDENT_INFO_TAB。 打开 IDC_DBCOMBO1 属性框,选项 All 选项卡,单击 DataSource 选项框,从列表中 选择 IDC_ADODC2,在 DataField 列表框中选择 STUDENT_NAME 选项,如图 2-34 所示。 图 2-34 设置 DataCombo 的 All 属性对话框 在 RowSource 列 表 框 中 选 择 IDC_ADODC2 选 项 , 在 ListField 列 表 框 中 选 择 STUDENT_NAME 选项,如图 2-35 所示。 — 65 — 图 2-35 设置 DataCombo 的 All 属性对话框 编译并运行程序,可以在 DataCombo 控件中显示学生的所有姓名信息,如图 2-36 所 示。 图 2-36 DataCombo 显示数据对话框 2.5.5 添加 Microsoft DataList 控件 Microsoft DataList 和普通列表框控件的样式很相似,也可以用来显示数据表的某一个 字段的所有数据。如图 2-37 所示,从 ActiveX Control 列表中选择 Microsoft DataList Control 选项。 图 2-37 添加 DataList 控件对话框 — 66 — 单击 OK 按钮,DataList 控件添加到对话框模板上,控件 ID 为 IDC_DATALIST1,为 了利用这个数据绑定控件显示数据,需要指定一个 ADO 数据控件,也可以利用 ADO 数据 控件 IDC_ADODC2。并设置 IDC_DATALIST1 的 DataSource、DataField、RowSource 和 ListField 值,使其和 DataCombo 控件 IDC_DBCOMBO1 拥有相同的参数值,DataList 控件 显示所有学生的姓名,编译并运行程序,如图 2-38 所示。 图 2-38 DataList 控件显示数据对话框 2.5.6 添加 Microsoft Chart 控件 Microsoft Chart 控件适合于显示统计数据。如图 2-39 所示,从 ActiveX Control 列表中 选择 Microsoft Chart Control 6.0 选项。 图 2-39 添加 Chart 控件对话框 单击OK 按钮,Microsoft Chart 控件添加到对话框模板上,控件 ID为IDC_ MSCHART1, 为了利用这个数据绑定控件显示数据,也需要指定一个 ADO 数据控件。按照 2.5.2 节中添 加 ADO 数据控件的方法再添加一个 ADO 数据控件,控件 ID 为 IDC_ADODC3。打开数据 控件 IDC_ADODC3 的属性框,选择 RecordSource 选项卡,在 Command Type 列表框中选 择 1 – adCmdText 选项,在 Command Text 文本框中输入获取年龄统计数据的 SQL 语句 “ select min(student_old) as 最 小 年 龄 , max(student_old) as 最 大 年 龄 , ROUND(avg(student_old),2) as 平均年龄 from STUDENT_INFO_TAB”。其中利用 ROUND 函数是为了四舍五入使平均年龄保留两位小数,如图 2-40 所示。 — 67 — 图 2-40 设置 ADO 控件 RecordSource 选项卡对话框 打开 IDC_ MSCHART1 的属性框,选择 All 选项卡,单击 DataSource 选项框,从列表 中选择 IDC_ADODC3,如图 2-41 所示。 图 2-41 设置 Chart 控件 All 属性对话框 在 RowCount 文本框中输入 1,如图 2-42 所示。 图 2-42 设置 Chart 控件 All 属性对话框 Microsoft Chart 控件在设置这些参数之后的界面模式如图 2-43 所示。 — 68 — 图 2-43 Chart 控件显示方式页面 编译并运行程序,显示年龄的统计信息,如图 2-44 所示。 图 2-44 Chart 控件显示数据页面 2.6 OO4O 数据库开发技术 OO4O(Oracle objects for OLE)是 Oracle 开发的基于 COM 接口的技术,是在 Oracle Call Interface(OCI)基础上编写的,直接访问 Oracle 数据库。正因为它是由 Oracle 提供的开发库, OO4O 能和 Oracle 数据库无缝结合,获取最优化的性能,以最好的性能访问 Oracle 数据库。 使用 OO4O 创建的应用程序在使用其他数据库的时候会不兼容,但是它却能直接地访问 Oracle 数据库,而且能够更广地访问 Oracle 数据库。 2.6.1 OO4O 主要类介绍 OO4O 提供了丰富的类库,为我们开发数据库项目提供了很多方便。常用的类有:数 据库类 ODatabase、记录集类 ODynaset 和异常类 OException,其中:ODatabase 类和 MFC ODBC 中的 CDatabase 数据库类非常相似。ODatabase 类对象建立了和数据库数据源的一个 连接,通过它可以对数据库进行操作。如调用它的 ExecuteSQL 方法来对数据库中的记录 进行添加、修改和删除的操作。 ODynaset 类提供了对数据库记录集的操作,和 MFC ODBC 中的 CRecordset 记录集对 象非常相似,用来访问记录集。利用记录集对象还可以实现记录集的添加、修改和删除的 — 69 — 操作。 OException 类对象处理 OO4O 类在操作数据库的时候发生的异常,这些异常一定要进 行处理;否则程序会不正常地中止,给用户带来不便甚至带来巨大的损失。 2.6.2 引入库文件 在使用 OO4O 的时候,要确保 OO4O 的版本,最好与 Oracle 数据库的版本一致。在安 装数据库的时候,OO4O 可能会没有安装,或者安装的版本比较低,为了更好地发挥 OO4O 的 性 能 , 可 以 到 Oracle 的 官 方 网 站 下 载 OO4O 新 的 版 本 。 地 址 为 http://otn.oracle.com/software/tech/windows/ole/index.html,本书使用的版本是 OO4O for Oracle9i Version 9.0.1.4.3,可以下载其中的OO4O90143.exe程序,解压并安装,OO4O 在安 装的时候会自动卸载以前的版本。 利用 OO4O 进行数据库开发的时候,需要引入 OO4O 类库。包含下列 3 个文件,它们 在 Oracle 服务端上的路径分别为: OOTO 的 C++ 类 定 义 文 件 oracl.h 路 径 : %oraclehome% \oracle\ora90\oo4o\CPP\INCLUDE 静态库 ORACLM32.LIB 路径:%oraclehome% \ora90\oo4o\CPP\LIB 动态库 oraclm32.dll 路径:%oraclehome %\ora90\BIN 其中“%oraclehome%”代表 Oracle 数据库的安装目录。找到这些文件之后,将 oracl.h 和 ORACLM32.LIB 文件拷贝到项目工程目录下,而把 oraclm32.dll 文件拷贝到应用程序 EXE 文件所在的目录下,并在项目工程中利用 include 语句引入 oracl.h 文件,oracl.h 包含 了 OO4O 所有的 C++类定义文件,格式为: #include "oracl.h" 在项目工程的环境设置中添加静态库 ORACLM32.LIB。 2.6.3 数据库的连接 正像前面各种数据库开发技术都需要建立一个数据库的连接一样,OO4O 也不例外, 利用 ODatabase 类的 Open 方法建立数据库的连接 首先定义一个 ODatabase 类的实例,代码如下: ODatabase m_odb; 调用 ODatabase 对象的 Open 方法创建数据库的连接,Open 函数的原型如下: oresult Open(const char *dbname, const char *username, const char *pwd, long options = ODATABASE_DEFAULT); 其中 dbname 是本地服务名,username 是访问数据库的用户名,pwd 是数据库用户的 密码,Options 为可选参数,取默认值。 连接本地服务名 ORADB,数据库用户名和密码都为 DB1 的数据库的代码如下: //判断数据库是否已经打开 if (!m_odb.IsOpen() ) — 70 — { try { //初始化库 OStartup(); //打开数据库 m_odb.Open("oradb", "db1", "db1" ); } catch(OException E) { AfxMessageBox(E.GetErrorText()); } } 首先判断数据库是否已经打开,如果打开了,就不做处理。在使用 OO4O 的时候,首 次进行数据库操作之前,要调用 OStartup 函数来加载 OO4O 的运行库(只需调用一次)。而 在项目工程结束的时候调用 OShutDown 退出 OO4O 运行库。代码中使用了 try 和 catch 来 处理数据库打开的时候可能出现的 OException 异常。 2.6.4 查询记录 ODynaset 类提供了从数据源中提取记录集,以及对这些记录集进行操作的方法。如果 查询的记录是多条的,可以利用 CRecordset 类的 MoveTo,MoveNext,MovePrev,MoveFirst 和 MoveLast 方法移动记录到指定的位置。 在操作记录集之前,需定义一个 ODynaset 类对象,并传入 ODatabase 对象和要打开的 SQL 语句,代码如下: CString sql = "select * from student_info_tab"; ODynaset rs(m_odb, sql); ODynaset 类对象会根据传入的数据库连接和 SQL 语句打开记录集,利用 IsEOF 函数 判断记录集是否到达末尾。如果没有,利用 ODynaset 类的 GetFieldValue 函数获取字段值, 只需要传入字段的名称或者字段的序号(从零开始)。查询表 STUDENT_INFO_TAB 中所有 记录的代码如下: try { CString sql = "select * from student_info_tab"; ODynaset rs(m_odb, sql); //遍历记录 while(!rs.IsEOF()){ int id,sex,old; OValue name; rs.GetFieldValue(0,&id); rs.GetFieldValue("STUDENT_NAME",&name); //OValue 自动转换 CString str = name; rs.GetFieldValue(2,&sex); rs.GetFieldValue(3,&old); — 71 — rs.MoveNext(); } } catch(OException E) { AfxMessageBox(E.GetErrorText()); } OValue 是一个 Oracle 数据类,提供了方便的数据转换功能。例如要获取学生姓名 STUDENT_NAME 字段值,可以先定义一个 OValue 变量 name,并调用 GetFieldValue 函数 获取 name 值,然后再定义一个 CString 类型的变量等于这个 OValue 变量 name,从而自动 地转化为字符串格式的值。 2.6.5 添加记录 利用 OO4O 添加记录有两种方法,一种是利用数据库类 ODatabase,另一种是利用记 录集类 ODynaset。 利用 ODatabae 类的 ExecuteSQL 方法来添加记录,只需要传入添加记录的 SQL 语句。 例如要添加一条学生 ID 为 7,姓名为“林风”,性别为 1,年龄为 22 的记录的代码如下: try { CString sql = "Insert into STUDENT_INFO_TAB(STUDENT_ID," "STUDENT_NAME,STUDENT_SEX," "STUDENT_OLD) VALUES(7,’林风’,1,22)"; m_odb.ExecuteSQL(sql); } catch(OException E) { AfxMessageBox(E.GetErrorText()); } 利用 ODatabase 类非常简单,而利用记录集类 ODynaset 也很容易。当要添加记录的时 候,调用记录集类 ODynaset 的 AddNewRecord 方法告知数据库接下来的操作是给数据库添 加一条记录,然后调用与获取字段值的 GetFieldValue 函数对应的 SetFieldValue 函数设置记 录的字段值,最后调用记录集的 Update 方法将数据添加到数据库中。添加一条学生 ID 为 8,姓名为“黄明”,性别为 1,年龄为 36 的记录的代码如下: try { CString sql = "select * from student_info_tab"; ODynaset rs(m_odb, sql); if(!rs.IsOpen()) return; //添加新记录 if(OSUCCESS!=rs.AddNewRecord()) return; rs.SetFieldValue(0,8); rs.SetFieldValue(1,"黄明"); — 72 — rs.SetFieldValue(2,1); rs.SetFieldValue(3,36); //更新到数据库中 rs.Update(); } catch(OException E) { AfxMessageBox(E.GetErrorText()); } 运行上述的两段代码,将在数据库中添加两条学生的记录。 2.6.6 修改记录 利用 OO4O 修改记录也有两种方法,一种是利用数据库类 ODatabase,另一种是利用 记录集类 ODynaset 类。利用 ODatabae 类的 ExecuteSQL 方法来修改记录比较简单,只需 传入修改数据库记录的 SQL 语句。 利用记录集类 ODynaset 类也很容易。调用记录集类 ODynaset 的 StartEdit 方法告知数 据库接下来的操作是要修改记录,同时调用 SetFieldValue 函数设置要修改的字段值,最后 调用记录集的 Update 方法将数据添加到数据库中。如对数据库表 STUDENT_INFO_TAB 中 STUDENT_ID 为 8 的记录进行修改,代码如下: try { CString sql = "select * from student_info_tab WHERE STUDENT_ID = 8 "; ODynaset rs(m_odb, sql); if(!rs.IsOpen()) return; //修改 STUDENT_ID 为 8 的记录 if(OSUCCESS!=rs.StartEdit()) return; rs.SetFieldValue(1,"刘燕"); rs.SetFieldValue(2,0); rs.SetFieldValue(3,26); //更新到数据库中 rs.Update(); } catch(OException E) { AfxMessageBox(E.GetErrorText()); } 运行这段代码将修改数据库表 STUDENT_INFO_TAB 中 STUDENT_ID 为 8 的记录, 使之姓名为“刘燕”,性别为 0,年龄为 26。 2.6.7 删除记录 利用 OO4O 删除记录也有两种方法,一种是利用数据库类 ODatabase,另一种是利用 记录集类 ODynaset。利用 ODatabae 类的 ExecuteSQL 方法删除记录比较简单,只需传入删 — 73 — 除数据库记录的 SQL 语句。 利用记录集类 ODynaset 类也很容易。调用记录集类 ODynaset 的 DeleteRecord 方法删 除记录。如删除数据库表 STUDENT_INFO_TAB 中 STUDENT_ID 为 8 的记录的代码如下: try { CString sql = "select * from student_info_tab WHERE STUDENT_ID = 8 "; ODynaset rs(m_odb, sql); if(!rs.IsOpen()) return; //删除记录 rs.DeleteRecord(); } catch(OException E) { AfxMessageBox(E.GetErrorText()); } 2.7 本章小结 本章首先介绍了 Visual C++开发数据库的优势和这些开发技术的种类,读者可以发现 利用 Visual C++开发数据库是非常灵活的,有很多种开发方法可供选择。接着详细介绍了 每一种数据库开发技术,以及它们操作数据库的基本方法,包括对数据库记录的查询、添 加、修改和删除的操作。本章给出了每一种数据库基本操作的多种处理方法,读者可以在 这些方法的对比学习中,快速了解 Visual C++的各种数据库开发技术,为读者在后续章节 的实例开发中打下良好的基础。例如仅采用记录集类实现添加记录的操作就有 4 种方法: MFC ODBC 中 CRecordset 类的 AddNew 方法、ADO 中 RecordSet 的 AddNew 方法、ADO 数据绑定技术中 IADORecordBinding 接口的 AddNew 方法以及 OO4O 中 ODynaset 类的 AddNewRecord 方法。而且还可以利用 MFC ODBC 中 CDatabase 类的 ExecuteSQL 方法、 ADO 中的连接对象(Connection Object)和命令对象(Command Object)的 Execute 方法,以及 OO4O 中的 ODatabase 类的 ExecuteSQL 方法,都能方便地添加记录。 本章还讲述了 ActiveX 数据绑定控件的一些基本使用方法,这些控件可以提高编程的 效率,增加程序的功能,读者会在第 9 章中学会更多的操作数据控件和数据绑定控件的方 法。 最后介绍了 OO4O 的数据库开发技术。OO4O 是 Oracle 提供的数据库开发库,能更好 地发挥 Oracle 的性能优势,不足之处,OO4O 创建的应用程序在使用其他数据库的时候会 不兼容。 第3章 家庭备忘录管理系统 如今,人们的生活丰富多彩,生活的节奏也加快了,却往往忽视了对生活有效的管理, 有时候还会出现乱做一团的情况,如错过好友的 Party、父母的生日、甚至更为重要的事情, 常常带来一些尴尬,更为重要的是,损害了个人的信誉度。如果我们对一些重要的事情做 一个备忘录,每天查看一下近期内有什么事情需要处理,例如,有个聚会要参加,或者是 一个好看的电视节目不想错过。家庭备忘录管理系统正好能满足这个需求,它用来管理家 庭所有成员的备忘录,方便每一个家庭成员有计划地安排生活,是使我们的生活更有效率 的一个工具。而且这个管理系统能够记载一个家庭成员一生中重要的事情。通过此管理系 统讲述,读者将开始熟悉利用 Visual C++开发数据库系统的方法。 3.1 系统设计 系统设计是系统开发最为关键的一环,系统设计好了,后面的代码设计就不会偏离方 向。通过系统的设计,开发人员能够更好地把握系统的需求,了解系统的各功能模块。 3.1.1 功能描述 家庭备忘录管理系统包括对家庭成员基本信息、备忘录配置信息、备忘录信息,以及 备忘录信息查询的管理,详细功能描述如下。 1. 家庭成员基本信息管理 家庭成员基本信息管理包括添加、删除、修改家庭成员的基本信息。家庭成员基本信 息包括姓名、出生日期等信息。 2. 备忘录配置信息管理 备忘录配置信息管理包括添加、修改和删除备忘录配置信息。备忘录配置信息包括备 忘录的类型信息。 3. 备忘录信息管理 备忘录信息管理包括添加、修改和删除备忘录信息。备忘录信息包括录入备忘录的时 间、备忘信息发生的时间、备忘信息类别、是否大事件和备注内容等信息。 4. 备忘录信息查询 备忘录信息查询包括生日提示、备忘录提示和大事记 3 个部分。生日提示显示家庭成 员的出生日期、年龄以及生日到来或过去的天数。备忘录提示既可以查询某一天会发生的 备忘录,也可以查询几天内要发生的备忘录,方便用户更好地规划自己的生活。大事记列 出了家庭成员一生中所有重要事件的记录,就如同一个人的简历一样,也可以列出家庭所 有成员的大事记。 — 76 — 3.1.2 功能模块设计 从上面的功能描述中,可以把家庭备忘录管理系统分为 4 个模块:家庭成员基本信息 管理、备忘录配置信息管理、备忘录信息管理和备忘录信息查询管理。在每一个模块下又 提供更为具体的功能。详细的家庭备忘录管理系统的功能模块图,如图 3-1 所示。 图 3-1 系统功能模块图 3.2 数据库设计与实现 数据库设计是系统开发中非常重要的一个环节。数据库结构设计的好坏将直接影响到 系统的效率和功能的实现。在设计数据库之前,要了解数据库的需求,从而确定数据库的 结构。否则如果在代码实现过程中再修改数据库的结构,将会带来巨大人力和物力的浪费。 3.2.1 数据库需求设计 通过对系统功能的分析,家庭备忘录管理系统需要包含以下数据库信息。 1. 家庭成员基本信息 包括成员 ID、姓名、出生日期。 2. 备忘录类型信息 包括备忘录类型 ID、备忘录名称。 3. 备忘录信息 包括备忘录 ID、备注家庭成员 ID、备忘录操作日期、备忘录发生日期、是否重大事 件、备忘录类型 ID 和备忘录内容。 3.2.2 数据库表的设计 通过数据库的需求,可以创建以下数据表。 — 77 — 1. 家庭成员基本信息表(family_member_tab) 家庭成员基本信息表包含了家庭成员的基本信息,家庭成员基本信息管理实际上是对 家庭成员基本信息表的管理。表的结构见表 3-1。 表 3-1 家庭成员基本信息表 字段名称 数据类型 可否为空 约束条件 说 明 member_id INTEGER NOT NULL 主键 成员 ID member_name VARCHAR2(24) NOT NULL 无 姓名 birth_date DATE NOT NULL 无 出生日期 2. 备忘录类型信息表(memo_type_tab) 备忘录类型信息表包含了备忘录类型信息,备忘录配置信息的管理实际上是对备忘录 类型信息表的管理。表的结构见表 3-2。 表 3-2 备忘录类型信息表 字段名称 数据类型 可否为空 约束条件 说 明 type_id INTEGER NOT NULL 主键 备注类型 ID type_name VARCHAR2(24) NOT NULL 无 备注类型名称 3. 备忘录信息表(memo_info_tab) 备忘录信息表包含了备忘录的信息,备忘录信息管理和备忘录信息查询都需要用到这 个表。表的结构见表 3-3。 表 3-3 备忘录信息表 字段名称 数据类型 可否为空 约束条件 说 明 memo_id INTEGER NOT NULL 主键 备注信息 ID,ID 值从系列中获取 member_id INTEGER NOT NULL 外键(family_member_tab) 成员 ID oper_date DATE NOT NULL 无 操作备忘录信息日期 happen_date DATE NOT NULL 无 备忘录发生日期 is_bigevent INTEGER NOT NULL 无 是否为重大时间,1 代表大事件 type_id INTEGER NOT NULL 外键(memo_type_tab) 备注类型 ID memo_text VARCHAR2(1000) NULL 无 备注信息 3.2.3 数据库表的创建 在创建数据库表之前需要创建一个数据库用户,而且还得先创建一个表空间。按照第 1 章讲述的方法可以创建一个 dbmemo 表空间和 dbmemo 数据库用户,用户密码也为 dbmemo。也可以按照下面的方法快速创建数据库表空间和用户。 打开企业管理控制台 Enterprise Manager Console,选择“独立启动”方式。双击“数 据库”树下的 ORADB 数据库树节点,弹出“数据库连接信息”对话框,在“用户名”文 本框中输入 system、在“密码”文本框中输入 james,选择连接身份 Normal,如图 3-2 所 示。 — 78 — 图 3-2 “数据库连接信息”对话框 单击“确定”按钮,连接 ORADB 数据库。选择“网络”|“数据库”|ORADB|“存储” |“表空间”树节点,在右侧页面列出 Oracle 数据库所有的表空间信息,包括第 1 章创建的 DB1 空间,以及它们的空间占用率,可以看出 DB1 表空间的占有率已经变为 1.88%左右, 说明第 2 章对 db1 的数据表 STUDENT_INFO_TAB 操作之后,表空间里已经有数据。 选择 DB1 表空间,右击,选择“类似创建”命令,弹出“创建表空间”窗口。在“名 称”文本框中输 DBMEMO,在数据文件列表中修改文件名称为 DBMEMO.ora。单击“创 建”按钮,弹出“表空间 创建成功”的信息提示框,单击“确定”按钮之后,表空间 DBMEMO 创建成功。比较一下表空间 DB1 和 DBMEMO 的参数,它们的参数设置是相同的,这就是 “类似创建”方法的好处,如图 3-3 所示。 图 3-3 DBMEMO 表空间窗口 — 79 — 同样可以按照“类似创建”的方法创建 DBMEMO 用户。选择企业管理控制台的“网 络”|“数据库”|ORADB|“安全性”|“用户”|DB1 树节点,右击,在弹出的快捷菜单中 选择“类似创建”命令,弹出“创建用户”对话框。在用户“名称”文本框中输入 DBMEMO, 在“输入口令”文本框中输入 DBMEMO,在“确认口令”文本框中输入 DBMEMO,在“默 认值”表空间列表中选择表空间 DBMEMO,如图 3-4 所示。 图 3-4 一般信息对话框 单击“创建”按钮,弹出“用户创建成功”的信息提示框。单击“确定”按钮, DBMEMO 用户也创建成功。同样,DBMEMO 用户和 DB1 用户具有相同的角色和系统权限等参数。 表空间和用户创建成功之后就可以创建数据表,创建家庭备忘录管理系统的所有数据 表的 SQL 语句如下: --创建家庭成员基本信息表 CREATE TABLE family_member_tab( member_id INTEGER NOT NULL, member_name VARCHAR(24) NOT NULL, birth_date DATE NOT NULL ); --添加成员 ID 主键 ALTER TABLE family_member_tab ADD ( PRIMARY KEY (member_id) ) ; --创建备忘录类型信息表 CREATE TABLE memo_type_tab( type_id INTEGER NOT NULL, type_name VARCHAR(24) NOT NULL ); — 80 — --添加类型 ID 主键 ALTER TABLE memo_type_tab ADD ( PRIMARY KEY (type_id) ) ; --创建备忘录类型信息表 CREATE TABLE memo_info_tab( memo_id INTEGER NOT NULL, member_id INTEGER NOT NULL, oper_date DATE NOT NULL, happen_date DATE NOT NULL, is_bigevent INTEGER NOT NULL CHECK (is_bigevent IN (0, 1)), type_id INTEGER NOT NULL, memo_text VARCHAR(1000) NULL ); --添加备注信息 ID 主键 ALTER TABLE memo_info_tab ADD ( PRIMARY KEY (memo_id) ) ; --添加成员 ID 外键 ALTER TABLE memo_info_tab ADD ( FOREIGN KEY (member_id) REFERENCES family_member_tab ) ; --添加类型 ID 外键 ALTER TABLE memo_info_tab ADD ( FOREIGN KEY (type_id) REFERENCES memo_type_tab ) ; --添加发生日期的索引 CREATE INDEX memo_info_tab_dateindex ON memo_info_tab(happen_date); --创建可以递增的系列号供 memo_id 使用 CREATE SEQUENCE seq_memo_id INCREMENT BY 1 START WITH 10000 NOMAXVALUE NOMINVALUE NOCYCLE; 打开 Oracle SQLPlus Worksheet,进入“Oracle Enterprise Manager 登录”对话框,在 “用户名”文本框中输入 dbmemo,在“口令”文本框中输入 dbmemo,在“服务”文本框 中输入 oradb,选择“连接身份”为 Normal,如图 3-5 所示。 图 3-5 “Oracle Enterprise Manager 登录”对话框 — 81 — 单击“确定”按钮,显示“已连接”日志信息。把上述的创建数据表的 SQL 语句拷贝 到页面的编辑区域内,然后选择“工作单”|“执行”命令,从日志信息中可以看到 SQL 语句的执行结果,如图 3-6 所示。 图 3-6 SQL 执行信息对话框 退出 Oracle SQLPlus Worksheet。在刚才打开的 Oracle 企业管理控制台中,选择“网络” |“数据库”|ORADB|“方案”|“表”|DBMEMO 树节点,可以看到添加的 3 个表 FAMILY_MEMBER_TAB、MEMO_INFO_TAB 和 MEMO_TYPE_TAB。双击表 MEMO_ INFO_TAB,选择右侧页面的“约束条件”选项卡,共列出 4 个约束条件,分别为:约束 名称 SYS_C002733,类型为 CHECK,代表“is_bigevent IN (0, 1)”,说明 is_bigevent 的字 段值只能为 0 和 1 值;约束名称 SYS_C002734,类型为 PRIMARY,是表 MEMO_INFO_TAB 的主键;约束名称 SYS_C002735,类型为 FOREIGN,是表 MEMO_INFO_TAB 的外键, 来 自 于 FAMILY_MEMBER_TAB , 即 MEMO_INFO_TAB 中 的 字 段 memo_id 要 和 FAMILY_MEMBER_TAB 中的字段 memo_id 一致;约束名称 SYS_C002736,类型为 FOREIGN, 是 表 MEMO_INFO_TAB 的 外 键 , 来 自 于 MEMO_TYPE_TAB, 即 MEMO_INFO_TAB 中的字段 type_id 要和 MEMO_TYPE_TAB 中的字段 type_id 一致,如 图 3-7 所示。 选择“网络”|“数据库”|ORADB|“方案”|“序列”|DBMEMO 树节点,可以看到 SEQ_MEMO_ID 系列,如图 3-8 所示。 — 82 — 图 3-7 DBMEMO 数据表的约束条件窗口 图 3-8 dbmemo 的系列窗口 3.3 系统的实现 完成系统功能模块的设计和数据库表的创建后,就可以创建一个家庭备忘录管理系统 — 83 — 了。 3.3.1 创建应用程序 运行 Visual C++,选择 File|New 命令,弹出 New 对话框。从 Projects 列表中选择 MFC AppWizard(exe)向导,在 Location 文本框中选择项目工程的目录“D:\VCSAMPLE\”,在 Project name 文本框中输入工程文件的名称 MemoDBS,如图 3-9 所示。 图 3-9 创建项目对话框 单击 OK 按钮,进入 MFC AppWizard – Step 1 页面,从应用程序的类型中选择 Dialog based 选项,从语言列表中选择“中文(中国)(APPWZCHS.DLL)”,单击 Finish 按钮, MemoDBS 对话框的应用程序创建完毕。 3.3.2 创建主对话框的界面 主对话框的布局如图 3-10 所示。其中包括“数据库的连接”、“家庭成员基本信息管 理”、“备忘录配置信息管理”、“备忘录信息管理”和“查询备忘录信息”4 个选项组。 1. 数据库的连接 控件类型、ID 及说明见表 3-4。 表 3-4 数据库的连接控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 数据库的连接 无 Label IDC_STATIC 数据库源名称 无 Edit Box IDC_EDIT_DBSOURCE 无 CString 类型变量 m_strDBSource Label IDC_STATIC 数据库用户名称 无 Edit Box IDC_EDIT_DBUSER 无 CString 类型变量 m_strDBUser Label IDC_STATIC 数据库用户密码 无 Edit Box IDC_EDIT_PASSWORD 无 CString 类型变量 m_strDBPassword Button IDC_BTN_DBCONNECT 连接数据库 函数 OnBtnDbconnect 处理数据库的连接 Button IDC_SYS_EXIT 退出 函数 OnSysExit()处理系统退出代码 — 84 — 图 3-10 系统主页面 2. 家庭成员基本信息管理 控件类型、ID 及说明见表 3-5。 表 3-5 基本信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 家庭成员基本信息管理 无 List Control IDC_LIST_FAMILY_MEMBER 无 列表控件类型变量 m_listMember Button IDC_BTN_ADD_MEMBER 添加 函数 OnBtnAddMember()添加家庭成员信息 Button IDC_BTN_MOD_MEMBER 修改 函数 OnBtnModMember()修改家庭成员信息 Button IDC_BTN_DEL_MEMBER 删除 函数 OnBtnDelMember()删除家庭成员信息 3. 备忘录配置信息管理 控件类型、ID 及说明见表 3-6。 表 3-6 控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 备忘录配置信息管理 无 List Control IDC_LIST_MEMO_TYPE 无 列表控件类型变量 m_listType Button IDC_BTN_ADD_TYPE 添加 函数 OnBtnAddType()添加备忘录类型信息 Button IDC_BTN_MOD_TYPE 修改 函数 OnBtnModType()修改备忘录类型信息 Button IDC_BTN_DEL_TYPE 删除 函数 OnBtnDelType()删除备忘录类型信息 4. 备忘录信息管理 控件类型、ID 及说明见表 3-7。 — 85 — 表 3-7 备忘录信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 备忘录信息管理 无 List Control IDC_LIST_MEMO_INFO 无 列表控件类型变量 m_listMemoInfo Button IDC_BTN_ADD_MEMO 添加 函数 OnBtnAddMemo()添加备忘录类型信息 Button IDC_BTN_MOD_MEMO 修改 函数 OnBtnModMemo()修改备忘录类型信息 Button IDC_BTN_DEL_MEMO 删除 函数 OnBtnDelMemo()删除备忘录类型信息 5. 查询备忘录信息 控件类型、ID 及说明见表 3-8。 表 3-8 查询备忘录信息控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 查询备忘录信息 无 Button IDC_BTN_QUERY_BIRTH 生日提示 函数 OnBtnQueryBirth()处理生日提示信息 Button IDC_BTN_MEMO_INFO 备忘录提示 函数 OnBtnMemoInfo()处理备忘录提示信息 Button IDC_BTN_MEMO_EVENT 大事记 函数 OnBtnMemoEvent()处理大事记信息 主对话框类名称为 CMemoDBSDlg,资源 ID 为 IDD_MEMODBS_DIALOG,对话框名 称为“家庭备忘录管理系统”。主界面用到了 3 个列表控件分别显示家庭成员基本信息、备 忘录类型信息和备忘录信息。需要为这 3 个列表控件添加显示的列,从而显示相应的数据 信息。为了代码设计的清晰,在 CMemoDBSDlg 类中定义了一个 InitControl 私有函数负责 添加控件的显示列,InitControl 函数的代码如下: void CMemoDBSDlg::InitControl() { //设置列表控件扩展风格 DWORD dwExStyle = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP | LVS_EX_ONECLICKACTIVATE | LVS_EX_UNDERLINEHOT; m_listMember.SetExtendedStyle(dwExStyle); m_listMemoInfo.SetExtendedStyle(dwExStyle); m_listType.SetExtendedStyle(dwExStyle); //初始化家庭成员列表控件 m_listMember.InsertColumn(0,"ID",LVCFMT_CENTER,60); m_listMember.InsertColumn(1,"姓名",LVCFMT_CENTER,100); m_listMember.InsertColumn(2,"出生日期",LVCFMT_CENTER,100); //初始化备注类型列表控件 m_listType.InsertColumn(0,"类型 ID",LVCFMT_CENTER,60); m_listType.InsertColumn(1,"类型名称",LVCFMT_CENTER,100); //初始化备注信息列表控件 m_listMemoInfo.InsertColumn(0,"备忘录 ID",LVCFMT_CENTER,60); m_listMemoInfo.InsertColumn(1,"姓名",LVCFMT_CENTER,60); m_listMemoInfo.InsertColumn(2,"备忘录操作日期",LVCFMT_CENTER,140); m_listMemoInfo.InsertColumn(3,"备忘录发生日期",LVCFMT_CENTER,140); m_listMemoInfo.InsertColumn(4,"大事件",LVCFMT_CENTER,60); m_listMemoInfo.InsertColumn(5,"备注类型",LVCFMT_CENTER,60); m_listMemoInfo.InsertColumn(6,"备注内容",LVCFMT_CENTER,200); } — 86 — 在 InitControl 函数中首先调用 CListCtrl 的 SetExtendedStyle 方法设置控件的扩展风格。 对这 3 个控件设置了相同的风格,以确保界面的一致性,设置扩展风格后的家庭成员列表 控件在获取鼠标选择和鼠标略过情况的样式如图 3-11 所示。 图 3-11 列表控件风格页面 更丰富的 CListCtrl 风格设置参数请详见 MSDN。调用 CListCtrl 的 InsertColumn 函数 来插入需要显示的列。在 OnInitDialog 函数末尾处添加 InitControl 函数的调用,这样系统 在启动的时候,就可以看到已添加显示列的列表控件。 3.3.3 显示数据到界面上 当系统启动之后,可以将数据库中的数据显示到界面上,需要处理数据库的连接和显 示数据到界面上的两个过程。 1. 数据库的连接 数据库的连接需要 3 个参数:ODBC 数据源名称、数据库用户名称和数据库用户密码。 其中 ODBC 数据源已经在第 2 章中配置了,其名称为 ORADB,数据库用户的名称和密码 均为 dbmemo,考虑到读者配置的数据源和用户名可能不一样,家庭备忘录管理系统提供 了在界面上配置这 3 个参数的功能,在“数据库的连接”中设置相应的数据库参数。在 CMemoDBSDlg 类的 OnBtnDbconnect 函数中处理数据库的连接,代码如下: void CMemoDBSDlg::OnBtnDbconnect() { //从界面控件中获取信息更新到控件变量中. if(!UpdateData()) return; //检查数据库是否已经连接,如果已连接,则返回. if(m_db.IsOpen()){ AfxMessageBox("数据库已经连接"); return; } //检查数据库配置参数. if(m_strDBSource.IsEmpty()||m_strDBUser.IsEmpty()||m_strDBPassword.IsEm pty()){ AfxMessageBox("数据库配置参数不能够为空"); return; } //创建连接字符串. — 87 — CString strConnect; strConnect.Format("DSN=%s;UID=%s;PWD=%s",m_strDBSource,m_strDBUser,m_st rDBPassword); //打开数据库的连接,并且捕获异常 TRY{ m_db.OpenEx(strConnect,CDatabase::noOdbcDialog); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH InitCtrlData(); } OnBtnDbconnect 函数首先判断数据库是否已经打开,如果已打开就退出函数;否则, 创建一个数据库的连接字符串,利用 CDatabase 的 OpenEx 方法打开数据库的连接,并处 理数据库的异常。 2. 显示数据到界面上 连接数据库之后,需要把数据库中的数据显示在 3 个列表控件中。在 CMemoDBSDlg 类中定义一个 InitCtrlData 私有函数,负责从数据库中读取数据并显示到列表控件中。同 时还需要定义 3 个分别把数据插入到列表控件中的函数,分别为:InsertMemberItem, InsertMemoTypeItem,InsertMemoInfoItem,在 MemoDBSDlg.h 文件中添加这 4 个函数的定 义,代码如下: private: //向家庭成员信息列表框中插入家庭成员列表信息 void InsertMemberItem(int id,CString name,CString date); //向备忘录类型信息列表框中插入类型信息. void InsertMemoTypeItem(int id,CString name); //向备忘录列表框中插入备忘录信息. void InsertMemoInfoItem(int id, CString name, CString operDate, CString happenDate, int isEvent, CString type, CString memoTxt); //从数据库中读取数据,并显示在.列表框中. void InitCtrlData(); — 88 — 向家庭成员信息列表控件中插入家庭成员信息的函数是 InsertMemberItem,代码如下: void CMemoDBSDlg::InsertMemberItem(int id, CString name, CString date) { //获取当前的显示条数 int nIndex = m_listMember.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //在 nIndex 一行插入数据. m_listMember.InsertItem(&lvItem); m_listMember.SetItemText(nIndex,1,name); m_listMember.SetItemText(nIndex,2,date); } InsertMemberItem 函数首先调用 CListCtrl 的 InsertItem 方法插入一个新行,并设置这 一新行第一列的数据,其他列的数据可以用 SetItemText 方法来设置,家庭成员基本信息列 表控件显示成员 ID、姓名和出生日期的信息。 向备忘录类型信息列表控件中添加备忘录类型信息的函数是 InsertMemoTypeItem,代 码如下: void CMemoDBSDlg::InsertMemoTypeItem(int id, CString name) { //获取当前的记录条数. int nIndex = m_listType.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //在最后一行插入备注类型数据. m_listType.InsertItem(&lvItem); m_listType.SetItemText(nIndex,1,name); } 向备忘录信息列表控件中添加备忘录信息的函数是 InsertMemoInfoItem,代码如下: void CMemoDBSDlg::InsertMemoInfoItem(int id, CString name, CString operDate, CString happenDate, int isEvent, CString type, CString memoTxt) { //获取当前的记录条数. int nIndex = m_listMemoInfo.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; — 89 — lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. m_listMemoInfo.InsertItem(&lvItem); //设置该行的其他列的值. m_listMemoInfo.SetItemText(nIndex,1,name); m_listMemoInfo.SetItemText(nIndex,2,operDate); m_listMemoInfo.SetItemText(nIndex,3,happenDate); m_listMemoInfo.SetItemText(nIndex,4,isEvent==0?"否":"是"); m_listMemoInfo.SetItemText(nIndex,5,type); m_listMemoInfo.SetItemText(nIndex,6,memoTxt); } 从数据库中读取数据并显示到 3 个列表控件中的函数为 InitCtrlData,代码如下: void CMemoDBSDlg::InitCtrlData() { TRY{ CRecordset rs; rs.m_pDatabase = &m_db; //向成员列表控件中添加成员记录信息 CString sql = "Select * from family_member_tab"; //获取所有的家庭成员记录集 rs.Open(CRecordset::dynaset, sql); int id; CString name,date; while (!rs.IsEOF()) { CDBVariant var; //获取成员 ID 字段值 rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) id = var.m_iVal; var.Clear(); //获取姓名字段值 rs.GetFieldValue(1, name); //获取出生日期字段值 rs.GetFieldValue(2, date); //把记录值插入到成员列表控件中. InsertMemberItem(id,name,date.Left(10)); rs.MoveNext(); } rs.Close(); //向备忘录类型列表控件中添加备忘录信息 sql = "Select * from MEMO_TYPE_TAB"; //获取所有的备忘录类型记录 rs.Open(CRecordset::dynaset, sql); while (!rs.IsEOF()) { CDBVariant var; — 90 — //获取备忘录类型 ID 字段值. rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) id = var.m_iVal; var.Clear(); //获取备忘录类型名称字段值 rs.GetFieldValue(1, name); //向备忘录类型列表控件中加入新的记录信息. InsertMemoTypeItem(id,name); rs.MoveNext(); } rs.Close(); //向备注列表控件中加入所有的备忘录信息 sql = "Select * from MEMO_INFO_TAB"; //打开所有的备忘录信息的记录. rs.Open(CRecordset::dynaset, sql); while (!rs.IsEOF()) { CDBVariant var; int memoID,memberID,isEvent,nType; CString happenDate,operDate,memoTxt,strName,strType; //获取备忘录 ID 字段值 rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) memoID = var.m_iVal; var.Clear(); //获取成员 ID 字段值 rs.GetFieldValue(1, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) memberID = var.m_iVal; var.Clear(); //获取操作日期字段值 rs.GetFieldValue(2, operDate); //获取发生日期字段值 rs.GetFieldValue(3, happenDate); var.Clear(); //获取是否大事记字段值 rs.GetFieldValue(4, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) isEvent = var.m_iVal; var.Clear(); //获取备忘录类型 ID 值. rs.GetFieldValue(5, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) nType = var.m_iVal; var.Clear(); //获取备忘录内容字段值. rs.GetFieldValue(6, memoTxt); CRecordset rs2(&m_db); //根据成员 ID 获取成员的姓名 CString temp; — 91 — temp.Format("Select MEMBER_NAME from FAMILY_MEMBER_TAB where " "MEMBER_ID = %d",memberID); rs2.Open(CRecordset::dynaset,temp); if(!rs2.IsEOF()) rs2.GetFieldValue((short)0, strName); rs2.Close(); //根据类型 ID 获取类型名称. temp.Format("Select TYPE_NAME from MEMO_TYPE_TAB where " "TYPE_ID = ’%d’",nType); rs2.Open(CRecordset::dynaset,temp); if(!rs2.IsEOF()) rs2.GetFieldValue((short)0, strType); rs2.Close(); //向列表控件中加入一条新的记录信息. InsertMemoInfoItem(memoID,strName,operDate,happenDate,isEvent,strType,m emoTxt); rs.MoveNext(); } } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } InitCtrlData 函数利用 CRecordset 对象打开记录集,首先打开所有家庭成员的记录集, 然后遍历记录集获取字段数据,并调用 InsertMemberItem 函数把数据插入到家庭成员信息 列表控件中。当访问完所有的记录之后,需要调用 CRecordset 类的 Close 方法关闭记录集, 从而可以继续利用这个 CRecordset 类实例打开其他的记录集。按照同样的方法获取所有的 备忘录类型信息和备忘录信息并显示在列表控件中。在处理备忘录信息的时候,由于只能 从备忘录信息表中获取成员 ID 和备注类型 ID,而备忘录信息列表控件需要显示成员姓名 和备忘录类型名称,所以还需要根据成员 ID 查询数据库获取成员的姓名,以及通过备注 类型 ID 获取备忘录类型名称。 把 InitCtrlData 函数放在 OnBtnDbconnect 函数的结尾处。这样,数据库在连接成功之 后,可以把数据显示到界面上。 — 92 — 在对话框中的“数据库源名称”文本框中输入 ODBC 数据源 ORADB,在“数据库用 户名称”文本框中输入用户名 dbmemo,在“数据库用户密码”文本框中输入用户密码 dbmemo,单击对话框上的“连接数据库”按钮,数据库连接成功之后,数据就显示到界面 上,如图 3-12 所示。 图 3-12 连接数据库之后的对话框 需要说明的是,图中显示的数据是一些测试数据,读者可以不用理会数据的具体含义。 3.3.4 家庭成员基本信息的管理 家庭成员基本信息的管理包括家庭成员基本信息的添加、修改和删除的功能。为了方 便添加和修改家庭成员的基本信息,可以创建一个对话框,对话框包含了对家庭成员基本 信息参数的设置,当要添加或者修改家庭成员基本信息的时候,只需要在弹出的家庭成员 信息对话框中设置家庭成员的数据。添加的对话框类名称为 CMemberDlg,资源 ID 为 IDD_DIALOG_MEMBER,对话框名称为“家庭成员信息”,界面如图 3-13 所示。 图 3-13 “家庭成员信息”对话框 — 93 — 控件类型、ID 及说明见表 3-9。 表 3-9 控件列表 控件类型 ID 属 性 变量或函数 Label IDC_STATIC 姓名 无 Edit Box IDC_EDIT_NAME 无 CString 类型变量 m_strName Label IDC_STATIC 生日 无 DateTime Picker IDC_DT_BIRTH 无 COleDateTime 类型变量 m_dtBirth Button 确定 确定 无 Button IDCANCEL 取消 无 3. 添加家庭成员信息 利用“家庭成员信息”对话框能够方便地实现家庭成员信息的添加操作。添加家庭成 员的函数为 OnBtnAddMember,代码如下: void CMemoDBSDlg::OnBtnAddMember() { //创建一个成员对话框实例 CMemberDlg dlg; //打开成员对话框 if(dlg.DoModal() == IDOK){ //从对话框中获取姓名和生日参数. CString strName = dlg.m_strName; CString strBirth = dlg.m_dtBirth.Format("%Y-%m-%d"); TRY{ //打开记录集,获取最大的成员 ID 值. CRecordset rs(&m_db); rs.Open(CRecordset::dynaset, "Select max(member_id) from family_member_tab"); //设置新添加记录的成员 ID 值. int newMemberID = 1; //如果数据库里面已经有记录了,则新的成员 ID 是成员 ID 最大值+1 if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) newMemberID = var.m_iVal + 1; } //创建插入新记录的字符串. CString sql ; sql.Format("Insert into family_member_tab(member_id," "member_name,birth_date) " "VALUES(" "%d,’%s’,to_date(’%s’,’yyyy-mm-dd’))",newMemberID,strName,strBirth); TRACE(sql); //把新记录插入到数据库中. m_db.ExecuteSQL(sql); //把新记录的信息显示在成员列表控件中. — 94 — InsertMemberItem(newMemberID,strName,strBirth); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } } 需要用到 CMemberDlg 对话框类,所以要在 MemoDBSDlg.cpp 文件的开始处引入 MemberDlg.h 文件,方法如下: #include "MemberDlg.h" OnBtnAddMember 函数首先创建家庭成员信息对话框 CMemberDlg 的一个实例,然后 调用 CDialog 的 DoModal 方法弹出“家庭成员信息”对话框,等待用户按下“确定”按钮, 如果用户按“取消”按钮,将退出 OnBtnAddMember 函数。如在“家庭成员信息”对话框 的“姓名”文本框中输入“李卫”,在“生日”日期控件中选择日期 1950-11-18,如图 3-14 所示。 图 3-14 添加家庭成员对话框 单击“确定”按钮,DoModal 返回 IDOK,就进入了将新添加的家庭成员信息插入数 据库并显示到界面上的代码处理中。通过访问“家庭成员信息”对话框实例的成员变量获 取姓名和生日信息,要添加这条新的家庭成员信息,需要一个新的成员 ID,可以从数据库 中获取最大的成员 ID 值,然后把这个 ID 值加 1 就是新的成员 ID 值。确定了新的成员 ID 之后,就可以构造插入新家庭成员记录的 SQL 语句,然后调用 CDataBase 的 ExecuteSQL 方法把新记录添加到数据库中,最后调用 InsertMemberItem 函数把新的家庭成员记录显示 到界面上。至此,一个完整的添加家庭成员信息的操作就完成了。在家庭成员信息列表控 件中可以看到已添加的家庭成员信息,如图 3-15 所示。 — 95 — 图 3-15 添加新成员对话框 4. 修改家庭成员信息 修改家庭成员的信息也需要用到“家庭成员信息”对话框,不同的是需要把要修改家 庭成员信息参数带入对话框中,修改家庭成员的函数为 OnBtnModMember,代码如下: void CMemoDBSDlg::OnBtnModMember() { //选择要修改的记录项. int nItem = m_listMember.GetNextItem(-1, LVNI_SELECTED); if(nItem == -1){ AfxMessageBox("没有选择要修改的成员"); return; } //获取第 nItem 列中的信息. int id = atoi(m_listMember.GetItemText(nItem,0)); CString name = m_listMember.GetItemText(nItem,1); CString strDate = m_listMember.GetItemText(nItem,2); //创建时间变量. COleDateTime dtTime(atoi(strDate.Left(4)),atoi(strDate.Mid(5,2)),atoi(strDate.Mid(8,2)), 0,0,0); //创建成员对话框实例. CMemberDlg dlg; //给成员对话框的实例的变量赋值. dlg.m_dtBirth = dtTime; dlg.m_strName = name; //打开成员对话框,进行修改. if(dlg.DoModal() == IDOK){ //获取修改后的参数. CString strName = dlg.m_strName; CString strBirth = dlg.m_dtBirth.Format("%Y-%m-%d"); TRY{ CString sql; //修改数据库相应的记录. sql.Format("update family_member_tab set member_name = ’%s’," "birth_date = to_date(’%s’,’yyyy-mm-dd’) " "where member_id = %d",strName,strBirth,id); TRACE(sql); m_db.ExecuteSQL(sql); //修改列表控件的值. — 96 — m_listMember.SetItemText(nItem,1,strName); m_listMember.SetItemText(nItem,2,strBirth); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } // TODO: Add your control notification handler code here } OnBtnModMember 函数首先从列表控件中获取要修改的信息,把这些信息赋给对话框 类 CMemberDlg 实例的成员变量,然后调用其 DoModal 方法,就可以在弹出的“家庭成员 信息”对话框中显示要修改的家庭成员信息,如在家庭成员基本信息列表控件中选择家庭 成员“李卫”的信息。然后单击“修改”按钮,弹出“家庭成员信息”对话框,显示“李 卫”的姓名和生日信息,如图 3-16 所示。 图 3-16 “家庭成员信息”对话框 在“生日”日期控件中选择日期 1950-11-18,单击“确定”按钮,进入将家庭成员信 息更新到数据库并修改界面数据的代码处理中。通过访问“家庭成员信息”对话框实例的 成员变量获取姓名和生日信息,要修改这条家庭成员的信息需要构造一个更新家庭成员记 录的 SQL 语句,并调用 ExecuteSQL 方法把数据更新到数据库中,最后修改界面的显示数 据。 5. 删除家庭成员信息 删除家庭成员的函数为 OnBtnDelMember,代码如下: void CMemoDBSDlg::OnBtnDelMember() { //获取要删除的成员记录所在的行. int nItem = m_listMember.GetNextItem(-1, LVNI_SELECTED); if(nItem == -1){ AfxMessageBox("没有选择要删除的成员"); — 97 — return; } //获取成员 ID. int id = atoi(m_listMember.GetItemText(nItem,0)); TRY{ CString sql; //从数据库中删除记录. sql.Format("DELETE family_member_tab " "where member_id = %d",id); TRACE(sql); m_db.ExecuteSQL(sql); //从列表控件中删除该记录. m_listMember.DeleteItem(nItem); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } 从家庭成员基本信息列表控件中选择需要删除的家庭成员信息,然后单击“删除”按 钮,就可以删除一个家庭成员的信息。在删除家庭成员的信息的代码处理中,首先获取要 删除家庭成员的 ID。然后从数据库中删除该成员 ID 对应的家庭成员记录,最后删除界面 上的家庭成员信息。 3.3.5 备忘录配置信息的管理 备忘录配置信息的管理包括备忘录类型信息的添加、修改和删除的功能。为了方便添 加和修改备忘录类型的信息,可以创建一个对话框,对话框包含对备忘录类型信息参数的 设置,当要添加或者修改备忘录类型信息的时候,只需要在弹出的“类型信息”对话框中 设置备忘录类型的数据。添加的对话框类名称为 CMemoTypeDlg , 资 源 ID 为 IDD_DIALOG_TYPE,对话框名称为“类型信息”,“类型信息”对话框如图 3-17 所示。 图 3-17 “类型信息”对话框 — 98 — “类型信息”对话框只需加入一个 Label 控件,名称为“请输入类型名称”,和一个编 辑框控件,ID 为 IDC_EDIT_TYPENAME,并添加这个编辑控件的 CString 类型变量 m_strTypeName。 1. 添加备忘录类型信息 添加备忘录类型信息的函数为 OnBtnAddType,代码如下: void CMemoDBSDlg::OnBtnAddType() { //初始化备注类型对话框实例. CMemoTypeDlg dlg; if(dlg.DoModal() == IDOK){ //从对话框中获取新添加的备注类型. CString strName = dlg.m_strTypeName; TRY{ CRecordset rs(&m_db); //获取最大的类型 ID 值. rs.Open(CRecordset::dynaset, "Select max(TYPE_ID) from MEMO_TYPE_TAB"); //初始化新类型 ID 值为 1. int newTypeID = 1; //如果数据库已经有类型记录,则新的类型 ID 为最大类型 ID+1 if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) newTypeID = var.m_iVal + 1; } CString sql; //向数据库中添加新的备忘录类型记录. sql.Format("Insert into MEMO_TYPE_TAB(TYPE_ID," "TYPE_NAME) " "VALUES(" "%d,’%s’)",newTypeID,strName); TRACE(sql); m_db.ExecuteSQL(sql); //向界面中添加记录行. InsertMemoTypeItem(newTypeID,strName); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } — 99 — END_CATCH } } 处理的方法和添加家庭成员基本信息相似。如图 3-18 所示,是添加一个类型名称为“电 视预告”之后的备忘录类型列表控件显示的信息。 图 3-18 备忘录配置信息页面 2. 修改备忘录类型信息 修改备忘录类型信息的函数为 OnBtnModType,代码如下: void CMemoDBSDlg::OnBtnModType() { int nItem = m_listType.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要修改的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要修改的类型"); return; } //获取要修改记录的 ID 和名称. int id = atoi(m_listType.GetItemText(nItem,0)); CString name = m_listType.GetItemText(nItem,1); //初始化备注类型对话框. CMemoTypeDlg dlg; //给对话框的变量赋值. dlg.m_strTypeName = name; if(dlg.DoModal() == IDOK){ //获取修改后的数据. CString strName = dlg.m_strTypeName; TRY{ CString sql; //更新记录. sql.Format("UPDATE MEMO_TYPE_TAB SET TYPE_NAME = ’%s’ " " WHERE TYPE_ID = %d",strName,id); TRACE(sql); m_db.ExecuteSQL(sql); //修改界面的值. m_listType.SetItemText(nItem,1,strName); } CATCH(CDBException,ex) — 100 — { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } } 处理方法和修改家庭成员基本信息相似。 3. 删除备忘录类型信息 删除备忘录类型信息的函数为 OnBtnDelType,代码和 OnBtnDelMember 函数相似,不 再列出。 3.3.6 备忘录信息的管理 备忘录信息的管理包括备忘录信息的添加、修改和删除的功能。为了方便添加和修改 备忘录的信息,可以创建一个对话框,对话框包含对备忘录信息参数的设置,当要添加或 者修改备忘录信息的时候,只需要在弹出的“备注信息”对话框中设置备忘录信息的数据。 添加的对话框类名称为 CMemoInfoDlg,资源 ID 为 IDD_DIALOG_MEMO,对话框名称为 “备注信息”,“备注信息”对话框如图 3-19 所示。 图 3-19 “备注信息”对话框 控件类型、ID 及说明见表 3-10。 — 101 — 表 3-10 控件列表 控件类型 ID 属 性 变量或函数 Label IDC_STATIC 选择备注成员 无 Combo Box IDC_COMBO_MEMBER 无 列表框控件变量 m_comboMember,字符串变量 m_strMember Label IDC_STATIC 选择备注类型 无 Combo Box IDC_COMBO_TYPE 无 列表框控件变量 m_comboType,字符串变量 m_strMemoType Label IDC_STATIC 备注发生日期 无 DateTime Picker IDC_DT_DATE 无 COleDateTime 类型变量 m_oleDate DateTime Picker IDC_DT_TIME 无 COleDateTime 类型变量 m_oleTime Check Box IDC_CHECK_EVENT 是大事件 BOOL 类型变量 m_isEvent Edit Box IDC_EDIT_MEMO 无 CString 类型变量 m_strMemoInfo Button 确定 确定 函数 OnOK()核查参数信息的合法性 Button IDCANCEL 取消 无 “备注信息”对话框的“选择备注成员”列表框列出了所有家庭成员的姓名,“选择 备注类型”列表框列出了所有的备忘录类型名称,这些信息可以直接从主界面上获取,省 去从数据库中获取数据的麻烦。在 CMemoInfoDlg 中定义两个字符串数组以保存所有的家 庭成员姓名和所有的备忘录类型名称,在 MemoInfoDlg.h 中添加这两个公有类型的字符串 数组,代码如下: //定义成员姓名字符串数组. CStringArray m_strMemberArray; //定义备注类型字符串数组. CStringArray m_strTypeArray; 需要在 CMemoInfoDlg 类中添加 OnInitDialog 的消息映射函数,可利用 ClassWizard 来 添加。选择 View|ClassWizard 命令,或者按 Ctrl+W 组合键,弹出 MFC ClassWizard 对话框, 选择 Message Maps 选项卡,从 Project 列表中选择 MemoDBS,从 Class Name 列表中选择 CMemoInfoDlg,从 Object IDs 列表中选择 CMemoInfoDlg,从 Messages 列表中选择 WM_INITDIALOG,然后单击 Add Function 按钮,添加 OnInitDialog 映射函数,最后单击 MFC ClassWizard 对话框中的 OK 按钮,退出 ClassWizard 对话框。在 OnInitDialog 函数中 添加初始化列表框控件数据的代码,代码如下: BOOL CMemoInfoDlg::OnInitDialog() { CDialog::OnInitDialog(); //在成员列表框控件中添加成员姓名数据. for(int i = 0 ; i < m_strMemberArray.GetSize(); i++) m_comboMember.AddString(m_strMemberArray.GetAt(i)); //如果是修改记录,则列表框的文本值为要修改的成员姓名. if(!m_strMember.IsEmpty()) m_comboMember.SetWindowText(m_strMember); //如果是添加记录,则列表框的文本值为列表框中成员列表中的第一个. else m_comboMember.SetCurSel(0); //向备忘录类型列表框控件中添加类型记录. for(i = 0 ; i < m_strTypeArray.GetSize(); i++) m_comboType.AddString(m_strTypeArray.GetAt(i)); //如果是修改备注信息记录,则列表框的文本值为要修改的备忘录类型. — 102 — if(!m_strMemoType.IsEmpty()) m_comboType.SetWindowText(m_strMemoType); //如果是添加记录,则列表框的文本值为列表框中成员列表中的第一个. else m_comboType.SetCurSel(0); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } 在 OnInitDialog 函数中,根据字符串数组的数据添加到列表框控件中。 CMemoInfoDlg 的 OnOK 函数负责检查备忘录类型和家庭成员姓名信息是否为空,代 码如下: void CMemoInfoDlg::OnOK() { if(!UpdateData()) return; //备忘录类型不能够为空 if(m_strMemoType.IsEmpty()){ AfxMessageBox("备忘录类型为空,请重新选择备忘录类型"); return; } //成员的姓名不能够为空 if(m_strMember.IsEmpty()){ AfxMessageBox("成员姓名为空,请重新选择成员姓名"); return; } CDialog::OnOK(); } 1. 添加备忘录信息 添加备忘录信息的函数为 OnBtnAddMemo,代码如下: void CMemoDBSDlg::OnBtnAddMemo() { //初始化备忘录信息对话框. CMemoInfoDlg dlg; //获取所有的家庭成员的名称. for(int i = 0 ; i < m_listMember.GetItemCount() ; i++ ) dlg.m_strMemberArray.Add(m_listMember.GetItemText(i,1)) ; //获取所有的类型信息. for(i = 0 ; i < m_listType.GetItemCount() ; i++ ) dlg.m_strTypeArray.Add(m_listType.GetItemText(i,1)) ; //打开对话框,添加新的记录. if(dlg.DoModal() == IDOK){ //从对话框中获取记录值. CString strName = dlg.m_strMember; CString strType = dlg.m_strMemoType; CString strHappenDate = dlg.m_oleDate.Format("%Y-%m-%d") + " " + dlg.m_oleTime.Format("%H:%M:%S"); — 103 — CString strOperDate = COleDateTime::GetCurrentTime().Format("%Y-%m-%d %H:%M:%S"); int isEvent = dlg.m_isEvent; CString strMemo = dlg.m_strMemoInfo; TRY{ CRecordset rs(&m_db); CString sql; //根据成员姓名获取成员 ID 值. sql.Format("Select MEMBER_ID from FAMILY_MEMBER_TAB where " "MEMBER_NAME = ’%s’",strName); rs.Open(CRecordset::dynaset,sql); int memberID = 1; if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) memberID = var.m_iVal; } rs.Close(); //根据备忘录类型获取备忘录类型 ID. sql.Format("Select TYPE_ID from MEMO_TYPE_TAB where " "TYPE_NAME = ’%s’",strType); rs.Open(CRecordset::dynaset,sql); int typeID = 1; if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) typeID = var.m_iVal; } rs.Close(); //从 SEQ_MEMO_ID 序列中获取下一个值.这个值就是新的备忘录 ID. rs.Open(CRecordset::snapshot, "Select SEQ_MEMO_ID.NEXTVAL from dual"); int memoID = 1000; if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) memoID = var.m_iVal; } //插入新记录. sql.Format("Insert into MEMO_INFO_TAB(MEMO_ID," "MEMBER_ID,OPER_DATE,HAPPEN_DATE," "IS_BIGEVENT,TYPE_ID,MEMO_TEXT) " "VALUES(" "%d,%d,to_date(’%s’,’yyyy-mm-dd hh24:mi:ss ’)" ",to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’),%d" ",%d,’%s’)",memoID,memberID, strOperDate,strHappenDate,isEvent,typeID,strMemo); — 104 — TRACE(sql); m_db.ExecuteSQL(sql); //向界面中插入新的记录. InsertMemoInfoItem(memoID,strName,strOperDate,strHappenDate,isEvent,str Type,strMemo) ; } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } } OnBtnAddMemo 函数首先创建一个“备注信息”对话框实例,从主界面中获取所有的 家庭成员姓名和备忘录类型名称,然后赋给这个对话框实例的 m_strMemberArray 和 m_strTypeArray 变量,并调用 DoModal 函数,弹出“备注信息”对话框,等待用户按下“确 定”按钮,如果用户按下“退出”按钮,将退出 OnBtnAddMemo 函数。如在“选择备注成 员”列表框中选择“李卫”,在“选择备注类型”列表框中选择“电视预告”,在“备注发 生日期”日期时间控件中选择日期时间 2004-6-20 22∶ 20∶ 00,在“备注内容”文本框中 输入“BTV-3 新梁山伯祝英台 2 集”,如图 3-20 所示。 图 3-20 “备注信息”对话框 单击“确定”按钮,进入添加新备注信息的代码处理中。添加这个新备注信息之后, 可以在备忘录信息控件中看到这条新备注信息,如图 3-21 所示。 — 105 — 图 3-21 新添加的备忘录信息对话框 2. 修改备忘录信息 修改备忘录信息的函数为 OnBtnModMemo,代码如下: void CMemoDBSDlg::OnBtnModMemo() { //获取要修改的备注信息所在的行数. int nItem = m_listMemoInfo.GetNextItem(-1, LVNI_SELECTED); if(nItem == -1){ AfxMessageBox("没有选择要修改的成员"); return; } //获取备注类型 ID. int memoID = atoi(m_listMemoInfo.GetItemText(nItem,0)); //创建对话框实例 CMemoInfoDlg dlg; //给对话框赋参数. dlg.m_strMember = m_listMemoInfo.GetItemText(nItem,1); dlg.m_strMemoType = m_listMemoInfo.GetItemText(nItem,5); dlg.m_oleDate.ParseDateTime(m_listMemoInfo.GetItemText(nItem,3)); dlg.m_oleTime.ParseDateTime(m_listMemoInfo.GetItemText(nItem,3)); if(m_listMemoInfo.GetItemText(nItem,4).CompareNoCase("是") == 0) dlg.m_isEvent = 1; else dlg.m_isEvent = 0; dlg.m_strMemoInfo = m_listMemoInfo.GetItemText(nItem,6); for(int i = 0 ; i < m_listMember.GetItemCount() ; i++ ) dlg.m_strMemberArray.Add(m_listMember.GetItemText(i,1)) ; for(i = 0 ; i < m_listType.GetItemCount() ; i++ ) dlg.m_strTypeArray.Add(m_listType.GetItemText(i,1)) ; //打开修改的对话框. if(dlg.DoModal() == IDOK){ //获取修改后的记录 CString strName = dlg.m_strMember; CString strType = dlg.m_strMemoType; CString strHappenDate = dlg.m_oleDate.Format("%Y-%m-%d") + " " + dlg.m_oleTime.Format("%H:%M:%S"); CString strOperDate = COleDateTime::GetCurrentTime().Format("%Y-%m-%d — 106 — %H:%M:%S"); int isEvent = dlg.m_isEvent; CString strMemo = dlg.m_strMemoInfo; TRY{ //为了修改的方便,先删除这条记录,然后再添加记录,备忘录 ID 值不变. CString deletedSql; deletedSql.Format("delete from MEMO_INFO_TAB where MEMO_ID = %d",memoID); m_db.ExecuteSQL(deletedSql); CRecordset rs(&m_db); CString sql; //根据成员姓名获取成员 ID 值. sql.Format("Select MEMBER_ID from FAMILY_MEMBER_TAB where " "MEMBER_NAME = ’%s’",strName); rs.Open(CRecordset::dynaset,sql); int memberID = 1; if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) memberID = var.m_iVal; } rs.Close(); //根据备忘录类型获取备忘录类型 ID. sql.Format("Select TYPE_ID from MEMO_TYPE_TAB where " "TYPE_NAME = ’%s’",strType); rs.Open(CRecordset::dynaset,sql); int typeID = 1; if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) typeID = var.m_iVal; } rs.Close(); //插入记录.备忘录 ID 不变. sql.Format("Insert into MEMO_INFO_TAB(MEMO_ID," "MEMBER_ID,OPER_DATE,HAPPEN_DATE," "IS_BIGEVENT,TYPE_ID,MEMO_TEXT) " "VALUES(" "%d,%d,to_date(’%s’,’yyyy-mm-dd hh24:mi:ss ’)" ",to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’),%d" ",%d,’%s’)",memoID,memberID, strOperDate,strHappenDate,isEvent,typeID,strMemo); TRACE(sql); m_db.ExecuteSQL(sql); //修改界面的值 m_listMemoInfo.DeleteItem(nItem); InsertMemoInfoItem(memoID,strName,strOperDate,strHappenDate,isEvent,strType ,strMemo) ; — 107 — } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } } 在修改备忘录的时候,需要在弹出的“备注信息”对话框中显示要修改的数据,因而 需要把要修改的备忘录信息传入“备注信息”对话框实例的变量中。这也是为什么在 OnInitDialog 函数中,如果是修改记录,需要在列表框的编辑框中显示要修改的数据;而 如果是添加记录,则只需将列表框中的第 1 条数据显示在列表框中的编辑框中的原因。 3. 删除备忘录信息 删除备忘录的函数为 OnBtnDelMemo,处理方法比较简单,代码不再列出。 3.3.7 查询备忘录信息 查询备忘录信息包括生日提示、备忘录提示和大事记 3 个功能。 1. 生日提示 生日提示显示所有家庭成员的出生日期、年龄、生日已过和相差天数的信息。为了显示这 些信息的方便,可以创建一个对话框,对话框类名称为 CBirthQueryDlg,资源 ID 为 IDD_DIALOG_BIRTHDATE,对话框名称为“生日提示”,“生日提示”对话框如图 3-22 所示。 图 3-22 “生日提示”对话框 控件类型、ID 及说明见表 3-11。 — 108 — 表 3-11 控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 生日信息 无 List Control IDC_LIST_BIRTHDATE 无 列表控件类型变量 m_listBirthInfo Button IDOK 退出 无 为了直接在 CBirthQueryDlg 类中操作数据库,需要定义一个公有的数据库指针,代码 如下: public CDatabase *m_pDB; 在 CMemoDBSDlg 类的 OnBtnQueryBirth 函数中添加弹出“生日提示”对话框和传入 数据库连接指针的代码,代码如下: void CMemoDBSDlg::OnBtnQueryBirth() { //创建生日提示对话框实列. CBirthQueryDlg dlg; //把数据库的指针传入对话框中. dlg.m_pDB = &m_db; //打开生日提示对话框. dlg.DoModal(); } OnBtnQueryBirth 函数在创建 CBirthQueryDlg 实例的时候,把 CMemoDBSDlg 类中的 数据库指针传给了 CBirthQueryDlg 类中的 m_pDB 变量,这样确保整个系统只有一个数据 库的连接。 需要在 CBirthQueryDlg 类中添加 OnInitDialog 的消息映射函数。在 OnInitDialog 函数 中添加生日提示信息列表控件的显示列,包括家庭成员的姓名、出生日期、年龄、生日已 过和相差天数。 在 CBirthQueryDlg 类中定义一个 InitData 函数,处理需要显示的生日信息,代码如下: void CBirthQueryDlg::InitData() { if(m_pDB->IsOpen()){ TRY{ CRecordset rs(m_pDB); //获取所有的家庭成员的信息. CString sql = "Select * from family_member_tab "; rs.Open(CRecordset::dynaset, sql); CString name,date; //获取所有的记录值. while (!rs.IsEOF()) { rs.GetFieldValue(1, name); rs.GetFieldValue(2, date); COleDateTime dt1,dt2; dt1.ParseDateTime(date); //获取当前时间. dt2 = COleDateTime::GetCurrentTime(); — 109 — //获取年龄信息. int nOld = dt2.GetYear() - dt1.GetYear(); //修改出生日期的年数,从而获取对于当前年,时间 dt1 所对应的天数. dt1.SetDateTime(dt2.GetYear(),dt1.GetMonth(),dt1.GetDay(),0,0,0); //分别获取 dt1 和 dt2 对应的天数,两者的差就是生日相差的天数 //如果 dt2 比 dt1 大,则说明还未过生日. int nDays1 = dt1.GetDayOfYear(); int nDays2 = dt2.GetDayOfYear(); int nDays = nDays2-nDays1; CString strTemp = "是"; //如过 dt1 较大,则生日已经过了. if(nDays1>nDays2){ nDays = nDays1-nDays2; strTemp = "否"; } //插入生日提示信息. int nIndex = m_listBirthInfo.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp = name; lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 m_listBirthInfo.InsertItem(&lvItem); m_listBirthInfo.SetItemText(nIndex,1,date.Left(10)); temp.Format("%d",nOld); m_listBirthInfo.SetItemText(nIndex,2,temp); m_listBirthInfo.SetItemText(nIndex,3,strTemp); temp.Format("%d",nDays); m_listBirthInfo.SetItemText(nIndex,4,temp); rs.MoveNext(); } } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } } 在 CBirthQueryDlg 类的 OnInitDialog 函数末尾处添加 InitData 函数的调用,这样在单击 — 110 — 主界面上的“生日提示”按钮之后,将在打开的“生日提示”对话框中显示生日提示信息。 2. 备忘录提示 备忘录提示既可以查询某一天会发生的备忘录,也可以查询几天内要发生的备忘录。 为了查询备忘录信息的方便,可以创建一个对话框,对话框类名称为 CMemoQueryDlg, 资源 ID 为 IDD_DIALOG_MEMO_QUERY,对话框名称为“备忘录信息”,“备忘录信息” 对话框如图 3-23 所示。 图 3-23 “备忘录信息”对话框 控件类型、ID 及说明见表 3-12。 表 3-12 备忘录信息控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 选择日期进行查询 无 Label IDC_STATIC 日期 无 DateTime Picker IDC_DT_DATE 无 COleDateTime 类型变量 m_oleDate Button IDC_BTN_QUERY 查询 函数 OnBtnQuery ()按照日期进行查询 Group Box IDC_STATIC 几天内的备忘录查询 无 Label IDC_STATIC 天数 无 Edit Box IDC_EDIT_DAY 无 整型变量 m_nDays Button IDC_BTN_QUERY_BYDAYS 查询 函数 OnBtnQueryBydays ()按照天数进行查询 Group Box IDC_STATIC 备忘录信息 无 List Control IDC_LIST_MEMO 无 列表控件类型变量 m_listMemoInfo Edit Box IDC_EDIT_MEMO 无 CString 类型变量 m_strMemoInfo Button IDOK 退出 无 为了直接在 CMemoQueryDlg 类中操作数据库,需要定义一个公有的数据库指针,代 码如下: public CDatabase *m_pDB; 在 CMemoDBSDlg 类的 OnBtnMemoInfo 函数中添加弹出“备忘录信息”对话框和传 入数据库连接指针的代码,代码如下: void CMemoDBSDlg::OnBtnMemoInfo() — 111 — { //创建备忘录提示对话框实列. CMemoQueryDlg dlg; //把数据库的指针传入对话框中. dlg.m_pDB = &m_db; //打开备忘录信息对话框. dlg.DoModal(); } 需要在 CMemoQueryDlg 类中添加 OnInitDialog 的消息映射函数。在 OnInitDialog 函数 中添加备忘录信息的显示列,和主界面的备忘录信息显示列是相同的。 备忘录提示提供了两种备忘录信息的查询方式。在编码过程中,经常要注意代码的优 化。例如在处理这两种查询方式的时候,可以发现这两种查询方式有相同的代码处理部分, 即打开记录集并显示到列表控件上,不同的是要打开什么样的记录集。因而可以定义一个 GetQueryData 函数,根据传入 SQL 语句打开记录集并显示到列表控件中。同时还需要定义 一个 InsertMemoInfoItem 函数负责向列表控件中插入备忘录信息,这和主界面中的 InsertMemoInfoItem 函数代码相同,在 MemoQueryDlg.h 文件中添加这两个函数的定义,代 码如下: //根据 SQL 语句获取数据,并显示在界面中. void GetQueryData(CString sql); //向列表控件中添加备忘录信息. void InsertMemoInfoItem(int id, CString name, CString operDate, CString happenDate, int isEvent, CString type, CString memoTxt); GetQueryData 函数的代码如下: void CMemoQueryDlg::GetQueryData(CString sql) { //删除列表控件中的信息. m_listMemoInfo.DeleteAllItems(); if(m_pDB->IsOpen()){ TRY{ CRecordset rs(m_pDB); TRACE(sql); //打开记录集. rs.Open(CRecordset::dynaset, sql); while (!rs.IsEOF()) { CDBVariant var; int memoID,memberID,isEvent,nType; CString happenDate,operDate,memoTxt,strName,strType; //获取备忘录 ID 字段值 rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) memoID = var.m_iVal; var.Clear(); //获取成员 ID 字段值 rs.GetFieldValue(1, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) — 112 — memberID = var.m_iVal; var.Clear(); //获取备忘录操作日期字段值 rs.GetFieldValue(2, operDate); //获取备忘录发生日期字段值 rs.GetFieldValue(3, happenDate); var.Clear(); //获取是否大事件字段值 rs.GetFieldValue(4, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) isEvent = var.m_iVal; var.Clear(); //获取备忘录类型 ID 字段值 rs.GetFieldValue(5, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) nType = var.m_iVal; var.Clear(); //获取备忘录内容字段值 rs.GetFieldValue(6, memoTxt); CRecordset rs2(m_pDB); CString temp; //根据成员 ID 获取成员的姓名. temp.Format("Select MEMBER_NAME from FAMILY_MEMBER_TAB where " "MEMBER_ID = %d",memberID); rs2.Open(CRecordset::dynaset,temp); if(!rs2.IsEOF()) rs2.GetFieldValue((short)0, strName); rs2.Close(); //根据类型 ID 获取类型名称. temp.Format("Select TYPE_NAME from MEMO_TYPE_TAB where " "TYPE_ID = ’%d’",nType); rs2.Open(CRecordset::dynaset,temp); if(!rs2.IsEOF()) rs2.GetFieldValue((short)0, strType); rs2.Close(); //把记录信息显示在界面上. InsertMemoInfoItem(memoID,strName,operDate,happenDate,isEvent,strType,m emoTxt); rs.MoveNext(); } } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); — 113 — AfxMessageBox (szError); } END_CATCH } } 根据日期查询备忘录信息的函数为 OnBtnQuery,代码如下: void CMemoQueryDlg::OnBtnQuery() { if(!UpdateData()) return; //创建查询字符串. COleDateTime dt = m_oleDate; dt.SetDateTime(m_oleDate.GetYear(),m_oleDate.GetMonth(), m_oleDate.GetDay(),0,0,0); CString strDate = dt.Format("%Y-%m-%d %H:%M:%S"); CString sql; //根据日期进行查询. sql.Format("Select * from MEMO_INFO_TAB where " "HAPPEN_DATE > to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’) " " and HAPPEN_DATE - 1 < to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’)",strDate,strDate); GetQueryData(sql); } 由于有了 GetQueryData 函数,OnBtnQuery 函数只需构造一个根据日期方式查询的 SQL 语句,然后调用 GetQueryData 函数就可以了。在 SQL 语句中可以直接对日期字段减 1 从 而得到日期字段所代表日期的前一天,加 1 得到日期字段所代表日期的后一天。 如在“备忘录信息”对话框的日期控件中选择要查询的日期 2004-6-18,然后单击“查 询”按钮,将列出在 2004-6-18 当天会发生的所有备忘录信息,如图 3-24 所示。 图 3-24 创建项目对话框 根据天数查询备忘录信息的函数为 OnBtnQueryByDays,可以查询几天内将会发生的 — 114 — 备忘录信息,代码如下: void CMemoQueryDlg::OnBtnQueryBydays() { if(!UpdateData()) return; //创建 SQL 语句. CString sql; //按照天数进行查询,查询几天之内的备忘录信息. sql.Format("Select * from MEMO_INFO_TAB where " "sysdate + %d >= HAPPEN_DATE and sysdate <=HAPPEN_DATE",m_nDays); GetQueryData(sql); } 3. 大事记 大事记列出了家庭成员一生中所有重要事件的记录,就如同一个人的简历一样,也可 以列出家庭所有成员的大事记。为了查询大事件信息的方便,可以创建一个对话框,对话 框类名称为 CEventDlg,资源 ID 为 IDD_DIALOG_EVENT,对话框名称为“大事记”,“大 事记”对话框如图 3-25 所示。 图 3-25 “大事记”对话框 控件类型、ID 及说明见表 3-13。 表 3-13 控件列表 控件类型 ID 属 性 变量或函数 Label IDC_STATIC 选择家庭成员 无 Combo Box IDC_COMBO_MEMBER 无 列表框控件变量 m_comboMember,字符串变量 m_strName Button IDC_BTN_QUERY 查询 函数 OnBtnQuery ()选择家庭成员进行查询 Group Box IDC_STATIC 大事记 无 List Control IDC_LIST_MEMO 无 列表控件类型变量 m_listMemoInfo Button IDOK 退出 无 为了直接在 CEventDlg 类中操作数据库,需要定义一个公有的数据库指针,代码如下: — 115 — public CDatabase *m_pDB; 在 CMemoDBSDlg 的 OnBtnMemoEvent 中添加弹出“大事记”对话框和传入数据库连 接指针的代码,代码如下: void CMemoDBSDlg::OnBtnMemoEvent() { //创建备注信息提示对话框实列. CEventDlg dlg; //把数据库的指针传入对话框中. dlg.m_pDB = &m_db; //打开大事记提示对话框. dlg.DoModal(); } 需要在 CEventDlg 类中添加 OnInitDialog 的消息映射函数。在 OnInitDialog 函数中添 加初始化大事记信息的显示列,以及在家庭成员列表框控件中添加家庭成员的姓名,代码 如下: BOOL CEventDlg::OnInitDialog() { CDialog::OnInitDialog(); DWORD dwExStyle = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP | LVS_EX_ONECLICKACTIVATE | LVS_EX_UNDERLINEHOT; m_listMemoInfo.SetExtendedStyle(dwExStyle); //初始化大事件信息列表控件 m_listMemoInfo.InsertColumn(0,"成员姓名",LVCFMT_CENTER,60); m_listMemoInfo.InsertColumn(1,"日期",LVCFMT_CENTER,140); m_listMemoInfo.InsertColumn(2,"备注类型",LVCFMT_CENTER,60); m_listMemoInfo.InsertColumn(3,"内容",LVCFMT_CENTER,300); //获取所有的家庭成员记录,并且加入的列表框里. if(m_pDB->IsOpen()){ CRecordset rs(m_pDB); CString sql = "Select * from family_member_tab"; rs.Open(CRecordset::dynaset, sql); CString name; while (!rs.IsEOF()) { rs.GetFieldValue(1, name); m_comboMember.AddString(name); rs.MoveNext(); } } //在列表框里添加一条所有人的选项. if(m_comboMember.GetCount()>0) m_comboMember.AddString("*所有人"); return TRUE; // return TRUE unless you set the focus to a control } 在 CEventDlg 类中添加一个私有函数 InsertMemoInfoItem,负责插入大事记信息,在 EventDlg.h 文件中定义该函数,代码如下: — 116 — private: void InsertMemoInfoItem(CString name, CString happenDate, CString type, CString memoTxt); InsertMemoInfoItem 函数的代码如下: void CEventDlg::InsertMemoInfoItem(CString name, CString happenDate, CString type, CString memoTxt) { //向备忘录信息列表控件中插入新的记录. int nIndex = m_listMemoInfo.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; lvItem.pszText = (char*)(LPCTSTR)name; //第一列 //插入信息到界面. m_listMemoInfo.InsertItem(&lvItem); m_listMemoInfo.SetItemText(nIndex,1,happenDate); m_listMemoInfo.SetItemText(nIndex,2,type); m_listMemoInfo.SetItemText(nIndex,3,memoTxt); } 根据家庭成员的姓名,查询大事记的函数为 OnBtnQuery,代码如下: void CEventDlg::OnBtnQuery() { //从界面获取数据. if(!UpdateData()) return; //删除列表控件中的数据. m_listMemoInfo.DeleteAllItems(); if(m_pDB->IsOpen()){ TRY{ CRecordset rs(m_pDB); //创建 SQL 语句,初始为查询所有人的大事记 CString sql = "Select * from MEMO_INFO_TAB where IS_BIGEVENT = 1 " " order by HAPPEN_DATE"; //如果是查询个人的大事记 if(m_strName.CompareNoCase("*所有人")!=0) sql.Format("Select * from MEMO_INFO_TAB a," "FAMILY_MEMBER_TAB b where a.MEMBER_ID = b.MEMBER_ID " "and b.MEMBER_NAME = ’%s’ and a.IS_BIGEVENT = 1" " order by HAPPEN_DATE",m_strName); TRACE(sql); //打开记录集. rs.Open(CRecordset::dynaset, sql); while (!rs.IsEOF()) { CDBVariant var; int memberID,nType; CString happenDate,operDate,memoTxt,strName,strType; — 117 — //获取成员 ID 字段值 rs.GetFieldValue(1, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) memberID = var.m_iVal; var.Clear(); //获取备忘录发生日期字段值 rs.GetFieldValue(3, happenDate); var.Clear(); //获取备忘录类型 ID 字段值 rs.GetFieldValue(5, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) nType = var.m_iVal; var.Clear(); //获取备忘录内容字段值 rs.GetFieldValue(6, memoTxt); CRecordset rs2(m_pDB); CString temp; //从成员 ID 获取成员的姓名. temp.Format("Select MEMBER_NAME from FAMILY_MEMBER_TAB where " "MEMBER_ID = %d",memberID); rs2.Open(CRecordset::dynaset,temp); if(!rs2.IsEOF()) rs2.GetFieldValue((short)0, strName); rs2.Close(); //从备注类型 ID 获取备注类型名称. temp.Format("Select TYPE_NAME from MEMO_TYPE_TAB where " "TYPE_ID = ’%d’",nType); rs2.Open(CRecordset::dynaset,temp); if(!rs2.IsEOF()) rs2.GetFieldValue((short)0, strType); rs2.Close(); //在界面中插入新的记录. InsertMemoInfoItem(strName,happenDate,strType,memoTxt); rs.MoveNext(); } } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } } — 118 — 如果一个家庭有如图 3-26 所示的备忘录信息。 图 3-26 “备忘录信息管理”对话框 选择主界面的“大事记”按钮,弹出“大事记”对话框。从“选择家庭成员”选项组 中选择成员“张丽”,单击“查询”按钮,列出所有“张丽”的备忘录信息为大事记的记录, 并按照备忘录发生日期的时间顺序列出来,如图 3-27 所示。 图 3-27 “大事记”对话框 图 3-27 中显示的信息如同“张丽”一生的简历,列出了张丽一生中所有重要的事件。 3.4 本章小结 本章详细介绍了家庭备忘录管理系统,从系统设计、数据库设计与实现再到系统的实 现,给读者呈现了一个比较简单的数据库管理系统的主要开发步骤。通常一个数据库管理 系统都不会小,因而读者应该有步骤有计划地进行系统的设计和实现,千万不要在设计的 情况下就开始编写代码,想到哪里写到哪里,往往事倍功半,浪费许多时间,这是很多初 学者常犯的错误。如果系统设计好了,代码的实现将是一个轻松的过程,以后的系统测试 也会轻松许多。本章采用了 MFC ODBC 的数据库开发技术,读者将学会利用 MFC ODBC 开发数据库应用系统的方法。 本章还介绍了如何利用“类似创建”的方法在 Oracle Enterprise Manager Console 中创 建表空间和用户,使用这种方法非常方便。 第4章 企业设备管理系统 公司经常需要采购一些设备,用以满足公司正常运营的需要,采购回来的设备不能堆 放在库房里就不管了,谁想用的时候就去拿,谁想换的时候就去换。这样,公司的设备恐 怕会越来越少,公司正常的运营就会受到影响。因此,需要对公司的设备进行库存管理, 保证设备借出和归还有序,还要能查到设备的借出情况。企业设备管理系统正好能满足这 个需求,它提供了对设备库存、设备借出、设备归还和设备统计信息的管理。 4.1 系统设计 系统设计是系统开发最为关键的一环,良好的系统设计需要把握系统的需求,并合理 地划分功能模块。企业设备管理系统的系统设计还需要把握一个关键点,就是明确设备的 惟一性,尽管设备可能一模一样,但是也应该区别开来,因为我们的借出、归还以及借出 历史的统计都是要具体到某一个设备的,需要对每一个设备赋予不同的设备编号,通常会 制作标签贴在设备上,这恐怕是库房管理员常做的工作,设备编号的方式可以根据公司具 体决定。 4.1.1 功能描述 企业设备管理系统包括设备库存管理、设备借出归还管理和设备统计信息管理,详细 的功能描述如下。 1. 设备库存管理 设备库存管理包括新设备入库、修改设备信息和陈旧设备的库存清理。新设备入库是 对新采购的设备进行入库的操作。修改设备信息是对入库设备的名称、购买人、入库时间、 设备说明等信息的修改。库存清理,是清理库存中不能再用的设备。 2. 设备借出归还管理 设备借出归还管理包括设备借出管理和设备归还管理。对于设备借出管理,如果设备 已经借出,要提示用户谁借走了设备。当借出成功的时候,需要在列表中显示借出人、借 出时间和借出设备信息,如果归还成功,还需要显示设备归还时间。 3. 设备统计信息管理 设备统计信息管理包括对设备借出历史信息、设备使用频率信息和未归还设备列表信 息的管理。设备借出历史统计显示了一个设备所有的借出历史,便于跟踪设备的使用情况。 如果设备出现问题,还可以顺藤摸瓜,查到谁有可能把设备弄坏了,从而找到设备损坏的 原因,便于及时修理。设备使用频率统计提供了设备借出次数的统计信息,如果一个设备 借出的非常频繁,那么企业可以考虑多购买这种设备,从而提高公司的效率。未归还设备 列表提供了尚未归还的设备信息,企业可以知道借出设备的使用情况。 — 120 — 4.1.2 功能模块设计 从上面的功能描述中,可以把企业设备管理系统分为 3 个模块:设备库存管理、设备 借出归还管理和设备统计信息管理。在每一个模块下又提供了更为具体的功能。详细的企 业设备管理系统的功能模块图,如图 4-1 所示。 图 4-1 系统功能模块图 4.2 数据库设计与实现 数据库设计的一个巧妙之处就是表的数量不能太多,逻辑层次也不能太多;否则,系 统的升级和维护将会非常困难。 4.2.1 数据库需求设计 通过对系统功能的分析,企业设备管理系统需要包含以下数据库信息。 1. 设备信息 包括设备编号、设备名称、设备描述、设备入库时间、设备购买人、设备借出状态和 设备借出 ID。 2. 设备借出信息 包括设备借出 ID、设备编号、借出人、借出时间和归还时间。 4.2.2 数据库表的设计 通过数据库的需求,可以创建以下数据表。 — 121 — 1. 设备信息表(device_info_tab) 设备信息表包含了设备的一些基本信息,还包括设备的借出信息,设备的库存管理和 设备的借出归还管理都需要操作这个数据表,表的结构见表 4-1。 表 4-1 设备信息表 字段名称 数据类型 可否为空 约束条件 说 明 device_code VARCHAR2(24) NOT NULL 主键 设备编号,要惟一 device_name VARCHAR2(24) NOT NULL 无 设备名称 description VARCHAR2(1000) NULL 无 设备描述 oper_date DATE NOT NULL 无 设备入库时间 buyer VARCHAR2(24) NULL 无 设备购买人 Lend_status INTEGER NULL 无 设备借出状态 lend_id INTEGER NULL 无 设备借出 ID,借出 ID 是惟一的但可以为空 2. 设备借出信息表(device_lend_info_tab) 设备借出信息表包含了设备的借出和归还信息,设备借出、设备归还,以及统计信息 的管理都需要用到这个数据表,表的结构见表 4-2。 表 4-2 设备借出信息表 字段名称 数据类型 可否为空 约束条件 说 明 lend_id INTEGER NOT NULL 主键 借出 ID,ID 值从系列中获取) device_code VARCHAR2(24) NOT NULL 外键(device_info_tab) 设备编号 borrower VARCHAR2(24) NOT NULL 无 借出人 borrow_date DATE NULL 无 设备借出时间 return_date DATE NULL 无 设备归还时间 4.2.3 数据库表的创建 利用第 3 章中讲述的方法创建表空间 dbdevice 和数据库用户 dbdevice,其中数据库用 户的密码为 dbdevice,选择的默认表空间为 dbdevice。 创建企业设备管理系统的所有数据表的 SQL 语句如下: --创建设备信息表 CREATE TABLE device_info_tab( device_code VARCHAR2(24) NOT NULL, device_name VARCHAR(24) NOT NULL, description VARCHAR(1000) NULL, oper_date DATE NOT NULL, buyer VARCHAR2(24) NULL, lend_status INTEGER NULL CHECK (lend_status IN (0, 1)), lend_id INTEGER NULL ); --添加设备编号主键 ALTER TABLE device_info_tab ADD ( PRIMARY KEY (device_code) ) ; — 122 — --创建设备借出信息表 CREATE TABLE device_lend_info_tab( lend_id INTEGER NOT NULL, device_code VARCHAR2(24) NOT NULL, borrower VARCHAR(24) NOT NULL, borrow_date DATE NULL, return_date DATE NULL ); --添加借出 ID 主键 ALTER TABLE device_lend_info_tab ADD ( PRIMARY KEY (lend_id) ) ; --添加设备编号外键 ALTER TABLE device_lend_info_tab ADD ( FOREIGN KEY (device_code) REFERENCES device_info_tab ) ; --创建可以递增的系列号供 lend_id 使用 CREATE SEQUENCE seq_lend_id INCREMENT BY 1 START WITH 1 NOMAXVALUE NOMINVALUE NOCYCLE; 利用 Oracle SQLPlus WorkSheet 工具执行上述的 SQL 语句从而创建数据库表。需要说 明的是,在打开 Oracle SQLPlus Worksheet 的“Oracle Enterprise Manager 登录”窗口的时 候,需要在“用户名”文本框中输入企业设备管理系统的用户名 dbdevice,在“口令”文 本框中输入用户密码 dbdevice,在“服务”文本框中输入数据库的本地服务名 ORADB, 选择连接方式 Normal,登录成功后,再运行上述的 SQL 语句。 4.3 系统的实现 完成了系统功能模块的设计和数据库表的创建后,就可以创建一个企业设备管理系 统。 4.3.1 创建应用程序 运行 Visual C++,选择 File|New 命令,弹出 New 对话框。从 Projects 列表中选择 MFC AppWizard(exe)向导,在 Location 文本框中选择项目工程的目录“D:\VCSAMPLE\”,在 Project name 文本框中输入工程文件的名称 DeviceDBS,单击 OK 按钮,进入 MFC AppWizard – Step 1 页面,从应用程序的类型中选择 Dialog based 选项,从语言列表中选择 “中文(中国)(APPWZCHS.DLL)”,单击 Finish 按钮,DeviceDBS 对话框的应用程序创建完 毕。 4.3.2 创建主对话框的界面 主对话框的布局如图 4-2 所示。其中包括设备库存管理、设备借出归还管理和设备统 计信息管理 3 个部分。 — 123 — 图 4-2 “企业设备管理系统”对话框 3. 设备库存管理 控件类型、ID 及说明见表 4-3。 表 4-3 设备库存管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 设备库存管理 无 Label IDC_STATIC 设备编号 无 Edit Box IDC_EDIT_DEVICECODE 无 CString 类型变量 m_strDeviceCode Label IDC_STATIC 设备名称 无 Edit Box IDC_EDIT_DEVICENAME 无 CString 类型变量 m_strDeviceName Label IDC_STATIC 购买人 无 Edit Box IDC_EDIT_BUYER 无 CString 类型变量 m_strBorrower Label IDC_STATIC 入库时间 无 Date Time Picker IDC_DT_DATE Short Date COleDateTime 类型变量 m_oleOperDate Date Time Picker IDC_DT_TIME Time COleDateTime 类型变量 m_oleOperTime Label IDC_STATIC 设备说明 无 Edit Box IDC_EDIT_DESCRIPTION 无 CString 类型变量 m_strDescription Button IDC_BTN_DEVICE_ADD 新设备入库 函数 OnBtnDeviceAdd ()处理新设备的入库管理 Button IDC_BTN_DEVICE_MOD 修改设备信息 函数 OnBtnDeviceMod ()修改设备信息 Button IDC_BTN_DEVICE_DEL 库存清理 函数 OnBtnDeviceDel ()处理库存设备的清理管理 Group Box IDC_STATIC 设备信息 无 List Control IDC_LIST_DEVICE 无 列表框控件类型变量 m_listDevice — 124 — 4. 设备借出归还管理 控件类型、ID 及说明见表 4-4。 表 4-4 设备借出归还管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 设备借出归还管理 无 Label IDC_STATIC 请输入借书人 无 Edit Box IDC_EDIT_BORROWER 无 CString 类型变量 m_strBorrower Label IDC_STATIC 请选择设备名称 无 Combo Box IDC_COMBO_NAME 无 列表框控件变量 m_comboName,CString 类型变量 m_strSelectedName Label IDC_STATIC 请选择设备编号 无 Combo Box IDC_COMBO_CODE 无 列表框控件变量 m_comboCode,CString 类型变量 m_strSelectedCode Button IDC_BTN_LEND 借出 函数 OnBtnLend ()处理设备借出管理 Button IDC_BTN_RETURN 归还 函数 OnBtnReturn ()处理设备归还管理 Group Box IDC_STATIC 借出信息 无 List Control IDC_LIST_LEND 无 列表框控件类型变量 m_listLend 5. 设备统计信息管理 控件类型、ID 及说明见表 4-5。 表 4-5 设备统计信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 设备统计信息管理 无 Button IDC_BTN_LEND_LIST 设备借出历史统计 函数 OnBtnLendList ()处理设备借出历史统计 Button IDC_BTN_FREQUENCY_LIST 设备使用频率统计 函数 OnBtnFrequencyList ()处理设备使用频率统计 Button IDC_BTN_LENT_DEVICE 未归还设备列表 函数 OnBtnLentDevice ()处理未归还设备统计 Button IDC_SYS_EXIT 退出 函数 OnSysExit()处理系统退出代码 主对话框类名称为 CDeviceDBSDlg,资源 ID 为 IDD_DEVICEDBS_DIALOG,对话框 名称为“企业设备管理系统”。主界面用到了两个列表框控件分别显示设备信息和借出信息。 需要为这两个列表框控件添加显示的列,从而显示相应的数据信息。为了代码设计的清晰, 在 CDeviceDBSDlg 类中定义了一个 InitControl 私有函数负责添加控件的显示列,InitControl 函数的代码如下: void CDeviceDBSDlg::InitControl() { //设置列表框控件扩展风格 DWORD dwExStyle = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP | LVS_EX_ONECLICKACTIVATE | LVS_EX_UNDERLINEHOT; m_listDevice.SetExtendedStyle(dwExStyle); m_listLend.SetExtendedStyle(dwExStyle); //初始化设备信息列表框控件 m_listDevice.InsertColumn(0,"设备编号",LVCFMT_CENTER,80); m_listDevice.InsertColumn(1,"设备名称",LVCFMT_CENTER,80); m_listDevice.InsertColumn(2,"设备入库时间",LVCFMT_CENTER,140); m_listDevice.InsertColumn(3,"设备购买人",LVCFMT_CENTER,80); — 125 — m_listDevice.InsertColumn(4,"设备描述",LVCFMT_CENTER,200); //初始化议题列表框控件 m_listLend.InsertColumn(0,"借出 ID",LVCFMT_CENTER,80); m_listLend.InsertColumn(1,"设备编号",LVCFMT_CENTER,80); m_listLend.InsertColumn(2,"设备名称",LVCFMT_CENTER,80); m_listLend.InsertColumn(3,"借出人",LVCFMT_CENTER,80); m_listLend.InsertColumn(4,"设备借出时间",LVCFMT_CENTER,140); m_listLend.InsertColumn(5,"设备归还时间",LVCFMT_CENTER,140); } 在 OnInitDialog 函数末尾处添加 InitControl 函数的调用,这样系统在启动的时候,就 可以看到已添加显示列的列表框控件。 4.3.3 显示数据到界面上 当系统启动之后,可以将数据库中的数据显示到界面上,需要处理数据库的连接和显 示数据到界面上的两个过程。 1. 数据库的连接 数据库的连接需要 3 个参数:ODBC 数据源名称、数据库用户名称和数据库用户密码。 其中数据源名称为 ORADB,数据库用户的名称和密码均为 dbdevice。考虑到读者配置的 数据源和用户名可能不一样,可以从配置文件中获取这些参数信息,配置文件的格式如下: [General] 数据库数据源=oradb 数据库用户=dbdevice 数据库密码=dbdevice 把这段文字保存为 DeviceDBS.ini 文件,可以根据读者自己配置的数据源、数据用户 和密码修改文件中的相应设置,并把 DeviceDBS.ini 文件放在 DeviceDBS.exe 运行程序的 同一目录下。 在 CDeviceDBSDlg 类中定义了一个私有类型的 ConnectDB 函数,处理数据库的连接, 代码如下: void CDeviceDBSDlg::ConnectDB() { char szPath[255]; //获取应用程序完全路径 ::GetModuleFileName(NULL,szPath,255); CString strFileName = szPath; //获取所在的目录名称 strFileName.Delete(strFileName.ReverseFind(’\\’)+1,strFileName.GetLengt h ()-strFileName.ReverseFind(’\\’)-1); //构造配置文件的完全路径 strFileName += "DeviceDBS.ini"; TCHAR sz[101]; memset(sz,0,sizeof(TCHAR)*101); //获取配置文件中数据库数据源的值,如果没有,默认值为 oradb GetPrivateProfileString(_T("General"),_T(" 数 据 库 数 据 源 — 126 — "),_T("oradb"),sz,100,strFileName); CString strSource(sz); GetPrivateProfileString(_T("General"),_T(" 数 据 库 用 户 "),_T("dbdevice"),sz,100,strFileName); CString strUser(sz); GetPrivateProfileString(_T("General"),_T(" 数 据 库 密 码 "),_T("dbdevice"),sz,100,strFileName); CString strPwd(sz); //创建连接字符串. CString strConnect; strConnect.Format("DSN=%s;UID=%s;PWD=%s",strSource,strUser,strPwd); //打开数据库的连接,并且捕获异常 TRY{ m_db.OpenEx(strConnect,CDatabase::noOdbcDialog); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH InitCtrlData(); } 函数 ConnectDB 利用系统的 GetPrivateProfileString 函数从文件中获取数据库的配置参 数, GetPrivateProfileString 函数的原型如下: DWORD GetPrivateProfileString( LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPTSTR lpReturnedString, DWORD nSize, LPCTSTR lpFileName ); 其中第 1 个参数是 Section 的名称,对应 DeviceDBS.ini 文件中的 General,即中括号 所包含的内容 General;第 2 个参数是键的名称,如 DeviceDBS.ini 文件中的“数据库数据 源”、“数据库用户”和“数据库密码”;第 3 个参数是默认值,如果没用成功获得键值,那 — 127 — 么就取lpDefault代表的数据;第 4 个参数是保存键值的缓冲区;第 5 个参数是缓冲区的大 小;第 6 个参数是文件的完全路径。 由于第 6 个参数需要获取文件 DeviceDBS.ini 的完全路径,因而需要利用系统的 GetModuleFileName 函数先获取应用程序 DeviceDBS.exe 所在的路径(因为 DeviceDBS.ini 文件和应用程序 DeviceDBS.exe 在同一目录下),然后构造配置文件 DeviceDBS.ini 的完全 路径。当从文件中获取到数据库配置参数之后,就可以创建数据库连接字符串,利用 CDatabase 的 OpenEx 方法打开数据库的连接,并处理数据库的异常。 2. 显示数据到界面上 连接数据库之后,需要把数据库中的设备信息显示到设备信息列表框控件中,并刷新 借出归还管理中的设备名称和设备编号两个列表框中的显示数据。因而在 CDeviceDBSDlg 类 中 定 义 了 一 个 InitCtrlData 私有函数,负责显示这些数据。同时还定义一个 RefreshComboNameData 函数,根据数据库的最新数据刷新借出归还管理中的两个列表框 的数据。在对设备信息添加、修改和删除之后都需要调用 RefreshComboNameData 函数来 更新列表框中的显示数据。同时还需要定义两个分别把数据插入到列表框控件中的函数, 分别为:InsertDeviceInfoItem 和 InsertLendInfoItem。在连接数据库成功之后,不需要显示 借出信息,因为比较多,也不是很重要,只需在借出和归还成功之后显示借出信息。在 DeviceDBSDlg.h 文件中添加这 4 个私有函数的定义,代码如下: //从数据库获取设备信息并更新到控件中 void InitCtrlData(); //更新设备名称和设备编号列表框的信息 void RefreshComboNameData(); //向设备信息控件中添加信息 void InsertDeviceInfoItem(CString code, CString name, CString date, CString buyer, CString description); //向借出信息控件中添加借出信息 void InsertLendInfoItem(int id,CString code,CString name,CString borrower,CString lendDate,CString returnDate); 向设备信息列表框控件中添加设备信息的函数是 InsertDeviceInfoItem,代码如下: void CDeviceDBSDlg::InsertDeviceInfoItem(CString code, CString name, CString date, CString buyer, CString description) { //获取当前的记录条数. int nIndex = m_listDevice.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; lvItem.pszText = (char*)(LPCTSTR)code; //第一列 //在最后一行插入记录值. m_listDevice.InsertItem(&lvItem); //设置该行的其他列的值. m_listDevice.SetItemText(nIndex,1,name); m_listDevice.SetItemText(nIndex,2,date); — 128 — m_listDevice.SetItemText(nIndex,3,buyer); m_listDevice.SetItemText(nIndex,4,description); } 向借出信息列表框控件中添加借出信息的函数是 InsertLendInfoItem,代码如下: void CDeviceDBSDlg::InsertLendInfoItem(int id,CString code,CString name,CString borrower,CString lendDate,CString returnDate) { //获取当前的记录条数. int nIndex = m_listLend.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. m_listLend.InsertItem(&lvItem); //设置该行的其他列的值. m_listLend.SetItemText(nIndex,1,code); m_listLend.SetItemText(nIndex,2,name); m_listLend.SetItemText(nIndex,3,borrower); m_listLend.SetItemText(nIndex,4,lendDate); m_listLend.SetItemText(nIndex,5,returnDate); } 把数据库中的数据显示到界面上的函数为 InitCtrlData,代码如下: void CDeviceDBSDlg::InitCtrlData(){ if(!m_db.IsOpen()){ MessageBox("数据库未打开"); return; } m_listDevice.DeleteAllItems(); TRY{ CRecordset rs(&m_db); //打开所有的设备信息记录. rs.Open(CRecordset::dynaset, "select * from device_info_tab"); while (!rs.IsEOF()) { CString strCode,strName,strBuyer,strDate,strDescription; //获取设备编号字段值 rs.GetFieldValue((short)0, strCode); //获取设备名称字段值 rs.GetFieldValue(1, strName); //获取设备描述字段值 rs.GetFieldValue(2, strDescription); //获取入库时间字段值 rs.GetFieldValue(3, strDate); //获取设备购买人字段值 — 129 — rs.GetFieldValue(4, strBuyer); //向会议列表框控件中加入一条新的记录信息. //向界面中插入新的设备信息. InsertDeviceInfoItem(strCode,strName, strDate,strBuyer,strDescription) ; rs.MoveNext(); } rs.Close(); //更新设备名称和设备编号列表框的数据 RefreshComboNameData(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } InitCtrlData 函数首先获取设备信息,然后调用 InsertDeviceInfoItem 函数将数据显示在 列表框控件中,最后调用 RefreshComboNameData 函数更新设备名称和设备编号的列表框 中的数据。 为了用户借出设备的方便,可以先选择设备名称,因为相同设备名称的设备可能有多 台。然后再从设备编号列表框中选择要借的设备,在进行这些选择之前都需要在设备名称 和设备编号列表框中添加可供选择的数据,RefreshComboNameData 函数会根据数据库的 最新数据刷新两个列表框的数据,其代码如下: void CDeviceDBSDlg::RefreshComboNameData() { m_comboName.ResetContent(); TRY{ CRecordset rs(&m_db); //打开所有的设备名称记录. rs.Open(CRecordset::dynaset, "select distinct device_name from device_info_tab"); while (!rs.IsEOF()) { CString strName; //获取设备名称字段值 — 130 — rs.GetFieldValue((short)0, strName); //向设备名称列表框添加所有设备名称. m_comboName.AddString(strName); rs.MoveNext(); } rs.Close(); m_comboName.SetCurSel(0); //更新设备编号列表框的数据 CString strSelected; m_comboName.GetLBText(0,strSelected); CString sql; sql.Format("select device_code from device_info_tab " "where device_name = ’%s’",strSelected); rs.Open(CRecordset::dynaset,sql); m_comboCode.ResetContent(); while (!rs.IsEOF()) { CString strCode; //获取设备编号字段值 rs.GetFieldValue((short)0, strCode); //向设备编号列表框添加名称为前面选择的设备名称的所有设备编号. m_comboCode.AddString(strCode); rs.MoveNext(); } m_comboCode.SetCurSel(0); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } RefreshComboNameData 函数首先从设备信息表 device_info_tab 中获取所有的设备名 称信息,并添加到设备名称列表框中。然后调用 CComboBox 的 SetCurSel 函数显示列表框 中的第 1 个数据(设备名称),调用 CComboBox 的 GetLBText 函数可以获取列表框选中的数 据(设备名称值 strSelected)。根据这个设备名称再从设备信息表 device_info_tab 中获取所有 的设备名称为 strSelected 的设备编号显示在设备编号列表框控件中。 — 131 — 把 InitCtrlData 函数放在 ConnectDB 函数的结尾处。这样,数据库在连接成功之后, 可以把数据显示到界面上。在 DeviceDBSDlg.cpp 文件的 OnInitDialog 函数末尾处添加初始 化列表框控件列和连接数据库并显示数据的代码,代码如下: //初始化列表框控件 InitControl(); //连接数据库 ConnectDB(); 系统在启动的时候,会自动连接数据库,数据库连接成功后,就在列表框控件中显示 数据库中的信息,如图 4-3 所示。 图 4-3 连接数据库之后的对话框 4.3.4 设备库存管理 设备库存管理包括新设备入库、修改设备信息、陈旧设备库存清理。当用户需要添加 和修改设备信息的时候,只需要在设备信息参数中输入和修改参数就可以了。 1. 新设备入库 新设备入库是对新采购的设备进行入库的操作,函数为 OnBtnDeviceAdd,代码如下: void CDeviceDBSDlg::OnBtnDeviceAdd() { //从界面控件中获取信息更新到控件变量中. — 132 — if(!UpdateData()) return; //构造入库时间 CString strDate = m_oleOperDate.Format("%Y-%m-%d") + " " + m_oleOperTime.Format("%H:%M:%S"); TRY{ CRecordset rs(&m_db); CString sql; //打开数据库,查看数据库里面是否含有相同的设备编号 //如果有提示用户,返回界面. sql.Format("Select device_code from device_info_tab " "where device_code = ’%s’",m_strDeviceCode); rs.Open(CRecordset::snapshot, sql); CString strCode = _T(""); if(!rs.IsEOF()) { rs.GetFieldValue((short)0, strCode); } if(!strCode.IsEmpty()){ MessageBox("已有此设备的编号,请重新输入设备的编号"); return; } //插入新的设备记录. sql.Format("Insert into device_info_tab(device_code," "device_name,description,oper_date," "buyer,lend_status,lend_id) " "VALUES(" "’%s’,’%s’,’%s’,to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’)" ",’%s’,0,0)",m_strDeviceCode,m_strDeviceName, m_strDescription,strDate,m_strBuyer); TRACE(sql); m_db.ExecuteSQL(sql); //向界面中插入新的设备信息. InsertDeviceInfoItem(m_strDeviceCode,m_strDeviceName, strDate,m_strBuyer,m_strDescription) ; //更新设备名称和设备编号列表框的数据 RefreshComboNameData(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } — 133 — OnBtnDeviceAdd 函数首先从界面中获取设备参数信息,并判断新设备的编号是否已经 存在。如果已存在,则提示用户重新输入设备编号,然后向数据库添加新的设备信息,显 示到界面上,最后要调用 RefreshComboNameData 函数刷新设备借出管理中的设备名称和 设备编号列表框中的数据。 如在设备库存管理中的“设备编号”文本框中输入 S0001,在“设备名称”文本框中 输入“扫描仪”,在“购买人”文本框中输入“张丽”,在“入库时间”日期时间控件中选 择 2004-6-23 12:33:36,在“设备说明”文本框中输入“购于中关村电脑城”,如图 4-4 所 示。 图 4-4 添加新设备对话框 单击“新设备入库”按钮,新设备的信息就添加到数据库中,并在设备信息列表框控 件中显示,如图 4-5 所示。 图 4-5 新设备添加之后对话框 在设备借出归还管理中的两个列表框中也能看到新添加的设备名称和设备编号,在 “请选择设备名称”列表框中选择“扫描仪”选项,就会在“请选择设备编号”列表框中 列出设备名称为“扫描仪”的所有设备编号,目前只有 S0001,如图 4-6 所示。由于目前 还没有添加设备名称列表框的 CBN_CLOSEUP(从列表框中的列表框选择数据,然后关掉 列表框)消息映射函数,这个消息映射函数的代码将在设备借出归还管理中介绍,所以目前 — 134 — 还不能在设备编号列表框中列出设备名称为“扫描仪”的所有设备编号。 图 4-6 设备名称和编号列表框显示信息对话框 2. 修改设备信息 为了用户修改设备信息的方便,可以添加设备信息列表框控件 IDC_LIST_DEVICE 的 NM_CLICK 消息映射函数 OnClickListDevice,代码如下: void CDeviceDBSDlg::OnClickListDevice(NMHDR* pNMHDR, LRESULT* pResult) { //获取已选择的记录项. int nItem = m_listDevice.GetNextItem(-1, LVNI_SELECTED); if(nItem != -1){ //从列表框控件的第 nItem+1 行获取数据,以将这些数据显示到设备参数的控件中. m_strDeviceCode = m_listDevice.GetItemText(nItem,0); m_strDeviceName = m_listDevice.GetItemText(nItem,1); CString strDT = m_listDevice.GetItemText(nItem,2); m_strBuyer = m_listDevice.GetItemText(nItem,3); m_strDescription = m_listDevice.GetItemText(nItem,4); m_oleOperDate.ParseDateTime(strDT); m_oleOperTime.ParseDateTime(strDT); //把设备列表框控件中当前已选择行的数据,更新到设备参数的控件中. UpdateData(FALSE); } *pResult = 0; } OnClickListDevice 函数首先获取选择的列表项 nItem,如果没有选择(nItem 为-1),则 不处理。然后获取选择的设备信息,并显示到设备信息的参数控件中。 修改设备信息的函数为 OnBtnDeviceMod,代码如下: void CDeviceDBSDlg::OnBtnDeviceMod() { int nItem = m_listDevice.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要修改的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要修改的设备信息"); return; } — 135 — CString strDeviceCode = m_listDevice.GetItemText(nItem,0); if(!UpdateData()) return; //构造入库时间 CString strDate = m_oleOperDate.Format("%Y-%m-%d") + " " + m_oleOperTime.Format("%H:%M:%S"); TRY{ CString sql; //更新数据库中的记录值. sql.Format("update device_info_tab " "set device_name = ’%s’," "description = ’%s’," "oper_date = to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’)," "buyer = ’%s’ " "where device_code = ’%s’",m_strDeviceName, m_strDescription,strDate,m_strBuyer,strDeviceCode); TRACE(sql); m_db.ExecuteSQL(sql); //更新列表框控件的值. m_listDevice.SetItemText(nItem,1,m_strDeviceName); m_listDevice.SetItemText(nItem,2,strDate); m_listDevice.SetItemText(nItem,3,m_strBuyer); m_listDevice.SetItemText(nItem,4,m_strDescription); //更新设备名称和设备编号列表框的数据 RefreshComboNameData(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } OnBtnDeviceMod 函数在获取修改的数据之后,调用更新设备信息的 SQL 语句将数 据更新到数据库中,同时修改界面的数据。最后要在 OnBtnDeviceMod 函数的末尾处调 用 RefreshComboNameData 函数,因为有可能更新了设备的名称,列表框中的数据也要 更新。 如从设备信息列表框控件中选择设备编号为 S0001 的设备,该设备的信息就会显示在 设备参数控件中,修改“设备说明”文本框中的数据为“购于海龙大厦”,单击“修改设备 信息”按钮,设备编号为 S0001 的设备就修改完成了,如图 4-7 所示。 — 136 — 图 4-7 修改设备信息对话框 3. 库存清理 库存清理就是清理库存中不能再用的设备,并删除该设备的所有借出信息,函数为 OnBtnDeviceDel,代码如下: void CDeviceDBSDlg::OnBtnDeviceDel() { int nItem = m_listDevice.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要修改的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要修改的设备信息"); return; } CString strDeviceCode = m_listDevice.GetItemText(nItem,0); TRY{ CString deletedSql; //首先删除设备编号为 strDeviceCode 的所有借出信息 deletedSql.Format("delete from device_lend_info_tab where device_code = ’%s’",strDeviceCode); m_db.ExecuteSQL(deletedSql); //然后删除设备编号为 strDeviceCode 设备信息记录 deletedSql.Format("delete from device_info_tab where device_code = ’%s’",strDeviceCode); m_db.ExecuteSQL(deletedSql); //从界面中删除记录信息. m_listDevice.DeleteItem(nItem); //更新设备名称和设备编号列表框的数据 RefreshComboNameData(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) — 137 — { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } OnBtnDeviceDel 函数首先从借出信息表中删除该设备的所有借出信息。然后删除设备 信息表中该设备的信息,并删除界面上的信息,最后调用 RefreshComboNameData 函数更 新两个列表框中的数据。 4.3.5 设备借出归还管理 设备借出归还管理包括设备借出的管理和设备归还的管理。 1. 设备借出 在处理设备借出的操作之前,添加一个设备名称列表框 IDC_COMBO_NAME 的 CBN_CLOSEUP 消息映射函数,函数为 OnCloseupComboName,代码如下: void CDeviceDBSDlg::OnCloseupComboName() { TRY{ CRecordset rs(&m_db); //打开所有的设备信息记录. CString sql; CString strSelected; //获取当前选择项目 int nIndex = m_comboName.GetCurSel(); //如果没有选择,退出 if(nIndex == -1) return; //获取当前选择的设备名称 m_comboName.GetLBText(nIndex,strSelected); //如果选项为空,退出 if(strSelected.IsEmpty()) return; //打开所有设备名称为 strSelected 的设备编号,因为会有相同的设备,但是 //设备的编号是不一样的,这样比较方便检索 sql.Format("select device_code from device_info_tab " "where device_name = ’%s’",strSelected); rs.Open(CRecordset::dynaset,sql); m_comboCode.ResetContent(); while (!rs.IsEOF()) { CString strCode; //获取设备编号字段值 rs.GetFieldValue((short)0, strCode); //向设备编号列表框添加名称为前面选择的设备名称的所有设备编号. m_comboCode.AddString(strCode); rs.MoveNext(); — 138 — } m_comboCode.SetCurSel(0); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } OnCloseupComboName 函数首先调用 CComboBox 的 GetCurSel 方法获取当前选择的项, 并调用 GetLBText 函数获取选择的设备名称值 strSelected,如果没有选择,则退出处理。然 后根据设备名称值从设备信息表 device_info_tab 中获取设备名称为 strSelected 的所有设备编 号并添加到设备编号列表框中,如在“请选择设备名称”列表框中选择“电视”,就会在“请 选择设备编号”列表框中列出所有的设备编号 ID:D0001 和 D0002,如图 4-8 所示。 图 4-8 选择设备编号对话框 处理设备借出的时候,需要输入借出人的姓名,选择设备名称和设备编号。然后单击 “借出”按钮完成借出的操作,处理借出的函数为 OnBtnLend,代码如下: void CDeviceDBSDlg::OnBtnLend() { //从界面控件中获取信息更新到控件变量中. if(!UpdateData()) return; if(m_strBorrower.IsEmpty()){ — 139 — MessageBox("请输入借书人的姓名"); return; } if(m_strSelectedName.IsEmpty()){ MessageBox("请选择设备名称"); return; } if(m_strSelectedCode.IsEmpty()){ MessageBox("请选择设备编号"); return; } TRY{ CRecordset rs(&m_db); CString sql; //获取需要借出设备的状态信息. sql.Format("Select lend_status,lend_id from device_info_tab " "where device_code = ’%s’",m_strSelectedCode); rs.Open(CRecordset::snapshot, sql); int lendStatus = 0; int lendID = 0 ; //如果有记录,说明设备已经借出,提示用户谁借了设备. if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) lendStatus = var.m_iVal; var.Clear(); //获取借出 ID,从而可以获取借出人的信息 rs.GetFieldValue(1, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) lendID = var.m_iVal; } rs.Close(); //如果已经借出 if(lendStatus == 1){ //获取借出人的姓名 sql.Format("Select borrower from device_lend_info_tab " "where lend_id = %d",lendID); rs.Open(CRecordset::snapshot, sql); CString borrower; if(!rs.IsEOF()) { rs.GetFieldValue((short)0, borrower); } rs.Close(); CString msg = "设备已经借出,借出人为:"; msg += borrower; //提示用户,谁已经借出了设备. MessageBox(msg); return; } — 140 — //从 SEQ_LEND_ID 序列中获取下一个值.这个值就是借出 ID. rs.Open(CRecordset::snapshot, "Select SEQ_LEND_ID.NEXTVAL from dual"); if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) lendID = var.m_iVal; } rs.Close(); //借出时间为当前时间. COleDateTime dt = COleDateTime::GetCurrentTime(); CString currentDT = dt.Format("%Y-%m-%d %H:%M:%S"); //插入借出信息记录. sql.Format("Insert into device_lend_info_tab(lend_id," "device_code,borrower,borrow_date," "return_date) " "VALUES(" "%d,’%s’,’%s’,to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’),null)", lendID,m_strSelectedCode, m_strBorrower,currentDT); TRACE(sql); m_db.ExecuteSQL(sql); //修改设备信息表中的借出状态和借出 ID. sql.Format("update device_info_tab " "set lend_status = 1," "lend_id = %d " "where device_code = ’%s’",lendID,m_strSelectedCode); TRACE(sql); m_db.ExecuteSQL(sql); //向界面中插入新的设备信息. InsertLendInfoItem(lendID,m_strSelectedCode, m_strSelectedName,m_strBorrower,currentDT,"") ; } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } 函数首先判断借出人、设备名称和设备编号的信息是否为空,如果为空,则提示输入 相应的信息。然后根据选择的设备编号查询设备信息表 device_info_tab 的 lend_status 字段 — 141 — 是否为零,如果为零,说明设备没有借走;如果不为零,则表明设备已经借走了,应该获 取借出 ID,从而根据借出 ID,查询借出信息表 device_lend_info_tab 获取借书人的姓名, 就可以提示用户设备已经借出和借出人的姓名。 如果没有借出,首先向借出信息表中插入一条借出信息记录,借出 ID 是从序列中获 取的。然后更新设备信息表 device_info_tab 的 lend_status 字段的为 1 和借出 ID 值,最后 在借出信息列表框控件中添加一行借出信息。 如在“请输入借出人”文本框中输入“李文”,在“请选择设备名称”列表框中选择 设备名称“电视”,在“请选择设备编号”列表框中选择设备编号 D0002,如图 4-9 所示。 图 4-9 设备借出对话框 单击“借出”按钮,在借出信息列表中显示借出信息,如图 4-10 所示。 图 4-10 设备借出之后对话框 如果再次单击“借出”按钮,弹出“设备已经借出,借出人为:李文”的信息提示框, 如图 4-11 所示。 图 4-11 设备已借出的提示信息对话框 2. 设备归还 设备归还只需提供设备编号的信息,在界面中选择设备编号之后可以进行设备归还的 — 142 — 处理。在设备归还的处理中需要判断设备是否已经借出,如果没有借出,应该有归还失败 的信息提示;如果设备已经借出,更新设备信息表中的借出 ID 和借出状态,以及借出信 息表中的归还时间。设备归还的函数为 OnBtnReturn,代码如下: void CDeviceDBSDlg::OnBtnReturn() { //从界面控件中获取信息更新到控件变量中. if(!UpdateData()) return; if(m_strSelectedName.IsEmpty()){ MessageBox("请选择设备名称"); return; } if(m_strSelectedCode.IsEmpty()){ MessageBox("请选择设备编号"); return; } TRY{ CRecordset rs(&m_db); CString sql; //查询设备是否已经借出,如果没有借出,则提示"此设备没有借出,归还失败"信息. sql.Format("Select lend_status,lend_id from device_info_tab " "where device_code = ’%s’",m_strSelectedCode); rs.Open(CRecordset::snapshot, sql); int lendStatus = 0; int lendID = 0 ; if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) lendStatus = var.m_iVal; var.Clear(); rs.GetFieldValue(1, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) lendID = var.m_iVal; } rs.Close(); if(lendStatus == 0){ MessageBox("此设备没有借出,归还失败"); return; } //设置借出信息表中的归还日期为当前日期. COleDateTime dt = COleDateTime::GetCurrentTime(); CString currentDT = dt.Format("%Y-%m-%d %H:%M:%S"); //插入借出信息记录. sql.Format("update device_lend_info_tab " "set return_date = to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’) " "where lend_id = %d",currentDT,lendID); TRACE(sql); — 143 — m_db.ExecuteSQL(sql); //设置设备信息表中的借出状态为 0,借出 ID 为 0,该设备可以被下次借出. sql.Format("update device_info_tab " "set lend_status = 0," "lend_id = 0 " "where device_code = ’%s’",m_strSelectedCode); TRACE(sql); m_db.ExecuteSQL(sql); //设置标志,表示在借出信息的列表框控件中是否含有要归还的设备 //信息,如果有就为 1,否则为 0 int flag = 0 ; //查询借出信息控件上是否含有借出 ID 为 LendID 的借出信息, //如果有直接更新界面上的归还日期值. for(int i = 0 ; i< m_listLend.GetItemCount(); i++){ int id = atoi(m_listLend.GetItemText(i,0)); if(id == lendID){ m_listLend.SetItemText(i,5,currentDT); flag = 1; } } //如果列表框控件中没有借出信息,则在借出信息列表框控件中的最后一行插入 //借出信息,归还日期为当前日期. if(flag == 0){ //从数据库中获取借出日期信息. sql.Format("Select borrower,borrow_date from device_lend_info_tab " "where lend_id = %d",lendID); rs.Open(CRecordset::snapshot, sql); CString borrower,date; if(!rs.IsEOF()) { rs.GetFieldValue((short)0, borrower); rs.GetFieldValue(1, date); } rs.Close(); InsertLendInfoItem(lendID,m_strSelectedCode, m_strSelectedName,borrower,date,currentDT) ; } } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } — 144 — OnBtnReturn 函数在向借出信息列表框控件中添加借出信息的时候需要判断借出信息 列表框控件中是否包含该设备的借出信息,如果有,只需修改借出信息中的归还时间;如 果没有,则添加一条新的借出信息。如在“请选择设备名称”列表框中选择设备名称“电 视”,在“请选择设备编号”列表框中选择设备编号 D0002,单击“归还”按钮,将在借出 信息列表框控件中看到一条完整的借出信息,设备的归还时间为当前时间,如图 4-12 所示。 图 4-12 设备归还对话框 现在以“王林”的身份借出所有的设备,所有的借出信息如图 4-13 所示。 图 4-13 借出所有设备之后对话框 退出设备管理系统,并重新运行,借出信息列表框控件不显示借出信息,因为借出的 信息比较多,只需在借出和归还成功之后显示借出信息。如在“请选择设备名称”列表框 中选择设备名称“电视”,在“请选择设备编号”列表框中选择设备编号 D0001,单击“归 还”按钮,就会在借出信息列表框控件中显示如图 4-14 所示的一条完整的借出信息。 图 4-14 设备归还对话框 — 145 — 4.3.6 统计信息管理 设备统计信息管理包括对设备借出历史信息、设备使用频率信息和未归还设备列表信 息的管理。 1. 借出历史统计 设备借出历史统计显示了一个设备所有的借出历史,便于跟踪设备的使用情况。为了 显示这些信息的方便,可以创建一个对话框,对话框类名称为 CLendInfoDlg,资源 ID 为 IDD_DIALOG_LEND_INFO,对话框名称为“借出统计信息”,“借出统计信息”对话框如 图 4-15 所示。 图 4-15 “借出统计信息”对话框 控件类型、ID 及说明见表 4-6。 表 4-6 控件列表 控件类型 ID 属 性 变量或函数 Label IDC_STATIC 请 选 择 设 备 名 称 无 Combo Box IDC_COMBO_NAME 无 列 表 框 控 件 变 量 m_comboName , CString 类 型 变 量 m_strSelectedName Label IDC_STATIC 请 选 择 设 备 编 号 无 Combo Box IDC_COMBO_CODE 无 列表框控件变量 m_comboCode,CString 类型变量 m_strSelectedCode Button IDC_BTN_SHOW 显示 函数 OnBtnShow ()显示借出历史统计信息 Button IDOK 退出 无 Group Box IDC_STATIC 借出信息 无 List Control IDC_LIST_LEND 无 列表框控件类型变量 m_listLend 为了直接在 CLendInfoDlg 类中操作数据库,需要定义一个公有的数据库指针,代码如 下: — 146 — public CDatabase *m_pDB; 在 CDeviceDBSDlg 类的 OnBtnLendList 函数中添加弹出“借出统计信息”对话框和传 入数据库连接指针的代码,代码如下: void CDeviceDBSDlg::OnBtnLendList() { //创建借出信息对话框实列. CLendInfoDlg dlg; //把数据库的指针传入对话框中. dlg.m_pDB = &m_db; //打开借出信息对话框. dlg.DoModal(); } 在 CDeviceDBSDlg 类中需要添加 OnInitDialog 和 OnCloseupComboName 两个消息映 射函数。在 OnInitDialog 函数中添加借出信息控件的显示列,包括借出 ID、借出人、设备 借出时间和设备归还时间,另外一个是设备名称列表框的 CBN_CLOSEUP 消息映射函数 OnCloseupComboName,和主界面中的 OnCloseupComboName 函数代码相似,不再列出。 在 CLendInfoDlg 的 OnInitDialog 函数中添加初始化设备信息列表框控件的代码,代码 如下: BOOL CLendInfoDlg::OnInitDialog() { CDialog::OnInitDialog(); //设置列表框控件扩展风格 DWORD dwExStyle = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP | LVS_EX_ONECLICKACTIVATE | LVS_EX_UNDERLINEHOT; m_listLend.SetExtendedStyle(dwExStyle); //初始化借出信息列表框控件 m_listLend.InsertColumn(0,"借出 ID",LVCFMT_CENTER,80); m_listLend.InsertColumn(1,"借出人",LVCFMT_CENTER,80); m_listLend.InsertColumn(2,"设备借出时间",LVCFMT_CENTER,140); m_listLend.InsertColumn(3,"设备归还时间",LVCFMT_CENTER,140); //更新设备名称和设备编号列表框的数据 RefreshComboNameData(); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } 在 CDeviceDBSDlg 类中也需要定义一个 RefreshComboNameData 函数,负责刷新设备 名称和设备编号列表框中的数据,函数和主界面上的 RefreshComboNameData 函数相似, 代码不再列出。在 OnInitDialog 函数的末尾处添加 RefreshComboNameData 函数的调用, 从而在列表框中显示设备名称和设备编号的信息,以供用户选择。 需要在 CLendInfoDlg 类中定义一个 InsertLendInfoItem 私有函数,负责向借出信息列 表框控件中添加借出信息,InsertLendInfoItem 函数的代码如下: void CLendInfoDlg::InsertLendInfoItem(int id,CString borrower,CString lendDate,CString returnDate) — 147 — { //获取当前的记录条数. int nIndex = m_listLend.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. m_listLend.InsertItem(&lvItem); //设置该行的其他列的值. m_listLend.SetItemText(nIndex,1,borrower); m_listLend.SetItemText(nIndex,2,lendDate); m_listLend.SetItemText(nIndex,3,returnDate); } 显示借出历史统计信息的函数为 OnBtnShow,代码如下: void CLendInfoDlg::OnBtnShow() { //从界面控件中获取信息更新到控件变量中. if(!UpdateData()) return; if(m_strSelectedName.IsEmpty()){ MessageBox("请选择设备名称"); return; } if(m_strSelectedCode.IsEmpty()){ MessageBox("请选择设备编号"); return; } m_listLend.DeleteAllItems(); TRY{ CRecordset rs(m_pDB); CString sql; //如果有提示用户,返回界面. sql.Format("Select * from device_lend_info_tab " "where device_code = ’%s’",m_strSelectedCode); rs.Open(CRecordset::snapshot, sql); while (!rs.IsEOF()) { CString borrower,brDate,rtDate; int lendID = 0; CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) lendID = var.m_iVal; var.Clear(); //获取设备名称字段值 rs.GetFieldValue(2, borrower); — 148 — rs.GetFieldValue(3, brDate); rs.GetFieldValue(4, rtDate); //向借出列表信息控件添加信息 InsertLendInfoItem(lendID,borrower,brDate,rtDate); rs.MoveNext(); } rs.Close(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } OnBtnShow 函数从界面中获取设备编号,然后在借出信息表中查询该设备编号的所有 借出信息,并显示在借出信息列表框控件中。如在“请选择设备名称”列表框中选择设备 名称“电视”,在“请选择设备编号”列表框中选择设备编号 D0001,单击“显示”按钮, 如图 4-16 所示。 图 4-16 “借出统计信息”对话框 在借出信息列表框控件中显示了王林于 2004-06-23 15:25:22 借出了设备 D0001,并且 于 2004-06-23 15:30:14 归还了设备。利用相同的方法可以查询其他设备的借出历史统计信 息。 — 149 — 2. 设备使用频率统计 设备使用频率统计显示了设备借出次数的统计信息。为了显示这些信息的方便,可以 创建一个对话框,对话框类名称为 CFreqListDlg,资源 ID 为 IDD_DIALOG_FREQ,对话 框名称为“设备使用频率统计信息”,“设备使用频率统计信息”对话框如图 4-17 所示。 图 4-17 “设备使用频率统计信息”对话框 控件类型、ID 及说明见表 4-7。 表 4-7 控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 使用频率信息 无 List Control IDC_LIST_LEND 无 列表框控件类型变量 m_listLend 为了直接在 CFreqListDlg 类中操作数据库,需要定义一个公有的数据库指针,代码如 下: public CDatabase *m_pDB; 在 CDeviceDBSDlg 类的 OnBtnFrequencyList 函数中添加弹出“设备使用频率统计信息” 对话框和传入数据库连接指针的代码,代码如下: void CDeviceDBSDlg::OnBtnFrequencyList() { //创建借出频率信息对话框实列. CFreqListDlg dlg; //把数据库的指针传入对话框中. dlg.m_pDB = &m_db; //打开借出频率信息对话框. dlg.DoModal(); } 需要在 CFreqListDlg 类中添加 OnInitDialog 的消息映射函数。在 OnInitDialog 函数中 添加使用频率信息列表框控件的显示列,包括设备编号、设备名称和使用次数,代码如下: BOOL CFreqListDlg::OnInitDialog() — 150 — { CDialog::OnInitDialog(); //设置列表框控件扩展风格 DWORD dwExStyle = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP | LVS_EX_ONECLICKACTIVATE | LVS_EX_UNDERLINEHOT; m_listLend.SetExtendedStyle(dwExStyle); //初始化借出信息列表框控件 //添加设备编号列 LV_COLUMN lvColumn; lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvColumn.fmt = LVCFMT_CENTER; lvColumn.cx = 80; lvColumn.iSubItem = 0; lvColumn.pszText = "设备编号"; m_listLend.InsertColumn(0, &lvColumn); //添加设备名称列 lvColumn.cx = 80; lvColumn.iSubItem = 1; lvColumn.pszText = "设备名称"; m_listLend.InsertColumn(1, &lvColumn); //添加使用次数列 lvColumn.cx = 140; lvColumn.iSubItem = 2; lvColumn.pszText = "使用次数"; m_listLend.InsertColumn(2, &lvColumn); InitCtrlData(); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } 在 CFreqListDlg 类中定义一个 InitCtrlData 函数,处理需要显示的使用频率信息,代码 如下: void CFreqListDlg::InitCtrlData() { TRY{ CRecordset rs(m_pDB); //打开记录. CString sql = "select a.DEVICE_CODE,b.device_name,count(a.DEVICE_CODE) " "from DEVICE_LEND_INFO_TAB a,device_info_tab b " "where a.device_code = b.device_code " "group by a.DEVICE_CODE,b.device_name"; rs.Open(CRecordset::dynaset, sql); while (!rs.IsEOF()) { int nCount; CString strCode,strName; //获取设备编号字段值 rs.GetFieldValue((short)0, strCode); //获取设备名称字段值 — 151 — rs.GetFieldValue(1, strName); CDBVariant var; //获取统计信息字段值 rs.GetFieldValue(2, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) nCount = var.m_iVal; //获取当前的记录条数. int nIndex = m_listLend.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; lvItem.pszText = (char*)(LPCTSTR)strCode; //第一列 //在最后一行插入记录值. m_listLend.InsertItem(&lvItem); //设置该行的其他列的值. m_listLend.SetItemText(nIndex,1,strName); CString temp; temp.Format("%d",nCount); m_listLend.SetItemText(nIndex,2,temp); rs.MoveNext(); } rs.Close(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } InitCtrlData 函数利用标准 SQL 语句中的 Group by 分组语句建立统计信息,从借出信 息表中获取相同设备编号借用的次数,并利用 where 语句建立两个表的关联,从而获取设 备的名称,最后将借出的统计信息显示在列表框控件中。 单击主界面上的“设备使用频率统计”按钮,弹出“设备使用频率统计信息”对话框, 在列表框控件中显示设备的使用频率信息,如图 4-18 所示。 — 152 — 图 4-18 “设备使用频率统计信息”对话框 从中可以看出 D0002 设备借用了两次,这和设备借出历史统计的两条借出信息相符合, 如图 4-19 所示。 图 4-19 “借出统计信息”对话框 3. 未归还设备列表 未归还设备列表显示了尚未归还的设备信息。为了显示这些信息的方便,可以创建一 个对话框,对话框类名称为 CDeviceNRtDlg,资源 ID 为 IDD_DIALOG_ NOTRET,对话 框名称为“未归还设备列表”,“未归还设备列表”对话框如图 4-20 所示。 图 4-20 “未归还设备列表”对话框 — 153 — 控件类型、ID 及说明见表 4-8。 表 4-8 控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 未归还设备信息 无 List Control IDC_LIST_DEVICE 无 列表框控件类型变量 m_listDevice 为了直接在 CDeviceNRtDlg 类中操作数据库,需要定义一个公有的数据库指针,代码 如下: public CDatabase *m_pDB; 在 CDeviceDBSDlg 类的 OnBtnLentDevice 函数中添加弹出“未归还设备列表”对话框 和传入数据库连接指针的代码,代码如下: void CDeviceDBSDlg::OnBtnLentDevice() { // TODO: Add your control notification handler code here //创建未归还信息对话框实列. CDeviceNRtDlg dlg; //把数据库的指针传入对话框中. dlg.m_pDB = &m_db; //打开未归还信息对话框. dlg.DoModal(); } 需要在 CDeviceNRtDlg 类中添加 OnInitDialog 的消息映射函数。在 OnInitDialog 函数 中添加未归还设备信息列表框控件的显示列,包括设备编号、设备名称、借出人和借出时 间,代码如下: BOOL CDeviceNRtDlg::OnInitDialog() { CDialog::OnInitDialog(); //设置列表框控件扩展风格 DWORD dwExStyle = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP | LVS_EX_ONECLICKACTIVATE | LVS_EX_UNDERLINEHOT; m_listDevice.SetExtendedStyle(dwExStyle); //初始化借出信息列表框控件 //添加设备编号列 LV_COLUMN lvColumn; lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvColumn.fmt = LVCFMT_CENTER; lvColumn.cx = 80; lvColumn.iSubItem = 0; lvColumn.pszText = "设备编号"; m_listDevice.InsertColumn(0, &lvColumn); //添加设备名称列 lvColumn.cx = 80; lvColumn.iSubItem = 1; lvColumn.pszText = "设备名称"; — 154 — m_listDevice.InsertColumn(1, &lvColumn); //添加借出人列 lvColumn.cx = 140; lvColumn.iSubItem = 2; lvColumn.pszText = "借出人"; m_listDevice.InsertColumn(2, &lvColumn); //添加设备借出时间列 lvColumn.cx = 140; lvColumn.iSubItem = 3; lvColumn.pszText = "借出时间"; m_listDevice.InsertColumn(3, &lvColumn); InitCtrlData(); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } 在 CFreqListDlg 类中定义一个 InitCtrlData 函数,处理需要显示的未归还设备信息,代 码如下: void CDeviceNRtDlg::InitCtrlData() { TRY{ CRecordset rs(m_pDB); //打开记录. CString sql = "select a.DEVICE_CODE,b.device_name,a.borrower,a.borrow_date " "from DEVICE_LEND_INFO_TAB a,device_info_tab b " "where a.lend_id = b.lend_id"; rs.Open(CRecordset::dynaset, sql); while (!rs.IsEOF()) { CString strCode,strName,strBorrower,strBorrowerDate; //获取设备编号字段值 rs.GetFieldValue((short)0, strCode); //获取设备名称字段值 rs.GetFieldValue(1, strName); //获取设备借出人字段值 rs.GetFieldValue(2, strBorrower); //获取设备借出时间字段值 rs.GetFieldValue(3, strBorrowerDate); //获取当前的记录条数. int nIndex = m_listDevice.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; lvItem.iSubItem = 0; lvItem.pszText = (char*)(LPCTSTR)strCode; //在最后一行插入记录值. m_listDevice.InsertItem(&lvItem); //设置该行的其他列的值. m_listDevice.SetItemText(nIndex,1,strName); — 155 — m_listDevice.SetItemText(nIndex,2,strBorrower); m_listDevice.SetItemText(nIndex,3,strBorrowerDate); rs.MoveNext(); } rs.Close(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } InitCtrlData 函数根据设备信息表 device_info_tab 中的 lend_id 值(因为已归还设备的 lend_id 值为 0),可以从借出信息列表中找到未归还设备的借出信息,并显示在列表框控件 中。单击主界面上的“未归还设备列表”按钮,弹出“未归还设备列表”对话框,在列表 框控件中显示未归还设备的信息,如图 4-21 所示。 图 4-21 “未归还设备列表”对话框 单击“退出”按钮,退回到主界面。在设备借出归还管理中,在“请选择设备名称” 列表框中选择设备名称“扫描仪”,在“请选择设备编号”列表框中选择设备编号 S0001, 单击“归还”按钮,设备归还成功信息如图 4-22 所示。 单击主界面上的“未归还设备列表”按钮,弹出“未归还设备列表”对话框,在列表 框控件中显示未归还设备的信息,如图 4-23 所示。 — 156 — 图 4-22 设备归还对话框 图 4-23 “未归还设备列表”对话框 现在只剩下两条未归还设备信息,少了刚才已经归还的扫描仪信息。 4.4 本章小结 本章详细介绍了企业设备管理系统的开发过程。在系统设计中要明确设备的惟一性, 否则系统很难实现设备的有效管理,因为公司通常会购买一批相同的设备,如果不对这些 设备进行区别,在以后的借出和归还管理中,很难确定是谁借走了哪一个设备,而且将来 要跟踪设备的使用情况也很难。在数据库的设计中一定要注意数据表的设计技巧,表的数 量不宜多,比如对于企业设备管理系统,仅用两个表就能够实现系统的核心功能,而且在 设备信息表中添加了借出 ID 和借出状态字段,能够很好地建立设备信息表和设备借出信 息表的关联,能够轻松地根据设备信息表的借出状态查出哪些设备已经借出,并借给了谁, 哪些设备还在库存里,可供用户借出。读者还可以在这两个表的基础上添加更多的应用, 如列出设备的库存情况,这样管理员能很清楚地知道库房里还有哪些设备,而不用到库房 里一个一个的清点。 企业设备管理系统是采用了 MFC ODBC 数据库开发技术,读者可以看出使用这种技 术开发数据库系统非常简便。在系统的实现过程中,在列表框控件中添加了 NM_CLICK 消息映射函数,方便用户的修改,用户只需要在列表框控件中选择希望修改的数据,这些 信息就会更新到界面的配置参数中。在列表框控件中添加了 CBN_CLOSEUP 消息映射函 数,方便用户选择设备编号。读者可以从企业设备管理系统的代码中学到很多开发数据库 系统的技巧,从而灵活应用到今后的数据库项目开发中。 第5章 人脉资源管理系统 人脉,是一个人的人际关系网络。根据对人力资源主管与求职者所进行的调查显示, 一多半的人力资源主管与求职者是透过人脉关系找到合适的人才或者工作的。因而,人脉 资源已成为一个人成功的有力因素,许多公司在招聘某一些职位的时候,会把求职者的人 脉资源作为一个重要的竞争优势。在我们日常的工作和生活中,常常会接触到很多人,但 是由于数量多,人们常会不经意的丢失了和某些人的联系方式,从而会失去联系,关系也 可能淡了,实在很可惜。如果我们能够对这些人的信息进行很好的管理,将是一个非常宝 贵的人脉资源库。当我们在生活中遇到困难或者需要建议的时候,就可以有人帮忙了。更 为重要的是,会给自己事业带来许多好处,使自己成为一个办事效率高、解决问题快的人, 更能得到领导的赏识,为自己的事业前途增添更多的机会。总之,长期累积人脉资源,能 够给自己创造更多的机会。人脉资源管理系统提供了对人脉信息的有效管理,而且还包括 丰富的查询和统计功能,能帮助用户管理好自己的人脉资源。 5.1 系统设计 系统设计是系统开发最为关键的一环,系统设计好了,系统的实现以及以后的系统测 试都会节省很多的物力和财力。通过系统的设计,开发人员能够更好地把握系统的需求, 了解系统的各功能模块。 5.1.1 功能描述 人脉资源管理系统包括系统配置信息管理、人脉信息管理和人脉查询管理,详细的功 能描述如下。 1. 系统配置信息管理 系统配置信息包括地区信息、认识途径信息、职位信息和行业信息。这些信息用来确 定一个人来自何方,通过什么方式认识的,以及在什么行业担当何种职位,这些都是一个 人脉信息的关键信息,而且这些信息比较固定,在系统运行之前就能确定下来,可把它们 作为系统配置信息来处理。系统配置信息管理就是对这些信息的管理,具体包括地区信息 管理、认识途径信息管理、职位信息管理和行业信息管理。 2. 人脉信息管理 人脉信息管理提供了人脉信息的添加、修改和删除的功能。人脉信息包括姓名、所在 地、职位、行业、所在单位、认识途径、联系方式和爱好等信息。 3. 人脉查询管理 人脉查询管理包括详细信息查询和统计信息查询的功能。详细信息查询提供了多种查 询方式,包括所在地区、认识途径、职位和行业查询方式,方便用户利用自己的人脉资源。 — 158 — 统计信息查询根据所在地区、认识途径、职位和行业信息进行人数信息的统计,便于用户 更加了解自己人脉情况。 5.1.2 功能模块设计 从上面的功能描述中,可以把人脉资源管理系统分为 3 个模块:系统配置信息管理、 人脉信息管理和人脉查询管理。在每一个模块下又提供了更为具体的功能。详细的人脉资 源管理系统的功能模块如图 5-1 所示。 图 5-1 系统功能模块图 5.2 数据库设计与实现 数据库设计是系统开发中非常重要的一个环节。数据库结构设计的好坏将直接影响到 系统的效率和功能的实现。在设计数据库之前,要了解数据库的需求,从而确定数据库的 结构。 5.2.1 数据库需求设计 通过以上的功能分析,人脉资源管理系统需要包含以下数据库信息。 1. 地方信息 包括地方 ID、地方名称。 2. 认识途径信息 包括认识途径 ID、认识途径名称。 3. 职位信息 包括职位 ID、职位名称。 — 159 — 4. 行业信息 包括行业 ID、行业名称。 5. 人脉信息 包括人脉 ID、姓名、所在地、职位、行业、所在单位、认识途径、e-mail 地址、固定 电话、移动电话、爱好和备注信息。 5.2.2 数据库表的设计 通过数据库的需求,可以创建以下数据表。 1. 地区信息表(area_info_tab) 地区信息表包括地区信息。地区信息的管理实际上是对地区信息表的管理,表的结构 见表 5-1。 表 5-1 地区信息表 字段名称 数据类型 可否为空 约束条件 说 明 area_id INTEGER NOT NULL 主键 地方 ID 值 area_name VARCHAR2(24) NOT NULL 无 地方名称 2. 认识途径信息表(approach_info_tab) 认识途径信息表包括认识途径种类信息。认识途径信息的管理实际上是对认识途径信 息表的管理,表的结构见表 5-2。 表 5-2 认识途径信息表 字段名称 数据类型 可否为空 约束条件 说 明 approach_id INTEGER NOT NULL 主键 认识途径 ID 值 approach _name VARCHAR2(24) NOT NULL 无 认识途径名称 3. 职位信息表(profession_info_tab) 职位信息表包括职位种类信息。职位信息的管理实际上是对职位信息表的管理,表的 结构见表 5-3。 表 5-3 职位信息表 字段名称 数据类型 可否为空 约束条件 说 明 profession_id INTEGER NOT NULL 主键 职位 ID 值 profession_name VARCHAR2(24) NOT NULL 无 职位名称 4. 行业信息表(vocation_info_tab) 行业信息表包括行业种类信息。行业信息的管理实际上是对行业信息表的管理,表的 结构见表 5-4。 — 160 — 表 5-4 行业信息表 字段名称 数据类型 可否为空 约束条件 说 明 vocation_id INTEGER NOT NULL 主键 行业 ID 值 vocation_name VARCHAR2(24) NOT NULL 无 行业名称 5. 人脉信息表(human_info_tab) 人脉信息表包括人脉信息。人脉信息的管理和人脉信息的查询都需要用到这个表,表 的结构见表 5-5。 表 5-5 人脉信息表 字段名称 数据类型 可否为空 约束条件 说 明 id INTEGER NOT NULL 主键 人脉 ID 值 name VARCHAR2(24) NOT NULL 无 姓名 area VARCHAR2(24) NULL 无 所在地 profession VARCHAR2(24) NULL 无 职位 vocation VARCHAR2(24) NULL 无 行业 company VARCHAR2(24) NULL 无 所在单位 approach VARCHAR2(24) NULL 无 认识途径 email VARCHAR2(24) NULL 无 e-mail 地址 phone VARCHAR2(24) NULL 无 固定电话 mobile VARCHAR2(24) NULL 无 移动电话 interest VARCHAR2(60) NULL 无 爱好 memo VARCHAR2(1000) NULL 无 备注 5.2.3 数据库表的创建 利用第 3 章中讲述的方法创建表空间 dbhr 和数据库用户 dbhr,其中数据库用户的密码 为 dbhr,选择的默认表空间为 dbhr。 创建人脉资源管理系统所有数据表的 SQL 语句如下: --创建地区信息表 CREATE TABLE area_info_tab( area_id INTEGER PRIMARY KEY, area_name VARCHAR(24) UNIQUE NOT NULL ); --创建认识途径信息表 CREATE TABLE approach_info_tab( approach_id INTEGER PRIMARY KEY, approach_name VARCHAR(24) UNIQUE NOT NULL ); --创建职位信息表 CREATE TABLE profession_info_tab( profession_id INTEGER PRIMARY KEY, profession_name VARCHAR(24) UNIQUE NOT NULL ); --创建行业信息表 CREATE TABLE vocation_info_tab( — 161 — vocation_id INTEGER PRIMARY KEY, vocation_name VARCHAR(24) UNIQUE NOT NULL ); --创建人脉信息表 CREATE TABLE human_info_tab( id INTEGER PRIMARY KEY, name VARCHAR2(24) NOT NULL, area VARCHAR2(24) NULL, profession VARCHAR2(24) NULL, vocation VARCHAR2(24) NULL, company VARCHAR2(24) NULL, approach VARCHAR2(24) NULL, email VARCHAR2(24) NULL, phone VARCHAR2(24) NULL, mobile VARCHAR2(24) NULL, interest VARCHAR2(60) NULL, memo VARCHAR2(1000) NULL ); --添加所在地 area 的索引 CREATE INDEX human_info_tab_areaindex ON human_info_tab(area); --添加 profession 的索引 CREATE INDEX human_info_tab_professionindex ON human_info_tab(profession); --添加所在地 vocation 的索引 CREATE INDEX human_info_tab_vocationindex ON human_info_tab(vocation); --添加所在地 approach 的索引 CREATE INDEX human_info_tab_approachindex ON human_info_tab(approach); --创建可以递增的系列号供人脉信息表的 id 使用 CREATE SEQUENCE seq_human_id INCREMENT BY 1 START WITH 10000 NOMAXVALUE NOMINVALUE NOCYCLE; --创建存储过程,提高数据库处理数据的速度. --该过程既可以向人脉信息表中添加数据也可以修改数据,因为过程先调用删除语句,再添加. --这个处理方法采用存储过程速度是非常快的. CREATE PROCEDURE add_human_info_tab ( param1 IN human_info_tab.id%TYPE, param2 IN human_info_tab.name%TYPE, param3 IN human_info_tab.area%TYPE, param4 IN human_info_tab.profession%TYPE, param5 IN human_info_tab.vocation%TYPE, param6 IN human_info_tab.company%TYPE, param7 IN human_info_tab.approach%TYPE, param8 IN human_info_tab.email%TYPE, param9 IN human_info_tab.phone%TYPE, param10 IN human_info_tab.mobile%TYPE, param11 IN human_info_tab.interest%TYPE, param12 IN human_info_tab.memo%TYPE ) AS — 162 — BEGIN DELETE FROM human_info_tab WHERE id = param1; INSERT INTO human_info_tab (id,name,area,profession,vocation,company,approach,email,phone,mobile,inter est,memo) VALUES(param1,param2,param3,param4,param5,param6,param7,param8,param9,p aram10,param11,param12); END; 上述的 SQL 语句创建了存储过程,存储过程的使用能提高数据库处理数据的速度。如 存储过程 add_human_info_tab 能够大大提高向数据库添加和修改人脉信息的速度。 利用 Oracle SQLPlus WorkSheet 工具执行上述的 SQL 语句从而创建数据库表和存储过 程。需要说明的是,在打开 Oracle SQLPlus Worksheet 的“Oracle Enterprise Manager 登录” 对话框的时候,需要在“用户名”文本框中输入企业设备管理系统的用户名 dbhr,在“口 令”文本框中输入用户密码 dbhr,在“服务”文本框中输入数据库的本地服务名 ORADB, 选择连接方式 Normal,登录成功之后,再运行上述的 SQL 语句。 在开发一个数据库应用系统的时候,可以向系统添加一些配置信息,相当于系统运行 的基本配置。可在人脉资源管理系统首次运行前添加一些地区信息、认识途径信息、职位 信息和行业信息。添加这些配置信息的 SQL 语句如下: --添加系统配置信息数据 --添加地区信息表 area_info_tab insert into area_info_tab(area_id,area_name) values(1,’北京’); insert into area_info_tab(area_id,area_name) values(2,’上海’); insert into area_info_tab(area_id,area_name) values(3,’天津’); insert into area_info_tab(area_id,area_name) values(4,’重庆’); insert into area_info_tab(area_id,area_name) values(5,’广州’); insert into area_info_tab(area_id,area_name) values(6,’中山’); insert into area_info_tab(area_id,area_name) values(7,’东莞’); insert into area_info_tab(area_id,area_name) values(8,’佛山’); insert into area_info_tab(area_id,area_name) values(9,’珠海’); insert into area_info_tab(area_id,area_name) values(10,’深圳’); insert into area_info_tab(area_id,area_name) values(11,’海口’); insert into area_info_tab(area_id,area_name) values(12,’三亚’); insert into area_info_tab(area_id,area_name) values(13,’武汉’); insert into area_info_tab(area_id,area_name) values(14,’荆门’); insert into area_info_tab(area_id,area_name) values(15,’南京’); insert into area_info_tab(area_id,area_name) values(16,’常熟’); insert into area_info_tab(area_id,area_name) values(17,’常州’); insert into area_info_tab(area_id,area_name) values(18,’昆山’); insert into area_info_tab(area_id,area_name) values(19,’苏州’); insert into area_info_tab(area_id,area_name) values(20,’杭州’); insert into area_info_tab(area_id,area_name) values(21,’温州’); insert into area_info_tab(area_id,area_name) values(22,’合肥’); insert into area_info_tab(area_id,area_name) values(23,’安庆’); insert into area_info_tab(area_id,area_name) values(24,’福州’); insert into area_info_tab(area_id,area_name) values(25,’厦门’); insert into area_info_tab(area_id,area_name) values(26,’兰州’); — 163 — insert into area_info_tab(area_id,area_name) values(27,’长沙’); insert into area_info_tab(area_id,area_name) values(28,’湘潭’); insert into area_info_tab(area_id,area_name) values(29,’南昌’); insert into area_info_tab(area_id,area_name) values(30,’九江’); --添加认识途径信息表 approach_info_tab insert into approach_info_tab(approach_id,approach_name) values(1,’同学’); insert into approach_info_tab(approach_id,approach_name) values(2,’同事’); insert into approach_info_tab(approach_id,approach_name) values(3,’邻居’); insert into approach_info_tab(approach_id,approach_name) values(4,’学友’); insert into approach_info_tab(approach_id,approach_name) values(5,’展会’); insert into approach_info_tab(approach_id,approach_name) values(6,’客户’); insert into approach_info_tab(approach_id,approach_name) values(7,’球友’); insert into approach_info_tab(approach_id,approach_name) values(8,’乘客’); insert into approach_info_tab(approach_id,approach_name) values(9,’俱乐部 会员’); insert into approach_info_tab(approach_id,approach_name) values(10,’室友’); --添加职位信息表 profession_info_tab insert into profession_info_tab(profession_id,profession_name) values(1,’ 董事长’); insert into profession_info_tab(profession_id,profession_name) values(2,’ 总经理’); insert into profession_info_tab(profession_id,profession_name) values(3,’ 市场经理’); insert into profession_info_tab(profession_id,profession_name) values(4,’ 销售经理’); insert into profession_info_tab(profession_id,profession_name) values(5,’ 研发经理’); insert into profession_info_tab(profession_id,profession_name) values(6,’ 软件工程师’); insert into profession_info_tab(profession_id,profession_name) values(7,’ 硬件工程师’); insert into profession_info_tab(profession_id,profession_name) values(8,’ 网络工程师’); insert into profession_info_tab(profession_id,profession_name) values(9,’ 技术总监’); insert into profession_info_tab(profession_id,profession_name) values(10,’ 项目经理’); insert into profession_info_tab(profession_id,profession_name) values(11,’ 测试工程师’); insert into profession_info_tab(profession_id,profession_name) values(12,’ 销售总监’); insert into profession_info_tab(profession_id,profession_name) values(13,’ 物流经理’); insert into profession_info_tab(profession_id,profession_name) values(14,’ 销售代表’); insert into profession_info_tab(profession_id,profession_name) values(15,’ 医药代表’); insert into profession_info_tab(profession_id,profession_name) values(16,’ 经销商’); insert into profession_info_tab(profession_id,profession_name) values(17,’ — 164 — 企业策划’); insert into profession_info_tab(profession_id,profession_name) values(18,’ 广告策划’); insert into profession_info_tab(profession_id,profession_name) values(19,’ 财务总监’); insert into profession_info_tab(profession_id,profession_name) values(20,’ 会计’); insert into profession_info_tab(profession_id,profession_name) values(21,’ 证券经纪人’); insert into profession_info_tab(profession_id,profession_name) values(22,’ 注册分析师’); insert into profession_info_tab(profession_id,profession_name) values(23,’ 投资顾问’); insert into profession_info_tab(profession_id,profession_name) values(24,’ 质量工程师’); insert into profession_info_tab(profession_id,profession_name) values(25,’ 教授’); insert into profession_info_tab(profession_id,profession_name) values(26,’ 副教授’); insert into profession_info_tab(profession_id,profession_name) values(27,’ 讲师’); insert into profession_info_tab(profession_id,profession_name) values(28,’ 学生’); insert into profession_info_tab(profession_id,profession_name) values(29,’ 导演’); insert into profession_info_tab(profession_id,profession_name) values(30,’ 摄影师’); --添加行业信息表 vocation_info_tab insert into vocation_info_tab(vocation_id,vocation_name) values(1,’娱乐’); insert into vocation_info_tab(vocation_id,vocation_name) values(2,’体育’); insert into vocation_info_tab(vocation_id,vocation_name) values(3,’教育’); insert into vocation_info_tab(vocation_id,vocation_name) values(4,’卫生’); insert into vocation_info_tab(vocation_id,vocation_name) values(5,’交通’); insert into vocation_info_tab(vocation_id,vocation_name) values(6,’环保’); insert into vocation_info_tab(vocation_id,vocation_name) values(7,’保险’); insert into vocation_info_tab(vocation_id,vocation_name) values(8,’制造业 ’); insert into vocation_info_tab(vocation_id,vocation_name) values(9,’服务业 ’); insert into vocation_info_tab(vocation_id,vocation_name) values(10,’房地产 ’); insert into vocation_info_tab(vocation_id,vocation_name) values(11,’林业’); insert into vocation_info_tab(vocation_id,vocation_name) values(12,’渔业’); insert into vocation_info_tab(vocation_id,vocation_name) values(13,’政府’); insert into vocation_info_tab(vocation_id,vocation_name) values(14,’科研’); insert into vocation_info_tab(vocation_id,vocation_name) values(15,’艺术’); --提交 commit; 利用 Oracle SQLPlus WorkSheet 工具执行上述添加配置信息的 SQL 语句,可以在数据 — 165 — 库表中添加 30 个地区信息、10 个认识途径信息、30 个职位信息和 15 个行业信息,而不用 在人脉资源管理系统上手动添加这些数据,将会很慢。 5.3 系统的实现 完成了系统功能模块的设计和数据库表的创建,就可以创建一个人脉资源管理系统。 5.3.1 创建应用程序 运行 Visual C++,选择 File|New 命令,弹出 New 对话框。从 Projects 列表中选择 MFC AppWizard(exe)向导,在 Location 文本框中选择项目工程的目录“D:\VCSAMPLE\”,在 Project name 文本框中输入工程文件的名称 HRDBS,单击 OK 按钮,进入 MFC AppWizard – Step 1 页面,从应用程序的类型中选择 Dialog based 选项,从语言列表中选择“中文(中 国)(APPWZCHS.DLL)”,单击 Finish 按钮,HRDBS 对话框的应用程序创建完毕。 5.3.2 创建主对话框的界面 主对话框的布局如图 5-2 所示。其中包括系统配置信息管理、人脉信息管理和人脉查 询管理 3 个部分。 图 5-2 “人脉资源管理系统”对话框 1. 系统配置信息管理 控件类型、ID 及说明见表 5-6。 — 166 — 表 5-6 系统配置信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 系统配置信息管理 无 Group Box IDC_STATIC 地区信息管理 无 Label IDC_STATIC 地区名称 无 Edit Box IDC_EDIT_CONFIG_AREA 无 CString 类型变量 m_strConfigArea List Control IDC_LIST_AREA 无 列表框控件类型变量 m_listArea Button IDC_BTN_AREA_ADD 添加 函数 OnBtnAreaAdd 添加地区信息 Button IDC_BTN_AREA_MOD 修改 函数 OnBtnAreaMod ()修改地区信息 Button IDC_BTN_AREA_DEL 删除 函数 OnBtnAreaDel ()删除地区信息 Group Box IDC_STATIC 认识途径信息管理 无 Label IDC_STATIC 认识途径 无 Edit Box IDC_EDIT_CONFIG_APPROACH 无 CString 类型变量 m_strConfigApproache List Control IDC_LIST_APPRAOCH 无 列表框控件类型变量 m_listApproach Button IDC_BTN_APPROACH_ADD 添加 函数 OnBtnApproachAdd ()添加认识途径信息 Button IDC_BTN_APPROACH_MOD 修改 函数 OnBtnApproachMod ()修改认识途径信息 Button IDC_BTN_APPROACH_DEL 删除 函数 OnBtnApproachDel ()删除认识途径信息 Group Box IDC_STATIC 职位信息管理 无 Label IDC_STATIC 职位名称 无 Edit Box IDC_EDIT_CONFIG_PROFESSION 无 CString 类型变量 m_strConfigProfession List Control IDC_LIST_PROFESSION 无 列表框控件类型变量 m_listProfession Button IDC_BTN_PROFESSION_ADD 添加 函数 OnBtnProfessionAdd ()添加职位信息 Button IDC_BTN_PROFESSION_MOD 修改 函数 OnBtnProfessionMod ()修改职位信息 Button IDC_BTN_PROFESSION_DEL 删除 函数 OnBtnProfessionDel ()删除职位信息 Group Box IDC_STATIC 行业信息管理 无 Label IDC_STATIC 行业名称 无 Edit Box IDC_EDIT_CONFIG_VOCATION 无 CString 类型变量 m_strConfigVocation List Control IDC_LIST_VOCATION 无 列表框控件类型变量 m_listVocation Button IDC_BTN_VOCATION_ADD 添加 函数 OnBtnVocationAdd ()添加行业信息 Button IDC_BTN_VOCATION_MOD 修改 函数 OnBtnVocationMod ()修改行业信息 Button IDC_BTN_VOCATION_DEL 删除 函数 OnBtnVocationDel ()删除行业信息 2. 人脉信息管理 控件类型、ID 及说明见表 5-7。 表 5-7 人脉信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 人脉信息管理 无 Label IDC_STATIC 姓名 无 Edit Box IDC_EDIT_NAME 无 CString 类型变量 m_strName Label IDC_STATIC 所在地区 无 Combo Box IDC_COMBO_AREA 无 列 表 框 控 件 变 量 m_comboArea , CString 类 型 变 量 m_strSelectedArea Label IDC_STATIC 职业 无 Combo Box IDC_COMBO_PROFESSION 无 列表框控件变量 m_comboProfession,CString 类型变量 m_strSelectedProfession Label IDC_STATIC 行业 无 Combo Box IDC_COMBO_VOCATION 无 列表框控件变量 m_comboVocation,CString 类型变量 m_strSelectedVocation Label IDC_STATIC 所在单位 无 Edit Box IDC_EDIT_COMPANY 无 CString 类型变量 m_strCompany Label IDC_STATIC 认识途径 无 Combo Box IDC_COMBO_APPROACH 无 列表框控件变量 m_comboApproach,CString 类型变量 m_strSelectedApproach — 167 — (续表) 控件类型 ID 属 性 变量或函数 Label IDC_STATIC e-mail 无 Edit Box IDC_EDIT_EMAIL 无 CString 类型变量 m_strEmail Label IDC_STATIC 固定电话 无 Edit Box IDC_EDIT_PHONE 无 CString 类型变量 m_strPhone Label IDC_STATIC 移动电话 无 Edit Box IDC_EDIT_MOBILE 无 CString 类型变量 m_strMobile Label IDC_STATIC 爱好 无 Edit Box IDC_EDIT_INTEREST 无 CString 类型变量 m_strInterest Label IDC_STATIC 说明 无 Edit Box IDC_EDIT_MEMO 无 CString 类型变量 m_strMemo Button IDC_BTN_HR_ADD 添加 函数 OnBtnHrAdd ()添加人脉信息 Button IDC_BTN_HR_MOD 修改 函数 OnBtnHrMod ()修改人脉信息 Button IDC_BTN_HR_DEL 删除 函数 OnBtnHrDel ()删除人脉信息 Button IDC_BTN_HR_QUERY 姓名查询 函数 OnBtnHrQuery ()用姓名方式查询 Group Box IDC_STATIC 人脉信息 无 List Control IDC_LIST_HR 无 列表框控件类型变量 m_listHR 3. 人脉查询管理 控件类型、ID 及说明见表 5-8。 表 5-8 人脉查询管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 人脉查询管理 无 Button IDC_BTN_DETAIL_QUERY 详细信息查询 函数 OnBtnDetailQuery ()详细信息查询 Button 统计信息查询 统计信息查询 函数 OnBtnStatQuery ()统计信息查询 Button ID_SYS_EXIT 系统退出 函数 OnSysExit ()系统退出 主对话框类名称为 CHRDBSDlg,资源 ID 为 IDD_HRDBS_DIALOG,对话框名称为“人 脉资源管理系统”。主界面用到了 5 个列表框控件分别显示系统配置信息和人脉信息。需要 为这 5 个列表框控件添加显示的列,从而显示相应的数据信息。为了代码设计的清晰,在 CHRDBSDlg 类中定义了一个 InitControl 私有函数负责添加控件的显示列,InitControl 函数 的代码如下: void CHRDBSDlg::InitControl() { //设置列表框控件扩展风格 DWORD dwExStyle = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP | LVS_EX_ONECLICKACTIVATE | LVS_EX_UNDERLINEHOT; m_listArea.SetExtendedStyle(dwExStyle); m_listApproach.SetExtendedStyle(dwExStyle); m_listProfession.SetExtendedStyle(dwExStyle); m_listVocation.SetExtendedStyle(dwExStyle); m_listHR.SetExtendedStyle(dwExStyle); //初始地区信息列表框控件 m_listArea.InsertColumn(0,"地区 ID",LVCFMT_CENTER,50); m_listArea.InsertColumn(1,"地区名称",LVCFMT_CENTER,90); //初始认识途径信息列表框控件 m_listApproach.InsertColumn(0,"认识途径 ID",LVCFMT_CENTER,50); — 168 — m_listApproach.InsertColumn(1,"认识途径名称",LVCFMT_CENTER,90); //初始职位信息列表框控件 m_listProfession.InsertColumn(0,"职位 ID",LVCFMT_CENTER,50); m_listProfession.InsertColumn(1,"职位名称",LVCFMT_CENTER,90); //初始行业信息列表框控件 m_listVocation.InsertColumn(0,"行业 ID",LVCFMT_CENTER,50); m_listVocation.InsertColumn(1,"行业名称",LVCFMT_CENTER,90); //初始人脉信息列表框控件 m_listHR.InsertColumn(0,"人脉 ID",LVCFMT_CENTER,60); m_listHR.InsertColumn(1,"姓名",LVCFMT_CENTER,80); m_listHR.InsertColumn(2,"所在地区",LVCFMT_CENTER,80); m_listHR.InsertColumn(3,"职业",LVCFMT_CENTER,80); m_listHR.InsertColumn(4,"行业",LVCFMT_CENTER,80); m_listHR.InsertColumn(5,"所在单位",LVCFMT_CENTER,80); m_listHR.InsertColumn(6,"认识途径",LVCFMT_CENTER,80); m_listHR.InsertColumn(7,"e-mail",LVCFMT_CENTER,80); m_listHR.InsertColumn(8,"固定电话",LVCFMT_CENTER,80); m_listHR.InsertColumn(9,"移动电话",LVCFMT_CENTER,80); m_listHR.InsertColumn(10,"爱好",LVCFMT_CENTER,80); m_listHR.InsertColumn(11,"说明",LVCFMT_CENTER,140); } 在 OnInitDialog 函数末尾处添加 InitControl 函数的调用,这样系统在启动的时候,就 可以看到已添加显示列的列表框控件。 5.3.3 显示数据到界面上 当系统启动之后,可以将数据库中的数据显示到界面上,需要处理数据库的连接和显 示数据到界面上的两个过程。 1. 数据库的连接 数据库的连接需要 3 个参数:ODBC 数据源名称、数据库用户名称和数据库用户密码。 其中数据源名称为 ORADB,数据库用户的名称和密码均为 dbhr。考虑到读者配置的数据 源和用户名可能不一样,可以从配置文件中获取这些参数信息,配置文件的格式如下: [General] 数据库数据源=oradb 数据库用户=dbhr 数据库密码=dbhr 把这段文字保存为 HRDBS.ini 文件,读者可以根据自己配置的数据源、数据库用户和 密码修改文件中相应的设置,并把 HRDBS.ini 文件放在 HRDBS.exe 运行程序的同一目录 下。 在 CHRDBSDlg 类中定义了一个私有类型的 ConnectDB 函数,处理数据库的连接,代 码如下: private: //使用 HRDBS.ini 配置文件获取连接数据库的参数 void ConnectDB(); — 169 — 在"HRDBSDlg.cpp"文件中的 ConnectDB()函数的实现代码如下: void CHRDBSDlg::ConnectDB() { char szPath[255]; //获取应用程序完全路径 ::GetModuleFileName(NULL,szPath,255); CString strFileName = szPath; //获取所在的目录名称 strFileName.Delete(strFileName.ReverseFind(’\\’)+1,strFileName.GetLengt h ()-strFileName.ReverseFind(’\\’)-1); //构造配置文件的完全路径 strFileName += "HRDBS.ini"; TCHAR sz[101]; memset(sz,0,sizeof(TCHAR)*101); //获取配置文件中数据库数据源的值,如果没有,默认值为 oradb GetPrivateProfileString(_T("General"),_T(" 数 据 库 数 据 源 "),_T("oradb"),sz,100,strFileName); CString strSource(sz); GetPrivateProfileString(_T("General"),_T(" 数 据 库 用 户 "),_T("dbhr"),sz,100,strFileName); CString strUser(sz); GetPrivateProfileString(_T("General"),_T(" 数 据 库 密 码 "),_T("dbhr"),sz,100,strFileName); CString strPwd(sz); //创建连接字符串. CString strConnect; strConnect.Format("DSN=%s;UID=%s;PWD=%s",strSource,strUser,strPwd); //打开数据库的连接,并且捕获异常 TRY{ m_db.OpenEx(strConnect,CDatabase::noOdbcDialog); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } — 170 — 当从文件中获取到数据库配置参数之后,就可以创建数据库连接字符串,利用 CDatabase 的 OpenEx 方法打开数据库的连接,并处理数据库的异常。 2. 显示数据到界面上 连接数据库之后,需要把数据库中的数据显示到 5 个列表框控件中。在 CHRDBSDlg 类中定义一个 InitCtrlData 私有函数,负责从数据库中读取数据并显示到列表框控件中。同 时还需要定义两个分别把数据插入到列表框控件中的函数,分别为:InsertConfigItem 和 InsertHRInfoItem。InsertConfigItem函数负责把数据插入到 4 个系统配置信息列表框控件中, 因为这 4 个控件都显示两列信息,可以共用一个函数,只需传入相应的列表框控件指针和 需要显示的数据,以区别不同的列表框控件。InsertHRInfoItem 函数负责把数据插入到人脉 信息列表框控件中。另外,还定义 4 个 函 数 , 分 别 为 : RefreshAreaComboData 、 RefreshApprComboData、RefreshProfComboData 和 RefreshVocComboData,负责刷新人脉 信息管理中的 4 个列表框的数据。它们根据数据库的最新数据刷新地区信息、认识途径信 息、职位信息和行业信息列表框中的数据。当添加、修改和删除这些信息之后,都需调用 相应的刷新函数更新列表框中的数据。在 HRDBSDlg.h 文件中添加这几个函数的定义,代 码如下: private: //使用 HRDBS.ini 配置文件获取连接数据库的参数 void ConnectDB(); //初始化列表框控件. void InitControl(); //从数据库获取设备信息并更新到控件中 void InitCtrlData(); //刷性地区信息列表框的数据 void RefreshAreaComboData(); //刷性认识途径信息列表框的数据 void RefreshApprComboData(); //刷性职位信息列表框的数据 void RefreshProfComboData(); //刷性行业信息列表框的数据 void RefreshVocComboData(); //向四个系统配置列表框控件中添加数据,因为地区信息、认识途径信息、职位信息和行业信息四 个列表框控件插入数据处理方法相似,可以共用一个函数.只需要传入一个 CListCtrl 类型的列表框控 件指针、id、名称值 void InsertConfigItem(CListCtrl* pList, int id, CString name); //向人脉信息列表框控件中添加数据 void InsertHRInfoItem(int id,CString name,CString area,CString prof,CString voc,CString com,CString appr,CString email,CString phone,CString mobile,CString interest,CString memo); 向 4 个系统配置信息列表框控件插入数据的函数是 InsertConfigItem,代码如下: void CHRDBSDlg::InsertConfigItem(CListCtrl* pList, int id, CString name) { //获取当前的记录条数. int nIndex = pList->GetItemCount(); — 171 — LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. pList->InsertItem(&lvItem); //设置该行的其他列的值. pList->SetItemText(nIndex,1,name); } 向人脉信息列表框控件插入数据的函数是 InsertHRInfoItem,代码如下: void CHRDBSDlg::InsertHRInfoItem(int id,CString name,CString area,CString prof,CString voc,CString com,CString appr,CString email,CString phone,CString mobile,CString interest,CString memo) { //获取当前的记录条数. int nIndex = m_listHR.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. m_listHR.InsertItem(&lvItem); //设置该行的其他列的值. m_listHR.SetItemText(nIndex,1,name); m_listHR.SetItemText(nIndex,2,area); m_listHR.SetItemText(nIndex,3,prof); m_listHR.SetItemText(nIndex,4,voc); m_listHR.SetItemText(nIndex,5,com); m_listHR.SetItemText(nIndex,6,appr); m_listHR.SetItemText(nIndex,7,email); m_listHR.SetItemText(nIndex,8,phone); m_listHR.SetItemText(nIndex,9,mobile); m_listHR.SetItemText(nIndex,10,interest); m_listHR.SetItemText(nIndex,11,memo); } 把数据库中的数据显示到界面上的函数为 InitCtrlData,代码如下: void CHRDBSDlg::InitCtrlData(){ if(!m_db.IsOpen()){ MessageBox("数据库未打开"); return; } — 172 — TRY{ CRecordset rs(&m_db); //打开所有的地区信息记录. rs.Open(CRecordset::dynaset, "select * from area_info_tab order by area_id"); while (!rs.IsEOF()) { int id; CString area; CDBVariant var; //获取地区 ID 字段值 rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) id = var.m_iVal; var.Clear(); //获取地区名称字段值 rs.GetFieldValue(1, area); InsertConfigItem(&m_listArea,id,area); rs.MoveNext(); } rs.Close(); //打开所有的认识途径信息记录. rs.Open(CRecordset::dynaset, "select * from approach_info_tab order by approach_id"); while (!rs.IsEOF()) { int id; CString approach; CDBVariant var; //获取认识途径 ID 字段值 rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) id = var.m_iVal; var.Clear(); //获取认识途径字段值 rs.GetFieldValue(1, approach); InsertConfigItem(&m_listApproach,id,approach); rs.MoveNext(); } rs.Close(); //打开所有的职位信息记录. rs.Open(CRecordset::dynaset, "select * from profession_info_tab order by profession_id"); while (!rs.IsEOF()) { int id; CString profession; CDBVariant var; //获取职位 ID 字段值 rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) id = var.m_iVal; var.Clear(); — 173 — //获取职位名称字段值 rs.GetFieldValue(1, profession); InsertConfigItem(&m_listProfession,id,profession); rs.MoveNext(); } rs.Close(); //打开所有的行业信息记录. rs.Open(CRecordset::dynaset, "select * from vocation_info_tab order by vocation_id"); while (!rs.IsEOF()) { int id; CString vocation; CDBVariant var; //获取行业 ID 字段值 rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) id = var.m_iVal; var.Clear(); //获取行业名称字段值 rs.GetFieldValue(1, vocation); InsertConfigItem(&m_listVocation,id,vocation); rs.MoveNext(); } rs.Close(); //打开所有的人脉信息记录 rs.Open(CRecordset::dynaset, "select * from human_info_tab order by id"); while (!rs.IsEOF()) { int id; CString name,area,profession,vocation,company; CString approach,email,phone,mobile,interest,memo; CDBVariant var; //获取人脉信息 ID 字段值 rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) id = var.m_iVal; var.Clear(); //获取姓名字段值 rs.GetFieldValue(1, name); //获取地区信息字段值 rs.GetFieldValue(2, area); //获取职业字段值 rs.GetFieldValue(3, profession); //获取行业字段值 rs.GetFieldValue(4, vocation); //获取所在单位字段值 rs.GetFieldValue(5, company); //获取认识途径字段值 rs.GetFieldValue(6, approach); //获取 email 字段值 rs.GetFieldValue(7, email); — 174 — //获取固定电话字段值 rs.GetFieldValue(8, phone); //获取移动电话字段值 rs.GetFieldValue(9, mobile); //获取爱好字段值 rs.GetFieldValue(10, interest); //获取备注字段值 rs.GetFieldValue(11, memo); //向人脉信息列表框控件中加入新的一行信息. InsertHRInfoItem(id,name,area,profession,vocation,company,approach, email,phone,mobile,interest,memo); rs.MoveNext(); } rs.Close(); //更新列表框的数据 RefreshAreaComboData(); RefreshApprComboData(); RefreshProfComboData(); RefreshVocComboData(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } InitCtrlData 函数将所有的系统配置信息和人脉信息显示到列表框控件中,最后调用 4 个列表框刷新函数更新人脉信息管理中的 4 个列表框的数据。 刷新地区信息列表框的数据的函数是 RefreshAreaComboData,代码如下: void CHRDBSDlg::RefreshAreaComboData() { m_comboArea.ResetContent(); TRY{ CRecordset rs(&m_db); //打开所有的地区信息记录. — 175 — rs.Open(CRecordset::dynaset, "select * from area_info_tab"); while (!rs.IsEOF()) { CString strName; //获取地区信息名称字段值 rs.GetFieldValue(1, strName); //向地区信息名称列表框添加地区信息名称. m_comboArea.AddString(strName); rs.MoveNext(); } m_comboArea.SetCurSel(0); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } 刷新认识途径信息列表框中的数据的函数为 RefreshApprComboData、刷新职位信息列 表框中的数据的函数为 RefreshProfComboData、刷新行业信息列表框中的数据的函数 RefreshVocComboData 处理方法和 RefreshAreaComboData 函数相同,代码不再列出。 在 HRDBSDlg.cpp 文件的 OnInitDialog 函数末尾处添加初始化列表框控件列和连接数 据库并显示数据的代码,代码如下: //初始化列表框控件 InitControl(); //连接数据库 ConnectDB(); //在列表框控件中显示数据 InitCtrlData(); 系统在启动的时候,会自动连接数据库,数据库连接成功后,就在列表框控件中显示 数据库中的信息,如图 5-3 所示。 — 176 — 图 5-3 连接数据库之后的对话框 5.3.4 系统配置信息管理 系统配置信息管理包括对地区信息、认识途径信息、职位信息和行业信息的管理。 1. 地区信息管理 地区信息管理包括对地区信息的添加、修改和删除操作。尽管在创建数据库表的时候, 添加了一些地区信息,但是这些信息不可能全面,如可能结交国外的一些朋友,这样,地 区信息很难定了,而在地区信息管理中添加这个地区信息很方便。 向地区信息列表框控件添加地区信息的函数是 OnBtnAreaAdd,代码如下: void CHRDBSDlg::OnBtnAreaAdd() { //从界面控件中获取信息更新到控件变量中. if(!UpdateData()) return; if(m_strConfigArea.IsEmpty()) return; TRY{ CRecordset rs(&m_db); — 177 — CString sql; //打开记录集,获取最大的地区 ID 值. rs.Open(CRecordset::dynaset, "Select max(area_id) from area_info_tab"); //设置新添加记录的地区 ID 值. int newID = 1; //如果数据库里面已经有记录了,则新的地区 ID 是地区 ID 最大值+1 if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) newID = var.m_iVal + 1; } //插入新的地区信息记录. sql.Format("Insert into area_info_tab(area_id," "area_name) " "VALUES(" "%d,’%s’)",newID,m_strConfigArea); TRACE(sql); m_db.ExecuteSQL(sql); //向界面中插入新的地区信息. InsertConfigItem(&m_listArea,newID,m_strConfigArea); //更新地区信息列表框的数据 RefreshAreaComboData(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } OnBtnAreaAdd 函数首先向地区信息表添加新的地区信息,然后向地区信息列表框控 件中添加显示数据,这时调用 InsertConfigItem 函数,并传入&m_listArea 指针、地区信息 ID 和地区名称,最后调用 RefreshAreaComboData 更新人脉信息管理中的地区信息列表框 控件的数据。 如在地区信息管理中的“地区名称”文本框中输入“襄樊”,单击“添加”按钮,就 可以在地区信息列表框控件中看到新添加的“襄樊”地区信息,如图 5-4 所示。 — 178 — 图 5-4 添加地区信息对话框 同时在人脉信息管理中的地区信息列表框控件中也会增加一条“襄樊”的地区信息数 据,如图 5-5 所示。 图 5-5 所在地区列表框显示信息对话框 为了用户修改地区信息的方便,可以添加地区信息列表框控件 IDC_LIST_AREA 的 NM_CLICK 消息映射函数 OnClickListArea,代码如下: void CHRDBSDlg::OnClickListArea(NMHDR* pNMHDR, LRESULT* pResult) { int nItem = m_listArea.GetNextItem(-1, LVNI_SELECTED); if(nItem != -1){ //从列表框控件中获取选择的地区信息. CString name = m_listArea.GetItemText(nItem,1); //将选择的地区信息显示在 ID 为 IDC_EDIT_CONFIG_AREA 的编辑框控件中 GetDlgItem(IDC_EDIT_CONFIG_AREA)->SetWindowText(name); } *pResult = 0; } 要修改地区信息,只需要在地区信息列表框控件中选择要修改的地区,地区名称就会 — 179 — 显示在“地区名称”文本框中。然后修改文本框的数据,单击“修改”按钮可以完成修改 地区信息的操作。修改地区信息的函数为 OnBtnAreaMod,代码如下: void CHRDBSDlg::OnBtnAreaMod() { if(!UpdateData()) return; if(m_strConfigArea.IsEmpty()) return; int nItem = m_listArea.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要修改的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要修改的地区信息"); return; } //从列表框控件中获取选择的地区信息. int id = atoi(m_listArea.GetItemText(nItem,0)); TRY{ CRecordset rs(&m_db); CString sql; //更新新的地区信息记录. sql.Format("update area_info_tab " "set area_name = ’%s’ " "where area_id = %d",m_strConfigArea,id); TRACE(sql); m_db.ExecuteSQL(sql); //修改界面上的值. m_listArea.SetItemText(nItem,1,m_strConfigArea); //更新地区信息列表框的数据 RefreshAreaComboData(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } 删除地区信息的函数为 OnBtnAreaDel,代码如下: void CHRDBSDlg::OnBtnAreaDel() { int nItem = m_listArea.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要删除的记录,返回. — 180 — if(nItem == -1){ AfxMessageBox("没有选择要删除的地区信息"); return; } //从列表框控件中获取选择的地区信息. int id = atoi(m_listArea.GetItemText(nItem,0)); TRY{ CString deletedSql; //删除地区信息 deletedSql.Format("delete from area_info_tab where area_id = %d",id); m_db.ExecuteSQL(deletedSql); //删除界面上的数据 m_listArea.DeleteItem(nItem); //更新地区信息数据 RefreshAreaComboData(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } 2. 其他配置信息管理 认识途径信息管理、职位信息管理与行业信息管理的处理方法和地区信息管理处理的 方法非常相似,代码不再列出。 5.3.5 人脉信息管理 人脉信息管理包括添加人脉信息、修改人脉信息、删除人脉信息和姓名查询。 1. 添加人脉信息 添加人脉信息的函数为 OnBtnHrAdd,代码如下: void CHRDBSDlg::OnBtnHrAdd() { if(!UpdateData()) return; if(m_strName.IsEmpty()){ AfxMessageBox("姓名不能够为空"); return; } TRY{ — 181 — m_db.BeginTrans(); CRecordset rs(&m_db); CString sql; //获取新的人脉 ID 值. rs.Open(CRecordset::dynaset, "Select seq_human_id.NEXTVAL from human_info_tab"); int newID = 1; if(!rs.IsEOF()) { CDBVariant var; rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) newID = var.m_iVal ; } //插入新的人脉信息记录. sql.Format("call add_human_info_tab(%d,’%s’,’%s’,’%s’,’%s’,’%s’," "’%s’,’%s’,’%s’,’%s’,’%s’,’%s’)",newID,m_strName,m_strSelectedArea, m_strSelectedProfession,m_strSelectedVocation,m_strCompany,m_strSelecte dApproach, m_strEmail,m_strPhone,m_strMobile,m_strInterest,m_strMemo); TRACE(sql); m_db.ExecuteSQL(sql); //向界面中插入新的人脉信息. InsertHRInfoItem(newID,m_strName,m_strSelectedArea,m_strSelectedProfess ion,m_strSelectedVocation,m_strCompany,m_strSelectedApproach, m_strEmail,m_strPhone,m_strMobile,m_strInterest,m_strMemo); m_db.CommitTrans(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); m_db.Rollback(); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); m_db.Rollback(); } END_CATCH } OnBtnHrAdd 函数使用存储过程来添加人脉信息,调用存储过程很方便,利用 CDataBase 类的 ExecuteSQL 方法,其中传入调用存储过程的 SQL 语句。调用存储过程就 如同像 Oracle 数据库调用数据库内部的一个函数一样,速度很快,因而使用存储过程能 够大大提高数据的处理速度。由于人脉信息表 human_info_tab 的数据通常会很多,而在 — 182 — 数据量很大的时候,利用通常的方法添加数据会比较慢,而利用存储过程能解决速度问 题。调用存储过程的 SQL 语句格式如 OnBtnHrAdd 函数中所示,利用 CALL 命令调用存 储 过 程 add_human_info_tab , 函 数 带 了 12 个参数,这些参数对应数据表 add_human_info_tab 的 12 个字段信息,可以在数据库表的创建一节中看到这个存储过程 的详细定义,存储过程中先调用 Delete 语句删除数据库中包含新添加人脉 ID 的人脉信息, 然后再调用 Insert 语句插入人脉信息。这样处理的好处,是添加和修改人脉信息可以使 用相同的存储过程。 OnBtnHrAdd 函数使用了事务处理。调用 CDataBase 类的 BeginTrans 方法表明一个事 务的开始,在进行一批数据库操作之后,调用 CDataBase 的 CommitTrans 方法结束这个事 务,从而把事务中包含的数据库操作更新到数据库中,而如果要取消这批数据库的操作, 只需调用 CDataBase 类的 Rollback 方法撤销这个事务,数据库中的数据不会发生变化。事 务常用来处理一批关联的数据库操作,如银行的转账处理,需要存款和销账的两个操作要 么都成功,要么都失败。 如在人脉信息管理的“姓名”文本框中输入“章荣”,在“所在地区”列表框中选择 “广州”,在“职业”列表框中选择“广告策划”,在“行业”列表框中选择“房地产”,在 “所在单位”文本框中输入“广州房地产公司”,在“认识途径”列表框中选择“乘客”, 在 e-mail 文本框中输入zhangr@sohu.com,在“固定电话”文本框中输入 02291234567,在 “移动电话”文本框中输入 1362781278,在“爱好”文本框中输入“篮球”,在“说明” 文本框中输入“火车上交谈,一见如故”,如图 5-6 所示。 图 5-6 添加人脉信息对话框 单击“添加”按钮,人脉信息显示到列表框控件中,如图 5-7 所示。 — 183 — 图 5-7 添加人脉信息之后的对话框 2. 修改人脉信息 为了用户修改人脉信息的方便,可以添加人脉信息列表框控件 IDC_LIST_HR 的 NM_CLICK 消息映射函数 OnClickListHr,代码如下: void CHRDBSDlg::OnClickListHr(NMHDR* pNMHDR, LRESULT* pResult) { int nItem = m_listHR.GetNextItem(-1, LVNI_SELECTED); if(nItem != -1){ //从列表框控件的第 nItem+1 行获取数据,以将这些数据显示到人脉信息配置参数控件中. m_strName = m_listHR.GetItemText(nItem,1); m_strSelectedArea = m_listHR.GetItemText(nItem,2); m_strSelectedProfession = m_listHR.GetItemText(nItem,3); m_strSelectedVocation = m_listHR.GetItemText(nItem,4); m_strCompany = m_listHR.GetItemText(nItem,5); m_strSelectedApproach = m_listHR.GetItemText(nItem,6); m_strEmail = m_listHR.GetItemText(nItem,7); m_strPhone = m_listHR.GetItemText(nItem,8); m_strMobile = m_listHR.GetItemText(nItem,9); m_strInterest = m_listHR.GetItemText(nItem,10); m_strMemo = m_listHR.GetItemText(nItem,11); //把设备列表框控件中当前已选择行的数据,更新到人脉信息参数的控件中. UpdateData(FALSE); } *pResult = 0; } 修改人脉信息的函数为 OnBtnHrMod,代码如下: void CHRDBSDlg::OnBtnHrMod() { if(!UpdateData()) — 184 — return; if(m_strName.IsEmpty()){ AfxMessageBox("姓名不能够为空"); return; } int nItem = m_listHR.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要修改的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要修改的人脉信息"); return; } //从列表框控件中获取选择的人脉信息. int id = atoi(m_listHR.GetItemText(nItem,0)); TRY{ m_db.BeginTrans(); CRecordset rs(&m_db); CString sql; //插入新的人脉信息记录. sql.Format("call add_human_info_tab(%d,’%s’,’%s’,’%s’,’%s’,’%s’," "’%s’,’%s’,’%s’,’%s’,’%s’,’%s’)",id,m_strName,m_strSelectedArea, m_strSelectedProfession,m_strSelectedVocation,m_strCompany,m_strSelecte dApproach, m_strEmail,m_strPhone,m_strMobile,m_strInterest,m_strMemo); TRACE(sql); m_db.ExecuteSQL(sql); //向界面中插入新的人脉信息. //设置该行的其他列的值. m_listHR.SetItemText(nItem,1,m_strName); m_listHR.SetItemText(nItem,2,m_strSelectedArea); m_listHR.SetItemText(nItem,3,m_strSelectedProfession); m_listHR.SetItemText(nItem,4,m_strSelectedVocation); m_listHR.SetItemText(nItem,5,m_strCompany); m_listHR.SetItemText(nItem,6,m_strSelectedApproach); m_listHR.SetItemText(nItem,7,m_strEmail); m_listHR.SetItemText(nItem,8,m_strPhone); m_listHR.SetItemText(nItem,9,m_strMobile); m_listHR.SetItemText(nItem,10,m_strInterest); m_listHR.SetItemText(nItem,11,m_strMemo); m_db.CommitTrans(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); m_db.Rollback(); } AND_CATCH(CException,e) { TCHAR szError[100]; — 185 — e->GetErrorMessage(szError,100); AfxMessageBox (szError); m_db.Rollback(); } END_CATCH } 在 OnBtnHrMod 函数中也使用了存储过程和事物处理。如要修改“章荣”的人脉信息, 修改“所在单位”文本框中的数据为“广州房地产开发公司”。然后单击“修改”按钮,将 在列表框控件中列出修改之后的数据,如图 5-8 所示。 图 5-8 修改人脉信息对话框 3. 删除人脉信息 删除人脉信息的函数为 OnBtnHrDel,代码如下: void CHRDBSDlg::OnBtnHrDel() { int nItem = m_listHR.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要删除的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要删除的人脉信息"); return; } //从列表框控件中获取选择的人脉信息. int id = atoi(m_listHR.GetItemText(nItem,0)); TRY{ m_db.BeginTrans(); CString deletedSql; //删除人脉信息 deletedSql.Format("delete from human_info_tab where id = %d",id); m_db.ExecuteSQL(deletedSql); //删除界面上的数据 — 186 — m_listHR.DeleteItem(nItem); m_db.CommitTrans(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); m_db.Rollback(); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); m_db.Rollback(); } END_CATCH } 4. 姓名查询 姓名查询提供了按照姓名的方式查询人脉信息的方法,函数为 OnBtnHrQuery,代码如 下: void CHRDBSDlg::OnBtnHrQuery() { if(!UpdateData()) return; if(m_strName.IsEmpty()){ AfxMessageBox("姓名不能够为空"); return; } if(!m_db.IsOpen()){ MessageBox("数据库未打开"); return; } m_listHR.DeleteAllItems(); TRY{ CRecordset rs(&m_db); //打开所有的人脉信息记录,根据提供的姓名模糊查询 CString temp = "%"; CString sql; sql.Format("select * from human_info_tab where name like ’%s%s%s’", temp,m_strName,temp); rs.Open(CRecordset::dynaset, sql); while (!rs.IsEOF()) { int id; CString name,area,profession,vocation,company; CString approach,email,phone,mobile,interest,memo; CDBVariant var; //获取人脉信息 ID 字段值 — 187 — rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) id = var.m_iVal; var.Clear(); //获取姓名字段值 rs.GetFieldValue(1, name); //获取地区信息字段值 rs.GetFieldValue(2, area); //获取职业字段值 rs.GetFieldValue(3, profession); //获取行业字段值 rs.GetFieldValue(4, vocation); //获取所在单位字段值 rs.GetFieldValue(5, company); //获取认识途径字段值 rs.GetFieldValue(6, approach); //获取 email 字段值 rs.GetFieldValue(7, email); //获取固定电话字段值 rs.GetFieldValue(8, phone); //获取移动电话字段值 rs.GetFieldValue(9, mobile); //获取爱好字段值 rs.GetFieldValue(10, interest); //获取备注字段值 rs.GetFieldValue(11, memo); //向人脉信息列表框控件中加入新的一行信息. InsertHRInfoItem(id,name,area,profession,vocation,company,approach, email,phone,mobile,interest,memo); rs.MoveNext(); } rs.Close(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } — 188 — END_CATCH } OnBtnHrQuery 函数采用了模糊查询的方式,如输入姓名“刘”,会在人脉信息列表框 控件中显示“刘成”的人脉信息,如图 5-9 所示。 图 5-9 姓名查询页面 5.3.6 人脉查询管理 人脉查询管理包括详细信息查询和统计信息查询的功能。 1. 详细信息查询 详细信息查询提供了根据所在地区、职业、行业和认识途径方式查询人脉信息的功能。 如希望查询北京的所有人脉信息,可以根据地区名称查询所有在北京的人脉信息。为了显 示这些信息的方便,可以创建一个对话框,对话框类名称为 CDetailQueryDlg,资源 ID 为 IDD_DIALOG_QUERY_DETAIL,对话框名称为“详细信息查询”,“详细信息查询”对话 框如图 5-10 所示。 图 5-10 “详细信息查询”对话框 — 189 — 控件类型、ID 及说明见表 5-9。 表 5-9 控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 查询方式 无 Label IDC_STATIC 所在地区 无 Combo Box IDC_COMBO_AREA 无 列 表 框 控 件 变 量 m_comboArea , CString 类 型 变 量 m_strSelectedArea Label IDC_STATIC 职业 无 Combo Box IDC_COMBO_PROFESSION 无 列表框控件变量 m_comboProfession,CString 类型变量 m_strSelectedProfession Label IDC_STATIC 行业 无 Combo Box IDC_COMBO_VOCATION 无 列 表 框 控 件 变 量 m_comboVocation , CString 类 型 变 量 m_strSelectedVocation Label IDC_STATIC 认识途径 无 Combo Box IDC_COMBO_APPROACH 无 列 表 框 控 件 变 量 m_comboApproach,CString 类 型 变 量 m_strSelectedApproach Group Box IDC_STATIC 人脉信息 无 List Control IDC_LIST_HR 无 列表框控件类型变量 m_listHR 为了直接在 CDetailQueryDlg 类中操作数据库,需要定义一个公有的数据库指针,代 码如下: public CDatabase *m_pDB; 在 CHRDBSDlg 类的 OnBtnDetailQuery 函数中添加弹出“详细信息查询”对话框和传 入数据库连接指针的代码,代码如下: void CHRDBSDlg::OnBtnDetailQuery() { //创建详细信息查询对话框实列. CDetailQueryDlg dlg; //把数据库的指针传入对话框中. dlg.m_pDB = &m_db; //打开详细信息查询对话框. dlg.DoModal(); } 在 CDetailQueryDlg 类中添加 OnInitDialog 消息映射函数,在其中添加人脉信息列表框 控件的显示列,和主界面上的人脉信息列表框控件相同。还要在 CDetailQueryDlg 类中定 义一个 InitComboData 私有函数,负责向所在地区、职业、行业和认识途径 4 个列表框控 件添加可供选择的数据,代码如下: void CDetailQueryDlg::InitComboData() { TRY{ CRecordset rs(m_pDB); //打开所有的地区信息记录. rs.Open(CRecordset::dynaset, "select * from area_info_tab"); while (!rs.IsEOF()) { CString strName; //获取地区信息名称字段值 — 190 — rs.GetFieldValue(1, strName); //向地区信息名称列表框添加地区信息名称. m_comboArea.AddString(strName); rs.MoveNext(); } rs.Close(); m_comboArea.SetCurSel(0); rs.Open(CRecordset::dynaset, "select * from approach_info_tab"); while (!rs.IsEOF()) { CString strName; //获取认识途径信息名称字段值 rs.GetFieldValue(1, strName); //向认识途径信息名称列表框添加认识途径信息名称. m_comboApproach.AddString(strName); rs.MoveNext(); } rs.Close(); m_comboApproach.SetCurSel(0); rs.Open(CRecordset::dynaset, "select * from profession_info_tab"); while (!rs.IsEOF()) { CString strName; //获取职位信息名称字段值 rs.GetFieldValue(1, strName); //向职位信息名称列表框添加职位信息名称. m_comboProfession.AddString(strName); rs.MoveNext(); } rs.Close(); m_comboProfession.SetCurSel(0); rs.Open(CRecordset::dynaset, "select * from vocation_info_tab"); while (!rs.IsEOF()) { CString strName; //获取行业信息名称字段值 rs.GetFieldValue(1, strName); //向行业信息名称列表框添加行业信息名称. m_comboVocation.AddString(strName); rs.MoveNext(); } m_comboVocation.SetCurSel(0); rs.Close(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); — 191 — } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } 在 OnInitDialog 函数的末尾处调用 InitComboData 函数,来加载 4 个列表框中的显示 数据。 为了方便用户根据所在地区、职业、行业和认识途径方式查询人脉信息,需要添加所 在地区、职业、行业和认识途径 4 个列表框控件的 CBN_CLOSEUP 消息映射函数,分别为: OnCloseupComboArea 、 OnCloseupComboApproach 、 OnCloseupComboProfession 和 OnCloseup ComboVocation。这样选择列表框中的项目之后,就可以根据选择的项目查询数 据库并显示在人脉信息列表框控件中。这些消息映射函数都需要查询数据库和在列表框控 件中显示数据,具有相同的处理过程,可以共用一个函数,根据传入的 SQL 语句查询数据 库并显示数据,在 CDetailQueryDlg 类中定义这个函数,名称为 RefreshCtrlData,代码如下: void CDetailQueryDlg::RefreshCtrlData(CString sql) { if(!m_pDB->IsOpen()){ MessageBox("数据库未打开"); return; } m_listHR.DeleteAllItems(); TRY{ CRecordset rs(m_pDB); //打开所有的满足条件的人脉信息记录 rs.Open(CRecordset::dynaset, sql); while (!rs.IsEOF()) { int id; CString name,area,profession,vocation,company; CString approach,email,phone,mobile,interest,memo; CDBVariant var; //获取人脉信息 ID 字段值 rs.GetFieldValue((short)0, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) id = var.m_iVal; var.Clear(); //获取姓名字段值 rs.GetFieldValue(1, name); //获取地区信息字段值 rs.GetFieldValue(2, area); //获取职业字段值 rs.GetFieldValue(3, profession); //获取行业字段值 rs.GetFieldValue(4, vocation); — 192 — //获取所在单位字段值 rs.GetFieldValue(5, company); //获取认识途径字段值 rs.GetFieldValue(6, approach); //获取 email 字段值 rs.GetFieldValue(7, email); //获取固定电话字段值 rs.GetFieldValue(8, phone); //获取移动电话字段值 rs.GetFieldValue(9, mobile); //获取爱好字段值 rs.GetFieldValue(10, interest); //获取备注字段值 rs.GetFieldValue(11, memo); //向人脉信息列表框控件中加入新的一行信息. InsertHRInfoItem(id,name,area,profession,vocation,company,approach, email,phone,mobile,interest,memo); rs.MoveNext(); } rs.Close(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } END_CATCH } 选择所在地区列表框控件中的数据之后的消息映射函数为 OnCloseupComboArea,代 码如下: void CDetailQueryDlg::OnCloseupComboArea() { CString sql; CString strSelected; //获取当前选择项目 int nIndex = m_comboArea.GetCurSel(); — 193 — //如果没有选择,退出 if(nIndex == -1) return; //获取当前选择的地区信息名称 m_comboArea.GetLBText(nIndex,strSelected); //如果选项为空,退出 if(strSelected.IsEmpty()) return; //构造打开所有地区信息名称为 strSelected 人脉信息的 SQL 语句 sql.Format("select * from human_info_tab where area = ’%s’ order by id", strSelected); //根据 sql 语句打开满足条件的人脉信息,显示在列表中 RefreshCtrlData(sql); } OnCloseupComboArea 函数构造查询人脉信息的 SQL 语句,然后调用 RefreshCtrlData 函数更新人脉信息列表框控件中的数据。 如在“所在地区”列表框中选择“北京”选项后,会在列表框控件中列出所有在“北 京”的人脉信息,如图 5-11 所示。 图 5-11 选择地区方式查询对话框 按照职业、行业和认识途径这 3 种方式查询和按照所在地区查询的处理方法是相似的, 只需在 OnCloseupComboApproach、OnCloseupComboProfession 和 OnCloseupComboVocation 消息映射函数中构造查询人脉信息的 SQL语句和调用RefreshCtrlData函数更新人脉信息列 表框控件中的数据,详细代码不再列出。 2. 统计信息查询 统计信息查询列出了根据所在地区、认识途径、职位和行业信息进行人数统计的信息。 为了显示这些信息的方便,可以创建一个对话框,对话框类名称为 CStatQueryDlg,资源 ID 为 IDD_DIALOG_QUERY_STAT,对话框名称为“统计信息”,“统计信息”对话框如图 5-12 所示。 — 194 — 图 5-12 “统计信息”对话框 控件类型、ID 及说明见表 5-10。 表 5-10 控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 统计信息 无 Group Box IDC_STATIC 地区信息统计 无 List Control IDC_LIST_AREA 无 列表框控件类型变量 m_listArea Group Box IDC_STATIC 认识途径信息统计 无 List Control IDC_LIST_APPRAOCH 无 列表框控件类型变量 m_listApproach Group Box IDC_STATIC 职位信息统计 无 List Control IDC_LIST_PROFESSION 无 列表框控件类型变量 m_listProfession Group Box IDC_STATIC 行业信息统计 无 List Control IDC_LIST_VOCATION 无 列表框控件类型变量 m_listVocation 为了直接在 CStatQueryDlg 类中操作数据库,需要定义一个公有的数据库指针,代码 如下: public CDatabase *m_pDB; 在 CHRDBSDlg 类的 OnBtnStatQuery 函数中添加弹出“统计信息”对话框和传入数据 库连接指针的代码,代码如下: void CHRDBSDlg::OnBtnStatQuery() { //创建统计信息查询对话框实列. CStatQueryDlg dlg; //把数据库的指针传入对话框中. dlg.m_pDB = &m_db; //打开统计信息查询对话框. dlg.DoModal(); } 需要在 CStatQueryDlg 类中添加 OnInitDialog 的消息映射函数,在其中添加 4 个统计 信息列表框的显示列,包括名称信息和数目信息。还要在 CDetailQueryDlg 类中添加一个 InitListData 私有函数,负责向所在地区、职业、行业和认识途径 4 个列表框控件中添加统 — 195 — 计数据,代码如下: void CStatQueryDlg::InitListData() { if(!m_pDB->IsOpen()){ MessageBox("数据库未打开"); return; } TRY{ CRecordset rs(m_pDB); //打开所有的地区信息记录. rs.Open(CRecordset::dynaset, "select area,count(area) from HUMAN_INFO_TAB group by area"); while (!rs.IsEOF()) { int count; CString area; CDBVariant var; //获取地区名称字段值 rs.GetFieldValue((short)0, area); //获取统计数目字段值 rs.GetFieldValue(1, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) count = var.m_iVal; var.Clear(); InsertListItem(&m_listArea,area,count); rs.MoveNext(); } rs.Close(); //打开所有的认识途径信息记录. rs.Open(CRecordset::dynaset, "select approach,count(approach) from HUMAN_INFO_TAB group by approach"); while (!rs.IsEOF()) { int count; CString approach; CDBVariant var; //获取认识途径字段值 rs.GetFieldValue((short)0, approach); //获取统计数目字段值 rs.GetFieldValue(1, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) count = var.m_iVal; var.Clear(); InsertListItem(&m_listApproach,approach,count); rs.MoveNext(); } rs.Close(); //打开所有的职位信息记录. rs.Open(CRecordset::dynaset, "select profession,count(profession) from HUMAN_INFO_TAB group by profession"); while (!rs.IsEOF()) { — 196 — int count; CString profession; CDBVariant var; //获取职位名称字段值 rs.GetFieldValue((short)0, profession); //获取职位统计数目字段值 rs.GetFieldValue(1, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) count = var.m_iVal; var.Clear(); InsertListItem(&m_listProfession,profession,count); rs.MoveNext(); } rs.Close(); //打开所有的行业信息记录. rs.Open(CRecordset::dynaset, "select vocation,count(vocation) from HUMAN_INFO_TAB group by vocation"); while (!rs.IsEOF()) { int count; CString vocation; CDBVariant var; //获取行业名称字段值 rs.GetFieldValue((short)0, vocation); //获取行业统计数目字段值 rs.GetFieldValue(1, var, SQL_C_SLONG); if (var.m_dwType != DBVT_NULL) count = var.m_iVal; var.Clear(); InsertListItem(&m_listVocation,vocation,count); rs.MoveNext(); } rs.Close(); } CATCH(CDBException,ex) { AfxMessageBox (ex->m_strError); AfxMessageBox (ex->m_strStateNativeOrigin); } AND_CATCH(CMemoryException,pEx) { pEx->ReportError(); AfxMessageBox ("memory exception"); } AND_CATCH(CException,e) { TCHAR szError[100]; e->GetErrorMessage(szError,100); AfxMessageBox (szError); } — 197 — END_CATCH } InitListData 函数利用标准 SQL 语句的 Group By 分组的方式实现统计查询。InitListData 函数除了获取统计数据外,还需要向 4 个列表框控件中添加数据,由于向 4 个列表框控件 插入数据的方法很相似,可以共用一个函数 InsertListItem,代码如下: void CStatQueryDlg::InsertListItem(CListCtrl* pList, CString name, int value) { //获取当前的记录条数. int nIndex = pList->GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; lvItem.pszText = (char*)(LPCTSTR)name; //第一列 //在最后一行插入记录值. pList->InsertItem(&lvItem); //设置该行的其他列的值. CString temp ; temp.Format("%d",value); pList->SetItemText(nIndex,1,temp); } 单击主界面上的“统计信息查询”按钮,在弹出的“统计信息”对话框中,可以看到 所有的统计信息,如图 5-13 所示。 图 5-13 “统计信息”对话框 图 5-13 中显示了详细的统计信息,如在地区信息统计中,北京有 3 个人,广州有 1 个 人。在职位信息统计中财物总监有 1 个,广告策划有 1 个。 5.4 本章小结 本章详细介绍了人脉资源管理系统的开发过程。在系统设计中把地区信息、认识途径 — 198 — 信息、职位信息和行业信息作为系统的配置信息来处理,便于用户管理好自己的人脉资源。 如对于地区信息来说,一方面在人脉信息管理中,可以直接在地区信息列表框控件中选择 地区信息,比较方便,而且避免了用户输入一些不规范的地区名称,用户可以在地区信息 管理中添加、修改或删除地区信息。另一方面在详细信息查询中,可以根据地区名称查询 相应地区的所有人脉信息,以及在统计信息查询中,了解各地区的人数分布,让用户更加 了解自己的人脉资源分布情况。 人脉资源管理系统采用了 MFC ODBC 数据库开发技术,并使用了存储过程和事务处 理。存储过程的使用能够大大提高数据库的处理速度,读者可以在 SQL 执行工具中采用普 通的插入数据的方法(如输入向人脉信息表中插入数据的“Insert into…”SQL 语句)和利用 存储过程(如输入“call add_human_info_tab…”的 SQL 语句),比较它们的执行时间,将会 相差几个数量级,尤其是人脉信息表中的数据很多的时候,执行时间的相差会更大。利用 事务处理,可以提高系统的安全性。对于一些非常关键的数据库操作利用事务处理是很有 必要的,因为可以避免一些不可预知的程序异常带来的破坏。存储过程和事务处理使用起 来很方便,读者可以把它们应用在今后的数据库开发中。 第6章 客户资源管理系统 对一个公司来说,把握了客户,就相当于把握了一个公司生存的命脉。管理好自己的 客户资源是一个企业实现利润的有效途径。同客户保持一定的联系,对客户的了解多一些, 就能在谈判中把握更多的主动。同时,管理好自己的客户资源,可以增加再次合作的机会。 客户资源管理系统能有效地管理好自己的客户资源,并提供对客户合同的有效的管理。 6.1 系统设计 系统设计是系统开发最为关键的一环,系统设计好了,系统的实现以及以后的系统测 试都会节省很多的物力和财力。通过系统的设计,开发人员能够更好地把握系统的需求, 了解系统的各功能模块。 6.1.1 功能描述 客户资源管理系统包括对客户信息管理、客户合同管理,以及合同客户信息管理。详 细的功能描述如下。 1. 客户信息管理 客户信息管理包括客户信息的添加、修改、删除和查询的功能。客户信息包括客户姓 名、客户地址、客户联系方式等信息。并非每一个客户都会成为签约的合同客户,而如果 能对这些潜在的客户进行一个有效的管理,增加对这些客户的联系和了解,可以增加合作 的机会。 2. 客户合同管理 客户合同的管理包括合同信息的添加、修改和删除的功能。合同信息包括客户名称、 合同名称、合同签订时间和合同金额等信息。 3. 合同客户信息管理 合同客户信息管理包括对未完成合同信息和合同金额统计信息的管理。未完成合同信 息是指尚未完工的合同信息。合同金额统计信息是对合同按照年度或月份进行统计的信息, 企业从而可以得知合同的进展及利润情况。 6.1.2 功能模块设计 从上面的功能描述中,可以把客户资源管理系统分为 3 个模块:客户信息管理、客户 合同管理及合同客户信息管理。在每一个模块下又提供了更为具体的功能。详细的客户资 源管理系统的功能模块图如图 6-1 所示。 — 200 — 图 6-1 系统功能模块图 6.2 数据库设计与实现 数据库设计是系统开发中非常重要的一个环节。数据库结构设计的好坏将直接影响到 系统的效率和功能的实现。在设计数据库之前,要了解数据库的需求,从而确定数据库的 结构。 6.2.1 数据库需求设计 通过以上的功能分析,客户资源管理系统需要包含以下数据库信息。 1. 客户信息 包括客户 ID、客户姓名、客户通信地址、客户邮编、客户职位、客户单位、e-mail 地 址、固定电话、移动电话、洽谈时间和客户说明。 2. 客户合同信息 包括合同 ID、客户 ID、合同名称、合同完成状态、合同签约时间、合同执行人、合 同金额和合同说明。 6.2.2 数据库表的设计 通过数据库的需求,可以创建以下数据表。 1. 客户信息表(customer_info_tab) 客户信息表包含客户信息。客户信息的管理实际是对客户信息表的管理,表的结构见 表 6-1。 — 201 — 表 6-1 客户信息表 字段名称 数据类型 可否为空 约束条件 说 明 customer_id INTEGER NOT NULL 主键 客户 ID 值 name VARCHAR2(24) NOT NULL 无 姓名 address VARCHAR2(60) NULL 无 客户通信地址 code VARCHAR2(24) NULL 无 邮编 profession VARCHAR2(24) NULL 无 职位 company VARCHAR2(24) NULL 无 所在单位 email VARCHAR2(24) NULL 无 e-mail 地址 phone VARCHAR2(24) NULL 无 固定电话 mobile VARCHAR2(24) NULL 无 移动电话 meet_time DATE NULL 无 洽谈时间 memo VARCHAR2(1000) NULL 无 客户说明 2. 客户合同信息表(contract_info_tab) 客户合同信息表包括客户信息。客户合同的管理和合同客户信息管理都需要用到这个 数据表,表的结构见表 6-2。 表 6-2 客户合同信息表 字段名称 数据类型 可否为空 约束条件 说 明 contract_id INTEGER NOT NULL 主键 合同 ID 值 customer_name VARCHAR2(24) NULL 无 合同客户姓名 contract_name VARCHAR2(60) NOT NULL 无 合同名称 status VARCHAR2(6) NOT NULL 无 合同完成状态 contract_date DATE NULL 无 合同签约时间 executive VARCHAR2(60) NULL 无 合同执行人 money float NULL 无 合同金额 memo VARCHAR2(1000) NULL 无 合同说明 6.2.3 数据库表的创建 利用第 3 章中讲述的方法创建表空间 dbcr 和数据库用户 dbcr,其中数据库用户的密码 为 dbcr,选择的默认表空间为 dbcr。 创建客户资源管理系统所有数据表的 SQL 语句如下: --客户信息表 CREATE TABLE customer_info_tab( customer_id INTEGER PRIMARY KEY, name VARCHAR2(24) NOT NULL, address VARCHAR2(60) NULL, code VARCHAR2(24) NULL, profession VARCHAR2(24) NULL, company VARCHAR2(24) NULL, email VARCHAR2(24) NULL, phone VARCHAR2(24) NULL, mobile VARCHAR2(24) NULL, meet_time DATE NULL, memo VARCHAR2(1000) NULL — 202 — ); --添加所在地 meet_time 的索引 CREATE INDEX customer_meet_timeindex ON customer_info_tab(meet_time); --客户合同信息表 CREATE TABLE contract_info_tab( contract_id INTEGER PRIMARY KEY, customer_name VARCHAR2(24) NOT NULL, contract_name VARCHAR2(60) NOT NULL, status VARCHAR2(6) NOT NULL CHECK (status IN (’未开始’,’开始’,’完成’)), contract_date DATE NULL, executive VARCHAR2(60) NULL, money float NULL, memo VARCHAR2(1000) NULL ); --添加所在地 contract_date 的索引 CREATE INDEX contract_dateindex ON contract_info_tab(contract_date); --创建可以递增的系列号供客户信息表的 customer_id 使用 CREATE SEQUENCE seq_customer_id INCREMENT BY 1 START WITH 10000 NOMAXVALUE NOMINVALUE NOCYCLE; --创建可以递增的系列号供客户合同信息表的 contract_id 使用 CREATE SEQUENCE seq_contract_id INCREMENT BY 1 START WITH 1 NOMAXVALUE NOMINVALUE NOCYCLE; --创建存储过程,提高数据库处理数据的速度. --该过程既可以向客户信息表中添加数据也可以修改数据,因为过程先调用删除语句,再添加. --这个处理方法采用存贮过程速度是非常快的. CREATE PROCEDURE add_customer_info_tab ( param1 IN customer_info_tab.customer_id%TYPE, param2 IN customer_info_tab.name%TYPE, param3 IN customer_info_tab.address%TYPE, param4 IN customer_info_tab.code%TYPE, param5 IN customer_info_tab.profession%TYPE, param6 IN customer_info_tab.company%TYPE, param7 IN customer_info_tab.email%TYPE, param8 IN customer_info_tab.phone%TYPE, param9 IN customer_info_tab.mobile%TYPE, param10 IN customer_info_tab.meet_time%TYPE, param11 IN customer_info_tab.memo%TYPE ) AS BEGIN DELETE FROM customer_info_tab WHERE customer_id = param1; INSERT INTO customer_info_tab (customer_id,name,address,code,profession,company,email,phone,mobile,meet_t ime,memo) VALUES(param1,param2,param3,param4,param5,param6,param7,param8,param9,p aram10,param11); END; — 203 — 利用 Oracle SQLPlus WorkSheet 工具执行上述的 SQL 语句从而创建数据库表。需要说 明的是,在打开 Oracle SQLPlus Worksheet 的“Oracle Enterprise Manager 登录”对话框时, 需要在“用户名”文本框中输入企业设备管理系统的用户名 dbcr,在“口令”文本框中输 入用户密码 dbcr,在“服务”文本框中输入数据库的本地服务名 ORADB,选择连接方式 Normal,登录成功之后,再运行上述的 SQL 语句。 6.3 系统的实现 完成了系统功能模块的设计和数据库表的创建,就可以创建一个客户资源管理系统。 6.3.1 创建应用程序 运行 Visual C++,选择 File|New 命令,弹出 New 对话框。从 Projects 列表中选择 MFC AppWizard(exe)向导,在 Location 文本框中选择项目工程的目录“D:\VCSAMPLE\”,在 Project name 文本框中输入工程文件的名称 CRDBS,单击 OK 按钮,进入 MFC AppWizard – Step 1 页面,从应用程序的类型中选择 Dialog based 选项,从语言列表中选择“中文(中 国)(APPWZCHS.DLL)”,单击 Next 按钮,进入 MFC AppWizard – Step 2 of 4 页面,选择 Automation 选项,CRDBS 对话框应用程序创建完毕。 6.3.2 创建主对话框的界面 主对话框的布局如图 6-2 所示。其中包括客户信息管理、客户合同管理和合同客户信 息管理 3 个部分。 图 6-2 “客户资源管理系统”对话框 — 204 — 1. 客户信息管理 控件类型、ID 及说明见表 6-3。 表 6-3 客户信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 客 户 信 息 管 理 无 Label IDC_STATIC 姓名 无 Edit Box IDC_EDIT_CUSTUMER_NAME 无 CString 类型变量 m_strCustomerName Label IDC_STATIC 通信地址 无 Edit Box IDC_EDIT_ADDRESS 无 CString 类型变量 m_strAddress Label IDC_STATIC 邮编 无 Edit Box IDC_EDIT_CODE 无 CString 类型变量 m_strCode Label IDC_STATIC 职位 无 Edit Box IDC_EDIT_PROFESSION 无 CString 类型变量 m_strProfession Label IDC_STATIC 所在单位 无 Edit Box IDC_EDIT_COMPANY 无 CString 类型变量 m_strCompany Label IDC_STATIC e-mail 无 Edit Box IDC_EDIT_EMAIL 无 CString 类型变量 m_strEmail Label IDC_STATIC 固定电话 无 Edit Box IDC_EDIT_PHONE 无 CString 类型变量 m_strPhone Label IDC_STATIC 移动电话 无 Edit Box IDC_EDIT_MOBILE 无 CString 类型变量 m_strMobile Label IDC_STATIC 时间 无 Date Time Picker IDC_MEET_DATE 无 COleDateTime 类型变量 m_oleMeetDate Date Time Picker IDC_MEET_TIME 无 COleDateTime 类型变量 m_oleMeetTime Label IDC_STATIC 说明 无 Edit Box IDC_EDIT_CUSTOMER_MEMO 无 CString 类型变量 m_strCustomerMemo Button IDC_BTN_CUSTOMER_ADD 添加 函数 OnBtnCustomerAdd ()添加客户信息 Button IDC_BTN_CUSTOMER_MOD 修改 函数 OnBtnCustomerMod ()修改客户信息 Button IDC_BTN_CUSTOMER_DEL 删除 函数 OnBtnCustomerDel ()删除客户信息 Button IDC_BTN_CUSTOMER_QUERY 姓名查询 函数 OnBtnCustomerQuery ()用姓名方式查询客户信息 Group Box IDC_STATIC 客户信息 无 List Control IDC_LIST_CUSTOMER 无 列表框控件类型变量 m_listCustomer 2. 客户合同管理 控件类型、ID 及说明见表 6-4。 表 6-4 客户合同管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 客户合同管理 无 Label IDC_STATIC 客户姓名 无 Edit Box IDC_EDIT_NAME 无 CString 类型变量 m_strName Label IDC_STATIC 合同名称 无 Edit Box IDC_EDIT_CONTRACT_NAME 无 CString 类型变量 m_strContractName Label IDC_STATIC 完成状态 无 Combo Box IDC_COMBO_STATUS 无 CString 类型变量 m_strStatus Label IDC_STATIC 合同金额(¥) 无 Edit Box IDC_EDIT_MONEY 无 Double 类型变量 m_fMoney Label IDC_STATIC 合同执行人 无 — 205 — (续表) 控件类型 ID 属 性 变量或函数 Edit Box IDC_EDIT_EXECUTIVE 无 CString 类型变量 m_strExecutive Label IDC_STATIC 签订时间 无 Date Time Picker IDC_CONTRACT_DATE 无 COleDateTime 类型变量 m_oleContractDate Date Time Picker IDC_CONTRACT_TIME 无 COleDateTime 类型变量 m_oleContractDate Label IDC_STATIC 说明 无 Edit Box IDC_EDIT_CONTRACT_MEMO 无 CString 类型变量 m_strContractMemo Button IDC_BTN_CONTRACT_ADD 添加 函数 OnBtnContractAdd ()添加合同信息 Button IDC_BTN_CONTRACT_MOD 修改 函数 OnBtnContractMod ()修改合同信息 Button IDC_BTN_CONTRACT_DEL 删除 函数 OnBtnContractDel ()删除合同信息 Group Box IDC_STATIC 合同信息 无 List Control IDC_LIST_CONTRACT 无 列表框控件类型变量 m_listContract 3. 合同客户信息管理 控件类型、ID 及说明见表 6-5。 表 6-5 合同客户信息控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 合同客户信息管理 无 Button IDC_BTN_UNCOMPLETED_LIST 未完成合同信息 函数 OnBtnUncompletedList ()未完成合同信息查询 Button IDC_BTN_MONEY_STAT 合同金额统计信息 函数 OnBtnMoneyStat ()合同金额统计信息查询 Button IDC_SYS_EXIT 系统退出 函数 OnSysExit ()系统退出 主对话框类名称为 CCRDBSDlg,资源 ID 为 IDD_CRDBS_DIALOG,对话框名称为“客 户资源管理系统”。主界面用到了两个列表框控件分别显示客户信息和合同信息。需要为这 两个列表框控件添加显示的列,从而显示相应的数据信息。为了代码设计的清晰,在 CCRDBSDlg 类中定义了一个 InitControl 私有函数负责添加控件的显示列,InitControl 函数 的代码如下: void CCRDBSDlg::InitControl() { //设置列表框控件扩展风格 DWORD dwExStyle = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP | LVS_EX_ONECLICKACTIVATE | LVS_EX_UNDERLINEHOT; m_listCustomer.SetExtendedStyle(dwExStyle); m_listContract.SetExtendedStyle(dwExStyle); //初始客户信息列表框控件 m_listCustomer.InsertColumn(0,"客户 ID",LVCFMT_CENTER,50); m_listCustomer.InsertColumn(1,"姓名",LVCFMT_CENTER,80); m_listCustomer.InsertColumn(2,"通信地址",LVCFMT_CENTER,100); m_listCustomer.InsertColumn(3,"邮编",LVCFMT_CENTER,80); m_listCustomer.InsertColumn(4,"职位",LVCFMT_CENTER,80); m_listCustomer.InsertColumn(5,"所在单位",LVCFMT_CENTER,80); m_listCustomer.InsertColumn(6,"e-mail",LVCFMT_CENTER,80); m_listCustomer.InsertColumn(7,"固定电话",LVCFMT_CENTER,80); m_listCustomer.InsertColumn(8,"移动电话",LVCFMT_CENTER,80); m_listCustomer.InsertColumn(9,"时间",LVCFMT_CENTER,140); m_listCustomer.InsertColumn(10,"客户说明",LVCFMT_CENTER,140); //初始客户合同信息列表框控件 m_listContract.InsertColumn(0,"合同 ID",LVCFMT_CENTER,60); — 206 — m_listContract.InsertColumn(1,"客户姓名",LVCFMT_CENTER,80); m_listContract.InsertColumn(2,"合同名称",LVCFMT_CENTER,100); m_listContract.InsertColumn(3,"完成状态",LVCFMT_CENTER,80); m_listContract.InsertColumn(4,"合同金额",LVCFMT_CENTER,80); m_listContract.InsertColumn(5,"合同执行人",LVCFMT_CENTER,100); m_listContract.InsertColumn(6,"签订时间",LVCFMT_CENTER,140); m_listContract.InsertColumn(7,"合同说明",LVCFMT_CENTER,140); } 在 OnInitDialog 函数末尾处添加 InitControl 函数的调用,这样系统在启动的时候,就 可以看到已添加显示列的列表框控件。 6.3.3 显示数据到界面上 当系统启动之后,可以将数据库中的数据显示到界面上,需要处理数据库的连接和显 示数据到界面上的两个过程。 1. 数据库的连接 ADO 数据库的连接需要 3 个重要的参数:数据库数据源的名称、数据库用户名称和数 据库用户密码。其中数据库数据源的名称就是本地服务名 ORADB,数据库用户的名称和 密码均为 dbcr。考虑到读者配置的本地服务名和用户名可能不一样,可以从配置文件中获 取这些参数信息,配置文件的格式如下: [General] 数据库数据源=oradb 数据库用户=dbcr 数据库密码=dbcr 把这段文字保存为 CRDBS.ini 文件,并把 CRDBS.ini 文件放在 CRDBS.exe 运行程序 的同一目录下。 在 CCRDBSDlg 类中定义一个私有类型的 ConnectDB 函数负责连接数据库,代码如下: void CCRDBSDlg::ConnectDB(){ //初始化 Connection 指针 m_pConnection.CreateInstance(_uuidof(Connection)); //初始化 Recordset 指针 m_pRecordset.CreateInstance(_uuidof(Recordset)); //初始化 Command 指针 m_pCommand.CreateInstance("ADODB.Command"); char szPath[255]; //获取应用程序完全路径 ::GetModuleFileName(NULL,szPath,255); CString strFileName = szPath; //获取所在的目录名称 strFileName.Delete(strFileName.ReverseFind(’\\’)+1,strFileName.GetLengt h ()-strFileName.ReverseFind(’\\’)-1); //构造配置文件的完全路径 strFileName += "CRDBS.ini"; TCHAR sz[101]; — 207 — memset(sz,0,sizeof(TCHAR)*101); //获取配置文件中数据库数据源的值,如果没有,默认值为 oradb GetPrivateProfileString(_T("General"),_T(" 数 据 库 数 据 源 "),_T("oradb"),sz,100,strFileName); CString strSource(sz); GetPrivateProfileString(_T("General"),_T(" 数 据 库 用 户 "),_T("dbcr"),sz,100,strFileName); CString strUser(sz); GetPrivateProfileString(_T("General"),_T(" 数 据 库 密 码 "),_T("dbcr"),sz,100,strFileName); CString strPwd(sz); try { CString strConnect; strConnect.Format("Provider=OraOLEDB.Oracle.1;Password=%s;User ID=%s;" "Data Source=%s;Persist Security Info=True",strPwd,strUser,strSource); //连接数据库 m_pConnection->Open((_bstr_t)strConnect,"","",-1); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 本章在利用 ADO 进行数据库开发的时候,需要用到连接对象、记录集对象和命令对 象,需要在 CRDBSDlg.h 中定义这 3 个对象对应的指针,代码如下: _ConnectionPtr m_pConnection; _RecordsetPtr m_pRecordset; _CommandPtr m_pCommand; 在 ConnectDB 函数中首先对这些对象进行实例化,从 CRDBS.ini 文件中获取数据库连 接参数,然后调用连接对象的 Open 方法创建数据库的连接。 2. 显示数据到界面上 连接数据库之后,需要把数据库中的数据显示在两个列表框控件中。在 CCRDBSDlg 类中定义一个 InitCtrlData 私有函数,负责从数据库中读取数据并显示到列表框控件中。同 时还需要定义两个分别把数据插入到列表框控件中的函数,分别为:InsertCustomer InfoItem 和 InsertContractInfoItem,在 CRDBSDlg.h 文件中添加这些函数的定义,代码如下: private: //使用 CRDBS.ini 配置文件获取连接数据库的参数 void ConnectDB(); //初始化列表框控件. void InitControl(); //从数据库获取设备信息并更新到控件中 void InitCtrlData(); — 208 — //向客户信息列表框控件中添加数据 void InsertCustomerInfoItem(int id,CString name,CString address,CString code,CString profession,CString company,CString email,CString phone,CString mobile,CString meet_time,CString memo); //向合同信息列表框控件中添加数据 void InsertContractInfoItem(int id,CString cust_name,CString con_name,CString status,CString date,CString executive,double money,CString memo); 向客户信息列表框控件中插入客户信息的函数为 InsertCustomerInfoItem,代码如下: void CCRDBSDlg::InsertCustomerInfoItem(int id,CString name,CString address,CString code,CString profession,CString company,CString email,CString phone,CString mobile,CString meet_time,CString memo) { //获取当前的记录条数. int nIndex = m_listCustomer.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. m_listCustomer.InsertItem(&lvItem); //设置该行的其他列的值. m_listCustomer.SetItemText(nIndex,1,name); m_listCustomer.SetItemText(nIndex,2,address); m_listCustomer.SetItemText(nIndex,3,code); m_listCustomer.SetItemText(nIndex,4,profession); m_listCustomer.SetItemText(nIndex,5,company); m_listCustomer.SetItemText(nIndex,6,email); m_listCustomer.SetItemText(nIndex,7,phone); m_listCustomer.SetItemText(nIndex,8,mobile); m_listCustomer.SetItemText(nIndex,9,meet_time); m_listCustomer.SetItemText(nIndex,10,memo); } 向合同信息列表框控件中插入合同信息的函数为 InsertContractInfoItem,代码如下: void CCRDBSDlg::InsertContractInfoItem(int id,CString cust_name,CString con_name,CString status,CString date,CString executive,double money,CString memo) { //获取当前的记录条数. int nIndex = m_listContract.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; — 209 — CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. m_listContract.InsertItem(&lvItem); //设置该行的其他列的值. m_listContract.SetItemText(nIndex,1,cust_name); m_listContract.SetItemText(nIndex,2,con_name); m_listContract.SetItemText(nIndex,3,status); temp.Format("%.2f",money); m_listContract.SetItemText(nIndex,4,temp); m_listContract.SetItemText(nIndex,5,executive); m_listContract.SetItemText(nIndex,6,date); m_listContract.SetItemText(nIndex,7,memo); } 从数据库中读取数据并显示到两个列表框控件中的函数为 InitCtrlData,代码如下: void CCRDBSDlg::InitCtrlData(){ try { CString strSql="select * from customer_info_tab"; BSTR bstrSQL = strSql.AllocSysString(); m_pRecordset->Open(bstrSQL,(IDispatch*)m_pConnection,adOpenDynamic,adLo ckOptimistic,adCmdText); //遍历所有记录 while(!m_pRecordset->adoEOF) { int id; CString name,address,code,profession; CString company,email,phone,mobile,meet_time,memo; _variant_t TheValue; //获取客户 ID 字段值. TheValue = m_pRecordset->Fields->GetItem("customer_id")->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; //获取客户姓名字段值. TheValue = m_pRecordset->GetCollect("name"); if(TheValue.vt!=VT_NULL) name= (char*)_bstr_t(TheValue); //获取客户通信地址字段值. TheValue = m_pRecordset->GetCollect("address"); if(TheValue.vt!=VT_NULL) address= (char*)_bstr_t(TheValue); //获取客户邮编字段值. TheValue = m_pRecordset->GetCollect("code"); if(TheValue.vt!=VT_NULL) code= (char*)_bstr_t(TheValue); //获取客户职位字段值. TheValue = m_pRecordset->GetCollect("profession"); if(TheValue.vt!=VT_NULL) — 210 — profession= (char*)_bstr_t(TheValue); //获取客户所在单位字段值. TheValue = m_pRecordset->GetCollect("company"); if(TheValue.vt!=VT_NULL) company= (char*)_bstr_t(TheValue); //获取客户 e-mail 字段值. TheValue = m_pRecordset->GetCollect("email"); if(TheValue.vt!=VT_NULL) email= (char*)_bstr_t(TheValue); //获取客户固定电话字段值. TheValue = m_pRecordset->GetCollect("phone"); if(TheValue.vt!=VT_NULL) phone= (char*)_bstr_t(TheValue); //获取客户移动电话字段值. TheValue = m_pRecordset->GetCollect("mobile"); if(TheValue.vt!=VT_NULL) mobile= (char*)_bstr_t(TheValue); //获取客户洽谈时间字段值. TheValue = m_pRecordset->GetCollect("meet_time"); if(TheValue.vt!=VT_NULL){ DATE dt; dt = TheValue.date; COleDateTime oleDT(dt); meet_time = oleDT.Format("%Y-%m-%d %H:%M:%S"); } //获取客户说明字段值. TheValue = m_pRecordset->GetCollect("memo"); if(TheValue.vt!=VT_NULL) memo= (char*)_bstr_t(TheValue); // InsertCustomerInfoItem(id,name,address,code,profession,company, email,phone,mobile,meet_time,memo); //转到下一条记录 m_pRecordset->MoveNext(); } //用完记录集后,一定要关闭记录集 m_pRecordset->Close(); m_pRecordset->Open("select * from contract_info_tab",(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic ,adCmdText); //遍历所有记录 while(!m_pRecordset->adoEOF) { int id; CString cust_name,con_name,status,date,executive,memo; double money; _variant_t TheValue; //获取合同 ID 字段值. TheValue = m_pRecordset->Fields->GetItem("contract_id")->Value; if(TheValue.vt!=VT_NULL) — 211 — id = TheValue.iVal; //获取客户姓名字段值. TheValue = m_pRecordset->GetCollect("customer_name"); if(TheValue.vt!=VT_NULL) cust_name= (char*)_bstr_t(TheValue); //获取合同名称字段值. TheValue = m_pRecordset->GetCollect("contract_name"); if(TheValue.vt!=VT_NULL) con_name= (char*)_bstr_t(TheValue); //获取合同完成状态字段值. TheValue = m_pRecordset->GetCollect("status"); if(TheValue.vt!=VT_NULL) status= (char*)_bstr_t(TheValue); //获取合同签订日期字段值. TheValue = m_pRecordset->GetCollect("contract_date"); if(TheValue.vt!=VT_NULL){ DATE dt; dt = TheValue.date; COleDateTime oleDT(dt); date = oleDT.Format("%Y-%m-%d %H:%M:%S"); } //获取合同执行人字段值. TheValue = m_pRecordset->GetCollect("executive"); if(TheValue.vt!=VT_NULL) executive= (char*)_bstr_t(TheValue); //获取合同金额字段值. TheValue = m_pRecordset->GetCollect("money"); if(TheValue.vt!=VT_NULL) money = TheValue.dblVal; //获取合同说明字段值. TheValue = m_pRecordset->GetCollect("memo"); if(TheValue.vt!=VT_NULL) memo= (char*)_bstr_t(TheValue); InsertContractInfoItem(id,cust_name,con_name,status,date,executive,mone y,memo); //转到下一条记录 m_pRecordset->MoveNext(); } m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } InitCtrlData 函数利用记录集对象的 GetItem 方法和 GetCollect 方法获取记录集中的字段 值。因为_variant_t 类是从 VARIANT 结构中继承的,所以要获取具体数据的时候,可以直接 调用其中的成员值。为了显示的方便,需要将数据表中的日期类型字段数据转化为字符串格 式,可以把日期类型字段直接当作字符串字段处理,也可以先获取日期格式的数据,然后转 — 212 — 化为字符串的格式,代码中获取合同鉴定日期的方法就是采用后一种的处理方法。尽管这两 种方法都可以获取合同鉴定日期,但是格式稍有不同,以日期 2004-07-02 14:22:21 为例。如 果把日期类型的字段当作字符串字段处理的时候,获取的日期格式为 2004-7-2 14:22:21,即 月份 7 只有一个字符,而不是 07,这和 MFC ODBC 中利用 CRecordset 类获取日期的格式是 不同的,其格式为 2004-07-02 14:22:21。由于系统的其他地方使用的是 2004-07-02 14:22:21 这种格式的日期字符串,因而可以先获取 DATE 格式的日期数据,然后转换成 COleDateTime 的格式,再利用 Format 函数,转化成 2004-07-02 14:22:21 格式的日期字符串。 在 CRDBSDlg.cpp 文件的 OnInitDialog 函数末尾处添加初始化列表框控件列和连接数 据库并显示数据的代码,代码如下: //初始化列表框控件 InitControl(); //连接数据库 ConnectDB(); //初始化列表框控件的数据 InitCtrlData(); 系统在启动的时候,会自动连接数据库,数据库连接成功后,就在列表框控件中显示 数据库中的信息,如图 6-3 所示。 图 6-3 连接数据库之后的对话框 6.3.4 客户信息管理 客户信息管理包括客户信息的添加、修改、删除和查询的功能。 — 213 — 1. 添加客户信息 添加客户信息的函数为 OnBtnCustomerAdd,代码如下: void CCRDBSDlg::OnBtnCustomerAdd() { if(!UpdateData()) return; if(m_strCustomerName.IsEmpty()){ AfxMessageBox("客户姓名不能够为空"); return; } //构造洽谈时间 CString strDate = m_oleMeetDate.Format("%Y-%m-%d") + " " + m_oleMeetTime.Format("%H:%M:%S"); try { m_pRecordset->Open("Select seq_customer_id.NEXTVAL from dual" ,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText); int id = 1; if(!m_pRecordset->adoEOF) { _variant_t TheValue; //从系列中获取新的客户 ID 值. TheValue = m_pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; } m_pRecordset->Close(); _variant_t vNULL; vNULL.vt = VT_ERROR; vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数 //调用存储过程,插入新的客户信息记录. CString sql; sql.Format("call add_customer_info_tab(%d,’%s’,’%s’,’%s’,’%s’,’%s’," "’%s’,’%s’,’%s’,to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’)," "’%s’)",id,m_strCustomerName,m_strAddress, m_strCode,m_strProfession,m_strCompany, m_strEmail,m_strPhone,m_strMobile,strDate,m_strCustomerMemo); TRACE(sql); //利用命令对象完成 sql 命令的执行 m_pCommand->ActiveConnection = m_pConnection; m_pCommand->CommandText = _bstr_t(sql); m_pCommand->Execute(&vNULL,&vNULL,adCmdText); //向界面中插入新的客户信息 InsertCustomerInfoItem(id,m_strCustomerName,m_strAddress, m_strCode,m_strProfession,m_strCompany, m_strEmail,m_strPhone,m_strMobile,strDate,m_strCustomerMemo); } — 214 — catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 函数 OnBtnCustomerAdd 调用了存储过程,调用存储过程的方法和 MFC ODBC 中的 CDataBase 类的 ExecuteSQL 方法非常相似,函数 OnBtnCustomerAdd 利用命令对象的 Execute 方法调用存储过程,也可以使用连接对象调用存储过程。在使用命令对象的 Execute 方法之前,需要传入一个活动的连接对象指针给 ActiveConnection,同时传入一个命令文 本给 CommandText,即调用存储过程的 SQL 语句。函数 OnBtnCustomerAdd 最后调用 InsertCustomerInfoItem 函数把客户信息添加到客户信息列表框控件中。 如在客户信息管理中的“姓名”文本框输入“王成”,在“通信地址”文本框中输入“朝 阳区世纪花园小区 6 号楼 202 室”,在“邮编”文本框中输入 100085,在“职位”文本框中输 入“市场总监”,在“所在单位”文本框中输入“北京叶之秋微电子公司”,在 e-mail 文本框 中输入 wangc@sohu.com,在“固定电话”文本框中输入 01098765432,在“移动电话”文本 框中输入 13945674567,在“时间”日期时间控件中选择 2004-6-1 13:54:00,如图 6-4 所示。 图 6-4 添加客户信息对话框 单击“添加”按钮,客户信息就添加到列表框控件中,如图 6-5 所示。 图 6-5 添加客户信息之后的对话框 — 215 — 2. 修改客户信息 为了用户修改客户信息的方便,可以添加客户信息列表框控件 IDC_LIST_ CUSTOMER 的 NM_CLICK 消息映射函数 OnClickListCustomer,代码如下: void CCRDBSDlg::OnClickListCustomer(NMHDR* pNMHDR, LRESULT* pResult) { int nItem = m_listCustomer.GetNextItem(-1, LVNI_SELECTED); if(nItem != -1){ //从类别控件中获取信息 m_strCustomerName = m_listCustomer.GetItemText(nItem,1); m_strAddress = m_listCustomer.GetItemText(nItem,2); m_strCode = m_listCustomer.GetItemText(nItem,3); m_strProfession = m_listCustomer.GetItemText(nItem,4); m_strCompany= m_listCustomer.GetItemText(nItem,5); m_strEmail = m_listCustomer.GetItemText(nItem,6); m_strPhone= m_listCustomer.GetItemText(nItem,7); m_strMobile = m_listCustomer.GetItemText(nItem,8); CString strDT = m_listCustomer.GetItemText(nItem,9); m_oleMeetDate.ParseDateTime(strDT); m_oleMeetTime.ParseDateTime(strDT); m_strCustomerMemo = m_listCustomer.GetItemText(nItem,10); //更新到客户参数的控件中. UpdateData(FALSE); } *pResult = 0; } 修改客户信息的函数为 OnBtnCustomerMod,代码如下: void CCRDBSDlg::OnBtnCustomerMod() { if(!UpdateData()) return; int nItem = m_listCustomer.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要修改的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要修改的客户信息"); return; } //从列表框控件中获取选择的客户信息 ID. int id = atoi(m_listCustomer.GetItemText(nItem,0)); if(m_strCustomerName.IsEmpty()){ AfxMessageBox("客户姓名不能够为空"); return; } //构造洽谈时间 CString strDate = m_oleMeetDate.Format("%Y-%m-%d") + " " + m_oleMeetTime.Format("%H:%M:%S"); try { — 216 — _variant_t vNULL; vNULL.vt = VT_ERROR; vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数 //调用存储过程,更新客户信息记录. CString sql; sql.Format("call add_customer_info_tab(%d,’%s’,’%s’,’%s’,’%s’,’%s’," "’%s’,’%s’,’%s’,to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’)," "’%s’)",id,m_strCustomerName,m_strAddress, m_strCode,m_strProfession,m_strCompany, m_strEmail,m_strPhone,m_strMobile,strDate,m_strCustomerMemo); TRACE(sql); m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的 连接赋值给它 m_pCommand->CommandText = _bstr_t(sql);///命令字串 m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集 //修改界面上的值 m_listCustomer.SetItemText(nItem,1,m_strCustomerName); m_listCustomer.SetItemText(nItem,2,m_strAddress); m_listCustomer.SetItemText(nItem,3,m_strCode); m_listCustomer.SetItemText(nItem,4,m_strProfession); m_listCustomer.SetItemText(nItem,5,m_strCompany); m_listCustomer.SetItemText(nItem,6,m_strEmail); m_listCustomer.SetItemText(nItem,7,m_strPhone); m_listCustomer.SetItemText(nItem,8,m_strMobile); m_listCustomer.SetItemText(nItem,9,strDate); m_listCustomer.SetItemText(nItem,10,m_strCustomerMemo); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 在 OnBtnCustomerMod 函数中也使用了存储过程。如要修改“王成”的客户信息,修 改“固定电话”文本框中的数据为 01065656565, 修 改 e-mail 文 本 框 中 的 数 据 为 wang2004@sina.com,单击“修改”按钮,如图 6-6 所示。 图 6-6 修改客户信息对话框 — 217 — 3. 删除客户信息 删除客户信息的函数为 OnBtnHrDel,代码如下: void CCRDBSDlg::OnBtnCustomerDel() {e if(!UpdateData()) return; int nItem = m_listCustomer.GetNextItem(-1, LVNI_SELECTED); if(nItem == -1){ AfxMessageBox("没有选择要删除的客户信息"); return; } //从列表框控件中获取选择的客户信息 ID. int id = atoi(m_listCustomer.GetItemText(nItem,0)); try { _variant_t vNULL; vNULL.vt = VT_ERROR; vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数 //删除客户信息记录. CString sql; sql.Format("delete from customer_info_tab where customer_id = %d ",id); m_pCommand->ActiveConnection = m_pConnection; m_pCommand->CommandText = _bstr_t(sql); m_pCommand->Execute(&vNULL,&vNULL,adCmdText); //删除界面上的记录 m_listCustomer.DeleteItem(nItem); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 4. 姓名查询 姓名查询提供了利用客户姓名进行模糊查询的方法,如果姓名为空,列出所有客户信 息;如果不为空,则列出所有客户姓名包含姓名文本框中的文字的客户信息。函数为 OnBtnCustomerQuery,代码如下: void CCRDBSDlg::OnBtnCustomerQuery() { if(!UpdateData()) return; m_listCustomer.DeleteAllItems(); try { CString temp = "%"; CString sql; //如果姓名文本框中为空,则查询将获取全部客户信息数据 — 218 — sql.Format("select * from customer_info_tab where name like ’%s%s%s’", temp,m_strCustomerName,temp); m_pRecordset->Open(_bstr_t(sql),(IDispatch*)m_pConnection,adOpenDynamic ,adLockOptimistic,adCmdText); //遍历所有记录 while(!m_pRecordset->adoEOF) { int id; CString name,address,code,profession; CString company,email,phone,mobile,meet_time,memo; _variant_t TheValue; //获取客户 ID 字段值. TheValue = m_pRecordset->Fields->GetItem("customer_id")->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; //获取客户姓名字段值. TheValue = m_pRecordset->GetCollect("name"); if(TheValue.vt!=VT_NULL) name= (char*)_bstr_t(TheValue); //获取客户通信地址字段值. TheValue = m_pRecordset->GetCollect("address"); if(TheValue.vt!=VT_NULL) address= (char*)_bstr_t(TheValue); //获取客户邮编字段值. TheValue = m_pRecordset->GetCollect("code"); if(TheValue.vt!=VT_NULL) code= (char*)_bstr_t(TheValue); //获取客户职位字段值. TheValue = m_pRecordset->GetCollect("profession"); if(TheValue.vt!=VT_NULL) profession= (char*)_bstr_t(TheValue); //获取客户所在单位字段值. TheValue = m_pRecordset->GetCollect("company"); if(TheValue.vt!=VT_NULL) company= (char*)_bstr_t(TheValue); //获取客户 e-mail 字段值. TheValue = m_pRecordset->GetCollect("email"); if(TheValue.vt!=VT_NULL) email= (char*)_bstr_t(TheValue); //获取客户固定电话字段值. TheValue = m_pRecordset->GetCollect("phone"); if(TheValue.vt!=VT_NULL) phone= (char*)_bstr_t(TheValue); //获取客户移动电话字段值. TheValue = m_pRecordset->GetCollect("mobile"); if(TheValue.vt!=VT_NULL) mobile= (char*)_bstr_t(TheValue); //获取客户洽谈时间字段值. TheValue = m_pRecordset->GetCollect("meet_time"); if(TheValue.vt!=VT_NULL) — 219 — meet_time= (char*)_bstr_t(TheValue); //获取客户说明字段值. TheValue = m_pRecordset->GetCollect("memo"); if(TheValue.vt!=VT_NULL) memo= (char*)_bstr_t(TheValue); InsertCustomerInfoItem(id,name,address,code,profession,company, email,phone,mobile,meet_time,memo); //转到下一条记录 m_pRecordset->MoveNext(); } m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 如在客户信息管理中的“姓名”文本框中输入“张”,然后单击“姓名查询”按钮, 就会显示“张丽”的详细客户信息,如图 6-7 所示。 图 6-7 姓名查询对话框 6.3.5 客户合同管理 客户合同的管理包括合同信息的添加、修改和删除的功能。 1. 添加合同信息 添加合同信息的函数为 OnBtnContractAdd,代码如下: void CCRDBSDlg::OnBtnContractAdd() { if(!UpdateData()) return; if(m_strContractName.IsEmpty()||m_strName.IsEmpty()){ AfxMessageBox("姓名不能够为空"); return; — 220 — } //构造合同签订时间 CString strDate = m_oleContractDate.Format("%Y-%m-%d") + " " + m_oleContractTime.Format("%H:%M:%S"); try { m_pRecordset->Open("Select seq_contract_id.NEXTVAL from dual" ,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText); int id = 1; if(!m_pRecordset->adoEOF) { _variant_t TheValue; //从系列中获取新的合同 ID 值. TheValue = m_pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; } m_pRecordset->Close(); _variant_t RecordsAffected; //插入新的合同信息记录. CString sql; sql.Format("insert into contract_info_tab(contract_id," "customer_name,contract_name,status," "contract_date,executive,money,memo) values(" "%d,’%s’,’%s’,’%s’," "to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’)," "’%s’,%.2f,’%s’)",id,m_strName,m_strContractName, m_strStatus.IsEmpty()?"未开始":m_strStatus,strDate, m_strExecutive,m_fMoney,m_strContractMemo); TRACE(sql); //利用连接对象直接调用 SQL 命令完成新合同信息的添加 m_pConnection->Execute(_bstr_t(sql),&RecordsAffected,adCmdText); //向界面插入新的合同信息 InsertContractInfoItem(id,m_strName,m_strContractName, m_strStatus.IsEmpty()?"未开始":m_strStatus,strDate, m_strExecutive,m_fMoney,m_strContractMemo); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } OnBtnContractAdd 函数是利用连接对象的 Execute 的方法实现合同信息的添加功能。 读者可以发现利用 ADO 编程是非常灵活的。 如在客户合同管理中的“客户姓名”文本框中输入“王成”,在“合同名称”文本框 中输入“叶之秋公司总部装修工程”,在“完成状态”列表框中选择“开始”,在“合同金 额”文本框中输入 2200000,在“合同执行人”文本框中输入“工程处”,在“签订时间” — 221 — 日期时间控件中选择 2004-7-2 09:00:00,在“说明”文本框中输入“需要 3 个月”,如图 6-8 所示。 图 6-8 添加合同信息页面 单击“添加”按钮,新的合同信息就添加到合同信息列表框控件中,如图 6-9 所示。 图 6-9 添加合同信息之后的对话框 2. 修改合同信息 为了用户修改合同信息的方便,可以添加合同信息列表框控件 IDC_LIST_ CONTRACT 的 NM_CLICK 消息映射函数 OnClickListContract,代码如下: void CCRDBSDlg::OnClickListContract(NMHDR* pNMHDR, LRESULT* pResult) { int nItem = m_listContract.GetNextItem(-1, LVNI_SELECTED); if(nItem != -1){ //获取合同信息数据 m_strName = m_listContract.GetItemText(nItem,1); m_strContractName = m_listContract.GetItemText(nItem,2); m_strStatus = m_listContract.GetItemText(nItem,3); double fMoney = atof(m_listContract.GetItemText(nItem,4)); m_fMoney = fMoney; m_strExecutive= m_listContract.GetItemText(nItem,5); CString strDT = m_listContract.GetItemText(nItem,6); m_oleContractDate.ParseDateTime(strDT); — 222 — m_oleContractTime.ParseDateTime(strDT); m_strContractMemo = m_listContract.GetItemText(nItem,7); //更新到合同参数的控件中. UpdateData(FALSE); } *pResult = 0; } OnClickListContract 函数把选定的行中的数据显示到合同配置信息的控件中。 修改合同信息的函数为 OnBtnContractMod,代码如下: void CCRDBSDlg::OnBtnContractMod() { if(!UpdateData()) return; if(m_strContractName.IsEmpty()||m_strName.IsEmpty()){ AfxMessageBox("姓名不能够为空"); return; } int nItem = m_listContract.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要修改的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要修改的合同信息"); return; } //从列表框控件中获取选择的合同信息 ID. int id = atoi(m_listContract.GetItemText(nItem,0)); //构造合同签订时间 CString strDate = m_oleContractDate.Format("%Y-%m-%d") + " " + m_oleContractTime.Format("%H:%M:%S"); try { _variant_t RecordsAffected; //更新合同信息. CString sql; sql.Format("update contract_info_tab " "set customer_name = ’%s’,contract_name = ’%s’,status = ’%s’," "contract_date = to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’)," "executive = ’%s’,money = %.2f,memo = ’%s’ " "where contract_id = %d",m_strName,m_strContractName, m_strStatus.IsEmpty()?"未开始":m_strStatus,strDate, m_strExecutive,m_fMoney,m_strContractMemo,id); TRACE(sql); m_pConnection->Execute(_bstr_t(sql),&RecordsAffected,adCmdText); //更新界面上的数据 m_listContract.SetItemText(nItem,1,m_strName); m_listContract.SetItemText(nItem,2,m_strContractName); m_listContract.SetItemText(nItem,3,m_strStatus.IsEmpty()?" 未 开 始 ":m_strStatus); CString temp; — 223 — temp.Format("%.2f",m_fMoney); m_listContract.SetItemText(nItem,4,temp); m_listContract.SetItemText(nItem,5,m_strExecutive); m_listContract.SetItemText(nItem,6,strDate); m_listContract.SetItemText(nItem,7,m_strContractMemo); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 如果要修改“王成”的客户合同,修改“签订时间”日期时间控件的数据为 2004-7-2 09:30:00,单击“修改”按钮,如图 6-10 所示。 图 6-10 修改合同信息对话框 3. 删除合同信息 删除合同信息的函数为 OnBtnContractDel,代码如下: void CCRDBSDlg::OnBtnContractDel() { if(!UpdateData()) return; int nItem = m_listContract.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要删除的合同信息,返回. if(nItem == -1){ AfxMessageBox("没有选择要删除的合同信息"); return; } //从列表框控件中获取选择的合同信息 ID. int id = atoi(m_listContract.GetItemText(nItem,0)); try { _variant_t RecordsAffected; //删除合同信息记录. CString sql; sql.Format("delete from contract_info_tab " — 224 — "where contract_id = %d",id); TRACE(sql); m_pConnection->Execute(_bstr_t(sql),&RecordsAffected,adCmdText); m_listContract.DeleteItem(nItem); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 6.3.6 合同客户信息管理 合同客户信息管理包括对未完成合同信息和合同金额统计信息的管理。 1. 未完成合同信息 未完成合同信息列出了尚未完工的合同的进展情况,方便公司了解工程的进度,合理 部署工作计划。为了显示这些信息的方便,可以创建一个对话框,对话框类名称为 CUnComplContractDlg,资源 ID 为 IDD_DIALOG_UNCOMPLETED,对话框名称为“未完 成合同信息”,“未完成合同信息”对话框如图 6-11 所示。 图 6-11 “未完成合同信息”对话框 “未完成合同信息”对话框只需要一个显示合同信息的列表框控件就可以了,控件 ID 为 IDC_LIST_CONTRACT,控件变量为 m_listContract。 为了直接在 CUnComplContractDlg 类中操作数据库,需要定义一个公有的连接对象的 指针,代码如下: public _ConnectionPtr m_pConnection; 在 CCRDBSDlg 类中 OnBtnUncompletedList 函数中添加弹出“未完成合同信息”对话 框和传入数据库连接对象指针的代码,代码如下: void CCRDBSDlg::OnBtnUncompletedList() { //创建未完成合同信息对话框实例. CUnComplContractDlg dlg; — 225 — //把数据库的连接对象指针传入对话框中. dlg.m_pConnection = m_pConnection; //打开未完成合同信息对话框. dlg.DoModal(); } 需要在 CUnComplContractDlg 类中添加 OnInitDialog 的消息映射函数,在其中添加合 同信息列表框控件的显示列,和主界面中的合同信息列表框控件相同。还要在 CUnComplContractDlg 类中添加一个 InitCtrlData 私有函数,负责显示未完成的合同信息, 代码如下: void CUnComplContractDlg::InitCtrlData(){ try { _RecordsetPtr pRecordset; pRecordset.CreateInstance(_uuidof(Recordset)); //打开所以合同状态不是“完成”的所有记录 pRecordset->Open("select * from contract_info_tab where status <> ’ 完 成 ’ order by contract_date",(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adC mdText); //遍历所有记录 while(!pRecordset->adoEOF) { int id; CString cust_name,con_name,status,date,executive,memo; double money; _variant_t TheValue; //获取合同 ID 字段值. TheValue = pRecordset->Fields->GetItem("contract_id")->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; //获取客户姓名字段值. TheValue = pRecordset->GetCollect("customer_name"); if(TheValue.vt!=VT_NULL) cust_name= (char*)_bstr_t(TheValue); //获取合同名称字段值. TheValue = pRecordset->GetCollect("contract_name"); if(TheValue.vt!=VT_NULL) con_name= (char*)_bstr_t(TheValue); //获取合同完成状态字段值. TheValue = pRecordset->GetCollect("status"); if(TheValue.vt!=VT_NULL) status= (char*)_bstr_t(TheValue); //获取合同签订日期字段值. TheValue = pRecordset->GetCollect("contract_date"); if(TheValue.vt!=VT_NULL){ DATE dt; dt = TheValue.date; COleDateTime oleDT(dt); — 226 — date = oleDT.Format("%Y-%m-%d %H:%M:%S"); } //获取合同执行人字段值. TheValue = pRecordset->GetCollect("executive"); if(TheValue.vt!=VT_NULL) executive= (char*)_bstr_t(TheValue); //获取合同金额字段值. TheValue = pRecordset->GetCollect("money"); if(TheValue.vt!=VT_NULL) money = TheValue.dblVal; //获取合同说明字段值. TheValue = pRecordset->GetCollect("memo"); if(TheValue.vt!=VT_NULL) memo= (char*)_bstr_t(TheValue); int nIndex = m_listContract.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. m_listContract.InsertItem(&lvItem); //设置该行的其他列的值. m_listContract.SetItemText(nIndex,1,cust_name); m_listContract.SetItemText(nIndex,2,con_name); m_listContract.SetItemText(nIndex,3,status); temp.Format("%.2f",money); m_listContract.SetItemText(nIndex,4,temp); m_listContract.SetItemText(nIndex,5,executive); m_listContract.SetItemText(nIndex,6,date); m_listContract.SetItemText(nIndex,7,memo); //转到下一条记录 pRecordset->MoveNext(); } pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 未完成的合同信息是指在合同信息表中 status 字段值不为“完成”,标准的 SQL 语句 为“select * from contract_info_tab where status <> ’完成’ order by contract_date”,其中利用 Order 参数,在创建表的时候添加了 contract_date 的索引,利用这个索引可以提高按照合同 鉴定日期顺序显示数据的速度。 单击主界面上的“未完成合同信息”按钮,弹出“未完成合同信息”对话框,列出所 — 227 — 有的未完成的合同信息,如图 6-12 所示。 图 6-12 “未完成合同信息”对话框 修改主界面中的合同 ID 为 5 的合同信息,修改“完成状态”列表框中的数据为“完 成”,修改“说明”文本框中的数据为“竣工”,修改“签订时间”日期时间控件中的数据 为 2004-6-5 10:00:00”,单击“修改”按钮,修改之后的合同信息如图 6-13 所示。 图 6-13 修改合同信息对话框 单击主界面上的“未完成合同信息”按钮,弹出“未完成合同信息”对话框。在其中 显示一条未完成的合同信息,如图 6-14 所示。 图 6-14 “未完成合同信息”对话框 — 228 — 2. 合同金额统计信息 合同金额统计信息是对合同按照年度或月份进行统计的信息。为了显示这些信息的方 便,可以创建一个对话框,对话框类名称为 CMoneyStatDlg,资源 ID 为 IDD_DIALOG_ MONEY_STAT,对话框名称为“合同金额统计信息”,“合同金额统计信息”对话框如图 6-15 所示。 图 6-15 “合同金额统计信息”对话框 控件类型、ID 及说明见表 6-6。 表 6-6 合同金额统计信息控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 合同统计信息 无 Label IDC_STATIC 请输入年度 无 Edit Box IDC_EDIT_YEAR 无 整数类型变量 m_nYear Label IDC_STATIC 选择月份 无 Combo Box IDC_COMBO_MONTH 无 列表框控件类型变量 m_comboMonth Group Box IDC_STATIC 合同信息 无 List Control IDC_LIST_CONTRACT 无 列表框控件类型变量 m_listContract Label IDC_STATIC 总额(¥) 无 Edit Box IDC_EDIT_TOTAL_MONEY 无 double 类型变量 m_fTotalMoney 为了直接在 CMoneyStatDlg 类中操作数据库,需要定义一个公有的连接对象的指针, 代码如下: public _ConnectionPtr m_pConnection; 在 CCRDBSDlg 类中 OnBtnMoneyStat 函数中添加弹出“合同金额统计信息”对话框和 传入数据库连接对象指针的代码,代码如下: void CCRDBSDlg::OnBtnMoneyStat() { //创建合同金额统计信息对话框实列. CMoneyStatDlg dlg; //把数据库的指针传入对话框中. — 229 — dlg.m_pConnection = m_pConnection; //打开合同金额统计信息对话框. dlg.DoModal(); } 需要在 CMoneyStatDlg 类中添加 OnInitDialog 的消息映射函数,在其中添加合同信息 列表框控件的显示列,和主界面中的合同信息列表框控件相同。还要在 CMoneyStatDlg 类 中添加一个月份列表框的 CBN_CLOSEUP 的消息,控件 ID 为 IDC_COMBO_MONTH,消 息映射函数为 OnCloseupComboMonth,代码如下: void CMoneyStatDlg::OnCloseupComboMonth() { CString sql; CString strMonth,strYear; GetDlgItem(IDC_EDIT_YEAR)->GetWindowText(strYear); int nYear = atoi(strYear); //获取当前选择项目 int nIndex = m_comboMonth.GetCurSel(); //如果没有选择,退出 if(nIndex == -1) return; m_listContract.DeleteAllItems(); //获取当前选择的月份 m_comboMonth.GetLBText(nIndex,strMonth); //如果选项为空,退出 if(strMonth.IsEmpty()) return; int nMonth; //如果是所有月份,就列出一年中所有的合同记录 if(strMonth.CompareNoCase("所有月份")==0){ nMonth = 1; } //如果选择某一确定的月份,就列出该月中所有的合同记录 else nMonth = atoi(strMonth.Left(1)); COleDateTime dt; dt.SetDateTime(nYear,nMonth,1,0,0,0); CString strDate = dt.Format("%Y-%m-%d %H:%M:%S"); //根据年份和月份进行查询. sql.Format("Select * from contract_info_tab where " "contract_date >= to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’) " " and contract_date < ADD_MONTHS(to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’),%d)",strDate,strDate,strMonth.CompareNoCase(" 所 有 月 份 ")==0?12:1); TRACE(sql); double total = 0; try { _RecordsetPtr pRecordset; pRecordset.CreateInstance(_uuidof(Recordset)); — 230 — pRecordset->Open(_bstr_t(sql),(IDispatch*)m_pConnection,adOpenDynamic,a dLockOptimistic,adCmdText); //遍历所有记录 while(!pRecordset->adoEOF) { int id; CString cust_name,con_name,status,date,executive,memo; double money; _variant_t TheValue; //获取合同 ID 字段值. TheValue = pRecordset->Fields->GetItem("contract_id")->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; //获取客户姓名字段值. TheValue = pRecordset->GetCollect("customer_name"); if(TheValue.vt!=VT_NULL) cust_name= (char*)_bstr_t(TheValue); //获取合同名称字段值. TheValue = pRecordset->GetCollect("contract_name"); if(TheValue.vt!=VT_NULL) con_name= (char*)_bstr_t(TheValue); //获取合同完成状态字段值. TheValue = pRecordset->GetCollect("status"); if(TheValue.vt!=VT_NULL) status= (char*)_bstr_t(TheValue); //获取合同签订日期字段值. TheValue = pRecordset->GetCollect("contract_date"); if(TheValue.vt!=VT_NULL){ DATE dt; dt = TheValue.date; COleDateTime oleDT(dt); date = oleDT.Format("%Y-%m-%d %H:%M:%S"); } //获取合同执行人字段值. TheValue = pRecordset->GetCollect("executive"); if(TheValue.vt!=VT_NULL) executive= (char*)_bstr_t(TheValue); //获取合同金额字段值. TheValue = pRecordset->GetCollect("money"); if(TheValue.vt!=VT_NULL) money = (float)TheValue.dblVal; //增加总的金额 total += money; //获取合同说明字段值. TheValue = pRecordset->GetCollect("memo"); if(TheValue.vt!=VT_NULL) memo= (char*)_bstr_t(TheValue); int nIndex = m_listContract.GetItemCount(); LV_ITEM lvItem; — 231 — lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. m_listContract.InsertItem(&lvItem); //设置该行的其他列的值. m_listContract.SetItemText(nIndex,1,cust_name); m_listContract.SetItemText(nIndex,2,con_name); m_listContract.SetItemText(nIndex,3,status); temp.Format("%.2f",money); m_listContract.SetItemText(nIndex,4,temp); m_listContract.SetItemText(nIndex,5,executive); m_listContract.SetItemText(nIndex,6,date); m_listContract.SetItemText(nIndex,7,memo); //转到下一条记录 pRecordset->MoveNext(); } pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } CString temp; temp.Format("%.2f",total); //设置合同金额的总额 GetDlgItem(IDC_EDIT_TOTAL_MONEY)->SetWindowText(temp); } 在“合同金额统计信息”对话框中的“选择月份”列表框中选择“所有月份”,将列 出该年度的所有合同金额统计信息,如果在“选择月份”列表框中选择某一月份,将列出 该月份下的所有合同金额统计信息。要实现这个功能,就应该构造相应的 SQL 语句。如在 “请输入年度”文本框中输入 2004,在“选择月份”列表框中选择“7 月”,就应该列出所 有签订合同日期在 2004 年 7 月 1 日和 2004 年 8 月 1 日之间的所有合同信息,利用 ADD_MONTHS 方法可以方便地构造这个 SQL 语句。在利用 ADD_MONTHS 的时候,只 需传入增加的月份数,数据库系统就会获取几个月之后的日期,而不用考虑繁琐的算法, 如需要考虑大月 31 天还是小月 30 天,以及 12 月下一个月为 1 月的问题。 单击主界面上的“合同金额统计信息”按钮,弹出“合同金额统计信息”对话框。在 “请输入年度”文本框中输入 2004,在“选择月份”列表框中选择“7 月”之后,就会在 合同信息列表框中显示 7 月份的一条合同信息,如图 6-16 所示。 在“合同金额统计信息”对话框中的“总额”文本框中显示 7 月份所有的合同总额 2200000.00 元。 如果在“合同金额统计信息”对话框中的“请输入年度”文本框中输入 2004,在“选 择月份”列表框中选择“所有月份”之后,就会在合同信息列表框中列出 2004 年度的所有 — 232 — 合同信息及合同总额信息 3200000.60 元,如图 6-17 所示。 图 6-16 “合同金额统计信息”对话框 图 6-17 年度“合同金额统计信息”对话框 6.4 本章小结 本章介绍了一个简化的客户资源管理系统的开发过程。作为一个客户资源管理系统, 需要对客户信息和合同信息提供有效的管理。该系统还适用于业务员或销售人员管理好自 己的客户和业绩。通常,业务员会面对许多客户,名片本也可能用完了几本,在如此众多 的名片中找寻需要的客户名片很不容易。而且业务员还需要了解自己和客户之间签订的合 同,并查询自己年度或者月份的业绩情况。利用这个客户资源管理系统可以非常方便地实 现这些功能。 客户资源管理系统采用 ADO 开发数据库开发技术。在系统中用到了连接对象、命令对 象和记录集对象来操作数据库,从中可见 ADO 编程的灵活性。客户资源管理系统也使用了 存储过程,可以提高向数据库中更新客户信息的速度,通常客户的信息是比较多的。 第 7 章 行业监管系统 对于每一个行业来说,都希望本行业的企业遵纪守法,公平竞争,既有利于企业的利 益,也有利于消费者的利益,而今面对越来越多的反倾销案,行业的团结更能在这些案件 中有更多胜诉的把握。行业监管系统,提供了对行业内企业信息的管理,以及对这些企业 质量信誉的管理。 7.1 系统设计 系统设计是系统开发最为关键的一环,系统设计好了,系统的实现以及以后的系统测 试都会节省很多的物力和财力。通过系统的设计,开发人员能够更好地把握系统的需求, 了解系统的各功能模块。 7.1.1 功能描述 行业监管系统包括系统登录管理、企业基本信息管理和企业质量信誉档案管理。详细 的功能描述如下。 1. 系统登录管理 系统登录管理包括系统密码设置和系统数据库参数的配置。密码设置,包括密码的校 验、修改和密码错误的处理。系统数据库参数的配置包括对数据库连接参数的配置和修改。 2. 企业基本信息管理 企业基本信息管理提供了对企业基本信息的添加、修改和删除的功能。企业基本信息 主要包括申请日期、企业名称、企业地址、企业类型、联系电话、工商登记号、企业信誉 等级、法定代表人和经营范围等信息。 3. 企业质量信誉档案管理 企业质量信誉档案管理提供了对企业质量和信誉信息的添加、修改和删除的功能。这 些信息一部分来自国家监管部门或者政府的公告,一部分来自于新闻媒体,还有一部分来 自于公司内部。如一些公司通过的质量认证、政府部门的表彰、荣誉称号,或者是监管部 门对某些企业的曝光,这些都是一个行业协会在监管自己内部企业的时候应该记载的质量 和信誉信息,并且可以通过这些信息评定行业内部的信誉等级,作为国家监管的一个补充。 7.1.2 功能模块设计 从上面的功能描述中,可以把行业监管系统分为 3 个模块:系统登录管理、企业基本 信息管理和企业质量信誉档案管理。在每一个模块下又提供了更为具体的功能。详细的行 业监管系统的功能模块图,如图 7-1 所示。 — 234 — 图 7-1 系统功能模块图 7.2 数据库设计与实现 数据库设计是系统开发中非常重要的一个环节。数据库结构设计的好坏将直接影响到 系统的效率和功能的实现。在设计数据库之前,要了解数据库的需求,从而确定数据库的 结构。 7.2.1 数据库需求设计 通过以上的功能分析,行业监管系统需要包含以下数据库信息。 1. 企业基本信息 包括企业 ID、申请日期、中文企业名称、英文企业名称、中文企业地址、英文企业地 址、邮政编码、企业类型、联系电话、工商登记号、企业信誉等级、法定代表人和经营范 围。 2. 企业质量信誉档案信息 包括档案 ID、企业 ID、时间和说明。 7.2.2 数据库表的设计 通过数据库的需求,可以创建以下数据表。 1. 企业基本信息表(enterprise_info_tab) 企业基本信息表包括企业的基本信息。企业基本信息的管理实际上是对企业基本信息 表的管理,表的结构见表 7-1。 — 235 — 表 7-1 企业基本信息表 字段名称 数据类型 可否为空 约束条件 说 明 enterprise_id INTEGER NOT NULL 主键 企业 ID 值 apply_time DATE NOT NULL 无 申请时间 ch_name VARCHAR2(60) NOT NULL 无 中文企业名称 en_name VARCHAR2(60) NULL 无 英文文企业名称 ch_address VARCHAR2(60) NULL 无 中文企业地址 En_address VARCHAR2(60) NULL 无 英文企业地址 code VARCHAR2(24) NULL 无 邮政编码 type VARCHAR2(24) NULL 无 企业类型 telephone VARCHAR2(24) NULL 无 联系电话 register_no VARCHAR2(40) NULL 无 工商登记号 credit_rank VARCHAR2(24) NULL 无 企业信誉等级 jur_person VARCHAR2(24) NULL 无 法定代表人 memo VARCHAR2(1000) NULL 无 经营范围 2. 企业质量信誉档案表(quality_credit_info_tab) 企业质量信誉档案表包括了企业质量信誉档案信息。企业质量信誉档案管理实际上是 对企业质量信誉档案表的管理,表的结构见表 7-2。 表 7-2 企业质量信誉档案表 字段名称 数据类型 可否为空 约束条件 说 明 info_id INTEGER NOT NULL 主键 档案 ID enterprise_id INTEGER NOT NULL 外键,来自 enterprise_info_tab 的 enterprise_id 企业 ID 值 info_date DATE NOT NULL 无 时间 memo VARCHAR2(1000) NULL 无 说明 7.2.3 数据库表的创建 利用第 3 章中讲述的方法创建表空间 dbindustry 和数据库用户 dbindustry,其中数据库 用户的密码为 dbindustry,选择的默认表空间为 dbindustry。 创建行业监管系统所有数据表的 SQL 语句如下: --企业基本信息表 CREATE TABLE enterprise_info_tab( enterprise_id INTEGER PRIMARY KEY, apply_time DATE NOT NULL, ch_name VARCHAR2(60) NOT NULL, en_name VARCHAR2(60) NULL, ch_address VARCHAR2(60) NULL, en_address VARCHAR2(60) NULL, code VARCHAR2(24) NULL, type VARCHAR2(24) NULL, telephone VARCHAR2(24) NULL, register_no VARCHAR2(40) NULL, credit_rank VARCHAR2(24) NULL, jur_person VARCHAR2(24) NULL, memo VARCHAR2(1000) NULL — 236 — ); --添加申请时间 apply_time 的索引 CREATE INDEX enterprise_apply_timeindex ON enterprise_info_tab(apply_time); --企业质量信誉档案表 CREATE TABLE quality_credit_info_tab( info_id INTEGER PRIMARY KEY, enterprise_id INTEGER NOT NULL, info_date DATE NOT NULL, memo VARCHAR2(1000) NULL ); --创建可以递增的系列号供企业基本信息表的 enterprise_id 使用 CREATE SEQUENCE seq_enterprise_id INCREMENT BY 1 START WITH 10000 NOMAXVALUE NOMINVALUE NOCYCLE; --创建可以递增的系列号供企业质量信誉档案表的 info_id 使用 CREATE SEQUENCE seq_info_id INCREMENT BY 1 START WITH 1 NOMAXVALUE NOMINVALUE NOCYCLE; 利用 Oracle SQLPlus WorkSheet 工具执行上述的 SQL 语句从而创建数据库表。需要说 明的是,在打开 Oracle SQLPlus Worksheet 的“Oracle Enterprise Manager 登录”对话框的 时候,需要在“用户名”文本框中输入企业设备管理系统的用户名 dbindustry,在“口令” 文本框中输入用户密码 dbindustry,在“服务”文本框中输入数据库的本地服务名 ORADB, 选择连接方式 Normal,登录成功之后,再运行上述的 SQL 语句。 7.3 系统的实现 完成了系统功能模块的设计和数据库表的创建,就可以创建一个行业监管系统。 7.3.1 创建应用程序 运行 Visual C++,选择 File|New 命令,弹出 New 对话框。从 Projects 列表中选择 MFC AppWizard(exe)向导,在 Location 文本框中选择项目工程的目录“D:\VCSAMPLE\”,在 Project name 文本框中输入工程文件的名称 IndustryDBS,单击 OK 按钮,进入 MFC AppWizard – Step 1 页面,从应用程序的类型中选择 Dialog based 选项,从语言列表中选择 “中文(中国)(APPWZCHS.DLL)”,单击 Next 按钮,进入 MFC AppWizard – Step 2 of 4 页 面,选择 Automation 选项,IndustryDBS 对话框应用程序就创建完成了。 7.3.2 创建系统登录界面 系统登录包括系统密码设置、系统密码登录和数据库连接参数设置 3 个部分,需要创 建相应的对话框来实现这些功能。 1. 系统密码设置 系统密码设置的界面,如图 7-2 所示。 — 237 — 图 7-2 “请设置系统密码”对话框 控件类型、ID 及说明见表 7-3。 表 7-3 系统密码设置控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 系统密码尚未设置,请设置密码 无 Label IDC_STATIC 设置密码 无 Edit Box IDC_EDIT_SET_PWD 无 CString 类型变量 m_strSetPwd Label IDC_STATIC 确认密码 无 Edit Box IDC_EDIT_CONFIRM_PWD 无 CString 类型变量 m_strConfirmPwd Button IDOK 确定 函数 OnOK ()验证密码 Button IDCANCEL 取消 无 类文件名称为 CSetPwdDlg。主要目的是设置系统登录的密码。 OnOK 函数的代码为: void CSetPwdDlg::OnOK() { if(!UpdateData()) return ; if(m_strSetPwd.Compare(m_strConfirmPwd)!=0) { MessageBox("两次密码输入不相同,请重新输入!", "错误", MB_OK|MB_ICONSTOP); return ; } CDialog::OnOK(); } OnOK 函数检验设置的密码和确认的密码是否一致,如果不一致,就提示用户重新输 入。系统第 1 次运行,将弹出系统密码设置对话框,设置系统的密码。 2. 系统密码登录 系统密码登录的界面,如图 7-3 所示。 图 7-3 “请输入密码”对话框 控件类型、ID 及说明见表 7-4。 — 238 — 表 7-4 系统密码登录控件列表 控件类型 ID 属 性 变量或函数 Label IDC_STATIC 请输入系统登录密码 无 Edit Box IDC_EDIT_PWD 无 CString 类型变量 m_strPwd Button IDOK 确定 无 Button IDCANCEL 取消 无 类文件名称为 CInputPwdDlg。如果系统的密码已经设置,在系统启动的时候,会弹出 “请输入密码”对话框,输入正确的密码之后就可以进入系统。 3. 数据库配置参数设置 数据库配置参数设置的界面,如图 7-4 所示。 图 7-4 “数据库配置参数”对话框 控件类型、ID 及说明见表 7-5。 表 7-5 数据库配置参数控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 请输入数据库参数 无 Label IDC_STATIC 数据库源名称 无 Edit Box IDC_EDIT_DBSOURCE 无 CString 类型变量 m_strDBSource Label IDC_STATIC 数据库用户名称 无 Edit Box IDC_EDIT_DBUSER 无 CString 类型变量 m_strDBUser Label IDC_STATIC 数据库用户密码 无 Edit Box IDC_EDIT_PASSWORD 无 CString 类型变量 m_strDBPwd Button IDOK 确定 无 Button IDCANCEL 取消 无 类文件名称为 CConfigDBDlg,用来配置系统的数据库参数,在系统首次运行的时候, 弹出“数据配置参数”对话框来配置完数据库参数。系统运行时可以利用这个对话框修改 数据库的连接参数。 7.3.3 创建主对话框的界面 主对话框的布局如图 7-5 所示。其中包括企业基本信息管理和质量信誉档案管理两个 部分。 — 239 — 图 7-5 “行业监管系统”对话框 1. 企业基本信息管理 控件类型、ID 及说明见表 7-6。 表 7-6 企业基本信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 企业基本信息管理 无 Label IDC_STATIC 申请日期 无 Date Time Picker IDC_APPLY_DATE 无 COleDateTime 类型变量 m_oleApplyDate Label IDC_STATIC 企业中文名称 无 Edit Box IDC_EDIT_CH_NAME 无 CString 类型变量 m_strCHName Label IDC_STATIC 企业英文名称 无 Edit Box IDC_EDIT_EN_NAME 无 CString 类型变量 m_strENName Label IDC_STATIC 企业中文地址 无 Edit Box IDC_EDIT_CH_ADDRESS 无 CString 类型变量 m_strCHAddress Label IDC_STATIC 企业英文地址 无 Edit Box IDC_EDIT_EN_ADDRESS 无 CString 类型变量 m_strENAddress Label IDC_STATIC 邮政编码 无 Edit Box IDC_EDIT_CODE 无 CString 类型变量 m_strCode Label IDC_STATIC 企业类型 无 Combo Box IDC_COMBO_TYPE 无 CString 类型变量 m_strEnterpriseType — 240 — (续表) 控件类型 ID 属 性 变量或函数 Label IDC_STATIC 联系电话 无 Edit Box IDC_EDIT_PHONE 无 CString 类型变量 m_strPhone Label IDC_STATIC 工商登记号 无 Edit Box IDC_EDIT_REGISTERNO 无 CString 类型变量 m_strRegisterNo Label IDC_STATIC 企业信誉等级 无 Combo Box IDC_COMBO_CREDITRANK 无 CString 类型变量 m_strCreditRank Label IDC_STATIC 法定代表人 无 Edit Box IDC_EDIT_JUR 无 CString 类型变量 m_strJurPerson Label IDC_STATIC 经营范围 无 Edit Box IDC_EDIT_MEMO 无 CString 类型变量 m_strMemo Button IDC_BTN_DB_MODIFY 修改数据库配置 函数 OnBtnDbModify ()修改数据库配置参数 Button IDC_BTN_PWD_MODIFY 修改系统密码 函数 OnBtnPwdModify ()修改系统密码 Button IDC_ENTERPRISE_ADD 添加企业信息 函数 OnEnterpriseAdd ()添加企业基本信息 Button IDC_ENTERPRISE_MOD 修改企业信息 函数 OnEnterpriseMod ()修改企业基本信息 Button IDC_ENTERPRISE_DEL 删除企业信息 函数 OnEnterpriseDel ()删除企业基本信息 Button IDC_ENTERPRISE_QUERY 企业名称查询 函数 OnEnterpriseQuery ()查询企业基本信息 Button IDC_SYS_EXIT 系统退出 函数 OnSysExit ()退出系统处理函数 Group Box IDC_STATIC 企业信息 无 List Control IDC_LIST_ENTERPRISE 无 列表框控件类型变量 m_listEnterprise 2. 质量信誉档案管理 控件类型、ID 及说明见表 7-7。 表 7-7 质量信誉档案管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 质量信誉档案管理 无 Label IDC_STATIC 日期 无 Date Time Picker IDC_INFO_DATE 无 COleDateTime 类型变量 m_oleInfoDate Label IDC_STATIC 质量信誉档案信息 无 Edit Box IDC_EDIT_DESCRIP 无 CString 类型变量 m_strDescrip Button IDC_BTN_INFO_ADD 添加 函数 OnBtnInfoAdd ()添加质量信誉档案管理信息 Button IDC_BTN_INFO_DEL 删除 函数 OnBtnInfoDel ()删除质量信誉档案管理信息 List Control IDC_LIST_INFO 无 列表框控件类型变量 m_listInfo 主对话框类名称为 CIndustryDBSDlg,资源 ID 为 IDD_INDUSTRYDBS_DIALOG,对 话框名称为“行业监管系统”。主界面用到了两个列表框控件分别显示企业基本信息和质量 信誉档案信息。需要为这两个列表框控件添加显示列,以显示相应的数据信息。为了代码 设计的清晰,在 CIndustryDBSDlg 类中定义了一个 InitControl 私有函数负责添加控件的显 示列,InitControl 函数比较简单,可以参见前面的章节,代码不再列出。在 OnInitDialog 函数末尾处添加 InitControl 函数的调用,这样系统在启动的时候,就可以看到已添加显示 列的列表框控件。 7.3.4 系统登录管理 系统登录包括系统密码设置、系统密码登录和数据库连接参数设置 3 个部分。在前面 的章节中介绍了利用配置文件获取数据库连接参数的方法,在这一章中将介绍利用注册表 读取或保存数据库连接参数和系统登录密码的方法。 — 241 — 要对注册表进行操作,需要定义两个函数,一个负责把数据保存到注册表中,另一个 负责从注册表中读取数据。从程序的设计规范来说,可以把这两个函数作为工具函数,不 隶属于任何一个类。把这些独立的工具函数放在文件中,在 tools.h 文件添加这些工具函数 的 extern 引用,而在 tools.cpp 文件中添加这些函数的具体实现。 另外,在 tools.h 文件中定义了一些字符串常量,代码如下: #ifndef TOOLS_H #define TOOLS_H #define INDUSTRY_DBS_ROOT "VC ORACLE 开发实例\\行业监管系统" #define INDUSTRY_DBS_SYS_PWD "系统登录密码" #define INDUSTRY_DBS_DB_SOURCE "数据库源名称" #define INDUSTRY_DBS_DB_USER "数据库用户名称" #define INDUSTRY_DBS_DB_PASSWORD "数据库密码" //从注册表中获取数据 extern BOOL LoadKey(CString strSection, CString strKey, CString& strValue); //向注册表中写入数据 extern BOOL SaveKey(CString strSection, CString strKey, CString strValue); #endif 在 tools.h 文件中使用#ifndef 和#endif 指令,是避免每次引用 tools.h 文件的时候,都定 义一次文件中的变量,至于 TOOLS_H 的写法没有限制,只需#ifndef 和#define 指令后的宏 是一致就可以。INDUSTRY_DBS_ROOT 宏是一个字符串常量,代表字符串“VC ORACLE 开发实例\\行业监管系统”,这个宏会在系统的其他地方用到,用于构造注册表项,其他几 个字符串常量是数据库连接参数和系统密码的键名。 当需要引用 tools.h 文件中的字符串常量、LoadKey 和 SaveKey 函数的时候,只需引入 tools.h 文件。在本系统中,把 tools.h 文件的引用和 ADO 库的引入都放在 StdAfx.h 文件中, 方法如下: #include "tools.h" #import "C:\Program Files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") 在 tools.cpp 文件中定义了 LoadKey 和 SaveKey 函数的具体实现,代码如下: #include "stdafx.h" #include "tools.h" BOOL LoadKey(CString strSection, CString strKey, CString& strValue) { strValue = _T(""); BOOL bReturn = TRUE; HKEY hRootKey,hKey; //打开注册表项"\HKEY_LOCAL_MACHINE\SOFTWARE\" bReturn &= RegOpenKeyEx(HKEY_LOCAL_MACHINE,"software",0,KEY_ALL_ACCESS|KEY_READ|KEY_WR ITE,&hRootKey)==ERROR_SUCCESS; //获取子项 bReturn &= RegCreateKey(hRootKey,strSection,&hKey)==ERROR_SUCCESS; unsigned char pValue[256]; — 242 — DWORD dwType; unsigned long lLen = 256; //获取子项下面的键值 bReturn &= RegQueryValueEx(hKey,strKey,NULL,&dwType,pValue,&lLen)==ERROR_SUCCESS; if(bReturn) strValue = pValue; else strValue = _T(""); RegCloseKey(hRootKey); RegCloseKey(hKey); return bReturn; } BOOL SaveKey(CString strSection, CString strKey, CString strValue) { BOOL bReturn = TRUE; HKEY hRootKey,hKey; //打开注册表项"\HKEY_LOCAL_MACHINE\SOFTWARE\" bReturn &= RegOpenKeyEx(HKEY_LOCAL_MACHINE,"software",0,KEY_ALL_ACCESS|KEY_READ|KEY_WR ITE,&hRootKey)==ERROR_SUCCESS; //获取子项 bReturn &= RegCreateKey(hRootKey,strSection,&hKey)==ERROR_SUCCESS; int nLen = strValue.GetLength (); BYTE* pData = new BYTE[nLen+1]; memcpy(pData,strValue,nLen); pData[nLen] = ’\0’; //设置子项下面的键值 bReturn &= RegSetValueEx(hKey,strKey,0,REG_SZ,pData,nLen+1)==ERROR_SUCCESS; delete pData; RegCloseKey(hKey); RegCloseKey(hRootKey); return bReturn; } LoadKey 函数获取注册表中的键值,SaveKey 函数向注册表中写入键值。 系统首次运行的时候,需要设置登录密码和数据库连接参数。以后的系统运行只需输 入正确的密码就可以进入系统的主页面,而输入错误的密码会退出系统,要实现这个功能 需要在主界面对话框类 CIndustryDBSDlg 的 OnInitDialog 函数中添加如下代码: if(!VerifyPwd()) OnSysExit(); //获取数据库连接参数,如果注册表中没有数据库连接参数,弹出数据库参数设置框 loadDBConfig(); VerifyPwd 函数实现系统登录密码设置和系统登录密码验证的功能,当系统登录密码 设置取消或系统登录密码验证失败的时候返回 FALSE,从而调用 OnSysExit 函数退出系统, 代码如下: — 243 — BOOL CIndustryDBSDlg::VerifyPwd() { bResult &= LoadKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_SYS_PWD,m_strSysPasswd); //如果没有密码信息 if(!bResult){ //探出密码设置对话框 CSetPwdDlg dlg; if(dlg.DoModal() == IDOK) { //获取设置的密码值,并保存到注册表 m_strSysPasswd = dlg.m_strConfirmPwd; SaveKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_SYS_PWD,m_strSysPasswd); return TRUE; } } //如果有密码信息,弹出输入密码对话框 else{ CInputPwdDlg dlg; int nTimes = 0 ; //有三次输入密码的机会,如果正确就返回 TRUE,否则返回 TRUE //从而控制程序的运行 while(nTimes < 3 && dlg.DoModal() == IDOK) { if (dlg.m_strPwd.Compare(m_strSysPasswd)==0) return TRUE; if(nTimes < 2) AfxMessageBox("密码错误,请重新输入!"); else AfxMessageBox("密码错误,您无权使用本系统,请与管理人员联系!\n 按“确 定”退出系统。"); nTimes++; } } return FALSE; } VerifyPwd 函数首先从注册表中获取系统登录密码,如果失败,说明是系统首次运行, 需要为系统设置登录密码,弹出“请设置系统密码”对话框进行系统密码的设置,如在“设 置密码”文本框中输入密码 12345678,和在“确认密码”文本框中输入 12345678,如图 7-6 所示。 图 7-6 “请设置系统密码”对话框 — 244 — 单击“确定”按钮,如果设置密码和确认密码一致,就把系统登录密码保存到注册表 中,选择系统的“开始”|“运行”命令,弹出“运行”对话框,在“打开”文本框中输入 regedit,单击“确定”按钮,弹出“注册表编辑器”窗口。在页面左侧的树中选择“我的 电脑”|HKEY_LOCAL_MACHINE|SOFTWARE|“VC ORACLE 开发实例”|“行业监管系 统”树节点,在右侧页面显示“系统登录密码”的键值,如图 7-7 所示。 图 7-7 “注册表编辑器”窗口 在实际项目开发的时候,需要对系统登录密码进行加密,可以使用不可逆的加密算法, 如 MD5 算法,把加密后的数据(取算法后的值)保存在注册项里。在系统登录的时候,只要 判断输入的密码取算法后的值和注册表的值是否一致,如果一致,输入的登录密码就是正 确的,从而登录系统的主页面。 设置系统首次运行的登录密码之后,再次运行系统就会弹出“请输入密码”对话框, 在对话框中的“请输入系统登陆密码”文本框中输入密码 12345678,如图 7-8 所示。 图 7-8 “请输入密码”对话框 如果密码输入错误,将弹出“密码错误,请重新输入!”的提示信息,如果 3 次都输 入错误的密码,将提示用户“密码错误,您无权使用本系统,请与管理人员联系!按‘确 定’退出系统”,退出系统。退出系统的函数为 OnSysExit。 void CIndustryDBSDlg::OnSysExit() — 245 — { if (CanExit()) CDialog::OnOK(); } 同时注释掉 OnOK 和 OnCancel 函数中的动生成代码,方法如下: void CIndustryDBSDlg::OnOK() { // if (CanExit()) // CDialog::OnOK(); } void CIndustryDBSDlg::OnCancel() { // if (CanExit()) // CDialog::OnCancel(); } 注释这些代码的目的是屏蔽掉键盘的 Enter 和 Esc 键的响应,让系统自己处理系统的 退出,因为默认情况下,当用户按 Enter 键和 Esc 键的时候,会退出对话框。 系统首次登录,并设置系统登录密码之后,弹出“数据库配置参数”对话框进行数据 库连接参数的配置。如在“数据库数据源名称”文本框中输入 ORADB,在“数据库用户 名称”文本框中输入 dbindustry,在“数据库用户密码”文本框中输入 dbindustry,如图 7-9 所示。 图 7-9 “数据库配置参数”对话框 单击“确定”按钮,将数据库的连接参数写入注册表中。loadDBConfig 函数就是用来 从注册表中读取数据库连接参数和当读取失败时,弹出“数据库配置参数”对话框设置数 据库配置参数的,代码如下: void CIndustryDBSDlg::loadDBConfig() { BOOL bResult = TRUE; //从注册表中获取数据库连接参数 bResult &= LoadKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_DB_SOURCE,m_strDBSource); bResult &= LoadKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_DB_USER,m_strDBUser); bResult &= LoadKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_DB_PASSWORD,m_strDBPasswd); //获取参数失败,弹出设置数据库连接参数的对话框 if(!bResult){ — 246 — CConfigDBDlg dlg; if(IDOK == dlg.DoModal()) { //获取数据库连接参数,并保存到注册表中 m_strDBSource = dlg.m_strDBSource; m_strDBUser = dlg.m_strDBUser; m_strDBPasswd = dlg.m_strDBPwd; SaveKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_DB_SOURCE,m_strDBSource); SaveKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_DB_USER,m_strDBUser); SaveKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_DB_PASSWORD,m_strDBPasswd); } } } 设置完数据库连接参数之后,将会在注册表中看到这些数据库连接参数的键值,如图 7-10 所示。 图 7-10 “注册表编辑器”窗口 7.3.5 显示数据到界面上 当系统启动之后,可以将数据库中的数据显示到界面上,需要处理数据库的连接和显 示数据到界面上的两个过程。 1. 数据库的连接 ADO 数据库的连接需要 3 个重要的参数:数据库数据源的名称、数据库用户名称和数 据库用户密码。其中数据库数据源的名称就是本地服务名 ORADB,数据库用户的名称和 密码均为 dbindustry。这些参数都保存在注册表中,有系统运行的时候从注册表中获取。 — 247 — 连接数据库的函数为 ConnectDB。定义如下: void CIndustryDBSDlg::ConnectDB(){ //如果数据库实例存在,关闭 if(m_pConnection) m_pConnection->Close(); //初始化 Connection 指针 m_pConnection.CreateInstance(_uuidof(Connection)); //初始化 Recordset 指针 m_pRecordset.CreateInstance(_uuidof(Recordset)); try { CString strConnect; strConnect.Format("Provider=OraOLEDB.Oracle.1;Password=%s;User ID=%s;" "Data Source=%s;Persist Security Info=True",m_strDBPasswd,m_strDBUser,m_strDBSource); //连接数据库 m_pConnection->Open((_bstr_t)strConnect,"","",-1); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } ConnectDB 函数首先检查 m_pConnection 的实例是否存在,如果存在,就关闭数据库 连接,以备重新打开数据库。在修改数据库的连接参数之后也需要用到这个函数重新建立 数据库的连接。将 ConnectDB 函数添加在 OnInitDialog 函数中,放在 LoadDBConfig 函数 的调用之后,代码如下: loadDBConfig(); //连接数据库 ConnectDB(); 修改数据库连接参数的函数为 OnBtnDbModify,代码如下: void CIndustryDBSDlg::OnBtnDbModify() { //修改数据库的连接参数 CConfigDBDlg dlg; //对话框打开的时候,传入数据库的参数 dlg.m_strDBSource = m_strDBSource; dlg.m_strDBUser = m_strDBUser; dlg.m_strDBPwd = m_strDBPasswd; if(IDOK == dlg.DoModal()) { //获取新的数据库连接参数,并保存到注册表中 m_strDBSource = dlg.m_strDBSource; m_strDBUser = dlg.m_strDBUser; m_strDBPasswd = dlg.m_strDBPwd; — 248 — SaveKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_DB_SOURCE,m_strDBSource); SaveKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_DB_USER,m_strDBUser); SaveKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_DB_PASSWORD,m_strDBPasswd); //用新的数据库参数,重新连接数据库 ConnectDB(); } } 单击主要界面上的“修改数据库配置”按钮。弹出“数据库配置参数”对话框,并自 动带入系统原来的数据库连接参数,如图 7-11 所示。 图 7-11 “数据库配置参数”对话框 修改其中的参数,单击“确定”按钮,就可以修改数据库的连接参数,最后调用 ConnectDB 函数重新连接数据库。 修改系统登录密码的函数为 OnBtnPwdModify,代码如下: void CIndustryDBSDlg::OnBtnPwdModify() { //修改系统的密码 CModifyPwdDlg dlg; if(dlg.DoModal() == IDOK) { //如果旧密码输入错误,返回 if(m_strSysPasswd.CompareNoCase(dlg.m_strOldPwd)!=0) { AfxMessageBox("输入的旧密码错误"); return; } //获取新的密码值,并保存到数据库 m_strSysPasswd = dlg.m_strConfirmPwd; SaveKey(INDUSTRY_DBS_ROOT,INDUSTRY_DBS_SYS_PWD,m_strSysPasswd); return; } } 单击主界面上的“修改系统密码”按钮,在弹出的“修改密码”对话框的“旧密码” 文本框中输入旧密码 12345678,在“新密码”文本框中输入 123,在“确认密码”文本框 中输入 123,如图 7-12 所示。 单击“确定”按钮,新的系统登录密码 123 就保存到注册表中了。 — 249 — 图 7-12 “修改密码”对话框 2. 显示数据到界面上 连接数据库之后,需要把数据库中的数据显示在两个列表框控件中。在 CIndustryDBSDlg 类中定义一个 RefreshCtrlData 私有函数,根据传入的 SQL 语句显示符合 条件的所有企业基本信息。再定义一个 RefreshQualityCreditInfoData 函数,负责显示当前 企业信息下的所有质量信息档案。同时,还要定义两个分别把数据插入到列表框控件中的 函 数 , 分 别 为 : InsertEnterpriseInfoItem 和 InsertQualityCreditInfoItem 。 在 头 文 件 为 IndustryDBSDlg.h 中定义这些私有函数,代码如下: private: //当前的企业信息 ID int m_currentEnterpriseID; //密码验证 BOOL VerifyPwd(); //加载数据库连接参数 void loadDBConfig(); //连接数据库 void ConnectDB(); //初始化列表框控件. void InitControl(); //从数据库获取企业信息并更新到控件中 void RefreshCtrlData(CString sql); //刷新当前企业的质量信誉档案 void RefreshQualityCreditInfoData(); //插入企业基本信息 void InsertEnterpriseInfoItem(int id,CString date,CString chname,CString enname,CString chaddress,CString enaddress,CString code,CString type,CString phone,CString registerno,CString creditrank,CString jurperson,CString memo); //插入质量信誉档案 void InsertQualityCreditInfoItem(int id,CString date,CString descrip); 向企业信息列表框控件中插入企业基本信息的函数为 InsertEnterpriseInfoItem,代码如 下: void CIndustryDBSDlg::InsertEnterpriseInfoItem(int id,CString date,CString chname,CString enname,CString chaddress,CString enaddress,CString code,CString type,CString phone,CString registerno,CString creditrank,CString jurperson,CString memo) { //获取当前的记录条数. — 250 — int nIndex = m_listEnterprise.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. m_listEnterprise.InsertItem(&lvItem); //设置该行的其他列的值. m_listEnterprise.SetItemText(nIndex,1,date); m_listEnterprise.SetItemText(nIndex,2,chname); m_listEnterprise.SetItemText(nIndex,3,enname); m_listEnterprise.SetItemText(nIndex,4,chaddress); m_listEnterprise.SetItemText(nIndex,5,enaddress); m_listEnterprise.SetItemText(nIndex,6,code); m_listEnterprise.SetItemText(nIndex,7,type); m_listEnterprise.SetItemText(nIndex,8,phone); m_listEnterprise.SetItemText(nIndex,9,registerno); m_listEnterprise.SetItemText(nIndex,10,creditrank); m_listEnterprise.SetItemText(nIndex,11,jurperson); m_listEnterprise.SetItemText(nIndex,12,memo); } 向质量信誉档案列表框控件中插入数据的函数为 InsertQualityCreditInfoItem,代码如 下: void CIndustryDBSDlg::InsertQualityCreditInfoItem(int id,CString date,CString descrip) { //获取当前的记录条数. int nIndex = m_listInfo.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. m_listInfo.InsertItem(&lvItem); //设置该行的其他列的值. m_listInfo.SetItemText(nIndex,1,date); m_listInfo.SetItemText(nIndex,2,descrip); } 根 据 传 入 的 SQL 语句把数据库中的企业信息显示到列表框控件中的函数为 RefreshCtrlData,代码如下: void CIndustryDBSDlg::RefreshCtrlData(CString sql) — 251 — { m_listEnterprise.DeleteAllItems(); try { m_currentEnterpriseID = 1; m_pRecordset->Open(_bstr_t(sql),(IDispatch*)m_pConnection,adOpenDynamic ,adLockOptimistic,adCmdText); //遍历所有记录 while(!m_pRecordset->adoEOF) { int id; CString date,chname,enname,chaddress,enaddress; CString code,type,phone,registerno,creditrank; CString jurperson,memo; _variant_t TheValue; //获取 ID 字段值. TheValue = m_pRecordset->Fields->GetItem("enterprise_id")->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; m_currentEnterpriseID = id; //获取申请时间字段值. TheValue = m_pRecordset->Fields->GetItem("apply_time")->Value; if(TheValue.vt!=VT_NULL) { DATE dt; dt = TheValue.date; COleDateTime oleDT(dt); date = oleDT.Format("%Y-%m-%d %H:%M:%S"); date = date.Left(10); } //获取企业中文名称字段值. TheValue = m_pRecordset->Fields->GetItem("ch_name")->GetValue(); if(TheValue.vt!=VT_NULL) chname= (char*)_bstr_t(TheValue); //获取企业英文名称字段值。 TheValue = m_pRecordset->Fields->GetItem("en_name")->GetValue(); if(TheValue.vt!=VT_NULL) enname= (char*)_bstr_t(TheValue); //获取企业中文地址字段值. TheValue = m_pRecordset->Fields->GetItem("ch_address")->GetValue(); if(TheValue.vt!=VT_NULL) chaddress= (char*)_bstr_t(TheValue); //获取企业英文地址字段值. TheValue = m_pRecordset->GetCollect("en_address"); if(TheValue.vt!=VT_NULL) enaddress= (char*)_bstr_t(TheValue); //获取邮政编码字段值. TheValue = m_pRecordset->GetCollect("code"); — 252 — if(TheValue.vt!=VT_NULL) code= (char*)_bstr_t(TheValue); //获取企业类型字段值. TheValue = m_pRecordset->GetCollect("type"); if(TheValue.vt!=VT_NULL) type= (char*)_bstr_t(TheValue); //获取联系电话字段值. TheValue = m_pRecordset->GetCollect("telephone"); if(TheValue.vt!=VT_NULL) phone = (char*)_bstr_t(TheValue); //获取工商登记号字段值. TheValue = m_pRecordset->GetCollect("register_no"); if(TheValue.vt!=VT_NULL) registerno = (char*)_bstr_t(TheValue); //获取企业信誉等级字段值 TheValue = m_pRecordset->GetCollect("credit_rank"); if(TheValue.vt!=VT_NULL) creditrank = (char*)_bstr_t(TheValue); //获取法定代表人字段值. TheValue = m_pRecordset->GetCollect("jur_person"); if(TheValue.vt!=VT_NULL) jurperson = (char*)_bstr_t(TheValue); //获取经营范围字段值. TheValue = m_pRecordset->GetCollect("memo"); if(TheValue.vt!=VT_NULL) memo= (char*)_bstr_t(TheValue); //转到下一条记录 m_pRecordset->MoveNext(); //显示到界面上 InsertEnterpriseInfoItem(id,date,chname,enname,chaddress,enaddress,code , type,phone,registerno,creditrank,jurperson,memo); } m_pRecordset->Close(); RefreshQualityCreditInfoData(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } RefreshCtrlData 函数利用记录集对象的 GetItem 方法和 GetCollect 方法获取数据库表的 字段值。函数在末尾处调用 RefreshQualityCreditInfoData 函数刷新质量信息档案列表框中 的数据,代码如下: void CIndustryDBSDlg::RefreshQualityCreditInfoData() { m_listInfo.DeleteAllItems(); — 253 — try { CString sql; sql.Format("select * from quality_credit_info_tab where enterprise_id = %d",m_currentEnterpriseID); m_pRecordset->Open(_bstr_t(sql),(IDispatch*)m_pConnection, adOpenDynamic,adLockOptimistic,adCmdText); //遍历所有记录 while(!m_pRecordset->adoEOF) { int id; CString date,descrip; //获取档案 ID 字段值. _variant_t TheValue; TheValue = m_pRecordset->Fields->GetItem("info_id")->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; //获取日期字段值. TheValue = m_pRecordset->Fields->GetItem("info_date")->Value; if(TheValue.vt!=VT_NULL) { DATE dt; dt = TheValue.date; COleDateTime oleDT(dt); date = oleDT.Format("%Y-%m-%d %H:%M:%S"); date = date.Left(10); } //获取档案说明字段值 TheValue = m_pRecordset->GetCollect("memo"); if(TheValue.vt!=VT_NULL) descrip= (char*)_bstr_t(TheValue); //转到下一条记录 m_pRecordset->MoveNext(); InsertQualityCreditInfoItem(id,date,descrip); } m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 系统在启动的时候,会自动连接数据库,数据库连接成功后,就在列表框控件中显示 数据库中的信息,如图 7-13 所示。 — 254 — 图 7-13 连接接数据库之后的对话框 7.3.6 企业基本信息管理 企业基本信息管理包括对企业基本信息的添加、修改和删除的操作。 企业的类型有很多,主要包括国有企业、集体企业、股份合作企业、国有联营企业、 集体联营企业、国有与集体联营企业、其他联营企业、有限责任公司、国有独资公司、私 营企业、私营独资企业、私营合伙企业、私营有限责任公司、私营股份有限公司、合资经 营企业(港或澳、台资)、合作经营企业(港或澳、台资)、港、澳、台商独资经营企业、港、 澳、台商投资、中外合资经营企业、中外合作经营企业、外资企业、外商投资股份有限公 司、个体户和个人合伙。这些信息放在企业类型对话框中显示,在列表框 IDC_COMBO_TYPE 属性中的 Data 选项卡的 Enter Listbox items 文本框中添加这些数据, 如图 7-14 所示。 图 7-14 企业类型列表框 Data 属性对话框 — 255 — 企业信誉等级共分为 AAA、AA、A、BBB、BB、B、CCC、CC、C 九级,在列表框 IDC_COMBO_CREDITRANK 属性中的 Data 选项卡的 Enter listbox items 文本框中添加这 些数据,如图 7-15 所示。 图 7-15 企业等级列表框 Data 属性对话框 为了按照 Data 选项卡的 Enter Listbox items 文本框中的顺序显示数据,需要去掉这两 个列表框属性中的 Styles 选项卡下的 Sort 选项。 1. 添加企业基本信息 添加企业基本信息的函数为 OnEnterpriseAdd,代码如下: void CIndustryDBSDlg::OnEnterpriseAdd() { if(!UpdateData()) return; if(m_strCHName.IsEmpty()){ AfxMessageBox("企业中文名称不能够为空"); return; } //构造申请时间 CString strDate = m_oleApplyDate.Format("%Y-%m-%d") ; try { m_pRecordset->Open("Select seq_enterprise_id.NEXTVAL from dual" , (IDispatch*)m_pConnection , adOpenDynamic , adLockOptimistic , adCmdText); int id = 1; if(!m_pRecordset->adoEOF) { _variant_t TheValue; //从系列中获取新的企业 ID 值。 TheValue = m_pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; } m_pRecordset->Close(); m_pRecordset->Open("Select * from enterprise_info_tab" , (IDispatch*)m_pConnection , adOpenDynamic , adLockOptimistic , adCmdText); //添加新记录 m_pRecordset->AddNew(); — 256 — m_pRecordset->Fields->GetItem("enterprise_id")->Value = (short)id; m_pRecordset->Fields->GetItem("apply_time")->Value = _bstr_t(strDate); m_pRecordset->Fields->GetItem("ch_name")->Value = _bstr_t(m_strCHName); m_pRecordset->Fields->GetItem("en_name")->Value = _bstr_t(m_strENName); m_pRecordset->Fields->GetItem(_variant_t("ch_address"))->PutValue(_bstr _t(m_strCHAddress)); m_pRecordset->Fields->GetItem(_variant_t("en_address"))->PutValue(_bstr _t(m_strENAddress)); m_pRecordset->Fields->GetItem("code")->Value = _bstr_t(m_strCode); m_pRecordset->Fields->GetItem("type")->Value = _bstr_t(m_strEnterpriseType); m_pRecordset->Fields->GetItem("telephone")->Value = _bstr_t(m_strPhone); m_pRecordset->Fields->GetItem("register_no")->Value = _bstr_t(m_strRegisterNo); m_pRecordset->Fields->GetItem("credit_rank")->Value = _bstr_t(m_strCreditRank); m_pRecordset->Fields->GetItem("jur_person")->Value = _bstr_t(m_strJurPerson); m_pRecordset->Fields->GetItem("memo")->Value = _bstr_t(m_strMemo); //更新数据库 m_pRecordset->Update(); m_pRecordset->Close(); //向界面中插入新的企业息 InsertEnterpriseInfoItem(id , strDate , m_strCHName , m_strENName , m_strCHAddress, m_strENAddress , m_strCode , m_strEnterpriseType , m_strPhone , m_strRegisterNo, m_strCreditRank,m_strJurPerson,m_strMemo); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } OnEnterpriseAdd 函数使用 RecordSet 记录集对象添加企业的基本信息。首先使用记录 集的 AddNew 方法告知数据库要添加新的数据,然后利用 GetItem 方法获取要操作记录的 字段,并设置字段对象的 Value 值,或者利用 PutValue 方法设置字段值,最后使用记录集 的 Update 方法将数据更新到数据库中。 如在企业基本信息管理中的“申请日期”日期控件中选择 2004-7-9,在“企业中文名 称”文本框中输入“北京微视科技”,在“企业英文名称”文本框中输入 beijing microview tech.,在“企业中文地址”文本框中输入“上海华兴科技园”,在“企业英文地址”文本框 中输入 beijing huaxin science park,在“邮政编码”文本框中输入 100012,在“企业类型” 列表框中选择“私营企业”,在“联系电话”文本框中输入 01056781234,在“工商登记号” — 257 — 文本框中输入 110000120222,在“企业信誉等级”列表框中选择“AA 级”,在“法定代表 人”文本框中输入“任华”,在“经营范围”文本框中输入“软件外包、企业 ERP 解决方 案”,如图 7-16 所示。 图 7-16 添加企业基本信息对话框 单击“添加”按钮,企业基本信息就添加到列表框控件中,如图 7-17 所示。 图 7-17 添加企业基本信息之后对话框 — 258 — 2. 修改企业基本信息 为了用户修改企业基本信息的方便,可以添加企业基本信息列表框控件 IDC_LIST_ENTERPRISE 的 NM_CLICK 消息映射函数 OnClickListEnterprise,代码如下: void CIndustryDBSDlg::OnClickListEnterprise(NMHDR* pNMHDR , LRESULT* pResult) { int nItem = m_listEnterprise.GetNextItem(-1, LVNI_SELECTED); if(nItem != -1){ //从类别控件中获取信息 m_currentEnterpriseID = atoi(m_listEnterprise.GetItemText(nItem,0)); CString date = m_listEnterprise.GetItemText(nItem,1); m_oleApplyDate.ParseDateTime(date); m_strCHName = m_listEnterprise.GetItemText(nItem,2); m_strENName = m_listEnterprise.GetItemText(nItem,3); m_strCHAddress = m_listEnterprise.GetItemText(nItem,4); m_strENAddress = m_listEnterprise.GetItemText(nItem,5); m_strCode = m_listEnterprise.GetItemText(nItem,6); m_strEnterpriseType = m_listEnterprise.GetItemText(nItem,7); m_strPhone = m_listEnterprise.GetItemText(nItem,8); m_strRegisterNo = m_listEnterprise.GetItemText(nItem,9); m_strCreditRank = m_listEnterprise.GetItemText(nItem,10); m_strJurPerson = m_listEnterprise.GetItemText(nItem,11); m_strMemo = m_listEnterprise.GetItemText(nItem,12); //更新到企业信息参数的控件中. UpdateData(FALSE); //根据当前的企业信息,获取质量信誉档案 RefreshQualityCreditInfoData(); } *pResult = 0; } OnClickListEnterprise 函数从选定的行中获取企业基本信息,并显示到企业的参数控件 中,然后调用 RefreshQualityCreditInfoData 函数刷新当前企业的质量信誉档案信息。 修改企业基本信息的函数为 OnEnterpriseMod,代码如下: void CIndustryDBSDlg::OnEnterpriseMod() { if(!UpdateData()) return; int nIndex = m_listEnterprise.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要修改的记录,返回. if(nIndex == -1){ AfxMessageBox("没有选择要修改的企业信息"); return; } //从列表框控件中获取选择的企业信息 ID. int id = atoi(m_listEnterprise.GetItemText(nIndex,0)); if(m_strCHName.IsEmpty()){ — 259 — AfxMessageBox("企业中文名称不能够为空"); return; } //构造企业申请时间 CString strDate = m_oleApplyDate.Format("%Y-%m-%d") ; try { CString sql; sql.Format("Select * from enterprise_info_tab where enterprise_id = %d", id); m_pRecordset->Open(_bstr_t(sql),(IDispatch*)m_pConnection, adOpenDynamic,adLockOptimistic,adCmdText); //打开要修改的记录 if(!m_pRecordset->adoEOF) { m_pRecordset->Fields->GetItem("enterprise_id")->Value = (short)id; m_pRecordset->Fields->GetItem("apply_time")->Value = _bstr_t(strDate); m_pRecordset->Fields->GetItem("ch_name")->Value = _bstr_t(m_strCHName); m_pRecordset->Fields->GetItem("en_name")->Value = _bstr_t(m_strENName); m_pRecordset->Fields->GetItem(_variant_t("ch_address"))->PutValue(_bstr _t(m_strCHAddress)); m_pRecordset->Fields->GetItem(_variant_t("en_address"))->PutValue(_bstr _t(m_strENAddress)); m_pRecordset->Fields->GetItem("code")->Value = _bstr_t(m_strCode); m_pRecordset->Fields->GetItem("type")->Value = _bstr_t(m_strEnterpriseType); m_pRecordset->Fields->GetItem("telephone")->Value = _bstr_t(m_strPhone); m_pRecordset->Fields->GetItem("register_no")->Value = _bstr_t(m_strRegisterNo); m_pRecordset->Fields->GetItem("credit_rank")->Value = _bstr_t(m_strCreditRank); m_pRecordset->Fields->GetItem("jur_person")->Value = _bstr_t(m_strJurPerson); m_pRecordset->Fields->GetItem("memo")->Value = _bstr_t(m_strMemo); //更新数据库 m_pRecordset->Update(); } m_pRecordset->Close(); //修改界面上的的企业信息 m_listEnterprise.SetItemText(nIndex,1,strDate); m_listEnterprise.SetItemText(nIndex,2,m_strCHName); m_listEnterprise.SetItemText(nIndex,3,m_strENName); m_listEnterprise.SetItemText(nIndex,4,m_strCHAddress); — 260 — m_listEnterprise.SetItemText(nIndex,5,m_strENAddress); m_listEnterprise.SetItemText(nIndex,6,m_strCode); m_listEnterprise.SetItemText(nIndex,7,m_strEnterpriseType); m_listEnterprise.SetItemText(nIndex,8,m_strPhone); m_listEnterprise.SetItemText(nIndex,9,m_strRegisterNo); m_listEnterprise.SetItemText(nIndex,10,m_strCreditRank); m_listEnterprise.SetItemText(nIndex,11,m_strJurPerson); m_listEnterprise.SetItemText(nIndex,12,m_strMemo); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } OnEnterpriseMod 利用记录集对象实现记录的修改操作,首先打开记录集,然后修改记 录集中相应的字段值,最后调用 Update 方法,修改后的数据就更新到数据库中。 3. 删除企业基本信息 删除企业基本信息的函数为 OnEnterpriseDel,代码如下: void CIndustryDBSDlg::OnEnterpriseDel() { if(!UpdateData()) return; int nItem = m_listEnterprise.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要删除的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要删除的企业信息"); return; } //从列表框控件中获取选择的企业信息 ID. int id = atoi(m_listEnterprise.GetItemText(nItem,0)); try { CString sql; sql.Format("select * from quality_credit_info_tab where enterprise_id = %d",id); m_pRecordset->Open(_bstr_t(sql),(IDispatch*)m_pConnection, adOpenDynamic,adLockOptimistic,adCmdText); //先删除该企业的质量信誉档案 while(!m_pRecordset->adoEOF) { m_pRecordset->Delete(adAffectCurrent); m_pRecordset->Update(); m_pRecordset->MoveNext(); } m_pRecordset->Close(); sql.Format("Select * from enterprise_info_tab where enterprise_id = %d", id); — 261 — m_pRecordset->Open(_bstr_t(sql),(IDispatch*)m_pConnection, adOpenDynamic,adLockOptimistic,adCmdText); //最后删除该企业的基本信息 if(!m_pRecordset->adoEOF) { m_pRecordset->Delete(adAffectCurrent); m_pRecordset->Update(); } m_pRecordset->Close(); //删除界面上的数据 m_listEnterprise.DeleteItem(nItem); m_listInfo.DeleteAllItems(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 在删除企业基本信息之前,首先要删除该企业的所有质量信息档案信息。 OnEnterpriseDel 函数使用 Recordset 记录集对象的 Delete 方法删除企业基本信息,首先利 用 Open 方法打开要删除的记录,然后遍历记录集,调用记录集对象的 Delete 方法删除当 前的记录,最后调用 Update 方法更新数据库,从而删除数据库中的记录。 4. 企业名称查询 企业名称查询提供了利用企业名称进行模糊查询的方法,如果“企业中文名称”文本 框中的数据为空,列出所有企业信息;如果不为空,则列出所有企业名称包含“企业中文 名称”文本框中的文字的企业信息。函数为 OnEnterpriseQuery,代码如下: void CIndustryDBSDlg::OnEnterpriseQuery() { if(!UpdateData()) return; CString temp = "%"; CString sql; //获取企业名称模糊查询的标准 SQL 语句 sql.Format("select * from enterprise_info_tab where ch_name like ’%s%s%s’", temp,m_strCHName,temp); RefreshCtrlData(sql); } 函数 OnEnterpriseQuery 构造一个模糊查询的 SQL 语句,然后调用 RefreshCtrlData 函 数显示相应的企业信息。 如在企业基本信息管理中的“企业中文名称”文本框输入“微视”,然后单击“企业 名称查询”按钮,就会在企业信息列表框显示“北京微视科技”的企业信息,如图 7-18 所 示。 — 262 — 图 7-18 修改企业基本信息对话框 7.3.7 企业质量信誉档案管理 企业质量信誉档案管理提供了对企业质量和信誉信息的添加、修改和删除的功能。比 如公司将通过的一些认证,或者被评为“优秀纳税大户”,这些都可以作为一个公司质量信 息档案,当然对于某些公司的违规操作而出现的曝光事件,也应该记录在案,从而能够更 好地规范整个行业,保持其健康的发展。 1. 添加企业质量信誉档信息 添加质量信誉档案信息的函数为 OnBtnInfoAdd,代码如下: void CIndustryDBSDlg::OnBtnInfoAdd() { if(!UpdateData()) return; if(m_strDescrip.IsEmpty()){ AfxMessageBox("描述不能够为空"); return; } //构造时间 CString strDate = m_oleInfoDate.Format("%Y-%m-%d") ; try { m_pRecordset->Open("Select seq_info_id.NEXTVAL from dual" , (IDispatch*)m_pConnection , adOpenDynamic , adLockOptimistic , adCmdText); int id = 1; if(!m_pRecordset->adoEOF) { — 263 — _variant_t TheValue; //从系列中获取新的档案 ID 值. TheValue = m_pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; } m_pRecordset->Close(); m_pRecordset->Open("Select * from quality_credit_info_tab" , (IDispatch*)m_pConnection , adOpenDynamic , adLockOptimistic , adCmdText); //添加一条新的档案记录 m_pRecordset->AddNew(); m_pRecordset->Fields->GetItem("info_id")->Value = (short)id; m_pRecordset->Fields->GetItem("enterprise_id")->Value = (short)m_currentEnterpriseID; m_pRecordset->Fields->GetItem("info_date")->Value = _bstr_t(strDate); m_pRecordset->Fields->GetItem("memo")->Value = _bstr_t(m_strDescrip); //更新到数据库 m_pRecordset->Update(); m_pRecordset->Close(); //向界面中插入新的档案信息 InsertQualityCreditInfoItem(id,strDate,m_strDescrip); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 如在质量信誉档案管理中的“日期”时间控件中选择日期 2002-3-1,在“质量信誉档 案信息”文本框中输入“通过软件成熟度模型 CMM 四级”,如图 7-19 所示。 图 7-19 添加质量信息档案信息对话框 单击“添加”按钮,质量信誉档案信息就添加到界面上,如图 7-20 所示。 继续添加几条档案信息之后的对话框如图 7-21 所示。 — 264 — 图 7-20 添加质量信息档案信息之后对话框 图 7-21 添加几条质量信息档案信息之后对话框 2. 删除企业质量信誉档信息 删除质量信誉档案的函数为 OnBtnInfoDel,代码如下: void CIndustryDBSDlg::OnBtnInfoDel() { if(!UpdateData()) return; int nItem = m_listInfo.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要删除的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要删除的档案信息"); return; } //从列表框控件中获取选择的档案信息 ID. int id = atoi(m_listInfo.GetItemText(nItem,0)); try { CString sql; sql.Format("select * from quality_credit_info_tab where info_id = %d", id); m_pRecordset->Open(_bstr_t(sql),(IDispatch*)m_pConnection, adOpenDynamic,adLockOptimistic,adCmdText); //删除选择的档案信息 if(!m_pRecordset->adoEOF) { m_pRecordset->Delete(adAffectCurrent); m_pRecordset->Update(); — 265 — m_pRecordset->MoveNext(); } m_pRecordset->Close(); m_listInfo.DeleteItem(nItem); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 如图 7-22 所示,选择质量信誉档案列表框中的“通过 ISO9002 质量标准”的这一行。 图 7-22 选择质量信息档案信息对话框 单击“删除”按钮,将在质量信誉档案列表框中删除该信息,如图 7-23 所示。 图 7-23 删除质量信息档案信息对话框 7.4 本章小结 本章详细介绍了行业监管系统的开发过程。在系统的设计中添加了系统登录的管理, 提供了系统密码设置和数据库连接参数设置的功能,有利于提高系统的安全性和操作的灵 活性。对于系统首次登录,提供用户友好的界面配置系统的运行参数,包括系统的登录密 码和数据库的连接参数。系统登录密码保证了系统的安全性,而数据库连接参数是操作数 据库之前必须有的参数。在读取系统密码和数据库连接参数的时候,采用微软的注册表, 即在注册表中保存行业监管系统的配置参数,这也是通常一些系统保存系统运行参数的地 方,读者将从本系统的介绍中学会操作注册表的方法。而对于系统再次登录,需要进行系 统的密码验证,从而保证系统的安全性。另外,系统在运行的情况下可以重新配置数据库 — 266 — 的连接参数,并重新连接数据库,这样可以避免系统重新启动。 行业监管系统采用了 ADO 数据库开发技术。在系统中用到了连接对象和记录集对象 来操作数据库,并且主要介绍了利用记录集对象实现数据的添加、修改和删除,读者可以 更好地掌握利用 ADO 的记录集对象操作数据库的方法。 第8章 公司人才储备管理系统 人才是公司最为重要的资源,每一个公司都希望获得更多的优秀人才。不过,公司管 理人员更应该了解公司的人才储备情况,才能利用好这些人力资源,而不至于浪费。例如 项目管理者在开发新项目的时候,可以从公司的人才储备库中找到需要的人才,从而组建 一个开发团队。而不至于每次需要人才的时候,都要临时招聘,会浪费很多财力和物力, 常常需要的人才可能就在公司的内部。同时人力资源管理部门可以根据每个人才不同的特 点,提供合适的培训。总之,这都需要对公司的人才储备情况有一个很好的管理。人才储 备管理系统可以满足这个需求,它提供了对公司的人才信息的有效管理。 8.1 系统设计 系统设计是系统开发最为关键的一环,系统设计好了,系统的实现以及以后的系统测 试都会节省很多的物力和财力。通过系统的设计,开发人员能够更好地把握系统的需求, 了解系统的各功能模块。 8.1.1 功能描述 人才储备管理系统包括人才信息管理、教育经历管理、职位变更管理,以及工作成果 管理。详细的功能描述如下。 1. 人才信息管理 人才信息管理包括对人才信息的添加、修改、删除和查询的功能。人才信息包括人才 的姓名、性别、出生年月、最高学位、来公司日期、转正日期、隶属部门、个人特长和专 业技能。 2. 教育经历管理 教育经历管理包括对教育经历信息的添加、修改和删除的功能。教育经历信息主要包 括院校、入学日期、毕业日期、专业和学位等信息。 3. 职位变更管理 职位信息包括职位变动、职称授予、部门变动等信息。职位变更管理包括对职位信息 的添加、修改和删除的功能。 4. 工作成果管理 工作成果是公司员工在公司所取的成果,如参加的各种项目。工作成果的管理包括对 工作成果信息的添加、修改和删除的功能。 8.1.2 功能模块设计 从上面的功能描述中,可以把人才储备管理系统分为 4 个模块:人才信息管理、教育 — 268 — 经历管理、职位变更管理及工作成果管理。在每一个模块下又提供了更为具体的功能。详 细的人才储备管理系统的功能模块图,如图 8-1 所示。 图 8-1 系统功能模块图 8.2 数据库设计与实现 数据库设计是系统开发中非常重要的一个环节。数据库结构设计的好坏将直接影响到 系统的效率和功能的实现。在设计数据库之前,要了解数据库的需求,从而确定数据库的 结构。 8.2.1 数据库需求设计 通过以上的功能分析,公司人才储备管理系统需要包含以下数据库信息。 1. 人才信息 包括人才 ID、姓名、性别、教育程度、出生日期、来公司日期、转正日期、隶属部门、 个人特长和专业技能。 2. 教育经历信息 包括教育经历 ID、人才 ID、院校、入学日期、毕业日期、专业和学位等信息。 3. 职位变更信息 包括职位变更 ID、人才 ID、变更日期和变更说明。 4. 工作成果信息 包括工作成果 ID、人才 ID、日期、成果描述和评价。 — 269 — 8.2.2 数据库表的设计 通过数据库的需求,可以创建以下数据表。 1. 人才信息表(employee_info_tab) 人才信息表包括公司人才的基本信息。人才信息管理实际上是对人才信息表的管理, 表的结构见表 8-1。 表 8-1 人才信息表 字段名称 数据类型 可否为空 约束条件 说 明 employee_id INTEGER NOT NULL 主键 人才 ID 值 name VARCHAR2(24) NOT NULL 无 姓名 Sex VARCHAR2(10) NULL 无 性别 birth VARCHAR2(20) NULL 无 出生年月 degree VARCHAR2(24) NULL 无 最高学位 In_date VARCHAR2(20) NULL 无 来公司日期 regular_date VARCHAR2(20) NULL 无 毕业日期 dept VARCHAR2(24) NULL 无 隶属部门 interest VARCHAR2(100) NULL 无 个人特长 speciality VARCHAR2(200) NULL 无 专业技能 2. 教育经历信息表(education_info_tab) 教育经历信息表包括公司人才的教育经历信息。教育经历管理实际上是对教育经历信 息表的管理,表的结构见表 8-2。 表 8-2 教育经历信息表 字段名称 数据类型 可否为空 约束条件 说 明 education_id INTEGER NOT NULL 主键 教育经历 ID 值 employee_id INTEGER NOT NULL 外键,来自 employee_info_tab 的 employee _id 人才 ID 值 school VARCHAR2(60) NULL 无 院校 enroll_date VARCHAR2(20) NULL 无 入学日期 graduate_date VARCHAR2(20) NULL 无 毕业日期 major VARCHAR2(30) NULL 无 专业 degree VARCHAR2(10) NULL 无 学位 3. 职位变更信息表(position_change_info_tab) 职位变更信息表包括公司人才的职位变更的信息。职位变更管理实际上是对职位变更 信息表的管理,表的结构见表 8-3。 表 8-3 职位变更信息表 字段名称 数据类型 可否为空 约束条件 说 明 position_id INTEGER NOT NULL 主键 职位变更 ID 值 employee_id INTEGER NOT NULL 外键,来自 employee_info_tab 的 employee _id 人才 ID 值 change_date VARCHAR2(20) NULL 无 变更日期 description VARCHAR2(200) NULL 无 变更说明 — 270 — 4. 工作成果信息表(achievement_info_tab) 工作成果信息表包括公司员工的公司成果信息。工作成果管理实际上是对工作成果信 息表的管理,表的结构见表 8-4。 表 8-4 工作成果信息表 字段名称 数据类型 可否为空 约束条件 说 明 achievement_id INTEGER NOT NULL 主键 成果 ID 值 employee_id INTEGER NOT NULL 外键,来自 employee_info_tab 的 employee _id 人才 ID 值 ach_date VARCHAR2(20) NULL 无 日期 achievement VARCHAR2(200) NULL 无 成果描述 comment VARCHAR2(200) NULL 无 评价 8.2.3 数据库表的创建 利用第 3 章中讲述的方法创建表空间 dbemployee 和数据库用户 dbemployee,其中数 据库用户的密码为 dbemployee,选择的默认表空间为 dbemployee。 创建公司人才储备管理系统所有数据表的 SQL 语句如下: --人才信息表 CREATE TABLE employee_info_tab( employee_id INTEGER PRIMARY KEY, name VARCHAR2(24) NOT NULL, sex VARCHAR2(10) NULL CHECK (sex IN (’男’, ’女’)), birth VARCHAR2(20) NULL, degree VARCHAR2(24) NULL, in_date VARCHAR2(20) NULL, regular_date VARCHAR2(20) NULL, dept VARCHAR2(24) NULL, interest VARCHAR2(100) NULL, speciality VARCHAR2(200) NULL ); --添加最后学位 degree 的索引 CREATE INDEX employee_info_degreeindex ON employee_info_tab(degree); --教育经历信息表 CREATE TABLE education_info_tab( education_id INTEGER PRIMARY KEY, employee_id INTEGER NOT NULL, school VARCHAR2(60) NULL, enroll_date VARCHAR2(20) NULL, graduate_date VARCHAR2(20) NULL, major VARCHAR2(30) NULL, degree VARCHAR2(10) NULL ); --添加人才 ID 外键 ALTER TABLE education_info_tab ADD ( FOREIGN KEY (employee_id) — 271 — REFERENCES employee_info_tab ) ; --职位变更信息表 CREATE TABLE position_change_info_tab( position_id INTEGER PRIMARY KEY, employee_id INTEGER NOT NULL, change_date VARCHAR2(20) NULL, description VARCHAR2(200) NULL ); --添加人才 ID 外键 ALTER TABLE position_change_info_tab ADD ( FOREIGN KEY (employee_id) REFERENCES employee_info_tab ) ; --工作成果信息表 CREATE TABLE achievement_info_tab( achievement_id INTEGER PRIMARY KEY, employee_id INTEGER NOT NULL, ach_date VARCHAR2(20) NULL, achievement VARCHAR2(200) NULL, description VARCHAR2(200) NULL ); --添加人才 ID 外键 ALTER TABLE achievement_info_tab ADD ( FOREIGN KEY (employee_id) REFERENCES employee_info_tab ) ; --创建可以递增的系列号供人才信息表的 employee_id 使用 CREATE SEQUENCE seq_employee_id INCREMENT BY 1 START WITH 1000 NOMAXVALUE NOMINVALUE NOCYCLE; --创建可以递增的系列号供教育经历信息表的 education_id 使用 CREATE SEQUENCE seq_education_id INCREMENT BY 1 START WITH 1 NOMAXVALUE NOMINVALUE NOCYCLE; --创建可以递增的系列号供职位变更信息表的 position_id 使用 CREATE SEQUENCE seq_position_id INCREMENT BY 1 START WITH 1 NOMAXVALUE NOMINVALUE NOCYCLE; --创建可以递增的系列号供工作成果信息表的 achievement_id 使用 CREATE SEQUENCE seq_achievement_id INCREMENT BY 1 START WITH 1 NOMAXVALUE NOMINVALUE NOCYCLE; 利用 Oracle SQLPlus WorkSheet 工具执行上述的 SQL 语句从而创建数据库表。需要说 明的是,在打开 Oracle SQLPlus Worksheet 的“Oracle Enterprise Manager 登录”对话框的 时候,需要在“用户名”文本框中输入企业设备管理系统的用户名 dbemployee,在“口令” 文本框中输入用户密码 dbemployee,在“服务”文本框中输入数据库的本地服务名 ORADB, 选择连接方式 Normal,登录成功之后,再运行上述的 SQL 语句。 8.3 系统的实现 完成了系统功能模块的设计和数据库表的创建,就可以创建一个公司人才储备管理系 统了。 — 272 — 8.3.1 创建应用程序 运行 Visual C++,选择 File|New 命令,弹出 New 对话框。从 Projects 列表中选择 MFC AppWizard(exe)向导,在 Location 文本框中选择项目工程的目录“D:\VCSAMPLE\”,在 Project name 文本框中输入工程文件的名称 EmployeeDBS,单击 OK 按钮,进入 MFC AppWizard – Step 1 页面,从应用程序的类型中选择 Dialog based 选项,从语言列表中选择 “中文(中国)(APPWZCHS.DLL)”,单击 Next 按钮,进入 MFC AppWizard – Step 2 of 4 页 面,选择 Automation 选项,EmployeeDBS 对话框应用程序创建完毕。 8.3.2 创建主对话框的界面 主对话框的布局如图 8-2 所示。其中包括人才信息管理、教育经历信息管理、职位变 更信息管理和工作成果信息管理 4 个部分。 图 8-2 “人才储备管理系统”对话框 1. 人才信息管理 控件类型、ID 及说明见表 8-5。 — 273 — 表 8-5 人才信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 人才信息管理 无 Label IDC_STATIC 姓名 无 Edit Box IDC_EDIT_NAME 无 CString 类型变量 m_strName Label IDC_STATIC 性别 无 Combo Box IDC_COMBO_SEX 无 CString 类型变量 m_strSex Label IDC_STATIC 教育程度 无 Combo Box IDC_COMBO_DEGREE 无 CString 类型变量 m_strDegree Label IDC_STATIC 出生日期 无 Edit Box IDC_EDIT_BIRTH_YEAR 无 整型变量 m_nBirthYear Label IDC_STATIC 年 无 Combo Box IDC_COMBO_BIRTH_MONTH 无 CString 类型变量 m_strBirthMonth Label IDC_STATIC 月 无 Combo Box IDC_COMBO_BIRTH_DAY 无 CString 类型变量 m_strBirthDay Label IDC_STATIC 日 无 Label IDC_STATIC 进公司日期 无 Edit Box I IDC_EDIT_IN_YEAR 无 整型变量 m_nInYear Label IDC_STATIC 年 无 Combo Box IDC_COMBO_IN_MONTH 无 CString 类型变量 m_strInMonth Label IDC_STATIC 月 无 Combo Box IDC_COMBO_IN_DAY 无 CString 类型变量 m_strInDay Label IDC_STATIC 日 无 Label IDC_STATIC 转正日期 无 Edit Box IDC_EDIT_REGULAR_YEAR 无 整型变量 m_nRegularYear Label IDC_STATIC 年 无 Combo Box IDC_COMBO_REGULAR_MONTH 无 CString 类型变量 m_strRegularMonth Label IDC_STATIC 月 无 Combo Box IDC_COMBO_REGULAR_DAY 无 CString 类型变量 m_strRegularDay Label IDC_STATIC 日 无 Label IDC_STATIC 隶属部门 无 Edit Box IDC_EDIT_DEPT 无 COleDateTime 类型变量 m_strDept Label IDC_STATIC 个人特长 无 Edit Box IDC_EDIT_INTEREST 无 CString 类型变量 m_strInterest Label IDC_STATIC 专业技能 无 Edit Box IDC_EDIT_SPECIALITY 无 CString 类型变量 m_strSpeciality Button IDC_BTN_EMPLOYEE_ADD 添加 函数 OnBtnEmployeeAdd ()添加人才信息 Button IDC_BTN_EMPLOYEE_MOD 修改 函数 OnBtnEmployeeMod ()修改人才信息 Button IDC_BTN_EMPLOYEE_DEL 删除 函数 OnBtnEmployeeDel ()删除人才信息 Button IDC_BTN_EMPLOYEE_QUERY 查询 函数 OnBtnEmployeeQuery ()人才信息查询 Button IDC_SYS_EXIT 查询 函数 OnSysExit ()处理系统退出 Group Box IDC_STATIC 人才信息 无 List Control IDC_LIST_EMPLOYEE 无 列表框控件类型变量 m_listEmployee 2. 教育经历信息管理 控件类型、ID 及说明见表 8-6。 表 8-6 教育经历信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 教育经历信息管理 无 List Control IDC_LIST_CONTRACT 无 列表框控件类型变量 m_listEducation Button IDC_BTN_CONTRACT_ADD 添加 函数 OnBtnEducationAdd ()添加教育经历信息 Button IDC_BTN_CONTRACT_MOD 修改 函数 OnBtnEducationMod ()修改教育经历信息 Button IDC_BTN_CONTRACT_DEL 删除 函数 OnBtnEducationDel ()删除教育经历信息 Group Box IDC_STATIC 合同信息 无 — 274 — 3. 职位变更信息管理 控件类型、ID 及说明见表 8-7。 表 8-7 职位变更信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 职位变更信息管理 无 List Control IDC_LIST_POSITION 无 列表框控件类型变量 m_listPosition Button IDC_BTN_POSITION_ADD 添加 函数 OnBtnPositionAdd ()添加职位变更信息 Button IDC_BTN_POSITION_MOD 修改 函数 OnBtnPositionMod ()修改职位变更信息 Button IDC_BTN_POSITION_DEL 删除 函数 OnBtnPositionDel ()删除职位变更信息 4. 工作成果信息管理 控件类型、ID 及说明见表 8-8。 表 8-8 控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 工作成果信息管理 无 List Control IDC_LIST_EDUCATION 无 列表框控件类型变量 m_listAchievement Button IDC_BTN_ACHIEVEMENT_ADD 添加 函数 OnBtnAchievementAdd ()添加工作成果信息 Button IDC_BTN_ACHIEVEMENT_MOD 修改 函数 OnBtnAchievementMod ()修改工作成果信息 Button IDC_BTN_ACHIEVEMENT_DEL 删除 函数 OnBtnAchievementDel ()删除工作成果信息 主对话框类名称为 CEmployeeDBSDlg,资源 ID 为 IDD_EMPLOYEEDBS_DIALOG, 对话框名称为“人才储备管理系统”。主界面用到了 4 个列表框控件分别显示客户信息和合 同信息。需要为这 4 个列表框控件添加显示的列,从而显示相应的数据信息。为了代码设 计的清晰,在 CEmployeeDBSDlg 类中定义了一个 InitControl 私有函数负责添加控件的显 示列,InitControl 函数比较简单,可以参见前面的章节,代码不再列出。其中人才信息列 表框控件显示人才 ID、姓名、性别、教育程度、出生日期、来公司日期、转正日期、隶属 部门、个人特长和专业技能信息;教育经历信息列表框控件显示教育经历 ID、院校、入学 日期、毕业日期、专业和学位信息;职位变更信息列表框控件显示职位变更 ID、日期和说 明信息;工作成果列表框控件显示工作成果 ID、日期、工作成果和描述信息。在 OnInitDialog 函数末尾处添加 InitControl 函数的调用,这样系统在启动的时候,就可以看到已添加显示 列的列表框控件。 8.3.3 显示数据到界面上 当系统启动之后,可以将数据库中的数据显示到界面上,需要处理数据库的连接和显 示数据到界面上的两个过程。 1. 数据库的连接 ADO 数据库的连接需要 3 个重要的参数:数据库数据源的名称、数据库用户名称和数 据库用户密码。其中数据库数据源的名称就是本地服务名 ORADB,数据库用户的名称和 密码均为 dbemployee。考虑到读者配置的本地服务名和用户名可能不一样,可以从配置文 — 275 — 件中获取这些参数信息,配置文件的格式如下: [General] 数据库数据源=oradb 数据库用户= dbemployee 数据库密码= dbemployee 把 这 段 文 字 保 存 为 EmployeeDBS.ini 文 件 , 并 把 EmployeeDBS.ini 文 件 放 在 EmployeeDBS.exe 运行程序的同一目录下。 在 CEmployeeDBSDlg 类中定义了一个私有类型的 ConnectDB 函数,函数和第 6 章的 数据库连接函数 ConnectDB 相似,只需把文件名称换为 EmployeeDBS.ini,代码不再列出。 2. 显示数据到界面上 连接数据库之后,需要把数据库中的数据显示到 4 个 列 表 框 控 件 中 。 在 CEmployeeDBSDlg 类中定义一个 RefreshCtrlData 私有函数,负责从数据库中读取数据并显 示到列表框控件中。另外还需要定义 3 个刷新函数,分别为:RefreshEducationCtrlData、 RefreshPositionCtrlData 和 RefreshAchievementCtrlData,根据当前的人才信息 ID,显示该 人才的教育经历信息、职位变更信息和工作成果信息。同时还需要定义向 4 个列表框控件 插入数据的函数。在文件 CEmployeeDBSDlg.h 中添加这些函数的定义,代码如下: //根据 SQL 语句获取数据,并刷行列表框控件中的数据. void RefreshCtrlData(CString sql); //根据当前的人才信息 ID 获取相关的教育经历信息,并显示教育经历信息列表框控件上 void RefreshEducationCtrlData(); //根据当前的人才信息 ID 获取相关的职位变更信息,并显示在职位变更信息列表框控件上 void RefreshPositionCtrlData(); //根据当前的人才信息 ID 获取相关的工作成果信息,并显示在工作成果列表框控件上 void RefreshAchievementCtrlData(); //向人才信息列表框控件中添加人才信息 void InsertEmployeeInfoItem(int id,CString name,CString sex,CString degree,CString birth,CString indate,CString regulardate,CString dept,CString interest,CString speciality); //向教育经历信息列表框控件中添加教育经历信息 void InsertEducationInfoItem(int id,CString school,CString enrolldate,CString graduatedate,CString major,CString degree); //向职位变更信息列表框控件中添加职位信息 void InsertPostionInfoItem(int id,CString date,CString description); //向工作成果信息列表框控件中添加工作成果信息 void InsertAchievementInfoItem(int id,CString date,CString achievement,CString description); 本章将采用 ADO 数据绑定技术来开发人才储备管理系统。首先需要建立数据绑定类, 建立数据库字段和 C++成员变量之间的绑定。人才储备管理系统共有 4 个数据表,需要建 立 4 个数据绑定类,从而可以利用这些绑定类访问数据库中的数据。可以把这 4 个数据绑 定类放在一个文件中,名称为 ADOExtData.h。在 ADOExtData.h 文件的开始处添加如下的 代码: #include "stdafx.h" — 276 — _COM_SMARTPTR_TYPEDEF(IADORecordBinding, __uuidof(IADORecordBinding)); inline void TESTHR(HRESULT _hr) { if FAILED(_hr) _com_issue_error(_hr); } 人才信息表 employee_info_tab 的数据绑定类 CEmployeeInfoRs,定义如下: //构造人才信息绑定类 class CEmployeeInfoRs : public CADORecordBinding { BEGIN_ADO_BINDING(CEmployeeInfoRs) ADO_FIXED_LENGTH_ENTRY(1, adInteger, m_n_id, m_ul_idStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_ch_name, sizeof(m_ch_name),m_ul_nameStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(3, adVarChar, m_ch_sex, sizeof(m_ch_sex),m_ul_sexStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_ch_birth, sizeof(m_ch_birth),m_ul_birthStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(5, adVarChar, m_ch_degree, sizeof(m_ch_degree),m_ul_degreeStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(6, adVarChar, m_ch_in_date, sizeof(m_ch_in_date),m_ul_in_dateStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(7, adVarChar, m_ch_regular_date, sizeof(m_ch_regular_date),m_ul_regular_dateStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(8, adVarChar, m_ch_dept, sizeof(m_ch_dept),m_ul_deptStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(9, adVarChar, m_ch_interest, sizeof(m_ch_interest),m_ul_interestStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(10, adVarChar, m_ch_speciality, sizeof(m_ch_speciality),m_ul_specialityStatus, true) END_ADO_BINDING() public: int m_n_id; CHAR m_ch_name[26]; CHAR m_ch_sex[12]; CHAR m_ch_birth[22]; CHAR m_ch_degree[26]; CHAR m_ch_in_date[22]; CHAR m_ch_regular_date[22]; CHAR m_ch_dept[26]; CHAR m_ch_interest[102]; CHAR m_ch_speciality[202]; ULONG m_ul_idStatus; ULONG m_ul_nameStatus; ULONG m_ul_sexStatus; ULONG m_ul_birthStatus; ULONG m_ul_degreeStatus; ULONG m_ul_in_dateStatus; ULONG m_ul_regular_dateStatus; ULONG m_ul_deptStatus; ULONG m_ul_interestStatus; ULONG m_ul_specialityStatus; — 277 — public: void SetAddNewAndUpdateStatus(); }; void CEmployeeInfoRs::SetAddNewAndUpdateStatus() { m_ul_idStatus = adFldOK; m_ul_birthStatus = adFldOK; m_ul_nameStatus = adFldOK; m_ul_sexStatus = adFldOK; m_ul_degreeStatus = adFldOK; m_ul_in_dateStatus = adFldOK; m_ul_regular_dateStatus = adFldOK; m_ul_deptStatus = adFldOK; m_ul_interestStatus = adFldOK; m_ul_specialityStatus = adFldOK; } 人才信息表 employee_info_tab 共包含 10 个字段,人才信息 ID 为整型,可利用定长类 型数据绑定格式 ADO_FIXED_LENGTH_ENTRY,其他都是字符串类型可用变长类型绑定 格式 ADO_VARIABLE_LENGTH_ENTRY2。 在数据绑定类 CEmployeeInfoRs 类中定义了一个 SetAddNewAndUpdateStatus 函数, 目的是将所有的字段状态值设为 adFldOK,因为在调用 AddNew 和 Update 方法的时候,系 统会根据字段的状态是否为 adFldOK 来更新相应字段的数据。如果不为 adFldOK,该字段 会无法更新或者出现异常;如果数据表中的记录为空,添加第 1 条记录而没有设定所有的 字段状态为 adFldOK,将会出现异常。 教育经历信息表 education_info_tab 的数据绑定类为 CEducationInfoRs,定义如下: //构造教育经历信息绑定类 class CEducationInfoRs : public CADORecordBinding { BEGIN_ADO_BINDING(CEducationInfoRs) ADO_FIXED_LENGTH_ENTRY(1, adInteger, m_n_educationid, m_ul_educationidStatus, true) ADO_FIXED_LENGTH_ENTRY(2, adInteger, m_n_employeeid, m_ul_employeeidStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(3, adVarChar, m_ch_school, sizeof(m_ch_school),m_ul_schoolStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_ch_enrolldate, sizeof(m_ch_enrolldate),m_ul_enrolldateStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(5, adVarChar, m_ch_graduatedate, sizeof(m_ch_graduatedate),m_ul_graduatedateStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(6, adVarChar, m_ch_major, sizeof(m_ch_major),m_ul_majorStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(7, adVarChar, m_ch_degree, sizeof(m_ch_degree),m_ul_degreeStatus, true) END_ADO_BINDING() public: — 278 — int m_n_educationid; int m_n_employeeid; CHAR m_ch_school[62]; CHAR m_ch_enrolldate[22]; CHAR m_ch_graduatedate[22]; CHAR m_ch_major[32]; CHAR m_ch_degree[12]; ULONG m_ul_educationidStatus; ULONG m_ul_employeeidStatus; ULONG m_ul_schoolStatus; ULONG m_ul_enrolldateStatus; ULONG m_ul_graduatedateStatus; ULONG m_ul_majorStatus; ULONG m_ul_degreeStatus; public: void SetAddNewAndUpdateStatus(); }; void CEducationInfoRs::SetAddNewAndUpdateStatus() { m_ul_educationidStatus = adFldOK; m_ul_employeeidStatus = adFldOK; m_ul_schoolStatus = adFldOK; m_ul_enrolldateStatus = adFldOK; m_ul_graduatedateStatus = adFldOK; m_ul_majorStatus = adFldOK; m_ul_degreeStatus = adFldOK; } 职位变更信息表 position_change_info_tab 的数据绑定类为 CPositionInfoRs,定义方法 如下: //构造职位变更信息绑定类 class CPositionInfoRs : public CADORecordBinding { BEGIN_ADO_BINDING(CPositionInfoRs) ADO_FIXED_LENGTH_ENTRY(1, adInteger, m_n_positionid, m_ul_positionidStatus, true) ADO_FIXED_LENGTH_ENTRY(2, adInteger, m_n_employeeid, m_ul_employeeidStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(3, adVarChar, m_ch_date, sizeof(m_ch_date),m_ul_dateStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_ch_description, sizeof(m_ch_description),m_ul_descriptionStatus, true) END_ADO_BINDING() public: int m_n_positionid; int m_n_employeeid; CHAR m_ch_date[22]; — 279 — CHAR m_ch_description[202]; ULONG m_ul_positionidStatus; ULONG m_ul_employeeidStatus; ULONG m_ul_dateStatus; ULONG m_ul_descriptionStatus; public: void SetAddNewAndUpdateStatus(); }; void CPositionInfoRs::SetAddNewAndUpdateStatus() { m_ul_positionidStatus = adFldOK; m_ul_employeeidStatus = adFldOK; m_ul_dateStatus = adFldOK; m_ul_descriptionStatus = adFldOK; } 工作成果信息表achievement_info_tab的数据绑定类为CAchievementInfoRs,定义如下: //构造工作成果信息绑定类 class CAchievementInfoRs : public CADORecordBinding { BEGIN_ADO_BINDING(CAchievementInfoRs) ADO_FIXED_LENGTH_ENTRY(1, adInteger, m_n_achievementid, m_ul_achivementidStatus, true) ADO_FIXED_LENGTH_ENTRY(2, adInteger, m_n_employeeid, m_ul_employeeidStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(3, adVarChar, m_ch_date, sizeof(m_ch_date),m_ul_dateStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_ch_achievement, sizeof(m_ch_description),m_ul_achievementStatus, true) ADO_VARIABLE_LENGTH_ENTRY2(5, adVarChar, m_ch_description, sizeof(m_ch_description),m_ul_descriptionStatus, true) END_ADO_BINDING() public: int m_n_achievementid; int m_n_employeeid; CHAR m_ch_date[22]; CHAR m_ch_achievement[202]; CHAR m_ch_description[202]; ULONG m_ul_achivementidStatus; ULONG m_ul_employeeidStatus; ULONG m_ul_dateStatus; ULONG m_ul_achievementStatus; ULONG m_ul_descriptionStatus; public: void SetAddNewAndUpdateStatus(); }; — 280 — void CAchievementInfoRs::SetAddNewAndUpdateStatus() { m_ul_achivementidStatus = adFldOK; m_ul_employeeidStatus = adFldOK; m_ul_dateStatus = adFldOK; m_ul_achievementStatus = adFldOK; m_ul_descriptionStatus = adFldOK; } 这 4 个数据绑定类放在 ADOExtData.h 文件中,这样在需要使用这些数据绑定类的时 候,引用 ADOExtData.h 文件就可以了,非常方便。 把数据库中的数据显示到列表框控件中的函数为 RefreshCtrlData,代码如下: void CEmployeeDBSDlg::RefreshCtrlData(CString sql) { try { //删除人才信息列表框控件中的数据 m_listEmployee.DeleteAllItems(); //设置当前人才信息 ID 初始状态为 1 m_nCurrentEmployeeID = 1; CEmployeeInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); m_pRecordset->Open(_bstr_t(sql), _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); int id; CString name,sex,degree,birth,indate; CString regulardate,dept,interest,speciality; //数据绑定 picRs->BindToRecordset(&rs); //遍历记录集合 while (!m_pRecordset->adoEOF) { id = rs.m_ul_idStatus == adFldOK ?rs.m_n_id:0; //设置当前的人才信息 ID m_nCurrentEmployeeID = id; //获取人才姓名、性别、教育程度、进公司日前、转正日期、 //隶属部门、特长、专业技能信息. name = rs.m_ul_nameStatus == adFldOK?rs.m_ch_name:""; sex = rs.m_ul_sexStatus == adFldOK?rs.m_ch_sex:""; degree = rs.m_ul_degreeStatus == adFldOK?rs.m_ch_degree:""; birth = rs.m_ul_birthStatus == adFldOK?rs.m_ch_birth:""; indate = rs.m_ul_in_dateStatus==adFldOK?rs.m_ch_in_date:""; regulardate = rs.m_ul_regular_dateStatus == adFldOK?rs.m_ch_regular_date:""; dept = rs.m_ul_deptStatus == adFldOK?rs.m_ch_dept:""; interest = rs.m_ul_interestStatus == adFldOK?rs.m_ch_interest:""; — 281 — speciality = rs.m_ul_specialityStatus == adFldOK ?rs.m_ch_speciality:""; //显示在界面上 InsertEmployeeInfoItem(id,name,sex,degree,birth,indate,regulardate, dept,interest,speciality); m_pRecordset->MoveNext(); } m_pRecordset->Close(); //根据当前的人才 ID 获取相关的教育经历、职位变更、工作成果信息 RefreshEducationCtrlData(); RefreshPositionCtrlData(); RefreshAchievementCtrlData(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } RefreshCtrlData 函数利用数据绑定类获取数据库中的字段数据。在利用数据绑定类查 询记录的时候,需要用到记录集对象打开需要查询的记录集,然后利用 IADORecordBinding 接口的 BindToRecordset 方法将打开的记录集和数据绑定类绑定起来,这样就可以通过访问 数据绑定类成员的方法来访问记录集中的字段值。在访问这些数据的时候,需要判断数据 字段的状态是否为 adFldOK,如果不为 adFldOK 需要给出一个默认值。RefreshCtrlData 函 数在遍历记录集的时候,需要更新当前人才信息 ID(m_nCurrentEmployeeID)值,从而在访 问完人才信息数据之后,可以根据当前的人才信息 ID 刷新教育经历信息、职位变更信息 和工作成果信息列表框中的显示数据。 根 据 当 前 人 才 ID 刷新教育经历信息列表框控件中的显示数据的函数为 RefreshEducationCtrlData,代码如下: void CEmployeeDBSDlg::RefreshEducationCtrlData() { try { //删除教育信息列表框控件中的数据 m_listEducation.DeleteAllItems(); CEducationInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); CString sql; sql.Format("select * from education_info_tab where employee_id = %d",m_nCurrentEmployeeID); m_pRecordset->Open(_bstr_t(sql), _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //数据绑定 — 282 — picRs->BindToRecordset(&rs); //显示所有该人才 ID 的所有教育经历记录 //遍历记录集合 while (!m_pRecordset->adoEOF) { int id; CString school,enrolldate,graduatedate,major,degree; //获取 ID、院校、入学日期、毕业日期、专业、学位信息 id = rs.m_ul_educationidStatus == adFldOK ?rs.m_n_educationid:0; school = rs.m_ul_schoolStatus == adFldOK?rs.m_ch_school:""; enrolldate = rs.m_ul_enrolldateStatus == adFldOK?rs.m_ch_enrolldate:""; graduatedate = rs.m_ul_graduatedateStatus == adFldOK?rs.m_ch_graduatedate:""; major = rs.m_ul_majorStatus == adFldOK?rs.m_ch_major:""; degree = rs.m_ul_degreeStatus ==adFldOK?rs.m_ch_degree:""; //显示在界面上 InsertEducationInfoItem(id,school,enrolldate,graduatedate,major,degree) ; m_pRecordset->MoveNext(); } m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 根 据 当 前 人 才 ID 刷新职位变更信息列表框控件中的显示数据的函数为 RefreshPositionCtrlData,代码如下: void CEmployeeDBSDlg::RefreshPositionCtrlData() { try { //删除职位变更信息列表框控件中的数据 m_listPosition.DeleteAllItems(); CPositionInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); //显示当前人才 ID 的所有职位变更信息 CString sql; sql.Format("select * from position_change_info_tab where employee_id = %d",m_nCurrentEmployeeID); m_pRecordset->Open(_bstr_t(sql), _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); — 283 — //数据绑定 picRs->BindToRecordset(&rs); //遍历记录集合 while (!m_pRecordset->adoEOF) { int id; CString date,description; //获取日期和说明的记录 id = rs.m_ul_positionidStatus == adFldOK ?rs.m_n_employeeid:0; date = rs.m_ul_dateStatus == adFldOK?rs.m_ch_date:""; description = rs.m_ul_descriptionStatus == adFldOK?rs.m_ch_description :""; //显示在界面上 InsertPostionInfoItem(id,date,description); m_pRecordset->MoveNext();//转到下一条记录 } m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 根 据 当 前 人 才 ID 刷新工作成果信息列表框控件中的显示数据的函数为 RefreshAchievementCtrlData,代码如下: void CEmployeeDBSDlg::RefreshAchievementCtrlData() { try { //删除工作成果列表框控件中的数据 m_listAchievement.DeleteAllItems(); CAchievementInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); CString sql; sql.Format("select * from achievement_info_tab where employee_id = %d",m_nCurrentEmployeeID); m_pRecordset->Open(_bstr_t(sql), _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //数据绑定 picRs->BindToRecordset(&rs); //遍历记录集合 while (!m_pRecordset->adoEOF) { int id; CString date,achievement,description; — 284 — //获取日期、成果、说明信息 id = rs.m_ul_achivementidStatus == adFldOK ?rs.m_n_achievementid:0; date = rs.m_ul_dateStatus == adFldOK?rs.m_ch_date:""; achievement = rs.m_ul_achivementidStatus == adFldOK?rs.m_ch_achievement:""; description = rs.m_ul_descriptionStatus == adFldOK?rs.m_ch_description :""; InsertAchievementInfoItem(id,date,achievement,description); //显示在界面上 m_pRecordset->MoveNext(); } m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 系统在启动的时候,会自动连接数据库,数据库连接成功后,就在列表框控件中显示 数据库中的信息,如图 8-3 所示。 图 8-3 连接数据库之后的对话框 为了更好地显示人才信息,需要添加人才信息列表框控件 IDC_LIST_EMPLOYEE 的 NM_CLICK 消息映射函数 OnClickListEmployee。当在人才信息列表框控件上选择某一个 人才的时候,人才信息显示到人才信息参数的控件中,并在教育经历信息、职位变更信息 和工作成果信息列表框控件中显示该人才的信息,代码如下: — 285 — void CEmployeeDBSDlg::OnClickListEmployee(NMHDR* pNMHDR, LRESULT* pResult) { //获取已选择的记录项. int nItem = m_listEmployee.GetNextItem(-1, LVNI_SELECTED); if(nItem != -1){ //更新当前选择的人才 ID 值 m_nCurrentEmployeeID = atoi(m_listEmployee.GetItemText(nItem,0)); //从列表框控件的第 nItem+1 行获取数据,以将这些数据显示到人才信息参数的控件中. m_strName = m_listEmployee.GetItemText(nItem,1); m_strSex = m_listEmployee.GetItemText(nItem,2); m_strDegree = m_listEmployee.GetItemText(nItem,3); CString strBirth = m_listEmployee.GetItemText(nItem,4); GetYMDByString(strBirth,m_nBirthYear,m_strBirthMonth,m_strBirthDay); CString strIndate = m_listEmployee.GetItemText(nItem,5); GetYMDByString(strIndate,m_nInYear,m_strInMonth,m_strInDay); CString strRegularDate = m_listEmployee.GetItemText(nItem,6); GetYMDByString(strRegularDate,m_nRegularYear,m_strRegularMonth,m_strReg ularDay); m_strDept = m_listEmployee.GetItemText(nItem,7); m_strInterest = m_listEmployee.GetItemText(nItem,8); m_strSpeciality = m_listEmployee.GetItemText(nItem,9); //根据当前选择的人才信息 ID,刷新该人才 ID 下的所有教育 //经历、职位变更、工作成果信息. UpdateData(FALSE); RefreshEducationCtrlData(); RefreshPositionCtrlData(); RefreshAchievementCtrlData(); } *pResult = 0; } 系统中需要用到两个日期转换函数,分别为:GetYMDByString 和 GetDateString 函数。 其中 GetYMDByString 函数是从日期字符串中获取年月日信息,GetDateString 函数是根据 年月日信息构造日期字符串。这两个函数的代码如下: bool CEmployeeDBSDlg::GetDateString(int nYear, CString month, CString day, CString &date) { COleDateTime dt; CString strDate; //构造时间字符串 strDate.Format("%d-%s-%s",nYear,month,day); TRY{ //检查时间字符串是否为标准的时间 if(!dt.ParseDateTime(strDate)) return false; //如果是转化为“2004-07-03”的格式 date = dt.Format("%Y-%m-%d"); } CATCH(CMemoryException,ex) — 286 — { AfxMessageBox("memory exception"); return false; } AND_CATCH(COleException,e) { AfxMessageBox("OleException"); return false; } END_CATCH return true; } void CEmployeeDBSDlg::GetYMDByString(CString strDate,int& nYear,CString& month,CString& day) { //设置初始值. nYear = 0; month = ""; day = ""; if(strDate.IsEmpty()) return; COleDateTime dt; TRY{ //分析时间字符串,如果有效,获取年、月、日的信息 if(!dt.ParseDateTime(strDate)) //分析时间的有效性 return; nYear = dt.GetYear(); month.Format("%d",dt.GetMonth()); day.Format("%d",dt.GetDay()); } CATCH(CMemoryException,ex) { AfxMessageBox("memory exception"); return; } AND_CATCH(COleException,e) { AfxMessageBox("OleException"); return; } END_CATCH } 8.3.4 人才信息管理 人才信息管理包括对人才信息的添加、修改、删除和查询的功能。 1. 添加人才信息 添加人才信息的函数为 OnBtnEmployeeAdd,代码如下: — 287 — void CEmployeeDBSDlg::OnBtnEmployeeAdd() { if(!UpdateData()) return; if(m_strName.IsEmpty()){ AfxMessageBox("姓名不能够为空"); return; } //获取日期字符串,格式为“2004-07-03” CString strBirth,strIndate,strRegulardate; if(!GetDateString(m_nBirthYear,m_strBirthMonth,m_strBirthDay,strBirth)) { AfxMessageBox("出生年月的日期不符合规范"); return; } if(!GetDateString(m_nInYear,m_strInMonth,m_strInDay,strIndate)) { AfxMessageBox("来公司的日期不符合规范"); return; } if(!GetDateString(m_nRegularYear,m_strRegularMonth,m_strRegularDay,strR egulardate)) { AfxMessageBox("转正日期不符合规范"); return; } try { m_pRecordset->Open("Select seq_employee_id.NEXTVAL from dual" ,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText); int id = 1; if(!m_pRecordset->adoEOF) { _variant_t TheValue; //从系列中获取新的人才 ID 值. TheValue = m_pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; } m_pRecordset->Close(); CEmployeeInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); m_pRecordset->Open("select * from employee_info_tab", _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //绑定记录集 TESTHR(picRs->BindToRecordset(&rs)); — 288 — //设置绑定对象 rs 的状态成员变量的值为 adFldOK,就可以实现 AddNew 的正常调用 rs.SetAddNewAndUpdateStatus(); //给新添加的记录赋值 rs.m_n_id = id; strcpy(rs.m_ch_name,m_strName); strcpy(rs.m_ch_birth,strBirth); strcpy(rs.m_ch_sex,m_strSex); strcpy(rs.m_ch_degree,m_strDegree); strcpy(rs.m_ch_in_date,strIndate); strcpy(rs.m_ch_regular_date,strRegulardate); strcpy(rs.m_ch_dept,m_strDept); strcpy(rs.m_ch_interest,m_strInterest); strcpy(rs.m_ch_speciality,m_strSpeciality); //调用 IADORecordBinding 接口的 AddNew 方法添加新数据,并显示到界面上 TESTHR(picRs->AddNew(&rs)); InsertEmployeeInfoItem(id,m_strName,m_strSex,m_strDegree,strBirth,strIn date, strRegulardate,m_strDept,m_strInterest,m_strSpeciality); m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } ADO 数据绑定技术是利用 IADORecordBinding 接口的 AddNew 方法将新数据添加到 数据库中的。首先利用记录集对象打开需要操作的数据表,并利用 IADORecordBinding 接 口 的 BindToRecordset 方法将打开的记录集和数据绑定类绑定起来,调用 SetAddNewAndUpdateStatus 函数设置字段的状态为 adFldOK,然后设置数据绑定类成员的 值,最后调用 IADORecordBinding 接口的 AddNew 方法将数据添加的数据库中。 如图 8-4 所示,输入“林东”的人才信息。 图 8-4 添加人才信息对话框 — 289 — 单击“添加”按钮,人才信息显示到列表框控件中,如图 8-5 所示。 图 8-5 添加人才信息之后的对话框 2. 修改人才信息 修改人才信息的函数为 OnBtnEmployeeMod,代码如下: void CEmployeeDBSDlg::OnBtnEmployeeMod() { if(!UpdateData()) return; int nItem = m_listEmployee.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要修改的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要修改的人才信息"); return; } //从列表框控件中获取选择的人才信息 ID. int id = atoi(m_listEmployee.GetItemText(nItem,0)); if(m_strName.IsEmpty()){ AfxMessageBox("姓名不能够为空"); return; } CString strBirth,strIndate,strRegulardate; if(!GetDateString(m_nBirthYear,m_strBirthMonth,m_strBirthDay,strBirth)) { AfxMessageBox("出生年月的日期不符合规范"); return; } if(!GetDateString(m_nInYear,m_strInMonth,m_strInDay,strIndate)) { AfxMessageBox("来公司的日期不符合规范"); return; } if(!GetDateString(m_nRegularYear,m_strRegularMonth,m_strRegularDay,strR egulardate)) — 290 — { AfxMessageBox("转正日期不符合规范"); return; } try { CEmployeeInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); CString sql; sql.Format("select * from employee_info_tab where employee_id = %d",id); m_pRecordset->Open(_bstr_t(sql), _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //绑定记录集 TESTHR(picRs->BindToRecordset(&rs)); //设置绑定对象 rs 的状态成员变量的值为 adFldOK,就可以实现 Update 的正常调用 rs.SetAddNewAndUpdateStatus(); //修改数据库的记录 rs.m_n_id = id; strcpy(rs.m_ch_name,m_strName); strcpy(rs.m_ch_birth,strBirth); strcpy(rs.m_ch_sex,m_strSex); strcpy(rs.m_ch_degree,m_strDegree); strcpy(rs.m_ch_in_date,strIndate); strcpy(rs.m_ch_regular_date,strRegulardate); strcpy(rs.m_ch_dept,m_strDept); strcpy(rs.m_ch_interest,m_strInterest); strcpy(rs.m_ch_speciality,m_strSpeciality); //调用 IADORecordBinding 接口的 Update 方法添加新数据 TESTHR(picRs->Update(&rs)); m_listEmployee.SetItemText(nItem,1,m_strName); m_listEmployee.SetItemText(nItem,2,m_strSex); m_listEmployee.SetItemText(nItem,3,m_strDegree); m_listEmployee.SetItemText(nItem,4,strBirth); m_listEmployee.SetItemText(nItem,5,strIndate); m_listEmployee.SetItemText(nItem,6,strRegulardate); m_listEmployee.SetItemText(nItem,7,m_strDept); m_listEmployee.SetItemText(nItem,8,m_strInterest); m_listEmployee.SetItemText(nItem,9,m_strSpeciality); m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } — 291 — 3. 删除人才信息 删除人才信息使用连接对象的 Execute 方法,首先删除该人才所有的教育经历、职位 变更和工作成果信息,再删除人才基本信息。删除人才信息的函数为 OnBtnEmployeeDel, 代码如下: void CEmployeeDBSDlg::OnBtnEmployeeDel() { if(!UpdateData()) return; int nItem = m_listEmployee.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要删除的人才信息,返回. if(nItem == -1){ AfxMessageBox("没有选择要删除的人才信息"); return; } //从列表框控件中获取选择的人才信息 ID. int id = atoi(m_listEmployee.GetItemText(nItem,0)); try { _variant_t RecordsAffected; CString sql; //先删除该人才的教育经历信息记录. sql.Format("delete from education_info_tab where employee_id = %d",id); m_pConnection->Execute(_bstr_t(sql),&RecordsAffected,adCmdText); //然后删除该人才的职位变更信息记录. sql.Format("delete from position_change_info_tab where employee_id = %d",id); m_pConnection->Execute(_bstr_t(sql),&RecordsAffected,adCmdText); //再删除该人才的工作成果信息记录. sql.Format("delete from achievement_info_tab where employee_id = %d",id); m_pConnection->Execute(_bstr_t(sql),&RecordsAffected,adCmdText); //最后删除人才信息记录 sql.Format("delete from employee_info_tab where employee_id = %d",id); m_pConnection->Execute(_bstr_t(sql),&RecordsAffected,adCmdText); //从界面上删除信息 m_listEmployee.DeleteItem(nItem); m_listEducation.DeleteAllItems(); m_listPosition.DeleteAllItems(); m_listAchievement.DeleteAllItems(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } — 292 — 4. 查询 人才储备管理系统提供了 10 种查询方式获取人才信息,分别为:所有记录、姓名、 性别、教育程度、隶属部门、个人特长、专业技能、院校、专业和工作成果信息。公司管 理者可以根据这些查询方式快速找到需要的人才以及了解公司的人才储备情况。 为了方便查询,可以创建一个对话框,对话框类名称为 CQueryDlg,资源 ID 为 IDD_DIALOG_QUERY,对话框名称为“选择查询条件”,“选择查询条件”对话框如图 8-6 所示。 图 8-6 “选择查询条件”对话框 控件类型、ID 及说明见表 8-9。 表 8-9 控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 选择查询条件 无 Radio Button IDC_RADIO1 所有记录 整型变量 m_nChoice 控制选项 Radio Button IDC_RADIO2 姓名 无 Edit Box IDC_EDIT_NAME 无 CString 类型变量 m_strName Radio Button IDC_RADIO3 性别 无 Combo Box IDC_COMBO_SEX 无 CString 类型变量 m_strSex Radio Button IDC_RADIO4 教育程度 无 Combo Box IDC_COMBO_DEGREE 无 CString 类型变量 m_strDegree Radio Button IDC_RADIO5 隶属部门 无 Edit Box IDC_EDIT_DEPARTMENT 无 CString 类型变量 m_strDept Radio Button IDC_RADIO6 个人特长 无 Edit Box IDC_EDIT_INTER 无 CString 类型变量 m_strInterest Radio Button IDC_RADIO7 专业技能 无 Edit Box IDC_EDIT_SPECIAL 无 CString 类型变量 m_strSpeciality Radio Button IDC_RADIO8 院校 无 Edit Box IDC_EDIT_SCHOOL 无 CString 类型变量 m_strSchool Radio Button IDC_RADIO9 专业 Edit Box IDC_EDIT_MAJOR 无 CString 类型变量 m_strMajor Radio Button IDC_RADIO10 个人特长 无 Edit Box IDC_EDIT_ACHIEVEMENT 无 CString 类型变量 m_strAchievement Button IDOK 确定 函数 OnOK()创建查询的 SQL 语句 Button IDCANCEL 取消 无 选择 IDC_RADIO1 属性的 General 选项卡,选中 Group 选项,而其他 Radio Button 的 属性不要选中 Group 选项,这些 Radio Button 的 Tab Order 顺序要连续,如图 8-7 所示。 — 293 — 图 8-7 Tab Order 顺序对话框 OnOK 函数创建了查询人才信息的 SQL 语句,代码如下: void CQueryDlg::OnOK() { if(!UpdateData()) return; //构造通配符"%",在 SQL 标准语句中的利用 like 关键字可以实现模糊查询. CString temp = "%"; switch(m_nChoice) { //查询所有记录 case 0: m_strSQL = "Select * from employee_info_tab"; break; //根据姓名进行模糊查询 case 1: m_strSQL.Format("Select * from employee_info_tab" " where name like ’%s%s%s’",temp,m_strName,temp); break; //根据性别进行查询 case 2: m_strSQL.Format("Select * from employee_info_tab" " where sex = ’%s’",m_strSex); break; //根据教育程度进行查询 case 3: m_strSQL.Format("Select * from employee_info_tab" " where degree = ’%s’",m_strDegree); break; //根据隶属部门进行查询 case 4: m_strSQL.Format("Select * from employee_info_tab" " where dept = ’%s’",m_strDept); break; //根据个人特长进行模糊查询 case 5: m_strSQL.Format("Select * from employee_info_tab" " where interest like ’%s%s%s’",temp,m_strInterest,temp); break; — 294 — //根据专业技能进行模糊查询 case 6: m_strSQL.Format("Select * from employee_info_tab" " where speciality like ’%s%s%s’",temp,m_strSpeciality,temp); break; //根据毕业院校进行模糊查询 case 7: m_strSQL.Format("Select distinct a.employee_id,name,sex,birth,a.degree,in_date," "regular_date,dept,interest,speciality from employee_info_tab a ,education_info_tab b" " where a.employee_id = b.employee_id and b.school like ’%s%s%s’",temp,m_strSchool,temp); break; //根据专业进行模糊查询 case 8: m_strSQL.Format("Select distinct a.employee_id,name,sex,birth,a.degree,in_date," "regular_date,dept,interest,speciality from employee_info_tab a ,education_info_tab b" " where a.employee_id = b.employee_id and b.major like ’%s%s%s’",temp,m_strMajor,temp); break; //根据工作成果进行模糊查询 case 9: m_strSQL.Format("Select distinct a.employee_id,name,sex,birth,a.degree,in_date," "regular_date,dept,interest,speciality from employee_info_tab a ,achievement_info_tab b" " where a.employee_id = b.employee_id and b.achievement like ’%s%s%s’",temp,m_strAchievement,temp); break; default: m_strSQL = "Select * from employee_info_tab"; } TRACE(m_strSQL); CDialog::OnOK(); } 在 CEmployeeDBSDlg 类的 OnBtnEmployeeQuery 函数中添加弹出“选择查询条件”对 话框和根据对话框中选择的查询条件显示人才信息的代码,代码如下: void CEmployeeDBSDlg::OnBtnEmployeeQuery() { //创建查询条件对话框实例 CQueryDlg dlg; //打开查询条件对话框 if(dlg.DoModal()==IDOK){ //获取查询条件的 SQL 语句从而刷新界面上的记录值 RefreshCtrlData(dlg.m_strSQL); — 295 — } } 利用院校、专业和工作成果信息查询人才信息需要用到表的关联。例如根据院校的查 询方式,首先在教育经历信息表中找到在该院校有学习经历的人才信息 ID,然后在人才基 本信息表中查出该人才 ID 的人才信息。如在“选择查询条件”对话框中选择“院校”的 查询方式,在“院校”文本框中输入院校名称“清华大学”,如图 8-8 所示。 图 8-8 “选择查询条件”对话框 单击“确定”按钮,就会在主界面中列出“刘丽”的详细人才信息、教育经历信息、 职位变更信息和工作成果信息。从教育经历信息列表框控件中可以看到“刘丽”的“清华 大学”入学经历,如图 8-9 所示。 图 8-9 查询之后的对话框 — 296 — 8.3.5 教育经历信息管理 教育经历信息管理包括对教育经历信息的添加、修改和删除的功能。为了方便教育经 历信息的添加和修改,可以创建一个“教育经历信息”对话框,如图 8-10 所示。 图 8-10 “教育经历信息”对话框 1. 添加教育经历信息 添加教育经历信息的函数为 OnBtnEducationAdd,代码如下: void CEmployeeDBSDlg::OnBtnEducationAdd() { CEducationDlg dlg; if(dlg.DoModal() == IDOK){ if(dlg.m_strSchool.IsEmpty()){ AfxMessageBox("学校姓名不能够为空"); return; } CString enrolldate,graduatedate; if(!GetDateString(dlg.m_nEnrollYear,dlg.m_strEnrollMonth,dlg.m_strEnrol lDay,enrolldate)) { AfxMessageBox("入学日期不符合规范"); return; } if(!GetDateString(dlg.m_nGraduateYear,dlg.m_strGraduateMonth,dlg.m_strG raduateDay,graduatedate)) { AfxMessageBox("毕业日期不符合规范"); return; } try { m_pRecordset->Open("Select seq_education_id.NEXTVAL from dual" ,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText); int id = 1; — 297 — if(!m_pRecordset->adoEOF) { _variant_t TheValue; //从系列中获取新的教育经历 ID 值. TheValue = m_pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; } m_pRecordset->Close(); CEducationInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); m_pRecordset->Open("select * from education_info_tab", _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //绑定记录集 TESTHR(picRs->BindToRecordset(&rs)); //设置绑定对象 rs 的状态成员变量的值为 adFldOK,就可以实现 AddNew 的正常调用 rs.SetAddNewAndUpdateStatus(); //给新添加的记录赋值 rs.m_n_educationid = id; rs.m_n_employeeid = m_nCurrentEmployeeID; strcpy(rs.m_ch_school,dlg.m_strSchool); strcpy(rs.m_ch_enrolldate,enrolldate); strcpy(rs.m_ch_graduatedate,graduatedate); strcpy(rs.m_ch_major,dlg.m_strSubject); strcpy(rs.m_ch_degree,dlg.m_strDegree); //调用 IADORecordBinding 接口的 AddNew 方法添加新数据,并显示在界面上 TESTHR(picRs->AddNew(&rs)); InsertEducationInfoItem(id,dlg.m_strSchool,enrolldate,graduatedate,dlg. m_strSubject,dlg.m_strDegree); m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } } 如在人才信息列表框控件中选择“林东”的人才信息,在“教育经历信息管理”控件 中单击“添加”按钮,弹出“教育经历信息”对话框。在“院校”文本框中输入“人民大 学”,在“入学日期”控件中选择日期 1991 年 9 月 1 日,在“毕业日期”控件中选择日期 1995 年 7 月 1 日,在“专业”文本框中输入“自动化”,在“学位”列表框中选择“本科”, 如图 8-11 所示。 — 298 — 图 8-11 添加教育经历信息的对话框 单击“确定”按钮,教育经历信息就显示到列表框控件中,如图 8-12 所示。 图 8-12 添加教育经历之后的对话框 按照上面的方式再添加一条林东的博士入学经历,如图 8-13 所示。 图 8-13 添加教育经历之后的对话框 2. 修改教育经历信息 修改教育经历信息的函数为 OnBtnEducationMod,代码如下: void CEmployeeDBSDlg::OnBtnEducationMod() { //获取要修改的信息所在的行数. int nItem = m_listEducation.GetNextItem(-1, LVNI_SELECTED); if(nItem == -1){ AfxMessageBox("没有选择要修改的教育信息"); return; — 299 — } //获取教育经历 ID. int id = atoi(m_listEducation.GetItemText(nItem,0)); CEducationDlg dlg; dlg.m_strSchool = m_listEducation.GetItemText(nItem,1); CString enrollDate = m_listEducation.GetItemText(nItem,2); GetYMDByString(enrollDate,dlg.m_nEnrollYear,dlg.m_strEnrollMonth,dlg.m_ strEnrollDay); CString graduateDate = m_listEducation.GetItemText(nItem,3); GetYMDByString(graduateDate,dlg.m_nGraduateYear,dlg.m_strGraduateMonth, dlg.m_strGraduateDay); dlg.m_strSubject = m_listEducation.GetItemText(nItem,4); dlg.m_strDegree = m_listEducation.GetItemText(nItem,5); if(dlg.DoModal() == IDOK){ if(dlg.m_strSchool.IsEmpty()){ AfxMessageBox("学校姓名不能够为空"); return; } CString enrolldate,graduatedate; if(!GetDateString(dlg.m_nEnrollYear,dlg.m_strEnrollMonth,dlg.m_strEnrol lDay,enrolldate)) { AfxMessageBox("入学日期不符合规范"); return; } if(!GetDateString(dlg.m_nGraduateYear,dlg.m_strGraduateMonth,dlg.m_strG raduateDay,graduatedate)) { AfxMessageBox("毕业日期不符合规范"); return; } try { CEducationInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); CString sql; sql.Format("select * from education_info_tab where education_id = %d",id); m_pRecordset->Open(_bstr_t(sql), _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //绑定记录集 TESTHR(picRs->BindToRecordset(&rs)); //设置绑定对象 rs 的状态成员变量的值为 adFldOK,就可以实现 AddNew 的正常调用 rs.SetAddNewAndUpdateStatus(); — 300 — //修改记录值 strcpy(rs.m_ch_school,dlg.m_strSchool); strcpy(rs.m_ch_enrolldate,enrolldate); strcpy(rs.m_ch_graduatedate,graduatedate); strcpy(rs.m_ch_major,dlg.m_strSubject); strcpy(rs.m_ch_degree,dlg.m_strDegree); //调用 IADORecordBinding 接口的 Update 方法添加新数据,并更新界面 TESTHR(picRs->Update(&rs)); m_pRecordset->Close(); m_listEducation.SetItemText(nItem,1,dlg.m_strSchool); m_listEducation.SetItemText(nItem,2,enrolldate); m_listEducation.SetItemText(nItem,3,graduatedate); m_listEducation.SetItemText(nItem,4,dlg.m_strSubject); m_listEducation.SetItemText(nItem,5,dlg.m_strDegree); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } } 3. 删除教育经历信息 删除教育经历信息的函数为 OnBtnEducationDel,函数利用连接对象的 Execute 方法删 除在列表框控件中选择的教育经历信息,方法比较简单,代码不再列出。 8.3.6 职位变更信息管理 职位变更信息管理包括对职位信息的添加、修改和删除的功能。为了方便职位变更信 息的添加和修改,可以创建一个“职位变更信息”对话框,如图 8-14 所示。 图 8-14 “职位变更信息”对话框 1. 添加职位变更信息 添加职位变更信息的函数为 OnBtnPositionAdd,代码如下: void CEmployeeDBSDlg::OnBtnPositionAdd() { CPositionDlg dlg; — 301 — if(dlg.DoModal() == IDOK){ CString date; if(!GetDateString(dlg.m_nYear,dlg.m_strMonth,dlg.m_strDay,date)) { AfxMessageBox("日期不符合规范"); return; } try { m_pRecordset->Open("Select seq_position_id.NEXTVAL from dual" ,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText); int id = 1; if(!m_pRecordset->adoEOF) { _variant_t TheValue; //从系列中获取新的职位变更 ID 值. TheValue = m_pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; } m_pRecordset->Close(); CPositionInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); m_pRecordset->Open("select * from position_change_info_tab", _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //绑定记录集 TESTHR(picRs->BindToRecordset(&rs)); //设置绑定对象 rs 的状态成员变量的值为 adFldOK,就可以实现 AddNew 的正常调用 rs.SetAddNewAndUpdateStatus(); //给新添加的记录赋值 rs.m_n_positionid = id; rs.m_n_employeeid = m_nCurrentEmployeeID; strcpy(rs.m_ch_date,date); strcpy(rs.m_ch_description,dlg.m_strDescription); //调用 IADORecordBinding 接口的 AddNew 方法添加新数据,并在界面上显示 TESTHR(picRs->AddNew(&rs)); InsertPostionInfoItem(id,date,dlg.m_strDescription); m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } } — 302 — 2. 修改职位变更信息 修改职位变更信息的函数为 OnBtnPositionMod,代码如下: void CEmployeeDBSDlg::OnBtnPositionMod() { //获取要修改的备注信息所在的行数. int nItem = m_listPosition.GetNextItem(-1, LVNI_SELECTED); if(nItem == -1){ AfxMessageBox("没有选择要修改的教育信息"); return; } //获取职位信息 ID. int id = atoi(m_listPosition.GetItemText(nItem,0)); CPositionDlg dlg; CString date = m_listPosition.GetItemText(nItem,1); GetYMDByString(date,dlg.m_nYear,dlg.m_strMonth,dlg.m_strDay); dlg.m_strDescription = m_listPosition.GetItemText(nItem,2); if(dlg.DoModal() == IDOK){ CString date; if(!GetDateString(dlg.m_nYear,dlg.m_strMonth,dlg.m_strDay,date)) { AfxMessageBox("日期不符合规范"); return; } try { CPositionInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); CString sql; sql.Format("select * from position_change_info_tab where position_id = %d",id); m_pRecordset->Open(_bstr_t(sql), _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //绑定记录集 TESTHR(picRs->BindToRecordset(&rs)); //设置绑定对象 rs 的状态成员变量的值为 adFldOK,就可以实现 Update 的正常调用 rs.SetAddNewAndUpdateStatus(); //给新添加的记录赋值 strcpy(rs.m_ch_date,date); strcpy(rs.m_ch_description,dlg.m_strDescription); //调用 IADORecordBinding 接口的 Update 方法添加新数据,并修改界面上的值 TESTHR(picRs->Update(&rs)); m_pRecordset->Close(); m_listPosition.SetItemText(nItem,1,date); m_listPosition.SetItemText(nItem,2,dlg.m_strDescription); — 303 — } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } } 3. 删除职位变更信息 删除职位变更信息的函数为 OnBtnPositionDel,函数利用连接对象的 Execute 方法删除 在列表框控件中选择的职位变更信息,方法比较简单,代码不再列出。 8.3.7 工作成果信息管理 工作成果信息管理包括对工作成果信息的添加、修改和删除的功能。为了方便工作成 果信息的添加和修改,可以创建一个“工作成果信息”对话框,如图 8-15 所示。 图 8-15 “工作成果信息”对话框 1. 添加工作成果信息 添加工作成果信息的函数为 OnBtnAchievementAdd,代码如下: void CEmployeeDBSDlg::OnBtnAchievementAdd() { CAchievementDlg dlg; if(dlg.DoModal() == IDOK){ CString date; if(!GetDateString(dlg.m_nYear,dlg.m_strMonth,dlg.m_strDay,date)) { AfxMessageBox("日期不符合规范"); return; } try { m_pRecordset->Open("Select seq_achievement_id.NEXTVAL from dual" ,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText); int id = 1; if(!m_pRecordset->adoEOF) — 304 — { _variant_t TheValue; //从系列中获取新的工作成果 ID 值。 TheValue = m_pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; } m_pRecordset->Close(); CAchievementInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); m_pRecordset->Open("select * from achievement_info_tab", _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //绑定记录集 TESTHR(picRs->BindToRecordset(&rs)); //设置绑定对象 rs 的状态成员变量的值为 adFldOK,就可以实现 AddNew 的正常调用 rs.SetAddNewAndUpdateStatus(); //给新添加的记录赋值 rs.m_n_achievementid = id; rs.m_n_employeeid = m_nCurrentEmployeeID; strcpy(rs.m_ch_date,date); strcpy(rs.m_ch_achievement,dlg.m_strAchievement); strcpy(rs.m_ch_description,dlg.m_strDescription); //调用 IADORecordBinding 接口的 AddNew 方法添加新数据 TESTHR(picRs->AddNew(&rs)); InsertAchievementInfoItem(id,date,dlg.m_strAchievement,dlg.m_strDescrip tion); m_pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } } 2. 修改工作成果信息 修改工作成果信息的函数为 OnBtnAchievementMod,代码如下: void CEmployeeDBSDlg::OnBtnAchievementMod() { //获取要修改的备注信息所在的行数. int nItem = m_listAchievement.GetNextItem(-1, LVNI_SELECTED); if(nItem == -1){ AfxMessageBox("没有选择要修改的教育信息"); return; — 305 — } //获取备注类型 ID. int id = atoi(m_listAchievement.GetItemText(nItem,0)); CAchievementDlg dlg; CString date = m_listAchievement.GetItemText(nItem,1); GetYMDByString(date,dlg.m_nYear,dlg.m_strMonth,dlg.m_strDay); dlg.m_strAchievement = m_listAchievement.GetItemText(nItem,2); dlg.m_strDescription = m_listAchievement.GetItemText(nItem,3); if(dlg.DoModal() == IDOK){ CString date; if(!GetDateString(dlg.m_nYear,dlg.m_strMonth,dlg.m_strDay,date)) { AfxMessageBox("日期不符合规范"); return; } try { CAchievementInfoRs rs; IADORecordBindingPtr picRs(m_pRecordset); CString sql; sql.Format("select * from achievement_info_tab where achievement_id = %d",id); m_pRecordset->Open(_bstr_t(sql), _variant_t((IDispatch*)m_pConnection,true), adOpenStatic, adLockOptimistic, adCmdText); //绑定记录集 TESTHR(picRs->BindToRecordset(&rs)); //设置绑定对象 rs 的状态成员变量的值为 adFldOK,就可以实现 AddNew 的正常调用 rs.SetAddNewAndUpdateStatus(); //给新添加的记录赋值 strcpy(rs.m_ch_date,date); strcpy(rs.m_ch_achievement,dlg.m_strAchievement); strcpy(rs.m_ch_description,dlg.m_strDescription); //调用 IADORecordBinding 接口的 Update 方法添加新数据 TESTHR(picRs->Update(&rs)); m_pRecordset->Close(); m_listAchievement.SetItemText(nItem,1,date); m_listAchievement.SetItemText(nItem,2,dlg.m_strAchievement); m_listAchievement.SetItemText(nItem,3,dlg.m_strDescription); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } } — 306 — 3. 删除工作成果信息 删除工作成果信息的函数为 OnBtnAchievementDel,函数利用连接对象的 Execute 方法 删除在列表框控件中选择的工作成果信息,方法比较简单,代码不再列出。 8.4 本章小结 本章详细介绍了人才储备管理系统的开发过程。作为一个人才储备管理系统,需要对 公司的人才信息提供有效的管理,还需要提供方便的查询功能从而更好地了解公司的人才 储备情况。在人才信息的查询功能中,提供了 10 种查询方式,通过这些方式能够快速地查 找需要的人才信息。由于利用了模糊查询,用户查询人才信息更为方便。如要查询某一个 公司项目的参加人员,可以根据工作成果进行查询,输入项目的名称,就可以列出参加过 这些项目的人员。因为工作成果信息表会包含参加这个项目的人员信息及具体负责的工作。 人才储备管理系统使用了 ADO 数据绑定技术。尽管需要编写数据绑定类,但是利用 这些绑定类可以方便和快速地访问数据库中的数据,这为我们的编程工作提供了很多的便 利。 第9章 家庭账务管理系统 如今,家庭理财已经越来越深入人心,尽管人们的收入越来越多,可是支出也在增多, 如果不合理支配,兜里的钱也会越来越空的。尤其对一些年青的工薪族来说,常常过着提 前消费的生活,而且还不知道钱是怎么用出去的,因而理财观念对现在的年青人来说是非 常有必要的。一个好的理财,需要了解收入和支出的明细情况,包括每天、每月及每年的 收入和支出情况,以及每年和每月的收支对比情况。家庭账务管理系统提供了方便的理财 功能。 9.1 系统设计 系统设计是系统开发最为关键的一环,系统设计好了,系统的实现以及以后的系统测 试都会节省很多的物力和财力。通过系统的设计,开发人员能够更好地把握系统的需求, 了解系统的各功能模块。 9.1.1 功能描述 家庭账务管理系统包括收支类型管理、收入信息管理、支出信息管理及统计查询管理。 详细的功能描述如下。 1. 收支类型管理 收支类型管理包括收入类型的管理和支出类型的管理。收入类型是指收入的来源,支 出类型是支出的方式。收入类型管理包括对收入类型的添加、修改和删除的操作。支出类 型管理包括对支出类型的添加、修改和删除的操作。 2. 收入信息管理 收入信息管理包括对收入信息的添加、修改、删除和日期查询的功能。收入信息主要 包括日期、收入类型、收入说明等信息。 3. 支出信息管理 支出信息管理包括对支出信息的添加、修改、删除和日期查询功能。支出信息主要包 括日期、支出类型、支出说明等信息。 4. 统计查询管理 统计查询包括收支统计查询和收支对比统计查询。收支统计查询是按照年度、月份、 日期列出收入、支出以及收支余额的信息。收支对比统计查询是按照年度和月份列出收入、 支出以及收支余额的对比信息。 9.1.2 功能模块设计 从上面的功能描述中,可以把家庭账务管理系统分为 4 个模块:收支类型管理、收入 — 308 — 信息管理、支出信息管理以及统计查询管理。在每一个模块下又提供了更为具体的功能。 详细的家庭账务管理系统的功能模块图,如图 9-1 所示。 图 9-1 系统功能模块图 9.2 数据库设计与实现 数据库设计是系统开发中非常重要的一个环节。数据库结构设计的好坏将直接影响到 系统的效率和功能的实现。在设计数据库之前,要了解数据库的需求,从而确定数据库的 结构。 9.2.1 数据库需求设计 通过以上的功能分析,家庭账务管理系统需要包含以下数据库信息。 1. 收入类型信息 包括收入类型 ID、收入类型。 2. 支出类型信息 包括支出类型 ID、支出类型。 3. 收入明细信息 包括收入明细 ID、日期、收入类型、收入说明。 4. 支出明细信息 包括支出明细 ID、日期、支出类型、支出说明。 9.2.2 数据库表的设计 通过数据库的需求,可以创建以下数据表。 — 309 — 1. 收入类型表(income_type_tab) 收入类型表包括收入类型信息。收入类型的管理实际上是对收入类型表的管理,表的 结构见表 9-1。 表 9-1 收入类型表 字段名称 数据类型 可否为空 约束条件 说 明 type_id INTEGER NOT NULL 主键 类型 ID 值 type_name VARCHAR2(24) NOT NULL 无 类型名称 2. 支出类型表(expense_type_tab) 支出类型表包括支出类型信息。支出类型的管理实际上是对支出类型表的管理,表的 结构见表 9-2。 表 9-2 支出类型表 字段名称 数据类型 可否为空 约束条件 说 明 type_id INTEGER NOT NULL 主键 类型 ID 值 Type_name VARCHAR2(24) NOT NULL 无 类型名称 3. 收入明细信息表(income_info_tab) 收入明细信息表包括详细的收入信息。收入信息管理实际上是对收入明细表的管理, 表的结构见表 9-3。 表 9-3 收入明细信息表 字段名称 数据类型 可否为空 约束条件 说 明 income_id INTEGER NOT NULL 主键 收入 ID 值 income_date DATE NOT NULL 无 收入日期 income_type VARCHAR2(20) NOT NULL 无 收入类型 description VARCHAR2(200) NULL 无 收入描述 4. 支出明细信息表(expense_info_tab) 支出明细信息表包括详细的支出信息。支出信息管理实际上是对支出信息表的管理, 表的结构见表 9-4。 表 9-4 支出明细信息表 字段名称 数据类型 可否为空 约束条件 说 明 expense_id INTEGER NOT NULL 主键 支出 ID 值 expense_date DATE NOT NULL 无 支出日期 expense_type VARCHAR2(20) NOT NULL 无 支出类型 description VARCHAR2(200) NULL 无 支出描述 — 310 — 9.2.3 数据库表的创建 利用第 3 章中讲述的方法创建表空间 dbaccount 和数据库用户 dbaccount,其中数据库 用户的密码为 dbaccount,选择的默认表空间为 dbaccount。 创建家庭账务管理系统所有数据表的 SQL 语句如下: --收入类型表 CREATE TABLE income_type_tab( type_id INTEGER PRIMARY KEY, type_name VARCHAR2(24) UNIQUE NOT NULL ); --支出类型表 CREATE TABLE expense_type_tab( type_id INTEGER PRIMARY KEY, type_name VARCHAR2(24) UNIQUE NOT NULL ); --收入明细信息表 CREATE TABLE income_info_tab( income_id INTEGER PRIMARY KEY, income_date DATE NOT NULL, income_type VARCHAR2(24), money float NULL, description VARCHAR2(1000) NULL ); --支出明细信息表 CREATE TABLE expense_info_tab( expense_id INTEGER PRIMARY KEY, expense_date DATE NOT NULL, expense_type VARCHAR2(24), money float NULL, description VARCHAR2(1000) NULL ); --创建可以递增的系列号供收入明细信息表的 income_id 使用 CREATE SEQUENCE seq_income_id INCREMENT BY 1 START WITH 2000 NOMAXVALUE NOMINVALUE NOCYCLE; --创建可以递增的系列号供支出明细信息表的 expense_id 使用 CREATE SEQUENCE seq_expense_id INCREMENT BY 1 START WITH 2000 NOMAXVALUE NOMINVALUE NOCYCLE; 利用 Oracle SQLPlus WorkSheet 工具执行上述的 SQL 语句从而创建数据库表。需要说 明的是,在打开 Oracle SQLPlus Worksheet 的“Oracle Enterprise Manager 登录”对话框的 时候,需要在“用户名”文本框中输入企业设备管理系统的用户名 dbaccount,在“口令” 文本框中输入用户密码 dbaccount,在“服务”文本框中输入数据库的本地服务名 ORADB, — 311 — 选择连接方式 Normal,登录成功之后,再运行上述的 SQL 语句。 然后添加一些收入类型和支出类型的数据,收入类型包括:工资、提成、分红和福利。 支出类型包括娱乐、住房、保健、食物、衣物和交通。插入这些数据的 SQL 语句如下: --添加收入类型数据 delete from income_type_tab; insert into income_type_tab(type_id,type_name) values(1,’工资’); insert into income_type_tab(type_id,type_name) values(2,’提成’); insert into income_type_tab(type_id,type_name) values(3,’分红’); insert into income_type_tab(type_id,type_name) values(4,’福利’); --添加支出类型数据 delete from expense_type_tab; insert into expense_type_tab(type_id,type_name) values(1,’娱乐’); insert into expense_type_tab(type_id,type_name) values(2,’住房’); insert into expense_type_tab(type_id,type_name) values(3,’保健’); insert into expense_type_tab(type_id,type_name) values(4,’食物’); insert into expense_type_tab(type_id,type_name) values(5,’衣物’); insert into expense_type_tab(type_id,type_name) values(6,’交通’); commit; 通常在编写完一个系统后,需要对整个系统进行测试,而测试就需要有测试数据,生 成这些测试数据有两种方法,一种是手动添加,一种是程序生成。利用手动添加测试数据, 可以提高数据的灵活性,但是要产生大量数据非常困难。通常情况下,如果要对系统进行 强度测试,数据量可以是上百万上千万的,手动添加这些数据是不可思议的。所以程序生 成数据是非常必要的,可以利用 Oracle PL/SQL 生成测试数据。Oracle 提供了使用方便的 PL/SQL,PL/SQL 是 Oracle 的程序设计语言,PL/SQL 补充了标准的关系数据库语言 SQL, 提供了各种过程化特性,包括循环、IF-THEN 语句、高级数据结构以及丰富的事务控制, 这些都紧密地集成到 Oracle 数据库服务器中,详细的语法可以参见 Oracle PL/SQL 的书籍。 利用 Oracle PL/SQL 生成支出信息和收入信息的测试数据的语句如下: declare i integer; j integer; k integer; l integer; m integer; dt DATE; description VARCHAR2 (100); num1 integer; num2 integer; --income_info_tab type c1 is table of income_info_tab.income_id%type index by binary_integer; type c2 is table of income_info_tab.income_date%type index by binary_integer; type c3 is table of income_info_tab.income_type%type index by binary_integer; type c4 is table of income_info_tab.money%type index by binary_integer; type c5 is table of income_info_tab.description%type index by — 312 — binary_integer; CL1 c1; CL2 c2; CL3 c3; CL4 c4; CL5 c5; --expense_info_tab type bc1 is table of expense_info_tab.expense_id%type index by binary_integer; type bc2 is table of expense_info_tab.expense_date%type index by binary_integer; type bc3 is table of expense_info_tab.expense_type%type index by binary_integer; type bc4 is table of expense_info_tab.money%type index by binary_integer; type bc5 is table of expense_info_tab.description%type index by binary_integer; BC_c1 bc1; BC_c2 bc2; BC_c3 bc3; BC_c4 bc4; BC_c5 bc5; begin num1 := 5; num2 := 365; --先删除数据表中的数据 delete from income_info_tab; delete from expense_info_tab; dt := to_date(’1999-07-01’,’yyyy-mm-dd’);--测试数据开始日期 commit; for i in 1..num1 loop --income_info_tab for j in 1..num2 loop --构造 income_info_tab 表的一条记录 k := (i-1)*num2+j; BC_c1(j):= k; BC_c2(j):= dt + k; l := round(ABS(SIN(k*k*k*k))*10000); --构造随机数据 m := MOD(l,4); if m = 0 then BC_c3(j):= ’工资’; elsif m = 1 then BC_c3(j):= ’提成’; elsif m = 2 then BC_c3(j):= ’分红’; elsif m = 3 then BC_c3(j):= ’福利’; end if; BC_c4(j):= MOD(l,100);--构造余额 description := concat(’今天的’,BC_c3(j)); description := concat(description,’收入是’); — 313 — description := concat(description,to_char(MOD(l,100))); BC_c5(j):= concat(description,’元’); end loop; forall j in 1..num2 --向 income_info_tab 表插入记录 insert into income_info_tab values( BC_c1(j),BC_c2(j),BC_c3(j),BC_c4(j),BC_c5(j)); commit; --expense_info_tab for j in 1..num2 loop k := (i-1)*num2+j; BC_c1(j):= k; BC_c2(j):= dt + k; l := round(ABS(COS(k*k*k*k))*10000); --构造随机数据 m := MOD(l,6); if m = 0 then BC_c3(j):= ’娱乐’; elsif m = 1 then BC_c3(j):= ’住房’; elsif m = 2 then BC_c3(j):= ’保健’; elsif m = 3 then BC_c3(j):= ’食物’; elsif m = 4 then BC_c3(j):= ’衣物’; elsif m = 5 then BC_c3(j):= ’交通’; end if; BC_c4(j):= MOD(l,100); description := concat(’今天的’,BC_c3(j)); description := concat(description,’支出是’); description := concat(description,to_char(MOD(l,100))); BC_c5(j):= concat(description,’元’); end loop; forall j in 1..num2 insert into expense_info_tab values( BC_c1(j),BC_c2(j),BC_c3(j),BC_c4(j),BC_c5(j)); commit; end loop; end; 利用 Oracle SQLPlus WorkSheet 工具执行上述的语句,将产生 5 年的收入信息和支出 信息数据。每天产生一条收入信息和支出信息。以收入信息的生成数据为例,一共产生了 1825(5×365)条数据。为了尽量模拟真实的数据,需要产生一些随机值,上述语句利用 ABS 取绝对值函数、SIN 取正弦函数、round 函数求整和 MOD 求余函数来构造随机值来确定收 入类型和收入金额的数据,如随机产生的收入类型“福利”,随机金额 79 元,那么收入说 明为“今天的福利收入是 79 元”。 — 314 — 9.3 系统的实现 完成了系统功能模块的设计和数据库表的创建,就可以创建一个家庭账务管理系统。 9.3.1 创建应用程序 运行 Visual C++,选择 File|New 命令,弹出 New 对话框。从 Projects 列表中选择 MFC AppWizard(exe)向导,在 Location 文本框中选择项目工程的目录“D:\VCSAMPLE\”,在 Project name 文本框中输入工程文件的名称 AccountDBS,单击 OK 按钮,进入 MFC AppWizard – Step 1 页面,从应用程序的类型中选择 Dialog based 选项,从语言列表中选择 “中文(中国)(APPWZCHS.DLL)”,单击 Next 按钮,进入 MFC AppWizard – Step 2 of 4 页 面,选择 Automation 选项,AccountDBS 对话框应用程序就创建完毕了。 9.3.2 创建主对话框的界面 主对话框的布局如图 9-2 所示。其中包括收支类型管理、收入信息管理、支出信息管 理和统计查询管理 4 个部分。 图 9-2 “家庭账务管理系统”对话框 — 315 — 1. 收支类型管理 控件类型、ID 及说明见表 9-5。 表 9-5 收支类型管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 收支类型管理 无 MS ADO Data Control IDC_ADODC1 Adodc1 无 MS ADO Data Control IDC_ADODC2 Adodc2 无 MS DataGrid Control IDC_DATAGRID1 收入类型管理 CDataGrid 类型变量 m_dgIncomeTypeCtrl MS DataGrid Control IDC_DATAGRID2 支出类型管理 CDataGrid 类型变量 m_dgExpenseTypeCtrl 2. 收入信息管理 控件类型、ID 及说明见表 9-6。 表 9-6 收入信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 收入信息管理 无 Label IDC_STATIC 日期 无 Date Time Picker IDC_DT_INCOME 无 COleDateTime 类型变量 m_oleIncome Label IDC_STATIC 收入类型 无 MS DataCombo Control IDC_DATACOMBO_INCOME 无 CDataCombo 类型变量 m_dcIncome Label IDC_STATIC 收入金额(¥) 无 Edit Box IDC_EDIT_INCOME_MONEY 无 double 类型变量 m_dbIncomeMoney Label IDC_STATIC 收入说明 无 Edit Box IDC_EDIT_INCOME 无 CString 类型变量 m_strIncome MS DataGrid Control IDC_DATAGRID3 收入详细信息 列表 CDataGrid 类型变量 m_dgIncomeCtrl Button IDC_BTN_INCOME_ADD 添加 函数 OnBtnIncomeAdd ()修改收入信息 Button IDC_BTN_INCOME_DEL 删除 函数 OnBtnIncomeDel ()删除收入信息 Button IDC_BTN_INCOME_QUERY 日期查询 函数 OnBtnIncomeQuery ()根据日前查询收 入信息 MS ADO Data Control IDC_ADODC3 Adodc3 CAdodc 类型变量 m_adoIncomeDataCtrl 3. 支出信息管理 控件类型、ID 及说明见表 9-7。 表 9-7 支出信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 支出信息管理 无 Label IDC_STATIC 日期 无 Date Time Picker IDC_DT_EXPENSE 无 COleDateTime 类型变量 m_oleMeetDate Label IDC_STATIC 支出类型 无 MS DataCombo Control IDC_DATACOMBO_EXPENSE 无 无 Label IDC_STATIC 支出金额(¥) 无 Edit Box IDC_EDIT_EXPENSE_MONEY 无 CString 类型变量 m_strName Label IDC_STATIC 支出说明 无 — 316 — (续表) 控件类型 ID 属 性 变量或函数 Edit Box IDC_EDIT_EXPENSE 无 CString 类型变量 m_strName MS DataGrid Control IDC_DATAGRID4 支出详细信息列表 CDataGrid 类型变量 m_dgExpenseCtrl Button IDC_BTN_EXPENSE_ADD 添加 函数 OnBtnExpenseAdd ()添加支出信息 Button IDC_BTN_EXPENSE_DEL 删除 函数 OnBtnExpenseDel ()删除支出信息 Button IDC_BTN_EXPENSE_QUERY 日期查询 函数 OnBtnExpenseQuery ()查询支出信息 MS ADO Data Control IDC_ADODC4 Adodc4 CAdodc 类型变量 m_adoExpenseDataCtrl 4. 统计查询管理 控件类型、ID 及说明见表 9-8。 表 9-8 统计查询管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 工作成果信息管理 无 Button IDC_BTN_ACCOUNT_QUERY 收支统计查询 函数 OnBtnAccountQuery ()收支统计查询 Button IDC_BTN_ACCOUNT_COMPARE_QUERY 收支对比统计查询 函数 OnBtnAccountCompareQuery ()收支 对比统计查询 Button IDC_SYS_EXIT 系统退出 函数 OnSysExit ()系统退出 在添加数据控件和数据绑定控件变量的时候,可以利用 ClassWizard,系统会自动添加 相关的控件类。以 ADO Data 控件为例,利用 ClassWizard 添加 ADO Data 控件变量的时候, 会自动弹出生产 ADO Data 控件类的提示框,如图 9-3 所示。 图 9-3 添加控件类提示对话框 单击“确定”按钮,弹出 Confirm Classes 对话框,提示要添加控件类,如图 9-4 所示。 图 9-4 Confirm Classes 对话框 — 317 — 单击 OK 按钮,这些控件类就自动生成并添加到项目中。有了这些控件类就可以添加 ADO 控件变量。按照相同的方法,可以添加 MS DataCombo 和 MS DataGrid 的控件变量。 为了在主界面上显示这些 ActiveX 控件,必须保证 4 个 DataGrid 控件和两个 DataCombo 控件的 Tab Order 顺序连续,并从 1 开始,如图 9-5 所示。 图 9-5 Tab Order 顺序对话框 这几个控件的 Tab Order 顺序为 1、2、3、4、5、6。如果不是这个顺序,将不会正常 显示这些控件。4 个 ADO Data 控件不需要显示出来,去掉它们的 Visible 属性。 9.3.3 显示数据到界面上 当系统启动之后,可以将数据库中的数据显示到界面上。设置完 ADO 数据控件和数 据绑定控件的属性之后,数据库中的数据就可以显示到数据绑定控件中。主界面用了 4 个 Microsoft ADO Data 数据控件、两个 Microsoft DataCombo 和 4 个 Microsoft DataGrid 数据 绑定控件,现在分别介绍这些控件的属性设置。 1. Microsoft ADO Data 数据控件 4 个数据控件属性的连接字符串 ConnectionString 属性都是“Provider=OraOLEDB. Oracle.1;Password=dbaccount;Persist Security Info=True;User ID=dbaccount;Data Source= ORADB”,属性框的 Control 选项卡下的设置如图 9-6 所示。 — 318 — 图 9-6 数据连接对话框 这 4 个控件不同的是属性框的 RecordSource 选项卡下的设置,设置方法如下。 选择 IDC_ADODC1 属性框的 RecordSource 选项卡,在 Command Type 列表框中选择 2 - adCmdTable,在 Table or Stored Procedure Name 列表框中选择表名 INCOME_TYPE_ TAB。 选择 IDC_ADODC2 属性框的 RecordSource 选项卡,在 Command Type 列表框中选择 2 - adCmdTable,在 Table or Stored Procedure Name 列表框中选择表名 EXPENSE_TYPE_ TAB。 选择 IDC_ADODC3 属性框的 RecordSource 选项卡,在 Command Type 列表框中选择 1 - adCmdText,在 Command Text 文本框中输入“select INCOME_ID,TO_CHAR(INCOME_ DATE,’yyyy-mm-dd’),INCOME_TYPE, MONEY, DESCRIPTION from INCOME_INFO_TAB ORDER BY INCOME_ID”。 选择 IDC_ADODC4 属性框的 RecordSource 选项卡,在 Command Type 列表框中选择 1 - adCmdText,在 Command Text 文本框中输入“select EXPENSE_ID,TO_CHAR(EXPENSE_ DATE,’yyyy-mm-dd’),EXPENSE_TYP E,MONEY, DESCRIPTION from EXPENSE_INFO_ TAB ORDER BY EXPENSE_ID”。 2. Microsoft DataGrid 数据绑定控件 Microsoft DataGrid 数据绑定控件常用来显示数据表的字段信息。主界面一共用了 4 个 Microsoft DataGrid 控 件 。 其 中 IDC_DATAGRID1 用来显示收入类型数据。打开 IDC_DATAGRID1 的属性框,选择 All 选项卡,在 DataSource 列表框中选择 IDC_ADODC1, 在 Caption 文本框中输入“收入类型管理”,在 AllowAddNew(允许界面上添加数据)列表框 中选择 True,在 AllowDelete(允许界面上删除数据)列表框中选择 True,在 AllowUpdate(允 许界面上更新数据)列表框中选择 True,这样就可以直接在 IDC_DATAGRID1 控件上添加、 修改和删除收入类型,如图 9-7 所示。 选择 Columns 选项卡,设置 DataGrid 控件显示列的名称和对应的字段。对于只有两个 字段的数据表来说,利用 Columns 属性选项卡设置很方便。如果表的字段个数超过两个, 可以使用 DataGrid 控件变量设置每一个显示列的名称。在 Column 列表框中选择 Column 0, 在 Caption 文本框中输入“类型 ID”,在 DataField 列表框中选择 TYPE_ID,如图 9-8 所示。 — 319 — 图 9-7 All 选项卡 图 9-8 Columns 选项卡 在 Column 列表框中选择 Column 1,在 Caption 文本框中输入“类型名称”,在 DataField 列表框中选择 TYPE_ NAME,如图 9-9 所示。 图 9-9 Columns 选项卡 — 320 — IDC_DATAGRID2 控件用来显示支出类型数据。打开 IDC_DATAGRID2 的属性框,选 择 All 选项卡,在 DataSource 列表框中选择 IDC_ADODC2,在 Caption 文本框中输入“支 出类型管理”,其他参数同 IDC_DATAGRID1。 利用IDC_DATAGRID1和IDC_DATAGRID2的控件变量设置控件列的宽度,代码如下: //设置收入类型 DataGrid 控件的列项宽度 CColumns columns1 = m_dgIncomeTypeCtrl.GetColumns(); _variant_t index; index.vt = 2; index.iVal = 0 ; columns1.GetItem(index).SetWidth(50); index.iVal = 1 ; columns1.GetItem(index).SetWidth(110); //设置支出类型 DataGrid 控件的列项名称和宽度 CColumns columns2 = m_dgExpenseTypeCtrl.GetColumns(); index.vt = 2; index.iVal = 0 ; columns2.GetItem(index).SetWidth(50); index.iVal = 1 ; columns2.GetItem(index).SetWidth(110); 把这段代码放在 CAccountDlg 类的 OnInitDialog 函数中,系统启动的时候就可以设置 这两个 DataGrid 控件的列宽。代码中使用 CDataGrid 类的 GetColumns 函数获取 CColumns 显示列,然后使用 CColumns 列的 SetWidth 函数设置显示列的宽度,列号从 0 开始。由于 SetWidth 函数只接收 VARIANT 类型的数据,所以需要把序号赋给 VARIANT 类型的变量 index。 编译并运行程序,就可以在这两个 DataGrid 控件中显示收入类型和支出类型数据,如 图 9-10 所示。 图 9-10 “收支类型管理”对话框 IDC_DATAGRID3 控件用来显示收入信息数据。打开 IDC_DATAGRID3 的属性框,选 择 All 选项卡,在 DataSource 列表框中选择 IDC_ADODC3,在 Caption 文本框中输入“收 入详细信息列表”。IDC_DATAGRID4 控件用来显示支出信息数据。打开 IDC_DATAGRID4 的属性框,选择 All 选项卡,在 DataSource 列表框中选择 IDC_ADODC4,在 Caption 文本 框中输入“支出详细信息列表”。再设置这两个在控件界面上修改数据的属性,选择 All 选项卡,在 AllowAddNew 列表框中选择 FALSE,在 AllowDelete 列表框中选择 FALSE, 在 AllowUpdate 列表框中选择 True,这样只能在 DataGrid 控件界面上修改数据,而不能添 加和删除数据。 — 321 — 在 CAccountDlg 类中定义一个 InitControl 函数,负责设置 IDC_DATAGRID3 和 IDC_DATAGRID4 控件显示列的宽度和名称,代码如下: void CAccountDBSDlg::InitControl() { //设置收入和支出信息 DataGrid 控件项的名称和项的宽度 //首先需要获取列项类,然后设置名称和宽度 CColumns columns1 = m_dgIncomeCtrl.GetColumns(); _variant_t index; index.vt = 2; index.iVal = 0 ; columns1.GetItem(index).SetCaption("收入 ID"); columns1.GetItem(index).SetWidth(50); index.iVal = 1 ; columns1.GetItem(index).SetCaption("日期"); columns1.GetItem(index).SetWidth(60); index.iVal = 2 ; columns1.GetItem(index).SetCaption("类型"); columns1.GetItem(index).SetWidth(50); index.iVal = 3 ; columns1.GetItem(index).SetCaption("金额"); columns1.GetItem(index).SetWidth(50); index.iVal = 4 ; columns1.GetItem(index).SetCaption("说明"); columns1.GetItem(index).SetWidth(127); CColumns columns2 = m_dgExpenseCtrl.GetColumns(); index.vt = 2; index.iVal = 0 ; columns2.GetItem(index).SetCaption("支出 ID"); columns2.GetItem(index).SetWidth(50); index.iVal = 1 ; columns2.GetItem(index).SetCaption("日期"); columns2.GetItem(index).SetWidth(60); index.iVal = 2 ; columns2.GetItem(index).SetCaption("类型"); columns2.GetItem(index).SetWidth(50); index.iVal = 3 ; columns2.GetItem(index).SetCaption("金额"); columns2.GetItem(index).SetWidth(50); index.iVal = 4 ; columns2.GetItem(index).SetCaption("说明"); columns2.GetItem(index).SetWidth(127); } 把 InitControl 函数放在 CAccountDlg 类的 OnInitDialog 函数中,编译并运行,将在这 两个 DataGrid 控件中显示收入和支出的详细信息,如图 9-11 所示。 — 322 — 图 9-11 收入信息和支出信息对话框 3. Microsoft DataCombo 数据绑定控件 主界面中用了两个 Microsoft DataCombo 控件,方便用户选择收入类型和支出类型。 在收支类型管理中如果对类型名称进行添加、修改和删除后,收支类型 DataCombo 框的数 据也需要更新,因此把收入类型 DataCombo 控 件 IDC_DATACOMBO_INCOME 的 RowSource 属性设置为 IDC_ADODC1,ListField 属性为 TYPE_NAME,如图 9-12 所示。 图 9-12 All 选项卡 编译并运行,选择收入类型 DataCombo 框,将在其中列出所有的收入类型,如图 9-13 所示。 图 9-13 收入类型 DataCombo 框对话框 — 323 — 在列表中可以看到收入类型:工资、提成、分红和福利,这和收入类型管理中列出的 收入类型是相同的,这就是为什么 IDC_DATACOMBO_INCOME 的 RowSource 属性值和 IDC_DATAGRID1 的 DataSource 属性值都设置为 IDC_ADODC1 的原因,能保证两个控件 的数据同步。 以同样的方法,设置 DataCombo 控件 IDC_DATACOMBO_EXPENSE 属性框的 All 选 项卡下的 RowSource 属性值为 IDC_ADODC2,ListField 属性值为 TYPE_NAME。编译并 运行,将在 DataCombo 控件 IDC_DATACOMBO_EXPENSE 中显示所有的支出类型数据, 如图 9-14 所示。 图 9-14 支出类型 DataCombo 框对话框 至此,设置完主界面上的 ADO 数据控件和数据绑定控件,运行程序,如图 9-15 所示 显示数据库中的信息。 图 9-15 系统对话框 — 324 — 另外,家庭账务管理系统也需要处理数据库的连接,从而可以利用 ADO 的连接对象 和记录集对象访问数据库。ADO 数据库的连接需要 3 个重要的参数:数据库数据源的名称、 数据库用户名称和数据库用户密码。其中数据库数据源的名称就是本地服务名 ORADB, 数据库用户的名称和密码均为 dbaccount。数据库连接的处理同第 6 章,不再赘述。 9.3.4 收支类型管理 收支类型管理包括对收入类型的管理和支出类型的管理。包括对收支类型的添加、修 改和删除操作,可以直接在收入类型和支出类型的 DataGrid 控件上添加、修改和删除数据, 数据库的数据会自动更新。 1. 添加收入类型 收入类型数据表只有两个字段,直接在收入类型 DataGrid 控件上添加收入类型的数据 是最方便的。单击收入类型 DataGrid 控件的左侧,会出现*号,如图 9-16 所示。 图 9-16 收入类型管理对话框 在*号右侧的空行中添加一个新的收入类型,类型 ID 为 5,类型名称为“奖金”,如图 9-17 所示。 图 9-17 添加收入类型对话框 单击类型 ID 为 5 的左侧*号,数据就自动添加到数据库中,而且会出现一个新的空行, 供用户添加,如图 9-18 所示。 图 9-18 添加收入类型之后的对话框 查看收入信息管理中的收入类型 DataCombo 框的数据,刚才添加的收入类型“奖金” 也在 DataCombo 框中显示,如图 9-19 所示。 — 325 — 图 9-19 收入类型 DataCombo 框对话框 支出类型的添加操作也是一样的。 2. 修改收入类型 要修改收入类型,只需在收入类型 DataGrid 控件上修改数据就可以了,修改后的数据 会自动更新到数据库中。如要修改刚才添加的收入类型“奖金”,只需将光标定位在“奖金” 选项,修改为“年度奖金”,如图 9-20 所示。 图 9-20 修改收入类型对话框 选择收入信息管理中的收入类型 DataCombo 框,从收入类型 DataCombo 框中可以看 到收入类型“年度奖金”,如图 9-21 所示。 图 9-21 收入类型 DataCombo 框对话框 支出类型的修改操作也是一样的。 3. 删除收入类型 要删除收入类型,只需在收入类型 DataGrid 控件中选择要删除的收入类型,然后按 Delete 键,就可以删除收入类型数据。如选择收入类型“年度奖金”一行的左侧,出现一 个三角号,并自动选择整行,如图 9-22 所示。 — 326 — 图 9-22 删除收入类型对话框 按下 Delete 键,收入类型“年度奖金”就删除了,如图 9-23 所示。 图 9-23 删除收入类型之后对话框 选择收入信息管理中的收入类型 DataCombo 框,收入类型 DataCombo 框中没有“年 度奖金”的收入类型,如图 9-24 所示。 图 9-24 收入类型 DataCombo 框对话框 支出类型的删除操作也是一样的。 9.3.5 收入信息管理 收入信息管理包括对收入信息的添加、修改、删除和日期查询的功能。 1. 添加收入信息 添加收入信息的函数为 OnBtnIncomeAdd,代码如下: void CAccountDBSDlg::OnBtnIncomeAdd() { // TODO: Add your control notification handler code here if(!UpdateData()) return; CString txtType = m_dcIncome.GetText(); if(txtType.IsEmpty()){ AfxMessageBox("类型不能够为空"); return; } — 327 — //获取收入时间 CString strDate = m_oleIncome.Format("%Y-%m-%d") ; try { m_pRecordset->Open("Select seq_income_id.NEXTVAL from dual" ,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText); int id = 1; if(!m_pRecordset->adoEOF) { _variant_t TheValue; //从系列中获取新的收入 ID 值。 TheValue = m_pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; } m_pRecordset->Close(); _variant_t RecordsAffected; //插入新的收入信息记录. CString sql; sql.Format("insert into income_info_tab(income_id," "income_date,income_type,money," "description) values(" "%d," "to_date(’%s’,’yyyy-mm-dd’)," "’%s’,%.2f,’%s’)",id,strDate,txtType, m_dbIncomeMoney,m_strIncome); TRACE(sql); m_pConnection->Execute(_bstr_t(sql),&RecordsAffected,adCmdText); //刷性 ADO 数据控件的数据,从而更新数据显示 m_adoIncomeDataCtrl.Refresh(); InitControl(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } OnBtnIncomeAdd 函数利用 ADO 连接对象的 Execute 方法向数据库添加数据,最后调 用 ADO Data 控件的 Refresh 方法刷新 IDC_ADODC3 连接的数据,同时会自动更新与之绑 定的 DataGrid 控件 IDC_DATAGRID3 中的显示数据,再次调用 InitControl 函数是为了设置 显示列的宽度和名称,因为调用 ADO Data 控件的 Refresh 方法刷新数据的时候,与之绑定 的 DataGrid 控件会按照默认的方式显示数据,需要再次设置显示列的宽度和名称。 如在收入信息管理中的“日期”时间控件选择 2004-7-14,在“收入类型”DataCombo 框中选择“分红”,在“收入金额”文本框中输入 200 元,在“收入说明”文本框中输入“今 天的分红收入为 200 元”,如图 9-25 所示。 — 328 — 图 9-25 添加收入信息页面 单击“添加”按钮,新的收入信息就添加到“收入详细信息列表”DataGrid 控件中, 如图 9-26 所示。 图 9-26 添加收入信息之后的对话框 2. 修改收入信息 修改收入信息的操作同收支类型管理中的修改操作,只需在“收入详细信息列表” DataGrid 控件中修改就可以了。 3. 删除收入信息 删除收入信息的函数为 OnBtnIncomeDel,代码如下: void CAccountDBSDlg::OnBtnIncomeDel() { //定位在所选择行的第一列。 m_dgIncomeCtrl.SetCol(0); //获取所选择行的收入信息 ID 数据. int id = atoi(m_dgIncomeCtrl.GetText()); try { _variant_t RecordsAffected; //删除此收入信息 ID 的记录. CString sql; sql.Format("delete from income_info_tab " "where income_id = %d",id); TRACE(sql); — 329 — m_pConnection->Execute(_bstr_t(sql),&RecordsAffected,adCmdText); //刷新 ADO 数据控件,从而重新更新数据 m_adoIncomeDataCtrl.Refresh(); //调用 InitControl 是为了设定项的名称和宽度,否则项的名称为默认的 InitControl(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } OnBtnIncomeDel 函数调用 DataGrid 控件的 SetCol 方法设定选择的文本为 DataGrid 控 件的第一列,然后调用 GetText 方法获取要删除的收入 ID。删除收入信息使用连接对象的 Execute 方法,删除数据之后同样需要调用 ADO Data 控 件 的 Refresh 方 法 刷 新 IDC_ADODC3 连接的数据,并自动刷新与之绑定的 DataGrid 控件 IDC_DATAGRID3 中的 显示数据。 4. 日期查询 日期查询提供了按照输入的日期查询当天所有收入信息的功能。如果要查询某一天所 有的收入信息,只需构造此日期下所有收入信息的 SQL 语句,调用 ADO 数据控件的 SetRecordSource 方法把这个 SQL 语句赋给 IDC_ADODC3 的记录集源。然后调用 ADO Data 控件的 Refresh 方法刷新数据,与之绑定的 DataGrid 控件的显示数据也会相应地更新。 void CAccountDBSDlg::OnBtnIncomeQuery() { // TODO: Add your control notification handler code here if(!UpdateData()) return; //构造日期查询的 SQL CString sql ; sql.Format("select INCOME_ID,TO_CHAR(INCOME_DATE,’yyyy-mm-dd’),INCOME_TYPE," "MONEY,DESCRIPTION from INCOME_INFO_TAB " "where income_date = to_date(’%s’,’yyyy-mm-dd’) ORDER BY INCOME_ID", m_oleIncome.Format("%Y-%m-%d") ); TRACE(sql); //重新设置 ADO 数据控件的记录集的源 m_adoIncomeDataCtrl.SetRecordSource(sql); //刷性 ADO 数据控件的数据,从而更新数据显示 m_adoIncomeDataCtrl.Refresh(); InitControl(); } 如在收入类型管理中的“日期”时间控件输入 2004-6-29,单击“日期查询”按钮,将 列出 2004-6-29 所有的收入详细信息,如图 9-27 所示。 — 330 — 图 9-27 收入信息日期查询对话框 9.3.6 支出信息管理 支出信息管理包括对支出信息的添加、修改、删除和日期查询的功能。 1. 添加支出信息 添加支出信息的函数为 OnBtnExpenseAdd,代码如下: void CAccountDBSDlg::OnBtnExpenseAdd() { // TODO: Add your control notification handler code here if(!UpdateData()) return; CString txtType = m_dcExpense.GetText(); if(txtType.IsEmpty()){ AfxMessageBox("类型不能够为空"); return; } //获取支出时间 CString strDate = m_oleExpense.Format("%Y-%m-%d") ; try { m_pRecordset->Open("Select seq_expense_id.NEXTVAL from dual" ,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText); int id = 1; if(!m_pRecordset->adoEOF) { _variant_t TheValue; //从系列中获取新的支出 ID 值. TheValue = m_pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) id = TheValue.iVal; } m_pRecordset->Close(); _variant_t RecordsAffected; //插入新的支出信息记录. CString sql; — 331 — sql.Format("insert into expense_info_tab(expense_id," "expense_date,expense_type,money," "description) values(" "%d," "to_date(’%s’,’yyyy-mm-dd’)," "’%s’,%.2f,’%s’)",id,strDate,txtType, m_dbExpenseMoney,m_strExpense); TRACE(sql); m_pConnection->Execute(_bstr_t(sql),&RecordsAffected,adCmdText); //刷性 ADO 数据控件的数据,从而更新数据显示 m_adoExpenseDataCtrl.Refresh(); InitControl(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 处理方法同收入信息的添加操作。 如在支出信息管理中的“日期”时间控件中选择 2004-6-29,在“支出类型”DataCombo 框中选择“交通”,在“支出金额”文本框中输入 20 元,在“支出说明”文本框中输入“今 天的交通支出为 20 元”,如图 9-28 所示。 图 9-28 添加支出信息对话框 单击“添加”按钮,新的支出信息就添加到“支出详细信息列表”DataGrid 控件中, 如图 9-29 所示。 图 9-29 添加支出信息之后的对话框 — 332 — 2. 修改支出信息 修改支出信息的操作同收支类型管理中的修改操作一样,只需在“支出详细信息列表” DataGrid 控件中修改就可以了。 3. 删除支出信息 删除支出信息的函数为 OnBtnExpenseDel,代码如下: void CAccountDBSDlg::OnBtnExpenseDel() { // 定位在选择行的第一列 m_dgExpenseCtrl.SetCol(0); //该行的支出 ID. int id = atoi(m_dgExpenseCtrl.GetText()); try { _variant_t RecordsAffected; //删除该 ID 的支出信息记录. CString sql; sql.Format("delete from expense_info_tab " "where expense_id = %d",id); TRACE(sql); //刷性 ADO 数据控件的数据,从而更新数据显示 m_pConnection->Execute(_bstr_t(sql),&RecordsAffected,adCmdText); m_adoExpenseDataCtrl.Refresh(); InitControl(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } } 4. 日期查询 日期查询提供了按照输入的日期查询当天所有支出信息的功能。如果要查询某一天所 有的支出信息,只需要构造此日期下所有支出信息的 SQL 语句,调用 ADO 数据控件的 SetRecordSource 方法把这个 SQL 语句赋给 IDC_ADODC4 的记录集源。然后调用 ADO Data 控件的 Refresh 方法刷新数据,与之绑定的 DataGrid 控件的显示数据也会相应的更新。代 码如下: void CAccountDBSDlg::OnBtnExpenseQuery() { // TODO: Add your control notification handler code here if(!UpdateData()) return; CString sql ; //构造日前查询的 SQL sql.Format("select EXPENSE_ID,TO_CHAR(EXPENSE_DATE,’yyyy-mm-dd’),EXPENSE_TYPE," — 333 — "MONEY,DESCRIPTION from EXPENSE_INFO_TAB " "where expense_date = to_date(’%s’,’yyyy-mm-dd’) ORDER BY EXPENSE_ID", m_oleExpense.Format("%Y-%m-%d") ); TRACE(sql); //刷性 ADO 数据控件的数据,从而更新数据显示 m_adoExpenseDataCtrl.SetRecordSource(sql); m_adoExpenseDataCtrl.Refresh(); InitControl(); } 如在支出信息管理中的“日期”时间控件中输入 2004-6-29,单击“日期查询”按钮, 将列出 2004-6-29 所有的支出详细信息,如图 9-30 所示。 图 9-30 “支出信息管理”对话框 9.3.7 统计查询管理 统计查询包括收支统计查询、收支对比统计查询,利用 MS Chart 控件能非常方便地显 示这些统计信息。 1. 收支统计查询 收支统计查询是按照年度、月份和日期列出收入、支出,以及收支余额的信息。为了 显示这些信息的方便,可以创建一个对话框,对话框类名称为 CACStatQuery,资源 ID 为 IDD_DIALOG_AC_STAT_QUERY,对话框名称为“收支查询信息”,“收支查询信息”对 话框如图 9-31 所示。 图 9-31 “收支查询信息”对话框 — 334 — 控件类型、ID 及说明见表 9-9。 表 9-9 控件列表 控件类型 ID 属 性 变量或函数 Label IDC_STATIC 选 择 查 询 的 日 期 无 Date Time Picker IDC_DT_INCOME 无 COleDateTime 类型变量 m_oleIncome Button IDC_BTN_YEAR_QUERY 年度查询 函数 OnBtnYearQuery ()根据选择的年度查询统计信息 Button IDC_BTN_MONTH_QUERY 月份查询 函数 OnBtnMonthQuery ()根据选择的月份查询统计信 息 Button IDC_BTN_DAY_QUERY 日期查询 函数 OnBtnDayQuery ()根据选定的日期查询统计信息 Button IDOK 退出 无 MS Chart Control IDC_MSCHART1 无 CMSChart 类型变量 m_chart 添加 MS Chart 控件变量的方法和前面讲述的添加 ADO 数据绑定控件是相同的,会自 动添加 MS Chart 相关的控件类。MS Chart 可以不绑定 ADO 数据绑定控件,利用控件变量 添加显示的数据。打开 IDC_ MSCHART1 属性框,选择 All 选项卡,在 RowCount(柱状图 的个数)文本框中输入 1,在 ColumnCount(柱状图列的个数)文本框中输入 3,如图 9-32 所 示。 图 9-32 All 选项卡 选择 Chart 选项卡,选择 Chart Type 为 2D、Bar/Pictograph,在 Chart Options 选项组中 选中 Show legend 单选按钮,如图 9-33 所示。 图 9-33 Chart 选项卡 — 335 — 为了直接在 CACStatQuery 类中操作数据库,需要定义一个公有的连接对象的指针, 代码如下: public _ConnectionPtr m_pConnection; 在 CAccountDBSDlg 类中的 OnBtnAccountQuery 函数中添加弹出“收支查询信息”对 话框和传入数据库连接对象指针的代码,代码如下: void CAccountDBSDlg::OnBtnAccountQuery() { //构造收支查询对话框的实例 CACStatQuery dlg; //传入数据库连接指针 dlg.m_pConnection = m_pConnection; //打开收支查询对话框 dlg.DoModal(); } 收支统计查询信息包括收入信息、支出信息和收支余额信息。收支余额信息是收入和 支出的差额。可以在弹出“收支查询信息”对话框中首先显示用户总的收支统计信息。收 支余额值可正可负,如果为负的话,表示支出的钱超过收入的钱了,也就是“入不敷出”。 收支统计查询需要获取收入或支出的总额信息,可以定义一个私用函数 GetTotalMoneyBySql,负责根据传入的 SQL 语句获取总额信息,代码如下: double CACStatQuery::GetTotalMoneyBySql(CString sql) { double money = 0; try { _RecordsetPtr pRecordset; pRecordset.CreateInstance(_uuidof(Recordset)); pRecordset->Open(_bstr_t(sql),(IDispatch*)m_pConnection,adOpenDynamic,a dLockOptimistic,adCmdText); TRACE(sql); //获取总的金额值 if(!pRecordset->adoEOF) { _variant_t TheValue; TheValue = pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) money = TheValue; } pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } return money; } — 336 — 获取收入和支出的总额信息之后,需要向 MS Chart 控件中添加数据。定义一个 SetChartData 私有函数,负责向 MS Chart 控件中插入总收入、总支出和总的收支余额信息, 并设定柱状图中显示列的名称,这 3 个列的名称是总收入金额、总支出金额和收支余额信 息,代码如下: void CACStatQuery::SetChartData(double income,double expense) { //设定一组柱状图的列的个数为 3 个 m_chart.SetColumnCount(3); //设置柱状图上的数据 m_chart.GetDataGrid().SetData(1, 1, income, (short)0); m_chart.GetDataGrid().SetData(1, 2, expense, (short)0); m_chart.GetDataGrid().SetData(1, 3, income-expense, (short)0); //设置柱状图上列的名称,包含收支信息和收支余额 m_chart.SetColumn(1); CString temp; temp.Format("总收入(%.2f 元)",income); m_chart.SetColumnLabel(temp); m_chart.SetColumn(2); temp.Format("总支出(%.2f 元)",expense); m_chart.SetColumnLabel(temp); m_chart.SetColumn(3); temp.Format("收支余额(%.2f 元)",income-expense); m_chart.SetColumnLabel(temp); } SetChartData 函数首先调用 MS Chart 控件类 CMSChart 的 SetColumnCount 函数设置一 组柱状图包含的显示列个数,如果不明确设置,会出现越界的危险,然后设置显示的数据 和列的名称。SetData 函数的第 1 个参数是柱状图的系列号(从 1 开始),目前只有一组柱状 图,故为 1;第 2 个参数是柱状图中列的系列号(从 1 开始),第 3 个参数是要显示的数据; 第 4 个参数默认为 0。最后调用 SetColumn 函数和 SetColumnLabel 设定显示列的名称。 在 CACStatQuery 类中定义一个私有函数 InitChartData,负责在 MS Chart 控件中显示 总的收入、支出和收支余额信息,代码如下: void CACStatQuery::InitChartData() { //获取总的收支信息 double totalIncome = GetTotalMoneyBySql("select sum(money) from income_info_tab"); double totalExpense = GetTotalMoneyBySql("select sum(money) from expense_info_tab"); //在 MS Chart 显示数据 SetChartData(totalIncome,totalExpense); m_chart.SetRowLabel("总的收支统计信息"); m_chart.Refresh(); } InitChartData 函数最后调用 SetRowLabel 函数设定柱状图下的名称,并调用 CMSChart — 337 — 类的 Refresh 函数刷新 MS Chart 上的数据。 在主界面上单击“收支统计查询”按钮,弹出“收支查询信息”对话框,在对话框中 列出总收入、总支出和总收支余额信息,如图 9-34 所示。 图 9-34 总的收支信息对话框 从对话框中可以看出总收入为 92 714.00 元,总支出为 91 136.00 元,还有余额 1578.00 元。 按照年度查询的函数为 OnBtnYearQuery。 void CACStatQuery::OnBtnYearQuery() { if(!UpdateData()) return; //根据年度查询的日期,如果选择 2003 年度,金额总值应该是 2003 年 1 月 1 日 //到 2004 年 1 月 1 日之前的总值。 COleDateTime dt; dt.SetDateTime(m_oleDate.GetYear(),1,1,0,0,0); CString strDate = dt.Format("%Y-%m-%d"); CString temp1,temp2; //获取总的收入金额和支出金额 temp1.Format("select sum(money) from income_info_tab " "where income_date >= to_date(’%s’,’yyyy-mm-dd’)" " and income_date < ADD_MONTHS(to_date(’%s’,’yyyy-mm-dd’),12)" ,strDate,strDate); temp2.Format("select sum(money) from expense_info_tab " "where expense_date >= to_date(’%s’,’yyyy-mm-dd’)" " and expense_date < ADD_MONTHS(to_date(’%s’,’yyyy-mm-dd’),12)" ,strDate,strDate); double totalIncome = GetTotalMoneyBySql(temp1); double totalExpense = GetTotalMoneyBySql(temp2); //设置 MS Chart 的显示数据 — 338 — SetChartData(totalIncome,totalExpense); //设置柱状图下的显示标题 temp1.Format("%d 年收支统计信息",m_oleDate.GetYear()); m_chart.SetRowLabel(temp1); m_chart.Refresh(); } 如果要统计 2003 年度的收支信息,OnBtnYearQuery 函数将统计从 2003 年 1 月 1 日到 2004 年 1 月 1 日所有的收支信息。如在“收支查询信息”对话框的“选择查询的日期”日 期控件中选择 2003-7-15,单击“年度查询”按钮,将列出 2003 年度的收支统计信息,如 图 9-35 所示。 图 9-35 年度查询对话框 从图 9-35 中可以看出 2003 年一共收入了 18 194 元,支出了 18 532 元,超支了 338 元, 柱状图的下方显示“2003 年收支统计信息”。 按照月份查询的函数为 OnBtnMonthQuery,代码如下: void CACStatQuery::OnBtnMonthQuery() { if(!UpdateData()) return; COleDateTime dt; //根据月份查询的日期,如果选择 2004 年 6 月度,金额总值应该是 2003 年 6 月 1 日 //到 2004 年 7 月 1 日之前的总值. dt.SetDateTime(m_oleDate.GetYear(),m_oleDate.GetMonth(),1,0,0,0); CString strDate = dt.Format("%Y-%m-%d"); CString temp1,temp2; temp1.Format("select sum(money) from income_info_tab " "where income_date >= to_date(’%s’,’yyyy-mm-dd’)" " and income_date < ADD_MONTHS(to_date(’%s’,’yyyy-mm-dd’),1)" ,strDate,strDate); — 339 — temp2.Format("select sum(money) from expense_info_tab " "where expense_date >= to_date(’%s’,’yyyy-mm-dd’)" " and expense_date < ADD_MONTHS(to_date(’%s’,’yyyy-mm-dd’),1)" ,strDate,strDate); double totalIncome = GetTotalMoneyBySql(temp1); double totalExpense = GetTotalMoneyBySql(temp2); //设置 MS Chart 显示的数据 SetChartData(totalIncome,totalExpense); //设置柱状图下的显示标题 temp1.Format("%d 年 %d 月 收 支 统 计 信 息 ",m_oleDate.GetYear(),m_oleDate.GetMonth()); m_chart.SetRowLabel(temp1); m_chart.Refresh(); } 如果要统计 2004 年 6 月份的收支信息,OnBtnMonthQuery 函数将统计从 2003 年 6 月 1 日到 2004 年 7 月 1 日所有收支信息。如在“收支查询信息”对话框的“选择查询的日期” 日期控件中选择 2004-6-15,单击“月份查询”按钮,就列出 2004 年 6 月份总的收支统计 信息,如图 9-36 所示。 图 9-36 月份查询对话框 从图 9-36 中可以看出 2004 年 6 月份一共收入了 1352 元,支出 1234 元,还有余额 118 元,柱状图下方显示“2004 年 6 月收支统计信息”。 按照日期查询的函数为 OnBtnDayQuery,代码如下: void CACStatQuery::OnBtnDayQuery() { if(!UpdateData()) return; //根据查询的日期,获取选择日期的所有总值. CString strDate = m_oleDate.Format("%Y-%m-%d"); — 340 — CString temp1,temp2; temp1.Format("select sum(money) from income_info_tab " "where income_date = to_date(’%s’,’yyyy-mm-dd’)" ,strDate); temp2.Format("select sum(money) from expense_info_tab " "where expense_date = to_date(’%s’,’yyyy-mm-dd’)" ,strDate); double totalIncome = GetTotalMoneyBySql(temp1); double totalExpense = GetTotalMoneyBySql(temp2); //设置 MS Chart 的显示数据 SetChartData(totalIncome,totalExpense); //设置柱状图下的显示标题 temp1.Format("%d 年 %d 月 %d 日 收 支 统 计 信 息 ",m_oleDate.GetYear(),m_oleDate.GetMonth(),m_oleDate.GetDay()); m_chart.SetRowLabel(temp1); m_chart.Refresh(); } 如在“收支查询信息”对话框的“选择查询的日期”日期控件中选择 2004-6-29,单击 “日期查询”按钮,就列出 2004-6-29 一天总的收支统计信息,如图 9-37 所示。 图 9-37 日期查询对话框 从图 9-37 中可以看出 2004-6-29 一天共收入了 71 元,支出 83 元,超支了 12 元。柱 状图的下方显示“2004 年 6 月 29 日收支统计信息”。 2. 收支对比统计查询 收支对比统计查询是按照年度和月份列出收入、支出以及收支余额的对比信息。为了 显示这些信息的方便,可以创建一个对话框,对话框类名称为 CACCompareStatDlg,资源 ID 为 IDD_DIALOG_AC_COMPARE_STATY,对话框名称为“收支查询对比信息”,“收支 查询对比信息”对话框如图 9-38 所示。 — 341 — 图 9-38 “收支查询对比信息”对话框 控件类型、ID 及说明见表 9-10。 表 9-10 控件列表 控件类型 ID 属 性 变量或函数 Label IDC_STATIC 选择查询方式 无 Combo Box IDC_COMBO_TYPE 无 CComboBox 类型变量 m_comboType Button IDC_BTN_STAT_QUERY 统计查询 函数 OnBtnStatQuery ()查询统计信息 Button IDOK 退出 无 MS Chart Control IDC_MSCHART1 无 CMSChart 类型变量 m_chart 此时,添加 MS Chart 控件变量就不能再用 ClassWizard 了,因为 ClassWizard 会再次 生成 MS Chart 控件的相关类,只是文件的名称不同而已。现在手动添加 MS Chart 控件的 变量,在 ACCompareStatDlg.h 文件开始处引入 mschart.h 头文件,在 ACCompareStatDlg.h 文件的 AFX_DATA 括号中添加 CMSChart 类型的变量 m_chart,添加控件变量之后的代码 如下: //{{AFX_DATA(CACCompareStatDlg) enum { IDD = IDD_DIALOG_AC_COMPARE_STAT }; CComboBox m_comboType; CMSChart m_chart; CString m_strType; //}}AFX_DATA 在 ACCompareStatDlg.cpp 文件的 DoDataExchange 函数添加 MS Chart 控件的绑定,添 加绑定后的代码如下: void CACCompareStatDlg::DoDataExchange(CDataExchange* pDX) — 342 — { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CACCompareStatDlg) DDX_Control(pDX, IDC_COMBO_TYPE, m_comboType); DDX_Control(pDX, IDC_MSCHART1, m_chart); DDX_CBString(pDX, IDC_COMBO_TYPE, m_strType); //}}AFX_DATA_MAP } 打开 IDC_ MSCHART1 的属性框,选择 All 选项卡,在 RowCount(柱状图的个数)文本 框中输入 12,在 ColumnCount(柱状图列的个数)文本框中输入 3,选择 Chart 选项卡,选择 Chart Type 为 2D、Bar/Pictograph,在 Chart Options 选项组中选中 Show Legend 复选框。 为了直接在 CACCompareStatDlg 类中操作数据库,需要定义一个共有的连接对象的指 针,代码如下: public _ConnectionPtr m_pConnection; 在 CAccountDBSDlg 类中的 OnBtnAccountCompareQuery 函数中添加弹出“收支查询 对比信息”对话框和传入数据库连接对象指针的代码,代码如下: void CAccountDBSDlg::OnBtnAccountCompareQuery() { //构造收支对比查询对话框的实例 CACCompareStatDlg dlg; //传入数据库连接指针 dlg.m_pConnection = m_pConnection; //打开收支对比查询对话框 dlg.DoModal(); } 收支对比统计查询的方式有两种,一种是根据选择的年度,列出一年中 12 个月份的 所有收支信息进行对比;另一种方式为列出所有年度的收支信息进行对比。 要获取所有年度的收支信息进行对比,需要从数据库中获取收入信息表和支出信息表 中包含的所有年份。在 CACCompareStatDlg 类中定义两个成员变量,最小年度 m_nMinYear 和最大年度 m_nMaxYear,同时定义一个 GetMinMaxYearBySQL 函数从收入信息表和支出 信息表中获取最小和最大年份值。还需要定义一个 InitComboData 函数,根据最小年度和 最大年度填充列表框中的年份数据,供用户选择。和收支统计查询一样也需要两个函数: GetTotalMoneyBySql(根据 SQL 语句获取总额值)和 SetChartData(设置柱状图中的显示数 据)。在 ACCompareStatDlg.h 文件中添加这些变量和函数的定义,代码如下: private: //最小年度和最大年度 int m_nMinYear; int m_nMaxYear; //初始化复合框控件中的数据 void InitComboData(); //列出所有年度的数据 void RefreshAllYearData(); — 343 — //根据 SQL 语句获取最小和最大年份值 void GetMinMaxYearBySQL(CString sql,int& min,int& max); //根据 SQL 语句获取总额值 double GetTotalMoneyBySql(CString sql); //设置柱状图的显示数据 void SetChartData(int row,double income,double expense); GetMinMaxYearBySQL 函数的代码如下: void CACCompareStatDlg::GetMinMaxYearBySQL(CString sql,int& min,int& max) { min = 0; max = 0; try { _RecordsetPtr pRecordset; pRecordset.CreateInstance(_uuidof(Recordset)); pRecordset->Open(_bstr_t(sql),(IDispatch*)m_pConnection,adOpenDynamic,a dLockOptimistic,adCmdText); TRACE(sql); //获取最小和最大的日期信息 if(!pRecordset->adoEOF) { _variant_t TheValue; CString str; TheValue = pRecordset->Fields->GetItem((short)0)->Value; if(TheValue.vt!=VT_NULL) { DATE dt; dt = TheValue.date; COleDateTime oleDT(dt); //设定最小日期的年份 min = oleDT.GetYear(); } TheValue = pRecordset->Fields->GetItem((short)1)->Value; if(TheValue.vt!=VT_NULL) { DATE dt; dt = TheValue.date; COleDateTime oleDT(dt); //获取最大的日期的年份 max = oleDT.GetYear(); } } pRecordset->Close(); } catch (_com_error e)//异常处理 { AfxMessageBox(e.ErrorMessage()); } — 344 — } 初始化“选择查询方式”列表框中的数据的函数为 InitComboData,代码如下: void CACCompareStatDlg::InitComboData() { CString sql; //获取收入信息表中的最小和最大年份 int minIncomeYear,minExpenseYear,maxIncomeYear,maxExpenseYear; sql = "select min(income_date),max(income_date) from income_info_tab"; GetMinMaxYearBySQL(sql,minIncomeYear,maxIncomeYear); //获取支出信息表中的最小和最大年份 sql = "select min(expense_date),max(expense_date) from expense_info_tab"; GetMinMaxYearBySQL(sql,minExpenseYear,maxExpenseYear); //根据收入和支出的最小年份值获取最小的年份值 int minYear = minIncomeYear>minExpenseYear?minExpenseYear:minIncomeYear; //根据收入和支出的最大年份值获取最大的年份值 int maxYear = maxIncomeYear>maxExpenseYear?maxIncomeYear:maxExpenseYear; //设定最小和最大年份值。 m_nMinYear = minYear; m_nMaxYear = maxYear; if(m_nMinYear == 0) return; //向复合框控件中插入所有的年份值 m_comboType.AddString("所有年列表"); for(int i = 0 ; i < m_nMaxYear - m_nMinYear +1 ; i++) { CString temp ; temp.Format("%d 年",minYear+i); m_comboType.AddString(temp); } } 根据测试数据,系统包含从 1999 年到 2004 年的数据,因而“选择查询方式”列表框 中将列出如图 9-39 所示的选项。 图 9-39 选择查询方式对话框 在“选择查询方式”列表框中选择某一年度,就可以列出某一年所有 12 个月份的对 比数据,如果选择“所有年列表”,就列出所有年度的收支信息对比数据。 设置柱状图的显示数据的函数为 SetChartData,代码如下: void CACCompareStatDlg::SetChartData(int row,double income,double expense) — 345 — { //设置每一组柱状图有 3 列 m_chart.SetColumnCount(3); //设置第 row 组柱状图的收入信数据、支出数据、收支金额数据 m_chart.GetDataGrid().SetData(row, 1, income, (short)0); m_chart.GetDataGrid().SetData(row, 2, expense, (short)0); m_chart.GetDataGrid().SetData(row, 3, income-expense, (short)0); //设置柱状图上列的名称 m_chart.SetColumn(1); m_chart.SetColumnLabel("总收入"); m_chart.SetColumn(2); m_chart.SetColumnLabel("总支出"); m_chart.SetColumn(3); m_chart.SetColumnLabel("收支余额"); } SetChartData 函数负责向第 row 组柱状图中插入总收入、总支出和收支余额数据。如 果在“选择查询方式”列表框中选择某一年度,将会有 12 组柱状图;如果选择“所有年列 表”,柱状图组的数目就是年的数目,对于测试数据来说,包含 1999 年到 2004 年共 6 年的 数据,所以有 6 组柱状图。 列出所有年度对比数据的函数 RefreshAllYearData 的代码如下: void CACCompareStatDlg::RefreshAllYearData() { CString sql; //获取所有年度的数目 int rowCount = m_nMaxYear - m_nMinYear +1; //设置 MS Chart 柱状图的组数目. m_chart.SetRowCount(rowCount); //设置每一组柱状图的数据 for(int row = 1 ; row <= rowCount;row++) { CString strDate; //获取该年度总的收入和支出总额 strDate.Format("%d-01-01",m_nMinYear+row-1); sql.Format("select sum(money) from income_info_tab " "where income_date >= to_date(’%s’,’yyyy-mm-dd’)" " and income_date < ADD_MONTHS(to_date(’%s’,’yyyy-mm-dd’),12)" ,strDate,strDate); double totalIncome = GetTotalMoneyBySql(sql); sql.Format("select sum(money) from expense_info_tab " "where expense_date >= to_date(’%s’,’yyyy-mm-dd’)" " and expense_date < ADD_MONTHS(to_date(’%s’,’yyyy-mm-dd’),12)" ,strDate,strDate); double totalExpense = GetTotalMoneyBySql(sql); //设置该组柱状图下的名称 CString temp ; temp.Format("%d 年",m_nMinYear+row-1); m_chart.SetRow(row); m_chart.SetRowLabel(temp); — 346 — //设置改组住状图下的所有数据 SetChartData(row,totalIncome,totalExpense); } m_chart.Refresh(); } 为了在弹出“收支查询对比信息”的对话框中显示所有年度对比数据,需要在 OnInitDialog 函数中添加如下的代码,代码如下: BOOL CACCompareStatDlg::OnInitDialog() { CDialog::OnInitDialog(); //初始化复合框控件中的数据 InitComboData(); //列出所有年度的数据 RefreshAllYearData(); //设置柱状图上显示数据的格式.利用 SetLocationType 函数的设置显示的数据的位置。0 不显 示数据 //1 显示在柱状图的外面,2 显示在柱状图内的上部,3 显示在柱状图内的中部,4 显示在柱状图 内的下部 m_chart.GetPlot().GetSeriesCollection().GetItem(1).GetDataPoints().GetI tem(-1).GetDataPointLabel().SetLocationType(1); m_chart.GetPlot().GetSeriesCollection().GetItem(2).GetDataPoints().GetI tem(-1).GetDataPointLabel().SetLocationType(1); m_chart.GetPlot().GetSeriesCollection().GetItem(3).GetDataPoints().GetI tem(-1).GetDataPointLabel().SetLocationType(1); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } 根据传入 SetLocationType 函数的参数值确定数据在柱状图上显示的位置。参数值 1 表 示显示在柱状图的外面,参数值 2 表示显示在柱状图内的上部,参数值 3 表示显示在柱状 图内的中部,参数值 4 表示显示在柱状图内的下部。程序中设定参数值为 1,即在柱状图 外面显示数据,可以在图 10-38 看到这些数据显示在柱状图的外面。要使用 SetLocationType 函数,需要引入下列头文件,这些文件都是在定义 MS Chart 控件变量的时候自动添加到工 程中的,只需要引入这些类定义文件就可以了,在 ACCompareStatDlg.cpp 文件的开始处引 入下列文件: #include "vcdatagrid.h" #include "vcplot.h" #include "vcseriescollection.h" #include "vcseries.h" #include "vcdatapoints.h" #include "vcdatapoint.h" #include "vcdatapointlabel.h" 如在“选择查询方式”列表框中选择“所有年列表”选项,单击“统计查询”按钮, 将列出 6 年的收支对比信息,如图 9-40 所示。 图中列出了从 1999 年到 2004 年的对比信息。 — 347 — 图 9-40 所有年统计查询对话框 收支对比统计查询的函数为 OnBtnStatQuery,代码如下: void CACCompareStatDlg::OnBtnStatQuery() { if(!UpdateData()) return; //如果选择所有年的列表,则列出所有年的收支信息 if(m_strType.CompareNoCase("所有年列表") == 0 ){ RefreshAllYearData(); return; } //获取要显示的年份 int nYear = atoi(m_strType.Left(4)); if(nYear == 0 || nYear m_nMaxYear) { AfxMessageBox("没有此年份的数据"); return; } //设置柱状图总的个数为 12,因为一年有 12 个月 m_chart.SetRowCount(12); for(int row = 1 ; row <= 12;row++) { CString strDate,sql; //获取 row 月份总的收支数额 strDate.Format("%d-%d-01",nYear,row); sql.Format("select sum(money) from income_info_tab " "where income_date >= to_date(’%s’,’yyyy-mm-dd’)" " and income_date < ADD_MONTHS(to_date(’%s’,’yyyy-mm-dd’),1)" — 348 — ,strDate,strDate); double totalIncome = GetTotalMoneyBySql(sql); sql.Format("select sum(money) from expense_info_tab " "where expense_date >= to_date(’%s’,’yyyy-mm-dd’)" " and expense_date < ADD_MONTHS(to_date(’%s’,’yyyy-mm-dd’),1)" ,strDate,strDate); double totalExpense = GetTotalMoneyBySql(sql); //设置柱状图下的名称为 row 月 CString temp ; temp.Format("%d 月",row); m_chart.SetRow(row); m_chart.SetRowLabel(temp); SetChartData(row,totalIncome,totalExpense); } m_chart.Refresh(); } OnBtnStatQuery 函数判断“选择查询方式”列表框中的数据,如果为“所有年列表”, 将调用 RefreshAllYearData 函数列出所有年度的对比数据,否则列出某一年度 12 个月份的 对比数据。如在“选择查询方式”列表框中选择“2003 年”,单击“统计查询”按钮,将 在 MS Chart 控件中显示 2003 年 1 月份到 12 月份的对比数据,如图 9-41 所示。 图 9-41 年度统计查询对话框 从图中可以看出 2003 年 12 月份的收入总额为 1760 元,支出为 1602 元,余额为 158 元。 也可以选择 MS Chart 控件的 Chart 属性卡下的 Series in rows 的选项,柱状图的数目就 变为 3 个,每个柱状图对应总收入、总支出和收支余额信息,如图 9-42 所示。 — 349 — 图 9-42 Chart 选项卡 需要对代码做一些修改,去掉 OnInitDialog 中最后设置数据格式的 3 行代码,而在 SetChartData 函数中添加一行设定显示数据格式的代码,添加代码后的 SetChartData 函数 代码如下: void CACCompareStatDlg::SetChartData(int row,double income,double expense) { //设置每一组柱状图有 3 列 m_chart.SetColumnCount(3); //设置第 row 组柱状图的收入信数据、支出数据、收支金额数据 m_chart.GetDataGrid().SetData(row, 1, income, (short)0); m_chart.GetDataGrid().SetData(row, 2, expense, (short)0); m_chart.GetDataGrid().SetData(row, 3, income-expense, (short)0); m_chart.GetPlot().GetSeriesCollection().GetItem(row).GetDataPoints().Get Item(-1).GetDataPointLabel().SetLocationType(1); //设置柱状图上列的名称 m_chart.SetColumn(1); m_chart.SetColumnLabel("总收入"); m_chart.SetColumn(2); m_chart.SetColumnLabel("总支出"); m_chart.SetColumn(3); m_chart.SetColumnLabel("收支余额"); } 如在“选择查询方式”列表框中选择“2003 年”选项,单击“统计查询”按钮,将在 MS Chart 中显示 2003 年从 1 月份到 12 月份的对比数据,如图 9-43 所示。 图 9-43 显示数据的方式和图 9-41 的方式不一样。图 9-43 可以非常方便地看出总收入 在 12 个月份的变化,例如 2003 年 12 个月的总收入变化情况为 1725 元、1432 元、1587 元、1477 元、1533 元、1400 元、1436 元、1605 元、1304 元、1517 元、1332 元和 1760 元。 — 350 — 图 9-43 年度统计查询对话框 9.4 本章小结 本章详细讲述了家庭账务管理系统的开发过程。家庭账务管理系统提供了收入信息和 支出信息的明细管理,用户可以清楚了解收入来源和支出的详细情况,从而深入了解家庭 的资金状况。更为方便的是,系统提供了良好的统计查询功能,包括收支统计查询和收支 对比查询。作为一个家庭,非常需要了解每年和每月的收支情况,以及和以往对比的收支 变化情况,这样,可以更好地规划家庭的生活。 家庭账务管理系统利用 ADO Data 控件和数据绑定控件开发数据库管理系统。利用这 些控件,一方面省去了很多代码,另一方面,提供了友好的界面。ADO Data 控件建立了 和数据库中的数据的一个关联,省去了建立数据库连接和打开数据库记录集的代码,而与 之绑定的 MS DataGrid 控件可以如列表框一样显示 ADO Data 控件中的数据。另外,MS DataCombo 控件的样式和列表框控件非常相似,用它来显示数据库表中某一个字段的数据 非常方便,如系统中用 DataCombo 控件显示收入类型和支出类型,而如果用普通的列表框 控件,一旦数据库中的数据发生更新,需要重新更新列表框中的数据。MS Chart 控件能非 常方便地显示统计信息数据,系统的统计查询都用了 MS Chart 控件,用户能从柱状图中了 解家庭的收支和收支对比情况。 第10章 会议纪要管理系统 如今,随着公司规模的扩大,项目的增多,往往会有很多会议。会议之前需要拟定会 议的内容,会议过程中需要对会议作简要的记录,会议之后要整理会议纪要,才能提高会 议的效率。随着会议的增多,会议纪要也越来越多,这就迫切确需要一个好的工具来管理 这些会议纪要,另外,还需要方便查询这些会议记录,以便以后的工作中使用。会议纪要 管理系统可以满足这个需求,它提供了对会议信息的有效管理。 10.1 系统设计 系统设计是系统开发最为关键的一环,系统设计好了,系统的实现以及以后的系统测 试都会节省很多的物力和财力。通过系统的设计,开发人员能够更好地把握系统的需求, 了解系统的各功能模块。通常一个会议包括多个议题,一个完整的会议纪要需要包括会议 的基本信息和议题信息,因而会议纪要管理系统需要提供对会议基本信息和议题信息的管 理。 10.1.1 功能描述 会议纪要管理系统包括会议基本信息管理和议题信息管理。详细的功能描述如下。 1. 会议基本信息管理 会议基本信息管理包括对会议基本信息的添加、修改和删除功能,以及会议信息的查 询功能。会议基本信息包括部门名称、会议地点、会议主持人、会议记录人和出席人员、 会议摘要等信息。 2. 议题信息管理 议题信息管理包括添加、修改和删除议题信息。议题信息包括议题内容、议题结果等 信息。 10.1.2 功能模块设计 从上面的功能描述中,可以把会议纪要管理系统分为两个模块:会议基本信息管理和 议题信息管理。在每一个模块下又提供了更为具体的功能。详细的会议纪要管理系统的功 能模块图,如图 10-1 所示。 10.2 数据库设计与实现 数据库设计是系统开发中非常重要的一个环节。数据库结构设计的好坏将直接影响到 系统的效率和功能的实现。在设计数据库之前,要了解数据库的需求,从而确定数据库的 结构。 — 352 — 图 10-1 系统功能模块图 10.2.1 数据库需求设计 通过以上的功能分析,会议纪要管理系统需要包含以下数据库信息。 1. 会议基本信息 包括会议 ID、部门名称、会议地点、会议日期、会议持续分钟、会议主持人、会议记 录人、出席人员、会议摘要。 2. 议题信息 包括议题 ID、会议 ID、议题内容、议题结果、议题是否通过、议题持续分钟。 10.2.2 数据库表的设计 通过数据库的需求,可以创建以下数据表。 1. 会议基本信息表(meeting_basic_info_tab) 会议基本信息表包括了会议基本信息。会议基本信息的管理实际上是对会议基本信息 表的管理,表的结构见表 10-1。 表 10-1 会议基本信息表 字段名称 数据类型 可否为空 约束条件 说 明 meeting_id INTEGER NOT NULL 主键 会议 ID,ID 从系列中获取 dept_name VARCHAR2(24) NOT NULL 无 单位名称 place VARCHAR2(24) NOT NULL 无 会议地点 meeting_date DATE NOT NULL 无 会议日期 lasting_minutes INTEGER NULL 无 会议持续分钟数目 moderator VARCHAR2(24) NULL 无 会议主持人 recorder VARCHAR2(24) NULL 无 会议记录人 attendent VARCHAR2(200) NULL 无 出席人员 summary VARCHAR2(1000) NULL 无 摘要 — 353 — 2. 议题信息表(topic_info_tab) 议题信息表包括议题信息。议题信息管理实际上是对议题信息表的管理,表的结构见 表 10-2。 表 10-2 议题信息表 字段名称 数据类型 可否为空 约束条件 说 明 topic_id INTEGER NOT NULL 主键 议题信息 ID,ID 值从系列中获取) meeting_id INTEGER NOT NULL 外键(meeting_basic_info_tab) 会议 ID Content VARCHAR2(2000) NOT NULL 无 议题内容 result VARCHAR2(200) NOT NULL 无 议题结果 is_passed INTEGER NOT NULL 无 议题是否通过,1 代表通过。 lasting_minutes INTEGER NOT NULL 无 议题持续分钟数目 10.2.3 数据库表的创建 利用第 3 章中讲述的方法创建表空间 dbmeetings 和数据库用户 dbmeetings,其中数据 库用户的密码为 dbmeetings,选择的默认表空间为 dbmeetings。 创建会议纪要管理系统所有数据表的 SQL 语句如下: --创建会议基本信息表 CREATE TABLE meeting_basic_info_tab( meeting_id INTEGER NOT NULL, dept_name VARCHAR(24) NOT NULL, place VARCHAR(24) NOT NULL, meeting_date DATE NOT NULL, lasting_minutes INTEGER NULL, moderator VARCHAR(24) NULL, recorder VARCHAR(24) NULL, attendent VARCHAR(200) NULL, summary VARCHAR(1000) NULL ); --添加会议 ID 主键 ALTER TABLE meeting_basic_info_tab ADD ( PRIMARY KEY (meeting_id) ) ; --创建议题信息表 CREATE TABLE topic_info_tab( topic_id INTEGER NOT NULL, meeting_id INTEGER NOT NULL, content VARCHAR(2000) NULL, result VARCHAR(200) NULL, is_passed INTEGER NULL CHECK (is_passed IN (0, 1)), lasting_minutes INTEGER NULL ); --添加议题信息 ID 主键 ALTER TABLE topic_info_tab ADD ( PRIMARY KEY (topic_id) ) ; — 354 — --添加成员 ID 外键 ALTER TABLE topic_info_tab ADD ( FOREIGN KEY (meeting_id) REFERENCES meeting_basic_info_tab ) ; --创建可以递增的系列号供 meeting_id 使用 CREATE SEQUENCE seq_meeting_id INCREMENT BY 1 START WITH 1 NOMAXVALUE NOMINVALUE NOCYCLE; --创建可以递增的系列号供 topic_id 使用 CREATE SEQUENCE seq_topic_id INCREMENT BY 1 START WITH 1 NOMAXVALUE NOMINVALUE NOCYCLE; 利用 Oracle SQLPlus WorkSheet 工具执行上述的 SQL 语句从而创建数据库表。需要说 明的是,在打开 Oracle SQLPlus Worksheet 的“Oracle Enterprise Manager 登录”窗口的时 候,需要在“用户名”文本框中输入企业设备管理系统的用户名 dbmeetings,在“口令” 文本框中输入用户密码 dbmeetings,在“服务”文本框中输入数据库的本地服务名 ORADB, 选择连接方式 Normal,登录成功之后,再运行上述的 SQL 语句。 10.3 系统的实现 完成了系统功能模块的设计和数据库表的创建,就可以创建一个会议纪要管理系统。 10.3.1 创建应用程序 运行 Visual C++,选择 File|New 命令,弹出 New 对话框。从 Projects 列表中选择 MFC AppWizard(exe)向导,在 Location 文本框中选择项目工程的目录“D:\VCSAMPLE\”,在 Project name 文本框中输入工程文件的名称 MeetingsDBS,单击 OK 按钮,进入 MFC AppWizard – Step 1 页面,从应用程序的类型中选择 Dialog based 选项,从语言列表中选择 “中文(中国)(APPWZCHS.DLL)”选项,单击 Finish 按钮,MeetingsDBS 对话框的应用程 序创建完毕。 本章采用 OO4O 数据库开发技术,需要引入 OO4O 开发库,所以需要对系统的运行环 境进行设置。首先按照第 2 章介绍的方法获取 3 个文件:OOTO 的 C++类定义文件 oracl.h、 静态库 ORACLM32.LIB 和动态库 oraclm32.dll。然后将 oracl.h 文件和 ORACLM32.LIB 文 件拷贝到硬盘的项目工程 MeetingsDBS 的目录下,即和主对话框类文件 MeetingsDBSDlg.h 在同一目录。把 oraclm32.dll 文件拷贝到应用程序 EXE 文件所在的目录下,即和应用程序 MeetingsDBS.exe 在同一目录。 在系统的 StdAfx.h 文件末尾出引入 oracl.h 文件,格式为: #include "oracl.h" 在 Visual C++的主菜单中,选择 Project|Settings 命令,弹出 Project Settings 对话框。选 择 Link 选项卡,在 Category 列表框中选择 General 选项,在 Object/library modules 文本框 中输入 ORACLM32.LIB,如图 10-2 所示。 — 355 — 图 10-2 项目设置对话框 10.3.2 创建主对话框的界面 主对话框的布局如图 10-3 所示。其中包括数据库的连接、会议基本信息管理和议题信 息管理 3 个部分。 图 10-3 “会议纪要管理系统”对话框 — 356 — 1. 数据库的连接 控件类型、ID 及说明见表 10-3。 表 10-3 数据库的连接控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 数据库的连接 无 Label IDC_STATIC 数据库源名称 无 Edit Box IDC_EDIT_DBSOURCE 无 CString 类型变量 m_strDBSource Label IDC_STATIC 数据库用户名称 无 Edit Box IDC_EDIT_DBUSER 无 CString 类型变量 m_strDBUser Label IDC_STATIC 数据库用户密码 无 Edit Box IDC_EDIT_PASSWORD 无 CString 类型变量 m_strDBPassword Button IDC_BTN_DBCONNECT 连接数据库 函数 OnBtnDbconnect()处理数据库的连接 Button IDC_SYS_EXIT 查询 函数 OnSysExit ()处理系统退出 2. 会议基本信息管理 控件类型、ID 及说明见表 10-4。 表 10-4 会议基本信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 会议基本信息管理 无 Label IDC_STATIC 部门名称 无 Edit Box IDC_EDIT_DEPTNAME 无 CString 类型变量 m_strDeptName Label IDC_STATIC 主持人 无 Edit Box IDC_EDIT_MODERATOR 无 CString 类型变量 m_strModerator Label IDC_STATIC 记录人 无 Edit Box IDC_EDIT_RECORDER 无 CString 类型变量 m_strRecorder Label IDC_STATIC 地点 无 Edit Box IDC_EDIT_PLACE 无 CString 类型变量 m_strPlace Label IDC_STATIC 日期 无 Date Time Picker IDC_DT_DATE Short Date COleDateTime 类型变量 m_mtDate Date Time Picker IDC_DT_TIME Time COleDateTime 类型变量 m_mtTime Label IDC_STATIC 持续时间 无 Edit Box IDC_EDIT_MEETING_LASTING 无 整型变量 m_nMeetingLastingMinutes Label IDC_STATIC 分钟 无 Label IDC_STATIC 出席人员 无 Edit Box IDC_EDIT_ATTENDENT 无 CString 类型变量 m_strAttendent Label IDC_STATIC 会议摘要 无 Edit Box IDC_EDIT_SUMMARY 无 CString 类型变量 m_strSummary Button IDC_BTN_MEETING_ADD 添加 函数 OnBtnMeetingAdd ()添加会议信息 Button IDC_BTN_MEETING_MOD 修改 函数 OnBtnMeetingMod ()修改会议信息 Button IDC_BTN_MEETING_DEL 删除 函数 OnBtnMeetingDel ()删除会议信息 Button IDC_BTN_MEETING_QUERY 查询 函数 OnBtnMeetingQuery ()查询会议信息 Group Box IDC_STATIC 会议记录信息 无 List Control IDC_LIST_FAMILY_MEMBER 无 列表框控件类型变量 m_listMeeting 3. 议题信息管理 控件类型、ID 及说明见表 10-5。 — 357 — 表 10-5 议题信息管理控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 议题信息管理 无 Label IDC_STATIC 议题内容 无 Edit Box IDC_EDIT_TOPIC_CONTENT 无 CString 类型变量 m_strTopicContent Label IDC_STATIC 议题结果 无 Edit Box IDC_EDIT_RESULT 无 CString 类型变量 m_strTopicResult Check Box IDC_CHECK_PASSED 通过 BOO 类型变量 m_isPassed Label IDC_STATIC 持续时间 无 Edit Box IDC_EDIT_TOPIC_LASTING 无 整型变量 m_nTopicLastingMinutes Label IDC_STATIC 分钟 无 Button IDC_BTN_TOPIC_ADD 添加 函数 OnBtnTopicAdd ()添加议题信息 Button IDC_BTN_TOPIC_MOD 修改 函数 OnBtnTopicMod ()修改议题信息 Button IDC_BTN_TOPIC_DEL 删除 函数 OnBtnTopicDel ()删除议题信息 List Control IDC_LIST_TOPIC 无 列表框控件类型变量 m_listTopic 主对话框类名称为 CMeetingsDBSDlg,资源 ID 为 IDD_MEETINGSDBS_DIALOG,对话 框名称为“会议纪要管理系统”。主界面用到了两个列表框控件分别显示会议基本信息和议题 信息。需要为这两个列表框控件添加显示的列,从而显示相应的数据信息。为了代码设计的清 晰,在 CMeetingsDBSDlg 类中定义一个 InitControl 私有函数负责添加控件的显示列,InitControl 函数比较简单,可以参见前面的章节,代码不再列出。其中会议基本信息列表框控件显示会议 ID、部门、主持人、记录人、地点、日期、持续时间、出席人员和会议摘要信息。议题信息 列表框控件显示议题 ID、议题内容、议题结果、通过和持续时间信息。 10.3.3 显示数据到界面上 当系统启动之后,可以将数据库中的数据显示到界面上,需要处理数据库的连接和显 示数据到界面上的两个过程。 1. 数据库的连接 由于本系统采用 OO4O 数据库开发技术,所以需要在首次操作数据库操作之前,调用 OStartup 函数来加载 OO4O 的运行库,否则是不能建立数据库连接的。由于 OStartup 函数 只需调用一次,因而把 OStartup 函数的调用放在 OnInitDialog 函数中是非常合适的,保证 系统只调用一次 OStartup 函数,OnInitDialog 函数的代码如下: BOOL CMeetingsDBSDlg::OnInitDialog() { CDialog::OnInitDialog(); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) — 358 — { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // Set the icon for this dialog. The framework does this automatically // when the application’s main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here InitControl(); //初始化库 OStartup(); return TRUE; // return TRUE unless you set the focus to a control } 另外,在系统退出的时候,需要调用 OShutDown 函数退出 OO4O 运行库,可以在系 统退出函数 OnSysExit 中添加该函数的调用,OnSysExit 函数的代码如下: void CMeetingsDBSDlg::OnSysExit() { m_odb.Close(); OShutdown(); CDialog::OnOK(); } 同时注释掉 OnOK 和 OnCancel 函数中自动生成的代码,方法如下: void CIndustryDBSDlg::OnOK() { // if (CanExit()) // CDialog::OnOK(); } void CIndustryDBSDlg::OnCancel() { // if (CanExit()) // CDialog::OnCancel(); } 注释这些代码的目的是屏蔽掉键盘上的 Enter 键和 Esc 键的响应,让系统自己处理系统 的退出,因为默认情况下,当用户按 Enter 键和 Esc 键的时候,会退出对话框。这样只有按 下主界面上的“退出”按钮,系统才会退出,且调用了 OShutDown 函数退出 OO4O 运行库。 利用 OO4O 开发库建立数据库的连接需要 3 个参数:数据源名称、数据库用户名称和 数据库用户密码。其中数据源的名称就是本地服务名 ORADB,数据库用户的名称和密码 均为 dbmeetings,考虑到读者配置的数据源和用户名可能不一样,会议纪要管理系统提供 了在界面上配置这 3 个参数的功能,在“数据库的连接”中设置相应的数据库连接参数。 在 CMeetingsDBSDlg 类的 OnBtnDbconnect 函数中处理数据库的连接,代码如下: void CMeetingsDBSDlg::OnBtnDbconnect() { — 359 — //从界面控件中获取信息,更新到控件变量中. if(!UpdateData()) return; //检查数据库配置参数. if(m_strDBSource.IsEmpty()||m_strDBUser.IsEmpty()||m_strDBPassword.IsEm pty()){ AfxMessageBox("数据库配置参数不能够为空"); return; } //判断数据库是否已经打开 if (!m_odb.IsOpen() ) { try { //打开数据库 m_odb.Open(m_strDBSource, m_strDBUser, m_strDBPassword); } catch(OException E) { AfxMessageBox(E.GetErrorText()); } } CString sql = "Select * from MEETING_BASIC_INFO_TAB"; RefreshCtrlData(sql); } OnBtnDbconnect 首先从界面上获取数据库连接的参数,然后判断数据库是否已经打 开,如果已打开,就退出函数;如果未打开,则调用 ODatabase 类的 Open 方法建立数据 库的连接并处理可能出现的 OException 异常。 2. 显示数据到界面上 连接数据库之后,需要把数据库中的数据显示在两个列表框控件中。需要在 CMeetingsDBSDlg 类中定义一个 RefreshCtrlData 函数,负责从数据库中读取数据并显示到 列表框控件中。因为议题信息是整个会议信息的一部份,如果在会议列表框控件选择一条 会议记录,就应该在议题信息列表框控件中显示该会议下面的所有议题信息,所以需要定 义 一 个 整 型 变 量 m_nCurrentMeetingID 代 表 当 前 的 会 议 ID。 还 需 要 定 义 一 个 RefreshTopicCtrl 函数,负责根据当前的会议 ID 在议题信息列表框控件中显示所有的议题 信息。同时还需要在 CMeetingsDBSDlg 类中定义两个分别把数据插入到列表框控件中的函 数,分别为 InsertMeetingInfoItem 和 InsertTopicInfoItem,在头文件为 MeetingsDBSDlg.h 中 添加这些变量和函数的定义,代码如下: private: //记录当前编辑的会议 ID int m_nCurrentMeetingID; //向会议录列表框中插入会议信息. void InsertMeetingInfoItem(int id, CString deptName,CString moderator,CString recorder,CString place, CString meetingDate, int nMinutes, CString attendent, CString summary); — 360 — //向议题列表框中插入议题信息. void InsertTopicInfoItem(int id, CString content,CString result,int isPassed, int nMinutes); //刷新会议和议题列表框控件中的数据. void RefreshCtrlData(CString sql); //刷新议题列表框控件中的数据 void RefreshTopicCtrl(); 向会议信息列表框控件中插入数据的函数为 InsertMeetingInfoItem,代码如下: void CMeetingsDBSDlg::InsertMeetingInfoItem(int id, CString deptName, CString moderator, CString recorder, CString place, CString meetingDate, int nMinutes, CString attendent, CString summary) { //获取当前的记录条数. int nIndex = m_listMeeting.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; //行数 lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; //第一列 //在最后一行插入记录值. m_listMeeting.InsertItem(&lvItem); //设置该行的其他列的值. m_listMeeting.SetItemText(nIndex,1,deptName); m_listMeeting.SetItemText(nIndex,2,moderator); m_listMeeting.SetItemText(nIndex,3,recorder); m_listMeeting.SetItemText(nIndex,4,place); m_listMeeting.SetItemText(nIndex,5,meetingDate); temp.Format("%d",nMinutes); m_listMeeting.SetItemText(nIndex,6,temp); m_listMeeting.SetItemText(nIndex,7,attendent); m_listMeeting.SetItemText(nIndex,8,summary); } 向议题信息列表框控件中插入数据的函数为 InsertTopicInfoItem 函数的代码如下: void CMeetingsDBSDlg::InsertTopicInfoItem(int id, CString content, CString result, int isPassed, int nMinutes) { //获取当前的记录条数. int nIndex = m_listTopic.GetItemCount(); LV_ITEM lvItem; lvItem.mask = LVIF_TEXT ; lvItem.iItem = nIndex; lvItem.iSubItem = 0; CString temp ; temp.Format("%d",id); lvItem.pszText = (char*)(LPCTSTR)temp; — 361 — //在最后一行插入记录值. m_listTopic.InsertItem(&lvItem); //设置其他列的值. m_listTopic.SetItemText(nIndex,1,content); m_listTopic.SetItemText(nIndex,2,result); m_listTopic.SetItemText(nIndex,3,isPassed==0?"否":"是"); temp.Format("%d",nMinutes); m_listTopic.SetItemText(nIndex,4,temp); } 根据传入的 SQL 语句在列表框控件中显示数据的函数为 RefreshCtrlData,代码如下: void CMeetingsDBSDlg::RefreshCtrlData(CString sql) { if(!m_odb.IsOpen()){ MessageBox("数据库未打开"); return; } m_listMeeting.DeleteAllItems(); try { ODynaset rs(m_odb, sql); //设置当前会议 ID 初始状态为 1 m_nCurrentMeetingID = 1; while (!rs.IsEOF()) { OValue value; int meetingID,nMeetings; CString strDeptName,strModerator,strRecorder; CString strPlace,strMeetingDate,strAttendent,strSummary; //获取会议 ID 字段值 rs.GetFieldValue(0, &meetingID); m_nCurrentMeetingID = meetingID; //获取部门名称字段值 rs.GetFieldValue(1, &value); strDeptName = value; //获取操作会议地点字段值 rs.GetFieldValue(2, &value); strPlace = value; //获取会议日期字段值 rs.GetFieldValue(3, &value); strMeetingDate = value; //获取会议持续时间字段值 rs.GetFieldValue(4, &nMeetings); //获取会议主持人字段值 rs.GetFieldValue(5, &value); strModerator = value; //获取会议记录人值 rs.GetFieldValue(6, &value); strRecorder = value; //获取出席人员字段值 rs.GetFieldValue(7, &value); — 362 — strAttendent = value; //获取会议摘要字段值 rs.GetFieldValue(8,&value); strSummary = value; //向会议列表控件中加入一条新的记录信息. InsertMeetingInfoItem(meetingID,strDeptName,strModerator, strRecorder,strPlace,strMeetingDate,nMeetings,strAttendent,strSummary) ; rs.MoveNext(); } //刷新议题列表控件数据,议题列表控件的数据是当前会议 ID 对应的所有议题信息. RefreshTopicCtrl(); } catch(OException E) { AfxMessageBox(E.GetErrorText()); } } RefreshCtrlData 函数带有一个 CString 类型的 sql 参数,它接收一个标准的 SQL 语句, 根据 SQL 语句打开数据库中的记录集,然后显示在列表框控件中,该函数还会在会议信息 查询中用到。ODynaset 类对象会根据传入的数据库连接和 SQL 语句打开记录集,利用 IsEOF 函数判断记录集是否到达末尾,如果没有,利用 ODynaset 类的 GetFieldValue 函数获取字段 值,只需要传入字段的名称或者字段的序号。在遍历会议记录的时候,要设置 m_nCurrentMeetingID 值,即当前的会议 ID。从而可以让议题列表框控件根据最后的一个会 议 ID 显示所有的议题信息,如果数据库的记录为空,m_nCurrentMeetingID 初始值为 0,而 m_nCurrentMeetingID 为 0 的议题记录是没有的(会议 ID 和议题 ID 是从系列中取的,系列的 最小值为 1)。调用 InsertMeetingInfoItem 把会议信息插入到会议信息列表框控件中。当访问 完所有的会议基本信息的记录的时候,调用 RefreshTopicCtrl 函数,会根据最后一个会议 ID 获取所有的议题信息显示在议题信息列表框控件中。RefreshTopicCtrl 函数的代码如下: void CMeetingsDBSDlg::RefreshTopicCtrl() { //刷新议题列表控件数据,议题列表控件的数据是当前会议 ID 对应的所有议题信息. m_listTopic.DeleteAllItems(); try { CString sql; sql.Format("Select * from topic_info_tab where meeting_id = %d ",m_nCurrentMeetingID); //打开所有的会议记录. TRACE(sql); ODynaset rs(m_odb, sql); while (!rs.IsEOF()) { OValue value; int topicID,nMeetings,isPassed; CString content,result; //获取议题 ID 字段值 — 363 — rs.GetFieldValue(0, &topicID); //获取议题内容字段值 rs.GetFieldValue(2,&value); content = value; //获取议题结果字段值 rs.GetFieldValue(3,&value); result = value; //获取是否通过字段值 rs.GetFieldValue(4, &isPassed); //获取会议持续时间字段值 rs.GetFieldValue(5, &nMeetings); //向列表控件中加入一条新的议题信息. InsertTopicInfoItem(topicID,content,result,isPassed,nMeetings) ; rs.MoveNext(); } } catch(OException E) { AfxMessageBox(E.GetErrorText()); } } 在主界面中的“数据库源名称”文本框中输入本地服务名 ORADB,在“数据库用户 名称”文本框中输入用户名 dbmeetings,在“数据库用户密码”文本框中输入用户密码 dbmeetings。单击主界面上的“连接数据库”按钮,数据库连接成功之后,数据显示到界 面上,如图 10-4 所示。 图 10-4 连接数据库之后的对话框 — 364 — 10.3.4 会议基本信息的管理 会议基本信息管理包括对会议基本信息添加、修改和删除的功能,以及会议信息查询 的功能。 1. 添加会议基本信息 添加会议基本信息的函数为 OnBtnMeetingAdd,代码如下: void CMeetingsDBSDlg::OnBtnMeetingAdd() { //从界面控件中获取信息更新到控件变量中. if(!UpdateData()) return; //构造会议开始时间 CString strMeetingDate = m_mtDate.Format("%Y-%m-%d") + " " + m_mtTime.Format("%H:%M:%S"); try { //从 SEQ_MEETING_ID 序列中获取下一个值.这个值就是新的会议 ID //打开方式要用 snapshot,用 dynaset 方式会增加 2. ODynaset rs(m_odb, "Select SEQ_MEETING_ID.NEXTVAL from dual"); int meetingID = 1; if(!rs.IsEOF()) rs.GetFieldValue(0, &meetingID); //插入新记录. CString sql; sql.Format("Insert into meeting_basic_info_tab(meeting_id," "dept_name,place,meeting_date," "lasting_minutes,moderator,recorder," "attendent,summary) " "VALUES(" "%d,’%s’,’%s’,to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’)" ",%d,’%s’,’%s’" ",’%s’,’%s’)",meetingID,m_strDeptName, m_strPlace,strMeetingDate,m_nMeetingLastingMinutes, m_strModerator,m_strRecorder,m_strAttendent,m_strSummary); TRACE(sql); m_odb.ExecuteSQL(sql); //向界面中插入新的会议信息. InsertMeetingInfoItem(meetingID,m_strDeptName,m_strModerator, m_strRecorder,m_strPlace,strMeetingDate,m_nMeetingLastingMinutes,m_strA ttendent,m_strSummary) ; } catch(OException E) { AfxMessageBox(E.GetErrorText()); } } OnBtnMeetingAdd 函数首先从界面中获取数据,然后从 SEQ_MEETING_ID 系列中获 — 365 — 取新的会议 ID ,并构造添加新的会议基本信息的 SQL 语句,最后用调用数据库类 ODatabase 的 ExecuteSQL 方法把新的会议基本信息添加到数据库中。 如在“会议基本信息管理”中的“部门名称”文本框中输入“销售部”,在“主持人” 文本框中输入“郑华”,在“记录人”文本框中输入“钱芳”,在“地点”文本框中输入“大 会议室”,在“日期”时间控件中选择 2004-6-20 9:00:00,在“持续时间”文本框中输入 60, 在“出席人员”文本框中输入“孙平,王小燕,周平”,在“会议摘要”文本框中输入“销售 总结和计划”,如图 10-5 所示。 图 10-5 添加会议信息对话框 单击“添加”按钮,会议基本信息就添加到数据库中并在会议记录信息列表框控件中 显示,如图 10-6 所示。 图 10-6 添加会议信息之后的对话框 2. 修改会议基本信息 为了用户修改会议基本信息的方便,可以添加会议基本信息列表框控件 IDC_LIST_ MEETING 的 NM_CLICK 消息映射函数 OnClickListMeeting,代码如下: void CMeetingsDBSDlg::OnClickListMeeting(NMHDR* pNMHDR, LRESULT* pResult) { — 366 — //获取已选择的记录项. int nItem = m_listMeeting.GetNextItem(-1, LVNI_SELECTED); if(nItem != -1){ //更新当前选择的会议 ID 值 m_nCurrentMeetingID = atoi(m_listMeeting.GetItemText(nItem,0)); //从列表框控件的第 nItem+1 行获取数据,以将这些数据显示到会议参数的控件中. m_strDeptName = m_listMeeting.GetItemText(nItem,1); m_strModerator = m_listMeeting.GetItemText(nItem,2); m_strRecorder = m_listMeeting.GetItemText(nItem,3); m_strPlace = m_listMeeting.GetItemText(nItem,4); CString strDT = m_listMeeting.GetItemText(nItem,5); m_mtDate.ParseDateTime(strDT); m_mtTime.ParseDateTime(strDT); m_nMeetingLastingMinutes = atoi(m_listMeeting.GetItemText(nItem,6)); m_strAttendent = m_listMeeting.GetItemText(nItem,7); m_strSummary = m_listMeeting.GetItemText(nItem,8); //把会议列表框控件中当前已选择行的数据,更新到会议参数的控件中. UpdateData(FALSE); //根据当前的会议 ID,更新该会议 ID 下的所有议题信息. RefreshTopicCtrl(); } *pResult = 0; } 修改会议基本信息的函数为 OnBtnModMember,代码如下: void CMeetingsDBSDlg::OnBtnMeetingMod() { int nItem = m_listMeeting.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要修改的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要修改的会议记录"); return; } if(!UpdateData()) return; CString strMeetingDate = m_mtDate.Format("%Y-%m-%d") + " " + m_mtTime.Format("%H:%M:%S"); try { CString sql; //更新数据库中的记录值. sql.Format("update meeting_basic_info_tab " "set dept_name = ’%s’," "place = ’%s’," "meeting_date = to_date(’%s’,’yyyy-mm-dd hh24:mi:ss’)," "lasting_minutes = %d," "moderator = ’%s’," "recorder = ’%s’," "attendent = ’%s’," "summary = ’%s’ " "where meeting_id = %d",m_strDeptName, — 367 — m_strPlace,strMeetingDate,m_nMeetingLastingMinutes, m_strModerator,m_strRecorder,m_strAttendent,m_strSummary,m_nCurrentMeet ingID); TRACE(sql); m_odb.ExecuteSQL(sql); //更新列表控件的值. m_listMeeting.SetItemText(nItem,1,m_strDeptName); m_listMeeting.SetItemText(nItem,2,m_strModerator); m_listMeeting.SetItemText(nItem,3,m_strRecorder); m_listMeeting.SetItemText(nItem,4,m_strPlace); m_listMeeting.SetItemText(nItem,5,strMeetingDate); CString temp; temp.Format("%d",m_nMeetingLastingMinutes); m_listMeeting.SetItemText(nItem,6,temp); m_listMeeting.SetItemText(nItem,7,m_strAttendent); m_listMeeting.SetItemText(nItem,8,m_strSummary); } catch(OException E) { AfxMessageBox(E.GetErrorText()); } } OnBtnMeetingMod 函数首先从界面中获取数据,从列表框控件的选项中获取会议 ID, 并构造修改会议基本信息的 SQL 语句,最后调用 ODatabase 的 ExecuteSQL 方法把会议基 本信息更新到数据库中。 选择会议 ID 为 14 的会议记录,相应的参数将显示在界面上,如图 10-7 所示。 图 10-7 选择会议信息对话框 — 368 — 修改“会议摘要”文本框中的数据为“新产品介绍和市场开发计划”,单击“修改” 按钮,会议的基本信息修改完成,如图 10-8 所示。 图 10-8 修改会议信息对话框 3. 删除会议基本信息 删除会议基本信息的函数为 OnBtnMeetingDel,代码如下: void CMeetingsDBSDlg::OnBtnMeetingDel() { //获取要删除的会议记录所在的行数. int nItem = m_listMeeting.GetNextItem(-1, LVNI_SELECTED); if(nItem == -1){ AfxMessageBox("没有选择要删除的会议记录"); return; } //获取要删除的会议 ID. int meetingID = atoi(m_listMeeting.GetItemText(nItem,0)); try { CString deletedSql; //首先删除会议 ID 为 meetingID 的所有议题记录 deletedSql.Format("delete from TOPIC_INFO_TAB where MEETING_ID = %d",meetingID); m_odb.ExecuteSQL(deletedSql); //然后删除会议 ID 为 meetingID 会议记录 deletedSql.Format("delete from MEETING_BASIC_INFO_TAB where MEETING_ID = %d",meetingID); m_odb.ExecuteSQL(deletedSql); //从界面中删除记录信息. m_listMeeting.DeleteItem(nItem); m_listTopic.DeleteAllItems(); } — 369 — catch(OException E) { AfxMessageBox(E.GetErrorText()); } } 因为一条会议信息通常包括若干条议题信息,所以在删除一条会议信息的时候,首先 删除该会议下的所有议题信息,再删除该会议信息,保证数据的完整性。 4. 会议信息查询 会议信息查询提供了根据会议地点、会议主持人、会议记录人、会议摘要和议题内容 的方式查询会议信息的功能。会议信息查询提供了模糊查询的方式,可以增加查询的范围, 如按照会议摘要方式进行查询,只需输入“总结”,就可以列出所有会议摘要中包含“总结” 文字的会议信息。为了方便查询,可以创建一个对话框,对话框类名称为 CQueryDlg,资 源 ID 为 IDD_DIALOG_QUERY,对话框名称为“查询条件”,“查询条件”对话框如图 10-9 所示。 图 10-9 “查询条件”对话框 控件类型、ID 及说明见表 10-6。 表 10-6 控件列表 控件类型 ID 属 性 变量或函数 Group Box IDC_STATIC 选择条件 无 Radio Button IDC_RADIO1 所有记录 整型变量 m_nChoice 控制选项 Radio Button IDC_RADIO2 会议地点 无 Edit Box IDC_EDIT_PLACE 无 CString 类型变量 m_strPlace Radio Button IDC_RADIO3 会议主持人 无 Edit Box IDC_EDIT_MODERATOR 无 CString 类型变量 m_strContent Radio Button IDC_RADIO4 会议记录人 无 Edit Box IDC_EDIT_RECORDER 无 CString 类型变量 m_strPlace Radio Button IDC_RADIO5 会议摘要 Edit Box IDC_EDIT_SUMMARY 无 CString 类型变量 m_strContent Radio Button IDC_RADIO6 议题内容 无 Edit Box IDC_EDIT_CONTENT 无 CString 类型变量 m_strContent Button IDOK 确定 函数 OnOK()创建查询的 SQL 语句 Button IDCANCEL 取消 无 — 370 — 选择 IDC_RADIO1 属性的 General 选项卡,选中 Group 复选框,而其他 Radio Button 中不要选中 Group 复选框,这些 Radio Button 的 Tab Order 顺序要连续,如图 10-10 所示。 图 10-10 “查询条件”对话框 OnOk 函数创建了查询会议信息的 SQL 语句。代码如下: void CQueryDlg::OnOK() { if(!UpdateData()) return; //构造通配符"%",在 SQL 标准语句中的利用 like 关键字可以实现模糊查询. CString temp = "%"; switch(m_nChoice) { //查询所有记录 case 0: m_strSQL = "Select * from meeting_basic_info_tab"; break; //根据会议地点进行模糊查询 case 1: m_strSQL.Format("Select * from meeting_basic_info_tab" " where place like ’%s%s%s’",temp,m_strPlace,temp); break; //根据会议主持人进行模糊查询 case 2: m_strSQL.Format("Select * from meeting_basic_info_tab" " where moderator like ’%s%s%s’",temp,m_strModerator,temp); break; //根据会议记录人进行模糊查询 case 3: m_strSQL.Format("Select * from meeting_basic_info_tab" " where recorder like ’%s%s%s’",temp,m_strRecorder,temp); break; //根据会议摘要进行模糊查询 case 4: m_strSQL.Format("Select * from meeting_basic_info_tab" " where summary like ’%s%s%s’",temp,m_strSummary,temp); break; — 371 — //根据议题内容进行模糊查询 case 5: m_strSQL.Format("Select distinct a.meeting_id,dept_name,place,meeting_date,a.lasting_minutes,moderator," "recorder,attendent,summary from meeting_basic_info_tab a ,topic_info_tab b" " where a.meeting_id = b.meeting_id and b.content like ’%s%s%s’",temp,m_strContent,temp); break; default: m_strSQL = "Select * from meeting_basic_info_tab"; } TRACE(m_strSQL); CDialog::OnOK(); } 利用 switch 和 case 结构,实现查询条件的选择。利用标准 SQL 语句中的 like 关键字 和通配符“%”实现模糊查询。例如条件语句“where place like ’%会议室%’”是会议地点 place 字段中包含“会议室”字符的所有会议记录。在 CMeetingsDBSDlg 类 的 OnBtnMeetingQuery 函数中,添加弹出“查询条件”对话框和根据对话框中选择的查询条 件显示会议信息的代码,代码如下: void CMeetingsDBSDlg::OnBtnMeetingQuery() { //创建查询条件对话框实例 CQueryDlg dlg; //打开查询条件对话框 if(dlg.DoModal()==IDOK){ //获取查询条件的 SQL 语句从而获取记录值 RefreshCtrlData(dlg.m_strSQL); } } 如在“查询条件”对话框中选中“会议地点”单选按钮,在“会议地点”文本框中输 入“会议室”,如图 10-11 所示。 图 10-11 “查询条件”对话框 — 372 — 单击“确定”按钮,将在会议记录信息列表框控件中显示两条会议信息,如图 10-12 所示。 图 10-12 “会议纪要管理系统”对话框 10.3.5 议题信息的管理 议题信息管理包括对议题信息的添加、修改和删除的功能。 1. 添加议题信息 添加议题信息的函数为 OnBtnTopicAdd,代码如下: void CMeetingsDBSDlg::OnBtnTopicAdd() { //从界面控件中获取信息更新到控件变量中. if(!UpdateData()) return; try { //从 SEQ_TOPIC_ID 序列中获取下一个值.这个值就是新的议题 ID. ODynaset rs(m_odb, "Select SEQ_TOPIC_ID.NEXTVAL from dual"); int topicID = 1; if(!rs.IsEOF()) — 373 — rs.GetFieldValue(0, &topicID); rs.Close(); rs.Open(m_odb,"select * from topic_info_tab"); //添加新记录 if(OSUCCESS!=rs.AddNewRecord()) return; rs.SetFieldValue(0,topicID); rs.SetFieldValue(1,m_nCurrentMeetingID); rs.SetFieldValue(2,m_strTopicContent); rs.SetFieldValue(3,m_strTopicResult); rs.SetFieldValue(4,m_isPassed); rs.SetFieldValue(5,m_nTopicLastingMinutes); //更新到数据库中 rs.Update(); //向界面中插入新的记录. InsertTopicInfoItem(topicID,m_strTopicContent,m_strTopicResult,m_isPass ed,m_nTopicLastingMinutes) ; } catch(OException E) { AfxMessageBox(E.GetErrorText()); } } OnBtnTopicAdd 函数首先从界面中获取数据,从 SEQ_TOPIC_ID 系列中获取新的议题 ID,然后调用记录集类 ODynaset 的 AddNewRecord 方法告知数据库接下来的操作是给数 据库添加一条记录,再调用 SetFieldValue 函数设置记录的字段值,最后调用记录集的 Update 方法将数据添加到数据库中。 在会议基本信息管理中的会议记录信息列表框中选择会议 ID 为 23 的会议基本信息, 该会议信息还没有添加议题信息。在议题信息管理中的“议题内容”文本框中输入“区域 销售计划”,在“议题结果”文本框中输入“初步确定”,选中“通过”单选按钮,在“持 续时间”文本框中输入 40,如图 10-13 所示。 图 10-13 添加议题信息对话框 单击“添加”按钮,议题信息就添加到数据库中,并在议题列表框控件中显示出来, 如图 10-14 所示。 — 374 — 图 10-14 添加议题信息之后的对话框 可以利用会议信息查询中的议题内容查询条件来查询会议信息。如在未查询前,列出 了 3 条会议基本信息,如图 10-15 所示。 图 10-15 “会议基本信息管理”对话框 单击“查询”按钮,弹出“查询条件”对话框,选中“议题内容”单选按钮,在“议 题内容”文本框中输入“区域”,如图 10-16 所示。 图 10-16 “查询条件”对话框 单击“确定”按钮,在会议记录信息列表框控件中列出会议 ID 为 23 的会议基本信息, 在议题信息列表框控件中列出议题 ID 为 101 的信息,如图 10-17 所示。 — 375 — 图 10-17 查询之后的对话框 从图 10-17 中可以看出,议题 ID 为 101 的议题内容为“区域销售计划”,包含“区域” 两个字。 2. 修改议题信息 为了用户修改议题信息的方便,可以添加议题列表框控件 IDC_LIST_ TOPIC 的 NM_CLICK 消息映射函数 OnClickListTopic,代码如下: void CMeetingsDBSDlg::OnClickListTopic(NMHDR* pNMHDR, LRESULT* pResult) { //获取已选择的记录项. int nItem = m_listTopic.GetNextItem(-1, LVNI_SELECTED); if(nItem != -1){ //从列表框控件的第 nItem+1 行获取数据,以将这些数据显示到议题参数的控件中. m_strTopicContent = m_listTopic.GetItemText(nItem,1); m_strTopicResult = m_listTopic.GetItemText(nItem,2); CString temp = m_listTopic.GetItemText(nItem,3); if(temp.CompareNoCase("是") == 0) m_isPassed = 1; else m_isPassed = 0; m_nTopicLastingMinutes = atoi(m_listTopic.GetItemText(nItem,4)); //把议题列表框控件中当前已选择行的数据,更新到议题参数的控件中. UpdateData(FALSE); — 376 — } *pResult = 0; } 修改议题信息的函数为 OnBtnTopicMod,代码如下: void CMeetingsDBSDlg::OnBtnTopicMod() { int nItem = m_listTopic.GetNextItem(-1, LVNI_SELECTED); //如果没有选择要修改的记录,返回. if(nItem == -1){ AfxMessageBox("没有选择要修改的议题"); return; } //获取议题 ID int topicID = atoi(m_listTopic.GetItemText(nItem,0)); if(!UpdateData()) return; try { CString sql ; sql.Format("select * from topic_info_tab WHERE topic_id = %d",topicID); ODynaset rs(m_odb, sql); if(!rs.IsOpen()) return; //修改记录 if(OSUCCESS!=rs.StartEdit()) return; rs.SetFieldValue(2,m_strTopicContent); rs.SetFieldValue(3,m_strTopicResult); rs.SetFieldValue(4,m_isPassed); rs.SetFieldValue(5,m_nTopicLastingMinutes); //更新到数据库中 rs.Update(); //更新议题控件中的数据. m_listTopic.SetItemText(nItem,1,m_strTopicContent); m_listTopic.SetItemText(nItem,2,m_strTopicResult); m_listTopic.SetItemText(nItem,3,m_isPassed==0?"否":"是"); CString temp; temp.Format("%d",m_nTopicLastingMinutes); m_listTopic.SetItemText(nItem,4,temp); } catch(OException E) { AfxMessageBox(E.GetErrorText()); } } OnBtnTopicMod 函数首先从界面中获取数据,然后调用记录集类 ODynaset 的 StartEdit 方法告知数据库接下来的操作是要修改记录,再调用 SetFieldValue 函数设置要修改的字段 值,最后调用记录集的 Update 方法将数据添加到数据库中。 — 377 — 选择刚才添加的议题信息,修改“持续时间”文本框中的数据为 45(分钟),单击“修 改”按钮,议题信息修改完成,如图 10-18 所示。 图 10-18 修改议题信息对话框 3. 删除议题信息 删除议题信息的函数为 OnBtnTopicDel,代码如下: void CMeetingsDBSDlg::OnBtnTopicDel() { //获取要删除的议题记录所在的行数. int nItem = m_listTopic.GetNextItem(-1, LVNI_SELECTED); if(nItem == -1){ AfxMessageBox("没有选择要删除的议题记录"); return; } //获取要删除的议题 ID. int topicID = atoi(m_listTopic.GetItemText(nItem,0)); try { CString deletedSql; //删除议题 ID 为 topicID 的议题记录 CString sql ; sql.Format("select * from TOPIC_INFO_TAB where topic_id = %d",topicID); ODynaset rs(m_odb, sql); if(!rs.IsOpen()) return; rs.DeleteRecord(); //从界面中删除议题信息. m_listTopic.DeleteItem(nItem); } catch(OException E) { AfxMessageBox(E.GetErrorText()); } } OnBtnTopicDel 函数调用记录集类 ODynaset 的 DeleteRecord 方法删除记录。 — 378 — 10.4 本章小结 本章详细介绍了会议纪要管理系统的开发过程。通常一个会议包括多个议题的讨论, 因而可以把一个完整的会议纪要分为两个部分:会议基本信息和议题信息,会议基本信息 只有一条,议题信息有多条。在数据库的设计中,要把这一条会议基本信息和多条议题信 息作为一个完整的会议纪要来看,只需要在议题信息表中添加一个会议 ID 字段和会议基 本信息表中的会议 ID 字段一致,就建立了会议基本信息和议题信息的关联。这个设计技 巧是数据库设计中通常用到的,利用这种方法可以建立多层关系,比如还可以细分议题信 息为更小的讨论内容,然后在讨论内容表中添加一个议题信息 ID 字段,这样就能够建立 从会议基本信息到议题信息再到讨论内容的三层逻辑关系。有了这种关系,会议纪要管理 系统就可以根据会议基本信息表查询到详细的议题信息,也可以通过议题信息的内容查询 到会议基本信息表的信息,同时也得到所有该会议下的议题详细信息。 会议纪要管理系统使用了 OO4O 数据库开发技术。至此,本书已经详细介绍了所有的 Oracle 数据库开发技术,而 OO4O 是访问 Oracle 数据库最快的数据库开发技术。在系统中 用到了数据库类 ODatabase 和记录集类 ODynaset 来操作数据库,利用数据库类 ODatabase 实现对会议基本信息表的添加、修改和删除的操作,利用记录集类 ODynaset 实现对议题信 息表的添加、修改和删除的操作,从中可见利用 OO4O 编程的灵活性。
还剩386页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享pdf获得金币 ] 4 人已下载

下载pdf

pdf贡献者

baitf

贡献于2012-04-11

下载需要 15 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf