Borland Online And The Cobb Group Present:


March, 1996 - Vol. 3 No. 3

Objectwindows programming - Coloring controls in OWL applications

by Kent Reisdorph

As you know, the default Windows color scheme is black text on a white background. This is fine for many applications­­but others call for a touch of color. You may have an application in which you want to change only the background color. Perhaps you want to change both the text and background colors. Or, you may want to highlight certain dialog box text in color.

Windows is a complicated beast, and one of the complications involves its use of color. What works in some contexts doesn't work in others. In this article, we'll show you how to change the background and text colors of both main windows and controls in your OWL applications. We'll also examine Windows' WM_CTLCOLOR message and OWL's handling of that message via the EvCtlColor() function. Before doing that, though, let's look at the different ways you can select colors using OWL's TColor class.

Color my world

Windows uses two types of color "objects" to paint onscreen­­pens and brushes. The specifics of how the Windows API handles these objects is beyond the scope of this article. (We also don't need to concern ourselves with these details because OWL encapsulates pens and brushes using the TPen and TBrush classes.)

A pen draws onscreen lines. Windows will draw a square, circle, rectangle, or simple line using the current pen. If you don't create a pen explicitly and select it into a device context, Windows will use the current default pen.

A brush fills a screen region. Any drawing operations that require color fill use the currently selected brush. For instance, the TDC class member function Rectangle() will draw the outline of a rectangle using the current pen and will fill the rectangle using the currently selected brush.

Closely related to pens and brushes (in terms of usage, not hierarchy) is the OWL class TColor. Both TPen and TBrush have several constructors, but the most commonly used is the constructor that takes a TColor object. The TColor class has several static TColor constants defined for commonly used system colors. For example, to create a light blue brush, you can use the TColor constant LtBlue in this code:

TBrush(TColor::LtBlue);

One of the TColor constructors allows you to specify the red, green, and blue components that make up a particular color. For example, using that constructor, the RGB equivalent of TColor::LtBlue is

TColor(0, 0, 255);

Keep in mind that the color you request and the actual color Windows displays will depend on the current palette. Windows will find the closest match to the color you request in the system palette, and display that color. Now that you know how to request specific colors, let's look at how you can use them.

Coloring the window background

Coloring the window background might be the most basic window-coloring need. As usual, OWL offers many ways to accomplish this task. In a straight Windows API program, you change the background color by setting the hbrBackground member of the WNDCLASS structure before creating the window. You can use the same technique in OWL, if you choose. If you elect to use this method, you must override two OWL functions: GetClassName() and GetWindowClass(). GetWindowClass() is a virtual function, but before your application can call it, you must first override GetClassName() and return a unique class name. The implementation of this method would look like the following:

char far* MyWindow::GetClassName()
{
  return "UniqueClassName";
}

void MyWindow::GetWindowClass(WNDCLASS far& wc)
{
  TWindow::GetWindowClass(wc);
  static TBrush yellow(TColor::LtYellow);
  wc.hbrBackground = yellow;
}

You can also change the background color by catching the WM_ERASEBKGND message and painting the client area of the window in the EvEraseBkgnd() function. To do so, first add the response table entry, EV_WM_ERASEBKGND, to your response table. Next, override EvEraseBkgnd() as follows:

bool MyWindow::EvEraseBkgnd(HDC hdc)
{
  TDC dc(hdc);
  dc.FillRect(GetClientRect(), 
	TBrush(TColor::LtYellow));
  return true;
}

This code simply paints a rectangle with the desired color on the screen each time your application receives a WM_ERASEBKGND message.

The simplest method of setting an application's background color is to use the TWindow function SetBkgndColor(). To do so, you can enter a line such as

SetBkgndColor(TColor::LtYellow);

Each method has its own merits. For most applications, SetBkgndColor() will be all you need to change the main window's background color. However, if you pass an RGB value to SetBkgndColor(), the function will find the closest match in the system palette and use that color. The resulting color may not be what you expected to see. For an example of how to set the background color to an exact color, see "Getting What You Ask For," below.

Coloring text in a window

Changing text color is fairly straightforward­­you simply call SetTextColor(). Since SetTextColor() is a TDC function, you first need to create a TDC object. Note that if you change the background color of your application­­to green, for instance­­and then use TextOut() to display your text, you'll get black text with a white background over the green window background.

To eliminate this effect, you need to set the text's background mode to transparent. Doing so will let the application's background color show through. The following code snippet pulls all of this together by illustrating how to display blue text on a yellow background:

SetBkgndColor(TColor::LtYellow);
TClientDC dc(*this);
dc.SetTextColor(TColor::LtBlue);
dc.SetBkMode(TRANSPARENT);
dc.TextOut(20, 20, "This is a test.");

Changing colors in controls

Changing colors in controls is a little trickier than changing colors in the main window of your application. Windows is designed so that the parent window is responsible for controlling the colors of its children. Windows sends a WM_CTLCOLOR message to the parent window for each child window. OWL handles this message through the EvCtlColor() function.

Take a look at the function signature for EvCtlColor():

HBRUSH EvCtlColor(HDC hdc, HWND hWndCtl, uint ctlType);

The first parameter is an HDC, which is a handle to the device context for the window for which the WM_CTLCOLOR message is being sent. You can use this HDC to perform any color changes­­with one exception, which we'll discuss when we look at the return value of EvCtlColor().

The second parameter is the handle (HWND) of the window for which the WM_CTLCOLOR message is being sent. This could be the handle of the parent window in the case of a dialog, or of any of its children. You can use this parameter to watch for a particular window and then change the colors of that window.

The third parameter is the control type, which could include any of the values shown in Table A. (This list isn't comprehensive. For a complete list of control types, see the online help for the WM_CTLCOLOR message.) By using the control-type parameter, you can change the text and background colors of a whole class of controls. For example, if you wanted to give all the edit controls a blue background, you could check to see if the control type equals CTLCOLOR_EDIT.

Table A: Control types
Control type Value
CTLCOLOR_DLG Dialog box
CTLCOLOR_EDIT Edit control
CTLCOLOR_LISTBOX List box
CTLCOLOR_STATIC Static control

Finally, the return type of EvCtlColor is a handle to a brush, or an HBRUSH. You can use most of the same functions we talked about earlier to change text colors for a control, but to change the background color, you return the handle to a brush of the desired color from EvCtlColor(). Let's look at an example.

Seeing red

Suppose you have a dialog box that contains one edit control and several other controls. You need the edit control to have a gray background and red text. You'd write the EvCtlColor() function for the dialog box as shown in Figure A.


Figure A - This EvCtlColor( ) function causes a dialog box's edit controls to be gray with red text.

TBrush* grayBrush = new TBrush(TColor::LtGray);
HBRUSH MyDialog::EvCtlColor(HDC hdc, HWND hWndCtl, uint ctlType)
{
  if (ctlType == CTLCOLOR_EDIT) {
    TDC dc(hdc);
    dc.SetTextColor(TColor::LtRed);
    dc.SetBkColor(*grayBrush);
    return *grayBrush;
  }
  return TDialog::EvCtlColor(hdc, hWndCtl, ctlType);
}

Note that you first create a global TBrush pointer called grayBrush. In reality, this brush would probably be a member of your dialog class. You need to have a global or class member TBrush object because you can't return a temporary object from EvCtlColor()­­the brush must exist for the life of the dialog box. It's important to realize the returned brush sets the background color for the control.

In this example, you check to see if the control type equals CTLCOLOR_EDIT. If the control type is not an edit control, then you simply call the base class EvCtlColor and return that value. Doing so allows default color processing for all controls except the edit control. If the control type indicates that the WM_CTLCOLOR message was sent for the edit control, then you have work to do. First, you set the text color to light red. Next, you set the text background color to gray, using the TBrush pointer created earlier. (OWL note: When we dereference a TBrush pointer, OWL returns either the TBrush object or an HBRUSH, depending on what casting we're applying. OWL accomplishes this via the TBrush::operator HBRUSH() function. Since the return value of EvCtlColor is an HBRUSH, we make an implicit cast to HBRUSH, and as a result, EvCtlColor returns the HBRUSH. This same feature applies to some other OWL GDI classes, such as TFont, TPen, and TPalette.) Finally, you set the edit control's background color to gray by returning a handle to your grayBrush object.

This example causes all edit controls in the dialog box to be gray with red text. If you want to modify only a specific control, then you can test the handle of the control against the HWND passed to EvCtlColor(). In such a case, the if statement on the fifth line of Figure A would look like this:

if (hWnd == myEdit->HWindow) { . . . }

You can specify the color for each control in a dialog box or in a window by using this method. Listing A contains an example program that demonstrates our technique in full.


Listing A: COLORS2.CPP

#include <owl\owlpch.h>
#pragma hdrstop

class ColorWindow : public TWindow {
	public:
		ColorWindow();
		~ColorWindow();
		void SetupWindow();
		void Paint(TDC&, bool, TRect&);
		HBRUSH EvCtlColor(HDC, HWND, uint);
		TEdit* ColoredEdit;
		TBrush* GrayBrush;

	DECLARE_RESPONSE_TABLE(ColorWindow);
};

DEFINE_RESPONSE_TABLE1(ColorWindow, TWindow)
	EV_WM_CTLCOLOR,
END_RESPONSE_TABLE;

ColorWindow::ColorWindow()
	: TWindow(0, 0, 0)
{
	ColoredEdit = new TEdit(this, 101,
		"This is an edit control with red "
        "text on a gray background.",
		 20, 60, 200, 60);
	ColoredEdit->Attr.Style |= ES_MULTILINE;
	ColoredEdit->Attr.Style &= ~ES_AUTOHSCROLL;
	TEdit* edit = new TEdit(this, 101,
		"This edit control's colors are not "
        "modified in this example.",
		 20, 140, 200, 60);
	edit->Attr.Style |= ES_MULTILINE;
	edit->Attr.Style &= ~ES_AUTOHSCROLL;
	GrayBrush = new TBrush(TColor::LtGray);
}

ColorWindow::~ColorWindow()
{
	delete GrayBrush;
}

void ColorWindow::SetupWindow()
{
	TWindow::SetupWindow();
	SetBkgndColor(TColor::LtYellow);
}

void ColorWindow::Paint(TDC& dc, bool, TRect&)
{
	dc.SetTextColor(TColor::LtBlue);
	dc.SetBkMode(TRANSPARENT);
	dc.TextOut(20, 20,
	 "The window's background is yellow. "
     "This text is blue.");
}

HBRUSH ColorWindow::EvCtlColor(HDC hDC, HWND hWnd, uint)
{
	if (hWnd == ColoredEdit->HWindow) {
		TDC dc(hDC);
		dc.SetTextColor(TColor::LtRed);
		dc.SetBkColor(TColor::LtGray);
		return *GrayBrush;
	}
	else return (HBRUSH)DefaultProcessing();
}

class TestApp : public TApplication {
	public:
		TestApp() : TApplication("Color Test App") {}
		void InitMainWindow()
		{
			MainWindow = new TFrameWindow(0,
			 "Color Test App", new ColorWindow());
		}
};

int OwlMain(int /*argc*/, char* /*argv*/ [])
{
	TestApp app;
	return app.Run();
}

Kent Reisdorph is a professional C++ developer and a member of TeamB, Borland's online support team. You can contact Kent via CompuServe at 75522,1174.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.