为了简化开发人员编写数据库应用程序,VC++的在其基础类库(MFC)里对ODBC API进行了封装,实现了一个面向对象的数据库编程接口,使VC++的数据库变得更加容易。
本章首先对MFC ODBC的概貌进行简要介绍,然后讲述利用MFC ODBC进行数据库开发的技巧,最后将通过具体数据库开发实例,详细讲述通过MFC ODBC开发数据库应用程序的方法和过程。
6.1 了解MFC ODBC
MFC是Microsoft Foundation Class(微软基础类库)的缩写,它的设计目标是简化开发人员的工作。MFC使开发人员创建基于Windows的应用程序,而不必掌握下层的Windows体系结构。由于数据库应用程序是管理数据的重要方面,Microsoft开发了ODBC API的封装程序,为ODBC编程提供了一个面向对象的方法。
MFC对ODBC的封装主要是开发了CDatabase类和CRecordSet类。使用时候,记得包含头文件D:\Microsoft Visual Studio 9.0\VC\atlmfc\include\afxdb.h
6.1.1 CDatabase类
CDatabase类用于应用程序建立同数据源的连接。CDatabase类包含一个m_hdbc变量,它代表了数据源的连接句柄。如果要建立CDatabase类的实例,应先调用该类的构造函数,再调用Open函数,通过调用,初始化环境变量,并执行与数据源的连接。关闭数据源连接的函数是Close。
CDatabase类提供了对数据库进行操作的函数,为了执行事务操作,CDatabase类提供了BeginTrans函数,当全部数据都处理完成后,可以通过调用CommitTrans函数提交事务,或者在特殊情况下通过调用Rollback函数将处理回退。
CDatabase类提供的函数可以用于返回数据源的特定信息,例如通过GetConnect函数返回在使用函数Open连接数据源时的连接字符串,通过调用IsOpen函数返回当前的CDatabase实例是否已经连接到数据源上,通过调用CanUpdate函数返回当前的CDatabase实例是否是可更新的,通过调用CanTransact函数返回当前的CDatabase实例是否支持事务操作,等等。
总之,CDatabase类为C++数据库开发人员提供了ODBC的面向对象的编程接口。
6.1.2 CRecordSet类
要实现对结果集的数据操作,就要用到CRecordSet类。CRecordSet类定义了从数据库接收或者发送数据到数据库的成员变量,CRecordSet类定义的记录集可以是表的所有列,也可以是其中的一列,这是由SQL语句决定的。
CRecordSet类的成员变量m_hstmt代表了定义该记录集的SQL语句句柄,m_nFields成员变量保存了记录集中字段的个数,m_nParams成员变量保存了记录集所使用的参数个数。
CRecordSet的记录集通过CDatabase实例的指针实现同数据源的连接,即CRecordSet的成员变量m_pDatabase。
如果记录集使用了WHERE子句,m_strFilter成员变量将保存记录集的WHERE子句的内容,如果记录集使用了ORDER BY子句,m_strSort成员变量将保存记录集的ORDER BY子句的内容。
由多种方法可以打开记录集,最常用的方法是使用Open函数执行一个SQL SELECT语句。有如下四种类型的记录集:
· CRecordset::dynaset:
动态记录集,支持双向游标,并保持同所连接的数据源同步,对数据的更新操作可以通过一个fetch操作获取。
· CRecordset::snapshot:
静态快照,一旦形成记录集,此后数据源的所有改变都不能体现在记录集里,应用程序必须重新进行查询,才能获取对数据的更新。该类型记录集也支持双向游标。
· CRecordset::dynamic:
同CRecordset::dynaset记录集相比,CRecordset::dynamic记录还能在fetch操作里同步其它用户对数据的重新排序。
· CRecordset::forwardOnly:
除了不支持逆向游标外,其它特征同CRecordset::snapshot相同。
6.2 MFC ODBC数据库访问技术
6.2.1 记录查询
使用CRecordSet的Open()和Requery()成员函数可以实现记录查询。需要注意的是,在使用CRecordSet的类对象之前,必须使用CRecordSet的成员函数Open()来获得有效的记录集。一旦使用过Open()函数,再次查询时使用Requery()函数就可以了。在调用Open()函数时,如果已经将一个打开的CDatabase对象指针传递给CRecordSet类对象的m_pDatabase成员变量,那么,CRecordSet类对象将使用该数据库对象建立ODBC连接;否则,如果m_pDatabase为空指针,对象就需要就新建一个CDatabase类对象并使其与缺省的数据源相连,然后进行CRecordSet类对象的初始化。缺省数据源由GetDefaultConnect()函数获得。也可以通过特定的SQL语句为CRecordSet类对象指定数据源,并以它来调用CRecordSet类的Open()函数,例如:
myRS.Open(AFX_DATABASE_USE_DEFAULT,strSQL);
如果没有指定参数,程序则使用缺省的SQL语句,即对在GetDefaultSQL()函数中指定的SQL语句进行操作,代码如下:
CString CMyRS::GetDefaultSQL()
{return _T("[Name],[Age]");}
对于GetDefaultSQL()函数返回的表名,对应的缺省操作是SELECT语句,例如:
SELECT * FROM BasicData,MainSize
在查询过程中,也可以利用CRecordSet类的成员变量m_strFilter和m_strSort来执行条件查询和结果排序。m_strFilter用于指定过滤字符串,存放着SQL语句中关键字WHERE后的条件语句;m_strSort用于指定用于排序的字符串,存放着SQL语句中关键字ORDER BY后的字符串。例如:
myRS.m_strFilter="Name='刘鹏'";
myRS.m_strSort="Age";
myRS.Requery();
数据库查询中对应的SQL语句为:
SELECT * FROM BasicData,MainSize WHERE Name='刘鹏' ORDER BY Age
除了直接赋值给成员变量m_strFilter以外,还可以通过参数化实现条件查询。利用参化可以更直观、更方便地完成条件查询任务。参数化方法的步骤如下:
(1)声明参变量,代码如下:
CString strName;
int nAge;
(2)在构造函数中初始化参变量如下:
strName =_T("");
nAge =0;
m_nParams=2;
(3)将参变量与对应列绑定,代码如下:
pFX->SetFieldType(CFieldExchange::param)
RFX_Text(pFX,_T("Name"), strName);
RFX_Single(pFX,_T("Age"), nAge);
完成以上步骤之后就可以利用参变量进行条件查询了,代码如下:
m_pmyRS->m_strFilter="Name=? AND age=?";
m_ pmyRS -> strName ="刘鹏";
m_ pmyRS ->nAge=26;
m_ pmyRS ->Requery();
参变量的值按绑定的顺序替换查询字串中的“?”通配符。
如果查询的结果是多条记录,可以利用CRecordSet类的成员函数Move(),MoveNext(),MovePrev(),MoveFirst()和MoveLast()来移动记录光标。
6.2.2 记录添加
使用AddNew()成员函数能够实现记录添加,需要注意的是,在记录添加之前必须保证数据库是以允许添加的方式打开的,代码如下:
m_ pmyRS ->AddNew(); // 在表的末尾添加新记录
m_ pmyRS ->SetFieldNull(&(m_pSet->m_type), FALSE);
m_ pmyRS ->m_strName="刘鹏"; // 输入新的字段值
m_ pmyRS ->m_nAge=26; // 输入新的字段值
m_ pmyRS -> Update(); // 将新记录存入数据库
m_ pmyRS ->Requery(); // 重新建立记录集
6.2.3 记录删除
调用Delete()成员函数能够实现记录删除,在调用Delete()函数后不需调用Update()函数,代码如下:
m_ pmyRS ->Delete();
if (!m_ pmyRS ->IsEOF())
m_ pmyRS ->MoveNext();
else
m_ pmyRS ->MoveLast();
6.2.4 记录修改
调用Edit()成员函数可以实现记录修改,在修改完成后需要调用Update()将修改结果存入数据库,代码如下:
m_ pmyRS ->Edit(); // 修改当前记录
m_ pmyRS ->m_strName="刘波"; // 修改当前记录字段值
...
m_ pmyRS ->Update(); // 将修改结果存入数据库
m_ pmyRS ->Requery();
6.2.5 撤销数据库更新操作
如果用户增加或者修改记录后希望放弃当前操作,可以在调用Update()函数之前调用Move()函数,就可以使数据库更新撤销了,代码如下:
CRecordSet::Move(AFX_MOVE_REFRESH);
该函数用于撤消增加或修改模式,并恢复在增加或修改模式之前的当前记录。其中参数AFX_MOVE_REFRESH的值为零。
6.2.6 直接执行SQL语句
虽然通过CRecordSet类我们可以完成大多数的数据库查询操作,而且在CRecordSet类的Open()成员函数中也可以提供SQL语句,但有的时候我们还想进行一些其他操作,例如建立新表、删除表、建立新的字段等等,这时就需要用到CDatabase类的直接执行SQL语句的机制。通过调用CDatabase类的ExecuteSQL()成员函数就能够完成QL语句的直接执行,代码如下:
BOOL CMyDB::ExecuteSQLWithReport (const CString& strSQL)
{
TRY
{
m_pMyDB->ExecuteSQL(strSQL); // 直接执行SQL语句
}
CATCH (CDBException,e)
{
CString strMsg;
strMsg.LoadString(IDS_EXECUTE_SQL_FAILED);
strMsg+=strSQL;
return FALSE;
}
END_CATCH
return TRUE;
}
需要注意的是,由于不同DBMS提供的数据操作语句不尽相同,直接执行SQL语句可能会破坏软件的DBMS无关性,因此在应用中应当慎用此类操作。
6.2.7 MFC ODBC的数据库操作过程
同ODBC API编程类似,MFC的ODBC编程也要先建立同ODBC数据源的连接,这个过程由一个CDatabase对象的Open函数实现。然后CDatabase对象的指针将被传递到CRecordSet对象的构造函数里,使CRecordSet对象与当前建立起来的数据源连接结合起来。
完成数据源连接之后,大量的数据库编程操作将集中在记录集的操作上。CRecordSet类的丰富的成员函数可以让开发人员轻松地完成基本的数据库应用程序开发任务。
当然,完成了所有的操作之后,在应用程序退出运行状态的时候,需要将所有的记录集关闭,并关闭所有同数据源的连接。
6.3 MFC ODBC编程实例
6.3.1 实例概述
需求调查与分析
某贸易公司经理需要对公司日常处理的业务通过计算机进行监控,因此需要开发一个浏览数据和报表的数据库应用软件,该软件的主要功能是数据表示和报表。数据的内容包括公司日常的销售报告、产品情况、客户情况、雇员情况以及与公司合作的运货商的情况,数据报表的主要目的是能够将浏览的信息以报表的方式打印出来。
数据库系统及其访问技术
在本实例里,我们采用MFC的ODBC数据库访问技术,从Access数据库里读取公司销售信息、产品情况、客户信息、雇员信息以及与公司合作的运货商的信息。这是一个小型的数据库应用,使用Access数据库就足够了。在这个实例里,我们借助MFC对ODBC封装类CRecordset,从该类派生应用程序里使用的CCommonRs类。以CCommonRs类为基础,实现对ODBC数据源的数据访问。
实例实现效果
ODBCDemo2是本书用于阐述MFC ODBC数据库编程的实例应用程序,该应用程序实现了对某销售公司日常销售业务的信息浏览和报表操作。应用程序运行界面如图6-1所示。
图6-1 ODBCDemo2实例应用程序的运行界面
6.3.2 实例实现过程
数据库设计
我们利用Microsoft Access工具设计本实例的数据库结构。在本实例里,我们需要利用数据库存放销售公司的如下信息:
· 日常销售信息:指公司日常销售帐单中的客户信息、雇员信息、订购日期、货主信息、运货商信息。
· 产品信息:指公司现经营产品的产品名称、供应商信息、类别信息、单价以及库存量等信息。
· 客户信息:指公司客户的客户名称、联系人信息和客户的地址信息。
· 雇员信息:指公司雇员的雇员姓名、头衔、尊称、出生日期、雇佣日期、联系信息及其上级等信息。
· 供应商信息:指与公司合作的供应商的名称、地址、联系人等信息。
· 运货商信息:指与公司合作的运货商的名称、电话信息。
· 产品类别信息:指公司所经营产品的类别。
我们为数据库设计了八个表,表“订单”和表“订单明细”存放公司日常销售信息,表“产品”存放公司的产品信息,表“客户”存放公司的客户信息,表“雇员”存放公司的雇员信息,表“供应商”存放公司的产品供应商信息,表“运货商”存放公司的运货商信息,表“类别”存放公司经营产品的类别信息。为了便于数据访问,我们还定义了三个视图,“SalesByCustomer”视图管理着以客户为统计方式的销售信息,“SalesByEmployee”视图管理着以雇员为统计方式的销售信息,“SalesByProduct”视图管理着以产品为统计方式的销售信息。
下面的表6-1列出了表“订单”的结构,表6-2列出了表“订单明细”的结构,表6-3列出了表“产品”的结构,表6-4列出了表“客户”的结构,表6-5列出了表“雇员”的结构,表6-6列出了表“供应商”的结构,表6-7列出了表“运货商”的结构,表6-8列出了表“类别”的结构。
表6-1 表“订单”的结构
字段名称 | 类型 | 字段名称 | 类型 |
订单ID(key) | 自动编号 | 运货费 | 货币 |
客户ID | 文本 | 货主名称 | 文本 |
雇员ID | 数字 | 货主地址 | 文本 |
订购日期 | 日期/时间 | 货主城市 | 文本 |
到货日期 | 日期/时间 | 货主地区 | 文本 |
发货日期 | 日期/时间 | 货主邮政编码 | 文本 |
运货商 | 数字 | 货主国家 | 文本 |
表6-2 表“订单明细”的结构
字段名称 | 类型 | 字段名称 | 类型 |
订单ID | 自动编号 | 数量 | 数字 |
产品ID | 数字 | 折扣 | 数字 |
单价 | 货币 |
|
|
表6-3 表“产品”的结构
字段名称 | 类型 | 字段名称 | 类型 |
产品ID(key) | 自动编号 | 单价 | 货币 |
产品名称 | 文本 | 库存量 | 数字 |
供应商ID | 数字 | 订购量 | 数字 |
类别ID | 数字 | 再订购量 | 数字 |
单位数量 | 文本 | 中止 | 是/否 |
表6-4 表“客户”的结构
字段名称 | 类型 | 字段名称 | 类型 |
客户ID(key) | 自动编号 | 地区 | 文本 |
公司名称 | 文本 | 邮政编码 | 文本 |
联系人姓名 | 文本 | 国家 | 文本 |
联系人头衔 | 文本 | 电话 | 文本 |
地址 | 文本 | 传真 | 文本 |
城市 | 文本 |
|
|
表6-5 表“雇员”的结构
字段名称 | 类型 | 字段名称 | 类型 |
名字ID | 自动编号 | 国家 | 文本 |
头衔 | 文本 | 邮政编码 | 文本 |
尊称 | 文本 | 家庭电话 | 文本 |
出生日期 | 日期/时间 | 分机 | 文本 |
雇用日期 | 日期/时间 | 照片 | OLE对象 |
地址 | 文本 | 备注 | 备注 |
城市 | 文本 | 上级 | 文本 |
地区 | 文本 |
|
|
表6-6 表“供应商”的结构
字段名称 | 类型 | 字段名称 | 类型 |
供应商ID(key) | 自动编号 | 地区 | 文本 |
公司名称 | 文本 | 邮政编码 | 文本 |
联系人姓名 | 文本 | 国家 | 文本 |
联系人头衔 | 文本 | 电话 | 文本 |
地址 | 文本 | 传真 | 文本 |
城市 | 文本 | 主页 | 超级链接 |
表6-7 表“运货商”的结构
字段名称 | 类型 | 字段名称 | 类型 |
运货商ID(key) | 自动编号 | 电话 | 货币类型 |
公司名称 | 文本 |
|
|
表6-8 表“类别”的结构
字段名称 | 类型 | 字段名称 | 类型 |
类别ID | 自动编号 | 说明 | 备注 |
类别名称 | 文本 | 图片 | OLE对象 |
在实例光盘的Database目录下,stocks.mdb文件是存放公司销售信息的Access数据库文件,读者可以查看这个文件,了解详细信息。
创建ODBCDemo2工程
ODBCDemo2工程是一个基于单文档的应用程序,创建应用程序工程时需要选择基于单文档的应用程序类型。
操作步骤:
1. (1) 打开VC++的工程创建向导。从VC++的菜单中执行“File>New”命令,将VC++ 6.0工程创建向导显示出来。如果当前的选项标签不是Project,单击Project选项标签将它选中。在左边的列表里选择MFC AppWizard(exe)项,在Project Name编辑区里输入工程名称“ODBCDemo2”,并在Location编辑区里调整工程路径,如图6-2所示。
图6-2 工程创建向导
2. (2) 选择应用程序的框架类型。单击“工程创建向导”窗口的OK按钮,进入“MFC AppWizard – Step 1”对话框。首先选择应用程序的框架类型。如图6-3所示。在本工程里,选择“Single document”,保持资源的语言类型为“中文”,单击“Next >”按钮。
图6-3 选择应用程序的框架类型
3. (3) 进入“MFC AppWizard – Step 2 of 6”对话框,设置应用程序数据库特性。在对话框里选择None,如图6-4所示。
4. (4) 设置应用程序对复杂文档的支持。在“MFC AppWizard – Step 2 of 6”对话框里,单击“Next >”按钮,进入“MFC AppWizard – Step 3 of 6”对话框。在对话框里选择如下两项:
· None
· ActiveX Controls
图6-4 设置应用程序数据库特性
如图6-5所示,单击“Next >”按钮。
图6-5 设置应用程序对复杂文档的支持
5. (5) 进入“MFC AppWizard – Step 4 of 6”对话框,设置应用程序的特征信息。如图6-6所示对话框是工程的特征信息,在本例中,ODBCDemo2工程有如下特征:
· Docking toolbar
· Initial statusbar
· Printing and print preview
· 3D controls
· Normal
6. (6) 在“MFC AppWizard – Step 4 of 6”对话框里单击“Next >”按钮,进入“MFC AppWizard – Step 5 of 6”对话框,选择工程风格和MFC类库的加载方式。在对话框里设置如下三项:
· MFC Standard
· Yes, Please
· As shared DLL
7. 如图6-7所示,单击“Next >”按钮,进入“MFC AppWizard – Step 6 of 6”对话框。
图6-7 设置应用程序特征信息
图6-8 选择工程风格和MFC类库的加载方式
8. (7) “MFC AppWizard – Step 6 of 6”对话框显示工程创建中的类信息。ODBCDemo2工程包含了四个类:
· CODBCDemo2View类,工程视图类
· CODBCDemo2App类,工程的应用类
· CMainFrame类,工程主框架类
· CODBCDemo2Doc类,工程文档类
这四个类构成了应用程序工程的主要框架。我们为CODBCDemo2View类选择ClistView基类,如图6-9所示。
图6-9 显示工程创建中的类信息
9. (8) 在对话框“MFC AppWizard – Step 6 of 6”里单击Finish按钮,完成工程创建。工程创建向导将该次工程创建的信息显示在一个“New Project Information”对话框里,如图6-10所示。在对话框里单击“OK”按钮,ODBCDemo2工程创建完成。
图6-10 工程创建信息
建立数据源
建立数据源的方法在6.3.2节里已经进行了介绍,这里只说明本实例在建立数据源时的不同之处。
本实例用到的数据源名称是“ODBCDemo2”,其描述为“Data source for ODBC MFC programming”,创建数据源时在“配置创建的新数据源”步骤里需要同ODBCDemo1数据源有所区别。另外,数据源所支持的数据库为stocks.mdb文件,我们已经在6.3.2节里完成了它的设计。
设计应用程序界面
在6.3.2节里,我们利用工程创建向导创建了一个基于单文档界面的工程,本节应用程序界面的设计工作主要是菜单和按钮的添加。为了为报表设置报表的内容和格式,我们还需要为应用程序设计一个报表向导,这个向导由三个步骤组成,因此我们需要设计三个对话框以实现这三个步骤的界面。
1.设计应用程序的主菜单
需要为应用程序设计的菜单包括:销售报告菜单、产品信息菜单、客户信息菜单、雇员信息菜单、供应商信息菜单、运货商信息菜单、类别菜单以及报表操作菜单。这些菜单的标识、标题以及提示信息如表6-9所示。
表6-9 工程的菜单资源
标识 | 标题 | 提示信息 | |
文件 | ID_OPTION | 选项 | 应用程序设置选项 |
ID_APP_EXIT | 退出 | 退出应用程序;提示保存文档\n退出 | |
销售报告 | ID_SALE_PRODUCT | 按产品 | 按照产品显示销售报告。\n产品销售报告 |
ID_SALE_CUSTOMER | 按客户 | 按照客户显示销售报告。\n客户销售报告 | |
ID_SALE_EMPLOYEE | 按雇员 | 按照雇员显示销售报告。\n雇员销售报告 | |
产品 | ID_VIEW_PRODUCT | 产品信息 | 显示产品信息 |
客户 | ID_VIEW_CUSTOMER | 客户信息 | 显示客户信息 |
雇员 | ID_EXPLOYEE | 雇员信息 | 显示雇员信息 |
运货商 | ID_VIEW_TRANSPORTOR | 运货商信息 | 显示运货商信息 |
供应商 | ID_VIEW_PROVIDER | 供应商信息 | 显示供应商信息 |
报表 | ID_REPORT_SETUP | 设置 | 设置报表标题、字段内容、注脚等内容的字体。\n报表设置 |
ID_FILE_PRINT_PREVIEW | 打印预览 | 显示整页\n打印预览 | |
ID_FILE_PRINT_SETUP | 打印设置 | 改变打印机及打印选项\n打印设置 | |
ID_FILE_PRINT | 打印 | 打印活动文档\n打印 |
2.设计应用程序的按钮
需要为应用程序设计的按钮主要是销售报告的三种显示方式。这三个按钮的标识、图标以及提示信息如表6-10所示。
表6-10 工程的按钮资源
标识 | 图标 | 提示信息 |
ID_SALE_PRODUCT | 按照产品显示销售报告。\n产品销售报告 | |
ID_SALE_EMPLOYEE | 按照雇员显示销售报告。\n雇员销售报告 | |
ID_SALE_CUSTOMER | 按照客户显示销售报告。\n客户销售报告 |
3.设计应用程序的报表向导
需要设计三个对话框:IDD_WZDFIELD,IDD_WZDFORMAT和IDD_WZDPREVIEW。这三个对话框分别代表了报表向导的三个步骤:报表字段选择、格式设置以及设置信息浏览三个步骤。
10. (1) 设计IDD_WZDFIELD对话框。使用VC++的“Insert>Resource”菜单命令可以将Dialog(对话框)资源加入到工程里。IDD_WZDFIELD对话框供用户选择报表字段,它的标题是“选择报表字段信息”,字段选择对话框的其它资源如表6-11所示。
表6-11 IDD_WZDFIELD对话框的资源
资源类型 | 资源ID | 标题 | 功能 |
编辑框 | IDC_MAINTITLE |
| 报表的大标题 |
编辑框 | IDC_FOOTER |
| 报表的脚注 |
编辑框 | IDC_FIELDTITLE |
| 报表的字段标题 |
列表框 | IDC_LISTPRE |
| 候选的报表字段列表 |
列表框 | IDC_LISTSEL |
| 选用的报表字段列表 |
组合框 | IDC_CBPNFORMAT |
| 页码类型组合框 |
复选框 | IDC_CHKPAGENUM | 使用页码 | 表示是否使用页码 |
标签 | IDC_STATIC | 报表标题: | 报表标题标签 |
标签 | IDC_STATIC | 供选择报表字段: | 候选报表字段列表标题 |
标签 | IDC_STATIC | 选用的报表字段: | 选用报表字段列表标题 |
标签 | IDC_STATIC | 字段标题: | 字段标题标签 |
标签 | IDC_STATIC | 注脚文本: | 注脚文本标签 |
标签 | IDC_STATIC | 页码格式: | 页码格式标签 |
按钮 | IDC_SELECT | > | 选用候选字段按钮 |
按钮 | IDC_DESELECT | < | 删除选用字段按钮 |
按钮 | IDC_SELECTALL | >| | 全部选中候选字段按钮 |
按钮 | IDC_DESELECTALL | |< | 全部删除选用字段按钮 |
设计完成后,字段选择对话框如图6-11所示。
图6-11 设计完成后的字段选择对话框
11. (2) 设计IDD_WZDFORMAT对话框。使用VC++的“Insert>Resource”菜单命令可以将Dialog(对话框)资源加入到工程里。IDD_WZDFORMAT对话框供用户选择报表格式,它的标题是“设置报表格式”。报表格式设置对话框的其它资源如表6-12所示。
表6-12 IDD_WZDFORMAT对话框的资源
资源类型 | 资源ID | 标题 | 功能 |
组合框 | IDC_CBTITLEFONT |
| 提供标题字体选择 |
组合框 | IDC_CBHEADFONT |
| 提供字段标题字体选择 |
组合框 | IDC_CBNORMALFONT |
| 提供正文字体选择 |
组合框 | IDC_CBFOOTERFONT |
| 提供注脚字体选择 |
组合框 | IDC_CBTITLESIZE |
| 提供标题字体大小选择 |
组合框 | IDC_CBHEADSIZE |
| 提供字段标题字体大小选择 |
组合框 | IDC_CBNORMALSIZE |
| 提供正文字体大小选择 |
组合框 | IDC_CBFOOTERSIZE |
| 提供注脚字体大小选择 |
静态控件 | IDC_PREVIEWAREA |
| 设置效果预览区域 |
标签 | IDC_STATIC | 字体名称: | 字体标签 |
标签 | IDC_STATIC | 字体大小: | 字体大小标签 |
标签 | IDC_STATIC | 标题(&T): | 标题标签 |
标签 | IDC_STATIC | 列头(&M): | 列头标签 |
标签 | IDC_STATIC | 正文(&M): | 正文标签 |
标签 | IDC_STATIC | 注脚(&F): | 注脚标签 |
标签 | IDC_STATIC | 预览: | 预览区域标签 |
设计完成后,报表格式设置对话框如图6-12所示。
图6-12 设计完成后的设置报表格式对话框
(3)设计IDD_WZDPREVIEW对话框。使用VC++的“Insert>Resource”菜单命令可以将Dialog(对话框)资源加入到工程里。IDD_WZDPREVIEW对话框供用户选择报表信息的显示与确认,它的标题是“报表预览”。设置信息浏览对话框的其它资源如表6-13所示。
6-13 IDD_WZDPREVIEW对话框的资源
资源类型 | 资源ID | 标题 | 功能 |
标签 | IDC_REPORTINFO | Info | 信息显示区域 |
静态控件 | IDC_STATIC | 信息 | 标签 |
设计完成后,设置信息浏览对话框如图6-13所示。
图6-13 设计完成后的“报表预览”对话框
编写工程代码
1.创建CCommonRs类
CCommonRs类由CRecordset类派生而来。
创建CCommonRs类的操作步骤:
12. (1) 执行菜单命令“Insert>New Class…”,VC++弹出“New Class”对话框,如图6-14所示。
图6-14 New Class对话框
(2)在“New Class”对话框里保持Class Type项为MFC Class,在Name编辑框里输入“CCommonRs”,然后在Base class下拉列表框里选择派生的父类CRecordset,如图6-15所示。
图6-15 设置派生的类名称与父类
(3)在“New Class”对话框里单击OK按钮,VC++弹出Database Options对话框,在这里,为CCommonRs设置缺省的数据源。选择ODBC数据源为刚刚创建的ODBCDemo2,保持其它设置,如图6-16所示。
图6-16 Database Options对话框
(4)在Database Options对话框里按下OK按钮,VC++弹出Select Database Tables对话框,为CCommonRs设置缺省的表,如图6-17所示。
(5)这里的表并不表示对类CCommonRs的操作只能在这个表上进行,这只是一个缺省的设置,因此可以任意选择一个表,注意,如果不选择任何表,单击OK按钮时操作是不能进行下去的。选择任一个表,单击OK按钮,完成类CCommonRs的创建。
图6-17 Select Database Tables对话框
2.创建全局变量
整个应用程序是通过一个CCommonRs对象对数据库进行操作的,这个对象不仅应用在视图类里,还被报表向导引用,以获取结果集的字段信息。因此有必要将这个CCommonRs对象声明为全局对象。我们在ODBCDemo2View.cpp文件的头部对CCommonRs对象声明如下:
#include "CommonRs.h"
CCommonRs *m_pCommonRS;
第一条语句将类CCommonRs的声明包括到ODBCDemo2View.cpp文件里,第二条语句声明CCommonRs对象的引用指针。
3.在CODBCDemo2View类里声明报表信息变量
为了将报表设置信息保存起来,下面为CODBCDemo2View类声明如下的报表信息变量:
public:
//报表信息设置变量
// 标题和注脚
CString m_strReportTitle;
CString m_strReportFooter;
BOOL m_fHavePage;
UINT m_uPageType;
// 报表字段
CStringArray m_saSelectedFields;
CStringArray m_saSelectedFieldsTitle;
// 报表格式
// 报表字体
CString m_strFontTitle;
CString m_strFontContentHead;
CString m_strFontContent;
CString m_strFontFooter;
// 字体大小
UINT m_uSizeTitle;
UINT m_uSizeContentHead;
UINT m_uSizeContent;
UINT m_uSizeFooter;
当报表向导完成了报表信息设置后,这些信息将保存到上述变量里。
4.编写CODBCDemo2View类的消息响应函数
对所有菜单命令,应用程序都将在CODBCDemo2View类里响应。前面,我们已经介绍了使用VC++的ClassWizard工具添加菜单命令响应函数的方法,这里只介绍如何为这些响应函数编写操作代码。
(1)编写“销售报告>按产品”菜单命令响应函数OnSaleProduct()。
OnSaleProduct()函数将销售报告按照产品进行统计,并将结果显示到视图里,它的代码如下:
CListCtrl& ctrlList = (CListCtrl&) GetListCtrl();
ctrlList.DeleteAllItems();
while(ctrlList.DeleteColumn(0));
UpdateWindow();
CString strSQL;
strSQL = _T("SELECT * FROM SalesByProduct;");
if(!ShowInformation(strSQL)) AfxMessageBox("数据获取失败!");
m_uStatus = TABLE_SPRODUCTS;
SQL语句“SELECT * FROM SalesByProduct”从数据库视图SalesByProduct里将所有数据检索出来,SalesByProduct视图是数据库设计阶段创建的一个数据库视图,它的意义就在于将销售报告(订单)按照产品进行统计。函数ShowInformation()将strSQL里的SQL语句检索结果显示到视图里。
(2)编写“销售报告>按客户”菜单命令响应函数OnSaleCustomer ()。
OnSaleProduct()函数将销售报告按照客户进行统计,并将结果显示到视图里,它的代码如下:
CListCtrl& ctrlList = (CListCtrl&) GetListCtrl();
ctrlList.DeleteAllItems();
while(ctrlList.DeleteColumn(0));
UpdateWindow();
CString strSQL(_T("select * from SalesByCustomer"));
if(!ShowInformation(strSQL)) AfxMessageBox("数据获取失败!");
m_uStatus = TABLE_SCUSTOMERS;
该函数操作方法同OnSaleProduct()函数类似,不同的是,它从SalesByCustomer数据库视图里检索数据。SalesByCustomer数据库视图能够将销售报告(订单)按照客户进行统计。
(3)编写“销售报告>按雇员”菜单命令响应函数OnSaleEmployee ()。
OnSaleEmployee ()函数将销售报告按照雇员进行统计,并将结果显示到视图里,它的代码如下:
CListCtrl& ctrlList = (CListCtrl&) GetListCtrl();
ctrlList.DeleteAllItems();
while(ctrlList.DeleteColumn(0));
UpdateWindow();
CString strSQL(_T("select * from SalesByEmployee"));
if(!ShowInformation(strSQL)) AfxMessageBox("数据获取失败!");
m_uStatus = TABLE_SIMPLOYEES;
该函数操作方法同OnSaleProduct()函数类似,不同的是,它从SalesByEmployee数据库视图里检索数据。SalesByEmployee数据库视图能够将销售报告(订单)按照雇员进行统计。
(4)编写“产品>产品信息”菜单命令响应函数OnViewProduct ()
OnViewProduct ()函数将产品信息显示到视图里,它的代码如下:
CListCtrl& ctrlList = (CListCtrl&) GetListCtrl();
ctrlList.DeleteAllItems();
while(ctrlList.DeleteColumn(0));
UpdateWindow();
CString strSQL;
CString strSQL(_T("select * from 产品"));
if(!ShowInformation(strSQL)) AfxMessageBox("数据获取失败!");
m_uStatus = TABLE_SPRODUCTS;
该函数从“产品”数据库表里检索数据,并将检索结果显示到视图里。
(5)编写“客户>客户信息”菜单命令响应函数OnViewCustomer ()
OnViewCustomer ()函数将客户信息显示到视图里,它的代码如下:
CListCtrl& ctrlList = (CListCtrl&) GetListCtrl();
ctrlList.DeleteAllItems();
while(ctrlList.DeleteColumn(0));
UpdateWindow();
CString strSQL(_T("select * from 客户"));
if(!ShowInformation(strSQL)) AfxMessageBox("数据获取失败!");
m_uStatus = TABLE_CUSTOMERS;
该函数从“客户”数据库表里检索数据,并将检索结果显示到视图里。
(6)编写“雇员>雇员信息”菜单命令响应函数OnExployee ()
OnExployee ()函数将雇员信息显示到视图里,它的代码如下:
CListCtrl& ctrlList = (CListCtrl&) GetListCtrl();
ctrlList.DeleteAllItems();
while(ctrlList.DeleteColumn(0));
UpdateWindow();
CString strSQL(_T("select * from 雇员"));
if(!ShowInformation(strSQL)) AfxMessageBox("数据获取失败!");
m_uStatus = TABLE_IMPLOYEES;
该函数从“雇员”数据库表里检索数据,并将检索结果显示到视图里。
(7)编写“供应商>供应商信息”菜单命令响应函数OnViewProvider ()
OnViewProvider ()函数将供应商信息显示到视图里,它的代码如下:
CListCtrl& ctrlList = (CListCtrl&) GetListCtrl();
ctrlList.DeleteAllItems();
while(ctrlList.DeleteColumn(0));
UpdateWindow();
CString strSQL(_T("select * from 供应商"));
if(!ShowInformation(strSQL)) AfxMessageBox("数据获取失败!");
m_uStatus = TABLE_PRODUCTS;
该函数从“供应商”数据库表里检索数据,并将检索结果显示到视图里。
(8)编写“运货商>运货商信息”菜单命令响应函数OnViewTransportor ()
OnViewTransportor ()函数将运货商信息显示到视图里,它的代码如下:
CListCtrl& ctrlList = (CListCtrl&) GetListCtrl();
ctrlList.DeleteAllItems();
while(ctrlList.DeleteColumn(0));
UpdateWindow();
CString strSQL(_T("select * from 运货商"));
if(!ShowInformation(strSQL)) AfxMessageBox("数据获取失败!");
m_uStatus = TABLE_PRODUCTS;
该函数从“运货商”数据库表里检索数据,并将检索结果显示到视图里。
(9)编写ShowInformation()函数
首先在CODBCDemo2View类的声明里添加该函数的声明:
public:
BOOL ShowInformation(CString strSQL);
然后在CODBCDemo2View类的实现文件里添加该函数的实现代码:
BOOL CODBCDemo2View::ShowInformation(CString strSQL)
{
CRect rect;
CListCtrl& ctrlList = (CListCtrl&) GetListCtrl();
ctrlList.GetWindowRect(rect);
try{
BeginWaitCursor();
// 如果结果集已被打开,则关闭它
if(m_pCommonRS->IsOpen()) m_pCommonRS->Close();
// 打开结果集
m_pCommonRS->Open(CRecordset::dynaset, strSQL);
if(!m_pCommonRS->IsEOF()){
m_pCommonRS->MoveLast();
m_pCommonRS->MoveFirst();
}
// 取得结果集的字段个数
int nFieldCount = m_pCommonRS->GetODBCFieldCount();
CODBCFieldInfo fieldinfo;
// 读取字段信息
for(int n=0;n<nFieldCount;n++){
m_pCommonRS->GetODBCFieldInfo(n, fieldinfo);
int nWidth = ctrlList.GetStringWidth(fieldinfo.m_strName) + 15;
ctrlList.InsertColumn(n, fieldinfo.m_strName,
LVCFMT_LEFT, nWidth);
}
// 读取记录信息
CString strValue;
m_pCommonRS->MoveFirst();
int nCount = 0;
while(!m_pCommonRS->IsEOF()){
ctrlList.InsertItem(nCount, strValue);
for(int j=0;j<nFieldCount;j++){
m_pCommonRS->GetFieldValue(j, strValue);
ctrlList.SetItemText(nCount, j, strValue);
}
m_pCommonRS->MoveNext();
nCount ++;
}
EndWaitCursor();
}
catch(CDBException *e){
e->ReportError();
EndWaitCursor();
return FALSE;
}
return TRUE;
}
该函数首先检测m_pCommonRS对象的状态,如果该对象处于打开状态,则关闭这个对象。接下来m_pCommonRS对象打开strSQL所包含的SQL语句结果集,并将结果集游标移动到头部。ShowInformation()函数需要取得结果集的字段信息,作为结果集显示的标题信息,m_pCommonRS对象的GetODBCFieldInfo()能够实现这个功能。最后函数读取结果集中的数据,并显示在视图里。