www.pudn.com > CTableDemo.rar > 3DMeterCtrl.cpp


// 3DMeterCtrl.cpp : implementation file 
// 
 
#include "stdafx.h" 
#include "math.h"  
#include "3DMeterCtrl.h" 
#include "MemDC.h" 
#ifdef _DEBUG 
#define new DEBUG_NEW 
#undef THIS_FILE 
static char THIS_FILE[] = __FILE__; 
#endif 
 
///////////////////////////////////////////////////////////////////////////// 
// C3DMeterCtrl 
 
C3DMeterCtrl::C3DMeterCtrl() 
{ 
	m_dCurrentValue  =  0.0 ; 
	m_dMaxValue      =  5.0 ; 
	m_dMinValue      = -5.0 ; 
 
	m_nScaleDecimals = 1 ; 
	m_nValueDecimals = 3 ; 
 
	m_strUnits.Format("Volts") ; 
 
	m_colorNeedle = RGB(255, 0, 0) ; 
 
} 
 
C3DMeterCtrl::~C3DMeterCtrl() 
{ 
	if ((m_pBitmapOldBackground) &&  
		  (m_bitmapBackground.GetSafeHandle()) &&  
			(m_dcBackground.GetSafeHdc())) 
		m_dcBackground.SelectObject(m_pBitmapOldBackground); 
} 
 
 
BEGIN_MESSAGE_MAP(C3DMeterCtrl, CStatic) 
	//{{AFX_MSG_MAP(C3DMeterCtrl) 
	ON_WM_PAINT() 
	ON_WM_SIZE() 
	//}}AFX_MSG_MAP 
END_MESSAGE_MAP() 
 
///////////////////////////////////////////////////////////////////////////// 
// C3DMeterCtrl message handlers 
 
void C3DMeterCtrl::OnPaint()  
{ 
	CPaintDC dc(this); // device context for painting 
		 
	// Find out how big we are 
	GetClientRect (&m_rectCtrl) ; 
 
	// make a memory dc 
	CMemDC memDC(&dc, &m_rectCtrl); 
 
	// set up a memory dc for the background stuff  
	// if one isn't being used 
	if ((m_dcBackground.GetSafeHdc() == NULL) || (m_bitmapBackground.m_hObject == NULL)) 
	{ 
		m_dcBackground.CreateCompatibleDC(&dc) ; 
		m_bitmapBackground.CreateCompatibleBitmap(&dc, m_rectCtrl.Width(),  
																							m_rectCtrl.Height()) ; 
		m_pBitmapOldBackground = m_dcBackground.SelectObject(&m_bitmapBackground) ; 
 
		// Fill this bitmap with the background. 
 
		// Note: This requires some serious drawing and calculating,  
		// therefore it is drawn "once" to a bitmap and  
		// the bitmap is stored and blt'd when needed. 
		DrawMeterBackground(&m_dcBackground, m_rectCtrl) ; 
						 
	} 
 
	// drop in the background 
	memDC.BitBlt(0, 0, m_rectCtrl.Width(), m_rectCtrl.Height(),  
			           &m_dcBackground, 0, 0, SRCCOPY) ; 
 
	// add the needle to the background 
	DrawNeedle(&memDC) ; 
 
	// add the value to the background 
	DrawValue(&memDC) ; 
 
} 
 
void C3DMeterCtrl::DrawValue(CDC *pDC) 
{ 
	CFont *pFontOld ; 
	CString strTemp ; 
 
	// Pick up the font. 
	// Note: the font was determined in the drawing 
	// of the background 
  pFontOld = pDC->SelectObject(&m_fontValue) ; 
 
	// set the colors based on the system colors 
	pDC->SetTextColor(m_colorText) ; 
	pDC->SetBkColor(m_colorButton) ; 
 
	// draw the text in the recessed rectangle 
	pDC->SetTextAlign(TA_CENTER|TA_BASELINE) ; 
	strTemp.Format("%.*f", m_nValueDecimals, m_dCurrentValue) ; 
	pDC->TextOut(m_nValueCenter, m_nValueBaseline, strTemp) ; 
 
	// restore the color and the font 
	pDC->SetBkColor(m_colorWindow) ; 
  pDC->SelectObject(pFontOld) ; 
 
} 
 
void C3DMeterCtrl::UpdateNeedle(double dValue) 
{ 
	m_dCurrentValue = dValue ; 
	Invalidate() ; 
 
} 
 
void C3DMeterCtrl::DrawNeedle(CDC *pDC) 
{ 
	int nResult ; 
	 
	double dAngleRad ; 
	double dTemp ; 
	CBrush brushFill, *pBrushOld ; 
	CPen penDraw, *pPenOld ; 
	CPoint pointNeedle[3] ; 
 
	// This function draws a triangular needle. 
	// The base of the needle is a horizontal line 
	// that runs through the center point of the arcs 
	// that make up the face of the meter. 
	// The tip of the needle is at an angle that is  
	// calculated based on the current value and the scale. 
 
	// The needle is constructed as a 3-point polygon 
	// (i.e. triangle).  The triangle is drawn to the  
	// screen based on a clipping region that is derived 
	// from the meter face.  See "DrawMeterBackground". 
 
	// calculate the first and last points of the needle. 
	pointNeedle[0].x = m_nBottomCX + m_nBottomRadius/20 ; 
	pointNeedle[0].y = m_nBottomCY ; 
	pointNeedle[2].x = m_nBottomCX - m_nBottomRadius/20 ; 
	pointNeedle[2].y = m_nBottomCY ; 
 
	// calculate the angle for the tip of the needle 
	dAngleRad = (m_dCurrentValue-m_dMinValue)*(m_dRightAngleRad-m_dLeftAngleRad)/ 
		          (m_dMaxValue-m_dMinValue) + m_dLeftAngleRad ; 
 
	// if the angle is beyond the meter, draw the needle 
	// at the limit (as if it is "pegged") 
	dAngleRad = max(dAngleRad, m_dRightAngleRad) ; 
	dAngleRad = min(dAngleRad, m_dLeftAngleRad) ; 
 
	// calculate the X position of the tip 
	dTemp = m_nBottomCX + m_nTopRadius*cos(dAngleRad) ; 
	pointNeedle[1].x = ROUND(dTemp) ; 
 
	// calculate the Y position of the tip 
	dTemp = m_nBottomCY - m_nTopRadius*sin(dAngleRad) ; 
	pointNeedle[1].y = ROUND(dTemp) ; 
 
	// select the clipping region based on the meter 
	pDC->SelectClipRgn(&m_rgnBoundary) ; 
 
	// create a pen and brush based on the needle color 
	brushFill.CreateSolidBrush(m_colorNeedle) ; 
	penDraw.CreatePen(PS_SOLID, 1, m_colorNeedle) ; 
 
	// select the pen and brush 
	pPenOld = pDC->SelectObject(&penDraw) ; 
	pBrushOld = pDC->SelectObject(&brushFill) ; 
 
	// draw the needle 
	pDC->Polygon(pointNeedle, 3) ; 
 
	// restore the clipping region 
	nResult = pDC->SelectClipRgn(NULL) ; 
 
	// restore the pen and brush 
	pDC->SelectObject(pPenOld) ; 
	pDC->SelectObject(pBrushOld) ; 
 
	 
} 
 
void C3DMeterCtrl::DrawMeterBackground(CDC *pDC, CRect &rect) 
{ 
	int i, nAngleDeg, nRef ; 
	int nHeight ; 
	int nHalfPoints ; 
	int nStartAngleDeg, nEndAngleDeg ; 
	int nTickDeg ; 
	int nAngleIncrementDeg ; 
 
	double dTemp, dAngleRad, dX, dY ; 
	double dRadPerDeg ; 
 
	CString strTemp ; 
 
	CPoint pointRecess[BOUNDARY_POINT] ; 
 
	CBrush brushFill, *pBrushOld ; 
 
	CFont *pFontOld ; 
 
	CPen penDraw, *pPenOld ; 
 
	TEXTMETRIC tm ; 
 
	// determine the centerpoint of the radii for 
	// the meter face. 
	m_nBottomCX = (rect.left+rect.right)/2 ; 
	m_nBottomCY = rect.bottom - rect.Height()/16 ; 
 
	// determine the radii for the top of the meter 
	// and the bottom of the meter 
	m_nTopRadius = rect.Height()*6/8 ; 
	m_nBottomRadius = m_nTopRadius/2 ; 
 
	// Radians per Degree 
	// This helps me lay things out in degrees 
	// which are more intuitive than radians. 
	dRadPerDeg = 4.0*atan(1.0)/180.0 ; 
 
	// set the left and right limits for the meter face 
	nStartAngleDeg =  60 ; 
	nEndAngleDeg   = 120 ; 
	nTickDeg       =  15 ; 
 
	// this is the density of points along the arcs 
	nAngleIncrementDeg = 5 ; 
 
	// convert these to radians  
	// for computer (rather than human) use! 
	m_dLeftAngleRad = nEndAngleDeg*dRadPerDeg ; 
	m_dRightAngleRad = nStartAngleDeg*dRadPerDeg ; 
 
 
	// construct the meter face region 
	// This is a polygon starting at the top right of 
	// the meter face and moving across the top arc. 
	nRef = 0 ; 
	for (nAngleDeg=nStartAngleDeg; nAngleDeg<=nEndAngleDeg; nAngleDeg+=nAngleIncrementDeg) 
	{ 
		// determine the current angle in radians 
		dAngleRad = nAngleDeg*dRadPerDeg ; 
 
		// determine the X position 
		dTemp = m_nBottomCX + m_nTopRadius*cos(dAngleRad) ; 
		m_pointBoundary[nRef].x = ROUND(dTemp) ; 
 
		// determine the Y position 
		dTemp = m_nBottomCY - m_nTopRadius*sin(dAngleRad) ; 
		m_pointBoundary[nRef].y = ROUND(dTemp) ; 
		nRef++ ; 
	} 
 
	// at this point we have constructed the entire 
	// top arc of the meter face 
 
	nHalfPoints = nRef ;   // hold onto this for later use 
 
 
	// now add points to the polygon starting at the  
	// left side of the lower arc. 
	for (nAngleDeg=nEndAngleDeg; nAngleDeg>=nStartAngleDeg; nAngleDeg-=nAngleIncrementDeg) 
	{ 
		dAngleRad = nAngleDeg*dRadPerDeg ; 
		dTemp = m_nBottomCX + m_nBottomRadius*cos(dAngleRad) ; 
		m_pointBoundary[nRef].x = ROUND(dTemp) ; 
		dTemp = m_nBottomCY - m_nBottomRadius*sin(dAngleRad) ; 
		m_pointBoundary[nRef].y = ROUND(dTemp) ; 
		nRef++ ; 
	} 
 
	 
	// Now construct a polygon that is just outside  
	// the meter face to use in drawing a "recess" 
	// around the meter face. 
	for (i=0; iSelectObject(&brushFill) ; 
	pDC->Rectangle(rect) ; 
	pDC->SelectObject(pBrushOld) ; 
 
	// Draw the meter recess. 
 
	// This happens by first drawing the  
	// top and left sides in the shadow color. 
	penDraw.DeleteObject() ; 
	penDraw.CreatePen(PS_SOLID, 1, m_colorShadow) ; 
	pPenOld = pDC->SelectObject(&penDraw) ; 
	pDC->MoveTo(pointRecess[0]) ; 
	pDC->PolylineTo(pointRecess, nHalfPoints+1) ; 
	pDC->SelectObject(pPenOld) ; 
 
	// and then drawing the  
	// left and bottom sides in the shadow color. 
	penDraw.DeleteObject() ; 
	penDraw.CreatePen(PS_SOLID, 1, m_colorHighlight) ; 
	pPenOld = pDC->SelectObject(&penDraw) ; 
	// draw the bottom arc 
	pDC->PolylineTo(&pointRecess[nHalfPoints], nHalfPoints) ; 
	pDC->LineTo(pointRecess[0]) ;  // connect it to the top 
	pDC->SelectObject(pPenOld) ; 
 
	// Draw the meter face 
 
	// use the text color for the border 
	penDraw.DeleteObject() ; 
	penDraw.CreatePen(PS_SOLID, 1, m_colorText) ; 
	pPenOld = pDC->SelectObject(&penDraw) ; 
 
	// use the current window color for filling 
	brushFill.DeleteObject() ; 
	brushFill.CreateSolidBrush(m_colorWindow) ; 
	pBrushOld = pDC->SelectObject(&brushFill) ; 
 
	// draw the meter face 
	pDC->Polygon(m_pointBoundary, nRef) ; 
 
	// restore the brush (but keep the pen!) 
	pDC->SelectObject(pBrushOld) ; 
 
	// Draw the tick marks. 
	// Make the tick marks extend down 10% of the face 
	dTemp = m_nTopRadius - 0.1*(m_nTopRadius-m_nBottomRadius) ; 
 
	// draw the right side tick marks 
	for (nAngleDeg=90; nAngleDeg>nStartAngleDeg; nAngleDeg-=nTickDeg) 
	{ 
		dAngleRad = nAngleDeg*dRadPerDeg ; 
 
		// move to the top of the tick mark 
		dX = m_nBottomCX + m_nTopRadius*cos(dAngleRad) ; 
		dY = m_nBottomCY - m_nTopRadius*sin(dAngleRad) ; 
		pDC->MoveTo(ROUND(dX), ROUND(dY)) ; 
 
		// move to the bottom of the tick mark 
		dX = m_nBottomCX + dTemp*cos(dAngleRad) ; 
		dY = m_nBottomCY - dTemp*sin(dAngleRad) ; 
		pDC->LineTo(ROUND(dX), ROUND(dY)) ; 
	} 
 
	// draw the left side tick marks 
	for (nAngleDeg=90+nTickDeg; nAngleDegMoveTo(ROUND(dX), ROUND(dY)) ; 
 
		// move to the bottom of the tick mark 
		dX = m_nBottomCX + dTemp*cos(dAngleRad) ; 
		dY = m_nBottomCY - dTemp*sin(dAngleRad) ; 
		pDC->LineTo(ROUND(dX), ROUND(dY)) ; 
	} 
 
	// now we're done with the pen! 
	pDC->SelectObject(pPenOld) ; 
 
	// Draw the recessed rectangle  
	// for the numerical value. 
 
	// draw the left and top sides with the shadow 
	penDraw.DeleteObject() ; 
	penDraw.CreatePen(PS_SOLID, 1, m_colorShadow) ; 
	pPenOld = pDC->SelectObject(&penDraw) ; 
	pDC->MoveTo(m_rectValue.left, m_rectValue.bottom) ; 
	pDC->LineTo(m_rectValue.left, m_rectValue.top) ; 
	pDC->LineTo(m_rectValue.right, m_rectValue.top) ; 
	pDC->SelectObject(pPenOld) ; 
 
	// draw the right and bottom sides with the highlight 
	penDraw.DeleteObject() ; 
	penDraw.CreatePen(PS_SOLID, 1, m_colorHighlight) ; 
	pPenOld = pDC->SelectObject(&penDraw) ; 
	pDC->LineTo(m_rectValue.right, m_rectValue.bottom) ; 
	pDC->LineTo(m_rectValue.left, m_rectValue.bottom) ; 
	pDC->SelectObject(pPenOld) ; 
 
	// determine the font size 
	nHeight = m_rectValue.Height()*85/100 ; 
 
  m_fontValue.DeleteObject() ; 
	m_fontValue.CreateFont (nHeight, 0, 0, 0, 400, 
												  FALSE, FALSE, 0, ANSI_CHARSET, 
												  OUT_DEFAULT_PRECIS,  
												  CLIP_DEFAULT_PRECIS, 
												  DEFAULT_QUALITY,  
												  DEFAULT_PITCH|FF_SWISS, "Arial") ; 
 
	// Now select this font and calculate the  
	// actual height to see what Windows gave us. 
	pFontOld = pDC->SelectObject(&m_fontValue) ; 
  pDC->GetTextMetrics(&tm) ; 
  m_nValueFontHeight = tm.tmHeight ; 
  
	// determine the location for the value within 
	// the rectangele based on the font height 
	m_nValueBaseline = m_rectValue.bottom -  
		                 m_nValueFontHeight/4 ; 
	m_nValueCenter   = m_rectValue.left +  
		                 m_rectValue.Width()/2 ; 
 
	// now we need to use the font to draw some text 
	// set the colors (based on system colors) 
	pDC->SetTextColor(m_colorText) ; 
	pDC->SetBkColor(m_colorButton) ; 
 
	// draw the units below the meter face 
	pDC->SetTextAlign(TA_CENTER|TA_BASELINE) ; 
	pDC->TextOut(m_nValueCenter,  
		           m_rectValue.top - m_nValueFontHeight/4, 
							 m_strUnits) ; 
 
 
	// draw the value on the minimum side of the meter 
	pDC->SetTextAlign(TA_LEFT|TA_BASELINE) ; 
	strTemp.Format("%.*lf", m_nScaleDecimals, m_dMinValue) ; 
	pDC->TextOut((rect.left+m_pointBoundary[nHalfPoints-1].x)/2,  
		           m_pointBoundary[nHalfPoints/2].y-m_nValueFontHeight*25/100 , 
							 strTemp) ; 
 
	// draw the value on the maximum side of the meter 
	pDC->SetTextAlign(TA_RIGHT|TA_BASELINE) ; 
	strTemp.Format("%.*lf", m_nScaleDecimals, m_dMaxValue) ; 
	pDC->TextOut((rect.right+m_pointBoundary[0].x)/2,  
		           m_pointBoundary[nHalfPoints/2].y-m_nValueFontHeight*25/100 , 
							 strTemp) ; 
 
	// restore the font and background color 
  pDC->SelectObject(pFontOld) ; 
	pDC->SetBkColor(m_colorWindow) ; 
	 
} 
 
 
void C3DMeterCtrl::OnSize(UINT nType, int cx, int cy)  
{ 
	CStatic::OnSize(nType, cx, cy); 
	 
	ReconstructControl() ; 
 
} 
 
void C3DMeterCtrl::ReconstructControl()  
{ 
	// if we've got a stored background - remove it! 
	if ((m_pBitmapOldBackground) &&  
		  (m_bitmapBackground.GetSafeHandle()) &&  
			(m_dcBackground.GetSafeHdc())) 
	{ 
			m_dcBackground.SelectObject(m_pBitmapOldBackground); 
			m_dcBackground.DeleteDC() ; 
			m_bitmapBackground.DeleteObject(); 
	} 
 
	Invalidate () ; 
 
} 
 
void C3DMeterCtrl::SetRange(double dMin, double dMax)  
{ 
	m_dMaxValue = dMax ; 
	m_dMinValue = dMin ; 
	ReconstructControl() ; 
} 
 
void C3DMeterCtrl::SetScaleDecimals(int nDecimals)  
{ 
	m_nScaleDecimals = nDecimals ; 
	ReconstructControl() ; 
} 
 
void C3DMeterCtrl::SetValueDecimals(int nDecimals)  
{ 
	m_nValueDecimals = nDecimals ; 
	ReconstructControl() ; 
} 
 
void C3DMeterCtrl::SetUnits(CString &strUnits)  
{ 
	m_strUnits = strUnits ; 
	ReconstructControl() ; 
} 
 
void C3DMeterCtrl::SetNeedleColor (COLORREF colorNeedle) 
{ 
	m_colorNeedle = colorNeedle ; 
	ReconstructControl() ; 
}