Click here to Skip to main content
15,884,176 members
Articles / Desktop Programming / MFC

XColorDialog - an MFC color picker control that displays a color hexagon and a color spectrum

Rate me:
Please Sign up or sign in to vote.
4.92/5 (8 votes)
5 Apr 2008CPOL4 min read 63.8K   2.5K   44  
XColorDialog displays a color hexagon and a color spectrum that allows user selection, and provides APIs for color based on RGB and HSL color models.
// XColorDialog.cpp  Version 1.0 - see article at CodeProject.com
//
// Author:  Hans Dietrich
//          hdietrich@gmail.com
//
// Description:
//     XColorDialog implements CXColorDialog, a dialog that mimics the color 
//     picker dialog in MS Office.
//
// History
//     Version 1.0 - 2008 April 5
//     - Initial public release
//
// License:
//     This software is released under the Code Project Open License (CPOL),
//     which may be found here:  http://www.codeproject.com/info/eula.aspx
//     You are free to use this software 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 "XColorDialogRes.h"
#include "XColorDialog.h"
#include "TabStandard.h"
#include "TabCustom.h"
#include "uxtheme.h"		// from platform sdk
#include "tmschema.h"		// from platform sdk
#include "rgbhsl.h"
#include "XColorSpectrumCtrl.h"
#include "XBalloonMsg.h"

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

#ifndef __noop
#if _MSC_VER < 1300
#define __noop ((void)0)
#endif
#endif

#undef TRACE
#define TRACE __noop
#undef TRACERECT
#define TRACERECT __noop

//=============================================================================
// if you want to see the TRACE output, uncomment this line:
//#include "XTrace.h"

//=============================================================================
BEGIN_MESSAGE_MAP(CXColorDialog, CDialog)
//=============================================================================
	//{{AFX_MSG_MAP(CXColorDialog)
	ON_NOTIFY(TCN_SELCHANGE, IDC_TAB, OnSelchangeTab)
	ON_NOTIFY(TCN_SELCHANGING, IDC_TAB, OnSelchangingTab)
	ON_WM_CTLCOLOR()
	ON_WM_PAINT()
	ON_WM_HELPINFO()
	//}}AFX_MSG_MAP
	ON_REGISTERED_MESSAGE(WM_XCOLORPICKER_SELCHANGE, OnSelChange)
	ON_REGISTERED_MESSAGE(WM_XCOLORPICKER_SELENDOK, OnSelendOk)
END_MESSAGE_MAP()

//=============================================================================
static BOOL IsVista()
//=============================================================================
{
	BOOL rc = FALSE;

	OSVERSIONINFO osvi = { 0 };
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	if (GetVersionEx(&osvi))
	{
		if ((osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) &&
			(osvi.dwMajorVersion >= 6))
		{
			rc = TRUE;
		}
	}

	return rc;
}

//=============================================================================
CXColorDialog::CXColorDialog(
//=============================================================================
	COLORREF crInitial /*= 0*/, 
	DWORD dwFlags /*= XCD_TOOLTIP_NONE | XCD_OPEN_HEXAGON*/,
	CWnd* pParent /*= NULL*/)
 :	CDialog(_T("IDD_XCOLOR_DIALOG"), pParent),
	m_crNew(crInitial),
	m_crCurrent(crInitial),
	m_dwFlags(dwFlags),
	m_nCurrentTab(0),
	m_strTitle(_T("")),
	m_nColorModel(-1)
{
	//{{AFX_DATA_INIT(CXColorDialog)
	//}}AFX_DATA_INIT
	m_nTooltipFormat = m_dwFlags & 0xF;
	m_brushNew.CreateSolidBrush(m_crNew);
	m_brushCurrent.CreateSolidBrush(m_crCurrent);

	m_hHelpIcon = (HICON) ::LoadImage(AfxGetInstanceHandle(), 
							IsVista() ? _T("IDI_HELP_VISTA") : _T("IDI_HELP_XP"),
							IMAGE_ICON, 16, 16, LR_LOADTRANSPARENT);
	if (!m_hHelpIcon)
	{
		TRACE(_T("ERROR - failed to load help icon\n"));
	}
}

//=============================================================================
CXColorDialog::~CXColorDialog()
//=============================================================================
{
	TRACE(_T("in CXColorDialog::~CXColorDialog\n"));
	delete m_tabPages[0];
	m_tabPages[0] = 0;
	delete m_tabPages[1];
	m_tabPages[1] = 0;
	if (m_brushNew.GetSafeHandle())
		m_brushNew.DeleteObject();
	if (m_brushCurrent.GetSafeHandle())
		m_brushCurrent.DeleteObject();
	if (m_hHelpIcon)
		::DestroyIcon(m_hHelpIcon);
	m_hHelpIcon = 0;
}

//=============================================================================
void CXColorDialog::DoDataExchange(CDataExchange* pDX)
//=============================================================================
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CXColorDialog)
	DDX_Control(pDX, IDC_TAB, m_TabCtrl);
	//}}AFX_DATA_MAP
}

//=============================================================================
BOOL CXColorDialog::OnInitDialog() 
//=============================================================================
{
	TRACE(_T("in CXColorDialog::OnInitDialog\n"));

	CDialog::OnInitDialog();

	//=========================================================================
	// get colors for gradient fill
	COLORREF crStartColor = GetTabBackgroundColor();
	double H, S, L;
	RGBtoHSL(crStartColor, &H, &S, &L);
	L -= 10.0;
	if (L < 0.0)
		L = 0.0;
	COLORREF crEndColor = HSLtoRGB(H, S, L);

	//=========================================================================
	// create tab pages
	m_tabPages[0] = new CTabStandard(m_crCurrent, crStartColor, crEndColor, 
		(CXColorHexagonCtrl::TOOLTIP_FORMAT) m_nTooltipFormat, this, 
		m_hHelpIcon);
	ASSERT(m_tabPages[0]);
	VERIFY(m_tabPages[0]->Create(_T("IDD_TAB_STANDARD"), this));
	m_tabPages[0]->ShowWindow(SW_HIDE);

	m_tabPages[1] = new CTabCustom(m_crCurrent, crStartColor, crEndColor, 
		(CXColorSpectrumCtrl::TOOLTIP_FORMAT) m_nTooltipFormat, this, 
		m_hHelpIcon);
	ASSERT(m_tabPages[1]);
	VERIFY(m_tabPages[1]->Create(_T("IDD_TAB_CUSTOM"), this));
	m_tabPages[1]->ShowWindow(SW_HIDE);

	//=========================================================================
	// set color model
	if (m_nColorModel == 0 || m_nColorModel == 1)
	{
		CTabCustom *pCustom = (CTabCustom *) m_tabPages[1];
		pCustom->SetColorModel(m_nColorModel);
	}

	//=========================================================================
	// set initial tab
	if (m_dwFlags & XCD_OPEN_SPECTRUM)
		m_nCurrentTab = 1;
	m_tabPages[m_nCurrentTab]->ShowWindow(SW_SHOW);

	//=========================================================================
	// set up tab control
	m_TabCtrl.InsertItem(TCIF_TEXT|TCIF_PARAM, 0, _T("Standard"), 0, 
		(LPARAM)m_tabPages[0]);
	m_TabCtrl.InsertItem(TCIF_TEXT|TCIF_PARAM, 1, _T("Custom"), 0, 
		(LPARAM)m_tabPages[1]);

	m_TabCtrl.SetCurSel(m_nCurrentTab);

	//=========================================================================
	// position tabs
	CRect rectTab;
	m_TabCtrl.GetWindowRect(&rectTab);
	ScreenToClient(&rectTab);
	TRACERECT(rectTab);

	CRect rectItem;
	m_TabCtrl.GetItemRect(0, &rectItem);
	TRACERECT(rectItem);

	CRect rectClient;
	m_TabCtrl.GetClientRect(&rectClient);
	TRACERECT(rectClient);

	rectTab.top  += rectItem.bottom + GetSystemMetrics(SM_CYEDGE);
	rectTab.bottom -= 2 * GetSystemMetrics(SM_CYEDGE);
	rectTab.left += GetSystemMetrics(SM_CXEDGE);
	rectTab.right = rectTab.left + rectClient.Width() - 
						2 * GetSystemMetrics(SM_CXEDGE) - 1;

	m_tabPages[0]->MoveWindow(rectTab);
	m_tabPages[1]->MoveWindow(rectTab);

	//=========================================================================
	// position new & current colors
	CRect rect;
	GetDlgItem(IDC_NEW_COLOR)->GetWindowRect(&rect);
	ScreenToClient(&rect);
	int h = rect.Height();
	rect.top += h;
	rect.bottom += h;
	GetDlgItem(IDC_CURRENT_COLOR)->MoveWindow(&rect);

	if (!m_strTitle.IsEmpty())
		SetWindowText(m_strTitle);

#ifdef _DEBUG
	//Test();
#endif

	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

#ifdef _DEBUG
//=============================================================================
// This function tests basic accuracy of algorithms.
void CXColorDialog::Test()
//=============================================================================
{
	struct RGBHSL
	{
		COLORREF cr;
		BYTE h, s, l;
	};

	static struct RGBHSL ColorData[] = 
	{
		RGB(255,0,0),		0,240,120,
		RGB(128,0,0),		0,240,60,
		RGB(255,255,0),		40,240,120,
		RGB(0,255,0),		80,240,120,
		RGB(0,128,0),		80,240,60,
		RGB(0,255,255),		120,240,120,
		RGB(0,0,255),		160,240,120,
		RGB(0,0,128),		160,240,60,
		RGB(255,0,255),		200,240,120,
		RGB(128,0,128),		200,240,60,
		RGB(255,255,255),	0,0,240,
		RGB(128,128,128),	0,0,120,
		RGB(0,0,0),			0,0,0
	};

	const int nColorDataSize = sizeof(ColorData) / sizeof(ColorData[0]);

	CTabCustom *pCustom = (CTabCustom *) m_tabPages[1];
	ASSERT(pCustom);

	for (int i = 0; i < nColorDataSize; i++)
	{
		COLORREF cr = ColorData[i].cr;
		pCustom->SetRGB(cr);
		BYTE h, s, l;
		pCustom->GetHSL(&h, &s, &l);
		ASSERT(h == ColorData[i].h &&
			   s == ColorData[i].s &&
			   l == ColorData[i].l);
		TRACE(_T(">>>>>  CXColorDialog::Test:   RGB(%d,%d,%d)  %d,%d,%d\n"),
			GetRValue(cr), GetGValue(cr), GetBValue(cr), 
			h, s, l);
	}
}
#endif

//=============================================================================
COLORREF CXColorDialog::GetTabBackgroundColor()
//=============================================================================
{
	COLORREF cr = GetSysColor(COLOR_BTNFACE);

	typedef HTHEME (__stdcall * OPENTHEMEDATA)(HWND hwnd, LPCWSTR pszClassList);
	typedef HRESULT (__stdcall * CLOSETHEMEDATA)(HTHEME hTheme);
	typedef HRESULT (__stdcall * GETTHEMECOLOR)(HTHEME hTheme,
									int iPartId, int iStateId, int iPropId,
									COLORREF *pColor);

	HMODULE hUxDll = ::LoadLibrary(_T("UxTheme.dll"));

	if (hUxDll)
	{
		OPENTHEMEDATA  pfOpenThemeData  = 
					(OPENTHEMEDATA) ::GetProcAddress(hUxDll, "OpenThemeData");
		CLOSETHEMEDATA pfCloseThemeData = 
					(CLOSETHEMEDATA)::GetProcAddress(hUxDll, "CloseThemeData");
		GETTHEMECOLOR  pfGetThemeColor  = 
					(GETTHEMECOLOR) ::GetProcAddress(hUxDll, "GetThemeColor");

		if (pfOpenThemeData && pfCloseThemeData && pfGetThemeColor)
		{
			HTHEME hThemeTabCtrl = 
					(*pfOpenThemeData)(AfxGetMainWnd()->GetSafeHwnd(), L"TAB");

			if (hThemeTabCtrl)
			{
				(*pfGetThemeColor)(hThemeTabCtrl, TABP_BODY, TIS_NORMAL, 
					TMT_FILLCOLORHINT, &cr);
				(*pfCloseThemeData)(hThemeTabCtrl);
				TRACE(_T("cr=0x%06X\n"), cr);
			}
		}

		::FreeLibrary(hUxDll);
	}

	return cr;
}

//=============================================================================
void CXColorDialog::OnSelchangeTab(NMHDR* /*pNMHDR*/, LRESULT* pResult) 
//=============================================================================
{
	int nNewTab = m_TabCtrl.GetCurSel();

	// hide the current tab
	if (IsWindow(m_tabPages[m_nCurrentTab]->m_hWnd))
		m_tabPages[m_nCurrentTab]->ShowWindow(SW_HIDE);

	// show the new tab
	if (IsWindow(m_tabPages[nNewTab]->m_hWnd))
	{
		if (nNewTab == 0)
		{
			CTabStandard *pStandard = (CTabStandard *) m_tabPages[0];
			ASSERT(pStandard && IsWindow(pStandard->m_hWnd));
			if (pStandard && IsWindow(pStandard->m_hWnd))
			{
				pStandard->SetRGB(m_crNew);
			}
		}
		else
		{
			CTabCustom *pCustom = (CTabCustom *) m_tabPages[1];
			ASSERT(pCustom && IsWindow(pCustom->m_hWnd));
			if (pCustom && IsWindow(pCustom->m_hWnd))
			{
				pCustom->SetRGB(m_crNew);
			}
		}
		m_tabPages[nNewTab]->ShowWindow(SW_SHOW);
	}

	m_nCurrentTab = nNewTab;

	*pResult = 0;
}

//=============================================================================
void CXColorDialog::OnSelchangingTab(NMHDR* /*pNMHDR*/, LRESULT* pResult) 
//=============================================================================
{
	m_nCurrentTab = m_TabCtrl.GetCurSel();
	*pResult = 0;
}

//=============================================================================
HBRUSH CXColorDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
//=============================================================================
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
	
	if (nCtlColor == CTLCOLOR_STATIC)
	{
		if (GetDlgItem(IDC_NEW_COLOR)->m_hWnd == pWnd->m_hWnd)
		{
			if (m_brushNew.GetSafeHandle())
				hbr = m_brushNew;
		}
		else if (GetDlgItem(IDC_CURRENT_COLOR)->m_hWnd == pWnd->m_hWnd)
		{
			if (m_brushCurrent.GetSafeHandle())
				hbr = m_brushCurrent;
		}
	}

	return hbr;
}

//=============================================================================
void CXColorDialog::OnPaint() 
//=============================================================================
{
	CPaintDC dc(this); // device context for painting
	
	// draw frame around color boxes
	CRect rect;
	GetDlgItem(IDC_NEW_COLOR)->GetWindowRect(&rect);
	ScreenToClient(&rect);
	rect.bottom += rect.Height();
	rect.InflateRect(1, 1);
	dc.Rectangle(&rect);
	
	// Do not call CDialog::OnPaint() for painting messages
}

//=============================================================================
// handler for WM_XCOLORPICKER_SELCHANGE
LRESULT CXColorDialog::OnSelChange(WPARAM wParam, LPARAM /*lParam*/)
//=============================================================================
{
	if (m_crNew != wParam)
	{
		m_crNew = wParam;
		if (m_brushNew.GetSafeHandle())
			m_brushNew.DeleteObject();
		m_brushNew.CreateSolidBrush(m_crNew);

		CRect rect;
		GetDlgItem(IDC_NEW_COLOR)->GetWindowRect(&rect);
		ScreenToClient(&rect);
		InvalidateRect(&rect, FALSE);
	}

	return 0;
}

//=============================================================================
// handler for WM_XCOLORPICKER_SELENDOK
LRESULT CXColorDialog::OnSelendOk(WPARAM /*wParam*/, LPARAM /*lParam*/)
//=============================================================================
{
	// dialog is about to close, no need to update display

	OnOK();

	return 0;
}

//=============================================================================
void CXColorDialog::GetHSL(BYTE *h, BYTE *s, BYTE *l)
//=============================================================================
{
	BOOL bHSL = FALSE;

	int nTab = m_TabCtrl.GetCurSel();

	if (nTab == 0)
	{
		CTabStandard *pStandard = (CTabStandard *) m_tabPages[0];
		ASSERT(pStandard && IsWindow(pStandard->m_hWnd));
		if (pStandard && IsWindow(pStandard->m_hWnd))
		{
			pStandard->m_ColorHexagon.GetHSL(h, s, l);
			bHSL = TRUE;
		}
	}
	else
	{
		CTabCustom *pCustom = (CTabCustom *) m_tabPages[1];
		ASSERT(pCustom && IsWindow(pCustom->m_hWnd));
		if (pCustom && IsWindow(pCustom->m_hWnd))
		{
			pCustom->m_ColorSpectrum.GetHSL(h, s, l);
			bHSL = TRUE;
		}
	}

	if (!bHSL)
	{
		// tabs not created yet, just use algorithm
		RGBtoHSL(m_crNew, h, s, l);
	}
}

//=============================================================================
int CXColorDialog::GetColorModel()
//=============================================================================
{
	int rc = 0;

	CTabCustom *pCustom = (CTabCustom *) m_tabPages[1];
	ASSERT(pCustom);

	if (pCustom)
	{
		rc = pCustom->GetColorModel();
	}

	return rc;
}

//=============================================================================
int CXColorDialog::GetCurTab()
//=============================================================================
{
	return m_nCurrentTab;
}

//=============================================================================
CXColorDialog& CXColorDialog::SetTooltipFormat(int nFormat)
//=============================================================================
{
	m_dwFlags &= ~0xF;
	m_dwFlags |= nFormat & 0xF;
	return *this;
}

//=============================================================================
CXColorDialog& CXColorDialog::SetStartTab(int nStartTab)
//=============================================================================
{
	m_dwFlags &= ~(XCD_OPEN_HEXAGON | XCD_OPEN_SPECTRUM);

	if (nStartTab != XCD_OPEN_HEXAGON && nStartTab != XCD_OPEN_SPECTRUM)
		nStartTab = XCD_OPEN_HEXAGON;

	m_dwFlags |= nStartTab;

	return *this;
}

//=============================================================================
CXColorDialog& CXColorDialog::SetCurrentColor(COLORREF cr)
//=============================================================================
{
	SetRGB(cr);
	return *this;
}

//=============================================================================
CXColorDialog& CXColorDialog::SetRGB(COLORREF cr)
//=============================================================================
{
	m_crNew = m_crCurrent = cr;
	return *this;
}

//=============================================================================
CXColorDialog& CXColorDialog::SetHSL(BYTE h, BYTE s, BYTE l)
//=============================================================================
{
	SetRGB(HSLtoRGB(h, s, l));
	return *this;
}

//=============================================================================
BOOL CXColorDialog::OnColorOK()
//=============================================================================
{
	TRACE(_T("in CXColorDialog::OnColorOK\n"));
	return 0;
}

//=============================================================================
void CXColorDialog::OnOK() 
//=============================================================================
{
	if (OnColorOK())
		return;

	CDialog::OnOK();
}

//=============================================================================
BOOL CXColorDialog::OnHelpInfo(HELPINFO* pHelpInfo)
//=============================================================================
{
	CString s = _T("");

	RECT rect;
	rect.left = pHelpInfo->MousePos.x;
	rect.top  = pHelpInfo->MousePos.y;

	int nCtrlId = pHelpInfo->iCtrlId;

	if (nCtrlId == IDC_STATIC_NEW_COLOR)
		nCtrlId = IDC_NEW_COLOR;
	else if (nCtrlId == IDC_STATIC_CURRENT_COLOR)
		nCtrlId = IDC_CURRENT_COLOR;

	CString strTitle = _T("");
	GetWindowText(strTitle);
	
	// the string resource id is the same as control id
	if (s.LoadString(nCtrlId))
	{
		CXBalloonMsg::Show(strTitle,
						   s, 
						   ::GetDlgItem(m_hWnd, pHelpInfo->iCtrlId), 
						   m_hWnd,
						   AfxGetInstanceHandle(),
						   (UINT) m_hHelpIcon,
						   TRUE,
						   30,
						   &rect);
	}
	
	return TRUE;
}

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