MFC Analog Clock Example

This is a very nice example of a MFC analog clock application courtesy of Programming Windows with MFC 2nd Ed. by Jeff Prosise. Compare it to the .NET Analog Clock. Download the complete source code: MFCCLOCK.zip

#include <afxwin.h>
#include <math.h>
#include "Clock.h"
#include "Resource.h"

#define SQUARESIZE	 20
#define ID_TIMER_CLOCK	1

CClockApp theApp;

BOOL CClockApp::InitInstance()
{
	m_pMainWnd = new CMainWnd;
	if(!((CMainWnd*)m_pMainWnd)->RestoreWindowState())
		m_pMainWnd->ShowWindow(m_nCmdShow);
	m_pMainWnd->UpdateWindow();
	return TRUE;
}

//SOME IMPORTANT MFC MACROS THAT WE ARE USING -- DO NOT REMOVE
BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd)
	ON_WM_CREATE()
	ON_WM_PAINT()
	ON_WM_TIMER()
	ON_WM_GETMINMAXINFO()
	ON_WM_NCHITTEST()
	ON_WM_SYSCOMMAND()
	ON_WM_CONTEXTMENU()
	ON_WM_ENDSESSION()
	ON_WM_CLOSE()
END_MESSAGE_MAP()

CMainWnd::CMainWnd()
{
	this->m_bAutoMenuEnable = FALSE;

	CTime time = CTime::GetCurrentTime();
	this->m_nPrevSecond = time.GetSecond();
	this->m_nPrevMinute = time.GetMinute();
	this->m_nPrevHour = time.GetHour() % 12; // 24 hour clock

	Create(AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW,
		theApp.LoadStandardCursor(IDC_ARROW),
		(HBRUSH)(COLOR_3DFACE+1),
		theApp.LoadIcon(IDI_APPICON)), _T("Clock"));
}

BOOL CMainWnd::PreCreateWindow(CREATESTRUCT &cs)
{
	if(!CFrameWnd::PreCreateWindow(cs))
		return FALSE;
	cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
	return TRUE;
}

int CMainWnd::OnCreate(LPCREATESTRUCT lpcs)
{
	if(CFrameWnd::OnCreate(lpcs) == -1)
		return -1;

	//set timer
	if(!SetTimer(ID_TIMER_CLOCK, 1000, NULL))
	{
		MessageBox(_T("SetTimer() failed."), _T("ERROR"), MB_ICONSTOP|MB_OK);
		return -1;
	}

	CMenu *pMenu = GetSystemMenu(FALSE);
	pMenu->AppendMenu(MF_SEPARATOR);
	pMenu->AppendMenu(MF_STRING, IDM_SYSMENU_FULL_WINDOW, _T("Remove &Title"));
	pMenu->AppendMenu(MF_STRING, IDM_SYSMENU_STAY_ON_TOP, _T("Stay on To&p"));
	return 0;
}

void CMainWnd::OnClose()
{
	SaveWindowState();
	KillTimer(ID_TIMER_CLOCK);
	CFrameWnd::OnClose();
}

void CMainWnd::OnEndSession(BOOL bEnding)
{
	if(bEnding)
		SaveWindowState();
	CFrameWnd::OnEndSession(bEnding);
}

void CMainWnd::OnGetMinMaxInfo(MINMAXINFO *pMMI)
{
	pMMI->ptMinTrackSize.x = 120;
	pMMI->ptMinTrackSize.y = 120;
}

LRESULT CMainWnd::OnNcHitTest(CPoint point)
{
	LRESULT nHitTest = CFrameWnd::OnNcHitTest(point);
	if((nHitTest == HTCLIENT) && (::GetAsyncKeyState(MK_LBUTTON) < 0))
		nHitTest = HTCAPTION;
	return nHitTest;
}

void CMainWnd::OnSysCommand(UINT nID, LPARAM lParam)
{
	UINT nMaskedID = nID & 0xFFF0;

	if(nMaskedID == IDM_SYSMENU_FULL_WINDOW)
	{
		this->m_bFullWindow = this->m_bFullWindow ? 0 : 1;
		this->SetTitleBarState();
		return;
	}
	else if(nMaskedID == IDM_SYSMENU_STAY_ON_TOP)
	{
		this->m_bStayOnTop = this->m_bStayOnTop ? 0 : 1;
		this->SetTopMostState();
	}
	CFrameWnd::OnSysCommand(nID, lParam);
}

void CMainWnd::OnContextMenu(CWnd *pWnd, CPoint point)
{
	CRect rect;
	this->GetClientRect(&rect);
	this->ClientToScreen(&rect);
	if(rect.PtInRect(point))
	{
		CMenu *pMenu = this->GetSystemMenu(FALSE);
		UpdateSystemMenu(pMenu);

		int nID = (int)pMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON|TPM_RETURNCMD, point.x, point.y, this);

		if(nID > 0)
			this->SendMessage(WM_SYSCOMMAND, nID, 0);

		return;
	}
	CFrameWnd::OnContextMenu(pWnd, point);
}

void CMainWnd::OnTimer(UINT nTimerID)
{
	if(this->IsIconic())
		return;

	CTime time = CTime::GetCurrentTime();
	int nSecond = time.GetSecond();
	int nMinute = time.GetMinute();
	int nHour = time.GetHour() % 12;

	if((nSecond == this->m_nPrevSecond) && 
		(nMinute == this->m_nPrevMinute) && 
		(nHour == this->m_nPrevHour))
		return;

	CRect rect;
	this->GetClientRect(&rect);

	CClientDC dc(this);
	dc.SetMapMode(MM_ISOTROPIC);
	dc.SetWindowExt(1000, 1000);
	dc.SetViewportExt(rect.Width(), -rect.Height());
	dc.SetViewportOrg(rect.Width()/2, rect.Height()/2);

	COLORREF clrColor = ::GetSysColor(COLOR_3DFACE);

	if(nMinute != this->m_nPrevMinute)
	{
		this->DrawHand(&dc, 200, 4, (this->m_nPrevHour * 30) + (this->m_nPrevMinute / 2), clrColor);
		this->DrawHand(&dc, 400, 8, this->m_nPrevMinute * 6, clrColor);
		this->m_nPrevMinute = nMinute;
		this->m_nPrevHour = nHour;
	}

	if(nSecond != this->m_nPrevSecond)
	{
		this->DrawSecondHand(&dc, 400, 8, this->m_nPrevSecond * 6, clrColor);
		this->DrawSecondHand(&dc, 400, 8, nSecond * 6, RGB(0,0,0));
		this->DrawHand(&dc, 200, 4, (nHour * 30) + (nMinute / 2), RGB(0,0,0));
		this->DrawHand(&dc, 400, 8, nMinute * 6, RGB(0,0,0));
		this->m_nPrevSecond = nSecond;
	}
}

void CMainWnd::OnPaint()
{
	CRect rect;
	this->GetClientRect(&rect);

	CPaintDC dc(this);
	dc.SetMapMode(MM_ISOTROPIC);
	dc.SetWindowExt(1000, 1000);
	dc.SetViewportExt(rect.Width(), -rect.Height());
	dc.SetViewportOrg(rect.Width()/2, rect.Height()/2);

	this->DrawClockFace(&dc);
	this->DrawHand(&dc, 200, 4, (this->m_nPrevHour * 30) + (this->m_nPrevMinute / 2), RGB(0,0,0));
	this->DrawHand(&dc, 400, 8, this->m_nPrevMinute * 6, RGB(0,0,0));
	this->DrawSecondHand(&dc, 400, 8, this->m_nPrevSecond * 6, RGB(0,0,0));
}

void CMainWnd::DrawClockFace(CDC *pDC)
{
	static CPoint point[12] = {
		CPoint(	 0, 450),
		CPoint( 225, 390),
		CPoint( 390, 225),
		CPoint( 450,	 0),
		CPoint( 390,-225),
		CPoint( 225,-390),
		CPoint(	 0,-450),
		CPoint(-225,-390),
		CPoint(-390,-225),
		CPoint(-450,	 0),
		CPoint(-390, 225),
		CPoint(-225, 390),
	};

	pDC->SelectStockObject(NULL_BRUSH);
	for(int i = 0; i < 12; i++)
		pDC->Rectangle(
		point[i].x - SQUARESIZE,
		point[i].y + SQUARESIZE,
		point[i].x + SQUARESIZE,
		point[i].y - SQUARESIZE);
}

void CMainWnd::DrawHand(CDC *pDC, int nLength, int nScale, int nDegrees, COLORREF clrColor)
{
	CPoint point[4];
	double nRadians = (double)nDegrees * 0.017453292;
	
	point[0].x = (int)(nLength * sin(nRadians));
	point[0].y = (int)(nLength * cos(nRadians));

	point[2].x = -point[0].x / nScale;
	point[2].y = -point[0].y / nScale;

	point[1].x = -point[2].y;
	point[1].y = point[2].x;

	point[3].x = -point[1].x;
	point[3].y = -point[1].y;

	CPen pen(PS_SOLID, 2, clrColor);
	CPen *pOldPen = pDC->SelectObject(&pen);
	pDC->MoveTo(point[0]);
	pDC->LineTo(point[1]);
	pDC->LineTo(point[2]);
	pDC->LineTo(point[3]);
	pDC->LineTo(point[0]);
	pDC->SelectObject(pOldPen);
}

void CMainWnd::DrawSecondHand(CDC *pDC, int nLength, int nScale, int nDegrees, COLORREF clrColor)
{
	CPoint point[2];
	double nRadians = (double)nDegrees * 0.017453292;

	point[0].x = (int)(nLength * sin(nRadians));
	point[0].y = (int)(nLength * cos(nRadians));

	point[1].x = -point[0].x / nScale;
	point[1].y = -point[0].y / nScale;

	CPen pen(PS_SOLID, 2, clrColor);
	CPen *pOldPen = pDC->SelectObject(&pen);
	pDC->MoveTo(point[0]);
	pDC->LineTo(point[1]);
	pDC->SelectObject(pOldPen);
}

void CMainWnd::SetTitleBarState()
{
	CMenu *pMenu = this->GetSystemMenu(FALSE);

	if(this->m_bFullWindow)
	{
		this->ModifyStyle(WS_CAPTION, 0);
		pMenu->ModifyMenu(IDM_SYSMENU_FULL_WINDOW, MF_STRING,
			IDM_SYSMENU_FULL_WINDOW, _T("Restore &Title"));
	}
	else
	{
		this->ModifyStyle(0, WS_CAPTION);
		pMenu->ModifyMenu(IDM_SYSMENU_FULL_WINDOW, MF_STRING,
			IDM_SYSMENU_FULL_WINDOW, _T("Remove &Title"));
	}
	SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_DRAWFRAME);
}

void CMainWnd::SetTopMostState()
{
	CMenu *pMenu = this->GetSystemMenu(FALSE);

	if(this->m_bStayOnTop)
	{
		this->SetWindowPos(&wndTopMost, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
		pMenu->CheckMenuItem(IDM_SYSMENU_STAY_ON_TOP, MF_CHECKED);
	}
	else
	{
		this->SetWindowPos(&wndNoTopMost, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
		pMenu->CheckMenuItem(IDM_SYSMENU_STAY_ON_TOP, MF_UNCHECKED);
	}
}

BOOL CMainWnd::RestoreWindowState()
{
	CString version = _T("Version 1.0");
	this->m_bFullWindow = theApp.GetProfileInt(version, _T("FullWindow"), 0);
	this->SetTitleBarState();
	this->m_bStayOnTop = theApp.GetProfileInt(version, _T("StayOnTop"), 0);
	this->SetTopMostState();

	WINDOWPLACEMENT wp;
	wp.length = sizeof(wp);
	this->GetWindowPlacement(&wp);

	if(((wp.flags = theApp.GetProfileInt(version, _T("flags"), -1)) != -1) && 
		((wp.showCmd = theApp.GetProfileInt(version, _T("showCmd"), -1)) != -1) && 
		((wp.rcNormalPosition.left = theApp.GetProfileInt(version, _T("x1"), -1)) != -1) &&
		((wp.rcNormalPosition.top = theApp.GetProfileInt(version, _T("y1"), -1)) != -1) &&
		((wp.rcNormalPosition.right = theApp.GetProfileInt(version, _T("x2"), -1)) != -1) &&
		((wp.rcNormalPosition.bottom = theApp.GetProfileInt(version, _T("y2"), -1)) != -1))
	{
		wp.rcNormalPosition.left = min(wp.rcNormalPosition.left, ::GetSystemMetrics(SM_CXSCREEN) - ::GetSystemMetrics(SM_CXICON));
		wp.rcNormalPosition.top = min(wp.rcNormalPosition.top, ::GetSystemMetrics(SM_CYSCREEN) - ::GetSystemMetrics(SM_CYICON));
		SetWindowPlacement(&wp);
		return TRUE;
	}
	return FALSE;
}

void CMainWnd::SaveWindowState()
{
	CString version = _T("Version 1.0");
	theApp.WriteProfileInt(version, _T("FullWindow"), this->m_bFullWindow);
	theApp.WriteProfileInt(version, _T("StayOnTop"), this->m_bStayOnTop);

	WINDOWPLACEMENT wp;
	wp.length = sizeof(wp);
	this->GetWindowPlacement(&wp);

	theApp.WriteProfileInt(version, _T("flags"), wp.flags);
	theApp.WriteProfileInt(version, _T("showCmd"), wp.showCmd);
	theApp.WriteProfileInt(version, _T("x1"), wp.rcNormalPosition.left);
	theApp.WriteProfileInt(version, _T("y1"), wp.rcNormalPosition.top);
	theApp.WriteProfileInt(version, _T("x2"), wp.rcNormalPosition.right);
	theApp.WriteProfileInt(version, _T("y2"), wp.rcNormalPosition.bottom);
}

void CMainWnd::UpdateSystemMenu(CMenu *pMenu)
{
	static UINT nState[2][5] = {
		{ MFS_GRAYED, MFS_ENABLED, MFS_ENABLED, MFS_ENABLED, MFS_DEFAULT },
		{ MFS_DEFAULT, MFS_GRAYED, MFS_GRAYED, MFS_ENABLED, MFS_GRAYED }
	};
	
	if(this->IsIconic())
		return;

	int i = 0;
	if(this->IsZoomed())
		i = 1;
	
	CString strMenuText;
	pMenu->GetMenuString(SC_RESTORE, strMenuText, MF_BYCOMMAND);
	pMenu->ModifyMenu(SC_RESTORE, MF_STRING|nState[i][0], SC_RESTORE, strMenuText);

	pMenu->GetMenuString(SC_MOVE, strMenuText, MF_BYCOMMAND);
	pMenu->ModifyMenu(SC_MOVE, MF_STRING|nState[i][1], SC_MOVE, strMenuText);

	pMenu->GetMenuString(SC_SIZE, strMenuText, MF_BYCOMMAND);
	pMenu->ModifyMenu(SC_SIZE, MF_STRING|nState[i][2], SC_SIZE, strMenuText);

	pMenu->GetMenuString(SC_MINIMIZE, strMenuText, MF_BYCOMMAND);
	pMenu->ModifyMenu(SC_MINIMIZE, MF_STRING|nState[i][3], SC_MINIMIZE, strMenuText);

	pMenu->GetMenuString(SC_MAXIMIZE, strMenuText, MF_BYCOMMAND);
	pMenu->ModifyMenu(SC_MAXIMIZE, MF_STRING|nState[i][4], SC_MAXIMIZE, strMenuText);

	::SetMenuDefaultItem(pMenu->m_hMenu, i ? SC_RESTORE : SC_MAXIMIZE, FALSE);
}