第4章 学生在线考试系统
随着计算机技术的发展和推广,现代教学中很多学科都逐步采用计算机作为工具进行考试,即无纸化考试。无纸化考试系统既能较客观、公正地反映学生的真实水平,又能节约人力、物力,提高考试效率。
东方学院学生在线考试系统是使用Visual C++开发的基于C/S结构的考试系统,主要用于配合计算机、英语等学科教学工作,对学生进行阶段性的考试测验,掌握学生的学习状况,并为下一阶段全面推进无纸化考试工作积累一定的基础。
本章的学习重点:
◆ ADO直接操作Access数据库文件。
◆ 考试流程设计。
◆ 标签页窗口的创建。
◆ SQL数据查询和数据操作语句。
4.1 开发背景
在信息技术迅速发展的今天,网络对于大多数人已不再陌生,并且其应用在人们的工作、学习和生活中越来越多地发挥着不可替代的作用。近年来随着软件工程技术、信息通信技术的快速发展,以及计算机网络技术的日趋成熟,网络教育在人们的教育活动中逐步得到普及。网上考试是网络教育不可缺少的组成部分,是网络教育的一个重要环节。
网上考试在国外一些国家已经得到了蓬勃发展,人们选学课程和考试都是通过网上进行的。例如国外一些著名的考试,如Microsoft公司的MCSE(Microsoft系统工程师认证考试)、GMAT(工商管理硕士入学考试)、托福考试、GRE(美国研究生入学考试)等,都是采用网上考试的形式进行的。
另外,在教育工作中,为学生考试出试卷和批改试卷是老师们最头痛的,不仅消耗大量的时间,而且消耗大量的精力体力。因此,考试过程由人工操作转向计算机操作是必然的结果。
正是基于此,为东方学院开发了基于C/S结构的学生在线考试测试系统。主要目的是辅助计算机和英语教学,对学生的学习情况进行阶段性的测试,掌握学生的学习状况,为以后全面推行网上考试积累经验。
4.2 系统分析
4.2.1 需求分析
根据学生考试的特点和学院的实际情况,该系统应以考试流程为基础,从专业角度出发,提供科学有效的考试模式。具体来讲,要求本系统具有以下功能。
学生考试:学生登录后要求在规定的时间内按照要求,以及提示完成各种试题的考试,当提交试卷后可以完成试题的评分。
试题维护:管理员可以设置试卷,对试题进行添加、修改,以及相关维护。
信息查询:可实现管理员对学生的成绩进行查询,按分数归类。
数据维护:对数据库进行备份,还原及初始化操作,减轻用户的工作量。
此外,对系统的性能主要有以下几个方面需求。
● 系统要求具有开放性,可运行在主流Windows操作系统平台上,便于以后系统的升级。
● 系统在设计过程中应充分考虑到可扩充性例如,在系统使用过程中,可能提出各种新的需求,这就要求系统拥有良好的可扩充性。
● 界面要具有友好性。由于本系统是面向广大考试学生,因此系统应提供统一的操作界面和方式,要求操作界面美观大方,布局合理,功能完善,容易上手。
4.2.2 功能分析
显然系统需要设置两种用户角色,管理员和考生。
考生即参加考试的学生,主要进行选择试卷、进行考试答题操作,具体需要实现的功能如下。
● 在系统中注册。
● 登录考试系统。
● 选择考试试卷。
● 进行答题操作。
● 提交答案,查看成绩。
管理员角色,可以主要实现考试系统的后台管理,如试题的维护、考试成绩的统计、数据库的维护等,具体需要实现的功能如下。
● 添加试卷。
● 为试卷设置试题。
● 查询统计成绩。
● 对数据库进行备份、还原。
4.3 系统设计
4.3.1 绘制用例图设计系统功能
东方学院在线考试系统,考生必须能够正常的登录、选择试卷、进行考试、提交试卷查看成绩等操作,其用例图如图4-1所示。
图4-1 考生用例图
管理员需要进行出题并对系统进行后台维护,其用例图如图4-2所示。
图4-2 管理员用例图
4.3.2 绘制系统流程图
本系统首先对登录用户身份进行验证,根据登录身份分别提供不同的使用界面。如果是考生,则选择试卷,显示试题,进行答题;如果是管理员,则提供后台管理操作界面,进行试卷维护、数据库维护和成绩统计等操作,系统的总流程可表示为如图4-3所示。
图4-3 系统操作流程图
4.3.3 开发工具和开发技术的选择
基于C/S结构的在线考试系统,其开发主要包括后台数据库的建立和维护,以及前端应用程序的开发两个方面。
本系统的前端应用程序设计是在Windows XP中文版操作系统环境下,使用Visual C++6.0中文版开发成功的。后端数据库采用Micorsoft的Access数据库系统,通过ADO数据库开发技术直接操作Access数据库文件。
4.3.4 系统的运行环境
系统可以直接在Win98、Win2000、WinXP环境下运行,因为数据库的操作是通过ADO直接操作Access数据库文件。因此程序运行时,不需要设置ODBC数据源,只需将数据库文件直接复制到应用程序所在的目录下。
4.3.5 系统演示
程序启动,首先弹出如图4-4所示的“用户登录”窗口,只有输入正确的用户类型、用户名和密码后,才能进入系统。
图4-4 “用户登录”窗口
如果登录用户为考生,则会弹出“考试信息”窗口,供考生选择考试试卷,如图4-5所示。
图4-5 “考试信息”窗口
选择了试卷后,单击“确定”按钮,即进入答题主界面窗口,如图4-6所示。考生通过单选按钮选择试题的答案,单击“上一题”和“下一题”按钮可以循环答题。
图4-6 答题主界面窗口
答题完毕后,单击“交卷”按钮结束答题,退出答题窗口,并弹出“考试结果信息”窗口,显示考生的答题状况和最终分数,如图4-7所示。
图4-7 “考试结果信息”窗口
而如果登录用户为管理员,则系统进入后台管理对窗口。它有3个标签页,分别为试题管理、学生成绩查询和数据库管理。
在试题管理标签页中,可以查看数据库中的试卷和试题,并可以进行添加、删除试卷,添加、删除和修改试题操作,如图4-8所示。
图4-8 试题管理标签页
在学生成绩查询标签页中,可以查看所有考生的各门考试成绩,如图4-9所示。
图4-9 学生成绩查询标签页
在数据库管理标签页中,管理员可以通过功能按钮执行备份、还原和初始化数据库操作。
4.3.6 系统类库设计
学生在线考试系统主框架的设计是通过MFC创建向导创建的基于对话框的窗口程序,系统的类库主要设计如下。
● 数据库操作类ADOConn:通过自己创建的ADOConn类,实现使用ADO连接、操作、断开Access数据库。
● 登录类CLOGIN:派生自CDialog类,用于创建登录对话框。
● 账号管理类:包括CRegister、CFind,派生自CDialog类,用于创建用户注册对话框和找回密码对话框。
● 管理员后台管理类:管理员后台管理类主要包含一些对话框类,如表4-1所示。
表4-1 管理员后台管理类及功能
● 主界面类:主界面类包含系统本身提供的框架类,另外还包含一些考生考试的相关类,如表4-2所示。
表4-2 主界面类与考生考试类
4.4 数据库分析与设计
4.4.1 数据库分析
因为系统为C/S结构,且经常达到数百人同时访问数据库,所示后台的数据库系统设计采用的是Micorsoft的SQL Server数据库系统。但是这里为了便于代码的开发与测试,首先采用Micorsoft的Access数据库系统,通过ADO数据库开发技术直接操作Access数据库文件。待开发测试成熟以后,即可将Access数据库数据直接导入到SQL Server数据库系统中,更改一下ADO操作数据库的方式即可。
4.4.2 数据库概念设计
通过对在线考试系统的分析,需要包含试题实体、试题的答案选项实体、考生考试成绩实体、考生答题实体和登录用户实体。
试题实体记录了试题的相关信息,其E-R图如图4-10所示。
图4-10 图书实体E-R图
试题答案选项实体记录了试题对应的答案选项(4个),其E-R图如图4-11所示。
图4-11 图书实体E-R图
考生考试成绩实体的E-R图可表示为,如图4-12所示。
图4-12 考生考试成绩实体的E-R图
考生答题实体的E-R图可表示为,如图4-13所示。
图4-13 考生答题实体的E-R图
登录用户实体的E-R图可表示为,如图4-14所示。
图4-14 登录用户实体的E-R图
4.4.3 数据库逻辑结构设计
根据前面设计好的各实体E-R图,就可以创建相关的数据库的逻辑结构,本系统数据库共创建了6个表,下面分别介绍。
TestQuestion表用于记录试卷考题的基本信息,该表的逻辑结构如表4-3所示。
表4-3 TestQuestion表字段描述
TestAnswer表与TestQuestion表对应,记录考题的答案选项,该表的逻辑结构如表4-4所示。
表4-4 TestAnswer表字段描述
Subject表用于记录试卷信息,该表的逻辑结构如表4-5所示。
表4-5 Subject表字段描述
ExammingInfo表是一个临时表,它用于记录考试过程中,考生的答题信息,该表的逻辑结构如表4-6所示。
表4-6 ExammingInfo表字段描述
Score表用于记录考生的考试结果信息,该表的逻辑结构如表4-7所示。
表4-7 Score表字段描述
Register表用于记录系统登录用户的信息,该表的逻辑结构如表4-8所示。
表4-8 Register表字段描述
4.4.4 数据库的创建
各表设计完毕后就可以创建数据库。在Access 2000中,首先创建数据库ExamSystem,然后在该数据库中创建各表,最终创建结果如图4-15所示。
图4-15 Access创建数据库和表
创建完毕后,即得到数据库文件“ExamSystem.mdb”。另外,还需要在Access中设置各表之间的关系,如图4-16所示。
图4-16 各表之间的关系
4.5 公共类(ADOConn)设计
在本系统中,对数据库的操作是通过采用ADO组件来实现的。为了便于对数据库的操作,这里设计了一个公用类ADOConn,通过构造ADO对象,实现对数据库的各种操作。
4.5.1 ADOConn类的声明
ADOConn类是一个基本类,在该类中声明了一些基本的ADO对象,以及连接、关闭、更新和查询数据库操作函数,具体代码如下。
例程4.1代码位置:光盘\第4章\ ExamSystem \ ADOConn.h
01 #import"C:\\Program Files\\Common Files\\System\\ado\\msado15.dll"no_namespace\ 02 rename("EOF","adoEOF") 03 class ADOConn 04 { 05 public: 06 BOOL ExecuteSQL(_bstr_t bstrSQL); //执行SQL操作语句 07 _RecordsetPtr&GetRecordSet(_bstr_t bstrSQL); //执行SQL查询语句 08 void ExitConn(); //断开连接 09 void OnInitADOConn(); //连接数据库 10 ADOConn(); 11 virtual~ADOConn();
12 _ConnectionPtr m_pCon; //_ConnectionPtr对象 13 _RecordsetPtr m_pRs; //_RecordsetPtr对象 14 };
第1、第2行代码使用导入符号#import导入ADO库文件,为了避免常数冲突,将常数EOF改名为adoEOF。
4.5.2 ADOConn类的实现
OnInitADOConn函数实现连接Access数据库,代码如下。
例程4.2代码位置:光盘\第4章\ ExamSystem \ ADOConn.cpp
01 void ADOConn::OnInitADOConn() //连接数据库 02 { 03 try 04 { 05 m_pCon.CreateInstance("ADODB.Connection"); 06 m_pCon->ConnectionTimeout=3; //连接尝试时间 07 //连接Access数据库 08 m_pCon->Open("Provider=Microsoft.Jet.OLEDB.4.0;DataSource= 09 ExamSystem.mdb","","",adModeUnknown); 10 } 11 catch(_com_error e) //捕捉异常 12 { 13 AfxMessageBox(e.Description()); 14 } 15 }
第8、第9 行代码实现使用OLEDB驱动对Access数据库的连接,指定数据库文件为“ExamSystem.mdb”。
通常在使用ADO操作数据库之前必须首先创建一个连接对象_ConnectionPtr。连接对象为ADO对数据源的操作提供了一个操作环境,可以用于操作事务处理。
ExitConn函数实现断开连接Access数据库,代码如下。
例程4.3代码位置:光盘\第4章\ ExamSystem \ ADOConn.cpp
01 void ADOConn::ExitConn() //断开连接 02 { 03 if(m_pRs!=NULL) 04 m_pRs->Close(); //关闭对象 05 m_pCon->Close(); //关闭对象 06 }
ExecuteSQL函数实现对数据库执行SQL操作语句,如Update、Delete、Insert等语句,代码如下。
例程4.4代码位置:光盘\第4章\ ExamSystem \ ADOConn.cpp
01 BOOL ADOConn::ExecuteSQL(_bstr_t bstrSQL) //执行SQL操作语句 02 { 03 try 04 { 05 if(m_pCon==NULL) 06 OnInitADOConn(); //连接数据库 07 m_pCon->Execute(bstrSQL,NULL,adCmdText); //执行SQL 08 return true; 09 } 10 catch(_com_error e) //捕捉异常 11 { 12 AfxMessageBox(e.Description()); 13 return false; 14 } 15 }
GetRecordSet函数实现对数据库执行SQL查询语句(Select语句),并返回查询结果集,代码如下。
例程4.5代码位置:光盘\第4章\ ExamSystem \ ADOConn.cpp
01 _RecordsetPtr&ADOConn::GetRecordSet(_bstr_t bstrSQL) //执行SQL查询语句 02 { 03 try 04 { 05 if(m_pCon==NULL) //没有连接 06 OnInitADOConn(); //连接数据库 07 m_pRs.CreateInstance("ADODB.Recordset");//创建记录集 08 m_pRs->Open(bstrSQL,m_pCon.GetInterfacePtr(),adOpenDynamic, 09 adLockOptimistic,adCmdText); 10 } 11 catch(_com_error e) //捕捉异常 12 { 13 AfxMessageBox(e.Description()); 14 } 15 return m_pRs; //返回结果集 16 }
在第7行代码创建CRecordSet记录集对象,然后在第8行代码通过记录集对象的Open函数执行查询。
4.6 登录模块设计
系统主框架是使用MFC创建向导创建的基于对话框的应用程序,工程名为“ExamSystem”。当用户登录时,根据用户的身份(考生或是管理员),构造了两种不同的主界面窗口。
系统启动时,首先弹出登录对话框,如图4-4所示。登录对话框相关的对话框类为CLOGIN,在系统类CExamSystemApp的InitInstance函数中,实现登录对话框对象的构造与调用,代码如下。
例程4.6代码位置:光盘\第4章\ ExamSystem \ ExamSystem.cpp
01 BOOL CExamSystemApp::InitInstance() 02 { 03 AfxEnableControlContainer(); 04 ::CoInitialize(NULL); 05 InitializeSkin(_T("XPCorona.ssk")); //加载皮肤资源 06 CLOGIN logindlg; //登录对话框 07 if(logindlg.DoModal()==IDOK) //单击登录对话框“登录”按钮 08 { 09 CString Name; 10 Name=logindlg.m_UserName; 11 CString sql="select*from register where username='"+Name+"'"; 12 m_AdoConn.OnInitADOConn(); //连接数据库 13 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 14 studentid=atoi((char*)(_bstr_t)m_pRs->GetCollect("studentid")); 15 CExamSystemDlg dlg; //考生考试对话框 16 m_pMainWnd=&dlg; //主对话框 17 int nResponse=dlg.DoModal(); //弹出对话框 18 } 19 ::CoUninitialize(); 20 return FALSE; 21 }
系统运行时,首先会运行InitInstance函数。在主窗口创建之前,第6、第7行代码首先构造创建登录对话框(CLOGIN),只有在登录对话框中单击“登录”按钮,第7~20行代码才能执行,否则退出系统。
登录对话框中的“登录”按钮响应函数OnButtonOk代码如下。
例程4.7代码位置:光盘\第4章\ ExamSystem \ LOGIN.cpp
01 void CLOGIN::OnButtonOk() 02 { 03 UpdateData(); //获取控件输入 04 CString str; 05 m_TypeList.GetLBText(m_TypeList.GetCurSel(),str); //下拉框的选项 06 if(m_UserName.IsEmpty()) //用户名为空 07 { 08 AfxMessageBox("用户名不能为空"); 09 return; 10 } 11 if(m_UserPasswd.IsEmpty()) //密码为空 12 { 13 AfxMessageBox("密码不能为空"); 14 return; 15 } 16 //根据用户输入从数据库查询登录用户 17 CString sql="select*from register where username='"+m_UserName+"'and 18 [password]='"+m_UserPasswd+"'and power='"+str+"'"; 19 try 20 { 21 _RecordsetPtr m_pRs; 22 ADOConn m_AdoConn; 23 m_AdoConn.OnInitADOConn(); //连接数据库 24 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql) //执行查询 25 if(m_pRs->adoEOF) //没有找到记录 26 { 27 //从数据库查询该用户名 28 sql="select*from register where username='"+m_UserName+"'"; 29 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql);//执行查询 30 if(!m_pRs->adoEOF) //没有找到记录 31 { 32 //登录密码不正确 33 if(m_UserPasswd!=(char*)(_bstr_t)m_pRs->GetCollect("password")) 34 { 35 if(MessageBox("密码错误,是否找回密码?","提示", 36 MB_YESNO)==IDYES) 37 { 38 CFind dlg; //查找密码对话框 39 dlg.Name=m_UserName; 40 CDialog::OnCancel(); 41 dlg.DoModal(); //弹出对话框 42 } 43 } 44 else 45 { 46 //权限不一致 47 if(str!=(char*)(_bstr_t)m_pRs->GetCollect("power")) 48 { 49 AfxMessageBox("权限错误"); 50 return; 51 } 52 } 53 } 54 else //用户名在数据库中不存在 55 { 56 if(MessageBox("用户名不存在,是否注册?","提示", 57 MB_YESNO)==IDYES) 58 { 59 CDialog::OnCancel(); 60 CRegister dlg; //注册对话框对象 61 dlg.m_UserName=m_UserName; 62 dlg.DoModal(); //注册用户 63 } 64 else 65 CDialog::OnCancel(); 66 } 67 } 68 else 69 { 70 if(str=="管理员") //登录用户为管理员 71 { 72 CDialog::OnCancel(); //退出登录对话框 73 CBack dlg; //后台管理对话框 74 dlg.DoModal(); 75 } 76 else 77 CDialog::OnOK(); //为考生退出对话框 78 } 79 m_AdoConn.ExitConn(); //断开连接数据库 80 } 81 catch(...) 82 { 83 AfxMessageBox("操作失败"); 84 return; 85 } 86 }
函数首先判断用户名、密码是否为空,若不为空,从数据库Register表中,根据用户的输入进行查询(第24行代码)。若通过验证(查询结果不为空),如果用户类别为考生,则退出对话框(第77行代码),继续执行CExamSystemApp的InitInstance函数;如果用户为管理员,则退出登录对话框,弹出后台管理窗口(第70~74行代码)。
如果登录用户没有通过验证(第30行代码,查询结果为空),首先根据用户名判断用户的密码是否正确(第33行代码),如果不正确,则弹出“提示”窗口(如图4-17所示),用户可以选择是否找回密码。要找回密码,单击“是(Y)”按钮,即弹出“找回密码”窗口(CFind,第38~41行代码),用户输入提示问题的答案,单击“提交”按钮,如果答案正确,则在对话框中显示用户密码,如图4-18所示。
图4-17 “提示”窗口
4-18 “找回密码”窗口
如果用户输入的用户权限与数据库中查询的该用户权限不一致,则给出错误提示对话框,返回登录对话框(第47~51行代码)。最后一种情况就是用户名不存在,则给出提出对话框,提示用户该用户不存在,是否注册。若选择注册,则弹出注册用户对话框,进行考生注册。系统启动,用户登录的流程可表示为如图4-19所示。
图4-19 系统启动,用户登录流程图
4.7 考生考试模块设计
考生考试模块主要窗口包括答题主界面窗口、选择试卷对话框和考试结果显示对话框,主要功能包括选择试卷、显示试题、答题、交卷、查看考试结果等。
4.7.1 答题主界面窗口的创建
答题主界面窗口对应的对话框类为CExamSystemDlg。当登录用户的权限为学生时,系统就会创建答题主界面窗口。在窗口初始化时,首先弹出试卷选择对话框,选择考试试卷,如图4-5所示。然后显示答题主界面窗口,如图4-6所示。此时,主界面窗口显示的是该试卷的第一题及答案选项。
CExamSystemDlg类的初始化函数OnInitDialog的代码如下。
例程4.8代码位置:光盘\第4章\ ExamSystem \ ExamSystemDlg.cpp
01 BOOL CExamSystemDlg::OnInitDialog() //对话框初始化 02 { 03 CDialog::OnInitDialog(); 04 …… 05 //TODO:Add extra initialization here 06 CExamInfo infodlg; 07 CTime time; 08 time=CTime::GetCurrentTime(); //当前时间 09 if(infodlg.DoModal()==IDOK) //选择试卷对话框 10 { 11 CString Subject=infodlg.Subject; 12 CString studentid; 13 studentid.Format("%d",theApp.studentid); //考生ID 14 CTime time; 15 time=CTime::GetCurrentTime(); //当前时间 16 TimeStr=time.Format("%m月%d日%H:%M"); 17 //向考试成绩表Score中,添加记录,包括开始试卷、试卷和学生ID 18 CString sql="insert into Score(starttime,subject,studentid) 19 values('"+TimeStr+"','"+Subject+"','"+studentid+"')"; 20 m_AdoConn.OnInitADOConn(); //连接数据库 21 m_AdoConn.ExecuteSQL((_bstr_t)sql); //执行Insert语句 22 CString question=infodlg.Question; 23 m_Test.SetWindowText(question); //试题题目 24 CString Id; 25 Id.Format("题号:%d",testnum); //题号 26 m_TestID.SetWindowText(Id); 27 Sid=infodlg.Sid; //考题ID 28 m_AdoConn.ExitConn(); //断开连接 29 PutAnswer(); //显示答案选项 30 } 31 else 32 { 33 CDialog::OnCancel(); 34 } 35 m_Time.SetWindowText(time.Format("%m月%d日%H:%M")); 36 SetTimer(1,60000,NULL); //设置定时器,每分钟触发 37 retime=30; //剩余时间 38 return TRUE; 39 }
在主对话框初始化之前,第9行代码首先创建选择试卷对话框CExamInfo,获取用户选择的试卷,第18~21行代码实现向Score表中添加该考生的考试记录,包括考试时间、试卷和考生ID,第23~26行代码实现在对话框中显示考题序号和考题题目,第29行代码调用自定义的PutAnswer函数,实现在对话框中显示试题对应的答案选项,PutAnswer函数的代码如下。
例程4.9代码位置:光盘\第4章\ ExamSystem \ ExamSystemDlg.cpp
01 void CExamSystemDlg::PutAnswer() //显示试题的答案选项 02 { 03 UpdateData(); 04 CString question; 05 m_Test.GetWindowText(question); //题目 06 //从testquestion表查询该题目记录 07 CString sql="select*from testquestion where question='"+question+"'"; 08 m_AdoConn.OnInitADOConn(); 09 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 10 //获取题目ID 11 CString testid=(char*)(_bstr_t)m_pRs->GetCollect("testid"); 12 //从testanswer表查询该试题对应的答案选项 13 sql="select*from testanswer where testid="+testid+""; 14 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 15 CString A,B,C,D; 16 A=(char*)(_bstr_t)m_pRs->GetCollect("AnswerA"); 17 B=(char*)(_bstr_t)m_pRs->GetCollect("AnswerB"); 18 C=(char*)(_bstr_t)m_pRs->GetCollect("AnswerC"); 19 D=(char*)(_bstr_t)m_pRs->GetCollect("AnswerD"); 20 //设置单选按钮的标题为答案选项 21 GetDlgItem(IDC_RADIO_A)->SetWindowText("A "+A); 22 GetDlgItem(IDC_RADIO_B)->SetWindowText("B "+B); 23 GetDlgItem(IDC_RADIO_C)->SetWindowText("C "+C); 24 GetDlgItem(IDC_RADIO_D)->SetWindowText("D "+D); 25 m_AdoConn.ExitConn(); //断开连接 26 }
函数首先从编辑框控件获取试题题目(第5行代码),接着从TestqQestion表查询该题目对应的试题ID(第7~11行代码),然后从TestAnswer表中,查询该试题ID对应的试题答案选项(第13~19行代码),最后就是将对话框中的单选按钮的标题设置为对应的答案选项。
4.7.2 选择试卷对话框的开发
如上节所述,考生登录系统后,首先弹出如图4-5所示的选择试卷对话框。该对话框实现的功能是通过下拉列表框列出所有试卷供用户选择,用户选择后,将该试卷的ID及第一题的题目传递给答题对话框。
选择试卷对话框对应的对话框类为CExamInfo,在其初始化函数OnInitDialog中,从subject表中查询所有试卷名称,并将其添加到对话框的下拉列表框中。
对话框“确定”按钮的响应函数为OnButtonOk,其代码如下。
例程4.10代码位置:光盘\第4章\ ExamSystem \ ExamInfo.cpp
01 void CExamInfo::OnButtonOk() 02 { 03 UpdateData(); 04 if(m_SubjectCombo.GetCurSel()==-1) //没有选择试卷 05 { 06 AfxMessageBox("请选择试卷"); 07 return; 08 } 09 int studentid=theApp.studentid; //考生ID 10 //获取考生选择的试卷 11 m_SubjectCombo.GetLBText(m_SubjectCombo.GetCurSel(),Subject); 12 m_AdoConn.OnInitADOConn(); //连接数据库 13 //查询Score表中,该考生是否参加过该试卷的考试 14 CString sql; 15 sql.Format("select*from Score where studentid=%d and Subject='%s'", 16 studentid,Subject); 17 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 18 if(m_pRs->adoEOF) //未参加此学科的考试 19 { 20 sql="select*from subject where subjectname='"+Subject+"'"; 21 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 22 Sid=atoi((char*)(_bstr_t)m_pRs->GetCollect("subjectid")); 23 //按题号排序试题 24 sql.Format("select*from testquestion where subjectid=%d order by testid",Sid); 25 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 26 Question=(char*)(_bstr_t)m_pRs->GetCollect("question"); //第一题题目 27 CDialog::OnOK(); 28 } 29 else 30 MessageBox("该考生以进行过本科目的考试"); 31 m_AdoConn.ExitConn(); //断开连接 32 }
函数首先查询Score表中,该考试是否参加过该试卷的考试。如果没有参加考试(第18行代码),首先从subject表中查询该试卷对用的试卷ID(第20~22行代码),然后根据此ID从TestQuestion表中查询该试卷包含的试题,并依据试题ID的升序排序,并获取第一条记录的试题题目(第24~26行代码)。
4.7.3 考生答题模块的开发
考生答题对话框创建完毕,考生就可以实现答题了。答题操作是通过单击相应的单选按钮来实现的,如答案A选项单选按钮的BN_CLICKED响应函数如下。
例程4.11代码位置:光盘\第4章\ ExamSystem \ ExamSystemDlg.cpp
01 void CExamSystemDlg::OnRadioA() 02 { 03 Answer='A'; 04 }
考生答题时,从第一题开始依次作答。在完成当前题后,可以通过单击“上一题”和“下一题”按钮,遍历作答考试试题,“下一题”按钮响应函数OnNext的代码如下。
例程4.12代码位置:光盘\第4章\ ExamSystem \ ExamSystemDlg.cpp
01 void CExamSystemDlg::OnNext() //下一题 02 { 03 UpdateData(); 04 //获取各选项按钮对象 05 CButton*m_checkA=(CButton*)GetDlgItem(IDC_RADIO_A); 06 CButton*m_checkB=(CButton*)GetDlgItem(IDC_RADIO_B); 07 CButton*m_checkC=(CButton*)GetDlgItem(IDC_RADIO_C); 08 CButton*m_checkD=(CButton*)GetDlgItem(IDC_RADIO_D); 09 CString sql; 10 CString question,answer; 11 m_Test.GetWindowText(question); //试题题目 12 CString Id; 13 Id.Format("题号:%d",testnum+1); //题号加1 14 m_AdoConn.OnInitADOConn(); //连接数据库 15 sql.Format("select*from testquestion where question='%s'",question); 16 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 17 //得到试题ID 18 CString testid=(char*)(_bstr_t)m_pRs->GetCollect("testid"); 19 //从临时表examminginfo中查询下一题记录 20 sql.Format("select*from examminginfo where testnum=%d",testnum+1); 21 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 22 if(m_pRs->adoEOF) //没有记录,即考生没有做过下一题 23 { 24 //从临时表examminginfo中查询当前题记录 25 sql.Format("select*from examminginfo where question='%s'",question); 26 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 27 if(m_pRs->adoEOF) //没有记录,即考生没有做过当前题目 28 { 29 if(Answer!='A'&&Answer!='B'&&Answer!='C'&&Answer!='D') 30 { 31 AfxMessageBox("请选择一个答案"); 32 return; //返回 33 } 34 SaveQuestion();//将当前题目用户的作答保存到表examminginfo中 35 } 36 //更新examminginfo表,记录考生当前的选择 37 sql.Format("select*from examminginfo where testnum=%d",testnum); 38 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 39 sql.Format("update examminginfo set answer='%s'where testnum=%d", 40 Answer,testnum); 41 answer=(char*)(_bstr_t)m_pRs->GetCollect("answer"); 42 if(answer!=Answer) //考生更改了答案 43 m_AdoConn.ExecuteSQL((_bstr_t)sql); 44 //进入下一题状态,各选项均未选中 45 m_checkA->SetCheck(false); 46 m_checkB->SetCheck(false); 47 m_checkC->SetCheck(false); 48 m_checkD->SetCheck(false); 49 Answer='e'; 50 //获取下一题的题目 51 int tid=atoi(testid); 52 sql.Format("select*from testquestion where subjectid=%d and 53 testid>%d order by testid",Sid,tid); 54 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 55 if(m_pRs->adoEOF) //已经是最后一题 56 { 57 CheckAnswer(); //提交答案 58 m_AdoConn.ExitConn(); 59 if(MessageBox("考试结束,是否交卷?","提示",MB_YESNO)==IDYES) 60 { 61 CDialog::OnCancel(); 62 CTestResult dlg; 63 dlg.TimeStr=TimeStr; 64 dlg.DoModal(); 65 } 66 else return; 67 } 68 else 69 { 70 //显示下一题的题目 71 question=(char*)(_bstr_t)m_pRs->GetCollect("question"); 72 m_TestID.SetWindowText(Id); 73 m_Test.SetWindowText(question); 74 testnum++; 75 PutAnswer(); //显示试题的答案选项 76 } 77 } 78 else //下一题已经答过 79 { 80 //更新examminginfo表,记录考生当前的选择 81 sql.Format("select*from examminginfo where testnum=%d",testnum); 82 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 83 sql.Format("update examminginfo set answer='%s'where testnum=%d", 84 Answer,testnum); 85 answer=(char*)(_bstr_t)m_pRs->GetCollect("answer"); 86 if(answer!=Answer) 87 m_AdoConn.ExecuteSQL((_bstr_t)sql); 88 //获取下一题的题目及考生以前的答案 89 testnum++; 90 sql.Format("select*from examminginfo where testnum=%d",testnum); 91 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 92 question=(char*)(_bstr_t)m_pRs->GetCollect("question"); 93 answer=(char*)(_bstr_t)m_pRs->GetCollect("answer"); 94 m_TestID.SetWindowText(Id); 95 m_Test.SetWindowText(question); 96 PutAnswer(); //获取、显示试题答案选项 97 //根据用户以前的答案设定各选项单选按钮的状态 98 if(answer=='A') 99 { 100 m_checkA->SetCheck(true); //选项A选中 101 m_checkB->SetCheck(false); //选项B未选中 102 m_checkC->SetCheck(false); //选项C未选中 103 m_checkD->SetCheck(false); //选项D未选中 104 } 105 if(answer=='B') 106 …… 107 } 108 }
函数首先根据当前考题序号,从记录学生答题信息的临时表ExammingInfo中,查找下一序号的答题信息(第14~21行代码),如果没有记录,则证明下一题考生没有作答。此时函数执行如下的操作:
● 从答题信息临时表ExammingInfo中,查询当前考题的记录(第25、第26行代码)。如果记录为空(第27行代码),则说明当前题目也是第一次作答,则调用SaveQuestion函数(第34行代码)将当前题目、考生的答案等信息添加到ExammingInfo表中。
● 从ExammingInfo表中,查询考生以前作答当前题目的答案,如果与现在的答案不一致,则更新ExammingInfo表中考生的答案(第37~43行代码)。
● 从TestQuestion表中查询下一试题记录(第51~54行代码),如果存在,则显示试题及答案选项(第71~75行代码)。如果不存在,则表明该题已是试卷的最后一题,通过CheckAnswer函数提交答案,并提示用户是否交卷(第57~64行代码)。
而如果下一道试题用户以前已经答过,则函数执行如下的操作:
● 下一题已经答过,说明当前题用户以前也已经答过,因此需要从ExammingInfo表中查询考生以前作答当前题目的答案,如果与现在的答案不一致,则更新ExammingInfo表中考生的答案(第81~87行代码)。
● 从ExammingInfo表中查询下一试题记录(第89~96行代码),并显示试题题目及答案选项。
● 根据ExammingInfo表中记录的下一试题用户选择的答案,设置答案选项单选按钮的状态(第98~107行代码)。
● “上一题”按钮响应函数OnBack的代码如下。
例程4.13代码位置:光盘\第4章\ ExamSystem \ ExamSystemDlg.cpp
01 void CExamSystemDlg::OnBack() //前一题 02 { 03 UpdateData(); 04 //获取各选项按钮对象 05 CButton*m_checkA=(CButton*)GetDlgItem(IDC_RADIO_A); 06 CButton*m_checkB=(CButton*)GetDlgItem(IDC_RADIO_B); 07 CButton*m_checkC=(CButton*)GetDlgItem(IDC_RADIO_C); 08 CButton*m_checkD=(CButton*)GetDlgItem(IDC_RADIO_D); 09 if(testnum==1) //第一题 10 { 11 AfxMessageBox("这已经是第一题了"); 12 return; //返回 13 } 14 CString question; 15 CString Id; 16 CString answer; 17 CString sql; 18 m_Test.GetWindowText(question); //获取试题题目 19 m_AdoConn.OnInitADOConn(); //连接数据库 20 testnum--; //题号减1 21 sql.Format("select*from ExammingInfo where testnum=%d",testnum); 22 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); //执行查询 23 question=(char*)(_bstr_t)m_pRs->GetCollect("question");//试题 24 answer=(char*)(_bstr_t)m_pRs->GetCollect("answer");//答案 25 Id.Format("题号:%d",testnum); //题号 26 m_TestID.SetWindowText(Id); 27 m_Test.SetWindowText(question); //显示试题题目 28 PutAnswer(); //显示答案选项 29 if(answer=='A') //作答的答案为A 30 { 31 m_checkA->SetCheck(true); //选项A选中 32 m_checkB->SetCheck(false); //选项B未选中 33 m_checkC->SetCheck(false); //选项C未选中 34 m_checkD->SetCheck(false); //选项D未选中 35 } 36 if(answer=='B') //作答的答案为B 37 …… 38 } 39 }
函数的核心操作就是根据题号从ExammingInfo表中查询上一题的记录(第19~22行代码),获取记录的试题题目(第23行代码)和作答的答案(第24行代码)。而后显示试题题目和答案选项,并根据作答的答案设置答案选项单选按钮的状态。
4.7.4 考生交卷模块开发
考生在以下3种情况下可以实现交卷操作:
● 考生答题做到最后一题,单击“下一题”按钮,系统弹出提示对话框,询问是否交卷,此时可实现交卷操作(参见上节OnNext函数的第59~65行代码)。
● 考生单击“交卷”按钮,则实现交卷操作,退出考试。
● 考试时间到(超过45分钟),自动实现交卷操作,退出考试。
“交卷”按钮响应函数的实现代码如下。
例程4.14代码位置:光盘\第4章\ ExamSystem \ ExamSystemDlg.cpp
01 void CExamSystemDlg::OnCheckin() //交卷 02 { 03 //TODO:Add your control notification handler code here 04 if(MessageBox("确定要交卷吗?","提示",MB_YESNO)==IDYES) 05 { 06 KillTimer(1); //销毁定时器 07 CheckAnswer(); //提交答案 08 m_AdoConn.ExitConn(); //断开连接 09 CDialog::OnCancel(); //关闭窗口 10 CTestResult dlg; //考试结果对话框 11 dlg.TimeStr=TimeStr; //考试开始时间 12 dlg.DoModal(); 13 } 14 else 15 return; //返回 16 }
其中第7行代码调用自定义的CheckAnswer函数,实现对考生的答案与正确答案进行校验,并将得分结果记录在ExammingInfo表中,CheckAnswer函数代码如下。
例程4.15代码位置:光盘\第4章\ ExamSystem \ ExamSystemDlg.cpp
01 void CExamSystemDlg::CheckAnswer() //提交答案 02 { 03 CString sql; 04 sql.Format("select*from examminginfo"); 05 m_AdoConn.OnInitADOConn(); //连接数据库 06 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 07 while(m_pRs->adoEOF==0) //遍历记录 08 { 09 _RecordsetPtr m_prs; 10 //试题题目和考生答案 11 CString question=(char*)(_bstr_t)m_pRs->GetCollect("question"); 12 CString answer=(char*)(_bstr_t)m_pRs->GetCollect("answer"); 13 //从testquestion表中查询试题的正确答案和分数 14 sql.Format("select*from testquestion where question='%s'",question); 15 m_prs=m_AdoConn.GetRecordSet((_bstr_t)sql); 16 CString rightanswer=(char*)(_bstr_t)m_prs->GetCollect("rightanswer"); 17 int totle=atoi((char*)(_bstr_t)m_prs->GetCollect("score")); 18 if(answer==rightanswer) //答案正确 19 { 20 //获取相应的分数 21 sql.Format("update examminginfo set score=%d where 22 question='%s'",totle,question); 23 m_AdoConn.ExecuteSQL((_bstr_t)sql); //执行更新语句 24 } 25 m_pRs->MoveNext(); //下一记录 26 } 27 }
在4.7.1节介绍的答题主对话框创建时,在初始化函数中就设置了定时器,每分钟触发一次。在定时器响应函数OnTimer中,实现计时,当考试时间到时,会自动提交答案,退出考试窗口,OnTimer函数的代码如下。
例程4.16代码位置:光盘\第4章\ ExamSystem \ ExamSystemDlg.cpp
01 void CExamSystemDlg::OnTimer(UINT nIDEvent) 02 { 03 //TODO:Add your message handler code here and/or call default 04 retime--; //剩余时间减1 05 if(retime>0) //更新显示剩余时间 06 { 07 CString str; 08 str.Format("剩下考试时间:%d分钟",retime); 09 m_ReTime.SetWindowText(str); //显示 10 } 11 else if(retime==0) //考试时间到 12 { 13 KillTimer(1); //销毁定时器 14 CheckAnswer(); //提交答案 15 m_AdoConn.ExitConn(); //断开连接 16 AfxMessageBox("考试时间到!"); 17 CDialog::OnCancel(); //关闭窗口 18 CTestResult dlg; //考试结果对话框 19 dlg.TimeStr=TimeStr; //考试开始时间 20 dlg.DoModal(); 21 } 22 CDialog::OnTimer(nIDEvent); 23 }
答题结束后,考生提交试卷,系统会弹出考试结果对话框,通过列表框的形式显示考生的答题状态和最终的得分,如图4-7所示。
考试结果对话框对应的对话框类为CTestResult,初始化函数OnInitDialog代码如下。
例程4.17代码位置:光盘\第4章\ ExamSystem \ TestResult.cpp
01 BOOL CTestResult::OnInitDialog() 02 { 03 CDialog::OnInitDialog(); 04 //设置列表框列表项 05 m_ResultList.SetExtendedStyle(LVS_EX_FLATSB|LVS_EX_FULLROWSELECT| 06 LVS_EX_GRIDLINES|LVS_EX_HEADERDRAGDROP); 07 m_ResultList.InsertColumn(0,"题号",LVCFMT_CENTER,200,0);//添加列 08 m_ResultList.InsertColumn(1,"结果",LVCFMT_CENTER,200,1);//添加列 09 //查询ExammingInfo表中的记录 10 m_AdoConn.OnInitADOConn(); //连接数据库 11 CString sql="select*from examminginfo"; 12 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); //执行查询 13 int i=0; 14 int sum=0; 15 while(m_pRs->adoEOF==0) //遍历记录集 16 { 17 CString num=(char*)(_bstr_t)m_pRs->GetCollect("testnum"); 18 CString result=(char*)(_bstr_t)m_pRs->GetCollect("score"); 19 m_ResultList.InsertItem(i,""); 20 m_ResultList.SetItemText(i,0,num); 21 if(result=='0') //得分为0 22 m_ResultList.SetItemText(i,1,"错误"); 23 else 24 m_ResultList.SetItemText(i,1,"正确"); 25 i++; 26 sum+=atoi(result); //计算总分 27 m_pRs->MoveNext(); //下一记录 28 } 29 CString str; 30 str.Format("%d",sum); 31 m_ResultList.InsertItem(i,""); //添加列表项 32 m_ResultList.SetItemText(i,0,"总分:"); 33 m_ResultList.SetItemText(i,1,str+"分"); 34 CTime time; 35 time=CTime::GetCurrentTime(); //当前时间 36 CString Tstr=time.Format("%m月%d日%H:%M"); 37 //更新Score表记录,添加结束时间和分数 38 sql="update Score set closetime='"+Tstr+"',score='"+str+"'where 39 starttime='"+TimeStr+"'"; 40 m_AdoConn.ExecuteSQL((_bstr_t)sql); //执行更新语句 41 m_AdoConn.ExitConn(); //断开连接 42 return TRUE; 43 }
第15~28行代码实现遍历ExammingInfo表中的记录,判断其result(得分)字段的值。如果为0,则表明该题考生回答错误,依次将各题作答结果显示在对话框的列表框中,并计算显示总得分。第34~39行代码实现将当前时间作为考试结束时间,并更新Score表记录,添加结束时间和分数。
在用户退出考试结果对话框时,需要删除临时数据表ExammingInfo中的记录。至此,考生交卷模块开发完毕。
4.8 管理员试题管理模块设计
管理员可以通过试题管理模块对试卷、试题进行管理和维护,能够实现查看试卷、试题,增加、删除试卷,增加、删除、修改试题等操作。
4.8.1 管理员后台管理对话框的创建
管理员后台管理对话框对应的对话框类为Cback,当登录用户的权限为管理员时,系统就会创建管理员后台管理对话框窗口。
在管理员后台管理对话框中,包含了一个标签控件(CTabCtrl),用于显示试题管理、学生成绩查询和数据库管理标签页,分别如图4-8、4-9和4-10所示。标签页的创建是在CBack类的初始化函数OnInitDialog中实现的,代码如下。
例程4.18代码位置:光盘\第4章\ ExamSystem \ Back.cpp
01 BOOL CBack::OnInitDialog() 02 { 03 CDialog::OnInitDialog(); 04 //添加三个标签页,设置窗口标题 05 m_BackTab.InsertItem(0,"试题管理"); 06 m_BackTab.InsertItem(1,"学生成绩查询"); 07 m_BackTab.InsertItem(2,"数据库管理"); 08 m_BackTab.SetCurSel(0); //当前显示标签页 09 CRect rect; 10 m_BackTab.GetClientRect(rect); //获取客户窗口区域 11 rect.DeflateRect(1,30,2,2); //缩小区域 12 //创建标签页对话框 13 testmanagedlg->Create(IDD_TEXTMANAGE,&m_BackTab); 14 resultselectdlg->Create(IDD_RESULTSELECT,&m_BackTab); 15 sqlmanagedlg->Create(IDD_SQLMANAGE,&m_BackTab); 16 testmanagedlg->MoveWindow(rect); 17 testmanagedlg->ShowWindow(SW_SHOW); //显示标签页 18 return TRUE; 19 }
其中,第11行代码DeflateRect函数实现的功能是将标签页客户窗口区域的左、上、右、下四个边界缩小相应的像素,第16行代码实现将对话框窗口在这个区域创建。
为了实现标签页窗口的切换操作,需要为标签控件添加TCN_SELCHANGE消息响应函数OnSelchangeBackTab,代码如下。
例程4.19代码位置:光盘\第4章\ ExamSystem \ Back.cpp
01 void CBack::OnSelchangeBackTab(NMHDR*pNMHDR,LRESULT*pResult) 02 { 03 CRect rect; 04 m_BackTab.GetClientRect(rect); //获取客户窗口区域 05 rect.DeflateRect(1,30,2,2); //缩小区域 06 int i=m_BackTab.GetCurSel(); //当前选中标签页项 07 //隐藏所有的标签页窗口 08 testmanagedlg->ShowWindow(SW_HIDE); 09 resultselectdlg->ShowWindow(SW_HIDE); 10 sqlmanagedlg->ShowWindow(SW_HIDE); 11 switch(i) 12 { 13 case 0: //显示第一个标签页 14 { 15 testmanagedlg->MoveWindow(rect);//移动到指定位置 16 testmanagedlg->ShowWindow(SW_SHOW);//显示 17 resultselectdlg->ShowWindow(SW_HIDE);//隐藏 18 sqlmanagedlg->ShowWindow(SW_HIDE);//隐藏 19 break; 20 } 21 case 1: //显示第二个标签页 22 { 23 resultselectdlg->MoveWindow(rect);//移动到指定位置 24 resultselectdlg->ShowWindow(SW_SHOW);//显示 25 …… 26 } 27 *pResult=0; 28 }
函数根据当前选中的标签页(第6行代码),对标签页对应的对话框窗口进行显示和隐藏,从而实现了标签页窗口的切换。
4.8.2 试题管理标签页窗口的创建
试题管理标签页窗口对应的对话框类为CTextManage,其窗口设计如图4-8所示。在树形控件中,显示试题科目(试卷),而在列表框中,则显示该试卷包含的试题和试题答案。通过对话框下方的功能按钮,实现对试卷和试题的操作。
在对话框初始化函数OnInitDialog中,初始化树形控件和列表框控件,代码如下。
例程4.20代码位置:光盘\第4章\ ExamSystem \ TextManage.cpp
01 BOOL CTextManage::OnInitDialog() 02 { 03 CDialog::OnInitDialog(); 04 AddToTree(); //向树形控件添加数据 05 //设置列表框列表项 06 m_TextList.SetExtendedStyle(LVS_EX_FLATSB 07 |LVS_EX_FULLROWSELECT|LVS_EX_HEADERDRAGDROP 08 |LVS_EX_ONECLICKACTIVATE|LVS_EX_GRIDLINES); 09 m_TextList.InsertColumn(0,"题目",LVCFMT_LEFT,250,0); //添加列 10 m_TextList.InsertColumn(1,"答案",LVCFMT_LEFT,100,0); //添加列 11 return TRUE; 12 }
在第4行代码中,通过调用自定义的AddToTree函数,实现向树形控件中添加节点和数据,代码如下。
例程4.21代码位置:光盘\第4章\ ExamSystem \ TextManage.cpp
01 void CTextManage::AddToTree() //添加树形控件数据 02 { 03 //添加根节点 04 HTREEITEM h_root; 05 h_root=m_TextTree.InsertItem("试题试卷",0,1); 06 //查询subject表中的记录 07 CString sql="select*from subject"; 08 _RecordsetPtr m_prs; 09 try 10 { 11 m_AdoConn.OnInitADOConn(); //连接数据库 12 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); //执行查询 13 while(m_pRs->adoEOF==0) //遍历查询结果记录 14 { 15 CString str=(char*)(_bstr_t)m_pRs->GetCollect("subjectname"); 16 m_TextTree.InsertItem(str,0,1,h_root); //试卷名称添加为子节点 17 m_pRs->MoveNext(); //下一记录 18 } 19 m_AdoConn.ExitConn(); //断开连接 20 } 21 catch(...) //捕捉异常 22 { 23 AfxMessageBox("读取数据失败"); 24 return; //返回 25 } 26 }
函数第4、第5 行代码首先向树形控件中添加根节点,然后在此根节点下添加子节点,节点名称为试卷名称(第16行代码)。
当用户在树形控件中时,单击选中子节点(试卷),在列表框中要显示该试卷所包含的所有试题和答案。这就需要为树形控件添加TVN_SELCHANGED消息响应函数,代码如下。
例程4.22代码位置:光盘\第4章\ ExamSystem \ TextManage.cpp
01 void CTextManage::OnSelchangedTextTree(NMHDR*pNMHDR,LRESULT*pResult) 02 { 03 NM_TREEVIEW*pNMTreeView=(NM_TREEVIEW*)pNMHDR; 04 //获取树形控件的选中项 05 HTREEITEM select=m_TextTree.GetSelectedItem(); 06 if(select!=m_TextTree.GetRootItem()) //选中的不是根节点 07 { 08 //获取选中项的文本,即考卷名称 09 CString TreeText=m_TextTree.GetItemText(select); 10 m_TextList.DeleteAllItems(); //清空列表框 11 try 12 { 13 //根据考卷名称从subject表中查询记录 14 CString sql="select*from subject where subjectname='"+TreeText+"'"; 15 m_AdoConn.OnInitADOConn(); //连接数据库 16 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 17 //获取试卷ID 18 int Sid=atoi((char*)(_bstr_t)m_pRs->GetCollect("subjectid")); 19 AddToList(Sid); //添加列表框数据 20 m_AdoConn.ExitConn(); //断开连接 21 } 22 catch(...) //捕捉异常 23 { 24 AfxMessageBox("操作失败"); 25 return; //返回 26 } 27 } 28 *pResult=0; 29 }
函数首先获取树形控件的选中项(第5行代码),然后得到该选中项的标题(第9行代码),该标题实际上就是考卷名称,依据考卷名称即可从subject表中获取其对应的试卷ID。以该试卷ID为参数,就可以通过调用AddToList函数在列表框中添加该试卷的试题及答案(第19行代码),AddToList函数的实现代码如下。
例程4.23代码位置:光盘\第4章\ ExamSystem \ TextManage.cpp
01 void CTextManage::AddToList(int Sid) 02 { 03 //根据试卷ID从testquestion表中查询试题记录 04 CString sql; 05 sql.Format("select*from testquestion where subjectid=%d",Sid); 06 m_AdoConn.OnInitADOConn(); //连接数据库 07 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); //执行查询 08 while(m_pRs->adoEOF==0) //遍历记录 09 { 10 CString Question=(char*)(_bstr_t)m_pRs->GetCollect("question"); 11 CString Answer=(char*)(_bstr_t)m_pRs->GetCollect("rightanswer"); 12 m_TextList.InsertItem(0,""); 13 m_TextList.SetItemText(0,0,Question); //添加试题题目 14 m_TextList.SetItemText(0,1,Answer); //添加试题答案 15 m_pRs->MoveNext(); //下一记录 16 } 17 }
这样,当用户在树形控件中选中了试卷后,在列表框中就会列出该试卷包含的试题及试题的正确答案。
4.8.3 增加、删除试卷开发
要添加考试试卷,管理员只需单击“增加试卷”按钮,弹出“新增试卷”窗口,如图4-20所示。在窗口中输入新增试卷的名称,单击“增加”按钮,即实现了向数据库中添加试卷。
图4-20 “新增试卷”窗口
“增加试卷”按钮的响应函数为OnAddsubject,代码如下。
例程4.24 代码位置:光盘\第4 章\ ExamSystem \TextManage.cpp
01 void CTextManage::OnAddsubject() //增加试卷 02 { 03 CAddSubject dlg; //新增试卷窗口 04 if(dlg.DoModal()==IDOK) 05 { 06 CString Name; 07 Name=dlg.m_SubjectName; //试卷名称 08 try 09 { 10 int subjectid; 11 CString sql="select*from subject"; 12 m_AdoConn.OnInitADOConn(); //连接数据库 13 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql);//执行查询 14 if(m_pRs->adoEOF) //数据库没有其他试卷 15 { 16 subjectid=1; //试卷ID 17 //向subject表中添加试卷 18 sql.Format("insert into subject(subjectid,subjectname) 19 values(%d,'%s')",subjectid,Name); 20 m_AdoConn.ExecuteSQL((_bstr_t)sql); //执行Insert语句 21 } 22 else //数据库中有其他试卷 23 { 24 m_pRs->MoveLast(); //最后一条记录 25 //将其试卷ID加1 26 subjectid=atoi((char*)(_bstr_t)m_pRs->GetCollect("subjectid"))+1; 27 //向subject表中添加试卷 28 sql.Format("insert into subject(subjectid,subjectname) 29 values(%d,'%s')",subjectid,Name); 30 m_AdoConn.ExecuteSQL((_bstr_t)sql); //执行Insert语句 31 } 32 m_AdoConn.ExitConn(); //断开连接 33 m_TextTree.DeleteAllItems(); //删除所有的树节点 34 AddToTree(); //重新添加树节点 35 } 36 catch(...) //捕捉异常 37 { 38 AfxMessageBox("操作失败"); 39 return; //返回 40 } 41 } 42 }
函数首先创建增加试卷窗口,通过窗口的输入得到增加试卷的名称(第7行代码)。下面就是为试卷进行ID编码,为了保证试卷ID的唯一性,采用ID值递增的方式。即首先查询subject表中的记录,如果记录为空(第14行代码),则ID值设为1,通过Insert语句将记录添加到subject表中。如果记录不为空,则取其最后一条记录(第24行代码),将其ID值加1作为新添加试卷的ID,通过Insert语句将记录添加到subject表中。
管理员如果要删除试卷,只需要在树形控件中选中试卷,然后单击“删除试卷”按钮。按钮的响应函数为OnDelsubject,代码如下。
例程4.25代码位置:光盘\第4章\ ExamSystem \ TextManage.cpp
01 void CTextManage::OnDelsubject() //删除试卷 02 { 03 //获取选中的节点 04 HTREEITEM m_Tree=m_TextTree.GetSelectedItem(); 05 CString sql,Name; 06 Name=m_TextTree.GetItemText(m_Tree); 07 if(MessageBox("确定要删除该科目吗?","系统提示", 08 MB_OKCANCEL|MB_ICONQUESTION)==IDOK) 09 { 10 m_AdoConn.OnInitADOConn(); //连接数据库 11 //执行删除操作 12 sql.Format("delete from subject where subjectname='%s'",Name); 13 m_AdoConn.ExecuteSQL((_bstr_t)sql); //执行Delete语句 14 m_AdoConn.m_pCon->Close(); //关闭连接 15 m_TextTree.DeleteAllItems(); //删除树形控件各节点 16 AddToTree(); //重新添加节点 17 }
函数首先获得要删除试卷的名称,即树形控件选中节点的标题(第4~6行代码),而后通过Delete语句删除subject表中该试卷的记录(第12、13行代码)。
4.8.4 增加、修改和删除试题开发
1. 增加试题
要为考试试卷增加试题,管理员只需单击“增加试题”按钮,弹出“新增试题”窗口,如图4-21所示。在窗口的下拉框中选择试卷,输入试题题目、答案选项、正确答案及分值,单击“增加”按钮,即实现了试卷中添加试题。
图4-21 “新增试题”窗口
“增加试题”按钮的响应函数为OnAddtest,代码如下。
例程4.26代码位置:光盘\第4章\ ExamSystem \ TextManage.cpp
01 void CTextManage::OnAddtest() //增加试题 02 { 03 CAddTest dlg; //新增试题窗口 04 if(dlg.DoModal()==IDOK) 05 { 06 CString Question; 07 CString Answer; 08 CString Subject; 09 CString totle; 10 CString AnswerA,AnswerB,AnswerC,AnswerD; 11 Subject=dlg.ComboText; //试卷名称 12 Question=dlg.m_Question; //试题问题 13 Answer=dlg.m_Answer; //正确答案 14 totle=dlg.m_Totle; //分值 15 AnswerA=dlg.m_AnswerA; //答案A选项 16 AnswerB=dlg.m_AnswerB; //答案B选项 17 AnswerC=dlg.m_AnswerC; //答案C选项 18 AnswerD=dlg.m_AnswerD; //答案D选项 19 int testid; 20 CString sql="select*from testquestion where question='"+Question+"'"; 21 try 22 { 23 m_AdoConn.OnInitADOConn(); 24 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 25 if(!m_pRs->adoEOF) //记录不为空,即试题已经存在 26 { 27 AfxMessageBox("试题已经存在"); 28 return; 29 } 30 else //试题不存在 31 { 32 //获取试题所在试卷的ID 33 sql="select*from subject where subjectname='"+Subject+"'"; 34 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 35 int Sid=atoi((char*)(_bstr_t)m_pRs->GetCollect("subjectid")); 36 //为试题设置ID 37 sql="select*from testquestion"; 38 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 39 if(m_pRs->adoEOF) //没有试题 40 { 41 testid=1000; 42 } 43 else 44 { 45 //将最后一个试题的ID值加1作为新的试题ID 46 m_pRs->MoveLast(); //最后一条记录 47 testid=atoi((char*)(_bstr_t)m_pRs->GetCollect("testid"))+1; 48 } 49 //向testquestion表中添加试题 50 sql.Format("insert into testquestion(testid,question,rightanswer,\ 51 subjectid,score)values(%d,'%s','%s',%d,'%s')", 52 \testid,Question,Answer,Sid,totle); 53 m_AdoConn.ExecuteSQL((_bstr_t)sql); //执行Insert语句 54 //向testanswer表中添加试题答案 55 sql.Format("insert into testanswer(testid,AnswerA,AnswerB,\ 56 AnswerC,AnswerD)values(%d,'%s','%s','%s','%s')",\ 57 testid,AnswerA,AnswerB,AnswerC,AnswerD); 58 m_AdoConn.ExecuteSQL((_bstr_t)sql); //执行Insert语句 59 m_AdoConn.ExitConn(); //断开连接 60 m_TextList.DeleteAllItems(); //清空列表框 61 AddToList(Sid); //重新添加列表框值 62 } 63 } 64 catch(...) //捕捉异常 65 { 66 AfxMessageBox("操作失败!"); 67 return; //返回 68 } 69 } 70 }
函数首先创建增加试题窗口,通过窗口的输入得到试题所属试卷、试题题目、试题答案选项、正确答案和分值信息(第3~18行代码)。而试题ID信息则是由程序自动添加的,与前面介绍的试卷ID相似,首先查询TestQuestion表,若没有记录,则将ID设置为1000(第39~42行代码),否则,获取TestQuestion表最后一条记录的试题ID值,将其值加1作为新添加的试题ID(第46、第47行代码)。最后,通过Insert语句向TestQuestion表和TestAnswer表添加记录。
2. 修改试题
管理员修改试题,只需在列表框中选中要修改的试题,单击“修改试题”按钮,即弹出“试题修改”窗口,如图4-22所示。在窗口可以修改题目和答案,修改完毕,单击“确定”按钮,即完成了试题的修改。
图4-22 “试题修改”窗口
“修改试题”按钮的响应函数为OnChange,代码如下。
例程4.27代码位置:光盘\第4章\ ExamSystem \ TextManage.cpp
01 void CTextManage::OnChange() //修改试题 02 { 03 //获取列表框中选中的试题 04 int i=m_TextList.GetSelectionMark(); //选中项序号 05 Question=m_TextList.GetItemText(i,0); //试题 06 Answer=m_TextList.GetItemText(i,1); 答案 07 if(i==-1) //没有选中试题 08 { 09 AfxMessageBox("请选择一个试题"); 10 return; //返回 11 } 12 else 13 { 14 CTestChange dlg; //试题修改窗口 15 dlg.m_Question=Question; //题目 16 dlg.m_Answer=Answer; //答案 17 //获取试题所属试卷ID 18 CString sql; 19 m_AdoConn.OnInitADOConn(); 20 sql.Format("select*from testquestion where question='%s'",Question); 21 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 22 int Sid=atoi((char*)(_bstr_t)m_pRs->GetCollect("subjectid")); 23 if(dlg.DoModal()==IDOK) 24 { 25 m_TextList.DeleteAllItems(); //清空列表框 26 AddToList(Sid); //重新显示试题 27 } 28 } 29 }
函数首先获取选中要修改的试题题目和答案(第4~6行代码),然后创建修改试题对话框CTestChange,修改试题操作实际上是在CTestChange类中实现的。
修改试题对话框中“确定”按钮响应函数OnOk的代码如下。
例程4.28代码位置:光盘\第4章\ ExamSystem \ TextManage.cpp
01 void CTestChange::OnOk() 02 { 03 UpdateData(); //获取控件输入 04 if(m_Answer=="a"||m_Answer=="b"||m_Answer=="c"||m_Answer=="d") 05 { 06 AfxMessageBox("答案请用大写字母"); 07 return; //返回 08 } 09 if(m_Answer.IsEmpty()||(m_Answer!='A'&&m_Answer!='B'&&m_Answer!='C 10 '&&m_Answer!='D')) 11 { 12 AfxMessageBox("请输入正确答案"); 13 return; //返回 14 } 15 CString sql; 16 sql.Format("update testquestion set question='%s',rightanswer='%s'where 17 testid=%d",m_Question,m_Answer,testid); 18 try 19 { 20 m_AdoConn.OnInitADOConn(); //连接数据库 21 m_AdoConn.ExecuteSQL((_bstr_t)sql); //执行Update语句 22 } 23 catch(...) //捕捉异常 24 { 25 AfxMessageBox("操作失败"); 26 return; //返回 27 } 28 CDialog::OnOK(); //退出对话框 29 }
函数校验用户输入的试题答案是否符合规范(第4~14行代码),然后使用Update语句依据试题ID更新记录(第15~22行代码)。
3. 删除试题
管理员要删除试题,只要在列表框中选中要删除的试题,单击“删除试题”按钮即可删除试题。“删除试题”按钮的响应函数为OnDel,代码如下。
例程4.29代码位置:光盘\第4章\ ExamSystem \ TextManage.cpp
01 void CTextManage::OnDel() //删除试题 02 { 03 //获取列表框的选中试题 04 int i=m_TextList.GetSelectionMark(); 05 CString str=m_TextList.GetItemText(i,0); 06 int testidold,testidnew; 07 if(i==-1) //没有选中试题 08 { 09 AfxMessageBox("请选择一个试题"); 10 return; //返回 11 } 12 try 13 { 14 //根据试题题目获取试题ID 15 CString sql="select*from testquestion where question='"+str+"'"; 16 m_AdoConn.OnInitADOConn(); 17 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); 18 testidold=atoi((char*)(_bstr_t)m_pRs->GetCollect("testid")); 19 CString strid; 20 strid.Format("%d",testidold); 21 //从testquestion表删除试题 22 sql="delete from testquestion where question='"+str+"'"; 23 m_AdoConn.ExecuteSQL((_bstr_t)sql); 24 //查询试题ID大于删除试题ID的记录 25 sql="select*from testquestion where testid>"+strid+""; 26 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); //执行查询 27 while(m_pRs->adoEOF==0) //遍历记录集 28 { 29 //更新记录,将ID值减1 30 testidold=atoi((char*)(_bstr_t)m_pRs->GetCollect("testid")); 31 testidnew=testidold-1; 32 sql.Format("update testquestion set testid=%d where 33 testid=%d",testidnew,testidold); 34 m_AdoConn.ExecuteSQL((_bstr_t)sql); //执行SQL 35 m_pRs->MoveNext(); //下一记录 36 } 37 m_AdoConn.ExitConn(); //断开连接 38 m_TextList.DeleteItem(i); //删除列表框行 39 } 40 catch(...) //捕捉异常 41 { 42 AfxMessageBox("操作失败"); 43 return; //返回 44 } 45 }
函数首先获取列表框选中项,得到要删除的试题题目(第4、第5 行代码),接着根据试题题目获取试题ID(第15~18行代码),从TestQuestion表中删除该试题(第22、第23行代码)。删除记录后,还需要更新TestQuestion表,将试题ID排在其后的试题ID值减1(第27~36行代码)。
4.9 考生成绩查询模块设计
管理员可以通过考生成绩查看模块查询所有考生的考试成绩,并可按照分数进行查询操作。
4.9.1 考生成绩查询窗口的创建
考生成绩查询窗口标签页对应的对话框类为CResultSelect,其窗口设计如图4-9所示。窗口创建时,在列表框中列出所有考生的成绩,成绩查询窗口对话框的初始化函数代码如下。
例程4.30代码位置:光盘\第4章\ ExamSystem \ ResultSelect.cpp
01 BOOL CResultSelect::OnInitDialog() 02 { 03 CDialog::OnInitDialog(); 04 //设置列表框 05 m_ResultList.SetExtendedStyle(LVS_EX_FLATSB|LVS_EX_FULLROWSELECT| 06 LVS_EX_GRIDLINES|LVS_EX_HEADERDRAGDROP); 07 m_ResultList.InsertColumn(0,"考生姓名",LVCFMT_CENTER,100,0); //添加列 08 m_ResultList.InsertColumn(1,"科目",LVCFMT_CENTER,150,1); //添加列 09 m_ResultList.InsertColumn(2,"分数",LVCFMT_CENTER,242,2); //添加列 10 m_ResultList.InsertColumn(3,"考生编号",LVCFMT_CENTER,100,0); //添加列 11 //查询Score表中的所有记录 12 CString sql="select*from Score"; 13 m_AdoConn.OnInitADOConn(); //连接数据库 14 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); //执行查询 15 CString subject,totle; 16 int i=0; //行号 17 while(m_pRs->adoEOF==0) //遍历记录集 18 { 19 //获取考生ID,姓名、试卷、分数 20 int studentid=atoi((char*)(_bstr_t)m_pRs->GetCollect("studentid")); 21 sql.Format("select*from register where studentid=%d",studentid); 22 _RecordsetPtr m_prs=m_AdoConn.GetRecordSet((_bstr_t)sql); 23 CString name=(char*)(_bstr_t)m_prs->GetCollect("name"); 24 subject=(char*)(_bstr_t)m_pRs->GetCollect("subject"); 25 totle=(char*)(_bstr_t)m_pRs->GetCollect("score"); 26 //添加到列表框 27 m_ResultList.InsertItem(i,""); 28 m_ResultList.SetItemText(i,0,name); 29 m_ResultList.SetItemText(i,1,subject); 30 m_ResultList.SetItemText(i,2,totle); 31 CString ss; 32 ss.Format("%d",studentid); 33 m_ResultList.SetItemText(i,3,ss); 34 i++; //行号加1 35 m_pRs->MoveNext(); //下一记录 36 } 37 m_Type.SetCurSel(0); 38 return TRUE; 39 }
函数的核心是从Score表中查询所有考生的成绩记录,获取相应字段的值,并添加到列表框中。
4.9.2 成绩查询功能设计
在考生成绩查询窗口中,管理员可以查询大于或者少于某一成绩的所有考生成绩记录。“查询”按钮的响应函数为OnSelect,代码如下。
例程4.31代码位置:光盘\第4章\ ExamSystem \ ResultSelect.cpp
01 void CResultSelect::OnSelect() //条件查询 02 { 03 m_ResultList.DeleteAllItems(); //清空列表框 04 UpdateData(); 05 int i=m_Type.GetCurSel(); //下拉列表框的选择项 06 CString sql; 07 int mark=atoi(m_Mark); //分数 08 if(i==0) //小于等于分数 09 { 10 sql.Format("select*from Score where score<=%d",mark); 11 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); //执行查询 12 CString subject,totle; 13 int i=0; 14 while(m_pRs->adoEOF==0) //遍历记录 15 { 16 //查询考生记录,获取各字段值 17 int studentid=atoi((char*)(_bstr_t)m_pRs->GetCollect("studentid")); 18 sql.Format("select*from register where studentid=%d",studentid); 19 _RecordsetPtr m_prs=m_AdoConn.GetRecordSet((_bstr_t)sql); 20 CString name=(char*)(_bstr_t)m_prs->GetCollect("name"); 21 subject=(char*)(_bstr_t)m_pRs->GetCollect("subject"); 22 totle=(char*)(_bstr_t)m_pRs->GetCollect("score"); 23 //添加到列表框相应字段 24 m_ResultList.InsertItem(i,""); 25 m_ResultList.SetItemText(i,0,name); 26 m_ResultList.SetItemText(i,1,subject); 27 m_ResultList.SetItemText(i,2,totle); 28 CString ss; 29 ss.Format("%d",studentid); 30 m_ResultList.SetItemText(i,3,ss); 31 i++; 32 m_pRs->MoveNext(); 33 } 34 } 35 if(i==1) //大于等于分数 36 { 37 sql.Format("select*from Score where score>=%d",mark); 38 m_pRs=m_AdoConn.GetRecordSet((_bstr_t)sql); //执行查询 39 CString subject,totle; 40 int i=0; 41 while(m_pRs->adoEOF==0) //遍历记录 42 { 43 //查询考生记录,获取各字段值 44 …… 45 } 46 } 47 }
条件查询的实现只是根据输入的条件设定相应的Select语句执行查询,并将结果添加到列表框中。
4.10 开发技巧和难点分析
4.10.1 标签页窗口的开发
在管理员后台管理主窗口中,系统采用了标签页窗口的形式,实现了不同的操作页面窗口。标签页窗口是通过使用标签控件来实现的,下面就简单介绍一下标签控件的创建和使用。
标签控件(Tab Control)是用来在一个窗口,如对话框中的同一用户区域控制多组显示信息或控制信息,由顶部的一组标签来控制不同的信息提示。标签既可以是文本说明也可以是一个代表文本含义的图标,或是两者的组合,MFC提供了CTabCtrl类实现对标签控件的封装。
CTabCtrl类的成员函数InsertItem实现了在标签控件中创建标签页,其原型如下:
BOOL InsertItem(int nItem,TC_ITEM*pTabCtrlItem)
其中参数nItem是创建的标签页的序号,此序号将在调用CTabCtrl的另一个成员函数GetCurSel时作为返回值。
InsertItem的关键在于第二个参数PTabCtrlItem,它是一个指向TC_ITEM结构的指针。TC_ITEM结构的定义如下:
typedef struct_TC_ITEM { UINT mask; //标签控件的类型 UINT lnReserved1; //VC保留,未用 UINT lnReserved2; //VC保留,未用 LPSTR pszText; //标签控件的项目文本 int cchTextMax; //pszText的长度 int iImage; //标签控件的图形序号 LPARAM lParam; //用于交换的数据 }TC_ITEM;
其中,mask指定了标签控件的类型,它可以是以下3个值:
● TCIF_TEXT: pszText成员有效。
● TCIF_IMAGE :iImage成员有效。
● TCIF_PARAM :iParam成员有效。
如果要使用多个属性,应该用按位或运算符“|”连接。例如要使pszText和iImage成员同时有效,则用TCIF_TEXT|TCIF_IMAGE作为mask的值。在编程中,真正经常使用的只有mask、pszText、iImage三个成员变量。
4.10.2 使用ADO操作数据库的步骤
本系统的核心是采用ADO对象完成对数据库的操作,通常情况下按照如下4个步骤即可。
(1)初始化OLE/COM库。
ADO是一组动态链接库,因此在使用之前还必须导入ADO并且初始化。在MFC应用里,一般在应用类的InitInstance成员函数里初始化OLE/COM库环境比较合适。初始化过程非常简单,只需简单地调用AfxOleInit()即可。
接着,还必须在工程的stdafx.h头文件里用直接导入符号#import,导入ADO库文件,以使编译器能正确编译,代码如下所示:
#import "C:\program files\common files\system\ado\msado15.dll" \ no_namespace rename("EOF","adoEOF")
这行代码声明“在工程中使用ADO但不使用ADO的名字空间,并且为了避免常数冲突将常数EOF改名为adoEOF”,现在就可以使用ADO接口了。
(2)用Connection对象连接数据库。
首先声明一个连接指针接口,如下所示:
_ConnectionPtr m_pConnection;
然后,调用_ConnectionPtr接口指针的方法CreateInstance,如下所示:
m_pConnection.CreateInstance(__uuidof(Connection));
接着设置连接字符串以便指定想要连接。
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。
(4)最后调用m_pConnection的Close方法关闭连接即可。
m_pConnection->Close();
由于初始化COM库的时候调用的是AfxOleInit,这种方法初始化COM库的优点就在于资源的释放也是自动进行的,所以不必担心资源泄露的问题。