VC 多个定时器

VC 多个定时器
SetTimer函数的原型:

UINT_PTR SetTimer(  HWND hWnd,              // 窗口句柄  UINT_PTR nIDEvent,      // 定时器ID,多个定时器时,可以通过该ID判断是哪个定时器  UINT uElapse,           // 时间间隔,单位为毫秒  TIMERPROC lpTimerFunc   // 回调函数);
在MFC程序中SetTimer被封装在CWnd类中,调用就不用指定窗口句柄了,例如:
SetTimer(1,100,NULL); //1为ID号.100为时间
例如:

// 添加两个定时器
SetTimer(1,500,NULL);
SetTimer(2,1000,NULL);

//停止定时器

KillTimer(1);//停止ID为1的定时器

KillTimer(2);//停止ID为2的定时器

 

定时器:

void CTimerTestDlg::OnTimer(UINT nIDEvent) 

{

switch (nIDEvent)

{

case 1:  ///处理ID为1的定时器

... 
break;

case 2:  ///处理ID为2的定时器

...

break;

}

 

 
 

很多应用程序都需要使用定时器,以便定期检查状态,并重新绘图。学过VB的朋友知道,VB中的定时器是一个控件,我们只需要放置一个定时器控件到窗体之上,然后设置其属性,并在程序中处理该控件产生的定时事件就行了。不过在VC中并没有定时器控件,我们可以通过两种方法来使用定时器:一种是直接调用WIN32函数SetTimer(),另一种则是调用CWnd::SetTimer(),实际上这两种方法的本质是一样的,下面我们就来看看后者的使用方法。

l          设置和释放定时器

CWnd::SetTimer()的功能是给当前窗口设置一个定时器,该函数的原型为:

UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );

由于一个窗口可以同时设置几个定时器,SetTimer()的第一个参数nIDEvent起标识不同定时器的作用,我们可以任意取值,只要不与其它定时器的标识重复就行了,以便将来能正确分辨出是哪个定时器发出的定时消息。nElapse表示每隔多少毫秒产生一次定时消息。lpfnTimer是一个函数指针,如果传递一个NULL指针,那么在产生定时中断时,Windows会把一条WM_TIMER消息放入到程序的消息队列之中,最终由CWnd对象相应的处理函数来处理该消息;如果传递了一个真正的函数指针,那么在产生定时中断时,Windows将直接调用该函数,由该函数来处理定时消息。这也就是说,我们可以使用两种方式来处理定时消息,它们有什么区别呢?

首先,WM_TIMER是一条低优先级的消息。如果消息队列之中还有其它高优先级的消息,那么WM_TIMER将被堵塞,直到最后才会被处理。其次,WM_TIMER被程序从消息队列中取回之后,还要经过一系列的过程才能被传递到它的处理函数。把这两点结合起来可以看出,在第一种方式下,定时消息有可能要拖延一段时间才会被处理,而第二种方式则不同,由于Windows将直接调用指定的函数,所以延迟时间要短得多。

由于Schedule对实时性要求并不苛刻,只要能精确到分钟左右就可以了,另外Schedule内部要进行的运算和处理也不多,所以心铃决定选用第一种方式,准备在视类中处理定时消息。在第十二讲给出的CScheduleView::OnInitialUpdate()的最后一行语句便是设置定时器的代码,现在我们可以把它前面的注释符号删掉了,这个定时器将使Schedule在每过20秒左右的时间得到一次重新检查各事件状态的机会。

定时器是一种有限的系统资源,如果多个程序都需要使用定时器,那么有可能会出现设置不成功的情况,因此我们应在调用SetTimer()函数时检查它的返回值,如果返回值是0,就表示系统中已经没有可用的定时器了。Schedule没有进行这步检查,心铃想请大家自己添上,并决定在出现这种情况时应该怎么办。

SetTimer()还有一个作用是它能够重新设置已分配的定时器的定时间隔,只要在nIDEvent中传递已分配的定时器的标识,那么该定时器的定时间隔就会改变成nElapse中指定的新值。

在程序退出时,我们应该将已分配的定时器释放掉。为此我们利用ClassWizard为视类添加一个处理WM_DESTROY消息的函数OnDestroy(),并编写以下代码:

void CScheduleView::OnDestroy() {

  KillTimer(1);

  CListCtrl & lst=(this->GetListCtrl());

  CImageList *pImageList=lst.GetImageList(LVSIL_SMALL);

  delete pImageList;

  CListView::OnDestroy();

}

KillTimer()用于释放定时器,其参数应等于SetTimer()的nIDEvent。如果程序分配了多个定时器,那么应调用多次KillTimer()。OnDestroy()同时也把与CListView内部的List控件关联的图象列表释放了。

l          编写消息处理函数

使用定时器的第一种方式需要用到一个回调函数,该函数必须具有以下原型:

void CALLBACK EXPORT TimerProc( HWND hWnd, UINT nMsg, UINT nIDEvent,

DWORD dwTime );

由于我们不使用这种方式,所以TimerProc各参数的含义请大家自己查阅MSDN库,下面我们来看看如何为第二种方式编写消息处理函数。

首先我们利用ClassWizard为视类添加一个处理WM_TIMER消息的函数OnTimer()。它只有一个参数nIDEvent,检查这个值便可以知道WM_TIMER消息是由哪个定时器发出的。下面便是OnTimer()的实现代码:

void CScheduleView::OnTimer(UINT nIDEvent) {

  CTime tiNow=CTime::GetCurrentTime();

  CTimeSpan tdif(0,0,15,0);

  CScheduleDoc * pDoc=GetDocument();

  CString Message;

  struct ScheduleItem* pUp,*pDown;

  pUp=pDoc->m_pNearestTaskUp;

  pDown=pDoc->m_pNearestTaskDown;

  if(nIDEvent==1) {

    if( ( (pUp!=NULL) && (pUp->ti<tiNow-tdif) ) ||

      ( (pDown!=NULL) && (pDown->ti<=tiNow+tdif) ) ) {

        pDoc->SearchNearestTask();

        //更新事件条目的状态

        pDoc->UpdateState();

        pDoc->UpdateAllViews(NULL,0,NULL);

        //更新显示

        if((pDown!=NULL) && ( pDown->ti <= (tiNow+tdif) )) {

          AfxGetMainWnd()->ShowWindow(SW_NORMAL);

          SetForegroundWindow(); //把窗口显示在前台

          Message="请注意:\""; //准备提示信息

          Message += pDown->des;

          Message += "\" 的时间很快就要到了!";

          KillTimer(1); //必须先暂时中断定时器

          AfxMessageBox(Message);

          SetTimer(1,20000,NULL); //重新设置定时器

        }

      }

    }

    //CListView::OnTimer(nIDEvent); 将ClassWizard生成的这行代码注释掉

}

 

 

 

图17-1:报警的消息框

 

在上面的代码中,我们首先检查处于时间边界的两个事件条目,判断是否需要改变它们的状态。如果是的话,就重新搜索处于时间边界的两个新事件条目,更新链表中各事件的状态,并更新显示。对于刚从等待报警状态(状态2)进入报警状态(状态1)的事件,我们有必要以某种方式通知用户,提醒他已经到了应做某某事情的时间。上面的代码首先将程序主窗口以正常方式显示,然后把窗口设置成前台,这两条语句组合起来便可以把Schedule显示在前台,即便它当时处于最小化状态,上述语句也能将它的窗口弹出来。窗口显示出来后,我们接着再显示一个消息框(见图17-1),其中给出了某某事情的时间已到了的提示信息。

消息框本质上是一个非常简单的由系统控制的对话框。上一讲给出的代码中也用到了它,我们使用的函数是MFC类库包装过的AfxMessageBox(),它比直接使用WIN32函数MessageBox()要稍微简单一些,但缺点是不能设置消息的标题条。除了上述两个函数外,CWnd类也有一个MessageBox()成员函数,其功能与WIN32函数类似,大家以后可以根据需要选择使用。

大家是否注意到了,上面的代码在显示消息框之前调用了一次KillTimer(),从消息框返回后又调用了一次SetTimer(),为什么要这样做呢?这个问题实际上涉及到了WM_TIMER消息的一些内在特点。

设置了定时器后,程序的窗口处理函数将会定期收到一条WM_TIMER消息,如果OnTimer()内部的处理需要花费很长的时间,就有可能在还未退出它之前,程序又收到一条WM_TIMER消息。此时会出现两种情况:一种是OnTimer()的内部调用了消息框或对话框,当前正在等待用户输入。在这种情况下,当前CPU的控制权实际上掌握在Windows手中,而不在程序手中,因此Windows将再次调用OnTimer()(称为“重入”,重复进入的意思),这样可能会造成不良后果。例如将再次显示出一个消息框或对话框,或者由于后一次调用改变了全局变量,从而对前一次调用产生影响。另一种情况是OnTimer()内部正在进行大量的处理,但没有调用任何可能把控制权交还给Windows的函数,那么第二个WM_TIMER消息将被保留在程序的消息队列之中。如果因为OnTimer()迟迟不能返回,最后导致第三个、第四个甚至更多的WM_TIMER消息也到来了,此时后面来的WM_TIMER消息将会冲掉前面的WM_TIMER消息,也即消息队列中只保留最后一条WM_TIMER消息,其余的都被丢弃了,这与WM_PAINT消息是类似的。

总之,上面两种情况均说明了OnTimer()或其它处理定时消息的函数不适合完成耗时很长的处理,如果不得已要进行这种处理,那么应考虑设立一些保护措施,以防止出现异常现象。

Schedule属于第一种情况,于是心铃选择了在显示消息框之前先暂时中断定时器,从消息框返回之后再重置定时器的方法。除此之外,我们也可以不暂停定时器,但必须设置一个初始值为假的重入标志,每次进入OnTimer()时首先检查该标志是否为真。如果是真(表示本次调用是重入)则马上退出函数,如果是假则把该标志置为真,然后进行各种处理,最后在退出函数之前又把该标志置为假。这种方法可以有效地避免因重入而带来不良的后果。

l          时间精度问题

现在我们来讨论一下定时器的精度问题。SetTimer()设置的定时器被称为系统定时器,该函数的参数nElapse是以毫秒为单位,但这并不意味着系统定时器的最小精度可达1毫秒。我们知道,PC机有一块时钟芯片连接在可编程中断控制器的IRQ0上,并以每秒钟18.2次的速度产生中断,相当于55毫秒一次,这便是Windows 9x(不同的操作系统和硬件平台可能会有不同的最小精度)的系统定时器的最小精度。也就是说,即使我们为nElapse设置了小于55的值,定时消息实际到达的间隔也在55毫秒之上。另一方面,由于Windows是一个多任务操作系统,应用程序遵循消息驱动模式,定时消息到达程序的消息队列之后可能要经过一段时间后才会被处理,这样就为两条定时消息之间的间隔加上了随机性,也即下一条定时消息被处理的时间是无法确定的。

Schedule对时间的精度并不敏感,因此我们使用系统时钟便可完全满足要求,然而系统时钟对那些实时性要求较高的应用程序就不够了。例如以每秒25帧速度播放动画或视频数据的多媒体程序,如果使用定时器,那么最低的精度要求都是40毫秒,系统时钟显然达不到这一精度。为此,Windows的多媒体子系统提供了另外一种定时器——多媒体定时器(Multimedia Timer),在多数系统上,它都能达到1毫秒的精度,基本上能满足各种多媒体应用的要求。不过需要注意的是,当定时消息发生间隔小于10毫秒时,系统性能就会受到比较明显的影响,因此我们在使用多媒体定时器时应把间隔定为能满足要求的最大值,而不能去追求最小值

CDialog::OnTimer(nIDEvent);

}

时间: 2024-09-23 09:20:27

VC 多个定时器的相关文章

VC++ MFC中标题栏的文字,如何横向滚动,用定时器实现的话怎么写呢?

问题描述 VC++ MFC中标题栏的文字,如何横向滚动,用定时器实现的话怎么写呢? VC++ MFC中标题栏的文字,如何横向滚动,用定时器实现的话怎么写呢? 解决方案 用CString的Mid函数,依次从左边去掉一个字符加在右边,然后设置下标题. 解决方案二: 主要就是定时器中每次都把字符串做一个偏移,然后再显示,这样看上去就是滚动的了

VC定时器的用法实例详解_C 语言

本文实例讲述了VC中定时器的用法,分享给大家供大家参考.具体用法分析如下: 定时器在VC中的使用频繁,其原型为: 复制代码 代码如下: WINUSERAPI UINT WINAPI SetTimer ( HWND hWnd , UINT nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc); 其中的参数用法如下: hWnd       是欲设置定时器的窗体句柄.定时时间到时,系统会向该窗体发送WM_TIMER消息. nIDEvent     定时器标识符.

VC++实现非窗口类中使用定时器的方法

定时器在Windows 的程序中的作用不可忽略,也随处可见.设定一个时间间隔每0.5秒或者1秒钟刷新一次时钟,这样就可以完成一个简单的电子钟程序.在不同的编程工具中定时器的用法也不同,Visual C++中也给我们提供了实现这种功能的方法,而且方法不只一种.在窗口类中是使用定时器比较很简单,用SetTimer()设置了定时器之后,并在Class Wizard中添加了WM_TIMER消息映射后,您就可以在映射函数OnTimer()中添加代码实现,来定时完成您的任务,而且还支持任意多个定时器,这种方

VC定时器的用法:SetTimer和Ontimer .

Settimer是设置一个计时器并开始执行计时器Ontimer中的代码,Ontimer是计时器所执行的代码.KillTimer用于停止计时器. Settimer是设置,Ontimer是响应Settimer消息的. SetTimer函数的用法 1 )用WM_TIMER来设置定时器 先请看SetTimer这个API函数的原型 UINT_PTR SetTimer(              HWND hWnd,//和定时器相关联的窗口     UINT_PTR nIDEvent,//一个非0的数字标志

VC++的win32小程序内存泄漏,求高手解答

问题描述 VC++的win32小程序内存泄漏,求高手解答 做的一个简单的交通模拟小程序,用三个定时器分别产生 汽车处理消息,红绿灯更换消息和汽车随机产生消息.但是最后程序内存越跑越大.求高手解答.http://download.csdn.net/detail/hdwbdbsm/6724747这个是程序的下载地址,求高手.

VC MFC专题

MFC程序如何实现给对话框添加背景图片 MFC游戏开发笔记十 游戏中的碰撞检测进阶:地图类型&障碍物 MFC游戏开发笔记九 游戏中的碰撞判定初步&怪物运动简单AI MFC游戏开发笔记八 游戏特效的实现(二):粒子系统 MFC游戏开发笔记七 游戏特效的实现(一):背景滚动 MFC游戏开发笔记六 图像双缓冲技术:实现一个流畅的动画 MFC游戏开发笔记五 定时器和简单动画 MFC游戏开发笔记四 键盘响应和鼠标响应:让人物动起来 MFC游戏开发笔记三 游戏贴图与透明特效的实现 MFC游戏开发笔记二

VC中基于Windows的精确定时

在工业生产控制系统中,有许多需要定时完成的操作,如定时显示当前时间,定时刷新屏幕上的进度条,上位 机定时向下位机发送命令和传送数据等.特别是在对控制性能要求较高的实时控制系统和数据采集系统中,就更需要精确定时操作. 众所周知,Windows 是基于消息机制的系统,任何事件的执行都是通过发送和接收消息来完成的. 这样就带来了一些问题,如一旦计算机的CPU被某个进程占用,或系统资源紧张时,发送到消息队列 中的消息就暂时被挂起,得不到实时处理.因此,不能简单地通过Windows消息引发一个对定时要求

vc.net中实现启动画面淡入淡出

找了半天没找实现启动画面来个淡入淡出的代码,只好自己写了个,呵呵,还不错拿给大家看看. #undef WINVER //取消原有版本定义,重新定义版本 #define WINVER 0x5000 //为了使AnimateWindow函数可用 #include <afxwin.h> 然后在相关文件分别加入OnCreate,OnClose,OnEraseBkgnd和OnTimer消息函数.记得在相关构析函数内加入 : SetTimer(1, 3000, NULL); //设定定时器1,定时3秒 O

用VC.NET制作启动屏幕的新方法

在利用VC++.net编程过程中,遇到制作启动屏幕的问题,几经试验,得出一种方便简单的制作方法. 基本原理是利用对话框窗口的特性,在启动时首先创建对话框,设置一个获得位图文件的句柄,利用Picture控件的SetBitmap(HBITMAP hBitmap)方法,使位图文件充满整个Picture控件窗口,最后设置定时器,整个启动屏幕就制作完成. 下面介绍具体实现步骤. 1. 打开VS.net的开发环境,新建VC++.net中的MFC应用程序,输入工程名称MySample,点击"确定",