1. Computing

Implementing OnStateIconClick In TListView

Change the State of a List Item By Clicking on the State Image (htOnStateIcon)

From , former About.com Guide

listview radio state items

listview radio state items

Delphi's TListView control displays a list of items in columns with column headers and sub-items, or vertically or horizontally, with small or large icons / images.

Each item in a list view can have an icon next to it. The list of icons is defined by the SmallImages (and/or LargeImages) property. Further, the StateImages property defines additional images to display next to each item in the list view.

Each item in the Items list can be associated with an image by setting its StateIndex property. The state image appears to the left of the normal list item.

Radio Style Checkable Items

I needed to have a way to allow a user to select / check / mark one item from the list. I could not use the Selected property as it was also needed that one item can be selected while the other can be "checked".

The Checkboxes property was not of help. Checkboxes allows checking several items at a time.

What I needed is something like a radio group where only one list item in a list view can be checked at a time.

I also needed for some items to be non-checkable.

Here's what I finally did:

  1. Use the StateIndex of an item to display a custom radio like image for the item.
  2. Handle OnClick to implement OnStateImageClick
  3. Apply bold to the checked item
//maps to the position of the state image in the TImageList
const
  lisSelected = 5;
  lisNotSelected = 4;
  lisNonSelectable = -1;

//handles ListView OnClick
procedure TlvForm.lvClick(Sender: TObject);
var
  hts : THitTests;
  lvCursosPos : TPoint;
  li, lii : TListItem;
begin
  inherited;

   //position of the mouse cursor related to ListView
   lvCursosPos := lv.ScreenToClient(Mouse.CursorPos) ;

   //click where?
   hts := lv.GetHitTestInfoAt(lvCursosPos.X, lvCursosPos.Y);

   //locate the state-clicked item
   if htOnStateIcon in hts then
   begin
     li := lv.GetItemAt(lvCursosPos.X, lvCursosPos.Y);
     if Assigned(li) then
     begin
       if li.StateIndex = lisNonSelectable then Exit;

       for lii in lv.Items do
        if lii.StateIndex <> lisNonSelectable then lii.StateIndex := lisNotSelectd;

       li.StateIndex := lisSelected;
     end;
   end;
end;

//handles ListView AdvancedCustomDrawItem
procedure TlvForm.lvAdvancedCustomDrawItem(Sender: TCustomListView;
  Item: TListItem; State: TCustomDrawState; Stage: TCustomDrawStage;
  var DefaultDraw: Boolean);
begin
  if Assigned(Item) then
  begin
    if Item.StateIndex = lisSelected then
      Sender.Canvas.Font.Style := [fsBold]
    else
      Sender.Canvas.Font.Style := [];
  end;
end;
In the OnClick event, ScreenToClient converts mouse position coordinates to list view's coordinate system. Next, GetHitTestInfoAt determines what elements of the list view are under the mouse. If the user clicked on the state icon, the htOnStateIcon is included in the result of the GetHitTestInfoAt.

Now, mark the "clicked" item as checked (lisSelected) and also make sure all other checkable items are unchecked.

That's it.

Note that you will need some glyphs that look like checked and non-checked radio buttons. Use those to display the state of the item.

©2013 About.com. All rights reserved.