MFC桌面电子时钟的设计与实现
目录
- 核心技术
- 需求分析
- 程序设计
- 程序展示
(一)核心技术
mfc(micosoft foundation class libay,微基础类库)是微基于windows平台下的c++类库集合,mfc包含了所有与系统相关的类,其中封装了大多数的api(application pogam inteface)方法,提供了应用程序设计的框架以及开发应用程序所需要使用的工具,应用程序向导、类向导、可视化资源设计等高效工具,使用消息映射处理进行对消息的响应,极大的简化了windows中应用程序的开发工作,使程序员可以从繁重的编辑工作中抽身出来,提高了程序员的工作效率。
1、mfc与windows编程
windows操作系统采用了图形用户界面,借助它提供的api方法,用户可以编出具有图形用户界面的程序。windows操作系统下的应用程序和控制台方式(ms-dos)下的应用程序相比,具有下特点:
(1)用户界面统一、友好。windows应用程序拥有相似的基本外观,包括窗口、菜单栏、工具、状态栏、滚动条等标准元素。
(2)独立于设备的图形操作。windows下的应用程序使用图形设备接口(gaphic d evice inteface),该接口屏蔽了不同的设备之间的差异,提供了与设备无关的图形输出能力。
(3)支持多事务处理机制。windows是一个多事务的操作环境,允许用户在同一时间运行多个独立的应用程序。
(4)事件驱动程序的程序设计。windows程序不是由事件的顺序来控制,而是由事件的发生与否来控制程序执行逻辑。而事件与消息之间的关系是关联的,windows应用程序的消息来源主要有以下4种:
输入消息:包括键盘和鼠标的输入。
控制消息:用来与windows的控制对象,如列表框、按钮、复选框等进行双向通信。
系统消息:对程序化的事件或系统时钟中断做出反应。
用户消息:这是程序员自己定义并在应用程序中主动发出的。
在vc++中编写windows应用程序有以下两种方法。
(1)直接使用了windows操作系统中所提供的windows api方法来对windows应用程序进行编写。通过windows api创建的windows应用程序包含了两个基本部分:应用程序主方法winmain和窗口方法。winmain方法是应用程序的入口点,相当于c++控制台应用程序的主方法main。与main方法一样,winmain方法名也是固定的。窗口方法的名字是用户自定义的,由系统调用,主要功能是用来处理窗口的消息,以此来完成某些特定的任务。使用windows api编写windows应用程序时,大量的繁杂琐碎的程序代码必须由程序员自己亲自动手编写,工作量十分大。
(2)使用mfc类库编写windows应用程序。mfc提供了大量预先编写好的类及支持代 码,用于处理多项标准的windows编程任务,如窗口的创建、消息的处理、工具栏的添加和对话框等。因此,使用mfc类库可以简化windows应用程序的开发工作量。
2.mfc应用程序框架
mfc封装了大部分windows api方法、数据结构和宏,以面向对象的类提供给程序员,并提供了一个应用程序框架,简化和标准化了windows程序设计。
mfc中的各种类加起来有几百个,其中只有5个核心类对应用程序框架有影响:cwinapp、cdocument、cview、cframewnd和 cdoctemplate。这5个类之中只有cwinapp是必不可少的类,cwinapp的对象在应用程序中必须有一个,也只有一个,并且是一个全局对象。全局对象是在windows操作系统调用winmain之前建立的,它开通了程序执行的路径。在mfc编程中,入口方法winmain被封装在mfc的应用程序框架内,称为afxwinmain,不需要也不可以再定义另一个winmain方法。
应用程序框架(application framework)是一组类构造起来的大模型。它的出现使得开发人员不需要构建程序框架结构,其初始代码可以由应用程序向导自动完成。
3.mfc应用程序向导
mfc应用程序向导(mfc appwizard)可以帮助程序员创建一个mfc应用程序框架,并且自动生成这个mfc应用程序框架所需要的全部文件。然后,程序员利用资源管理器和类向导(classwizard),为应用程序添加实现特定功能的代码,以实现应用程序所要求的功能。
在visual c++中,程序员可以创建以下3种典型的windows应用程序,它们都是通过mfc应用程序向导(appwizard)进行创建的:
(1)基于对话框的应用程序:该类应用程序比较适合于文档文献比较少而交互操作需求量比较多的应用场合,比如windows自带的计算器程序。
(2)单文档界面(sdi)应用程序:该类程序一次只能打开一个文档,如windows自带的notepad程序。
(3)多文档界面(mdi)应用程序:该类程序可以同时打开多个文档并对文档进行处理,处理的过程中很容易进行切换,如microsoft word。
下面通过构建应用程序myprogl,简单介绍如何使用mfc应用程序向导。
启动microsoft visual c++ 6.0应用软件,进入到visual c++的集成的开发环境(ide)中,并进行如下操作:
(1)选择“文件(file)”→“新建(new)”菜单,弹出“新建”对话框。
(2)在“新建”对话框中,切换到“工程(projects)”选项卡,从左边窗口所示的“项目类型”列表框中选择需要进行创建的项目类型,这里需要选择的类型是“mfc appwizard (exe)”。在“工程名称(projects name)”文本框中键入应用程序名,这里为myprogl。在“位置(location)”文本框中键入用于存放应用程序的目录,这里为f:\myproj\myprog 1。从“平台(platforms)”列表框中选择可用的目标平台,默认平台为win32。最后单击“确定”按钮,弹出mfc app wizard-step1对话框。
(3) mfc应用程序向导-步骤一(appwizard-step1of6)对话框的作用在于它是用户确定应用程序的结构:是单文档还是多文档亦或者是基本对话框,并为对应的资源选择一种需要使用的语言。
这里所选择的结构是单文档(single document),其余步骤(步骤二至步骤六)取默认设置,单击“完成”按钮,显示应用程序所具有的特征。单击“确定”按钮,确认之前所做的所有选择没有出现错误,则mfc appwizard将会根据这些选择自行生成应用程序的相关源文件。
(4) 完成上述步骤后,应用程序的框架即被生成,并在开发环境(developer studio)的程序工作区窗口中打开生成的程序。其中class view面板显示的是所创建的类和成员方法;resource view面板显示的是所创建的资源;fileview面板显示的是所创建的初始文件。
(5)选择“组建(build)”→“组建<myprog1.exe>”命令,编译、连接、建立运行程序。
(6)选择“组建(build)”→“执行<myprog1.exe>”命令,运行应用程序。
从运行结果可以看出,尽管还未写入一句代码,但myprogl程序已经是一个完整的可执行程序了,其整个的运行结果包括了标题栏、工具栏、菜单栏和一个打开的文档边框窗口。
生成应用程序框架后,这仅仅是一个程序的最基本的骨架,往往还需往项目中添加大量的代码,包括类、资源、消息处理方法等。
4. mfc与消息映射
windows应用程序都是消息(message)驱动的,消息处理是windows应用程序的核心部分。消息是用来请求对象执行某一处理、某一行为的信息。某对象在执行相应的处理时,如果需要,它可以通过传递消息请求其他对象完成某些处理工作或回答某些信息。其他对象在执行所要求的处理活动时,同样可以通过传递消息与别的对象联系,即windows应用程序的运行是靠对象间传递消息来完成的。消息实现了对象与外界,对象与其他对象之间的联系。
消息主要有3种类型:标准windows消息、控件通知消息和命令消息。
(1)标准windows消息。
除了一个特定的wm-command外,所有其他的以“wm-”为前缀的消息都是标准的windows消息。标准windows消息是由窗口和视图进行处理,该类消息通常会含有用于确定如何对消息迸行处理的一些参数。标准windows消息都有缺省的处理方法,这些方法在cwnd类中进行了预定义。mfc类库以消息名为基础形成这些处理方法的名称,这些处理方法的名称都有前缀“on”。
(2)控件通知消息。
控件通知消息包含从控件和其他子窗口传送给父窗口的wm-command的通知消息。与其他标准的windows消息相同,控件通知消息由窗口和视图进行处理,但当用户单击按钮控件时,发生的bn-clicked控件通知消息将会被系统作为命令消息来处理。
(3)命令消息。
命令消息包含来自用户界面对象(如菜单项、工具栏按钮和加速键等)的wm-command 通知消息。命令消息的处理与其他消息的处理不同,命令消息可以被更广泛的对象(如文档、文档模板、应用程序对象、窗口和视图)处理。如果某条命令直接影响某个特定的对象,则应该让该对象来处理这条命令。
windows程序这种“接收消息-处理消息-再等消息”的往复过程即称为“消息循环”。 消息循环是windows应用程序与ms-dos应用程序的最大差异点。
在windows平台,程序员不能决定程序执行的流程,而只能决定接收到消息时的程序的动作(编写完成局部的代码)。在visual c++中编程不是考虑要让程序按照什么样的顺序执行,而应考虑在某一消息下程序应该干什么。
(二)需求分析
查阅相关资料得知本题涉及到的主要知识点有:
时钟指针运动算法、屏幕重绘方法、定时器消息、鼠标消息、菜单命令、对话框、画笔/画刷、显示文字等。指针运动算法和屏幕重绘方法是本程序主要难点所在。
时针分针秒针,每次转动均以弧度(一秒的角度)为基本单位,且都以表盘中心为转动圆心。计算指针端点的公式如下:
:表示圆心坐标
:表示指针长度
:表示指针方向角
注意,指针长度是指自圆心至指针一个端点的长度(是整个指针的一部分),为了方便显示每一个指针都需要计算两个端点。
三个指针的运动是相关联的,秒针转一圈引起分针运动一格,分针转一圈引起时针运动一格,因此只需要使用一个定时器消息来处理指针的运动即可。
由于屏幕的重绘速度很快(50微秒一次),如果采用全屏删除式重绘则闪烁十分明显,显示效果不佳。在参阅多方资料之后,作者本人程序采用网上常用的方法——非删除式重绘,即假定指针将要移动一格,则先采用背景色(这里是白色)重绘原来指针以删除原来位置的指针,再采用指针的颜色在当前位置绘制指针;如果指针没有动,则直接绘制指针。
另外,秒表需要采用单独的定时器消息控制。
(三)程序设计
在这次设计的时钟程序中,实现了指针图形时钟,数字电子时钟,当前日期以及当前星期的显示。绘制当前数字日期的代码如下:
void cclockdate::drawdate(cdc *pdc, crect rectclient, ctime otime)
{
setposition(rectclient);
uint nyear,nmonth, nday,ndayofweek;
nyear = otime.getyear();
nmonth = otime.getmonth();
nday = otime.getday();
cstring strdate,stryear,strmonth,strday,strdayofweek;
stryear.format("%d",nyear);
strmonth.format("%d",nmonth);
if (nmonth <10)
{
strmonth.format("0%d",nmonth);
}
strday.format("%d",nday);
if (nday <10)
{
strday.format("0%d",nday);
}
strdate.format("%s年%s月%s日",stryear,strmonth,strday);
strdayofweek = weekday( otime);
int nbkmode = pdc->setbkmode(transparent);
pdc->settextcolor(m_dcolor);
pdc->textout(m_dx,m_dy,strdate);
pdc->settextcolor(m_wcolor);
pdc->textout(m_wx,m_wy,strdayofweek);
pdc->setbkmode(nbkmode);
}
绘制星期的代码如下:
cstring cclockdate::weekday(ctime otime)
{
cstring str;
int ndayofweek = otime.getdayofweek();
switch(ndayofweek )
{
case 1:
str = "星期日";
break;
case 2:
str = "星期一";
break;
case 3:
str = "星期二";
break;
case 4:
str = "星期三";
break;
case 5:
str = "星期四";
break;
case 6:
str = "星期五";
break;
case 7:
str = "星期六";
break;
}
return str;
}
绘制图形时钟模块代码如下:
绘制指针(分针、时针以及秒针)代码如下:
void cclockhand::drawhand(cdc *pdc, int nvalue,handtype typehand,cpoint &ptmiddle,ctime otime)
{
m_ptmiddle.x = ptmiddle.x;
m_ptmiddle.y = ptmiddle.y;
m_nridius = min(m_ptmiddle.x,m_ptmiddle.y);
m_npointwidth = (int)m_nridius/20;
cpoint pthand[4];
//得到指针的位置
gethandpoints(nvalue,typehand,pthand, otime);
cbrush brhandh(m_hcolor);
cpen penhandh(ps_solid,1,m_hbordcolor);
cbrush brhandm(m_mcolor);
cpen penhandm(ps_solid,1,m_mbordcolor);
cpen penrgb(ps_solid,1,m_sbordcolor);
switch(typehand)
{
case hour_hand:
//设置画刷、画笔
pdc->selectobject(&brhandh);
pdc->selectobject(&penhandh);
//绘制一个四边形
pdc->polygon(pthand,4);
break;
case minute_hand:
//设置画刷、画笔
pdc->selectobject(&brhandm);
pdc->selectobject(&penhandm);
//绘制一个四边形
pdc->polygon(pthand,4);
break;
case second_hand:
pdc->selectobject(&penrgb);
pdc->moveto(pthand[0]);
pdc->lineto(pthand[1]);
break;
}
}
绘制好了指针后我们将会根据不同的指针确定指针的形态:
void cclockhand::gethandpoints(int nvalue, handtype typehand, cpoint *ppthand,ctime otime)
{
uint nminute = otime.getminute();
cclockscale scale;
scale.m_ptmiddle.x = m_ptmiddle.x;
scale.m_ptmiddle.y = m_ptmiddle.y;
int nlength = 0;
//根据指针的类型区分
switch(typehand)
{
case hour_hand:
//时针长为钟面半径的一半
nlength = muldiv(m_nridius, 50, 100);
//因为绘制时针按照分针进行,故需要一些变换
nvalue *= 5;
nvalue += (nminute/12);
break;
case minute_hand:
nlength = muldiv(m_nridius, 70, 100);
break;
case second_hand:
nlength = muldiv(m_nridius, 80, 100);
break;
default:
assert(false);
}
//得到时针和分针外形的四个点
if (typehand == hour_hand || typehand == minute_hand)
{
ppthand[0] = scale.computerfacepoint(nvalue+30,m_npointwidth*2);
ppthand[1] = scale.computerfacepoint(nvalue+15,m_npointwidth);
ppthand[2] = scale.computerfacepoint(nvalue,nlength);
ppthand[3] = scale.computerfacepoint(nvalue-15,m_npointwidth);
}
//得到秒针的两个端点
else
{
ppthand[0] = m_ptmiddle;
ppthand[1] = scale.computerfacepoint(nvalue,nlength);
}
}
绘制完指针的形态后对不同的指针进行上色:
void cclockhand::sethandcolor(colorref hbcolor, colorref hcolor, colorref mbcolor, colorref mcolor, colorref scolor)
{
m_hbordcolor = hbcolor;
m_hcolor = hcolor;
m_mbordcolor = mbcolor;
m_mcolor = mcolor;
m_sbordcolor = scolor;
}
void cclockhand::gethandcolor(colorref &hbcolor, colorref &hcolor, colorref &mbcolor, colorref &mcolor, colorref &scolor)
{
hbcolor = m_hbordcolor;
hcolor = m_hcolor;
mbcolor = m_mbordcolor;
mcolor = m_mcolor;
scolor = m_sbordcolor;
}
(四)运行截图
1 <html> 2 <head> 3 项目下载地址 4 </head> 5 <body> 6 <a href="http://webcodeschool.hrxxkj.com/webindex">pc端</a> 7 <h3> 移动端关注vx:校猿码。 8 </body> 9 </html>