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 TTimer;

// A timer's state
enum class TTimerState {
  stopped,
  pauzed,
  running
};

// Notifies when the timer went off
struct TTimerWentOffEvent: public TEventTypeNoResult {
  TTimer* timerPtr;
};

class TTimer:
  private TSubclassedWindow,
  public TSignal<TTimerWentOffEvent> {
public:
  // Con- & destructor
  TTimer();
  virtual ~TTimer();

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

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

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

  // 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
  TTimerState State() const;

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

  // Our ID
  DWORD m_id;

  // The window's handle
  HWND m_window;

  // The timer state
  TTimerState m_state;

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

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

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

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

  // Raises the timer event
  void RaiseTimerEvent();

  // Processes the given message
  void ProcessMessage(TMessage& msg);
};

#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 TTimer::m_lastUsedID = 0;

// Con- & destructor
TTimer::TTimer():
 m_id(m_lastUsedID++),
 m_window(NULL),
 m_state(TTimerState::stopped),
 m_reAdjustTimer(false) {
}
TTimer::~TTimer() {
  Stop(false);
}

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

  // Note the window to use
  m_window = window;

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

// Starts or restarts the timer
void TTimer::Start(UINT millisecondInterval) {
  if (m_window != NULL) {
    m_interval = millisecondInterval;
    m_lastStartedTime = GetTickCount();
    SetTimer(m_window, m_id, millisecondInterval, nullptr);
    m_state = TTimerState::running;
  }
}

// Stops the timer
void TTimer::Stop(bool raiseTimerEvent) {
  // Kill the timer
  if (m_window != NULL) {
    KillTimer(m_window, m_id);
    m_state = TTimerState::stopped;
  }

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

// Pauses/resumes the timer
void TTimer::Pauze() {
  // Look if we're running at all
  if (m_state == TTimerState::running) {
    // Yes -> pauze the timer
    if (m_window != NULL) {
      KillTimer(m_window, m_id);
      m_state = TTimerState::pauzed;
      DWORD now = GetTickCount();
      DWORD ranInterval = now - m_lastStartedTime;
      m_remainingInterval = m_interval - ranInterval;
    }
  }
}
void TTimer::Resume() {
  // Look if we're pauzed
  if (m_state == TTimerState::pauzed) {
    // Yes -> restart the timer where we left off
    if (m_window != NULL) {
      DWORD now = GetTickCount();
      UINT usedInterval = m_interval - m_remainingInterval;
      m_lastStartedTime = now - usedInterval;
        // ...fake an earlier start to match the desired interval
      m_reAdjustTimer = true;
      SetTimer(m_window, m_id, m_remainingInterval, nullptr);
      m_state = TTimerState::running;
    }
  }
}

// Whether the timer is running
bool TTimer::IsRunning() const {
  return m_state != TTimerState::stopped;
}

// Whether the timer is pauzed
bool TTimer::IsPauzed() const {
  return m_state == TTimerState::pauzed;
}

// The timer's state
TTimerState TTimer::State() const {
  return m_state;
}

// Raises the timer event
void TTimer::RaiseTimerEvent() {
  TTimerWentOffEvent event;
  event.timerPtr = this;
  TSignal<TTimerWentOffEvent>::RaiseEvent(event);
}

// Processes the given message
void TTimer::ProcessMessage(TMessage& msg) {
  // Look if it's a timer message
  msg.stop = false;
  if (msg.code == WM_TIMER) {
    // Yes -> look if it's our timer
    if (msg.wParam == m_id) {
      // Yes -> re-adjust the timer, if needed
      if (m_reAdjustTimer && m_state == TTimerState::running) {
        KillTimer(m_window, m_id);
        m_lastStartedTime = GetTickCount();
        SetTimer(m_window, m_id, m_interval, nullptr);
        m_reAdjustTimer = false;
      }

      // And raise the timer event
      msg.stop = true;
      RaiseTimerEvent();
    }
  }
}