The WM_CTLCOLOR Messages

WM_CTLCOLOR is a message that a predefined child window control sends to its parent window procedure when the child window is about to paint its client area. The parent window can use this opportunity to alter the colors that the child window procedure will use for painting.

When the parent window procedure receives a WM_CTLCOLOR message, the wParam and lParam parameters have the following meaning:

wParam Handle to child window's device context
LOWORD (lParam) Handle to child window
HIWORD (lParam) Type of window

The high word of lParam can be one of the following:

HIWORD (lParam) Type of Window

CTLCOLOR_MSGBOX Message box
CTLCOLOR_EDIT Edit control
CTLCOLOR_LISTBOX List box control
CTLCOLOR_BTN Button control
CTLCOLOR_DLG Dialog box
CTLCOLOR_SCROLLBAR Scroll bar control
CTLCOLOR_STATIC Static control

Right now, we're interested in CTLCOLOR_BTN, the WM_CTLCOLOR message from a button control. When the parent window procedure gets this message, the child window control has already obtained its device context. The handle to this device context is in wParam. Any GDI (Graphics Device Interface) calls you make using this device context will affect the painting that the child window does when you pass control back to the child window.

You must perform three actions when processing a WM_CTLCOLOR message:

Set a text color using SetTextColor.

Set a background color using SetBkColor.

Return a handle to a brush to the child window.

A ”brush“ is a GDI object that defines a bitmapped pattern of pixels. Windows uses brushes to fill areas with color. You can get a handle to a brush using GetStockOb- ject, CreateSolidBrush, CreateHatchBrush, or CreatePatternBrush. For processing the WM_CTLCOLOR message, you'll probably use CreateSolidBrush. Before your program terminates, you must explicitly delete any brushes you create. A good time to do this is while processing the WM_DESTROY message.

For most child window controls, the color you set in SetBkColor should be the same as the color of the brush you return from the WM_CTLCOLOR message. For instance, button controls use the brush to color the background of the entire child window client area. The text background color is used only for the background behind the text. These two colors should be the same. To see how this works, let's take an example of processing a WM- CTLCOLOR message for button controls where the window procedure simply sets the normal default colors. During initialization (probably when processing a WM_CREATE message), you can create a brush:

hBrush = CreateSolidBrush (GetSysColor (COLOR_WINDOW)) ;

The hBrush brush handle should be stored in a static variable. Here's what the WM- CTLCOLOR processing looks like:

case WM_CTLCOLOR :

if (HIWORD (lParam) == CTLCOLOR_BTN)

{

SetBkColor (wParam, GetSysColor (COLOR_WINDOW)) ;

SetTextColor (wParam, GetSysColor (COLOR_WINDOWTEXT)) ;

UnrealizeObject (hBrush) ;

point.x = point.y = 0 ;

ClientToScreen (hwnd, &point) ;

SetBrushOrg (wParam, point.x, point.y) ;

return ((DWORD) hBrush) ;

}

break ;

Note that wParam is the device context handle of the button control. The four statements that culminate in the SetBrushOrg call require some further explanation.

As noted earlier, a brush defines a bitmapped pattern of pixels. When Windows uses this brush to fill an area with color, the pattern of the brush is repeated horizontally and vertically until the area is filled. The origin of this brush—the place where Windows assumes the repeating pattern begins—is the upper left corner of the client area associated with the device context.

But if you color both the client area of a parent window and the client area of a child window with this same brush, the pattern won't merge correctly at the edge of the child window because Windows is using two different origins for the same brush. To avoid this problem, you call UnrealizeObject. This function causes Windows to reset the origin of the brush the next time it is selected into a device context (which will follow the return from the WM_CTLCOLOR processing). The origin Windows will use is the one you set with SetBrushOrg; in this example, the function sets the brush origin to the screen origin of the parent window. (Don't use UnrealizeObject for a stock brush handle that you obtain from GetStockObject, and don't worry if this sounds a bit obscure right now. We'll cover the issues in more depth in Chapter 12.)

The brush we created in our example is based on the system color COLOR_WINDOW. If this color changes while the program is running, the window procedure receives a WM_SYSCOLORCHANGE message. The program deletes the brush and creates a new one:

case WM_SYSCOLORCHANGE :

DeleteObject (hBrush) ;

hBrush = CreateSolidBrush (GetSysColor (COLOR_WINDOW)) ;

return 0 ;

Finally, when the program is about to terminate, the brush should be deleted:

case WM_DESTROY :

DeleteObject (hBrush) ;

PostQuitMessage (0) ;

return 0 ;

I've shown here how you can reproduce the default processing of WM_CTLCOLOR messages for button controls. Using your own colors is much the same. You would not need to trap WM_SYSCOLORCHANGE messages unless you wanted to base the brush on a system color. We'll come back to WM_CTLCOLOR messages later in this chapter, when we use the COLORS1 program. For now, let's explore another class of child window controls.