July 7th, 2014

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           "&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.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.