集中上机实习报告指导教师:王利姓名:赵蕊学号: 2010211864班级: 0411003时间: 2012.5模拟时钟程序一.需求分析1.题目内容:编写一个模拟时钟程序,此程序在屏幕左方有一指针式钟面,右方有两个矩形框,上面以数字方式显示日期和时间,该时间应与指针显示的时间一致,下方的矩形框作为秒表。
用菜单选项或按钮设置时间和秒表。
时间不必与机器系统时间相同,只要可任意设置即可。
2.题目分析:(1)模拟时钟是一种集计时器和时钟显示于一体的程序。
编写一个指针式时钟程序,此程序在屏幕左方有一个指针式钟面,右方有两个矩形框,上面以数字方式显示日期、星期和时间。
指针式的时钟表盘为圆形,并且圆周上有分布均匀的60个刻度,刻度要求显示清楚,钟面上有长度不相同的指针,即时针、分针、秒针,指针的运动要求具有规律性,且为顺时针。
数字钟显示时间的格式是年月日星期时分秒,小时为24进制,分钟和秒是60进制,指针式的时钟和数字式的时钟显示的时间同步。
按下时钟控制设置时间菜单项可弹出一对话框,用于设置当前的时间、日期和星期;按下秒表控制按钮后,秒表显示窗中显示从0开始的时间,单位为百分之一秒。
再次按下秒表控制按钮后计时停止,该窗口显示累计时间。
(2)本实验主要涉及到的知识点有:时钟指针运动算法、屏幕重绘方法、定时器消息、鼠标消息、菜单命令、对话框、画笔/画刷、显示文字等。
指针运动算法和屏幕重绘方法是本程序主要难点所在。
3.最终效果如下图:二.概要设计1.程序系统模块划分2.程序算法分析本程序不论何种指针,每次转动均以π/30弧度(一秒的角度)为基本单位,且都以表盘中心为转动圆心。
计算指针端点(x, y)的公式如下:x =圆心x坐标+ 指针长度* cos (指针方向角)y =圆心y坐标+ 指针长度* sin (指针方向角)指针长度是指自圆心至指针一个端点的长度,由于指针要跨越圆心,因此一个指针需要计算两个端点。
三个指针的运动是相关联的,秒针转一圈引起分针运动一格,分针每转12格引起时针运动一格,而因此应该使用一个定时器消息来处理指针的运动。
若用三个定时器消息分别处理时针、分针和秒针的运动,就会使问题复杂化且不易实现三个指针联动的正确规律。
采用一个定时器消息可以很容易实现指针联动算法。
由于屏幕的重绘速度快,如果采用全屏删除式重绘则闪烁十分明显,显示效果不佳。
本程序采用非删除式重绘,假定指针将要移动一格,则先采用表盘背景色(这里为浅绿色)重绘原来指针以删除原来位置的指针,再采用指针的颜色在当前位置绘制指针;如果指针没有动,则直接绘制指针。
另外,秒表需要采用单独的定时器消息控制,秒表重绘速度很快,屏幕闪烁明显,故本程序采用了双缓冲绘图。
3.系统流程图置时间三.详细设计1.模块描述(1)指针式时钟指针式的时钟表盘为圆形,并且圆周上有分布均匀的12个数字刻度和48个较小的原点刻度,刻度显示清楚,用来表示小时、分钟和秒数。
钟面上有长度不相同的指针,即时针、分针、秒针,指针的运动具有规律性,为顺时针。
(2)数字式时钟数字钟显示时间的格式是时分秒,小时为24进制,分钟和秒是60进制,指针式的时钟和数字式的时钟显示的时间同步,且俩时钟可以同时设置。
(3)秒表按下秒表控制按钮后,秒表显示窗中显示从0开始的时间,单位为百分之一秒。
再次按下秒表控制按钮后计时停止,该窗口显示累计时间。
2.函数描述(1)CPoint p;double angle = nvalue *PI/30 ;p.x = m_Center.x + (int)(nlenth*sin(angle));p.y = m_Center.y - (int)(nlenth*cos(angle));return p;用来得到时钟刻度点和时针、分针、秒针的端点(2)h our = hour*5;hour = hour + minute/12;m_OldHour[0] = m_Hour[0];m_OldHour[1] = m_Hour[1];m_Hour[0] = GetPoint(int(m_Radious/2),hour);m_Hour[1] = GetPoint(7,hour + 30);m_OldMin[0] = m_Minute[0];m_OldMin[1] = m_Minute[1];m_Minute[0] = GetPoint(int(m_Radious*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_Radious*8/10),second);m_Second[1] = GetPoint(30, second + 30);用来得到时针、分针、秒针的当前位置和上一时刻位置并将其保存(3)CRect rc;CDC dcMem;GetClientRect(&rc);CBitmap bmp;dcMem.CreateCompatibleDC(pDC);bmp.CreateCompatibleBitmap(pDC,rc.Width(),rc.Height());CBitmap *pOldBit=dcMem.SelectObject(&bmp);dcMem.FillSolidRect(rc,RGB(255,255,255));………….//画图代码pDC->BitBlt(0,0,rc.Width(),rc.Height(),&dcMem,0,0,SRCCOPY);dcMem.DeleteDC();bmp.DeleteObject();利用双缓冲防止屏幕画面闪烁四.实现环境及工具Win7 环境下VC++ 6.0 MFC五.程序测试1.对时间设置测试按下确定后测试其设置为需要设置的时间,并正常运行。
2.对秒表测试点下启动后它正常运行;点下停止后它停止运行并记录当前累积时间。
再次按下启动按钮时它计时清零并重新开始计时。
3.整体3部分测试同时运行时三部分均正常运行,并且屏幕也不闪烁。
六.心得体会通过本次实验,对c++编程有了进一步的了解和能够更加熟练的编程,还学习了MFC的一些画图处理函数,以及一些运行机理,能够基本掌握用MFC画图,且能够实现一些基本的功能。
在本次设计的初期,只实现了小部分功能,对画图及其不了解,且对窗口设置的不合理倒是画面相当丑陋,后来通过请教同学、老师、上网查资料,最终设计出比较满意的画面。
还有,本次设计的秒表设计,由于它的刷新频率太高,导致屏幕闪烁严重,画面看起来很不舒畅,后面经查资料发现双缓冲技术能够解决这一问题,也将这个问题成功解决。
觉得本次设计中的采用不同的计时器来控制不同的模块效果比较好,对每一模块进行独立的控制很短单,而且思路清晰。
总之在本次实验中我学到了很多知识,而且培养了自己的自学能力,逼着自己进行查阅大量的资料。
在本次实验中自己对程序一直修改,一直增加功能和改善画面,大大的提高了自己的动手能力。
附:源代码及详细注释(由于使用的MFC向导形成工程只附部分核心代码)在中加public:int year;int month;int day;int hour;int second;int minute;int watch;int wday;CPenm_HouPen,m_NoHouPen,m_MinPen,m_NoMinPen,m_SecPen,m_NoSecPen,m _circle,m_RectPen;HBRUSHm_HurkBrush,m_CircleBrush,m_RecBrush,m_RectBrush,m_RectBrush1 ,m_RectBrush2;CPoint m_Center;double m_Radious;CPoint m_Hour[2],m_OldHour[2];CPoint m_Minute[2],m_OldMin[2];CPoint m_Second[2],m_OldSec[2];CRect m_WatchStart;CRect m_WatchStop;void SetClock(int hour,int minute,int second);CPoint GetPoint(int nlenth,int nvalue);#include "stdafx.h"#include "Clock.h"#define PI 3.14159265#include<cmath>#include"SetTimeDlg.h"#include "ClockDoc.h"#include "ClockView.h"#include<ctime>#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE[] = __FILE__;#endif中初始化设置变量///////////////////////////////////////////////////////// ////////////////////// CSetTimeDlg dialogCSetTimeDlg::CSetTimeDlg(CWnd* pParent /*=NULL*/): CDialog(CSetTimeDlg::IDD, pParent){//{{AFX_DATA_INIT(CSetTimeDlg)long now = time(NULL);tm local = *localtime(&now);m_Day = local.tm_mday;;m_Hour = local.tm_hour;m_Minute = local.tm_min;m_Month = local.tm_mon + 1;m_Second = local.tm_sec;m_Year = local.tm_year+1900;m_Wday = local.tm_wday;//}}AFX_DATA_INIT}void CSetTimeDlg::DoDataExchange(CDataExchange* pDX) {CDialog::DoDataExchange(pDX);//{{AFX_DATA_MAP(CSetTimeDlg)DDX_Text(pDX, ID_DAY, m_Day);DDV_MinMaxInt(pDX, m_Day, 1, 31);DDX_Text(pDX, ID_HOUR, m_Hour);DDV_MinMaxInt(pDX, m_Hour, 0, 23);DDX_Text(pDX, ID_MINUTE, m_Minute);DDV_MinMaxInt(pDX, m_Minute, 0, 59);DDX_Text(pDX, ID_MONTH, m_Month);DDV_MinMaxInt(pDX, m_Month, 1, 12);DDX_Text(pDX, ID_SECOND, m_Second);DDV_MinMaxInt(pDX, m_Second, 0, 59);DDX_Text(pDX, ID_YEAR, m_Year);DDV_MinMaxInt(pDX, m_Year, 0, 3000);DDX_Text(pDX, ID_WDAY, m_Wday);DDV_MinMaxInt(pDX, m_Wday, 0, 6);//}}AFX_DATA_MAP}///////////////////////////////////////////////////////// ////////////////////// CClockViewIMPLEMENT_DYNCREATE(CClockView, CView)BEGIN_MESSAGE_MAP(CClockView, CView)//{{AFX_MSG_MAP(CClockView)ON_COMMAND(ID_START, OnStart)ON_COMMAND(ID_STOP, OnStop)ON_COMMAND(ID_STETIME, OnStetime)ON_WM_TIMER()ON_WM_LBUTTONDOWN()//}}AFX_MSG_MAP// Standard printing commandsON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)ON_COMMAND(ID_FILE_PRINT_PREVIEW,CView::OnFilePrintPreview)END_MESSAGE_MAP()///////////////////////////////////////////////////////// ////////////////////// CClockView construction/destructionCClockView::CClockView(){ //初始化时间为系统时间long now = time(NULL);tm local = *localtime(&now);year = local.tm_year+1900;month = local.tm_mon + 1;day = local.tm_mday;minute = local.tm_min;hour = local.tm_hour;second = local.tm_sec;wday = local.tm_wday;//创建画笔、画刷m_HouPen.CreatePen(PS_SOLID,5 ,RGB(255,50,0));m_NoHouPen.CreatePen(PS_SOLID,5 ,RGB( 204,255, 102 )); m_MinPen.CreatePen(PS_SOLID,3 ,RGB(0,0,255));m_NoMinPen.CreatePen(PS_SOLID,3 ,RGB( 204,255, 102 )); m_SecPen.CreatePen(PS_SOLID,1 ,RGB(0,0,0));m_NoSecPen.CreatePen(PS_SOLID,1 ,RGB( 204,255, 102 )); m_circle.CreatePen(PS_SOLID,2 ,RGB(0,0,0));m_RectPen.CreatePen(PS_SOLID,3 ,RGB(204,102,0));m_HurkBrush = CreateSolidBrush( RGB( 255,0, 0 ));m_CircleBrush = CreateSolidBrush( RGB( 204,255, 102 )); m_RecBrush = CreateSolidBrush( RGB( 255,255, 51 ));m_RectBrush = CreateSolidBrush( RGB( 102,51, 0 ));m_RectBrush1 = CreateSolidBrush( RGB( 255,51, 0));m_RectBrush2 = CreateSolidBrush( RGB( 0,102, 204));m_Center.x = 200;m_Center.y = 200;m_Radious = 185;SetClock(hour,minute,second);watch = 0;m_WatchStart =CRect(480,310,560,340);m_WatchStop= CRect(590,310,670,340);// TODO: add construction code here}CClockView::~CClockView(){}void CClockView::SetClock(int hour,int minute,int second) {hour = hour*5;//时针每过1小时转5*PI/3hour = hour + minute/12; //时针每过12分钟转PI/3//保存时钟原位置m_OldHour[0] = m_Hour[0];m_OldHour[1] = m_Hour[1];//计算时针当前位置m_Hour[0] = GetPoint(int(m_Radious/2),hour);m_Hour[1] = GetPoint(7,hour + 30);//保存分针位置m_OldMin[0] = m_Minute[0];m_OldMin[1] = m_Minute[1];//计算分针位置m_Minute[0] = GetPoint(int(m_Radious*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_Radious*8/10),second); m_Second[1] = GetPoint(30, second + 30);}CPoint CClockView::GetPoint(int nlenth,int nvalue) { // 用来计算指针的俩端点的位置和表盘上刻度的位置CPoint p;double angle = nvalue *PI/30 ;p.x = m_Center.x + (int)(nlenth*sin(angle));p.y = m_Center.y - (int)(nlenth*cos(angle));return p;}BOOL CClockView::PreCreateWindow(CREATESTRUCT& cs){// TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT csreturn CView::PreCreateWindow(cs);}///////////////////////////////////////////////////////// ////////////////////// CClockView drawing//*********画表***********//void CClockView::OnDraw(CDC* pDC){SetTimer(1,1000,NULL);CClockDoc* pDoc = GetDocument();ASSERT_VALID(pDoc);// TODO: add draw code for native data hereCRect rc;CDC dcMem;GetClientRect(&rc);CBitmap bmp; //内存中承载临时图象的位图dcMem.CreateCompatibleDC(pDC); //依附窗口DC创建兼容内存DC//创建兼容位图(必须用pDC创建,否则画出的图形变成黑色)bmp.CreateCompatibleBitmap(pDC,rc.Width(),rc.Height());CBitmap *pOldBit=dcMem.SelectObject(&bmp); //按原来背景填充客户区,不然会是黑色dcMem.FillSolidRect(rc,RGB(255,255,255));//画图,添加你要画图的代码,不过用dcMem画,而不是pDc//画表盘最外面的大圆并填充颜色dcMem.SelectObject(m_circle);dcMem.Ellipse(m_Center.x - 195,m_Center.y-195,m_Center.x + 195,m_Center.y + 195);dcMem.SelectObject(m_CircleBrush);dcMem.Ellipse(m_Center.x - 195,m_Center.y -195,m_Center.x + 195,m_Center.y + 195);//画60个刻度for(int i = 0; i < 60 ; i++){CPoint pt = GetPoint(m_Radious,i);dcMem.SelectObject(m_SecPen);//画小时刻度if(i%5 ==0){CString m_Number;dcMem.SetBkMode(TRANSPARENT); //将textout背景透明m_Number.Format("%2d",(i/5==0 ? 12 : i/5));dcMem.TextOut(pt.x-7,pt.y-7,m_Number);}//画小时之间的刻度else{dcMem.SelectObject(m_HurkBrush);dcMem.Ellipse(pt.x - 2,pt.y -2,pt.x + 2,pt.y + 2);}}//画时针if(m_OldHour[0] != m_Hour[0]){ //用表盘背景色覆盖原来的时针dcMem.SelectObject(m_NoHouPen);dcMem. MoveTo(m_OldHour[0]);dcMem.LineTo(m_OldHour[1]);//时针绘制dcMem.SelectObject(m_HouPen);dcMem. MoveTo(m_Hour[0]);dcMem.LineTo(m_Hour[1]);}else{dcMem.SelectObject(m_HouPen); dcMem. MoveTo(m_Hour[0]); dcMem.LineTo(m_Hour[1]);}//画分针if(m_OldMin[0] != m_Minute[0]) { //用表盘背景覆盖原来的分针dcMem.SelectObject(m_NoMinPen); dcMem. MoveTo(m_OldMin[0]);dcMem.LineTo(m_OldMin[1]);//分针绘制dcMem.SelectObject(m_MinPen);dcMem. MoveTo(m_Minute[0]);dcMem.LineTo(m_Minute[1]);}else{ dcMem.SelectObject(m_MinPen); dcMem. MoveTo(m_Minute[0]);dcMem.LineTo(m_Minute[1]);}//画秒针if(m_OldSec[0] != m_Second[0]) {//用表盘背景覆盖原来的秒针dcMem.SelectObject(m_NoSecPen); dcMem. MoveTo(m_OldSec[0]); dcMem.LineTo(m_OldSec[1]);//秒针绘制dcMem.SelectObject(m_SecPen); dcMem. MoveTo(m_Second[0]); dcMem.LineTo(m_Second[1]);}else{ dcMem.SelectObject(m_SecPen); dcMem. MoveTo(m_Second[0]);dcMem.LineTo(m_Second[1]);}//数字时钟显示//画一个矩形框然后将其用颜色填充dcMem.SelectObject(m_RectPen);dcMem.Rectangle(450,30,700,180);dcMem.SelectObject(m_RectBrush);dcMem.Rectangle(450,30,700,180);//显示“当前时间”以及日期、星期、时间dcMem.TextOut(530,10,"当前时间");CString m_Date, m_Time,m_Wday;m_Date.Format("%4d年%2d月%2d日",year,month,day); dcMem.TextOut(510,70,m_Date);switch(wday){ case 0:m_Wday.Format("星期日");dcMem.TextOut(540,88,m_Wday);break;case 1:m_Wday.Format("星期一");dcMem.TextOut(540,88,m_Wday); break;case 2:m_Wday.Format("星期二");dcMem.TextOut(540,88,m_Wday); break;case 3:m_Wday.Format("星期三");dcMem.TextOut(540,88,m_Wday); break;case 4:m_Wday.Format("星期四");dcMem.TextOut(540,88,m_Wday); break;case 5:m_Wday.Format("星期五");dcMem.TextOut(540,88,m_Wday); break;}m_Time.Format("%4d时%2d分%2d秒",hour,minute,second);dcMem.TextOut(510,118,m_Time);//秒表显示//画一个矩形框然后将其用颜色填充dcMem.SelectObject(m_RecBrush);dcMem.Rectangle(450,220,700,370);//输出秒表、计时dcMem.TextOut(545,200,"秒表");int minSec = watch%100;int Sec = (watch/100)%60;int Min = (watch/100)/60;m_Time.Format("%02d:%02d:%02d",Min,Sec,minSec);dcMem.TextOut(535,260,m_Time);//画俩个不同颜色的矩形框dcMem.SelectObject(m_RectBrush1);dcMem.Rectangle(&m_WatchStart);dcMem.SelectObject(m_RectBrush2);dcMem.Rectangle(&m_WatchStop);//在俩矩形框内写"启动"停止"dcMem.TextOut(m_WatchStart.left + 18,m_WatchStart.top + 5,"启动");dcMem.TextOut(m_WatchStop.left + 18,m_WatchStop.top + 5,"停止");pDC->BitBlt(0,0,rc.Width(),rc.Height(),&dcMem,0,0,SRCCO PY); //将内存DC上的图象拷贝到前台//绘图完成后的清理dcMem.DeleteDC(); //删除DCbmp.DeleteObject(); //删除位图}///////////////////////////////////////////////////////// ////////////////////// CClockView printingBOOL CClockView::OnPreparePrinting(CPrintInfo* pInfo){// default preparationreturn DoPreparePrinting(pInfo);}void CClockView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/){// TODO: add extra initialization before printing}void CClockView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/){// TODO: add cleanup after printing}///////////////////////////////////////////////////////// ////////////////////// CClockView diagnostics#ifdef _DEBUGvoid CClockView::AssertValid() const{CView::AssertValid();}void CClockView::Dump(CDumpContext& dc) const{CView::Dump(dc);}CClockDoc* CClockView::GetDocument() // non-debug version is inline{ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CClockDoc))) ;return (CClockDoc*)m_pDocument;}#endif //_DEBUG///////////////////////////////////////////////////////// ////////////////////// CClockView message handlers//菜单系统时间void CClockView::OnStart(){long now = time(NULL);tm local = *localtime(&now);year = local.tm_year+1900;month = local.tm_mon + 1;day = local.tm_mday;minute = local.tm_min;hour = local.tm_hour;second = local.tm_sec;wday = local.tm_wday;SetTimer(1,1000,NULL);}//菜单时间停止void CClockView::OnStop(){// TODO: Add your command handler code here KillTimer(1);}//菜单时间设置void CClockView::OnStetime(){CSetTimeDlg SetDlg;if(SetDlg.DoModal()==IDOK){ day = SetDlg.m_Day ;hour = SetDlg.m_Hour ;minute = SetDlg.m_Minute;month = SetDlg.m_Month;second = SetDlg.m_Second ;year = SetDlg.m_Year ;wday = SetDlg.m_Wday;}SetClock(hour,minute,second);Invalidate(TRUE);}void CClockView::OnTimer(UINT nIDEvent){// TODO: Add your message handler code here and/or call default//时钟定时器消息处理if(nIDEvent == 1||nIDEvent == 3){second++;if(second > 59) {second = 0;minute++; }if(minute > 59) {minute = 0; hour++;}if(hour > 23) {hour = 0;day++;wday++;}if(wday > 6) {wday = 0;}switch(month) {case 1:case 3:case 5:case 7:case 8:case 10:case 12:if(day > 31){day = 1;month++;}break;case 4:case 6:case 9:case 11:if(day > 30){day = 1;month++;}break;case 2:if(year%4 == 0&&day > 29){day = 1;month++;}if(year%4 != 0&&day > 28) {day = 1;month++;}break;}if(month > 12){ month = 1;year++;}SetClock(hour,minute,second); Invalidate(FALSE);}//秒表定时器消息处理if(nIDEvent== 2){watch++;Invalidate(FALSE);CView::OnTimer(nIDEvent);}}// 按钮时间定时器void CClockView::OnLButtonDown(UINT nFlags, CPoint point) {// TODO: Add your message handler code here and/or call defaultif(m_WatchStart.PtInRect(point)){watch = 0;SetTimer(2,10,NULL);SetTimer(3,1000,NULL);}if(m_WatchStop.PtInRect(point)){KillTimer(2);KillTimer(3);}CView::OnLButtonDown(nFlags, point); }。