Sample Project: Drawing on a ListView's Header Control 
By: Damon Chandler

Customizing a ListView's Header control is common practice in some of the more advanced applications, including Netscape's Collabra, Messenger and many Microsoft releases such as Outlook Express.  Commonly you'll see bitmaps in the Header control and/or colored or bolded text.  Accomplishing this task is not at all trivial, but it is relatively straight forward.  The VCL created ListView's header control is, by default, not owner drawn.  This style is necessary for the ListView to receive the WM_DRAWITEM message upon which to act according to the DRAWITEMSTRUCT passed in the lParam parameter.  Indeed, Windows message handling is a complex field that takes practice and experience to master (if that's even possible). 

    So what exactly do we need to do?  First, we need to flag the owner-drawn style for the header control.  This is accomplished by first using the GetDlgItem() API call to get an actual handle to the header control.  Then we create an HD_ITEM structure that specifies the attributes that we want to change (specifically flagging the owner draw style.)   Finally we called Header_SetItem() to implement the styles specified.  Only when the header control is owner drawn, will it send the ListView the WM_DRAWITEM message.  Yes, this message is sent to the ListView, not the Form.  What that means is, to examine this message, we'll have to subclass the window procedure of the ListView, and specifically look for the message.  Fortunately, with the WindowProc property, this isn't too complicated.

    Inside the message handler, we'll need to implement the drawing.  The DRAWITEMSTRUCT, passed as the long parameter (LParam) of the WM_DRAWITEM message is somewhat of a generic structure.  It is used for most every owner-drawn standard control.  This being the case, some of the members of this structure will not be used.  The first task is to grab a pointer to this structure, and decode all it's members.  The information we'll extract will include useful information such as the column of the header to draw, the bounding rectangle, the device context and the state (depressed or not).   There arises a problem when using VCL graphics methods inside the message handler.  Namely, assigning the HDC member of the DRAWITEMSTRUCT to the Handle of a Canvas.  What this leads to is mangled data; setting the Font::Attributes for example sometimes won't stick.  In certain cases, wrapping the code code in calls to BeginPaint()/EndPaint() is sufficient, however that is not the case here.  Keep in mind that the TCanvas is a very loose wrapper for GDI methods and device contexts. 
 

KEYWORDS: GetDlgItem, DRAWITEMSTRUCT


 

//in header... 

    Controls::TWndMethod OldListViewWP;
   void __fastcall NewListViewWP(TMessage &Msg);

 


 
 

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
 : TForm(Owner)
{
    //Get the Header control's handle (KEY METHOD)
    HWND HeaderHandle = GetDlgItem(ListView1->Handle, 0);
    HD_ITEM hdi;

    for (int index = 0; index < ListView1->Columns->Count; index++)
    {
       //Specify that we're going to change the fmt member
        hdi.mask = HDI_FORMAT;

       //Flag owner draw state
        hdi.fmt = HDF_OWNERDRAW;

       //Force the changes
        Header_SetItem(HeaderHandle, index, &hdi);
    }

    //subclass the window procedure
    OldListViewWP = ListView1->WindowProc;
    ListView1->WindowProc = NewListViewWP;
}
//---------------------------------------------------------------------------

//new window procedure
void __fastcall TForm1::NewListViewWP(TMessage &Msg)
{
    if (Msg.Msg == WM_DRAWITEM)
    {
        LPDRAWITEMSTRUCT lpdis = (DRAWITEMSTRUCT *)Msg.LParam;

        HDC HeaderHDC = lpdis->hDC;
        RECT Rect = lpdis->rcItem;
        int Index = lpdis->itemID;
        int State = lpdis->itemState;

        TFont *HeaderFont = new TFont();
        TBrush *HeaderBrush = new TBrush();
        HeaderFont->Style = HeaderFont->Style << fsBold;
        HeaderBrush->Color = clBlue;

        SelectObject(HeaderHDC, HeaderFont->Handle);

        ::FillRect(HeaderHDC, &Rect, HeaderBrush->Handle);
        ::SetTextColor(HeaderHDC, ColorToRGB(clYellow));
        ::SetBkMode(HeaderHDC, TRANSPARENT);

        int x_offset = 2;
        int y_offset = 1;
        int text_offset = 5;

        //Handle the depressed case by
        //offsetting the text and bitmap
        if (State & ODS_SELECTED)
        {
            x_offset = x_offset + 1;
            y_offset = y_offset + 1;
            text_offset = text_offset + 1;
        }

        //Lets draw Image1 only on the first columns header
        if (Index == 0)
        {
             BitBlt(HeaderHDC, Rect.left + x_offset,
                    Rect.top + y_offset,
                    Image1->Picture->Bitmap->Width,
                    Image1->Picture->Bitmap->Height,
                    Image1->Canvas->Handle, 0, 0, SRCCOPY);

             Rect.left = Rect.left + Image1->Picture->Bitmap->Width
                         + text_offset;
        }
        else Rect.left = Rect.left + text_offset;
        Rect.top = Rect.top + y_offset;

        AnsiString text = ListView1->Columns->Items[Index]->Caption;
        ::DrawText(HeaderHDC, text.c_str(), text.Length(), &Rect, DT_LEFT);

        delete HeaderFont;
        delete HeaderBrush;

        Msg.Result = true;
    }
    else OldListViewWP(Msg);
}
 

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
    ListView1->WindowProc = OldListViewWP;
}
 
 

 


 
 

Download Sample project: Sample4.zip (4.55 KB)