第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库的优点就在于资源的释放也是自动进行的,所以不必担心资源泄露的问题。