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

CGroupLine

Rate me:
Please Sign up or sign in to vote.
4.84/5 (29 votes)
18 Nov 2008CPOL5 min read 81.6K   3.2K   68  
CStatic enhancement that adds a trailing horizontal line, and supports WinXP themes.
//////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "GroupLine.h"
#include <Shlwapi.h>  // for IsThemed() function
#include <Uxtheme.h>  // for CloseThemeData (etc)
#include <TmSchema.h> // for BP_GROUPBOX (etc)
//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
#pragma comment(lib, "uxtheme.lib")
// *** NOTE ON LINKING ***
//
// If you are going to use this in a program that should run on
// versions of windows before XP (e.g. Windows 2000), you will likely
// want to delay-load the UxTheme.Dll in the project settings.
//
// For Example (in Visual Studio 2003)
// Configuration Properties->Linker->Input->Delay Loaded Dlls->Add-->"UxTheme.DLL" (without the quotes)
// You may also have to add support for delay loading DLLs:
// Configuration Properties->Linker->Input->Additional Depenedencies->Add->"delayimp.lib" (without the quotes)
//
//////////////////////////////////////////////////////////////////////////
// Unfortunately, we cannot use:
// #pragma comment(linker, "/DelayLoad:UxTheme.dll")
// because:
//////////////////////////////////////////////////////////////////////////
// http://dotnet247.com/247reference/msgs/41/208171.aspx
//
// Delay loading is fine. Adding a delayload by using the pragma isn't guaranteed to work.
// The problem is that the linker parses the pragma generated data beyond the
// point where it already made some decisions that it cannot undo that _may_
// conflict with delayloading this dll.
// 
// Ronald Laeremans [MSFT] (VIP)
//////////////////////////////////////////////////////////////////////////


IMPLEMENT_DYNAMIC(CGroupLine, CStatic)
CGroupLine::CGroupLine(eThemeControl useTheme /*= eThemeAutomatic*/, eDisabledThemeStyle disabledThemeStyle /*= eGrayIfDisabled*/)
	: m_automaticUseTheme(eThemeAutomatic)
	, m_useTheme(useTheme)
	, m_disabledThemeStyle(disabledThemeStyle)
{ }

CGroupLine::~CGroupLine()
{ }

void CGroupLine::SetUseTheme(eThemeControl useTheme){
	m_useTheme = useTheme;
}
void CGroupLine::SetDisabledThemeStyle(eDisabledThemeStyle disabledThemeStyle){
	m_disabledThemeStyle = disabledThemeStyle;
}

BEGIN_MESSAGE_MAP(CGroupLine, CStatic)
	ON_WM_PAINT()
END_MESSAGE_MAP()

void CGroupLine::OnPaint()
{
	if(eThemeAutomatic==m_automaticUseTheme){ // uninitialized
		m_automaticUseTheme = (IsThemed()) ? (eThemeOn) : (eThemeOff);
	}

	const bool drawAsEnabled(0!=IsWindowEnabled());

	const bool drawAsThemed( (eThemeOn==m_useTheme) // force themes ON
		|| ((eThemeAutomatic==m_useTheme) && (eThemeOn==m_automaticUseTheme)) // use automatic, and automatic is ON
		);

	CPaintDC dc(this); // device context for painting

	// Get the text
	CString text;
	GetWindowText(text);

	// Get the rectangle
	CRect clientRect;
	GetClientRect(&clientRect);
	CRect textRect(clientRect);
	CRect lineRect(clientRect);

	// Need to select the font (or it will be UGLY)
	CFont* pOldFont(dc.SelectObject(GetFont()));
	const int oldBkMode(dc.SetBkMode(TRANSPARENT)); // needed for non-themed text
	
	// Style is set using the "Align Text" property in the dialog editor, or as a parameter to CreateWindow or CreateWindowEx
	const DWORD windowStyle(GetStyle());
	
	HTHEME theme = (drawAsThemed) ? (::OpenThemeData(this->m_hWnd, L"BUTTON")) : (NULL);

	// Calculate the text size
	CSize textSize(0,0);
	if(NULL!=theme){
		CRect textSizeRect(0,0,0,0);
		const CStringW wideString(text);
		::GetThemeTextExtent(theme, dc.m_hDC, BP_GROUPBOX, GBS_NORMAL, wideString, -1, DT_LEFT | DT_SINGLELINE, NULL, &textSizeRect);
		textSize = textSizeRect.Size();
	}else{
		textSize =  dc.GetTextExtent(text);
	}
	
	
	const int offsetSize(textSize.cy / 2); // offset the start of the line a little bit from the edge of the text
	const DWORD textDrawingFlags(DT_SINGLELINE | DT_NOCLIP | DT_LEFT);

	int lineHeight(0);
	if(SS_CENTERIMAGE & windowStyle){ // text and line are vertically centered
		textRect.top = clientRect.CenterPoint().y - (textSize.cy / 2);
		lineHeight = lineRect.CenterPoint().y;
	}else{ // text and line run along the top edge of the control
		lineHeight = textSize.cy / 2;
	}
	
	CPoint textLocation(0,0); // have to calculate, since DrawState does not have a center flag (is there a DSS_CENTER, and I just missed it ???)

	// Draw The Line(s)
	if(SS_RIGHT & windowStyle){ // right-aligned text
		textRect.left = textRect.right - textSize.cx;
		DrawLine(dc, drawAsThemed, lineHeight,
			lineRect.left,
			lineRect.right - (textSize.cx + offsetSize));

	}else if (SS_CENTER & windowStyle){ // centered text		
		const int eachSideWidth = (clientRect.Width() - (textSize.cx + offsetSize*2)) / 2;
		textRect.left = eachSideWidth + offsetSize;

		DrawLine(dc, drawAsThemed, lineHeight,
			lineRect.left,
			lineRect.left + eachSideWidth);
		
		DrawLine(dc, drawAsThemed, lineHeight,
			lineRect.right - eachSideWidth,
			lineRect.right);

	}else{ // left-aligned text

		DrawLine(dc, drawAsThemed, lineHeight,
			lineRect.left + (textSize.cx + offsetSize),
			lineRect.right);
	}

	// Draw the text
	const bool useThemeDrawing( (drawAsEnabled) ? (drawAsThemed) : (eSameAsEnabled==m_disabledThemeStyle) );
	if(drawAsThemed && useThemeDrawing){
		const int drawThemeFlags(drawAsEnabled ? GBS_NORMAL : GBS_DISABLED); // NOTE: by default GBS_DISABLED looks the same as GBS_NORMAL
		const CStringW wideString(text);
		::DrawThemeText(theme, dc.m_hDC, BP_GROUPBOX, drawThemeFlags, wideString, -1, textDrawingFlags, NULL, &textRect);
	}else{
		const UINT drawStateFlags(drawAsEnabled ? DSS_NORMAL : DSS_DISABLED);
		dc.DrawState(
			CPoint(textRect.left, textRect.top),
			CSize(0,0), // 0,0 says to calculate it (per DrawState SDK docs)
			text, drawStateFlags, TRUE, 0, (HBRUSH)NULL);
	}

	if(NULL!=theme){
		::CloseThemeData(theme);
	}
	
	dc.SetBkMode(oldBkMode);
	if(NULL!=pOldFont){
		dc.SelectObject(pOldFont);
	}
	// Auto-generated note --> Do not call CStatic::OnPaint() for painting messages
}

void CGroupLine::DrawLine(CDC& dc, bool drawAsThemed, int height, int left, int right)
{
	if(drawAsThemed){
		// NOTE: color is hard-coded here. It should probably use something like 
		// DrawThemeEdge / DrawThemeBackground / GetThemeColor - but I could not find
		// the correct set of parameters (there is  a DrawThemeEdge call that gives a similar line, but slightly darker)
		CPen pen(PS_SOLID, 1, RGB(208,208,191));
		CPen* pOldPen = dc.SelectObject(&pen);
		
		dc.MoveTo(left , height);
		dc.LineTo(right, height);
		
		if(NULL!=pOldPen){
			dc.SelectObject(pOldPen);
		}
	}else{		
		CPen penTop(PS_SOLID, 1, ::GetSysColor(COLOR_3DHIGHLIGHT));
		CPen* pOldPen = dc.SelectObject(&penTop);
		dc.MoveTo(left , height);
		dc.LineTo(right, height);
		
		CPen penBottom(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW));
		dc.SelectObject(&penBottom);
		dc.MoveTo(left , height - 1);
		dc.LineTo(right, height - 1);

		if(NULL!=pOldPen){
			dc.SelectObject(pOldPen);
		}
	}
}


BOOL CGroupLine::IsThemed()
{
	//////////////////////////////////////////////////////////////////////////
	// 99% of this function is taken from the following article:
	//
	// How to accurately detect if an application is theme-enabled?
	// By Nishant Sivakumar
	// http://www.codeproject.com/tips/DetectTheme.asp
	//
	// Except: 
	//  1) changed the version check to include Windows versions beyond XP
	//  2) _T() macro removed from GetProcAddress functions
	//
	//////////////////////////////////////////////////////////////////////////

	BOOL ret = FALSE;
	OSVERSIONINFO ovi = {0};
	ovi.dwOSVersionInfoSize = sizeof ovi;
	GetVersionEx(&ovi);	
	if(   (ovi.dwMajorVersion  > 5) ||
	    ( (ovi.dwMajorVersion == 5) && (ovi.dwMinorVersion >= 1) ) )
	{
		//Windows >= XP detected
		typedef BOOL WINAPI ISAPPTHEMED();
		typedef BOOL WINAPI ISTHEMEACTIVE();
		ISAPPTHEMED* pISAPPTHEMED = NULL;
		ISTHEMEACTIVE* pISTHEMEACTIVE = NULL;
		HMODULE hMod = LoadLibrary(_T("uxtheme.dll"));
		if(hMod)
		{
			pISAPPTHEMED = reinterpret_cast<ISAPPTHEMED*>(
				GetProcAddress(hMod, "IsAppThemed"));
			pISTHEMEACTIVE = reinterpret_cast<ISTHEMEACTIVE*>(
				GetProcAddress(hMod, "IsThemeActive"));
			if(pISAPPTHEMED && pISTHEMEACTIVE)
			{
				if(pISAPPTHEMED() && pISTHEMEACTIVE())                
				{                
					typedef HRESULT CALLBACK DLLGETVERSION(DLLVERSIONINFO*);
					DLLGETVERSION* pDLLGETVERSION = NULL;

					HMODULE hModComCtl = LoadLibrary(_T("comctl32.dll"));
					if(hModComCtl)
					{
						pDLLGETVERSION = reinterpret_cast<DLLGETVERSION*>(
							GetProcAddress(hModComCtl, "DllGetVersion"));
						if(pDLLGETVERSION)
						{
							DLLVERSIONINFO dvi = {0};
							dvi.cbSize = sizeof dvi;
							if(pDLLGETVERSION(&dvi) == NOERROR )
							{
								ret = dvi.dwMajorVersion >= 6;
							}
						}
						FreeLibrary(hModComCtl);                    
					}
				}
			}
			FreeLibrary(hMod);
		}
	}    
	return ret;
}

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)
Canada Canada
www.IconsReview.com[^]
Huge list of stock icon collections (both free and commercial)

The picture is from September 2006, after picking up a rental car at the airport in Denver, Colorado. I'm smiling in the picture, because I have yet to come to the realization that I just wasted 400 bucks ( because you don't really need a car in downtown Denver - you can just walk everywhere).

Comments and Discussions