Sample Project: Adding CheckBoxes to an owner-drawn ListView control 
By: Damon Chandler
 

With the release of the new IE styles, adding checkboxes with full functionality to a ListView is trivial.  (I think it's encapsulated in BCB4, but if anyone is interested here it is...)

#define LVS_EX_CHECKBOXES 0x4
#define LVM_SETEXTENDEDLISTVIEWSTYLE (LVM_FIRST + 54)

ListView1->Perform(LVM_SETEXTENDEDLISTVIEWSTYLE, 0, (LPARAM)LVS_EX_CHECKBOXES);
 

Here comes the tricky part,  in order to gray some lines, we'll have to make the ListView owner-drawn.  If this is done, the above code will not automatically show the checkboxes, we are responsible for drawing them ourselfs.  The good thing is that using the above style offsets the icon, and keeps track of the checked state even though the checkboxes aren't automatically drawn.  To handle the drawing, the easiest thing to do is use the DrawFrameControl() function.  This function is ideal for drawing standard buttons.  So now what we'll need to do is make the ListView owner drawn, check all the checkboxes (all ListItems enabled), then draw the checkbox in the proper state.  Further, we'll have to add the functionality to check/uncheck the checkbox when the user clicks the item.  The goal of this project is to create a ListView that has checkboxes which indicate the enambled state of the item.  If the checkbox is checked, then the ListItem is enabled and draw in black.  Otherwise, the ListItem is drawn disabled.
 



Notes

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

(2) Since your ListView is an owner-drawn type, Windows will send it's owner window the WM_DRAWITEM message which we have to map and decode the DRAWITEMSTRUCT structure to change the line colors. 

 

//in Form header...
   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)
 

 


 
 

//---------------------------------------------------------------------------
//in source...
#define LV_EX_CHECKED 0x2000;
#define LVS_EX_CHECKBOXES 0x4
#define LVM_SETEXTENDEDLISTVIEWSTYLE (LVM_FIRST + 54)

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

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

    //add the checkbox style
    ListView1->Perform(LVM_SETEXTENDEDLISTVIEWSTYLE, 0, (LPARAM)LVS_EX_CHECKBOXES);

    //check all checkboxes
    for (int index = 0; index < ListView1->Items->Count; index++)
    {
        LONG state = LV_EX_CHECKED;
        ListView_SetItemState(ListView1->Handle, index, state, LVIS_STATEIMAGEMASK);
    }
}

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

//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)
{
    LPDRAWITEMSTRUCT lpdis = (DRAWITEMSTRUCT *)Message.LParam;

    //the WM_DRAWITEM may be shared (for example, BitBtns)
    //only handle the drawing if the message corresponds
    //to the target ListView
    if (lpdis->hwndItem != ListView1->Handle)
    {
        TForm::Dispatch(&Message);
        return;
    }

    int Index = lpdis->itemID;
    UINT ItemState = lpdis->itemState;
    LVCanvas->Handle = lpdis->hDC;
    TRect DrawRect = lpdis->rcItem;

    RECT ItemRect;
    ListView_GetItemRect(ListView1->Handle, Index, &ItemRect, LVIR_SELECTBOUNDS);

    LONG CBDrawState = DFCS_BUTTONCHECK;
    RECT CBRect;
    CBRect.left = 0;
    CBRect.top = DrawRect.Top;
    CBRect.right = check_box_width;
    CBRect.bottom = DrawRect.Bottom;

    BeginPaint(ListView1->Handle, &ps);

    //determine if the checkbox is checked
    LONG state = SendMessage(ListView1->Handle, LVM_GETITEMSTATE,
                             Index, LVIS_STATEIMAGEMASK);
    bool checked = state & LV_EX_CHECKED;

    if (checked) //flag the "checked" drawing style
    {
        CBDrawState = CBDrawState | DFCS_CHECKED;
        LVCanvas->Font->Color = ListView1->Font->Color;
        LVCanvas->Brush->Color = ListView1->Color;
        LVCanvas->FillRect((TRect)ItemRect);
    }
    else//gray the item
    {
        LVCanvas->Font->Color = clGrayText;
        LVCanvas->Brush->Color = ListView1->Color;
        LVCanvas->FillRect((TRect)ItemRect);
    }

    if (checked && (ItemState & ODS_SELECTED)) //if selected
    {
        LVCanvas->Font->Color = clHighlightText;
        LVCanvas->Brush->Color = clHighlight;
        LVCanvas->FillRect((TRect)ItemRect);
    }

    //draw the checkbox
    DrawFrameControl(LVCanvas->Handle, &CBRect, DFC_BUTTON, CBDrawState);

    //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
    if (checked)
    {
        LVCanvas->Font->Color = ListView1->Font->Color;
        LVCanvas->Brush->Color = ListView1->Color;
    }
    DrawRect.Left = IconRect.left - check_box_width + 5;
    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);
}

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

//this OnMouseDown event handler uses the
//ListView_HitTest macro to determine if/which
//item in a ListView is clicked.  It then toggles
//the state of the corresponding checkbox whose changes
//are reflected by the DrawListView message handler

void __fastcall TForm1::ListView1MouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
    LV_HITTESTINFO lvhti;
    lvhti.pt.x = X;
    lvhti.pt.y = Y;

    int index = ListView_HitTest(ListView1->Handle, &lvhti);

    //if (index != -1 && X < 20)  for only a checkbox click
    if (index != -1)
    {
        LONG state = SendMessage(ListView1->Handle, LVM_GETITEMSTATE,
                                 index, LVIS_STATEIMAGEMASK);

        bool checked = state & LV_EX_CHECKED;
        if (checked) state = 0x1000;  //uncheck
        else state = LV_EX_CHECKED;  //check

        ListView_SetItemState(ListView1->Handle, index,
                              state, LVIS_STATEIMAGEMASK);
    }
 }

 


 

There are also some other conditions that need to be handled, including scrolling and the addition of new items.  The foundations are solid though.  Have a look at the sample project, and see if you can improve the ListView's functionality.
 
 

Download Sample Project:  Sample2.zip (33.3 KB)