Microsoft Foundation Classes                           Microsoft Corporation
Technical Notes

#14 : Custom Controls and other topics

This note describes the custom control support in MFC, how self
drawing controls are supported as well as the interface and use
of the CBitmapButton class.

Also dynamic subclassing is described, as well as general advice on
ownership of CWnd objects v.s. HWNDs.

The MFC sample application 'CTRLTEST' illustrates many of these
features.  Please refer to the source code to that sample
(in \C700\MFC\SAMPLES\CTRLTEST) as well as the general
README.TXT for the samples (\C700\MFC\SAMPLES\README.TXT).

=============================================================================
Custom Control Interface
========================

Owner Draw Controls/Menus:
--------------------------

Windows provides support for "owner draw" for controls and menus.
These are windows messages sent to a parent window of a control or
menu that allow you customize the visual appearance and behaviour
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 behaviour,
just as it is done in the C API.

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/Menus:
----------------------------

MFC provides a default implementation (in CWnd) 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
using "owner draw" semantics, only the code for drawing the
control is in the control class and not the owner.  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 listboxes:
    CListBox:MeasureItem(LPMEASUREITEMSTRUCT);
        // measure the size of an item in this listbox
    CListBox:DrawItem(LPDRAWITEMSTRUCT);
        // draw an item in this listbox

    CListBox:CompareItem(LPCOMPAREITEMSTRUCT);
        // compare two items in this listbox if LBS_SORT
    CListBox:DeleteItem(LPDELETEITEMSTRUCT);
        // delete an item from this listbox

For self draw comboboxes:
    CComboBox:MeasureItem(LPMEASUREITEMSTRUCT);
        // measure the size of an item in this combobox
    CComboBox:DrawItem(LPDRAWITEMSTRUCT);
        // draw an item in this combobox

    CComboBox:CompareItem(LPCOMPAREITEMSTRUCT);
        // compare two items in this combobox if CBS_SORT
    CComboBox:DeleteItem(LPDELETEITEMSTRUCT);
        // delete an item from this combobox


For details on the owner draw structures, DRAWITEMSTRUCT, MEASUREITEMSTRUCT,
COMPAREITEMSTRUCT and DELETEITEMSTRUCT please refer to the MFC
documentation for CWnd::OnDrawItem, OnMeasureItem, OnCompareItem,
and OnDeleteItem respectively.

You do not have to examine the 'CtlType' or 'CtlID' fields of these
structures (MFC does that for you).

For self drawing menus you must override both MeasureItem and DrawItem
member functions.

For self drawing listboxes and comboboxes 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 listbox (the Win 3.1 member functions
CListBox::SetItemHeight and CComboBox::SetItemHeight can be used
to get around this limitation).

For self drawing listboxes and comboboxes with the SORT style
(LBS_SORT and CBS_SORT respectively) you must override the
CompareItem member function.


Examples of Self Drawing Controls/Menus:
----------------------------------------
The CTRLTEST sample application (\c700\mfc\samples\ctrltest) provides
samples of an self draw menu (showing colors) and an self draw
listbox (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 so common we have provided
a class in MFC that directly provides this functionality.
(see CBitmapButton below).

=============================================================================
CBitmapButton:
==============

The CBitmapButton class is derived from CButton and provides a
self-draw implementation of a push button.  This button uses
bitmaps instead of text for the face of the button.

Creating a bitmap button:
Here are the steps to create a bitmap button and use it in
a dialog.  This just shows one way of using this class.
There are more options available using the CBitmapButton class.
    1) Create the bitmaps:
        Create one, two or three bitmaps using IMAGEDIT.EXE.
        Normally you should create all three.
        The first bitmap is for the "up" button state, the second
        for the "down" button state.  The third bitmap is for the
        "focused" button state which used when the input focus is
        on the button (usually the same as "up" but with a heavier
        border).  All bitmaps should be the same size, but there
        is no restriction on that size.

        For each button, pick an image name for that button
        (up to 7 characters, eg: "MYIMAGE").
        Save the bitmaps to three separate files with file
        names ending in "U", "D" and "F", all with the .BMP
        extension, for example: MYIMAGEU.BMP, MYIMAGED.BMP,
        and MYIMAGEF.BMP.
        
    2) Placing a bitmap button in a dialog:
        Using DLGEDIT to edit your dialog.  Add an owner-draw
        button wherever you want to have a bitmap button
        (add a pushbutton and set the "Owner-Draw" style).
        Set the text to the image name (eg: "MYIMAGE"),
        and define a symbol for that button (eg: IDC_MYIMAGE).
        The size of the button you draw on the dialog does
        not matter, since the default CBitmapButton autoload
        routine will resize it to the exact size of the bitmap.

    3) Add the bitmaps to your .RC file:
        Each of the bitmaps should be included in the RC file
        with the same name as the file, for example:
            MYIMAGEU    bitmap  MYIMAGEU.BMP
            MYIMAGED    bitmap  MYIMAGED.BMP
            MYIMAGEF    bitmap  MYIMAGEF.BMP

        Note the choice of "U", "D" and "F" is not arbitrary,
        the AutoLoad function relies on these particular names.

    3) Aliasing the dialog control with the C++ CBitmapButton object:
        Create a C++ dialog class (derived from CModalDialog usually).
        For each bitmap button in the dialog, have a CBitmapButton
        member object (the name of the member is not important).
        In the OnInitDialog routine for your dialog, call AutoLoad
        for each bitmap button (passing the control ID for the button
        and the dialog pointer).
        The AutoLoad member function will load in the bitmaps (based
        on the image name we set up earlier).  AutoLoad will also
        attach the dialog control to the C++ CBitmapButton object
        using SubclassDlgItem (SubclassDlgItem is a very general
        control/window aliasing mechanism that is described in detail below).

        class CMyDlg : ...
        {
            CBitmapButton   m_mybtn;
            ...
        };

        BOOL CMyDlg::OnInitDialog()
        {
            VERIFY(m_mybtn.AutoLoad(IDC_MYIMAGE, this));
                // verify we have loaded the image into the button with
                // control ID of IDC_MYIMAGE
            ...
        }


Examples of CBitmapButtons:
---------------------------
See CTRLTEST and SPEAKN for examples of bitmap buttons.

Member functions of CBitmapButton:
----------------------------------

    CBitmapButton       two constructors are provided, one with no
                            parameters that does nothing, and another
                            with 3 parameters that will load the
                            bitmaps for you.
    LoadBitmaps         loads the bitmaps from the named resource
                            The first bitmap is required, the other
                            two are optional.
    SizeToContent       resizes the button to the size of the first
                            bitmap

    AutoLoad            does everything:
                            * loads in the bitmaps based on the text
                            of the button + suffices "U", "D" and "F"
                            * resizes the button to content
                            * dynamically subclasses the button object

=============================================================================
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 (super class) 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 very similar in functionality - except for the fact 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 (aka an HWND).

There are three common ways these are related:
    * CWnd creates the HWND.  The behaviour can be modified in a derived class.
        This is a case of "class derivation" and is done by creating
        a class derived from CWnd and created with calls to 'Create'.
    * CWnd gets attached to an existing HWND.  The behaviour 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 behaviour in a derived class.
        This is called "dynamic subclassing" since we are changing
        the behaviour (and hence the class) of a Windows object at runtime.

This last case is done with the member functions:
    CWnd::SubclassWindow and 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 CTRLTEST example for several examples of
when to use SubclassWindow and SubclassDlgItem.

=============================================================================
Cleanup: CWnd::PostNCDestroy:
=============================

The following is an important topic.  If you follow the guidelines
set out below, you will have very few problems with cleanup problems
(either in forgetting to delete/free C++ memory, forgetting to
free up system resources like HWNDs, or freeing objects too many times).

There are typically four kinds of CWnd derived objects:
    * child windows / controls (derived from CWnd)
    * main frame windows (MDI and SDI included, derived from CFrameWnd)
    * modeless dialogs (derived from CDialog)
    * modal dialogs (derived from CModalDialog)


Here are the recommended ownership rules:
    * child windows / controls should be embedded as members in
        their parent window / dialog class.  They will get automatically
        cleaned up when the parent window / dialog object gets
        destroyed.
    * main frame windows should be allocated with 'new'.
        The standard application startup will do this, eg:
            m_pMainWnd = new CMyMainWindow;
    * related to the above: main frame windows should not be
        embedded as members in other classes/objects.
    * main frame windows should not be deleted with the 'delete'
        operator, but use DeleteWindow instead.
    * modeless dialogs usually follow the same rules as main frame
        windows - but you must override PostNcDestroy yourself
        to call "delete this".
    * modal dialogs should be embedded on the frame (i.e. auto
        variables) and get cleaned up when the routine using
        the dialog returns.

When destroying a Windows window, the last windows message sent to
the window is 'WM_NCDESTROY'.  The default CWnd handler for that
message (CWnd::OnNcDestroy) will detach the HWND from the C++
object and call the virtual function 'PostNcDestroy'.

The default PostNcDestroy implementation does nothing.
The CFrameWnd implementation will delete the C++ object.

When to override PostNcDestroy?:
    * if you have a CFrameWnd derived class embedded in another
        class or statically allocated.
    * if you have a modeless dialog you want to automatically
        cleanup.

For example, if you wish to have a frame window class that can
be allocated on the stack frame, as a static member, or embedded in 
another object, you would derive your own class as
usual.  You would, in addition, override the virtual member function
PostNcDestroy as follows:

class CMyFrameWnd : public CFrameWnd
// Frame window class that can be allocated on the stack or
// in static memory.
{
    // standard function overrides, constructors, message handlers

protected:
    virtual void PostNcDestroy();
};

void CMyFrameWnd::PostNcDestroy()
{
    // do nothing
}

The owner of the object is responsible for calling DestroyWindow() and
making sure the object is properly cleaned up.  This is demonstrated 
in the CTRLTEST sample application.

=============================================================================
