Coded Article: An Introduction to Custom Draw -- The TreeView Control
(By Damon Chandler)

    With the release of IE3 (comctrl32 version 4.70) Windows introduced the powerful way to customize the appearance of common controls.  This new technology, rightfully named Custom Draw, is light years beyond the owner-drawn hangover from Windows 3.0.  Custom Draw allows the developer to have complete control over the  appearance without the need to draw every aspect of the control. 

    We all know that for most controls, Windows allows what is called an owner-drawn style.  This allows the developer to customize appearance.  The problem is that all of the drawing is put on the weight of the developer.  For example, to create an owner-drawn ListView, one must map the WM_DRAWITEM and WM_MEASUREITEM messages as usual.  Then within these message handlers, the programmer must code the entire drawing routine -- from the normal items, to the subitems, to the selected and focussed items; all while maintaining text spacing and the more difficult task of speed.  In other words, it's a major hassle!

    With Custom Draw, the developer can selectively choose which aspects to draw.  If one chooses just to change the font, then it's as simple as sending a message to Windows and selecting the new font into the provided device context.  At any time, the developer may choose to let Windows do the default drawing, or completely skip the default.  This selective drawing has several advantages.  To change the color of a single list item requires absolutely no drawing code whatsoever.

    The thing to keep in mind is that Custom Draw is nothing more than a fancy communication between you and Windows.  Windows sends you messages that drawing is about to begin (CDDS_PREPAINT).  You respond by telling Windows what drawing stages you want notification of, in this case CDRF_NOTIFYITEMDRAW for each node.  Windows then responds by sending you messages corresponding to each node being drawn (CDDS_ITEMPREPAINT).  Finally, you change certain attributes, then respond to Windows by telling it that you changed an attribute (CDRF_NEWFONT).  At any stage, you can tell Windows to do the default drawing (CDRF_DODEFAULT) or not to draw anything at all which means you have complete control over appearance (CDRF_SKIPDEFAULT).

    To use the Custom Draw service, you need to handle the WM_NOTIFY message and test the code member of the NMHDR member of the NMCUSTOMDRAW structure (sent as the LParam) for the NM_CUSTOMDRAW notification.  Also, test the hwndFrom member of the NMHDR to make sure the notification corresponds to the target control.  From there, simply test the drawing stage and respond with the correct CDRF_* constant (response flag).   The following list describes the information conveyed in each member of the NMCUSTOMDRAW structure (for TreeViews):
 

typedef struct tagNMCUSTOMDRAWINFO
{
    NMHDR hdr; <--- This the the NMHDR structure that describes the sender 
                    of the message and the actual type of notification 
                    (we're looking for NM_CUSTONDRAW in thie case).

    DWORD dwDrawStage; <--- This member reports the drawing stage of the 
                            Custom Draw process (CDDS_*). 

    HDC hdc; <--- This member is a handle to the device context to draw on.

    RECT rc; <--- This member is the rectangle of the particular Node
                  referred to by the dwItemSpec member.

    DWORD dwItemSpec; <--- This member reports the HTREEITEM of the 
                           current Node that is being drawn (use 
                           TTreeNode::ItemId to compare).

    UINT  uItemState; <--- This member reports the state of the item 
                           (e.g., selected, hot, focused, etc.).  It can be 
                           a combination of one or more of the CDIS_* 
                           constants. 

    LPARAM lItemlParam; <--- extra information associated with each item

} NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
 
 

KEYWORDS:  WM_NOTIFY, Custom Draw
REFERENCES:  http://msdn.microsoft.com/library/sdkdoc/shellcc/commctls/CustDraw/CustDraw.htm
 
 

//in header...
typedef struct tagNMCUSTOMDRAWINFO
{
    NMHDR  hdr;
    DWORD  dwDrawStage;
    HDC    hdc;
    RECT   rc;
    DWORD  dwItemSpec;
    UINT   uItemState;
    LPARAM lItemlParam;
} NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;

typedef struct tagNMTVCUSTOMDRAW
{
    NMCUSTOMDRAW nmcd;
    COLORREF clrText;
    COLORREF clrTextBk;
    int iLevel;  // IE 4 only
} NMTVCUSTOMDRAW, *LPNMTVCUSTOMDRAW;

// Custom Draw constants
#define NM_CUSTOMDRAW (NM_FIRST - 12)
#define CDDS_ITEM 0x10000
#define CDDS_PREPAINT 0x1
#define CDDS_ITEMPREPAINT CDDS_ITEM | CDDS_PREPAINT

#define CDRF_DODEFAULT 0x0
#define CDRF_NEWFONT 0x2
#define CDRF_NOTIFYITEMDRAW 0x20

// itemState flags
#define CDIS_SELECTED 0x0001
#define CDIS_GRAYED         0x0002
#define CDIS_DISABLED       0x0004
#define CDIS_CHECKED        0x0008
#define CDIS_FOCUS          0x0010
#define CDIS_DEFAULT        0x0020
#define CDIS_HOT            0x0040
#define CDIS_MARKED         0x0080
#define CDIS_INDETERMINATE  0x0100
 

  TFont *NewFont;

// Next, since the Custom Draw message is sent in the form 
// of a WM_NOTIFY message,
// you'll need to map the WM_NOTIFY message...
  void __fastcall WMNotify(TMessage &Msg);

BEGIN_MESSAGE_MAP
    MESSAGE_HANDLER(WM_NOTIFY, TMessage, WMNotify)
END_MESSAGE_MAP(TForm)
 

 


 
 
 

//---------------------------------------------------------------------------
//in source...
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    // create the new font that you want to assign 
    // to the particular TreeItem(s)
    NewFont = new TFont();
    NewFont->Size = 10;
    NewFont->Name = "Comic Sans MS";
}
 

void __fastcall TForm1::WMNotify(TMessage &Msg)
{
   // grab a pointer to the NM_TREEVIEW structure
   LPNM_TREEVIEW lpnm = (NM_TREEVIEW *)Msg.LParam;

    // see if the message is a Custom Draw message
    if (lpnm->hdr.code == NM_CUSTOMDRAW)
    {
        // see if the message is from the TreeView
        if (lpnm->hdr.hwndFrom == TreeView1->Handle)
        {
            // grab a pointer to the NMTVCUSTDRAW structure 
           LPNMTVCUSTOMDRAW lptvcd = (NMTVCUSTOMDRAW *)Msg.LParam;

            // check the drawing stage
            switch (lptvcd->nmcd.dwDrawStage)
            {
                // prior to painting...
                case CDDS_PREPAINT:
                {
                    // tell Windows we want individual
                    // notification of each item being drawn
                  Msg.Result = CDRF_NOTIFYITEMDRAW;
                  break;
                }

                // notification of each item...
                case CDDS_ITEMPREPAINT:
                {
                    // change the font of the first item for example
                    // for TreeViews, dwItemSpec corresponds to ItemId
                    ULONG HItem = (ULONG)TreeView1->Items->Item[0]->ItemId;
                    if (lptvcd->nmcd.dwItemSpec == HItem)
                    {
                        SelectObject(lptvcd->nmcd.hdc, NewFont->Handle);
                    }

                    // determine the state of the TreeView item
                    UINT State = lptvcd->nmcd.uItemState;

                    // change background color (clHighlight if selected 
                    // item otherwise TreeView's color
                    if (State & CDIS_SELECTED)
                        lptvcd->clrTextBk = ColorToRGB(clHighlight);
                    else
                        lptvcd->clrTextBk = ColorToRGB(TreeView1->Color);

                    // tell Windows we changed an attribute
                    Msg.Result = CDRF_NEWFONT;
                    break;
                 }

                //otherwise, let Windows draw the Node
                default: Msg.Result = CDRF_DODEFAULT;
                return;
            }
        }
    }

    // otherwise it's not a Custom Draw message, so 
    // let the Form handle it
    TForm::Dispatch(&Msg);
}
 

// clean up
__fastcall TForm1::~TForm()
{
    delete NewFont; 
}

 


 
 

Custom Draw can be used with most common controls.  Some are more difficult to implement, but it is leaps and bounds easier than owner-drawn techniques.  Another point that is worth mentioning -- Custom Draw takes the hassle out of double buffering.  For many owner-drawn controls, using VCL or even straight GDI methods without double-buffering can lead to annoying flicker.  Custom Drawn controls don't suffer from this limitation.