Click here to Skip to main content
Click here to Skip to main content
Go to top

Color Picker by Owner Drawn List Box and Combo Box

, 30 Jul 2002
Rate this:
Please Sign up or sign in to vote.
Demonstrates how to implement a WIN32 color picker by owner drawn list box or combo box
<!-- Download Links -->

Sample Image - fig0.jpg

Introduction

Although there are many fully functional color pickers on this site [1, 2, 3], the code is usually MFC or WTL stuff. I would like to present here a color picker written in nothing but the Windows API. I also want to convince you that the clean and basic API functions can produce friendly, neat and powerful controls.

First, I will describe how to produce a partial office-like color picker panel in detail, by using an owner drawn list box. Second, I will briefly explain how to do a similar thing using an owner draw combo box.

Implementation

Firstly, you must start Visual C++ and then create a Win32 application with a typical "Hello World" string displayed. Next what we need to do is to create a dialog box and then embed a list box within it. The properties sheet of the list box should be like this:

Now we need to write code to draw the list box ourselves. Don't panic because there are not so many lines of code. We only need to respond to some messages in the callback function of the dialog box, which is the owner window of the list box.

/**
 * Callback function of "Background color" dialog box.
 */
LRESULT CALLBACK background(HWND hdlg, UINT message, WPARAM wParam, LPARAM lparam)
{    
    switch (message)
    {
    case WM_INITDIALOG:
        // Place your own code here
    case WM_MEASUREITEM:
        // Place your own code here   
    case WM_DRAWITEM:
        // Place your own code here
    case WM_CTLCOLORLISTBOX:
        // Place your own code here
    case WM_COMMAND:
        // Place your own code here
    }
    return FALSE;
}

For initializing the list box control, we need to process the WM_INITDIALOG message. We do nothing but assign the color values pre-defined as global variables to each list box items:

case WM_INITDIALOG:
    int nColor;
    for (nColor = 0; nColor < sizeof(g_crItems)/sizeof(DWORD); nColor++)
    {
        SendDlgItemMessage(hdlg, IDC_BACKGROUND, LB_ADDSTRING, nColor, (LPARAM) "");
        SendDlgItemMessage(hdlg, IDC_BACKGROUND, LB_SETITEMDATA, 
                           nColor, (LPARAM) g_crItems[nColor]);
        if (g_bgColor == g_crItems[nColor])
            SendDlgItemMessage(hdlg, IDC_BACKGROUND, LB_SETCURSEL, nColor, 0);
    }
    return TRUE;

Next, we need to tell the dialog the dimension of every list box item when receiving the WM_MEASUREITEM message. The message is sent when the list box control is created. The parameter lparam is a pointer to a structure of type MEASUREITEMSTRUCT. In the demo color picker panel, we have 6 rows and 8 columns.

case WM_MEASUREITEM:
    RECT rc;
    LPMEASUREITEMSTRUCT lpmis; 
    lpmis = (LPMEASUREITEMSTRUCT) lparam; 
    GetWindowRect(GetDlgItem(hdlg, lpmis->CtlID), &rc);
    lpmis->itemHeight = (rc.bottom-rc.top)/6; 
    lpmis->itemWidth = (rc.right-rc.left)/8;
    break;

An owner drawn control sends its parent window a WM_DRAWITEM message whenever it needs to be repainted. This occurs when the control is first created, when it is pressed or released, when it gains or loses the input focus, and whenever else it needs repainting. The lparam message parameter is a pointer to a structure of type DRAWITEMSTRUCT. It contains the information necessary for a program to draw the control. The structure fields important for working with list box are hDC (the device context), rcItem (a RECT structure providing the size of each list box item), (list box ID), and <code>itemState (which indicates whether the item is pushed or has the input focus).

case WM_DRAWITEM:
    HDC hdc;
    COLORREF cr;
    HBRUSH hbrush;

    DRAWITEMSTRUCT * pdis;

    pdis = (DRAWITEMSTRUCT *)lparam;
    hdc = pdis->hDC;
    rc = pdis->rcItem;

    // Transparent.
    SetBkMode(hdc,TRANSPARENT);

    // NULL object
    if (pdis->itemID == -1) return 0; 

    switch (pdis->itemAction)
    {
    case ODA_DRAWENTIRE:
        switch (pdis->CtlID)
        {
        case IDC_BACKGROUND:
            rc = pdis->rcItem;
            cr = (COLORREF) pdis->itemData;
            InflateRect(&rc, -3, -3);
            hbrush = CreateSolidBrush((COLORREF)cr);
            FillRect(hdc, &rc, hbrush);
            DeleteObject(hbrush);
            FrameRect(hdc, &rc, (HBRUSH) GetStockObject(GRAY_BRUSH));
            break;
        }
        // *** FALL THROUGH ***
    case ODA_SELECT:
        rc = pdis->rcItem;
        if (pdis->itemState & ODS_SELECTED)
        {
            rc.bottom --;
            rc.right --;
            // Draw the lighted side.
            HPEN hpen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNSHADOW));
            HPEN holdPen = (HPEN)SelectObject(hdc, hpen);
            MoveToEx(hdc, rc.left, rc.bottom, NULL);
            LineTo(hdc, rc.left, rc.top);
            LineTo(hdc, rc.right, rc.top);
            SelectObject(hdc, holdPen);
            DeleteObject(hpen);
            // Draw the darkened side.
            hpen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNHIGHLIGHT));
            holdPen = (HPEN)SelectObject(hdc, hpen);
            LineTo(hdc, rc.right, rc.bottom);
            LineTo(hdc, rc.left, rc.bottom);
            SelectObject(hdc, holdPen);
            DeleteObject(hpen);
        }
        else 
        {  
            hbrush = CreateSolidBrush(GetSysColor(COLOR_3DFACE));
            FrameRect(hdc, &rc, hbrush);
            DeleteObject(hbrush);
        }
        break;
    case ODA_FOCUS:
        rc = pdis->rcItem;
        InflateRect(&rc, -2, -2);
        DrawFocusRect(hdc, &rc);
        break;
    default:
        break;
    }
    return true;

The default background color of the list box is white, which make it look weird because the background of the dialog box is gray. To modify the background color of the list box, we need to respond to the WM_CTLCOLORLISTBOX message. This message is sent before the system draws the list box. We simply return a handle to a brush which the system uses to paint the background of the list box.

case WM_CTLCOLORLISTBOX:
    return (LRESULT) CreateSolidBrush(GetSysColor(COLOR_3DFACE));

Finally, we need to retrieve the color that the user has selected when the "OK" button is clicked, or just dismiss the dialog box if the user click the "CANCEL" button.

case WM_COMMAND:
    if (LOWORD(wParam) == IDOK) 
    {
        int nItem;
        nItem = SendDlgItemMessage(hdlg, IDC_BACKGROUND, LB_GETCURSEL, 0, 0L);
        g_bgColor = SendDlgItemMessage(hdlg, 
                                       IDC_BACKGROUND, 
                                       LB_GETITEMDATA, 
                                       nItem, 0L);
        EndDialog(hdlg, LOWORD(wParam));
        InvalidateRect(GetParent(hdlg), NULL, true);
        return TRUE;
    }
    else if (LOWORD(wParam) == IDCANCEL)
    {
        EndDialog(hdlg, LOWORD(wParam));
        return TRUE;
    }
    break;

Ahaaa, that's all. Compile the code and you will get a color picker as shown at the top of this article. This is not the whole story, however. By changing the content of the list box items and controlling their size, more fancy controls could be created by this way. The picture below shows not only a color picker, but also a line type selector and a marker selector.

For a combo box, only little modifications are needed. First, replace the message prefix LB_ by CB_ and second, WM_CTLCOLORLISTBOX no longer needs to be processed. The final appearance of the dialog looks like this:

Well, this is not good enough as the name of each color should be put on the right hand side of each item. Charles Petzold (author of Programming Windows 5th edition) has nice example of how to do this.

Problems

For the owner drawn list box color picker, if it is the first control in the tab order, the focus rectangle could not be drawn correctly. I spent lot of time trying to correct this but finally gave up. Also, when moving the mouse cursor over each color item, it would be better to popup a tooltip that displays the color name. Your feedback and code enhancements are highly appreciated

License

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

Share

About the Author

Li Zhaoming
Engineer
China China
My home locates in Shenzhen, China. I am working on optical communications.

My programming experience includes C/C++, java, Visual Basic etc
 
At my leisure time, I wrote a small application named "Datascape" by using pure windows api. The application can be found in TUCOWS and Download.com

Comments and Discussions

 
GeneralMy vote of 5 Pinmembermanoj kumar choubey23-Feb-12 0:39 
GeneralMFC Pinsusslaiju8-Jul-05 20:18 
GeneralLine Picker PinmemberAntonDel13-Nov-03 23:15 
GeneralRe: Line Picker PinmemberLi Zhaoming23-Nov-03 23:03 
GeneralDouble click event PinmemberSylvain Piette16-Apr-03 5:02 

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
Web01 | 2.8.140922.1 | Last Updated 31 Jul 2002
Article Copyright 2002 by Li Zhaoming
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid