Subclassing and Custom Controls

By: Kenn Scribner for TechTalk


Run the demo (all demos require Visual C++ 5.0!)
Download the demo source

Background: I designed my first custom control as a "thermometer" control for tracking GPS satellite signal strength. It started out simply enough, but soon I had added a bunch of code just to keep it from flickering! These days, I code this stuff as a matter of course, but I remember the fun I had getting things to work correctly, and how I learned so much from the attempt. This article is way too small to address the issues you'll uncover, but it is a start and the source code is fun. :)

Subclassing and Custom Controls

Someday it would be fun to write a book about this topic. The subject is rich with interesting details and fun code. However, we haven’t the space for a book here, so I’ll try to give you some details in a nutshell.

In order for us to create a "custom control", we must have a mechanism for accessing the control’s window procedure (this assumes we’re not simply doing "owner-drawn" control work or worrying about wrapping our control in ActiveX garb). By the way, for our purposes here, a definition "custom control" could be a control for which we accept responsibility for either painting or processing user input, or both. We require access to the particular controls’ window procedure because we want to handle certain windows messages for the control to provide the additional functionality we require. We could handle one or a couple of messages, like WM_PAINT and WM_ERASEBKGND, or we could get very much more complex and handle all mouse and keyboard input and/or non-client area painting. In effect, we want to "steal" messages from the control’s message queue and process them ourselves.

Briefly, in C-style, Petzoldian Windows programming, you would use Get/SetWindowLong() to access the particular window’s message pump. If you pass in an index value of GWL_WNDPROC with the control’s window handle, you will receive in return the pointer to that control’s message handler. If you were to set the window’s pointer to a message pump of your own, then pass messages you weren’t interested in back to the original message pump (using CallWindowProc(), of course, for threading/address space reasons) but keep those you did want, you would have "subclassed" the window.

When we deal with MFC and C++, in many cases much of this happens automatically for us. For example, each time you use the DDX_Control() function you’ll be subclassing the given dialog box control (you’ll see this in the demo code…). Interestingly, we often "subclass" in the C++ sense also…if we derive a new groupbox class from CButton (the groupbox’s parent C++ class), we’ve subclassed in the C++ sense too! In C++, we often see "subclassing" referred to as "inheritance".

So, what this brief description is attempting to say is we have a mechanism for picking and choosing what messages we want to intercept and process on behalf of another window, usually a child control. If you recall the last issue, I provided a drawing of the standard groupbox when CWnd::OnCtlColor() informed the child control it should use text with a transparent background. The lines of the groupbox were visible under the text, as the text was drawn with transparency. Should we insert a check in OnCtlColor() for any and all controls we want to handle in a special manner? Probably not, as there could be many controls and interdependencies.

A better alternative is to provide a MFC class that handles the messages of the underlying child control window. For the groupbox, we’ll handle painting and background erasure chores, but we could handle more messages if we desired. To draw our very own special groupbox, here’s what we do:

  1. In the dialog editor, place a groupbox into the dialog template--be sure to change its ID from IDC_STATIC to something else! (You'll need to do this so you can refer to the control by ID.)
  2. In the ClassWizard, assign the groupbox a variable name, making sure you select the "Control" category.
  3. Create your custom class using the ClassWizard. Simply invoke the ClassWizard and select "Add Class" and "New", then fill in the appropriate information. Since we subclassed a groupbox, we based our new class on CButton.
  4. Edit the dialog’s header file to change the groupbox’s class from CButton to whatever class you’ve created for the groupbox…the demo uses CCustomGroupBox. You’ll also need to add the appropriate header file for the custom class or the dialog box code will not compile. The demo adds "CustomGroupBox.h" to the dialog header.
  5. Edit the new control’s class source file to add the behavior you desire. See CCustomGroupBox.cpp for my version!

This is how I created the code for the demonstration.

I also created a new custom control for a "color box" control, which you’ll see in the demo. It shows how to notify its parent of an event (a double-click). Further, I personally try to have each "object" handle its own chores, so I created a new CDialog class to handle custom painting…I called it CCustomDialog. Using CCustomDialog, you simply change your normal MFC dialog class to inherit from CCustomDialog rather than CDialog. Then using custom painted dialogs is as easy as using my custom class…see the GpBoxDlg.cpp file in the demo for an example.

Whew! This was a brief glimpse into the world of custom controls. It’s amazing the power you command when you use subclassing techniques to leverage existing control functionality while providing your own tailored custom behaviors. If you’d like, feel free to download this issue’s demonstration code from the top of page. In the next issue, we’ll be using our custom dialogs for splash screen processing…it’s good stuff, so stay tuned!

Comments? Questions? Find a bug? Please send me a note!


[Back] [Left Arrow] [Right Arrow][Home]