Coded Article: Custom Draw(ing) the ListView's Header Control
(By Damon Chandler)

Adding small glyphs to a ListView's header control is common practice in many applications.  This is easily accomplished and outlined in the CAQ: Adding Bitmaps to a ListView's Header Control.  However, perhaps you'd like to change the background color of the header, or draw a bitmap transparently in a certain header section.  While Windows allows the header control to be owner-drawn as discussed in the CAQ: Drawing on a ListView's Header Control,  this approach will only work for API created ListViews or TListViews from BCB1.  More specifically, the header loses it's owner-drawn style upon column resizing.  Obviously this is an unintentional side effect of some type of header manipulation in newer versions of the VCL.

Fortunately, like many of the common controls, the header control provides the Custom Draw service.  This is, in fact, more powerful than an owner-drawn approach.  As mentioned in the CAQ: An Introduction to Custom Draw, this requires comctl32 version 4.70+ (IE 3).  There are very few MSDN examples on Custom Draw, but fortunately, the techniques are similar for all common controls.  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 header section.  Windows then responds by sending you messages corresponding to each header section being drawn (CDDS_ITEMPREPAINT).  Finally, you draw the header section, then respond to Windows by telling it that you drew the header (CDRF_SKIPDEFAULT), so that it doesn't draw the header again.

While BCB4+ encapsulates the Custom Draw service for TListView and TTreeView controls, it does not do so for header controls.  In fact, it is probably a good idea to learn Custom Draw the API way, as there are other common controls that allow the service for which the VCL provides no Custom Draw encapsulation.  The ToolBar and TrackBar controls are some examples of this. 

Since Custom Draw messages are sent to the parent of the control, and the header control is a child of the ListView, you'll need to subclass the ListView and handle the WM_NOTIFY message in the subclass procedure.  This is easily accomplished via the WindowProc property.  In the subclass procedure, handle the WM_NOTIFY message and test the code member of the NMHDR member of the NMCUSTOMDRAWstructure (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 header 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:
 

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 header 
                  section referred to by the dwItemSpec member.

    DWORD dwItemSpec; <---This member reports the zero-based index of the 
                           current header section that is being drawn.

    UINT  uItemState; <---This member reports the state of the item 
                           (e.g., pushed, 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;

#define NM_CUSTOMDRAW           (NM_FIRST - 12)
#define CDDS_PREPAINT           0x00000001
#define CDDS_ITEM               0x10000
#define CDDS_ITEMPREPAINT       (CDDS_ITEM | CDDS_PREPAINT)
#define CDDS_SUBITEM            0x00020000

#define CDRF_DODEFAULT          0x00000000
#define CDRF_NEWFONT            0x2
#define CDRF_NOTIFYITEMDRAW     0x20
#define CDRF_NOTIFYSUBITEMDRAW  0x00000020

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

    // since Custom Draw messages are sent to the 
    // parent of the control, you'll need to 
    // subclass the ListView...
    Controls::TWndMethod OldListViewWP;
   void __fastcall NewListViewWP(TMessage &Msg);
 

 


 
 
 

//---------------------------------------------------------------------------

// in source...
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    OldListViewWP = ListView1->WindowProc;
    ListView1->WindowProc = NewListViewWP;
}
 

void __fastcall TForm1::NewListViewWP(TMessage &Msg)
{
    if (Msg.Msg == WM_NOTIFY)
    {
        LPNMCUSTOMDRAW lpnmcd = (NMCUSTOMDRAW *)Msg.LParam;

        // if the notification is a Custom Draw notification 
        // and from the header control
        if (lpnmcd->hdr.code == NM_CUSTOMDRAW &&
            lpnmcd->hdr.hwndFrom == GetDlgItem(ListView1->Handle, 0))
        {
            // check the drawing stage
            switch (lpnmcd->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 being drawn
                case CDDS_ITEMPREPAINT:
                {
                    TCanvas *HeaderCanvas = new TCanvas();
                    HeaderCanvas->Handle = lpnmcd->hdc;
                    TRect Rect = lpnmcd->rc;
                    int Index = lpnmcd->dwItemSpec; 
                    unsigned int State = lpnmcd->uItemState;
                    AnsiString text =
                        ListView1->Columns->Items[Index]->Caption;

                    HeaderCanvas->Font->Name = "Tahoma";
                    HeaderCanvas->Font->Style = TFontStyles() << fsBold;

                    if (Index == 0)
                        HeaderCanvas->Brush->Color = clBlue;
                    else if (Index == 1)
                        HeaderCanvas->Brush->Color = clRed;
                    else if (Index == 2)
                        HeaderCanvas->Brush->Color = clYellow;

                    // if depressed
                    if (State & CDIS_SELECTED)
                        Frame3D(HeaderCanvas, Rect, clBtnShadow,
                                clBtnHighlight, 2);
                    else
                        Frame3D(HeaderCanvas, Rect, clBtnHighlight,
                                clBtnShadow, 2);

                    HeaderCanvas->FillRect(Rect);
                    HeaderCanvas->TextRect(Rect, Rect.Left + 2, Rect.Top,
                                           text);

                    HeaderCanvas->Handle = 0;
                    delete HeaderCanvas;

                    // tell Windows the item has already been drawn 
                    Msg.Result = CDRF_SKIPDEFAULT;
                    break;
                }
            }
            return
        }
    }
    // un-subclass the ListView
    if (Msg.Msg == WM_DESTROY) ListView1->WindowProc = OldListViewWP;

    // pass on the messages
    OldListViewWP(Msg);
}

 


 
 
 

If you've read the other Custom Draw articles, you've probably notice that all the techniques are strikingly similar.  This is one of the advantages of Custom Draw.  Namely, the implementation has little dependance on the particular common control begin drawn.  This of course does not hold for the actual drawing routine, however, the idea of drawing stages and responses is indeed common.  Another important thing to note is that I used a TCanvas in the above implementation.  This is generally not recommended for Custom Draw and/or owner-drawn controls.  In some situations using a TCanvas will render all items in bold and a different size/font (buggy TCanvas).  If possible, use the handle to the device context and GDI drawing routines directly.