Changing the text color of a single line in a ListView 

 

KEYWORDS: OwnerDrawn, LVS_OWNERDRAWFIXED, BeginPaint, EndPaint, ListView_GetItemRect

 

(1) Except for int the latest version, the VCL doesn't encapsulate ListView owner-drawn ListView's although Windows does allow them if in Report View.  To achieve this end, we'll use a combination of GetWindowLong and SetWindowLong to flag the owner-draw style.
 

(2) If you have BCB 4 then I think there is an OnDrawItem event handler that encapsulates the code below.  If you are using version 1 (like me) or 3, then you'll have to make the ListView owner-drawn via GetWindowLong() and SetWindowLong() API calls.  Once the ListView is owner-drawn, it's Parent (your Form) will be sent the WM_DRAWITEM message which you must trap and decode. 

 

 

//in header file 
TCanvas *LVCanvas; //this will be used to draw the ListItems
PAINTSTRUCT ps; //dummy variable for Begin/EndPaint

void __fastcall DrawListView(TMessage &Message);

BEGIN_MESSAGE_MAP
    MESSAGE_HANDLER(WM_DRAWITEM, TMessage, DrawListView);
END_MESSAGE_MAP(TForm)

 

 

 

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
    LVCanvas = new TCanvas();
    LVCanvas->Font = ListView1->Font;

    //These two lines make the ListView owner-drawn...
    //only possible in vsReport ViewStyle
    LONG dwStyle = GetWindowLong(ListView1->Handle, GWL_STYLE);
    SetWindowLong(ListView1->Handle, GWL_STYLE, dwStyle | LVS_OWNERDRAWFIXED);
}

//This is the function that the WM_DRAWITEM message maps to.
//The LParam member is in the form of a DRAWITEMSTRUCT structure
void __fastcall TForm1::DrawListView(TMessage &Message)
{
    BeginPaint(ListView1->Handle, &ps);
    TRect DrawRect;

    int Index = ((LONG *)Message.LParam)[2];
    UINT ItemState = ((UINT *)Message.LParam)[4];
    LVCanvas->Handle = ((HDC *)Message.LParam)[6];
    DrawRect.Left = ((LONG *)Message.LParam)[7];
    DrawRect.Top = ((LONG *)Message.LParam)[8];
    DrawRect.Right = ((LONG *)Message.LParam)[9];
    DrawRect.Bottom = ((LONG *)Message.LParam)[10];

    //Handle the selected case
    RECT ItemRect;
    ListView_GetItemRect(ListView1->Handle, Index, &ItemRect, LVIR_SELECTBOUNDS);

    //example ListItems background we want to color
    //color first 10 items blue
    if (Index < 10
    {
        LVCanvas->Brush->Color = ListView1->Color;
        LVCanvas->Font->Color = clBlue;
        LVCanvas->FillRect((TRect)ItemRect);
    }
    elseif (ItemState & ODS_SELECTED) //if selected
    {
        LVCanvas->Font->Color = clHighlightText;
        LVCanvas->Brush->Color = clHighlight;
        LVCanvas->FillRect((TRect)ItemRect);
    }
    else
    {
        LVCanvas->Font->Color = ListView1->Font->Color;
        LVCanvas->Brush->Color = ListView1->Color;
        LVCanvas->FillRect((TRect)ItemRect);
    }

    //Draw the corresponding icon
    RECT IconRect;
    ListView_GetItemRect(ListView1->Handle, Index, &IconRect, LVIR_ICON);
    int IconIndex = ListView1->Items->Item[Index]->ImageIndex;
    ImageList1->Draw(LVCanvas, IconRect.left, IconRect.top, IconIndex);

    DrawRect.Left = IconRect.right + 2;
    LVCanvas->Brush->Style = bsClear;

    DrawRect.Right = ListView1->Columns->Items[0]->Width; 
    LVCanvas->TextRect(DrawRect, DrawRect.Left, DrawRect.Top, 
    ListView1->Items->Item[Index]->Caption);

    //draw the sub-items
    DrawRect.Left = IconRect.left;
    for (int col = 0; col < ListView1->Items->Item[Index]->SubItems->Count; col++)
    {
        DrawRect.Left = DrawRect.Left + ListView1->Columns->Items[col]->Width + 1;
        DrawRect.Right = DrawRect.Left + ListView1->Columns->Items[col + 1]->Width;

        AnsiString TextToDraw;
        TextToDraw = ListView1->Items->Item[Index]->SubItems->Strings[col];
        LVCanvas->TextRect(DrawRect, DrawRect.Left, DrawRect.Top, TextToDraw);
    }
    EndPaint(ListView1->Handle, &ps);
}