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 havent the space for a book here, so Ill 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 controls window procedure (this assumes were 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 controls message queue and process them ourselves.
Briefly, in C-style, Petzoldian Windows programming, you would use Get/SetWindowLong() to access the particular windows message pump. If you pass in an index value of GWL_WNDPROC with the controls window handle, you will receive in return the pointer to that controls message handler. If you were to set the windows pointer to a message pump of your own, then pass messages you werent 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 youll be subclassing the given dialog box control (youll 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 groupboxs parent C++ class), weve 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, well handle painting and background erasure chores, but we could handle more messages if we desired. To draw our very own special groupbox, heres what we do:
This is how I created the code for the demonstration.
I also created a new custom control for a "color box" control, which youll 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. Its amazing the power you command when you use subclassing techniques to leverage existing control functionality while providing your own tailored custom behaviors. If youd like, feel free to download this issues demonstration code from the top of page. In the next issue, well be using our custom dialogs for splash screen processing its good stuff, so stay tuned!
Comments? Questions? Find a bug? Please send me a note!