Skip navigation links
IT services and product development
Menu
TwoLogs
IT services and product development

Source code for C_Timer

Download

The source files for this module are listed below.  You can also download the module C_Timer as a zip archive; this archive contains all the source files and documentation.

Description

CTimer allows you to receive events on regular intervals without handling the Windows timer messages yourself.

Information

This source code also uses our signal/slot library and our CSubclassedWindow class.  You can also download these at our website.

To start using a timer, you first have to initialize it.  Since the timer uses the standard Windows timer messages, the timer needs to have a window to route the messages through.  This is done by subclassing the window.  Since each timer has it's own ID behind the scenes, you can attach as many timers to a window as desired.  You can re-initialize a timer to a different window in order to use that window and stop using the previous one.

Once you have initialized the timer, you can call it's Start, Pauze, Resume and Stop methods in order to start, pauze, resume and stop the timer events.  Calling Start two times in a row will restart the timer on the second call with the given new interval.  Calling Pauze and Resume will only work if the timer was started/resumed or pauzed, respectively.  The current state of the timer can be retrieved by calling the methods IsRunning, IsPauzed and State.

When you stop the timer, you can also let the timer generate a last timer event so the event sequence is terminated.  This timer event is however fired immediately.

The event that is raised has no return value.  It is only meant for informative purposes.

Notes / todo's

  • Add an extra parameter to the Stop method to have the last event sent not immediately but at the expected timer interval.

Files

Each file belonging to this source code module is listed below.

Timer.h

/*******************************************************************************

  Version: 5
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  This code is freely distributable, as long as this comment remains intact.
  If you find this source useful, you may use this code in your own projects
  free of charge, but some acknowledgement to the author of this code is always
  appreciated :)
  The source is however distributed 'as is' without waranty and/or support, and
  may not be fit for each and every application.  Use it at your own discretion
  and at your own risk.
  The source already has undergone testing.  This doesn't mean however that all
  bugs are removed from this piece of code.  If you find one of them, please
  contact me about it.  I can however not guarantee when and if the bug will be
  fixed.

  More information about this module can be found in the accompanying HTML file.

*******************************************************************************/

#ifndef INCLUDE_TWOLOGS_COMMON_TIMER_H
#define INCLUDE_TWOLOGS_COMMON_TIMER_H

#include "..\C_SubclassedWindow\SubclassedWindow.h"
#include "..\C_SignalSlot\Signal.h"

class CTimer;

// A timer's state
enum class ETimerState {
  eStopped,
  ePauzed,
  eRunning
};

// Notifies when the timer went off
struct CTimerWentOffEvent: public CEventTypeNoResult {
  CTimer* poTimer;
};

class CTimer:
  private CSubclassedWindow,
  public CSignal<CTimerWentOffEvent> {
public:
  // Con- & destructor
  CTimer();
  virtual ~CTimer();

  // (Re-)initializes the timer to use the given window
  void Initialize(HWND hWindow);

  // Starts or restarts the timer
  void Start(UINT nMillisecondInterval);

  // Stops the timer
  void Stop(bool bRaiseTimerEvent);

  // Pauses/resumes the timer
  void Pauze();
  void Resume();

  // Whether the timer is running
  bool IsRunning() const;

  // Whether the timer is pauzed
  bool IsPauzed() const;

  // The timer's state
  ETimerState State() const;

private:
  // The last used ID
  static DWORD m_nLastUsedID;

  // Our ID
  DWORD m_nID;

  // The window's handle
  HWND m_hWindow;

  // The timer state
  ETimerState m_eState;

  // The interval set (in millisec)
  UINT m_nInterval;

  // The time the timer was last started
  DWORD m_nLastStartedTime;

  // The remaining number of milliseconds to run, if pauzed
  UINT m_nRemainingInterval;

  // Whether to re-adjust the physical timer to the intended interval
  bool m_bReAdjustTimer;

  // Raises the timer event
  void RaiseTimerEvent();

  // Processes the given message
  void ProcessMessage(CMessage& oMsg);
};

#endif // INCLUDE_TWOLOGS_COMMON_TIMER_H

Timer.cpp

/*******************************************************************************

  Version: 5
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  This code is freely distributable, as long as this comment remains intact.
  If you find this source useful, you may use this code in your own projects
  free of charge, but some acknowledgement to the author of this code is always
  appreciated :)
  The source is however distributed 'as is' without waranty and/or support, and
  may not be fit for each and every application.  Use it at your own discretion
  and at your own risk.
  The source already has undergone testing.  This doesn't mean however that all
  bugs are removed from this piece of code.  If you find one of them, please
  contact me about it.  I can however not guarantee when and if the bug will be
  fixed.

  More information about this module can be found in the accompanying HTML file.

*******************************************************************************/

#include "Timer.h"

// The last used ID
DWORD CTimer::m_nLastUsedID = 0;

// Con- & destructor
CTimer::CTimer():
 m_nID(m_nLastUsedID++),
 m_hWindow(NULL),
 m_eState(ETimerState::eStopped),
 m_bReAdjustTimer(false) {
}
CTimer::~CTimer() {
  Stop(false);
}

// (Re-)initializes the timer to use the given window
void CTimer::Initialize(HWND hWindow) {
  // Look if to detach from a previous window
  if (m_hWindow != NULL) {
    // Yes -> do so
    StopSubclassing(m_hWindow);
  }

  // Note the window to use
  m_hWindow = hWindow;

  // And start subclassing it, if needed
  if (m_hWindow != NULL) {
    Subclass(m_hWindow);
  }
}

// Starts or restarts the timer
void CTimer::Start(UINT nMillisecondInterval) {
  if (m_hWindow != NULL) {
    m_nInterval = nMillisecondInterval;
    m_nLastStartedTime = GetTickCount();
    SetTimer(m_hWindow, m_nID, nMillisecondInterval, nullptr);
    m_eState = ETimerState::eRunning;
  }
}

// Stops the timer
void CTimer::Stop(bool bRaiseTimerEvent) {
  // Kill the timer
  if (m_hWindow != NULL) {
    KillTimer(m_hWindow, m_nID);
    m_eState = ETimerState::eStopped;
  }

  // And look if to raise a final timer event
  if (bRaiseTimerEvent) {
    // Yes -> do so
    RaiseTimerEvent();
  }
}

// Pauses/resumes the timer
void CTimer::Pauze() {
  // Look if we're running at all
  if (m_eState == ETimerState::eRunning) {
    // Yes -> pauze the timer
    if (m_hWindow != NULL) {
      KillTimer(m_hWindow, m_nID);
      m_eState = ETimerState::ePauzed;
      DWORD nNow = GetTickCount();
      DWORD nRanInterval = nNow - m_nLastStartedTime;
      m_nRemainingInterval = m_nInterval - nRanInterval;
    }
  }
}
void CTimer::Resume() {
  // Look if we're pauzed
  if (m_eState == ETimerState::ePauzed) {
    // Yes -> restart the timer where we left off
    if (m_hWindow != NULL) {
      DWORD nNow = GetTickCount();
      UINT nUsedInterval = m_nInterval - m_nRemainingInterval;
      m_nLastStartedTime = nNow - nUsedInterval;
        // ...fake an earlier start to match the desired interval
      m_bReAdjustTimer = true;
      SetTimer(m_hWindow, m_nID, m_nRemainingInterval, nullptr);
      m_eState = ETimerState::eRunning;
    }
  }
}

// Whether the timer is running
bool CTimer::IsRunning() const {
  return m_eState != ETimerState::eStopped;
}

// Whether the timer is pauzed
bool CTimer::IsPauzed() const {
  return m_eState == ETimerState::ePauzed;
}

// The timer's state
ETimerState CTimer::State() const {
  return m_eState;
}

// Raises the timer event
void CTimer::RaiseTimerEvent() {
  CTimerWentOffEvent oEvent;
  oEvent.poTimer = this;
  CSignal<CTimerWentOffEvent>::RaiseEvent(oEvent);
}

// Processes the given message
void CTimer::ProcessMessage(CMessage& oMsg) {
  // Look if it's a timer message
  oMsg.bStop = false;
  if (oMsg.eCode == WM_TIMER) {
    // Yes -> look if it's our timer
    if (oMsg.nWParam == m_nID) {
      // Yes -> re-adjust the timer, if needed
      if (m_bReAdjustTimer && m_eState == ETimerState::eRunning) {
        KillTimer(m_hWindow, m_nID);
        m_nLastStartedTime = GetTickCount();
        SetTimer(m_hWindow, m_nID, m_nInterval, nullptr);
        m_bReAdjustTimer = false;
      }

      // And raise the timer event
      oMsg.bStop = true;
      RaiseTimerEvent();
    }
  }
}