Setting View Properties with MFC

Dear Dr. GUI:

What is the best way to change the background color of a CFormView? I would rather not use OnEraseBackground because the code would execute repeatedly. I would rather register the correct window class background color and pass that to CFormView to use during initialization. How do you initialize the properties of a CFormView?

Don Baechtel

Dr. GUI replies:

Now Don has touched on an insidious subject that afflicts a substantial, but silent (and subsequently Dr. GUI T-shirtless), portion of the Hippocratic faithful. When your CFormView derived object's window is created, you are actually using CreateDialog, rather than the CreateWindow function, to create the window as a child dialog box from the dialog template you designed with App Studio. Because CreateDialog will use only the Windows internal window class, WC_DIALOG, you are forced to accept some unmodifiable window properties, one of which is the background color. A brief foray into the realm of OOP-madness might lead you to try setting the background color with CWinApp::SetDialogBkColor; however, slightly more sane logic would remind you that CFormView is not derived from CDialog and that this method would have no effect on your view's background color. Hmmmm...let's try to trace back to what Windows does when we're not tinkering with the system. That seems easy enough, because Windows just sends a WM_ERASEBKGND message when the window's background needs to be redrawn. Does this mean we write a message handler for WM_ERASEBKGND? Doing so would repaint the background as we want, but how would the background of the controls on the view be redrawn? Once again, the answer lies in how Windows does it. Investigate the default dialog procedure (check the Visual C++ 1.5 DEFPROCS sample in the Development Library for DEFDLG.C) and you will find that WM_ERASEBKGND is dealt with by a call to FillWindow before returning a value of TRUE:

case WM_ERASEBKGND:
   FillWindow(hwnd, hwnd, (HDC) wParam, (HBRUSH) CTLCOLOR_DLG);
   return ((LRESULT) (LONG) TRUE);

If you've skipped ahead and tried to find FillWindow in any of the documentation sources, you will have noticed that it isn't anywhere to be found. Fortunately, Dr. GUI, diviner of all functions obscure and nested, can help demystify this for you. Drum roll, please...it fills a window!! Admittedly, that was a bit anticlimactic, but only because I haven't told you the interesting bit yet. If the HBRUSH parameter with which FillWindow is called is one of a few special values, such as CTLCOLOR_DLG as above, it will send a WM_CTLCOLOR message to the window's parent. This is where we should step in with a message handler in our CFormView derived class. Specifically, we want to create a WM_CTLCOLOR message handler, OnCtlColor, that returns a handle to a brush of the desired background color when it is passed a CTLCOLOR_DLG value.

With the view's background dealt with properly, we need to start thinking about redrawing the controls on our view. Conveniently enough, the other CTLCOLOR_XXX values that accompany WM_CTLCOLOR correspond to the different types of controls, so all we need to do is check for the appropriate values in our OnCtlColor function. Controls that have text attached to them, such as radio buttons and static controls, require an additional processing step to color the background beneath the text. For example, to differentiate text tags with a different color, call CDC::SetBkColor from the appropriate CTLCOLOR_XXX handler inside OnCtlColor. If, on the other hand, you'd like to maintain a consistent background color, use CDC::SetBkMode to change the background mode to TRANSPARENT.

The condensed version of all of this is pretty simple: Set the background color of a CFormView derived object and the controls on it by checking for the appropriate CTLCOLOR_XXX in the OnCtlColor message handler of your CFormView derived class.

Now that we've dealt with the fundamentals, someone has invariably done a bit of exploring and found that CTLCOLOR_LISTBOX and CTLCOLOR_EDIT do not take care of the corresponding components of a combo-box control. A brief review of the WM_CTLCOLOR message will remind you that this message is passed to the parent of the window that needs to be redrawn, which, in our case, is the CFormView derived class for the view's background and "simple" controls. Combo boxes are a bit more complex than their brethren, however, as there are child windows for the edit control and list box components of the combo-box control. Herein lies our problem. When the child windows need to have their backgrounds redrawn, they are sending WM_CTLCOLOR messages to the combo-box control object rather than to the parent's parent, CFormView. The solution is to derive a class from CComboBox to augment its behavior and then attach it to the control object via subclassing. Because this subject is outside the scope of our discussion, I will simply point you to an excellent treatment of the subject by my gifted cohort, Dale Rogerson, titled "Subclassing Windows with the Microsoft Foundation Class Library," which can be found in the Technical Articles section of the Microsoft Development Library. For specific implementation issues, please review the VIEWPROP sample that is included in the January 1995 Development Library. This sample demonstrates everything we have talked about, including how to subclass combo boxes so that you can change their background color.