I am using ListView in Delphi. I set the OwnerData property to true so that I will provide data of the item in OnData event.
Moreover, I want to add a checkbox to each item of the list view. But the Checkbox property only supports vsList and vsReport mode, not vsIcon and vsSmallicon. Is there a workaround for it? Or is there any ready-to-use 3rd party VCL that supports such a feature?
But the Checkbox property only supports vsList and vsReport mode ...
That's not correct. You are looking at VCL documentation which states
Set Checkboxes to true to make check boxes appear next to the list items when ViewStyle is vsList or vsReport. ...
This is outdated information. See the documentation for the native control:
Version 6.00 and later Check boxes are visible and functional with all list view modes except the tile view mode introduced in ComCtl32.dll version 6. ...
Indeed if you try it on a regular TListView
control in one of the icon modes, you'll see there are no problems with check boxes.
However that's not going to help you. In this regard your question is malformed, it has the assumption that Checkboxes
work OK in list and report modes with a virtual list view control. That's not the case.
Check boxes are good when you can use the Checked
property on a list item. In a virtual list view control there are no items you can check. I'm quoting from the LVM_SETITEMCOUNT
message:
If the list-view control was created without the LVS_OWNERDATA style, sending this message causes the control to allocate its internal data structures for the specified number of items. ...
...
If the list-view control was created with the LVS_OWNERDATA style (a virtual list view), sending this message sets the virtual number of items that the control contains.
All the control knows is that there are that many items, there is no per item storage. VCL reflects the API control: when you request an item and your control has OwnerData
set, the OnData
event handler is called on a temporary item to reflect the item's properties.
In a virtual list view you manage checks by using state images. Quoting from the documentation:
... You can use state images, such as checked and cleared check boxes, to indicate application-defined item states. State images are displayed in icon view, small icon view, list view, and report view.
Below you'll find a basic implementation which holds item state information in a separate array. To run it, create a blank new form, create an OnCreate
handler for the form and paste the code.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.ImgList;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
FListView: TListView;
FListViewCheckStateArray: array of 0..1;
procedure ListViewData(Sender: TObject; Item: TListItem);
procedure ListViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
Bmp: TBitmap;
begin
FListView := TListView.Create(Self);
FListView.Parent := Self;
FListView.Align := alClient;
FListView.OwnerData := True;
FListView.ViewStyle := vsSmallIcon;
FListView.StateImages := TImageList.Create(Self);
Bmp := TBitmap.Create;
Bmp.PixelFormat := pf32bit;
Bmp.SetSize(16, 16);
DrawFrameControl(Bmp.Canvas.Handle, Rect(0, 0, 16, 16), DFC_BUTTON,
DFCS_BUTTONCHECK);
FListView.StateImages.Add(Bmp, nil);
DrawFrameControl(Bmp.Canvas.Handle, Rect(0, 0, 16, 16), DFC_BUTTON,
DFCS_BUTTONCHECK or DFCS_CHECKED);
FListView.StateImages.Add(Bmp, nil);
Bmp.Free;
FListView.Items.Count := 257;
SetLength(FListViewCheckStateArray, FListView.Items.Count);
FListView.OnData := ListViewData;
FListView.OnMouseDown := ListViewMouseDown;
end;
procedure TForm1.ListViewData(Sender: TObject; Item: TListItem);
begin
Item.Caption := Format('This is item %.2d', [Item.Index]);
Item.StateIndex := FListViewCheckStateArray[Item.Index];
end;
procedure TForm1.ListViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
Item: TListItem;
begin
if (Button = mbLeft) and
(htOnStateIcon in FListView.GetHitTestInfoAt(X, Y)) then begin
Item := FListView.GetItemAt(X, Y);
Assert(Assigned(Item));
FListViewCheckStateArray[Item.Index] :=
Ord(not Boolean(FListViewCheckStateArray[Item.Index]));
FListView.UpdateItems(Item.Index, Item.Index);
end;
end;
end.
PS: Drawing artifacts should be the subject of a different question.