Hey,

Is there an easy way with a TListView in report mode, to store a unique integer with each row? I've tried using the ".Data" property, but seem to end up with corruption quite often.

I'm trying to do this because I want to associate each row with a primary key ID from a database. At the moment I'm using a dynamically sized array, and adding the row id, to the db ID so I have to use, for example, rowsToIds[lv.SelectedIndex]; but it seems like a long-winded way to do it.

Is there an easier way to do this? Simple example of exactly what I'm trying to do...

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
  li: TListItem;
begin
  for i := 0 to 50 do begin
    li := lv1.Items.Add;
    li.Caption := 'xyz';
    li.SubItems.Add('abc');
    //Store a unique integer with the row here...
  end;
end;

procedure TForm1.lv1DblClick(Sender: TObject);
begin
  //Show the stored integer here...
end;

Thanks

Dani AI

Generated

A robust, portable pattern is to attach a small object (wrapper) to each TListItem and manage its lifetime yourself. That avoids fragile index-based lookups and the dangers of casting integers to pointers. ’s “Tag” idea is a quick hack (and exists on FMX items), and ’s SubItems.Objects integer-cast trick can work on 32-bit, but both are brittle—casting integers to TObject or relying on indexes will cause corruption or break on x64.

Store a tiny TObject that holds the DB ID, put its pointer in the item’s Data, and free it when the item is removed (use OnDeletion or free everything before clearing the list). Use NativeInt/NativeUInt for any pointer-sized integer conversions if you must cast; otherwise prefer the object wrapper.

Example (safe, minimal):

type
  TRowInfo = class
    DBID: NativeInt;
    constructor Create(AID: NativeInt);
  end;

constructor TRowInfo.Create(AID: NativeInt);
begin
  inherited Create;
  DBID := AID;
end;

Add the item and attach the object:

var
  li: TListItem;
  info: TRowInfo;
begin
  li := ListView1.Items.Add;
  li.Caption := '...';
  li.SubItems.Add('...');
  info := TRowInfo.Create(SomeDBID);
  li.Data := info; // store pointer to the object
end;

Retrieve and free (OnDblClick / OnDeletion):

// retrieval
info := TRowInfo(ListView1.Selected.Data);
ShowMessage(IntToStr(info.DBID));

// free when item is deleted
procedure TForm1.ListView1Deletion(Sender: TObject; Item: TListItem);
begin
  if Assigned(Item.Data) then
    TRowInfo(Item.Data).Free;
end;

Extra notes: use ListView.OwnerData (virtual list) or a TDictionary mapping IDs for very large datasets; always free any objects you allocate before clearing Items; and debug corruption by checking whether you accidentally cast integers to pointers or double-freeing the stored object.

Recommended Answers

All 2 Replies

li.Tag perhaps. Not a very nice solution, but it works.

unit IntObject;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs,
ComCtrls;

type
TForm1 = class(TForm)
ListView1: TListView;
procedure FormCreate(Sender: TObject);
procedure ListView1DblClick(Sender: TObject);
procedure ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
private
{ Private declarations }
aValue:Integer;
public
{ Public declarations }
end;

var
Form1 : TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var
i : Integer;
begin
for i := 0 to 50 do
begin

with ListView1.Items.Add do
begin
Caption := 'xyz';
SubItems.AddObject('abc', tobject(i));
end;
end;
end;
procedure TForm1.ListView1DblClick(Sender: TObject);
begin

if avalue>-1 then
showmessage(inttostr(aValue));
end;

procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
begin
if selected then
avalue:=Integer(Item.SubItems.Objects[0]) else
aValue:=-1;
end;

end.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.