设计一个在线考试系统,卷面计时是必须解决的一个问题,因为考试都有一个答题时间限制,时间一到,针对那些仍未交卷的考生,系统应该有强制收卷的功能。当然,对于一个在线考试系统,最重要的还应该是他的答案保存功能,这里不做讨论,只分析卷面计时问题,最终设计出一个运行在客户端、超时后可以调用服务器端方法的计时器控件。
问题分析
(1)考生打开装载试题的页面标志着考试的开始,所以应该从这一刻起开始计时。(2)服务器只能在有客户端请求的时候才会提供数据,而不会主动向浏览器post信息,所以必须在客户端实现计时。(3)强制交卷本质上就是保存答案并退出答题界面,需要回发数据,因而该操作应该是一个服务器端方法。(4)应该有一个友好界面,提示考生还剩多少时间。(5)为了防止某些考生以给考试界面抓图保存、退出系统后按图做题、然后回来填写答案的方式作弊,开始答题的同时交卷时间确定。(6)对于那些受网络掉线、死机等不确定性因素影响而在考试过程中意外退出答题界面的考生,因为无法准确判断他们和蓄意作弊考生的区别,我找不到合适的解决方法,只能算他们倒霉,浪费掉那些等待时间。你要有办法,可以讨论一下大家。提出方案 针对以上的分析,可以提出一个大概的解决方法。
在数据库方面,在考试信息表中应该包含考生标识(string,用考号就可以)、开始答题时间(DateTime)、答题结束时间(DateTime)、已交卷(bool)四个字段(当然还得有其他的必要字段,这里就不详细说明了,毕竟我要说的只是卷面计时问题)。
在客户端方面,答题页面onload的时候调用一个javascript函数开始计时,这个函数是循环执行的,以便随时保存已用时间,这里设置其循环周期为1分钟,在函数体内,首先需要判断是否已到限制时间,若是则强制交卷,若否则显示友好界面的计时信息,然后等待下一次调用,相关js代码如下:
< script language = " JavaScript " > var myTimeOut = 30 ; // 可用时间,单位为分钟 var myPassTime = 0 ; // 已用时间,单位为分钟 window.attachEvent( " onload " , myTimer); // 绑定到onload事件 function myTimer() { if(myPassTime<myTimeOut){ //已用时间是否小于可用时间 myPassTime+=1; //保存客户端已用时间 //显示友好界面的计时信息 这里先空着 }else{ //执行强制交卷 //可以通过模拟点击一个linkbutton来实现, //在服务器端把强制交卷的代码先在linkbutton的Click里就可以了 } window.setTimeout("myTimer()",60000);//一分钟循环一次} </ script >在服务器代码方面,Page_Load事件中对考试信息进行初始化:if(根据标识检索到当前考生的考试信息){ if(已交卷){ 反馈已交卷信息 } else { if(当前时间<答题结束时间){ 设置好已用时间,让考生继续答题 } else { 强制交卷 } }} else { 添加考生的考试信息 设置好开始答题时间(设为当前时间)、答题结束时间(当前时间加上总的可用时间)、已交卷为(false) 正常进入考场}
考试页面放置一个linkbutton,在该控件的Click里编写强制交卷代码,以供js模拟点击的时候执行。设计控件
为了方便以后使用,这里把计时相关的功能封装成一个自定义控件。
控件名称:clientTimer,从PlaceHolder继承,命名空间为myControl。
公开属性:
属性名:TimeOutUnits 类 型:TimeOutUnitsType 介 绍:计时单位,有秒、分钟、小时三种,默认为分钟。
属性名:TimeOutLength 类 型:int 介 绍:计时超时时间(单位与TimeOutUnits属性一致)。
属性名:PassTimeLength 类 型:int 介 绍:已用去的时间(单位与TimeOutUnits属性一致)。
属性名:TimerEnabled 类 型:bool 介 绍:是否启用计时器。 属性名:CountDown 类 型:bool 介 绍:是否以倒计时的方式显示友好界面,是则显示还剩多少时间,否则显示用了多少时间。
公开事件:
事件名:onTimeOut 介 绍:超时的时候执行,可以把强制交卷的代码放里头执行
控件源码:
using System; using System.IO; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.ComponentModel; using System.ComponentModel.Design; namespace myControl ... { /**//**//**//// <summary> /// 客户端计时器clientTimer控件 /// 在线考试系统中卷面计时所用,你可以自由修改 /// 丛兴滋(cncxz) 2005-12-3 /// </summary> [Description("客户端计时器clientTimer")] [Designer(typeof(clientTimerDesigner))] [ToolboxData("<{0}:clientTimer runat=server></{0}:clientTimer>")] public class clientTimer: System.Web.UI.WebControls.PlaceHolder ...{ public onTimeOutEventHandler onTimeOut; //超时事件 private LinkButton myLB; private Label myLabel; "公共属性""公共属性"#region "公共属性" [Browsable(true),Category("计时相关"),DefaultValue(TimeOutUnitsType.Minute),Description("计时单位,有秒、分钟、小时三种,默认为分钟。")] public TimeOutUnitsType TimeOutUnits ...{ get ...{ object obj=ViewState["TimeOutUnits"]; return (obj==null)?TimeOutUnitsType.Minute:(TimeOutUnitsType)obj; } set ...{ ViewState["TimeOutUnits"]=value; } } [Browsable(true),Category("计时相关"),DefaultValue(30),Description("计时超时时间(单位与TimeOutUnits属性一致)。")] public int TimeOutLength ...{ get ...{ object obj=ViewState["TimeOutLength"]; return (obj==null)?30:int.Parse(obj.ToString()); } set ...{ ViewState["TimeOutLength"]=value; } } [Browsable(true),Category("计时相关"),DefaultValue(0),Description("已用去的时间(单位与TimeOutUnits属性一致)。")] public int PassTimeLength ...{ get ...{ object obj=ViewState["PassTimeLength"]; return (obj==null)?0:int.Parse(obj.ToString()); } set ...{ ViewState["PassTimeLength"]=value; } } [Browsable(true),Category("行为"),DefaultValue(false),Description("是否以倒计时的方式显示友好界面,是则显示还剩多少时间,否则显示用了多少时间。")] public bool CountDown ...{ get ...{ object obj=ViewState["CountDown"]; return (obj==null)?false:(bool)obj; } set ...{ ViewState["CountDown"]=value; } } [Browsable(true),Category("行为"),DefaultValue(true),Description("是否启用计时器")] public bool TimerEnabled ...{ get ...{ object obj=ViewState["TimerEnabled"]; return (obj==null)?true:(bool)obj; } set ...{ ViewState["TimerEnabled"]=value; } } #endregion public clientTimer() ...{ myLB=new LinkButton(); myLB.Click+=new EventHandler(myLB_Click); myLabel=new Label(); } private void myLB_Click(object sender, System.EventArgs e)...{ if(onTimeOut!=null)...{ onTimeOut(); } } protected override void OnLoad(EventArgs e) ...{ if(this.TimerEnabled) ...{ myLB.ID=this.ClientID+"_LB_TimeOut"; myLB.Text=""; myLabel.ID=this.ClientID+"_Label_Msg"; myLabel.Text=""; this.Controls.Add(myLB); this.Controls.Add(myLabel); } base.OnLoad(e); } protected override void Render(HtmlTextWriter writer) ...{ if(this.TimerEnabled) ...{ switch(this.TimeOutUnits) ...{ case TimeOutUnitsType.Second: writer.Write(this.strJS(1000," 秒")); break; case TimeOutUnitsType.Minute: writer.Write(this.strJS(60000," 分钟")); break; case TimeOutUnitsType.Hour: writer.Write(this.strJS(3600000," 小时")); break; } } base.Render(writer); } private string strJS(int intCycLength,string strUnits)...{ string strFunction=this.ClientID+"_Timer"; string strTimeOut=this.ClientID+"_TimeOut"; string strPassTime=this.ClientID+"_PassTime"; string scriptString =" "; scriptString += @"<script language=""JavaScript"">"+" "; scriptString += @" <!--"+" "; scriptString += "var "+strTimeOut+"="+this.TimeOutLength.ToString()+"; "; scriptString += "var "+strPassTime+"="+this.PassTimeLength.ToString()+"; "; scriptString += @" window.attachEvent(""onload"", "+strFunction+");"+" "; scriptString +="function "+strFunction+"() { "; scriptString += " if("+strPassTime+"<"+strTimeOut+"){ "; scriptString += @" //未超时"+" "; scriptString += " "+strPassTime+"+=1; "; if(this.CountDown) ...{ scriptString += " var myNum="+strTimeOut+"-"+strPassTime+"; "; scriptString += @" document.getElementById("""+this.myLabel.ClientID+@""").innerText=""剩余时间:""+myNum+"""+strUnits+@""";"+" "; } else ...{ scriptString += @" document.getElementById("""+this.myLabel.ClientID+@""").innerText=""已用时间:""+"+strPassTime+@"+"""+strUnits+@""";"+" "; } scriptString += " }else{ "; scriptString += @" //时间到"+" "; scriptString += @" document.getElementById("""+this.myLB.ClientID+@""").click();"+" "; scriptString += " } "; scriptString += @" window.setTimeout("""+strFunction+@"()"","+intCycLength.ToString()+@");"+" "; scriptString += "} "; scriptString += @"//-->"+" "; scriptString += @"</script>"+" "; return scriptString; } } /**//**//**//// <summary> /// 计时单位的类型。 /// </summary> public enum TimeOutUnitsType:byte ...{ /**//**//**//// <summary> /// 秒。 /// </summary> Second, /**//**//**//// <summary> /// 分钟。 /// </summary> Minute, /**//**//**//// <summary> /// 小时。 /// </summary> Hour } public delegate void onTimeOutEventHandler(); public class clientTimerDesigner:System.Web.UI.Design.ControlDesigner ...{ private clientTimer CT; public clientTimerDesigner()...{ } public override string GetDesignTimeHtml() ...{ CT=(clientTimer)Component; string str=""; str+=@"<span style=""height:20px;padding:2px 10px 2px 10px;border-left:1px solid #fafafa;border-top:1px solid #fafafa;border-bottom:1px solid #d0d0d0;border-right:1px solid #d0d0d0;FILTER: progid:DXImageTransform.Microsoft.Gradient(startColorStr='#f5f5f5', endColorStr='#e5e5e5', gradientType='0');"">"; str+=CT.ID+@"</span>"; return str; } } }控件测试
1、将控件源码保存并编译成一个dll文件,然后添加到vs的控件面板
2、新建一个WebApplication工程(WebApplication1)
3、向默认的WebForm1.aspx添加一个clientTime控件(clientTime1),一个Label控件(Label1),随便排列一下位置
4、选中clientTime1,在属性面板中设置:
clientTime1.TimerEnabled属性为trueClientTimer1.TimeOutUnits属性为Second //便于观察效果ClientTimer1.TimeOutLength属性为30 ClientTimer1.PassTimeLength属性为5ClientTimer1.CountDown属性为true //倒计时
5、在WebForm1.aspx的空白处双击,切换到codebehind视图,加入如下代码:
private void Page_Load( object sender, System.EventArgs e) { // 在此处放置用户代码以初始化页面 this.ClientTimer_Test.onTimeOut+=new myControl.onTimeOutEventHandler(this.ClientTimer1_onTimeOut);} private void ClientTimer1_onTimeOut() { this.ClientTimer_Test.TimerEnabled=false; Label1.Text=DateTime.Now.ToString()+"到时间了";}6、按F5编译运行,在WebForm1.aspx界面上可以看到 一个读秒的倒计时,归零后会在Label1那里显示 当前时间和到时间了
控件使用
1、将控件源码保存并编译成一个dll文件,然后添加到vs的控件面板
2、拖动一个控件到考试页面(假设其id为clientTime1)
3、Page_Load事件中注册clientTime1的onTimeOut事件,当然,还得有考试信息初始化的代码
private void Page_Load( object sender, System.EventArgs e) { this.ClientTimer_Test.onTimeOut+=new myControl.onTimeOutEventHandler(this.ClientTimer1_onTimeOut);// if(根据标识检索到当前考生的考试信息){// if(已交卷){// ClientTimer1.TimerEnabled=false; //不用计时了// 反馈已交卷信息// } else {// if(当前时间<答题结束时间){// ClientTimer1.TimerEnabled=true; //启用计时功能// ClientTimer1.TimeOutUnits=myControl.TimeOutUnitsType.Minute;//设置单位为分钟// ClientTimer1.TimeOutLength=答题结束时间 与 开始答题时间 之差; //设置答题总时间// ClientTimer1.PassTimeLength=答题结束时间 与 当前时间 之差; //设置已用时间 // //考生继续答题// } else {// ClientTimer1.TimerEnabled=false; //不用计时了// 强制交卷// }// }// } else {// 添加考生的考试信息// 设置好开始答题时间(设为当前时间)、答题结束时间(当前时间加上总的可用时间)、已交卷为(false) // ClientTimer1.TimerEnabled=true; //启用计时功能// ClientTimer1.TimeOutUnits=myControl.TimeOutUnitsType.Minute;//设置单位为分钟// ClientTimer1.TimeOutLength=总的可用时间; //设置答题总时间// ClientTimer1.PassTimeLength=0; //设置已用时间 // 正常进入考场// }} private void ClientTimer1_onTimeOut() { this.ClientTimer1.TimerEnabled=false; //不用计时了 //这里放 强制交卷 的代码}补充说明
使用中注意计时单位的选取,整体上要一致,建议使用分钟。
另外就是,我没仔细考虑整个考试系统的设计,只是选取了卷面计时这一点来分析,希望对将要设计考试系统的朋友有所帮助。
