Customing the standard color-picker dialog


Today's Little Program does a little bit of customization of the Choose­Color dialog. We do this by, um, doing what the documentation says.

For the color dialog, we take the template in color.dlg and modify it. Just to get our feet wet, we won't customize anything at all! This ensures that we have the basics down before we start trying anything fancy.

Handy tip: Before trying to customize something, first write code that does it uncustomized. That way, you have a known working starting point.

#include <windows.h>

#include <colordlg.h>

1 DIALOG LOADONCALL MOVEABLE DISCARDABLE 2, 0, 298, 184
STYLE WS_BORDER | DS_MODALFRAME | WS_CAPTION | WS_POPUP | WS_SYSMENU |
      DS_3DLOOK
CAPTION "Color"
FONT 8 "MS Shell Dlg"
BEGIN
    LTEXT           "&Basic colors:", -1, 4, 4, 140, 9
    CONTROL         "", COLOR_BOX1, "static",
                    SS_SIMPLE | WS_CHILD | WS_TABSTOP | WS_GROUP,
                    4, 14, 140, 86

    LTEXT           "&Custom colors:", -1, 4, 106, 140, 9
    CONTROL         "",  COLOR_CUSTOM1, "static",
                    SS_SIMPLE | WS_CHILD | WS_TABSTOP | WS_GROUP,
                    4, 116, 140, 28

    PUSHBUTTON      "&Define Custom Colors >>" COLOR_MIX, 4, 150, 138, 14,
                    WS_TABSTOP | WS_GROUP

    DEFPUSHBUTTON   "OK", IDOK, 4, 166, 44, 14, WS_GROUP | WS_TABSTOP
    PUSHBUTTON      "Cancel", IDCANCEL, 52, 166, 44, 14, WS_GROUP | WS_TABSTOP
    PUSHBUTTON      "&Help", pshHelp, 100, 166, 44, 14, WS_GROUP | WS_TABSTOP

    CONTROL         "", COLOR_RAINBOW, "static",
                    SS_SUNKEN | SS_SIMPLE | WS_CHILD, 152, 4, 118, 116

    CONTROL         "", COLOR_LUMSCROLL, "static",
                    SS_SUNKEN | SS_SIMPLE | WS_CHILD, 280, 4, 8, 116

    CONTROL         "", COLOR_CURRENT, "static",
                    SS_SUNKEN | SS_SIMPLE | WS_CHILD, 152, 124, 40, 26

    PUSHBUTTON      "&o", COLOR_SOLID, 300, 200, 4, 14, WS_GROUP
    RTEXT           "Color", COLOR_SOLID_LEFT, 152, 151, 20, 9
    LTEXT           "|S&olid", COLOR_SOLID_RIGHT, 172, 151, 20, 9

    RTEXT           "Hu&e:", COLOR_HUEACCEL, 194, 126, 20, 9
    EDITTEXT,       COLOR_HUE, 216, 124, 18, 12, WS_GROUP | WS_TABSTOP

    RTEXT           "&Sat:", COLOR_SATACCEL, 194, 140, 20, 9
    EDITTEXT,       COLOR_SAT, 216, 138, 18, 12, WS_GROUP | WS_TABSTOP

    RTEXT           "&amp;Lum:", COLOR_LUMACCEL, 194, 154, 20, 9
    EDITTEXT,       COLOR_LUM, 216, 152, 18, 12, WS_GROUP | WS_TABSTOP

    RTEXT           "&Red:", COLOR_REDACCEL, 243, 126, 24, 9
    EDITTEXT,       COLOR_RED, 269, 124, 18, 12, WS_GROUP | WS_TABSTOP

    RTEXT           "&Green:", COLOR_GREENACCEL, 243, 140, 24, 9
    EDITTEXT,       COLOR_GREEN, 269, 138, 18, 12, WS_GROUP | WS_TABSTOP

    RTEXT           "Bl&ue:", COLOR_BLUEACCEL, 243, 154, 24, 9
    EDITTEXT,       COLOR_BLUE, 269, 152, 18, 12, WS_GROUP | WS_TABSTOP

    PUSHBUTTON      "&Add to Custom Colors", COLOR_ADD, 152, 166, 142, 14,
                    WS_GROUP | WS_TABSTOP
END

Our resource file is just a copy of the original color.dlg file with no customizations. We stick a windows.h in front, and assign it a custom resource ID of 1. Let's see if we can display it.

#define UNICODE
#define _UNICODE
#define STRICT
#include <windows.h>
#include <commdlg.h>

int WINAPI wWinMain(
    HINSTANCE hinst, HINSTANCE hinstPrev,
    LPWSTR lpCmdLine, int nCmdShow)
{
 COLORREF rgCustColors[16] = { 0 };
 CHOOSECOLOR cc = { sizeof(cc) };
 cc.hInstance = reinterpret_cast<HWND>(hinst);
 cc.lpTemplateName = MAKEINTRESOURCE(1);
 cc.Flags = CC_ENABLETEMPLATE;
 cc.lpCustColors = rgCustColors;
 if (ChooseColor(&cc)) {
  MessageBox(nullptr, TEXT("Thank you"), TEXT("Sample"), MB_OK);
 }
 return 0;
}

The hInstance member of the CHOOSE­COLOR structure is incorrectly declared as HWND, so we need to stick in a cast to keep everybody happy.

Run this program, and... everything looks perfectly normal. Good! Now we can customize it.

First, let's just add a message to the dialog.

1 DIALOG LOADONCALL MOVEABLE DISCARDABLE 2, 0, 298, 198
...

    LTEXT           "Don't forget to smile!",
                    -1, 4, 166, 138, 14

    DEFPUSHBUTTON   "OK", IDOK, 4, 180, 44, 14, WS_GROUP | WS_TABSTOP
    PUSHBUTTON      "Cancel", IDCANCEL, 52, 180, 44, 14, WS_GROUP | WS_TABSTOP
    PUSHBUTTON      "&Help", pshHelp, 100, 180, 44, 14, WS_GROUP | WS_TABSTOP
...

Rebuild the program and run it. Hey look, a happy message! Note that in order to fit the message in the dialog box, we had to make the dialog box taller and move some buttons out of the way.

Just adding static text is nice, but it's not particularly interesting. So let's add a check box to the dialog too.

    AUTOCHECKBOX    "I remembered to s&mile",
                    1000, 4, 166, 138, 14, WS_TABSTOP

In addition to remembering the color the user chose, we also want to remember whether they checked the box that says that they smiled. The documentation says that when the hook procedure receives a WM_INIT­DIALOG, the lParam points to the CHOOSE­COLOR dialog. We therefore have two options for passing extra data to the hook procedure.

I'll use the traditional method for now. The lCust­Data is a pointer to a BOOL that receives the checkbox state on exit.

UINT_PTR CALLBACK CCHookProc(
    HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uMsg)
  {
  case WM_INITDIALOG:
    {
      auto pcc = reinterpret_cast<CHOOSECOLOR*>(lParam);
      auto pfSmiled = reinterpret_cast<BOOL*>(pcc->lCustData);
      SetProp(hdlg, TEXT("SmileResult"), pfSmiled);
    }
    break;

  case WM_DESTROY:
    {
      auto pfSmiled = reinterpret_cast<BOOL*>(
           GetProp(hdlg, TEXT("SmileResult")));
      if (pfSmiled) {
       *pfSmiled = IsDlgButtonChecked(hdlg, 1000);
      }
      RemoveProp(hdlg, TEXT("SmileResult"));
    }
  }
  return 0;
}

int WINAPI wWinMain(
    HINSTANCE hinst, HINSTANCE hinstPrev,
    LPWSTR lpCmdLine, int nCmdShow)
{
 COLORREF rgCustColors[16] = { 0 };
 BOOL fSmiled = FALSE;
 CHOOSECOLOR cc = { sizeof(cc) };
 cc.hInstance = reinterpret_cast<HWND>(hinst);
 cc.lpTemplateName = MAKEINTRESOURCE(1);
 cc.Flags = CC_ENABLETEMPLATE  | CC_ENABLEHOOK;
 cc.lpCustColors = rgCustColors;
 cc.lCustData = reinterpret_cast<LPARAM>(&fSmiled);
 cc.lpfnHook = CCHookProc;
 if (ChooseColor(&cc) && !fSmiled) {
  MessageBox(nullptr, TEXT("You forgot to smile."),
             TEXT("Sample"), MB_OK);
 }
 return 0;
}

Now, the program displays a message if you selected a color but did not check the I remembered to smile box.

Comments (11)
  1. Andrew says:

    You may want to review the post title….

  2. mc says:

    Perhaps Raymond couldn't decide between customizing and customising and so took an unusual way out of the dilemma.

  3. AsmGuru62 says:

    Nice!

    I once hooked the color dialog to real-time changes of color.

    User makes a selection in dialog controls or just drags the slider and color

    changes are seen in real-time without closing the color dialog.

  4. mikeb says:

    "Just to get our feet wet, we won't customize anything at all! This ensures that we have the basics down before we start trying anything fancy"

    Sometimes I try to take a shortcut by skipping this step when coding something new. That's pretty much always a mistake.

    PS: look for an upcoming episode of Microspeak: "Customing"

  5. 12Bitslab says:

    I think that is supposed to be Custombing.

  6. Henrik says:

    Think it's costuming.

    Putting on a new costume for the Color dialog :)

  7. alexcohn says:

    I believe that private extension of CHOOSECOLOR should not be encouraged. What if there will be new standard data fields in 2024 version of Choose­Color dialog? When the documented API provides a special dedicated pointer to user-defined data, it's safer and cleaner.

  8. Csaboka says:

    Alex: Well, CHOOSECOLOR starts with an lStructSize member just like most of the structures defined by the Windows API. Even in 2024, Windows will be able to tell that you are using the 2014 version of the struct from the fact that its first member contains the size of the 2014 version. Whatever custom data you choose to store after it will be left alone by Windows, and you can spare the (admittedly small) overhead of an extra malloc call.

  9. John Doe says:

    I bet Raymond has an open bet on how many comments will be written about the typo.  I wonder if this one counts…

  10. DebugErr says:

    On another note, I'd like to replace the old-school color picker dialog system-wide… ;)

  11. alexcohn says:

    @Csaboka: in real life scenario, your hookProc may receive messages that did not originate from your customization. You will need to decide if the extra bytes come from your code or not; some may only go _through_ your code. With lCustData, chaining customizations is a couple of bits easier.

Comments are closed.