一、引言
随着计算机信息技术的迅速发展,网考在国内外已逐渐成为发展的趋势,如驾驶执照的理论考试、职业医师证书的理论考试等。采用网考可节省大量的人员组织和阅卷等烦琐的中间过程,利于提高效率。但是传统网考的考题部分却只能交由专职软件公司来设计,缺点是软件应用领域的单一性以及软件的生存期等方面都受到局限,缺乏一定的灵活性,如果考题变化就又得投入大量资金去续订。为此,本文重点讨论的就是如何在传统网考系统的基础上去实现将一切考试设置以及出题的任务交由用户直接配置并能适用于各行各业的网考系统。
二、总体设计思路
所谓万能网考系统关键就是通过设计一个网考试卷制作机和与之对应的网考机来实现与传统网考系统进行对接。其中网考试卷制作机这部分设计的主要思路是用户将考试的基本设置配置在INI总表中;将各套考题题目写入题库中;再将题目索引与答案配置在各自那套题的INI文件中。网考机这部分实现的关键则就是如何去阅读INI配置并启动考题;然后将考试中所选的答案以及对比后所得的分数配置在一个临时INI文件中;最后交卷时根据临时INI文件中的记载将各分数汇总起来写入以考号为文件名的INI文件中上传服务器,再由传统的监考系统将上传所有考生成绩汇入总表中,从而实现了既能由用户自行出题又能适用于各行各业并与传统网考相对接的所谓万能网考系统。
三、应用设计
在应用实现时,主要分两部分来进行介绍,其中第一部分是网考试卷制作机的实现,第二部分是与之对应的网考机的实现。先在此作个预备性的交代,为什么在设置各选题题目时不采用按钮而采用列表选项形式呢,主要原因是按钮会受到数量上的限制而列表选项则不受此限制。具体处理在后面再作详细交代。
1.分网考试卷制作机的实现方法
这部分主要完成的功能是由用户自行设定试卷套数、当前所在那套题的序号、考试时间等基本设置并写入INI总表中;然后依次进入单选题、判断题、多选题等界面去设置各套考题并将各题目标题写入题库;再将各题的题目索引以及标准答案写入当前那套题的INI文件中,这样便完成了网考试卷制作机的基本考试设置与试卷制作功能。具体实现方法是:首先在MFC平台下新建一个名为MakeTestPaperSys的对话框程序、依次插入五个对话框并按以下图形进行布局,其中图1为主对话框。图3、图4、图5、图6中左面均为ListBox列表框且注意在属性中取消SORT排序选项;右边为Edit文本框并在属性中设置为多行且允许回车。具体按以下步骤进行。
图1 主窗口设计 图2 基本考试设置 图3 单选题设置
图4 判断题设置 图5 多选题设置 图6 操作题设置
第一步:程序的主界面如图1,这部分完成的主要功能是依次用相应的按钮去启动出所对应的各对话框,实现步骤如下。
(1)首先添加出包头代码
#include "Dlg_SetBase.h" //代表基本设置对话框
#include "Dlg_SetSelectSingle.h" //代表单选题设置对话框
#include "Dlg_SetSetJudge.h" //代表判断题设置对话框
#include "Dlg_SetSelectMore.h" //代表多选题设置对话框
#include "Dlg_SetOperator.h" //代表操作题设置对话框
(2)然后在初始化函数BOOL CMakeTestPaperSysDlg::OnInitDialog()中添加如下代码
CreateDirectory(".\\TestBase",false); //如果题库文件夹不存在则创建
(3)然后依次在主界面中为各按钮添加单击响应函数与启动所对应的对话框代码
CDlg_SetBase SetBaseDlg; SetBaseDlg.DoModal(); //基本考试设置按钮中添加
CDlg_SetSelectSingle SetSingleDlg; SetSingleDlg.DoModal(); //单选题设置按钮中添加
CDlg_SetSetJudge SetJudgeDlg; SetJudgeDlg.DoModal(); //判断题设置按钮中添加
CDlg_SetSelectMore SetMoreDlg; SetMoreDlg.DoModal(); //多选题设置按钮中添加
CDlg_SetOperator SetOperatorDlg; SetOperatorDlg.DoModal(); //操作题设置按钮中添加
第二步:基本设置对话框如图2,这部分完成的主要功能是将如图所示的各项有关设置写入CONFIG.INI的总表中以方便修改和考试时调用,实现步骤如下。
(1)首先为各文本框添加CString类型变量
m_TestCount //试卷套数 m_CurrentConfig //当前所在套
m_TestTime //考试时间
m_SelectSingleCount //单选题数量 m_SelectSinglePoints //单选题每小题分数
m_SelectMoreCount //多选题数量 m_SelectMorePoints //多选题每小题分数
m_JudgeCount //判断题数量 m_JudgePoints //判断题每小题分数
m_OperationCount //操作题数量 m_OperationPoints //操作题每小题分数
(2)然后为该对话框添加初始化消息函数WM_INITDIALOG并在其中添加出如下代码,主要目的是从CONFIG.INI总表中获取有关数据并显示在各文本框中,以方便随时修改。
UpdateData(true);
::GetPrivateProfileString("CONFIG","TestCount",NULL,m_TestCount.GetBuffer(28),28,".\\CONFIG.INI");
::GetPrivateProfileString("CONFIG","CurrentConfig",NULL,m_CurrentConfig.GetBuffer(28),28,
".\\CONFIG.INI");
::GetPrivateProfileString("CONFIG","TestTime",NULL,m_TestTime.GetBuffer(28),28,".\\CONFIG.INI");
::GetPrivateProfileString("CONFIG","SelectSingleCount",NULL,m_SelectSingleCount.GetBuffer(28),28, ".\\CONFIG.INI");
::GetPrivateProfileString("CONFIG","SelectSinglePoints",NULL,m_SelectSinglePoints.GetBuffer(28),28, ".\\CONFIG.INI");
::GetPrivateProfileString("CONFIG","SelectMoreCount",NULL,m_SelectMoreCount.GetBuffer(28),28, ".\\CONFIG.INI");
::GetPrivateProfileString("CONFIG","SelectMorePoints",NULL,m_SelectMorePoints.GetBuffer(28),28, ".\\CONFIG.INI");
::GetPrivateProfileString("CONFIG","JudgeCount",NULL,m_JudgeCount.GetBuffer(28),28,".\\CONFIG.INI");
::GetPrivateProfileString("CONFIG","JudgePoints",NULL,m_JudgePoints.GetBuffer(28),28,".\\CONFIG.INI");
::GetPrivateProfileString("CONFIG","OperationCount",NULL,m_OperationCount.GetBuffer(28),28,
".\\CONFIG.INI");
::GetPrivateProfileString("CONFIG","OperationPoints",NULL,m_OperationPoints.GetBuffer(28),28,
".\\CONFIG.INI");
UpdateData(false);
(3)最后在确认设置按钮中添加如下代码,主要目的是将有关设置写入CONFIG.INI总表中。
UpdateData(true);
if(atoi(m_TestCount) < atoi(m_CurrentConfig)) //判断一下当前INI配置序号是否超出规定范围
{ AfxMessageBox("当前配置超出规定范围!\n设置不成功"); return; }
::WritePrivateProfileString("CONFIG","TestCount",m_TestCount,".\\CONFIG.INI");
::WritePrivateProfileString("CONFIG","CurrentConfig",m_CurrentConfig,".\\CONFIG.INI");
::WritePrivateProfileString("CONFIG","TestTime",m_TestTime,".\\CONFIG.INI");
::WritePrivateProfileString("CONFIG","SelectSingleCount",m_SelectSingleCount,".\\CONFIG.INI");
::WritePrivateProfileString("CONFIG","SelectSinglePoints",m_SelectSinglePoints,".\\CONFIG.INI");
::WritePrivateProfileString("CONFIG","SelectMoreCount",m_SelectMoreCount,".\\CONFIG.INI");
::WritePrivateProfileString("CONFIG","SelectMorePoints",m_SelectMorePoints,".\\CONFIG.INI");
::WritePrivateProfileString("CONFIG","JudgeCount",m_JudgeCount,".\\CONFIG.INI");
::WritePrivateProfileString("CONFIG","JudgePoints",m_JudgePoints,".\\CONFIG.INI");
::WritePrivateProfileString("CONFIG","OperationCount",m_OperationCount,".\\CONFIG.INI");
::WritePrivateProfileString("CONFIG","OperationPoints",m_OperationPoints,".\\CONFIG.INI");
//仿照以上方式将传统网考登陆服务器所需要的有关信息写入CONFIG.INI总表中(这部分略去)
//***注意WritePrivateProfileString ( )函数,主要作用是把要写入的数据以字符串的形式写入INI文件
//***其中第一个参数是项目名,第二个为项目中选项名,第三要写入的字符串,第四个是INI文件名
AfxMessageBox("基本设置成功!");
第三步:单选题设置对话框如图3,这部分主要完成的功能是将单选题选项显示在列表中;将题目标题写入题库;再将该题的题目索引连同设置的答案写入当前那套题的INI文件中,具体按以下步骤进行。
首先为列表框添加CListBox类型变量m_List且取消SORT属性;为文本框添加CString类型变量为m_edit且在属性中选中多行并允许回车;并在头文件部添加以下几个公有全局变量以方便在程序的各个地方随时赋值、取值。
CButton *RedioA; CButton *RedioB; CButton *RedioC; CButton *RedioD;
int iSingleCount; //单选题的数量
int iSelectSingle ; //记录当前单选题题目序号标记
int ListSinglePos; //记录当前单选题选项在列表中的位置,便于在完成答案时给出OK提示
int iConfig; //当前选中那套题的序号
CString strCurrentINI; //当前的INI文件路径与文件名
CString strFileName; //对应题库中的文件名
然后为该对话框添加初始化消息函数WM_INITDIALOG并在其中添加如下代码,主要功能是为全局变量中定义的各变量赋予初始值以方便各地方调用,代码如下:
//先定义出公有全局指针变量,在初试化函数中赋值,以方便在各个地方调用
RedioA = (CButton *)GetDlgItem(IDC_RADIOA); RedioB = (CButton *)GetDlgItem(IDC_RADIOB);
RedioC = (CButton *)GetDlgItem(IDC_RADIOC); RedioD = (CButton *)GetDlgItem(IDC_RADIOD);
//从CONFIG.INI总表中获得当前那套题的序号并处理成INI文件名,以方便在各个地方进行正确读写
iConfig = ::GetPrivateProfileInt("CONFIG", "CurrentConfig", 0 ,".\\CONFIG.INI");
strCurrentINI.Format( ".\\Config%d.ini", iConfig );
//从INI总表中获得单选题数量并将选题显示在列表中,同时对已设置的题目赋予OK标志以防遗漏
iSingleCount = ::GetPrivateProfileInt("CONFIG", "SelectSingleCount", 0 ,".\\CONFIG.INI");
//***注意GetPrivateProfileInt( )函数,主要作用是直接从INI文件中获得int整型数据
//***其中第一个参数代表项目名;第二个代表该项目中选项名;最后一个代表INI文件名
CString strItem; CString strListItem; CString strResult;
for(int i=1;i<=iSingleCount;i++)
{ strItem.Format("Item%d", i);
::GetPrivateProfileString("SelectSingleTitle",strItem,NULL,strResult.GetBuffer(128),128,strCurrentINI);
//***注意GetPrivateProfileString ( )函数,主要作用是从INI文件中读取字符串
//***其中第一个参数代表项目名;第二个为该项目中选项名;第四个是要获得的字符串值与长度
if(strResult!="") { strListItem.Format(" 单选题%d OK!", i); m_List.AddString(strListItem); }
else { strListItem.Format(" 单选题%d",i); m_List.AddString(strListItem); }
}
分别添加一个WriteFile(CString FileName)函数和ReadFile(CString FileName)函数,主要功能是将单选题中设置的题目写入题库中或从题库中读出选题题目并显示在文本框中。
void CDlg_SetSelectSingle::WriteFile(CString FileName)
{ CFile file; file.Open(strFileName,CFile::modeWrite|CFile::modeCreate);
int len=m_edit.GetLength(); char *buffer=new char[len+1]; strcpy(buffer,m_edit); buffer[len]='\0';
file.Write(buffer,len); file.Close(); delete buffer;
}
void CDlg_SetSelectSingle::ReadFile(CString FileName)
{ CFile file; if(!file.Open(strFileName,CFile::modeRead)) { m_edit=""; UpdateData(FALSE); return; }
else
{ int len=file.GetLength(); char *buffer=new char[len+1]; file.Read(buffer,len); buffer[len]='\0';
m_edit=buffer; UpdateData(FALSE); delete buffer; file.Close();
}
}
这部分主要针对单选题选项的单击响应处理,主要功能是根据单击时所留下的标记,从当前INI配置文件中获取当前题目所对应题库中的文件名并将题目与答案显示出来,以方便修改。
先添加一个单选题的设置函数SetSingleItem(),具体如下:
void CDlg_SetSelectSingle::SetSingleItem()
{ //清空所有被选中的单选框以避免引起混乱
RedioA->SetCheck(0); RedioB->SetCheck(0); RedioC->SetCheck(0); RedioD->SetCheck(0);
//根据单选题标记,从INI配置中找出该题的配置的答案并显示出来
CString strSelectItem; CString strItemResult; strSelectItem.Format("Item%d", iSelectSingle);
::GetPrivateProfileString("SelectSingleResult",strSelectItem,NULL,strItemResult.GetBuffer(128),128, strCurrentINI);
if(strItemResult == "A") RedioA->SetCheck(1); if(strItemResult == "B") RedioB->SetCheck(1);
if(strItemResult == "C") RedioC->SetCheck(1); if(strItemResult == "D") RedioD->SetCheck(1);
//根据单选题标记,从当前INI配置中找出该题的题目标题并在Edit框中显示出来
CString strItemName; strItemName.Format("Item%d", iSelectSingle);
::GetPrivateProfileString("SelectSingleTitle",strItemName,NULL,strFileName.GetBuffer(128),128,strCurrentINI);
ReadFile(strFileName);
//设置单选题选项按下时对窗口与确认按钮的标题设置提示
CString strDlgTitle; CString strOK; strDlgTitle.Format("单选题%d", iSelectSingle);
SetWindowText(strDlgTitle); strOK.Format("确认单选题%d的设置", iSelectSingle);
GetDlgItem(IDC_SET_SINGLE_OK)->SetWindowText(strOK+"的设置");
}
然后为列表框添加列表单击响应函数LBN_SELCHANGE且在函数中作好标记后调用单选题的设置函数:
void CDlg_SetSelectSingle::OnSelchangeList1()
{ ListSinglePos = m_List.GetCurSel(); iSelectSingle = m_List.GetCurSel()+1; SetSingleItem(); }
确认按钮的处理,这部分主要完成的功能是将当前所设置的单选题题目与答案写入当前的INI文件中。
void CDlg_SetSelectSingle::OnSetSingleOk()
{ //检测单选题设置格式是否合要求
if(!RedioA->GetCheck()&&!RedioB->GetCheck()&&!RedioC->GetCheck()&&!RedioD->GetCheck())
{AfxMessageBox("必须设置一个正确答案!,然后确认"); return; }
UpdateData(true);
if(m_edit =="") { AfxMessageBox("必须填写出该题的题目!然后确认"); return; }
//将当前的单选题序号所对应的答案处理成字符串写入当前的INI文件
CString strKey; CString strSel; strSel.Format("Item%d", iSelectSingle);
if(RedioA->GetCheck()) strKey = "A"; if(RedioB->GetCheck()) strKey = "B";
if(RedioC->GetCheck()) strKey = "C"; if(RedioD->GetCheck()) strKey = "D";
::WritePrivateProfileString("SelectSingleResult",strSel,strKey,strCurrentINI);
strSel.Format("单选题%d设置成功!", iSelectSingle); MessageBox(strSel);
//将单选题序号所对应的题目写入题库、题目索引写入INI文件
CString strSelect; strSelect.Format("Item%d", iSelectSingle);
strFileName.Format( ".\\TestBase\\%dSingle%d.txt", iConfig, iSelectSingle );
::WritePrivateProfileString("SelectSingleTitle", strSelect, strFileName, strCurrentINI);
WriteFile(strFileName);
//设置写入INI成功后将列表框中相应的单选题选项标题变为OK!并给出提示
CString strList; strList.Format( " 单选题%d OK!", iSelectSingle );
m_List.DeleteString(ListSinglePos); m_List.InsertString( ListSinglePos, strList );
GetDlgItem(IDC_SET_SINGLE_OK)->SetWindowText("OK!");
}
第四步:即判断题设置对话框的处理如图4,这部分主要完成的功能是将判断题显示在列表中;将判断题的题目标题写入题库中,再将相应题目索引以及设置的标准答案记录在当前所在那套题的INI文件中,具体代码如下步骤进行处理。
首先为列表添加CListBox类型变量m_List;为文本框添加CString 类型变量m_Judge并设置如下几个全局变量:
CButton *RedioY; CButton *RedioN; //前一个代表正确Redio单选按钮,后一个代表错误单选按钮
int iJudgeCount; //表示判断题的数量
int iJudge ; //记录当前的判断题选项的序号标记
int ListJudgePos; //记录当前判断题选项在列表中的位置,便于完成答案时给出OK提示
int iconFig; //当前选中那套题的序号
CString strINI; //当前那套题的文件名
CString strFilename; //当前所对应题库中的文件名
然后为该对话框添加初始化消息函数WM_INITDIALOG并在其中添加如下代码,主要功能是为全局变量中定义的各变量赋予初始值以方便各地方调用,代码如下:
//在初试化函数中赋值,以方便在各个地方调用
RedioY = (CButton *)GetDlgItem(IDC_RADIOY); RedioN = (CButton *)GetDlgItem(IDC_RADION);
//从INI总表中获得当前那套题的序号并处理成当前INI文件名,以方便在各个地方进行正确读写
iconFig = ::GetPrivateProfileInt("CONFIG","CurrentConfig",0,".\\CONFIG.INI");
strINI.Format( ".\\Config%d.ini", iconFig );
//从INI总表中获得判断题数量并将选题显示在列表中,同时对已设置的题目赋予OK标志以防遗漏
iJudgeCount = ::GetPrivateProfileInt("CONFIG","JudgeCount",0,".\\CONFIG.INI");
CString strItem; CString strListItem; CString strResult;
for(int i=1; i<=iJudgeCount; i++)
{ strItem.Format( "Item%d", i );
::GetPrivateProfileString("JudgeTitle",strItem,NULL,strResult.GetBuffer(128),128,strINI);
if(strResult!="") { strListItem.Format(" 判断题%d OK!", i); m_List.AddString(strListItem);}
else { strListItem.Format( " 判断题%d", i ); m_List.AddString(strListItem); }
}
分别添加一个WriteFile(CString FileName)函数和ReadFile(CString FileName)函数,主要功能是将判断题中设置的题目写入题库中或从题库中读出选题题目并在文本框中显示出来。
void CDlg_SetJudge::WriteFile(CString FileName)
{ CFile file; file.Open(strFilename, CFile::modeWrite | CFile::modeCreate);
int len=m_Judge.GetLength(); char *buffer=new char[len+1]; strcpy(buffer,m_Judge); buffer[len]='\0';
file.Write(buffer, len); delete buffer; file.Close(); UpdateData(false);
}
void CDlg_SetJudge::ReadFile(CString FileName)
{ CFile file; if(!file.Open(strFilename, CFile::modeRead)) { m_Judge=""; UpdateData(false); return; }
else
{ int len = file.GetLength(); char *buffer = new char[len+1]; file.Read(buffer, len);
buffer[len]='\0'; m_Judge=buffer; UpdateData(false); delete buffer; file.Close();
}
}
这部分主要针对判断题选项的单击响应处理,主要功能是根据单击时的所在标记与提示并从当前INI配置文件中获取当前题目标题与答案显示出来,以方便修改。
先添加一个判断题选项的设置函数SetJudgeItem(),具体如下:
void CDlg_SetJudge::SetJudgeItem()
{ //清空所有被选中的单选框以避免引起混乱
RedioY->SetCheck(0); RedioN->SetCheck(0);
//根据判断题序号标记,从INI配置中找出该题的配置的答案并显示出来
CString strJudgeItem; CString strItemResult; strJudgeItem.Format( "Item%d", iJudge );
::GetPrivateProfileString("JudgeResult",strJudgeItem,NULL,strItemResult.GetBuffer(128),128,strINI);
if(strItemResult == "Y") RedioY->SetCheck(1) ; if(strItemResult == "N") RedioN->SetCheck(1);
//根据判断题序号标记,从当前INI配置中找出该题在题库中文件名并启动显示在Edit框中
CString strItemName; strItemName.Format("Item%d", iJudge);
::GetPrivateProfileString("JudgeTitle",strItemName,NULL, strFilename.GetBuffer(128),128,strINI);
ReadFile(strFilename);
//设置判断题按下时窗口与确认按钮的标题提示
CString strDlgTitle;CString strOK;
strDlgTitle.Format( "判断题%d", iJudge ); SetWindowText(strDlgTitle);
strOK.Format( "确认判断题%d的设置", iJudge );
GetDlgItem(IDC_JUDGE_OK)->SetWindowText(strOK);
}
然后为列表框添加列表单击响应函数且在函数中作好标记后调用判断题的配置函数
void CDlg_ CDlg_SetJudge::OnSelchangeList1()
{ ListJudgePos = m_List.GetCurSel(); iJudge = m_List.GetCurSel()+1; SetJudgeItem(); }
确认按钮的处理,这部分所主要完成的功能是将当前所设置的判断题题目与答案写入当前的INI文件中:
void CDlg_ CDlg_SetJudge:: OnJudgeOk()
{ //检测判断题设置格式是否合要求
if(!RedioY->GetCheck() && !RedioN->GetCheck())
|