Using Custom Draw with the ListView Control

The ListView control's appearance is particularly difficult to customize due to the fact that the LVS_OWNERDRAWFIXED style is only applicable to report-mode ListViews.  Further, handling the WM_DRAWITEM message is a major hassle since every aspect of each item needs to be drawn manually.  What happens when you need to change only the color of a particular item in another ViewStyle?  Without Custom Draw, you'd need to subclass the ListView and handle the WM_PAINT message directly -- not a pretty task.  In fact, handling the WM_PAINT message requires almost a complete rewrite of the entire control's drawing mechanism.  This is obviously not a good solution for changing only a small aspect of the ListView's appearance.  Luckily with the release of comctl32 version 4.70 (IE3) Microsoft introduced the Custom Draw service.  Below are a few examples of using Custom Draw to render a TListView control's item in different colors.  The first example deals with the vsICon ViewStyle, while the latter example demonstrates how to use this service with the vsReport ViewStyle.  For a brief primer on Custom Draw, have a look at: An Introduction to Custom Draw.  Also note that in BCB4 this process is encapsulted via the OnCustomDraw* events. 
 

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 ListViews):
 

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

    DWORD dwItemSpec; <--- This member reports the index of the 
                           current ListItem that is being drawn.

    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 tagNMLVCUSTOMDRAW
{
    NMCUSTOMDRAW nmcd;
    COLORREF clrText;
    COLORREF clrTextBk;
    int iSubItem;
} NMLVCUSTOMDRAW, *LPNMLVCUSTOMDRAW;

#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
 

    // map the WM_NOTIFY message in the Parent of the
    // "custom drawn control"
    void __fastcall WMNotify(TMessage &Msg);

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

 


 
 
 

Example 1:  vsIcon ViewStyle


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

// in source...
void __fastcall TForm1::WMNotify(TMessage &Msg)
{
    LPNM_LISTVIEW lpnm = (NM_LISTVIEW *)Msg.LParam;

    // see if the message is a Custom Draw message
    if (lpnm->hdr.code == NM_CUSTOMDRAW)
    {
        // see if the message is from the ListView
        if (lpnm->hdr.hwndFrom == ListView1->Handle)
        {
            LPNMLVCUSTOMDRAW lplvcd = (NMLVCUSTOMDRAW *)Msg.LParam;
            switch (lplvcd->nmcd.dwDrawStage)
            {
                // prior to painting
                case CDDS_PREPAINT:
                {
                    // tell Windows we want notification
                    // of each item being drawn
                    Msg.Result = CDRF_NOTIFYITEMDRAW;
                   return;
                }
 
                // prior to each item being drawn
                case CDDS_ITEMPREPAINT:
                {
                    // extract relevant info from the
                    // custom draw structure
                    int Index = lplvcd->nmcd.dwItemSpec;
                    TListItem *Item =
                        ListView1->Items->Item[Index]; 
                    HDC Hdc = lplvcd->nmcd.hdc;
                    RECT IconRect, CaptionRect;

                    // get the rectangles for the icon and caption
                    IconRect.left = LVIR_ICON;
                    CaptionRect.left = LVIR_LABEL;
                    SNDMSG(ListView1->Handle, LVM_GETITEMRECT,
                           Index, (LPARAM)&IconRect);
                    SNDMSG(ListView1->Handle, LVM_GETITEMRECT,
                           Index, (LPARAM)&CaptionRect);

                    unsigned int DrawStyle = ILD_TRANSPARENT;
 
                    // if selected
                    if (lplvcd->nmcd.uItemState & CDIS_SELECTED)
                    {
                        DrawStyle = ILD_SELECTED;
                        SetTextColor(Hdc, ColorToRGB(clHighlightText));

                        // fill the caption rectangle
                        LOGBRUSH lb;
                        lb.lbStyle = BS_SOLID;
                        lb.lbColor = ColorToRGB(clHighlight);
                        HBRUSH HBrush = CreateBrushIndirect(&lb);
                        FillRect(Hdc, &CaptionRect, HBrush);
                        DeleteObject(HBrush);
                    }

                    // calculate the icon's horizontal position
                    int IconLeft = IconRect.left + 0.5 *
                                   (IconRect.right - IconRect.left -
                                   ImageList1->Width);
 
                    // draw the icon
                    ImageList_Draw((HIMAGELIST)ImageList1->Handle,
                                   Item->ImageIndex, Hdc,
                                   IconLeft, IconRect.top,
                                   DrawStyle);

                    // draw the caption
                    DrawText(Hdc, Item->Caption.c_str(),
                             Item->Caption.Length(), &CaptionRect,
                             DT_CENTER | DT_WORDBREAK);

                    // tell Windows we drew the item manually
                    Msg.Result = CDRF_SKIPDEFAULT;
                   return;
                } 
                // otherwise have Windows draw the item
                default: Msg.Result = CDRF_DODEFAULT;
                return;
            }
        }
    }
    // otherwise, let TForm handle the message
    TForm::Dispatch(&Msg);
}

 


 
 
 
 

Example 2: vsReport ViewStyle


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

// in source...
void __fastcall TForm1::WMNotify(TMessage &Msg)
{
    LPNM_LISTVIEW lpnm = (NM_LISTVIEW *)Msg.LParam;

    // see if the message is a Custom Draw message
    if (lpnm->hdr.code == NM_CUSTOMDRAW)
    {
        // see if the message is from the ListView
        if (lpnm->hdr.hwndFrom == ListView1->Handle)
        {
            LPNMLVCUSTOMDRAW lplvcd = (NMLVCUSTOMDRAW *)Msg.LParam;
            switch (lplvcd->nmcd.dwDrawStage)
            {
                // prior to painting the items...
                case CDDS_PREPAINT:
                {
                    Msg.Result = CDRF_NOTIFYITEMDRAW;
                    return;
                }
                case CDDS_ITEMPREPAINT:
                {
                    // tell Windows we want notification of
                    // each subitem being drawn
                    Msg.Result = CDRF_NOTIFYSUBITEMDRAW;
                    return;
                }
                case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
                {
                    // index of current Item (row) being drawn
                    int ItemIndex = lplvcd->nmcd.dwItemSpec;

                    switch (lplvcd->iSubItem)
                    {
                        // first column (not really a subitem)
                        case 0:
                        {
                            if (lplvcd->nmcd.uItemState & CDIS_HOT)
                                lplvcd->clrText = ColorToRGB(clHot);
                            else
                                lplvcd->clrText = ColorToRGB(clYellow);

                            // change background color
                            lplvcd->clrTextBk = ColorToRGB(clRed);
                            break;
                        }

                        // second column (first actual subitem)
                        case 1:
                        {
                            // change text color of every other item
                            if (ItemIndex % 3 == 0)
                                lplvcd->clrText = ColorToRGB(clRed);
                            else lplvcd->clrText =
                                    ColorToRGB(ListView1->Font->Color);

                            // change background color
                            lplvcd->clrTextBk = ColorToRGB(clYellow);
                            break;
                        }

                        // third column (second actual subitem)
                        case 2:
                        {
                            // change text color of every third item
                            if (ItemIndex % 3 == 0)
                                lplvcd->clrText = ColorToRGB(clWhite);
                            else lplvcd->clrText =
                                    ColorToRGB(ListView1->Font->Color);

                            // change background color
                            lplvcd->clrTextBk = ColorToRGB(clNavy);
                            break;
                        }
                    }
                    // tell Windows we changed an attribute
                    Msg.Result = CDRF_NEWFONT;
                    return;
                }
                // otherwise have Windows draw the item
                default: Msg.Result = CDRF_DODEFAULT;
                return;
            }
        }
    }
    // otherwise, let TForm handle the message
    TForm::Dispatch(&Msg);
}