Click here to Skip to main content
Click here to Skip to main content

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

, 5 Apr 2008
Rate this:
Please Sign up or sign in to vote.
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.

Introduction

XColorDialog mimics behavior of color picker dialog found in Microsoft Office®:
screenshot screenshot

This article is based on my articles XColorHexagonCtrl and XColorSpectrumCtrl.

XColorDialog Features and Behaviors

XColorDialog has the same visual appearance as the two screenshots above, and offers an API that is similar to CColorDialog, but without support for the multiple custom colors that you see here:

screenshot

For a detailed description of the user interface behaviors of the two tabs in XColorDialog, please see my articles XColorHexagonCtrl and XColorSpectrumCtrl.

XColorDialog is implemented using standard MFC CDialog and CTabCtrl. Two tab pages host the color controls, which communicate with the parent dialog via registered window messages. The parent dialog then keeps the color control on the other tab page informed of currently selected color, and updates color display on the parent dialog:

screenshot screenshot screenshot
When the user selects a new color by double-clicking left mouse button, this has same effect as clicking on OK button.

The Custom tab includes the same controls as the MS Office® dialog, allowing user to select the color model (RGB or HSL) and values:

screenshot

As user clicks on spin controls, color selectors (crosshair and luminosity arrow) are updated, and new color is also sent to parent dialog, so that New/Current display is continuously updated.

XColorDialog API

Here is the XColorDialog API:

Function Description
COLORREF GetColor() Retrieves RGB value for current color
int GetColorModel() Retrieves color model setting from Custom tab
int GetCurTab() Retrieves current tab
void GetHSL(BYTE *h, BYTE *s, BYTE *l) Retrieves HSL values for current color
COLORREF GetRGB() Retrieves RGB value for current color
CXColorDialog& SetColorModel(int nColorModel) Sets starting color model for Custom tab
CXColorDialog& SetCurrentColor(COLORREF cr) Sets the current color from RGB values
CXColorDialog& SetHSL(BYTE h, BYTE s, BYTE l) Sets the current color from HSL values
CXColorDialog& SetRGB(COLORREF cr) Sets the current color from RGB values
CXColorDialog& SetStartTab(int nStartTab) Sets the starting tab
CXColorDialog& SetTitle(LPCTSTR lpszTitle) Sets the title of the dialog box
CXColorDialog& SetTooltipFormat(int nFormat) Sets the tooltip format for the CXColorHexagonCtrl and CXColorSpectrumCtrl controls

Implementation Notes

The implementation of XColorDialog was fairly straightforward. The only real surprise I got had to do with the visibility of the grayscale hexagons on the Standard tab. The first time I ran the demo program, this is what I saw:

screenshot

The large white hexagon and the small grayscale hexagon closest to it are indiscernible from the background. When I took another look at the MS Office® color dialog, I saw that what I thought was a solid background color was in fact a color gradient. Using the RGB-to-HSL algorithms I already had, I quickly calculated a lower (ending) color that was 10 points darker than the start color. Then I added WM_ERASEBKGND handler to each tab page, so that I could paint the background with GDI function GradientFill():
BOOL CTabStandard::OnEraseBkgnd(CDC* pDC) 
{
    CRect rectClient;
    GetClientRect(&rectClient);

    TRIVERTEX vert[2];
    vert[0].x      = 0;
    vert[0].y      = 0;
    vert[0].Red    = (COLOR16) (GetRValue(m_crStartColor) << 8);
    vert[0].Green  = (COLOR16) (GetGValue(m_crStartColor) << 8);
    vert[0].Blue   = (COLOR16) (GetBValue(m_crStartColor) << 8);
    vert[0].Alpha  = 0x0000;

    vert[1].x      = rectClient.right;
    vert[1].y      = rectClient.bottom; 
    vert[1].Red    = (COLOR16) (GetRValue(m_crEndColor) << 8);
    vert[1].Green  = (COLOR16) (GetGValue(m_crEndColor) << 8);
    vert[1].Blue   = (COLOR16) (GetBValue(m_crEndColor) << 8);
    vert[1].Alpha  = 0x0000;

    GRADIENT_RECT rect;
    rect.UpperLeft  = 0;
    rect.LowerRight = 1;

    ::GradientFill(pDC->m_hDC, vert, 2, &rect, 1, GRADIENT_FILL_RECT_V);

    return TRUE;    //CDialog::OnEraseBkgnd(pDC);
}

Here is what the Standard tab looks like with gradient fill:

screenshot

Demo App

Here is what demo app looks like:

screenshot

How to use

The following steps assume you want to add XColorDialog to a dialog. Steps would be similar for CFormView or CPropertyPage.

Step 1 - Add Files

To integrate CXColorDialog into your app, you first need to add following files to your project:

  • CXCD.h
  • CXRect.h
  • CXToolTipCtrl.h
  • help_vista.ico
  • help_xp.ico
  • rgbhsl.cpp
  • rgbhsl.h
  • TabCustom.cpp
  • TabCustom.h
  • TabStandard.cpp
  • TabStandard.h
  • XBalloonMsg.cpp
  • XBalloonMsg.h
  • XColorDialog.cpp
  • XColorDialog.h
  • XColorDialog.rc
  • XColorDialogRes.h
  • XColorHexagonCtrl.cpp
  • XColorHexagonCtrl.h
  • XColorSpectrumCtrl.cpp
  • XColorSpectrumCtrl.h

The .cpp files marked with should be set to Not using precompiled header in Visual Studio. Otherwise, you will get error

    fatal error C1010: unexpected end of file while looking for precompiled header directive

Step 2 - Add XColorDialog Resource File

screenshot For Visual Studio 6 - go to View | Resource Includes... and in bottom listbox, scroll down to end. Insert #include "XColorDialog.rc" right before #endif:

screenshot

screenshot For Visual Studio 2005 - right-click .rc file in Resource View, then choose Resource Includes... from shortcut menu:

screenshot

In bottom listbox, scroll down to end. Insert #include "XColorDialog.rc" right before #endif:

screenshot

The dialog templates in XColorDialog.rc are invoked via string resource name rather than numeric resource ID. This technique allows you to use XColorDialog without having to worry about resource ID collisions with the other dialogs in your app.

Step 3 - Add Code to Create XColorDialog

In source module where you want to call XColorDialog, include header file XColorDialog.h. Then create XColorDialog like this:
    CMyColorDialog dlg(m_crCurrent, m_nTooltip | ((m_nStartTab+1) << 4));
    dlg.SetTitle(_T("My Colors"));
    if (dlg.DoModal() == IDOK)
    {
        //  < Add code here to handle selected color >
    }

Step 4 (Optional) - Add Derived Class

The demo app shows how you can derive your own class from CXColorDialog to make use of virtual function OnColorOK():
class CMyColorDialog : public CXColorDialog
{
public:
    CMyColorDialog(COLORREF crInitial = 0, 
                   DWORD dwFlags = XCD_TOOLTIP_NONE | XCD_OPEN_HEXAGON,
                   CWnd* pParent = NULL)
    : CXColorDialog(crInitial, dwFlags, pParent)
    {
    }

    virtual BOOL OnColorOK()
    {
        COLORREF cr = GetRGB();
        if (cr == RGB(255,0,0))
        {
            AfxMessageBox(_T("That color is not allowed."));
            return 1;
        }
        return 0;
    }
};

Revision History

Version 1.0 - 2008 April 5

  • Initial public release

Usage

This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

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

 
GeneralMy vote of 5 PinmemberFarhan Ghumra17-Jun-12 23:28 
QuestionUsing this control with VB.NET project PinmemberTonielro29-Feb-12 11:01 
GeneralProblem (and solution) with hexagonal control and 16-bit colour depth PinmemberTim Hards23-Jul-08 2:37 
GeneralDuplicate Resource: IDD_XCOLOR_DIALOG PinmvpJeffrey Walton9-Jun-08 12:33 
GeneralRe: Duplicate Resource: IDD_XCOLOR_DIALOG PinmvpHans Dietrich9-Jun-08 19:59 
GeneralRe: Duplicate Resource: IDD_XCOLOR_DIALOG PinmvpJeffrey Walton10-Jun-08 0:35 
GeneralRe: Duplicate Resource: IDD_XCOLOR_DIALOG PinmvpHans Dietrich9-Jun-08 20:05 
GeneralAutomatically determine which tab to show based on the initial colour PinmemberTim Hards23-Apr-08 6:33 
Thank you very much for this and the excellent supporting articles Hans.
 
I made a slight modification so that the dialog could automatically determine which tab to show based on the initial colour - to match Microsoft Office's behaviour. If the colour is displayed in the hexagon control, that tab is shown, otherwise the spectrum tab is shown.
 
To do this I modified CXColorHexagonCtrl so that the IsHexagonColor function was publically available. I also changed the body of this function so that instead of looking up the colours in the m_paColorCells array, it uses hard-coded knowledge of what the colours should be. This isn't very clean because it breaks the relationship between IsHexagonColor() and DrawHexagon() - but I needed to use this function before the hexagon was drawn. The rewritten function looks like this:
 
      BOOL CXColorHexagonCtrl::IsHexagonColor(COLORREF cr)
      {
            // This uses knowledge of how the hexagon is constructed in the DrawHexagon function.
 
            if (cr == RGB(0, 0, 0) || cr == RGB(255, 255, 255))
            {
                  return TRUE;
            }
 
            for (int i = 0; i < nCellColors; i++)
            {
                  if (g_crCellColors[i] == cr)
                  {
                        return TRUE;
                  }
            }
 
            for (i = 0; i < nGrayScale; i++)
            {
                  if (g_crGrayScale[i] == cr)
                  {
                        return TRUE;
                  }
            }
 
            return false;
      }
 
I then modified CXColorDialog::OnInitDialog() so that in the part which sets the initial tab, if m_dwFlags has neither XCD_OPEN_HEXAGON nor XCD_OPEN_SPECTRUM set the function decides which tab to show first. So, this:
 
      // set initial tab
      if (m_dwFlags & XCD_OPEN_SPECTRUM)
            m_nCurrentTab = 1;
 
was changed to:
 
      // set initial tab
      if (m_dwFlags & XCD_OPEN_HEXAGON)
            m_nCurrentTab = 0;
      else if (m_dwFlags & XCD_OPEN_SPECTRUM)
            m_nCurrentTab = 1;
      else
      {
            CTabStandard *pStandard = (CTabStandard *) m_tabPages[0];
            if (pStandard->m_ColorHexagon.IsHexagonColor(m_crCurrent))
                  m_nCurrentTab = 0;
            else
                  m_nCurrentTab = 1;
      }
 
For completeness, I also modified CXColorDialog::SetStartTab() to not default to XCD_OPEN_HEXAGON if neither XCD_OPEN_HEXAGON nor XCD_OPEN_SPECTRUM are set (e.g. if nStartTab is 0).
 
After all these changes, if a CXColorDialog object is constructed using 0 as the second argument, the initial tab will be automatically decided according to the specified colour.
 
I hope this is useful to someone - and thanks again Hans!
Tim
 
Tim Hards
http://timhards.com/
GeneralRe: Automatically determine which tab to show based on the initial colour PinmvpHans Dietrich23-Apr-08 16:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 6 Apr 2008
Article Copyright 2008 by Hans Dietrich
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid