模拟时钟程序.docx
《模拟时钟程序.docx》由会员分享,可在线阅读,更多相关《模拟时钟程序.docx(26页珍藏版)》请在冰点文库上搜索。
模拟时钟程序
1.基本功能描述1
2.设计思路1
3.软件设计3
3.1设计步骤3
3.2界面设计4
3.3关键代码的实现8
3.3.1构造函数功能实现原理8
3.3.2确定指针位置实现原理8
3.3.3OnDraw()函数实现原理9
3.3.4OnTimer()函数的实现原理9
3.3.5其它功能的实现原理9
4.结论与心得体会10
5.参考文献10
6.思考题11
7.附录11
7.1调试报告11
7.2测试结果13
7.3关键源代码13
模拟时钟程序
1.基本功能描述
模拟时钟程序为指针式时钟,在时钟的上方用数字形式显示日期,时钟下方用数字形式显示数字时钟。
数字时钟与指针时钟显示一致,并且与系统时间保持一致。
另外,在菜单栏上添加控制按钮,分别实现时钟的暂停、启动、时间调整、恢复系统时间等功能。
同时,添加工具栏,以便快捷实现上述操作功能。
当暂停按钮按下时,指针停止走动,数字时钟停止显示;当启动按钮按下时,指针重新开始走动,数字时钟开始显示;当恢复系统时间按钮被按下时,指针时钟和系统时间重新保持一致;当时间调整按钮被按下时,弹出对话框,对时间进行设置,按下确定按钮后,指针时间显示与设置时间一致并开始转动。
2.设计思路
程序开始启动后,计时器启动,每次间断一秒自动调用OnTimer()函数,OnTimer()函
数包含了各个时钟的计时规则,即秒、分、时、日、月、年等计时规则。
此后进行刷新重绘操作,即调用OnDraw()函数,对指针和数字时钟进行重绘。
程序产生消息循环,当收到暂停消息时,关闭计时器,停止计时;当收到启动消息时,启动计时器,重新开始计时;当接收到恢复系统时间消息时,调用系统时间,并初始化各时间变量开始计时;当接收到调整时间消息时,构建对象,并获取对象中各时间变量的值,并开始计时。
在指着重绘时,需要知道其位置,通过调用SetClock()函数实现。
SetClock()函数形参为时、分、秒时间变量,通过数学关系确定其在屏幕中的位置,以方便绘图。
程序总体流程图如图1所示:
图2Ondraw()函数流程图
3.软件设计
3.1设计步骤
鉴于要求实现的功能,我们采用MFC的单文档编程来实现上述功能。
首先,新建一
个基于单文档的工程,集体操作步骤如以下所示:
图3建立新工程图
点击确定,弹出如下对话框:
图4新建单文档程序图
如不需要作其它改变,这里我们可以直接点击完成,一个新的基于单文档的应用程序框架便已经给出。
然后,分别链接、运行程序,得到如下结果:
图5程序运行图
3.2界面设计
首先,设计钟面标记,这里,我采用整点用较大的正方形表示,非整点用小圆圈表示,
用蓝色较细长指针表示秒针、绿色稍细长指针表示分针、红色粗短指针表示时针,设计效
果如下图所示:
□”•口
日期和数字钟分别位于钟面的上下方,在Ondraw()函数中通过调用
TextOut()函数实现。
同时,给菜单栏添加控制选项,并设置相应按钮,如下图所示:
匚动止吏皋制月宿调恢
Weslie,rc-tdr_matmframe(Menu)
图7添加控制菜单
给个按钮添加ID号和消息响应,我们以启动按钮为例进行说明,双击启动按钮,进
行相应设置,如下图所示:
图8启动按钮设置图
停止、调整、恢复按钮也作同样设置,分别添加相应ID号,即分别为他们添加的ID
号为ID_STOP、ID_SetTime、ID_Reset。
在空白处点击鼠标左键,建立类向导,给启动按钮添加消息处理函数,如下图所示:
iMlEimb&rVfirlfltilcs
AutonnaMon
nanic-
emofccl;
|Jien|iu>np■n_kiekihe■n_h.Efi-heV1>cw
U;"□恻h陌Ihpalipuli—ha■heVicthvtiFahiiniri^lkjEvhcVltWiE(uip
■牌凸ID屛
mi-Tirvfajmi>DRsisflllir^cTliinc
itj■slolp
—
UPOATECoMMAHD
_UI
Ai^IyeXLveniaLluiiSInlfl
IFmnctlan
IJrlrlrI
点击addFunktion按钮添加消息处理函数,至此,启动按钮的相关设置便已经完成。
参照启动按钮设置,我们为其它各个按钮分别添加消息处理函数。
即最后为启动、停止、设置、恢复等各个按钮分别添加消息处理函数OnSTART()、OnSTOP()、OnSetTime()、
OnReset()函数。
为了在点击调整按钮之后弹出一个新的对话框,我们还必须建立一个对话框类,在资源视图中添加对话框资源,如下图所示:
图10新建对话框图
点击新建之后便弹出一个可绘制的编辑窗口,我们选择相应控件,最终效果如下图所
示:
图11对话框效果图
建立好对话框之后,我们需要利用类向导添加一个新的对话框类,即鼠标右键点击对
话框空白区域,选择建立类向导,弹出窗口,我们将对话框命名为CSetTimeDIg,给各个
控件添加相应的ID号,完成后我们需要给各控件添加变量,即分别为时间输入控件添加相应的时间变量,以便于获取输入的时间,并做相应调整,添加结果如下表所示:
表1变量及控件对应表
控件ID
变量类型
变量名称
IDYEAR
int
mYear
IDMONTH
int
mMonth
IDDAY
int
mDay
IDHOUR
int
mHour
IDMINUTE
int
mMinute
IDSECOND
int
mSecond
为了方便控制,我们给程序添加工具栏。
首先将程序默认的工具栏选项删除,然后逐
个添加我们需要的工具栏按钮,界面效果如下图所示:
Hcherlpdiipail_lc«hejtlDR_MAI^jrRAMr(nitmap)
图12工具栏效果图
为工具栏分别添加对应ID值,以实现对时钟的控制。
其中S对应停止按钮、R对应恢复按钮、A对应调整按钮、Q对应启动按钮等的ID号。
至此,程序整体便已设计完成。
对程序中需要的使用的各变量分别进行定义,这里,我们把它们放到视图类的公有成员中,如下图所示:
pubXic;
intyear,mantha.haur?
viinuteucinij,uj«atchRinit訂/^加叶=|蜜乍
CPen^llouren.n_MlitFE?
nBm_SecPen:
//占和牛的直琏CBtu^huNjrkfimth;//衷盘祢ul两旦用]
CPcintnconttr:
ff表的屮止,
doaUpn_R*dius;//看脚丰桎
CPfllncn_HMir[2],n_oidnourr^l;"琳t当煎眞前一宓仃星叭intnMinute[?
].mOlriWin[?
];//;冷当刁及闿-少巳直CPointn_S»cond『斫”in_01JSecf±l;//也:
卡亠勻甘阴.曲一反夕筐
CHectrt;
MuidSeLtlock(intbuur,Lntfiltiuit*}inthetond);
engineurtrolnttintntentn,IncnVdiu?
):
图13添加变量图
添加好各个变量之后,我们开始为程序添加消息处理函数和Ondraw()实现函数的具体
代码。
在使用工具栏时应注意,实现工具栏按钮的删除操作时应直接把不需要的按钮脱出
工具栏即可,用delete键是无法删除的,也无法对其进行编辑。
3.3关键代码的实现
3.3.1构造函数功能实现原理
在这个程序中,我们需要用到系统时间,因此首先定义一个CTime对象,然后调用
GetCurrentTime()函数得到系统时间,并通过相应函数获得各个时间分量的值传给相应时间变量。
为了在绘图函数中得到我们想要的效果,我们还可以定义相应的画笔和画刷,这需要调用CreatePen(nPenStylenWideth,rgbCOLOR)函数,其形参分别为画线类型画线宽度和画线颜色,颜色我们通过调用RGB(mRed,mGreen,mBlue)函数获得。
3.3.2确定指针位置实现原理
在程序中,我们要解决的两一个问题便是:
如何确定各个指针的位置?
通过分析我们不难发现,各个指针总是围绕一个相同的圆心运动,且有如下规律:
不论何种指针,每次转动均以n/30弧度(一秒的角度)为基本单位,且都以表盘中心为转动圆心。
计算指针端点(x,y)的公式如下:
x=圆心x坐标+指针长度*cos(指针方向角)
y=圆心y坐标+指针长度*sin(指针方向角)
而指针方向角如何确定?
通过分析我们发现,钟面上共有60个刻度,而每个刻度对
应的方向角都是唯一的,即angle=(刻度值)*PI/30-PI/2。
将所得到的刻度值代入上述公式,我们便可以确定某个指针的具体位置。
在这个程序中,我们新建了一个SetClock(hour,minute,second函数,通过调用该函数获得指针端点的位置,有一个需要注意的问题便是,
钟面上只有12个时钟刻度,因而在确定其位置时,我们需要用当前的hour值乘5并加上
minute除以12后的值,这样得到的才是真正的指针对应的刻度值。
为了方便调用,我们还新建一个GetPoint(nLength,Nvalue)函数,其实现
具体位置的确定,nLength变量表示指针长度,Nvalue变量表示刻度值,并用公式:
x=圆心x坐标+指针长度*cos(指针方向角)
y=圆心y坐标+指针长度*sin(指针方向角)
其中指针方向角angle=(刻度值)*PI/30-PI/2,由此,便可以确定指针的具体位置了。
3.3.3OnDraw()函数实现原理
程序要求中,钟面必须位于客户区的中央,因而,我们需要调用相应函数获得用户区的大小。
定义一个CRect对象,调用GetClientRect(&rt)函数,便可得到用户区的相关信息。
由于钟面共有60个指针刻度,我们可以采用一个循环来画出这些标记,画标记之前判断该刻度值是否能被5整除,能整除则表示是整点值,此时画较大的正方形否则画较小的圆圈。
由于一个指针有两个端点,所以我们用一个数组来保存着两个端点的位置信息,为了避免刷新整个用户区而造成屏幕闪烁,我们采用直接覆盖原位置指针的方法。
首先调用SelectObject(R2_WHITE)函数,并通过调用MoveTo(x,y)函数得到一个指针端点的位置,然后调用Lineto(x,y)函数,便可用白色覆盖原位置的指针,然后调用SelectObject(R2_COPY)
函数便可恢复选用的画笔,然后MoveTo()、LineTo()函数便可画出新的指针位置了。
程序不断接收到刷新请求时,便执行Ondrow()函数。
数字钟部分,通过调用TextOut()函数来进行输出,调用GetCurrentTime()函数获取系统时间并将其显示。
3.3.4OnTimer()函数的实现原理
通过类向导建立一个视图类的WM_TIMER消息响应函数OnTimer(),这样启动计时器后,每隔一定时间(由自己设置),由程序自动调用OnTimer()函数,该函数计时规则的设定,即分、秒、时、日、月的进制设定,进行相关运算后,确定各指针位置,并保存在预先定义的数组中,调用Invanidate()函数进行刷新,调用Ondraw()函数,进行重绘,便实现了指针的转动和数字时钟的走动。
3.3.5其它功能的实现原理
在调整按钮的消息处理函数中,调用DoModle()函数创建新的对话框,并判断返回值
是否为IDOK,如果是,就将各时间变量按照设定的值初始化,并刷新;在实现停止功能时,只需要调用KillTimer
(1)函数即可;在实现重新启动时,调用SetTimer()函数;在实现
重新恢复系统时间时,调用GetCurrrentTime()函数获得系统时间,并对时间变量进行初始化,至此,程序的基本功能便已经完全实现,基本符合任务书要求。
4.结论与心得体会
在此次设计中,综合考察了我们学习并运用知识的能力。
由于这部分内容在考试中基本无从实现,老是把这个内容放到课程设计中来考察,也是对大家掌握知识能力的一个检测。
MFC从来都不是从零开始的,而我们需要掌握的是学会运用系统所提供的各种类和框架来实现想要的程序。
在模拟时钟程序中,我们需要熟练掌握用户区的重绘、刷新以及系统定时器的用法,这给了我们一个综合运用所学知识的机会与平台。
当然在调试过程中也出现过许多问题,但是通过自己的努力,慢慢的将一些问题解决,从中也着实学到不少东西。
在此次设计中,参考了一些相关的资料,这给了我们很大帮助。
通过此次课程设计,我们也更加了解MFC程序的基本框架和流程图,对加深我们对MFC的理解颇有帮助。
相较于我们这个专业,C语言是一门重要的课程,因为它与我们今后的就业息息相关。
通过一些具体程序的设计,也大大激发了我们的学习热情,在不知不觉中,我们不仅有了学到了知识,还收获了一份设计完成后的自豪。
由于此次课程设计基本靠我们个人独立完成,因而,肯定会有一些不完善的地方,设计思路也会有较大不同。
参考别人的程序,获得一个不同的思路,对我们的学习大有裨益。
当程序出现问题时,冷静按照流程分析能帮助我们较快解决问题,同时,熟练运用调试功能也能帮我们较快解决问题。
在此次设计中,便遇到过多次语法错误不存在,但是运行报错的情况,通过调试我发现,问题便是由于某些变量没有正确初始化,及变量的值是随机数,由于过大或过小而导致程序不能正确运行。
在调试过程中,我们能够清楚看到各个变量的值,并分析可能出现的问题,因而,熟练运用这个功能是我们在检查程序错误时应当学会的,这能帮我们快速解决问题。
总的来说,这次实验设计令我们受益匪浅,不论学到的知识还是处理问题的方法对我们今后的学习都有很大帮助。
当然,此次实验设计也基本取得成功。
5.参考文献
[1]揣锦华•面向对象程序设计与VC++实践[M].西安:
西安电子科技大学出版社,2005.
[2]马德骏,张建宏,汤练兵.C语言程序设计[M].北京:
科学出版社,2009.
[3]严蔚敏,吴伟民.数据结构(C语言版)[M].北京:
清华大学出版社,2007.
6.思考题
1.程序中如何实现时钟时间与计算机系统实践保持一致?
答:
在初始化时间变量时,调用GetCurrentTime()函数获取系统时间,并用其对时间变量进行初始化,而SetTimer()函数定时为1000ms,即每隔一秒刷新一次,因而指针时间能与系统时间保持一致,数字钟本身显示的就是系统时间,直接通过调用GetCurrentTime()函数获得,因而始终与系统时间一致。
2.SetTimer()函数和KillTimer()函数的调用位置与程序运行的关系?
答:
在程序开始运行时即应当调用SetTimer()函数,计时器开始计时,而钟表开始走动,当停止按钮被按下时,调用KillTimer()函数,计时器停止计时,钟表停止走动;当启动按钮被按下时,调用SetTimer()函数,计时器开始计时,钟表开始走动;当程序窗口撤销时,应调用KillTimer()函数,关闭计时器。
3.说明SetTimer()函数、WM_TIMER消息和OnTimer()函数的调用关系?
答:
由于OnTimer()函数定义的是视图类的WM_TIMER消息处理函数,因而当
SetTimer()函数每隔一秒就会发出WM_TIMER消息,此时OnTimer()函数。
4.实现数字钟时,涉及到的编程有哪些?
答:
在实现数字钟时,先调用GetCurrentTime()函数,获得系统时间,而后调用SetBkColor()函数确定背景颜色,调用SetTextColor()函数确定字体颜色,调用Format()函数确定输出格式,在调用TextOut()函数进行输出。
7.附录
7.1调试报告
当基本工作完成后,编译,程序报错,查看错误记录,其提示为:
“errorC2065:
'R2_WHRIT':
undeclaredidentifier”、“errorC2065:
'CSettimeDIg':
undeclaredidentifie”,通过观察,发现第一个错误是由于拼写错误导致的,将其改为“R2_WHITE”后,错误消失,
而第二个错误是由于没有对新建的对话框类进行申明,在视图类的头文件里加上
#include”CSettimeDlg.h”后问题得到解决。
再次编译、链接程序,没有错误,但程序无法运行,结果如下图所示:
MicrosoftVisualCH■十DeIjliqLibr-ary
DebugAssertionFailed?
ProgramiD门資羁l面向对^.\chenpanpan_keshe\Debug\chenpanpan_ksshe.exe
File:
afxv\Jin2.ini
Line^S
Forinfoimationonhowyourprogram匚白ncausearassertionFatlurcjseetheVisualC-K+docurrertaiEicnonasserts.
(PressRetrytodebugtheapplication)
终止何怒略⑴
图14运行结果图
点击忽略后,程序结果如下图所示:
就绪I—甌宇7
图15错误结果图
初步分析,可能跟圆心位置不正确有关,在视图类的构造函数前插入断点,进行调试,发现当圆心坐标的初始化无效,即为一个很大的随机数,从而确定问题的根源。
通过进一步分析,得出的结论是调用构造函数时,用户区窗口还没有产生,因而圆心坐标的确定应当放到Ondraw()函数中进行确定,改进后问题得到解决。
进而观察程序运行结果,发现数字时钟在由0变为1的时候会出现如下结果:
2O:
43:
31)
图16数字钟错误图
通过分析,由于在调用SetTimer()函数时,刷新采用的方式为Invalidate(false),此时会重新调用Ondraw()函数但是不会对用户区进行刷新,因而造成以上结果,将false改为true,问题得到解决,但是时钟开始闪烁,数字钟也会闪烁,这与我的设计初衷是相违背的,因而这种方法显然不是想要的结果。
此时,想到另一种方法,就是通过覆盖的方法,即在每次输出数字钟之前通过调用TextOut()函数输出一串空格符,用以覆盖之前的数字显示,采用该方法后,问题得到圆满解决,至此程序能够正确运行。
7.2测试结果
经过调试,程序能够正确运行,在操作界面中,工具栏上的S按钮对应停止功能,即时钟暂停走动;R按钮对应恢复按钮,即使时钟时间与系统时间保持一致;A按钮对应调整按钮,即A被按下后,弹出对话框,对时间进行调整;Q按钮对应启动功能,即当停止按钮按下后,按下Q按钮,时钟重新开始走动。
整体运行结果如下图所示:
图17整体效果图
7.3关键源代码
CChenpanpan_kesheView:
:
CChenpanpan_kesheView()
{
//初始化时间
CTimetime;
time=CTime:
:
GetCurrentTime();
year=time.GetYear();
month=time.GetMonth();
day=time.GetDay();
hour=time.GetHour();
minute=time.GetMinute();
second=time.GetSecond();
//选取画笔颜色及宽度
m_HouPen.CreatePen(PS_SOLID,5,RGB(255,0,0));时针宽度及颜色m_MinPen.CreatePen(PS_SOLID,3,RGB(0,255,0));份针宽度及颜色
m_SecPen.CreatePen(PS_SOLID,1,RGB(0,0,255))秒针宽度及颜色init=1;watch=1;
//设置时钟半径
m_Radius=200;
//计算指针位置
}
voidCChenpanpan_kesheView:
:
SetClock(inthour,intminute,intsecond)
{
hour=hour*5;
hour=hour+minute/12;
〃保存时针位置
m_OldHour[0]=m_Hour[0];
m_OldHour[1]=m_Hour[1];
//计算时针当前位置
m_Hour[0]=GetPoint(int(m_Radius/2),hour);
m_Hour[1]=GetPoint(7,hou叶30);
〃保存分针位置
m_OldMin[0]=m_Minute[0];
m_OldMin[1]=m_Minute[1];
//计算分针当前位置
m_Minute[0]=GetPoint(int(m_Radius*7/10),minute);
m_Minute[1]=GetPoint(10,minute+30);
〃保存秒针位置
m_OldSec[0]=m_Second[0];
m_OldSec[1]=m_Second[1];
//计算秒针当前位置
m_Second[0]=GetPoint(int(m_Radius*8/10),second);
m_Second[1]=GetPoint(30,second+30);
}
CPointCChenpanpan_kesheView:
:
GetPoint(intnlength,intnvalue)
{
CPointp;
doubleangle=nvalue*PI/30-PI/2;〃计算角度
p.x=m_Center.x+(int)(nlength*cos(angle));//确定X位置
p.y=m_Center.y+(int)(nlength*sin(angle));//确定Y位置
returnp;
}
voidCChenpanpan_kesheView:
:
OnDraw(CDC*pDC)
{
CChenpanpan_kesheDoc*pDoc=GetDocument();
ASSERT_VALID(pDoc);
//TODO:
adddrawcodefornativedatahere
GetClientRect(&rt);〃获得用户区大小
m_Center.x=rt.right/2;m_Center.y=rt.bottom/2;/圆心选择用户区中央
if(init==1)
{
SetClock(hour,minute,second);
init=0;
}
//定义字体大小
LOGFONTlo