Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

3D Thermometer Control

, 2 Feb 2007
3D Thermometer Control Drawn with GDI Plus
thermometerctrltest_app.zip
ThermometerCtrlTest.exe
thermometerctrltest_src.zip
ThermometerCtrlTest
ThermometerCtrlTest.aps
ThermometerCtrlTest.ico
ThermometerCtrlTest.manifest
ThermometerCtrlTest.ncb
ThermometerCtrlTest.suo
//==============================================================================
// TempCtrl.cpp : Thermometer Control (GDI+) 
//==============================================================================
#include "stdafx.h"
#include "TempCtrl.h"
#include ".\tempctrl.h"

// Fahrenheit To Celsius
static inline double F2C(double dF){return((dF - 32) / 1.8);}
// Celsius To Fahrenheit 
static inline double C2F(double dC){return((dC * 1.8) + 32);}


BEGIN_MESSAGE_MAP(CTempCtrl, CStatic)
	ON_WM_PAINT()
END_MESSAGE_MAP()

IMPLEMENT_DYNAMIC(CTempCtrl, CStatic)

//=================================================
// Construction / Destruction
//=================================================
CTempCtrl::CTempCtrl()
{
	m_dRangeMin = -20;
	m_dRangeMax = 120;
	m_dVal = 0;
	m_bDrawTics	= TRUE;
	m_iSmallTicFreq	= 5;
	m_iLargeTicFreq	= 20;
	m_bDisplayF = TRUE;
	m_clrFore = RGB(255,64,64);
}

CTempCtrl::~CTempCtrl()
{
}

//=================================================
// Overrides
//=================================================
BOOL CTempCtrl::PreCreateWindow(CREATESTRUCT& cs)
{
	cs.style |= SS_OWNERDRAW;
	return CStatic::PreCreateWindow(cs);
}

void CTempCtrl::PreSubclassWindow()
{
	ModifyStyle(0, SS_OWNERDRAW);
	CStatic::PreSubclassWindow();
}

void CTempCtrl::DrawItem(LPDRAWITEMSTRUCT /*pDI*/)
{
	// Drawn in OnPaint()
}

//=================================================
// Windows Message Handlers
//=================================================
void CTempCtrl::OnPaint()
{
	CPaintDC dc(this); 
	Graphics gfx(dc.m_hDC);
	CRect rClient;
	GetClientRect(rClient);
	CRect rCtrl = rClient;
	rCtrl.InflateRect(-5,-5,-5,-5);

	// Paint the background with the parent's background color
	//HBRUSH hBackBrush = (HBRUSH)GetParent()->SendMessage(
	//	WM_CTLCOLORSTATIC, (WPARAM)dc.GetSafeHdc(), (LPARAM)m_hWnd);
	//if(hBackBrush)
	//	::FillRect(dc.GetSafeHdc(), rClient, hBackBrush);

	// Double-buffer
	Bitmap bmMem(rCtrl.Width(), rCtrl.Height());
	Graphics gfxMem(&bmMem);
	gfxMem.SetSmoothingMode(SmoothingModeAntiAlias);

	// Erase background
	Color clrBackground;
	clrBackground.SetFromCOLORREF(::GetSysColor(COLOR_BTNFACE));
	gfxMem.Clear(clrBackground);

	// Create GDI+ objects
	Color clrFore, clrBack, clrScale, clrOutline;
	clrFore.SetFromCOLORREF(m_clrFore);
	clrBack.SetFromCOLORREF(::GetSysColor(COLOR_3DFACE));
	clrScale.SetFromCOLORREF(RGB(0,0,0));
	clrOutline.SetFromCOLORREF(RGB(64,0,0));
	Pen fgPen(clrFore);
	Pen	scalePen(clrScale);
	Pen outlinePen(clrOutline);
	SolidBrush blackBrush(Color(255, 0, 0, 0));
	SolidBrush fillBrush(clrBack);
	Font fntText(L"Arial", 10, FontStyleBold);
	StringFormat strfmtText;
	strfmtText.SetAlignment(StringAlignmentCenter);
	strfmtText.SetLineAlignment(StringAlignmentCenter);
	
	// Everything is drawn from center
	Point ptCenter(rCtrl.CenterPoint().x, rCtrl.CenterPoint().y);
	float fTmpWidth = (float)(rCtrl.Width() / 5.0);

	// Get bulb dimensions
	RectF rBulb((float)ptCenter.X - (float)fTmpWidth,	
		(float)rCtrl.bottom - (float)((fTmpWidth*2) + 25),
		(float)fTmpWidth*2, (float)fTmpWidth*2);

	// Draw the bulb
	LinearGradientBrush brKnob(rBulb, 
		OffsetColor(clrFore, 55), 
		OffsetColor(clrFore, -55), 
		LinearGradientModeHorizontal);
	gfxMem.FillEllipse(&brKnob, rBulb);                         
	gfxMem.DrawEllipse(&outlinePen, rBulb);

	// Get cylinder coordinates
	RectF rCylinder(
		(float)ptCenter.X - (float)fTmpWidth/2,	
		(float)rCtrl.top + (m_bDrawTics ? 25 : 10), // 25 pixels on top for F/C
		(float)fTmpWidth, 
		(float)rBulb.GetTop() - rCtrl.top - (m_bDrawTics ? 20 : 5)); // 5 pixel overlap over bulb

	// Make sure we have positive values to work with
	float fRange = (float)(m_dRangeMax - m_dRangeMin);
	float fVal = (float)m_dVal;
	if(m_dRangeMin < 0)
		fVal += (float)abs((int)m_dRangeMin);

	// Draw the cylinder
	FillCylinder(&gfxMem, rCylinder, &fillBrush, clrOutline);
	if(fVal > 0) 
	{
		// Calculate full rectangle
		float fPctFull = ((fVal / fRange) * 100);
		float fPixFull = ((rCylinder.Height / 100) * fPctFull);
		RectF rFull(
			(float)rCylinder.GetLeft(), 
			(float)rCylinder.GetBottom() - fPixFull, 
			(float)rCylinder.Width, (float)fPixFull);
		FillCylinder(&gfxMem, rFull, &brKnob, clrOutline);
	}

	// Outline top (empty) plane
	RectF rEmptyTopPlane(rCylinder.X, rCylinder.Y - 5, rCylinder.Width, 5);
	gfxMem.DrawEllipse(&outlinePen, rEmptyTopPlane);

	if(m_bDrawTics)
	{
		Font fntMark(L"Arial", 7);//(fMarkFreq - 2));
		StringFormat strfmtMark;
		strfmtMark.SetAlignment(StringAlignmentFar);
		strfmtMark.SetLineAlignment(StringAlignmentCenter);
		wchar_t strDegree[10];
		Point ptStart, ptEnd;
		PointF ptText;
		
		// The range and the values are in fahrenheit
		float fPixPerDegree = rCylinder.Height / fRange;
		float fMarkFreq = fPixPerDegree * m_iLargeTicFreq;
		long lMarkVal = (long)m_dRangeMax;

		// Draw large marks and text
		for(float y = rCylinder.GetTop(); y <= rCylinder.GetBottom(); y += fMarkFreq)
		{
			ptStart = Point((int)rCylinder.GetRight() + 3, (int)y);
			ptEnd = Point((int)rCylinder.GetRight() + 10, (int)y);
			gfxMem.DrawLine(&scalePen, ptStart, ptEnd);
			swprintf(strDegree, L"%d", lMarkVal);
			ptText = PointF((float)rCylinder.GetRight() + 30, (float)y);
			gfxMem.DrawString(strDegree, (int)wcslen(strDegree), 
				&fntMark, ptText, &strfmtMark, &blackBrush);
			lMarkVal -= m_iLargeTicFreq;
		}

		// Draw small marks
		fMarkFreq = fPixPerDegree * m_iSmallTicFreq;
		for(y = rCylinder.GetTop(); y <= rCylinder.GetBottom(); y += fMarkFreq)
		{
			ptStart = Point((int)rCylinder.GetRight() + 3, (int)y);
			ptEnd = Point((int)rCylinder.GetRight() + 8, (int)y);
			gfxMem.DrawLine(&scalePen, ptStart, ptEnd);
		}

		// The range and the values are stored in fahrenheit but we must draw celsius too
		fRange = (float)(F2C(m_dRangeMax) - F2C(m_dRangeMin));
		fPixPerDegree = rCylinder.Height / fRange;
		fMarkFreq = fPixPerDegree * m_iLargeTicFreq;
		lMarkVal = (long)F2C(m_dRangeMax);
		// Round celsius value to a multiple of 10 
		int iMod = lMarkVal % 10;
		//if(iMod != 0)
		//	iMod = (10 - iMod);
		lMarkVal -= iMod;
		// Get vertical pixel offset
		int iCYPix = (int)(fPixPerDegree * iMod);

		// Draw large marks and text
		for(float y = rCylinder.GetTop(); y <= rCylinder.GetBottom(); y += fMarkFreq)
		{
			int iCy = (int)y + iCYPix;
			if(iCy > rCylinder.GetBottom())
				break;
			ptStart = Point((int)rCylinder.GetLeft() - 10, (int)iCy);
			ptEnd = Point((int)rCylinder.GetLeft() - 3, (int)iCy);
			gfxMem.DrawLine(&scalePen, ptStart, ptEnd);
			swprintf(strDegree, L"%d", lMarkVal);
			gfxMem.DrawString(strDegree, (int)wcslen(strDegree), 
				&fntMark, PointF((float)rCylinder.GetLeft() - 15, 
				(float)iCy), &strfmtMark, &blackBrush);
			lMarkVal -= m_iLargeTicFreq;
		}

		// Draw small marks every m_iSmallTicFreq degrees
		fMarkFreq = fPixPerDegree * m_iSmallTicFreq;
		for(y = rCylinder.GetTop(); y <= rCylinder.GetBottom(); y += fMarkFreq)
		{
			int iCy = (int)y + iCYPix;
			if(iCy > rCylinder.GetBottom())
				break;
			ptStart = Point((int)rCylinder.GetLeft() - 8, (int)iCy);
			ptEnd = Point((int)rCylinder.GetLeft() - 3, (int)iCy);
			gfxMem.DrawLine(&scalePen, ptStart, ptEnd);
		}
	
		// Draw F/C
		RectF rText = RectF((float)ptCenter.X + 20,
			(float)rCtrl.top, (float)20, (float)20);
		gfxMem.DrawString(L"F", 1, &fntText, rText, &strfmtText, &blackBrush);
		rText = RectF((float)ptCenter.X - 40,
			(float)rCtrl.top, (float)20, (float)20);
		gfxMem.DrawString(L"C", 1, &fntText, rText, &strfmtText, &blackBrush);
	}

	// Draw the value
	RectF rText((float)rCtrl.left, (float)rBulb.GetBottom() + 5,
		(float)rCtrl.Width(), (float)(rCtrl.bottom - (rBulb.GetBottom() + 5)));
	wchar_t strValue[10];
	if(m_bDisplayF)
		swprintf(strValue, L"%.01f �F", m_dVal);
	else
		swprintf(strValue, L"%.01f �C", F2C(m_dVal));
	gfxMem.DrawString(strValue, (int)wcslen(strValue), 
		&fntText, rText, &strfmtText, &blackBrush);

	// Update screen                    
	gfx.DrawImage(&bmMem, 0, 0);
}

void CTempCtrl::FillCylinder(Graphics* pGfx, RectF rCtrl, 
	Brush* pFillBrush, Color cOutlineColor)
{
	RectF rTopPlane(rCtrl.X, rCtrl.Y - 5, rCtrl.Width, 5);
	RectF rBottomPlane(rCtrl.X, rCtrl.GetBottom() - 5, rCtrl.Width, 5);
	// Outline pen
	Pen penOutline(cOutlineColor);
	// Draw body
	GraphicsPath gfxPath;
	gfxPath.AddArc(rTopPlane, 0, 180); 
	gfxPath.AddArc(rBottomPlane, 180, -180); 
	gfxPath.CloseFigure();
	// Fill body
	pGfx->FillPath(pFillBrush, &gfxPath);
	// Outline body
	pGfx->DrawPath(&penOutline, &gfxPath);
	// Draw top plane
	gfxPath.Reset(); 
	gfxPath.AddEllipse(rTopPlane); 
	// Fill top plane 
	pGfx->FillPath(pFillBrush, &gfxPath); 
	// Outline top plane 
	pGfx->DrawPath(&penOutline, &gfxPath);
} 

//=================================================
// Implementation
//=================================================
void CTempCtrl::GetRange(double& dMin, double& dMax)
{
	dMin = m_dRangeMin;
	dMax = m_dRangeMax;
}

void CTempCtrl::SetRange(double dMin, double dMax, BOOL bRedraw/*=TRUE*/)
{
	m_dRangeMin = dMin;
	m_dRangeMax = dMax;
	if(GetSafeHwnd() && bRedraw)
		Invalidate();
}

void CTempCtrl::SetRangeMin(double dMin, BOOL bRedraw/*=TRUE*/)
{
	m_dRangeMin = dMin;
	if(GetSafeHwnd() && bRedraw)
		Invalidate();
}

void CTempCtrl::SetRangeMax(double dMax, BOOL bRedraw/*=TRUE*/)
{
	m_dRangeMax = dMax;
	if(GetSafeHwnd() && bRedraw)
		Invalidate();
}

void CTempCtrl::SetPos(double dPos, BOOL bRedraw/*=TRUE*/)
{
	if(dPos > m_dRangeMax)
		dPos = m_dRangeMax;
	if(dPos < m_dRangeMin)
		dPos = m_dRangeMin;
	m_dVal = dPos;
	if(GetSafeHwnd() && bRedraw)
		Invalidate();
}

//=================================================
// Helper Functions
//=================================================
Color CTempCtrl::OffsetColor(Color clr, short sOffset)
{
	BYTE bRed = 0;
	BYTE bGreen = 0;
	BYTE bBlue = 0;
	short sOffsetR = sOffset;
	short sOffsetG = sOffset;
	short sOffsetB = sOffset;

	if((sOffset < -255) || (sOffset > 255))	
		return clr;

	// Get RGB components of specified color
	bRed = clr.GetR();
	bGreen = clr.GetG();
	bBlue = clr.GetB();

	// Calculate max. allowed real offset
	if(sOffset > 0)
	{
		if((bRed + sOffset) > 255)
			sOffsetR = (255 - bRed);
		if((bGreen + sOffset) > 255)
			sOffsetG = (255 - bGreen);
		if((bBlue + sOffset) > 255)	
			sOffsetB = (255 - bBlue);

		sOffset = min(min(sOffsetR, sOffsetG), sOffsetB);
	}
	else
	{
		if((bRed + sOffset) < 0)
			sOffsetR = -bRed;
		if((bGreen + sOffset) < 0)
			sOffsetG = -bGreen;
		if((bBlue + sOffset) < 0)
			sOffsetB = -bBlue;

		sOffset = max(max(sOffsetR, sOffsetG), sOffsetB);
	}

	return Color(clr.GetAlpha(), (BYTE)(bRed + sOffset), 
		(BYTE)(bGreen + sOffset), (BYTE)(bBlue + sOffset));
}

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

Share

About the Author

JJMatthews
Software Developer
United States United States
No Biography provided

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 2 Feb 2007
Article Copyright 2007 by JJMatthews
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid