Click here to Skip to main content
15,885,546 members
Articles / Desktop Programming / MFC

XTimer - Timer and Stopwatch Utility with Source Code

Rate me:
Please Sign up or sign in to vote.
4.81/5 (33 votes)
20 Aug 2007CPOL3 min read 165.6K   6.7K   82  
XTimer provides countdown timer and stopwatch features in a compact MFC dialog app.
// XTimerDlg.cpp  Version 1.3 - article available at http://www.codeproject.com/tools/xtimer.asp
//
// Author:  Hans Dietrich
//          hdietrich@gmail.com
//
// History
//     Version 1.3 - 2007 August 11
//     - Save program options in registry
//     - Left doubleclick on time display opens options dialog
//     - Added Quick Timer menu option
//     - Added VS2005 project
//
//     Version 1.2 - 2003 June 16
//     - Fixed display bug reported by Pit M. 
//
//     Version 1.1 - 2003 June 14
//     - Added "Play once, Play until stopped" option 
//
//     Version 1.0 - 2003 June 1
//     - Initial public release 
//
// License:
//     This software is released into the public domain.  You are free to use
//     it in any way you like, except that you may not sell this source code.
//
//     This software is provided "as is" with no expressed or implied warranty.
//     I accept no liability for any damage or loss of business that this 
//     software may cause.
//
///////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "XTimer.h"
#include "XTimerDlg.h"
#include "TimerOptions.h"
#include <io.h>
#include "mmsystem.h"
#include "about.h"

#pragma comment(lib, "Winmm.lib")

#pragma warning(disable : 4996)	// disable bogus deprecation warning

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

const TCHAR * TIME_DISPLAY_FORMAT = _T("%H:%M:%S.%s");
const int ONE_MINUTE = 60;	// seconds
const int ONE_HOUR = 3600;	// seconds

//=============================================================================
BEGIN_MESSAGE_MAP(CXTimerDlg, CDialog)
//=============================================================================
	//{{AFX_MSG_MAP(CXTimerDlg)
	ON_WM_CONTEXTMENU()
	ON_WM_DESTROY()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_NCLBUTTONDBLCLK()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_WM_SYSCOMMAND()
	ON_WM_TIMER()
	ON_BN_CLICKED(IDC_MORE, OnMore)
	ON_BN_CLICKED(IDC_RESET, OnReset)
	ON_BN_CLICKED(IDC_START, OnStart)
	ON_COMMAND(ID_ABOUT, OnAbout)
	ON_COMMAND(ID_START, OnStart)
	ON_COMMAND(ID_STOP, OnStop)
	ON_COMMAND(ID_STOPWATCH, OnStopwatch)
	ON_COMMAND(ID_TIMER, OnXTimer)
	ON_COMMAND(ID_TIMER_OPTIONS, OnTimerOptions)
	//}}AFX_MSG_MAP
	ON_COMMAND_RANGE(ID_ONE_MINUTE, ID_EIGHT_HOURS, OnQuickTimer)
END_MESSAGE_MAP()

//=============================================================================
CXTimerDlg::CXTimerDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CXTimerDlg::IDD, pParent)
//=============================================================================
{
	//{{AFX_DATA_INIT(CXTimerDlg)
	//}}AFX_DATA_INIT
	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_bTimerStarted   = FALSE;
	m_nTimerSeconds   = 60*10;	// 10 minutes
	m_bTimer          = TRUE;
	m_bPlaySound      = TRUE;
	m_bContinuousPlay = FALSE;
	m_nPlayOnce       = 0;
	m_strSoundFile    = _T("");
	m_TimeLeft.SetHighTimeSpan(0, 0, 0, m_nTimerSeconds, 0, 0, 0);	// for timer
	m_TimeElapsed.SetHighTimeSpan(0, 0, 0, 0, 0, 0, 0);				// for stopwatch
}

//=============================================================================
void CXTimerDlg::DoDataExchange(CDataExchange* pDX)
//=============================================================================
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CXTimerDlg)
	DDX_Control(pDX, IDC_TIME, m_Time);
	DDX_Control(pDX, IDC_START, m_btnStart);
	DDX_Control(pDX, IDC_RESET, m_btnReset);
	DDX_Control(pDX, IDC_MORE, m_btnMore);
	//}}AFX_DATA_MAP
}

//=============================================================================
BOOL CXTimerDlg::OnInitDialog()
//=============================================================================
{
	CDialog::OnInitDialog();

	// Add "About..." menu item to system menu.

	m_brush.CreateStockObject(BLACK_BRUSH);

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		CString strAboutMenu;
		strAboutMenu.LoadString(IDS_ABOUTBOX);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	LoadProfile();

	SetWindowText();

	RestoreWindowPos(m_hWnd, _T("position"));

	SetWindowPos(&wndTopMost, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);

	m_Time.SetDraw3DBar(FALSE);

	short shBtnColor = 30;
	m_btnStart.SetIcon(IDI_START, (int)BTNST_AUTO_GRAY);
	m_btnStart.OffsetColor(CButtonST::BTNST_COLOR_BK_IN, shBtnColor);
	m_btnStart.SetTooltipText(_T("Start the timer"));

	m_btnReset.SetIcon(IDI_RESET, (int)BTNST_AUTO_GRAY);
	m_btnReset.OffsetColor(CButtonST::BTNST_COLOR_BK_IN, shBtnColor);
	m_btnReset.SetTooltipText(_T("Reset the timer"));

	m_btnMore.SetIcon(IDI_MORE, (int)BTNST_AUTO_GRAY);
	m_btnMore.OffsetColor(CButtonST::BTNST_COLOR_BK_IN, shBtnColor);
	m_btnMore.SetTooltipText(_T("Display more options"));

	OnReset();

	return TRUE;  // return TRUE  unless you set the focus to a control
}

//=============================================================================
void CXTimerDlg::SetWindowText()
//=============================================================================
{
	TCHAR *s = _T("");
	if (m_bTimer && m_nPlayOnce)
		s = _T("XTimer [TIMER+]");
	else if (m_bTimer)
		s = _T("XTimer [TIMER]");
	else
		s = _T("XTimer [STOPWATCH]");
	CDialog::SetWindowText(s);
}

//=============================================================================
void CXTimerDlg::LoadProfile()
//=============================================================================
{
	m_nTimerSeconds = theApp.GetProfileInt(_T("Options"),    _T("Seconds"), 60*10);
	m_bTimer        = theApp.GetProfileInt(_T("Options"),    _T("Timer"), FALSE);
	m_strSoundFile  = theApp.GetProfileString(_T("Options"), _T("SoundFile"), _T(""));
	m_bPlaySound    = theApp.GetProfileInt(_T("Options"),    _T("PlaySound"), TRUE);
	m_nPlayOnce     = theApp.GetProfileInt(_T("Options"),    _T("PlayOnce"), 0);
}

//=============================================================================
void CXTimerDlg::SaveProfile()
//=============================================================================
{
	theApp.WriteProfileInt(_T("Options"),    _T("Seconds"),   m_nTimerSeconds);
	theApp.WriteProfileInt(_T("Options"),    _T("Timer"),     m_bTimer);
	theApp.WriteProfileString(_T("Options"), _T("SoundFile"), m_strSoundFile);
	theApp.WriteProfileInt(_T("Options"),    _T("PlaySound"), m_bPlaySound);
	theApp.WriteProfileInt(_T("Options"),    _T("PlayOnce"),  m_nPlayOnce);
}

//=============================================================================
void CXTimerDlg::SaveWindowPos(HWND hWnd, LPCTSTR lpszEntry, UINT nShowCmd)
//=============================================================================
{
	ASSERT(hWnd != 0);
	if (hWnd == 0)
		return;
	ASSERT(::IsWindow(hWnd));
	if (!::IsWindow(hWnd))
		return;
	ASSERT(lpszEntry != NULL);
	if (lpszEntry == NULL)
		return;

	WINDOWPLACEMENT wp;
	wp.length = sizeof(WINDOWPLACEMENT);

	// get window position and iconized/maximized status
	::GetWindowPlacement(hWnd, &wp);

	if (wp.showCmd == SW_SHOWNORMAL)
	{
		wp.flags   = 0;
		wp.showCmd = nShowCmd;
	}
	else if (wp.showCmd == SW_SHOWMAXIMIZED)
	{
		wp.flags   = WPF_RESTORETOMAXIMIZED;
		wp.showCmd = SW_SHOWMAXIMIZED;
	}
	else if (wp.showCmd == SW_SHOWMINIMIZED)
	{
		wp.flags   = wp.flags ? WPF_RESTORETOMAXIMIZED : 0;
		wp.showCmd = SW_SHOWMINNOACTIVE;
	}
	else
	{
		wp.flags   = 0;
		wp.showCmd = SW_SHOWNORMAL;
	}

	TCHAR tmp[100];
	_stprintf(tmp, _T("%u,%u,%u,%u,%u,%u"),
		wp.flags,
		wp.showCmd,
		wp.rcNormalPosition.left,
		wp.rcNormalPosition.top,
		wp.rcNormalPosition.right,
		wp.rcNormalPosition.bottom);

	// save position
	theApp.WriteProfileString(_T("Window"), lpszEntry, tmp);
}

//=============================================================================
void CXTimerDlg::RestoreWindowPos(HWND hWnd, LPCTSTR lpszEntry)
//=============================================================================
{
	ASSERT(hWnd != 0);
	if (hWnd == 0)
		return;
	ASSERT(::IsWindow(hWnd));
	if (!::IsWindow(hWnd))
		return;
	ASSERT(lpszEntry != NULL);
	if (lpszEntry == NULL)
		return;

	// read window position
	CString s = _T("");

	s = theApp.GetProfileString(_T("Window"), lpszEntry,
			_T("0,1,400,400,400,400"));

	TCHAR tmp[200];
	_tcsncpy(tmp, s, sizeof(tmp)/sizeof(TCHAR)-1);

	// get WINDOWPLACEMENT info
	WINDOWPLACEMENT wp;
	memset(&wp, 0, sizeof(wp));
	wp.length = sizeof(WINDOWPLACEMENT);
	wp.rcNormalPosition.left = 0;
	wp.rcNormalPosition.top = 0;
	wp.ptMinPosition = CPoint(0, 0);
	wp.ptMaxPosition = CPoint(-::GetSystemMetrics(SM_CXBORDER),
		-::GetSystemMetrics(SM_CYBORDER));

	CRect rect;
	GetWindowRect(&rect);

	TCHAR *cp;
	if ((cp = _tcstok(tmp, _T(",\r\n"))) != NULL)
		wp.flags = _ttoi(cp);
	if ((cp = _tcstok(NULL, _T(",\r\n"))) != NULL)
		wp.showCmd = _ttoi(cp);
	if ((cp = _tcstok(NULL, _T(",\r\n"))) != NULL)
		wp.rcNormalPosition.left = _ttoi(cp);
	if ((cp = _tcstok(NULL, _T(",\r\n"))) != NULL)
		wp.rcNormalPosition.top = _ttoi(cp);
	wp.rcNormalPosition.right = wp.rcNormalPosition.left + rect.Width();
	wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + rect.Height();

	// sets window's position and iconized/maximized status
	::SetWindowPlacement(hWnd, &wp);
}

//=============================================================================
void CXTimerDlg::OnSysCommand(UINT nID, LPARAM lParam)
//=============================================================================
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}

//=============================================================================
void CXTimerDlg::OnPaint() 
//=============================================================================
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

//=============================================================================
HCURSOR CXTimerDlg::OnQueryDragIcon()
//=============================================================================
{
	return (HCURSOR) m_hIcon;
}

//=============================================================================
void CXTimerDlg::OnNcLButtonDblClk(UINT /*nHitTest*/, CPoint /*point*/) 
//=============================================================================
{
	// disable double-click in caption - disallow full-screen
	
	//CDialog::OnNcLButtonDblClk(nHitTest, point);
}

//=============================================================================
void CXTimerDlg::OnTimer(UINT nIDEvent) 
//=============================================================================
{
	if (nIDEvent == 1)
	{
		CHighTime curtime = CHighTime::GetPresentTime();
		CHighTimeSpan ts = curtime - m_StartTime;
		CString s = _T("");

		if (m_bTimer)
		{
			m_TimeLeft -= ts;
			s = m_TimeLeft.Format(TIME_DISPLAY_FORMAT);

			if (m_TimeLeft < CHighTimeSpan(0, 0, 0, 0, 100, 0, 0))
			{
				if (m_nPlayOnce)
				{
					TRACE(_T("switching to continuous play"));

					KillTimer(1);
					SetTimer(2, 30000, NULL);		// play sound every 30 seconds 
													// until stopped
					m_bContinuousPlay = TRUE;

					m_TimeLeft.SetHighTimeSpan(0, 0, 0, 0, 0, 0, 0);	// for timer
					CString s = _T("");
					s = m_TimeLeft.Format(TIME_DISPLAY_FORMAT);
					m_Time.Display(s);
					m_Time.UpdateWindow();

					PlaySoundFile();

					return;
				}

				OnStop();
				OnReset();
				PlaySoundFile();
				return;
			}
		}
		else
		{
			m_TimeElapsed += ts;
			s = m_TimeElapsed.Format(TIME_DISPLAY_FORMAT);
		}
		m_StartTime = curtime;
		m_Time.Display(s);
		m_Time.UpdateWindow();
	}
	else if (nIDEvent == 2)
	{
		PlaySoundFile();
	}
}

//=============================================================================
void CXTimerDlg::LoadMenu(CPoint point)
//=============================================================================
{
	CMenu menu;

	if (!m_bTimerStarted)
	{
		if (!menu.LoadMenu(IDR_CONTEXT1))
		{
			TRACE(_T("ERROR failed to load IDR_CONTEXT1\n"));
			return;
		}
		menu.CheckMenuRadioItem(ID_STOPWATCH, ID_TIMER, 
			m_bTimer ? ID_TIMER : ID_STOPWATCH, MF_BYCOMMAND);
	}
	else
	{
		if (!menu.LoadMenu(IDR_CONTEXT2))
		{
			TRACE(_T("ERROR failed to load IDR_CONTEXT2\n"));
			return;
		}
		menu.CheckMenuRadioItem(ID_STOPWATCH, ID_TIMER, 
		  m_bTimer ? ID_TIMER : ID_STOPWATCH, MF_BYCOMMAND);
	}

	menu.GetSubMenu(0)->TrackPopupMenu(0,
		point.x, point.y, this, NULL);
}

//=============================================================================
void CXTimerDlg::OnContextMenu(CWnd* /*pWnd*/, CPoint point) 
//=============================================================================
{
	LoadMenu(point);
	SetWindowText();
}

//=============================================================================
void CXTimerDlg::OnStart()
//=============================================================================
{
	if (m_bTimerStarted)
	{
		OnStop();
		return;
	}

	m_bTimerStarted = TRUE;

	m_btnStart.SetIcon(IDI_STOP, (int)BTNST_AUTO_GRAY);
	m_btnStart.SetWindowText(_T("Stop"));
	m_btnStart.SetTooltipText(_T("Stop the timer"));

	m_StartTime = CHighTime::GetPresentTime();

	SetTimer(1, 100, NULL);
}

//=============================================================================
void CXTimerDlg::OnStop()
//=============================================================================
{
	KillTimer(1);
	KillTimer(2);
	m_bTimerStarted = FALSE;

	if (m_bContinuousPlay)
		Reset();
		
	m_bContinuousPlay = FALSE;

	m_btnStart.SetIcon(IDI_START, (int)BTNST_AUTO_GRAY);
	m_btnStart.SetWindowText(_T("Start"));
	m_btnStart.SetTooltipText(_T("Start the timer"));
}

//=============================================================================
void CXTimerDlg::Reset()
//=============================================================================
{
	m_TimeLeft.SetHighTimeSpan(0, 0, 0, m_nTimerSeconds, 0, 0, 0);	// for timer
	m_TimeElapsed.SetHighTimeSpan(0, 0, 0, 0, 0, 0, 0);				// for stopwatch

	CString s = _T("");

	if (m_bTimer)
		s = m_TimeLeft.Format(TIME_DISPLAY_FORMAT);
	else
		s = m_TimeElapsed.Format(TIME_DISPLAY_FORMAT);

	m_Time.Display(s);
	m_Time.UpdateWindow();
}

//=============================================================================
void CXTimerDlg::OnReset()
//=============================================================================
{
	OnStop();
	Reset();
}

//=============================================================================
void CXTimerDlg::OnMore() 
//=============================================================================
{
	CRect rectButton;
	m_btnMore.GetWindowRect(&rectButton);
	CPoint point(rectButton.left, rectButton.bottom);

	LoadMenu(point);
}

//=============================================================================
void CXTimerDlg::OnXTimer() 
//=============================================================================
{
	m_bTimer = TRUE;
	SetWindowText();
	OnReset();
}

//=============================================================================
void CXTimerDlg::OnStopwatch() 
//=============================================================================
{
	m_bTimer = FALSE;
	SetWindowText();
	OnReset();
}

//=============================================================================
void CXTimerDlg::OnTimerOptions()
//=============================================================================
{
	OnStop();

	CTimerOptions dlg;

	dlg.m_bPlaySound = m_bPlaySound;
	dlg.m_nPlayOnce = m_nPlayOnce;
	dlg.m_strSoundFile  = m_strSoundFile;

	int nSeconds = m_nTimerSeconds;
	dlg.m_nHours = nSeconds / (60 * 60);
	nSeconds -= dlg.m_nHours * 60 * 60;
	dlg.m_nMinutes = nSeconds / 60;
	nSeconds -= dlg.m_nMinutes * 60;
	dlg.m_nSeconds = nSeconds;

	if (dlg.DoModal() == IDOK)
	{
		m_bPlaySound    = dlg.m_bPlaySound;
		m_nPlayOnce     = dlg.m_nPlayOnce;
		m_strSoundFile  = dlg.m_strSoundFile;
		m_nTimerSeconds = dlg.m_nHours * 60 * 60 +
						  dlg.m_nMinutes * 60 +
						  dlg.m_nSeconds;
		SetWindowText();
	}

	OnReset();
}

//=============================================================================
void CXTimerDlg::OnAbout() 
//=============================================================================
{
	CAboutDlg dlg;
	dlg.DoModal();
}

//=============================================================================
void CXTimerDlg::PlaySoundFile()
//=============================================================================
{
	FLASHWINFO fwi;
	fwi.cbSize    = sizeof(fwi);
	fwi.hwnd      = m_hWnd;
	fwi.dwFlags   = FLASHW_ALL | FLASHW_TIMER;
	fwi.uCount    = 5;
	fwi.dwTimeout = 500;
	::FlashWindowEx(&fwi);

	if (m_bPlaySound)
	{
		if (_taccess(m_strSoundFile, 04) == 0)
		{
			::PlaySound(m_strSoundFile, AfxGetApp()->m_hInstance,
				SND_FILENAME|SND_ASYNC|SND_NOWAIT|SND_NODEFAULT);
		}
		else
		{
			TRACE(_T("The sound file '%s' cannot be played, playing default sound\n"), 
				m_strSoundFile);
			::PlaySound(_T("IDR_DEFAULTSOUND"), AfxGetApp()->m_hInstance,
				SND_RESOURCE|SND_ASYNC|SND_NOWAIT|SND_NODEFAULT);
		}
	}
}

//=============================================================================
void CXTimerDlg::OnDestroy() 
//=============================================================================
{
	SaveProfile();
	SaveWindowPos(m_hWnd, _T("position"), SW_SHOW);
	CDialog::OnDestroy();
}

//=============================================================================
void CXTimerDlg::OnLButtonDblClk(UINT nFlags, CPoint point) 
//=============================================================================
{
	OnTimerOptions();
	CDialog::OnLButtonDblClk(nFlags, point);
}

//=============================================================================
void CXTimerDlg::OnQuickTimer(UINT nID)
//=============================================================================
{
	switch (nID)
	{
		default:
		case ID_ONE_MINUTE:			m_nTimerSeconds = 1 * ONE_MINUTE; break;
		case ID_TWO_MINUTES:		m_nTimerSeconds = 2 * ONE_MINUTE; break;
		case ID_FIVE_MINUTES:		m_nTimerSeconds = 5 * ONE_MINUTE; break;
		case ID_TEN_MINUTES:		m_nTimerSeconds = 10 * ONE_MINUTE; break;
		case ID_FIFTEEN_MINUTES:	m_nTimerSeconds = 15 * ONE_MINUTE; break;
		case ID_TWENTY_MINUTES:		m_nTimerSeconds = 20 * ONE_MINUTE; break;
		case ID_THIRTY_MINUTES:		m_nTimerSeconds = 30 * ONE_MINUTE; break;
		case ID_FORTYFIVE_MINUTES:	m_nTimerSeconds = 45 * ONE_MINUTE; break;
		case ID_ONE_HOUR:			m_nTimerSeconds = 1 * ONE_HOUR; break;
		case ID_TWO_HOURS:			m_nTimerSeconds = 2 * ONE_HOUR; break;
		case ID_THREE_HOURS:		m_nTimerSeconds = 3 * ONE_HOUR; break;
		case ID_FOUR_HOURS:			m_nTimerSeconds = 4 * ONE_HOUR; break;
		case ID_FIVE_HOURS:			m_nTimerSeconds = 5 * ONE_HOUR; break;
		case ID_SIX_HOURS:			m_nTimerSeconds = 6 * ONE_HOUR; break;
		case ID_SEVEN_HOURS:		m_nTimerSeconds = 7 * ONE_HOUR; break;
		case ID_EIGHT_HOURS:		m_nTimerSeconds = 8 * ONE_HOUR; break;
	}

	m_bTimer = TRUE;
	SetWindowText();
	OnReset();
	OnStart();
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see www.hdsoft.org.






Comments and Discussions