TN014: Custom Controls
This note describes the MFC Support for custom and self-drawing controls. Dynamic subclassing is also described. General advice on ownership of CWnd objects vs. HWNDs is presented.
The MFC sample application CTRLTEST illustrates many of these features. Please refer to the source code for the MFC General sample and online help.
Owner-Draw Controls/Menus
Windows provides support for "owner draw" controls and menus. These are Windows messages sent to a parent window of a control or menu that allow you to customize the visual appearance and behavior of the control or menu.
MFC directly supports owner draw with the message map entries:
CWnd::OnDrawItem
CWnd::OnMeasureItem
CWnd::OnCompareItem
CWnd::OnDeleteItem
You can override these in your CWnd-derived class (usually a dialog or main frame window) to implement the owner-draw behavior.
This approach does not lead to reusable code. If you have two similar controls in two different dialogs, you must implement the custom control behavior in two places. The MFC-supported self-drawing control architecture solves this problem.
Self-Drawing Controls and Menus
MFC provides a default implementation (in CWnd and CMenu) for the standard owner-draw messages. This default implementation will decode the owner-draw parameters and delegate the owner-draw messages to the controls or menu. This is called "self-draw" since the drawing (/measuring/comparing) code is in the class of the control or menu, not in the owner window.
This allows you to build reusable control classes that display the control using "owner draw" semantics. The code for drawing the control, not the owner of the control, is in the control class. This is an object-oriented approach to custom control programming.
For self-draw buttons:
CButton:DrawItem(LPDRAWITEMSTRUCT); // draw this button
For self-draw menus:
CMenu:MeasureItem(LPMEASUREITEMSTRUCT); // measure the size of an item in this menu CMenu:DrawItem(LPDRAWITEMSTRUCT); // draw an item in this menu
For self-draw list boxes:
CListBox:MeasureItem(LPMEASUREITEMSTRUCT); // measure the size of an item in this list box CListBox:DrawItem(LPDRAWITEMSTRUCT); // draw an item in this list box CListBox:CompareItem(LPCOMPAREITEMSTRUCT); // compare two items in this list box if LBS_SORT CListBox:DeleteItem(LPDELETEITEMSTRUCT); // delete an item from this list box
For self-draw combo boxes:
CComboBox:MeasureItem(LPMEASUREITEMSTRUCT); // measure the size of an item in this combo box CComboBox:DrawItem(LPDRAWITEMSTRUCT); // draw an item in this combo box CComboBox:CompareItem(LPCOMPAREITEMSTRUCT); // compare two items in this combo box if CBS_SORT CComboBox:DeleteItem(LPDELETEITEMSTRUCT); // delete an item from this combo box
For details on the owner-draw structures (DRAWITEMSTRUCT, MEASUREITEMSTRUCT, COMPAREITEMSTRUCT, and DELETEITEMSTRUCT) refer to the MFC documentation for CWnd::OnDrawItem, CWnd::OnMeasureItem, CWnd::OnCompareItem, and CWnd::OnDeleteItem respectively.
Using self-drawing controls and menus
For self-drawing menus, you must override both MeasureItem and DrawItem member functions.
For self-drawing list boxes and combo boxes, you must override MeasureItem and DrawItem. You must specify the OWNERDRAWVARIABLE style in the dialog template (LBS_OWNERDRAWVARIABLE and CBS_OWNERDRAWVARIABLE respectively). The OWNERDRAWFIXED style will not work with self-drawing items since the fixed item height is determined before self-drawing controls are attached to the list box. (The Win 3.1 member functions CListBox::SetItemHeight and CComboBox::SetItemHeight can be used to get around this limitation.)
In addition, note that switching to an OWNERDRAWVARIABLE style will affect the NOINTEGRALHEIGHT style. Because the control can not calculate an integral height with variable sized items, the default style of INTEGRALHEIGHT is ignored and the control is always NOINTEGRALHEIGHT. If your items are fixed height, you can prevent partial items from being drawn by specifying the control size to be an integral multiplier of the item size.
For self-drawing list boxes and combo boxes with the SORT style (LBS_SORT and CBS_SORT respectively), you must override the CompareItem member function.
For self-drawing list boxes and combo boxes, DeleteItem is not normally overridden. DeleteItem can be overridden if additional memory or other resources are stored with each list box or combo box item.
Examples of Self-Drawing Controls/Menus
The MFC General sample provides samples of a self-draw menu (showing colors) and a self-draw list box (also showing colors).
The most typical example of a self-drawing button is a bitmap button (a button that shows one, two, or three bitmap images for the different states). This is provided in the MFC class CBitmapButton.
Dynamic Subclassing
Subclassing is the Windows term for replacing the WndProc of a window with a different WndProc and calling the old WndProc for default (superclass) functionality.
This should not be confused with C++ class derivation (C++ terminology uses the words "base" and "derived" while the Windows object model uses "super" and "sub"). C++ derivation with MFC and Windows subclassing are functionally very similar, except C++ does not support a feature similar to dynamic subclassing.
The CWnd class provides the connection between a C++ object (derived from CWnd) and a Windows window object (also known as an HWND).
There are three common ways these are related:
CWnd creates the HWND. The behavior can be modified in a derived class. "Class derivation" is done by creating a class derived from CWnd and created with calls to Create.
CWnd gets attached to an existing HWND. The behavior of the existing window is not modified. This is a case of "delegation" and is made possible by calling Attach to alias an existing HWND to a CWnd C++ object.
CWnd gets attached to an existing HWND and you can modify the behavior in a derived class. This is called "dynamic subclassing," since we are changing the behavior (and hence the class) of a Windows object at run time.
This last case is done with the member functions:
CWnd::SubclassWindow
CWnd::SubclassDlgItem.
Both routines attach a CWnd object to an existing Windows HWND. SubclassWindow takes the HWND directly, and SubclassDlgItem is a helper that takes a control ID and the parent window (usually a dialog). SubclassDlgItem is designed for attaching C++ objects to dialog controls created from a dialog template.
Please refer to the example for several examples of when to use SubclassWindow and SubclassDlgItem.