Click here to Skip to main content
Licence 
First Posted 17 Jan 2006
Views 49,492
Bookmarked 31 times

Timers in MFC / C++

By | 17 Jan 2006 | Article
Implementing one-shot timers.

Introduction

Setting a timer in C++/Win32 that fires once, normally requires a lot of work, considering that all you really want to do is wait a bit and execute a function, but not block mainline code execution. This simple class makes OneShot timers easy. No more struggling with static functions and 'this' pointers.

Using the code

The only special instructions for using this class are:

  • You must instantiate the timer object using new, because the object deletes itself after the timer fires.
  • The target function is called using PostMessage, so there must be a MESSAGE_MAP entry (either ON_MESSAGE, or ON_COMMAND) for the target function.
// Map the function you want to call.
// You can use either Command or Message format:
BEGIN_MESSAGE_MAP(CMyClass, CWnd)
       ON_COMMAND( IDC_DOCOMMAND, OnDoCommand )
    ON_MESSAGE( IDC_DOMESSAGE, OnDoMessage )
END_MESSAGE_MAP()
// Your Functions
void CMyClass::OnDoCommand()
{
   return;
}

LRESULT CMyClass::OnDoMessage( WPARAM wParam, 
                               LPARAM lParam )
{
   return 0;
}
// In your code, fire the OneShots. Remember they must use new, as 
//they are self deleting once the timer fires.
//The TimerID is guaranteed to be unique as the object 
//exists for the the duration of the timer.

new CTimerOneShot( 2000, GetSafeHwnd(), 
                   IDC_DOCOMMAND ); // Command Version

// or

new CTimerOneShot( 2000, GetSafeHwnd(), 
                   IDC_DOMESSAGE, 123, 456 ); // Message Version
// And here is the OneShot class. 
#pragma once

class CTimerOneShot
{
public:

   // MESSAGE Format
   CTimerOneShot( int a_msecs, HWND a_targetHwnd, 
                  int a_messageID, WPARAM a_wparam, 
                  LPARAM a_lparam )
   {
      m_timerID = (UINT) this;
      m_targetHwnd = a_targetHwnd;
      m_messageID = a_messageID;
      m_lparam = a_lparam;
      m_wparam = a_wparam;
      m_msecs = a_msecs;

      ::SetTimer( a_targetHwnd, m_timerID, 
                  m_msecs, TimerProcA );
   }

   static void CALLBACK EXPORT TimerProcA( HWND a_hwnd, 
          UINT /*WM_TIMER*/, 
          UINT a_timerID, DWORD a_systemtime )
   {
      CTimerOneShot* pOneShot = (CTimerOneShot*) a_timerID;
      ASSERT( a_hwnd == pOneShot->m_targetHwnd );
      ::KillTimer( pOneShot->m_targetHwnd, a_timerID );
      ::PostMessage( pOneShot->m_targetHwnd, 
                     pOneShot->m_messageID, 
                     pOneShot->m_wparam, 
                     pOneShot->m_lparam );
      delete pOneShot;
   }

   // COMMAND Format
   CTimerOneShot( int a_msecs, 
      HWND a_targetHwnd, int a_commandID )
   {
      m_timerID = (UINT) this;
      m_targetHwnd = a_targetHwnd;
      m_commandID = a_commandID;
      m_msecs = a_msecs;

      ::SetTimer( a_targetHwnd, m_timerID, 
                  m_msecs, TimerProcB );
   }

   static void CALLBACK EXPORT TimerProcB( HWND a_hwnd, 
          UINT /*WM_TIMER*/, UINT a_timerID, 
          DWORD a_systemtime )
   {
      CTimerOneShot* pOneShot = (CTimerOneShot*) a_timerID;
      ASSERT( a_hwnd == pOneShot->m_targetHwnd );
      ::KillTimer( pOneShot->m_targetHwnd, a_timerID );
      ::PostMessage( pOneShot->m_targetHwnd, 
                     WM_COMMAND, pOneShot->m_commandID, 0 );
      delete pOneShot;
   }

   HWND   m_targetHwnd;
   int    m_messageID;
   int    m_commandID;
   LPARAM m_lparam;
   WPARAM m_wparam;
   UINT   m_timerID;
   int    m_msecs;
};

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Steve Johnson (Sven)



United States United States

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
Generalmyapp interface Pinmembergiannib2k23:49 20 May '07  
GeneralRe: myapp interface PinmemberSteve Johnson (Sven)4:01 21 May '07  
GeneralExcellent - Very Handy PinmemberYogesh P. Dhakad1:19 22 Apr '07  
GeneralThanks for the Info Pinmemberlanrosta10:40 20 Jan '06  
GeneralRe: Thanks for the Info PinmemberRstudio18:33 24 Jan '06  
lanrosta wrote:
spent the last 3 days trying to figure out the SetTimer Callback BS. Only to find a bunch of non static errors. This has been the hardest timer I have ever had to figure out. I've used other programming languages and it just seems ridiculous that one would have to go through this much work to get a simple timer function. Argh!

 

SetTimer is easy if you understand the limitations of "callbacks".
 
Every time the timer expires, the system invokes the function you provided to SetTimer. Simple enough. But if you need your function to interact with classes like MFC or whatever, you have to work a little harder.
 
In the easiest implementation, the callback function you write cannot access your Dialog CEdit windows, for example. Even if you initiate the timer by pressing a dialog button, when the function finally gets to run, it is NOT running it in the context of your classes.
 
The term "callback" is misleading. The func doesn't get called back into the environment you were in when you set the timer (as I once thought it should) but simply gets called. The term "back" merely means that a function you gave is given back to you... sort of.
 
In case you haven't gotten the simplest timer going, here's an example that rings the PC speaker bell every second until you press the Stop button. You should be able to get this going in your own MFC Dialog (or wherever).
 #define MYTIMER 1
 bool bRepeatable = true;
 int iMilliseconds = 1000;
 CEdit *textwin;
  
 // declaration of the callback function
 void CALLBACK MyTimer (
     HWND hWnd,
     UINT nMsg,
     UINT nIDEvent,
     DWORD dwTime);
  
 ...
 
 bool CSetTimerDlg::OnInitDialog()
 {
     ...
  
     textwin = &CEdit_myDialogEditWindow;
 }
  
 void CSetTimerDlg::OnBnClickedStartTimer()
 {
     SetTimer(MYTIMER, iMilliseconds, MyTimer);
 }
  
 void CSetTimerDlg::OnBnClickedStopTimer()
 {
     KillTimer(MYTIMER);
 }
  
 void CSetTimerDlg::OnBnClickedCheckRepeatable()
 {
     bRepeatable = (BST_CHECKED ==
         CheckButton_Repeatable.GetCheck());
 }
  
 // definition of the callback function
 void CALLBACK MyTimer (
     HWND hWnd,
     UINT nMsg,
     UINT nIDEvent,
     DWORD dwTime)
 {
     Beep(300, 30);
     if (!bRepeatable) {         
         KillTimer(hWnd, nIDEvent);
         // global pointer to an edit window
         textwin->SetWindowText("stopped");
     }
 }
The most common problem is arranging for the independently running callback function to be able to access whatever class members it's supposed to work with. Note the global pointer to the Dialog CEdit text window (initialized during the Dialog init function). There may be better solutions, but this works for simple access. Of course, you have to be careful whenever you cheat this way. The more your callback needs to work with a class, the more you punch holes in the class.
 
Finally, it should be noted that you can't get less than about a 15-millisecond granularity with WM_TIMER timers under the most perfect circumstances. If you need better accuracy you can use a multimedia timer, but it's not generally worth the trouble.

GeneralRe: Thanks for the Info Pinmemberlanrosta11:51 25 Jan '06  
GeneralRe: Thanks for the Info PinmemberRstudio11:44 26 Jan '06  
GeneralRe: Thanks for the Info PinmemberSteve Johnson (Sven)11:54 26 Jan '06  
GeneralRe: Thanks for the Info PinmemberRstudio14:16 28 Jan '06  
GeneralRe: Thanks for the Info PinmemberSteve Johnson (Sven)16:48 28 Jan '06  
GeneralI wouldn't use WM_TIMER PinmemberJohn Simmons / outlaw programmer9:41 17 Jan '06  
GeneralRe: I wouldn't use WM_TIMER PinmemberSteve Johnson (Sven)9:56 17 Jan '06  
GeneralRe: I wouldn't use WM_TIMER PinmemberJohn Simmons / outlaw programmer3:23 18 Jan '06  
GeneralRe: I wouldn't use WM_TIMER PinmemberSteve Johnson (Sven)4:44 18 Jan '06  
QuestionRe: I wouldn't use WM_TIMER PinmemberChris Hills5:05 18 Mar '06  
AnswerRe: I wouldn't use WM_TIMER PinmemberSteve Johnson (Sven)7:04 18 Mar '06  
GeneralRe: I wouldn't use WM_TIMER PinmemberRasqual Twilight3:28 17 Aug '06  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120529.1 | Last Updated 17 Jan 2006
Article Copyright 2006 by Steve Johnson (Sven)
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid