Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > May 1997
May 1997


Code for this article: C++0597.exe (22KB)
Paul DiLascia is a freelance software consultant specializing in training and software development in C++ and Windows. He is the author of Windows ++: Writing Reusable Code in C++ (Addison-Wesley, 1992).

Q How do I write an app that paints textured backgrounds like the ones in Microsoft® Encarta®, Bookshelf®, and Money? I've tried handling WM_ ERASEBKGND but I can't get it to work.
Robert H. Mowery III

A I know what you're all thinking: some marketeer from the Northwest paid me to publish this "question" as a shameless plug for Microsoft products. It's not true, I swear! This is an honest-to-goodness, bona fide question from a reader! At least, as far as I know.
WM_ERASEBKGND is the right idea, but the details of painting textured backgrounds can be a real pain in the cerebrum, if you know what I mean. I wrote an app called Pform (Painted Form or Pretty Form—take your pick) that shows how to do it. Pform is a standard MFC app that uses CFormView to display a dialog as a form. It lets you display the form with several different backgrounds: plain, dents, marble with cyan controls, or all marble (see Figure 1). I chose a form-based app because dialogs give you the least amount of control over how Windows® paints things; if you use your own main window with custom controls you can do anything you like, so painting backgrounds poses less of a challenge. Figure 2 shows the code for CMyFormView. The rest of Pform is generic MFC fare that you can download from the link at the top of this article.

Figure 1: Pform--plain
Figure 1: Pform—plain

The simplest and most reliable way to create a textured background is to use something called a pattern brush. The basic idea is to handle WM_CTLCOLOR messages, which let you tell Windows what brush to use for painting the background of various controls. Windows sends WM_CTLCOLOR to the parent of a control when it's about to paint the control. This is your big chance to change the color of the control. (For more information on setting control colors, see my July 1996 column.)
In the old days there was a single message, WM_CTLCOLOR, with subcodes like CTLCOLOR_BTN for button backgrounds or CTLCOLOR_DLG for the entire dialog. In the newfangled world of Win32®, WM_CTLCOLOR has been replaced with individual WM_CTLCOLORXXX messages. Figure 3 shows all the new messages. By way of backward compatibility, MFC maps the new messages back to the old, technically obsolete WM_CTLCOLOR, which is sometimes more convenient.
In particular, Pform uses a single OnCtlColor handler to set the same background for all WM_CTLCOLORXXX messages. For the dented background, it looks like this:
HBRUSH CMyFormView::OnCtlColor(
 CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    pDC->SetBkMode(TRANSPARENT);
    pDC->SetTextColor(
        GetSysColor(COLOR_WINDOWTEXT));
    return m_brDents;
}
I set the background mode to transparent so that any text (for example, the text of a checkbox, static, or edit control)
Figure 4
Figure 4
is drawn transparently, without changing the background. Otherwise, Windows would use the current text background color. You can also call pDC->SetTextColor or SetBkColor here if you want to change the text or background color.
After setting the background mode, OnCtlColor returns a handle to a brush, m_brDents. Note that the MFC conversion operator CBrush::operator HBRUSH converts the CBrush to an HBRUSH. By returning an HBRUSH, you tell Windows to use it to draw the background of the control. To create a pattern brush, you must first load the bitmap, then call—what else?—CreatePatternBrush.
// In CMyFormView::CMyFormView
m_bmDents.LoadBitmap(IDB_DENTS);
m_brDents.CreatePatternBrush(&m_bmDents);
IDB_DENTS identifies the bitmap resource, CMyFormView::CMyFormView, loads the CBitmap m_bmDents, then calls CBrush::CreatePatternBrush to create the brush from the bitmap.Figure 4 shows the bitmap, a Lilliputian-sized square of 8 X 8 pixels that paints a single dent. When Windows paints a region with a pattern brush, it BitBlts the pattern repeatedly to give the effect you see in Figure 5.
Figure 5: Dents View
Figure 5: Dents View

Finally, you have to set the text color to COLOR_ WINDOWTEXT because otherwise the text defaults to black, even if the user has customized his screen colors. When you return a non-NULL value from WM_ONCTLCOLOR, Windows does not set the text color; you must do it yourself.
So far, so good. There's just one little problem with pattern brushes: in Windows 95, they can't be more than 8 X 8 pixels. (In Windows NT® they can be as big as you like.) If you want to use a larger bitmap in Windows 95—such as the marble pattern in Figure 6—you can't use pattern brushes and OnCtlColor. Instead, you have to handle WM_ERASEBKGND. One feature, two ways of doing it—don't you just love Windows?
Windows sends WM_ERASEBKGND just before WM_PAINT if someone has called InvalidateWindow with fErase= TRUE. The default message procedure (DefWndProc) paints the window with the system color GetSysColor(COLOR_WINDOW). However, you can handle WM_ ERASEBKGND to do your own thing. CMyFormView::OnEraseBkgnd in Pform handles it by tiling the window with the marble bitmap. That is, it paints this bitmap repeatedly—left to right, bottom to top—until the
Figure 6: Marble patterns
Figure 6:
Marble patterns
whole window is painted marble. But wait a minute! OnEraseBkgnd works for the parent window only, not the controls. How do you get radio buttons, checkboxes, and static controls to show the marble background?
You have to be tricky. In Pform, I return a hollow brush from OnCtlColor. A hollow or null brush is a brush that doesn't paint anything. You can create one by calling CGdiObject::CreateStockObject(NULL_BRUSH). Using a hollow brush is like drawing text in transparent mode: the controls leave alone whatever is on the screen as they draw themselves. So now the dialog tiles its background with the marble bitmap (in response to WM_ERASEBKGND), and the controls paint themselves with the hollow brush, giving the effect of leaving the marble background from the parent. Pretty clever, eh? Only it doesn't work for two types of controls: edit controls and static icons.
The problem for edit controls is that they can be edited. If the user types abc and then presses Backspace to delete the c, the edit control updates itself by painting first ab, then the remaining blank area with the background brush returned from OnCtlColor. Normally, this would erase the c, but since I've overridden OnCtlColor to use a hollow brush, the c is never erased. Sigh. You can observe this buggy behavior yourself if you download and build Pform, select View | Marble—Hollow Brush with View | Paint Edit Controls Too checked, then type into the edit control and try backspacing after you've typed. The deleted characters refuse to go away—you have to resize to get rid of them.
The hollow brush technique also fails with static icons that use a transparent color. Icons are 16 X 16 or 32 X 32 bitmaps that let you use a transparent color that's supposed to allow what's under them show through. This is how icons appear nonrectangular. If you return a colored or pattern brush, icons work fine; if you return a hollow brush from OnCtlColor, Windows doesn't draw the icon properly.
Personally, I consider this a bug, but it's easy to guess how it arises: the static control probably allocates a memory DC, paints it with the background brush, then draws the icon transparently and blts it to the screen. If the background brush is hollow, the background is left with whatever random memory comes up when Windows allocates the DC.
So how do you fix these two problems? For edit controls, I simply punted:

 HBRUSH CMyFormView::OnCtlColor(...)
 {
         if (nCtlColor==CTLCOLOR_EDIT)
                  // normal processing:
                  // don't do anything
 return CFormView::OnCtlColor(pDC,
                    pWnd, nCtlColor);
 •
 •
 •
 }
Figure 7 White edit controls
Figure 7 White edit controls

Now edit controls appear white (actually WINDOW_COLOR) when normal (see Figure 7). Personally, I think it's a good idea to leave edit controls alone anyway. If you really want your edit controls to have the bitmap background, you'll have a lot of work to do because the edit control does a lot of incremental painting as you type. The procedure is beyond the scope of a single column, but a good compromise is to use a solid color that looks good with your bitmap, like the cyan brush I used in Figure 8.
Figure 8 A cyan background
Figure 8 A cyan background

With static icons, you have two choices. You can either design your icon without the transparent color, or you can use an owner draw icon, which is something new for Windows 95. For Pform, I chose the latter approach, which I'll describe in just a minute. But first, I'll point out a few other issues you need to consider when doing textured backgrounds. In particular, you have to consider how legible the text is against your background. As Figure 7 shows, if your bitmap has dark colors in it, the text looks pretty ratty. The best solution is to use a solid background brush that looks good with your bitmap, as in Figure 8. Alternatively, you can choose a bitmap that uses only light colors. Of course, even then you never know what zipperhead will set his text color to yellow, so you might want to force black text—but then you're starting to stray from the user interface guidelines—ideally you should let the user set the text color.
Finally, I have to explain how you make buttons blend into the background. OnCtlColor lets you control only the background color of the button, not its face. This is rather dopey since buttons don't actually have any background that shows. To color the face, you have to implement an owner draw button and handle WM_DRAWITEM messages. Pform shows how to do it; it's mostly just a lot of boring mechanics.
CMyFormView::OnDrawItem does the work. It draws either a solid cyan or a marbled button with text using COLOR_BTNTEXT and a border of the same color, shifted down and to the right if the button is pressed. The same function also takes care of the static icon for the case when Pform is using the marble with the hollow brush: after painting the icon background with marble, it calls DrawIcon to manually draw the icon. Nit-pickers will notice that I fudged in both situations by not aligning the marble bitmap. Technically, I should calculate where within the bitmap to set the paint origin, but this was more than my poor brain could tolerate at one in the morning under deadline, so I simply started at (0,0). If you look closely, you can see that the marble bitmap is off slightly inside the buttons and static icon in Figure 7, but from a distance and without your geek magnifying lenses it looks OK. Having a border around the control helps, which is why I made the static icon sunken. For pattern brushes (such as the dents example), you have to use SetBrushOrgEx to set the origin of the brush, which, as I said, is too much of a brain strain. I left the buttons normal in the dented view, which is solid grey. My implementation of OnDrawItem for the buttons is also less than stellar in that I don't draw a focus rectangle—something else for you to do some lonely evening. (Hint: use DrawFocusRect.)
I leave the nitty-gritty brush origin stuff as an exercise for advanced or lonely readers who have nothing better to do with their evenings. One of the many peculiar differences between Windows 95 and Windows NT is that Windows NT automatically aligns pattern brushes for you, so you never have to bother with SetBrushOrgEx.
So, there you have it—a lot of messy work. As always with Windows, what looks pretty on the outside is ghastly on the inside. If you want one of those fancy UIs that Encarta and company have, there are no easy shortcuts. You'll have to grit your teeth and write some code—more code than I have space to show you here if you want edit controls, too. At least if you write the code as a collection of C++ classes, you can reuse them in more than one app. Whether or not you decide to take the plunge depends on what your users want. One final word of caution: if you go with a bitmap, pick one that uses only the 20 standard colors in the system palette, otherwise you'll have another nightmare realizing palettes.

Q I have a form-based app that I built using MFC's CFormView class. Whenever I resize the main window, the display flickers a lot. Is there some way I can get rid of the flicker? While a lot of commercial apps do flicker, I've noticed that others, such as Microsoft Developer Studio, don't flicker when I resize, so I figure there must be a way to get a smooth display during sizing.

From numerous readers

A Display flicker is one of those annoying bugaboos that seems to creep into every program sooner or later. The cause is always the same: some part of your program paints on the screen, and then some other part paints something different over it. For a brief moment between the two paint operations, the user can see the first thing painted. While the causes are fundamentally the same, there are two different situations to consider when attempting to foil the flicker demon.
If all of the painting happens within a single window, you can almost always eliminate flicker by painting into a memory device context (DC), then blasting the final DC onto the screen. I showed readers how to do this in my January 1997 column, where I described how to draw a shaded caption bar. There, the flicker arose because my program first drew the shaded caption bar, then the title text over it. For a brief moment, the caption-with-no-text was visible before I painted the text immediately over it. The solution was to create a compatible device context in memory, draw everything into it, and then—only when the caption was fully drawn—call BitBlt to blast the final bits from memory to screen.
Flicker also can appear when you have a window with child windows. A typical example of this is a form or dialog. The main window is just a blank rectangle on which various child windows or controls (such as edit controls, listboxes, buttons, and so on) draw themselves. In this scenario, flicker arises when Windows erases the main window background because your program called InvalidateWindow with fErase=TRUE. Windows doesn't actually erase the window when you or it calls InvalidateWindow(TRUE). It erases the window later when it gets around to painting—either because there's nothing else to do or because someone called UpdateWindow to force it. Either way, before sending WM_PAINT, Windows asks the window to erase itself by sending another message: WM_ ERASEBKGND. The default window procedure (DefWndProc) responds by painting the window with whatever color is returned by GetSysColor(COLOR_WINDOW), normally white. Once the window has erased itself, Windows sends WM_PAINT and the window paints itself. (In the case of a form/dialog, the window doesn't paint anything; only the child controls actually paint something.) The result is flicker: first you see the entire window go blank (WM_ERASEBKGND), then all of the controls paint themselves (WM_PAINT). Figure 9 illustrates the situation. The flicker is especially noticeable when the user sizes the window because Windows constantly erases and repaints all the controls. The more controls, the worse the flicker.

Figure 9 Normal Erase/Paint Sequence
Figure 9 Normal Erase/Paint Sequence

If you're a Windows guru—or if you read my answer to the previous question—you might think the solution would be to handle WM_ERASEBKGND somehow. A good idea, but there's an easier way. One of the styles you can specify when you create a window is WS_CLIPCHILDREN. This tells Windows that whenever a program or Windows itself attempts to paint the window, it should clip or remove all child windows from the painted area. In other words, even if you draw over the entire window rectangle, Windows won't paint inside the child controls (see Figure 10). The clipping applies to the erase operation Windows does in response to WM_ERASEBKGND because it calls normal GDI drawing functions. So all you have to do to eliminate parent/child flicker is set WS_CLIPCHILDREN for any window that has child windows. This entails some performance penalty, but it's not as bad as the flicker.
Figure 10  Erase/Paint Sequence with WS_CLIPCHILDREN
Figure 10 Erase/Paint Sequence with WS_CLIPCHILDREN

Of course, there's always a catch. The WS_CLIPCHILDREN trick fails if any of the controls are hollow—that is, dependent on the parent having painted the background behind them. This is the case with the marble-with-hollow-brush background in the Pform program from the previous question. There, the main window tiles the entire client area with the marble bitmap, then the controls paint over it using a hollow brush. In this scenario, WS_CLIPCHILDREN shouldn't be set because the controls depend on the parent having painted their backgrounds under them. The only solution I can think of would be to subclass all the child controls so they paint their backgrounds with the marble bitmap. For Pform, I decided that was too much work, so I punted the case of marble texture with the hollow brush. Hey, I can live with a little flicker.
Just to show off, I changed WS_CLIPCHILDREN dynamically in my OnSize function.

 void CMyFormView::OnSize(UINT nType, int cx, int cy)
 {
  if (m_nWhich != ID_VIEW_MARBLE_HOLLOW)
       // turn on WS_CLIPCHILDREN
       ModifyStyle(0, WS_CLIPCHILDREN);

  // Do default thing
  // default handler
 CFormView::OnSize(nType, cx, cy);

 if (m_nWhich!=ID_VIEW_MARBLE_HOLLOW)
    {
     // force repaint now
     UpdateWindow();

     // turn off WS_CLIPCHILDREN
     ModifyStyle(WS_CLIPCHILDREN, 0);
    }
 }
Normally, WS_CLIPCHILDREN is off. When the user sizes the window, I turn it on for the size operation to eliminate flicker—unless the form is currently using the hollow marble brush, in which case I leave WS_CLIPCHILDREN off the whole time (and suffer the flicker). For most vanilla apps that don't use the hollow brush trick, you won't need to set WS_CLIPCHILDREN dynamically. All you have to do is check Clip children under Styles in the Developer Studio dialog property sheet (see Figure 11).
Figure 11 Dialog Property Sheet
Figure 11 Dialog Property Sheet

I had to do it this way because Pform is an especially bizarre app that displays its form several different ways—and to remind you that style flags can be changed dynamically if you ever need to be really sneaky.

Have a question about programming in C or C++? Send it to askpd@pobox.com

From the May 1997 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1997 Microsoft Corporation. All rights reserved. Legal Notices.

© 2015 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft