Reproduction: click the button to initialize the ListView, then select any item (except the first). The output in the Form caption will show that, having accessed TopItem
, the value of Item
has now been changed.
Can anyone explain this?
procedure TForm1.Button1Click(Sender: TObject);
begin
ListView1.OwnerData := True;
ListView1.Items.Count := 5;
end;
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
Item.Caption := '...';
end;
procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
var
one, two: string;
begin
if Item <> nil then
one := IntToStr(Item.Index)
else
one := 'nil';
if ListView1.TopItem <> nil then;
if Item <> nil then
two := IntToStr(Item.Index)
else
two := 'nil';
Form1.Caption := one + '/' + two;
end;
In non-virtual mode (OwnerData=False
), the ListView holds physical items, where every item is represented by a unique TListItem
object in memory. But, in virtual mode (OwnerData=True
), the ListView has no physical items at all, so no TListItem
object is created for each item.
However, since TListView
exposes public interfaces that use TListItem
, and those interfaces have to continue working even in virtual mode, TListView
holds 1 internal TListItem
object in virtual mode, which gets reused for any operation that involves a TListItem
.
So, in your example, when you select any list item, the OnSelectItem
event is fired with a TListItem
parameter, so TListView
has to first fire its OnData
event to fill the internal TListItem
with data for the selected item. Then, when you access the TListView.TopItem
property, TListView
has to fire its OnData
event again to fill that same internal TListItem
object with new data for the top visible item.
That is why you are seeing the Item.Index
property change value.
When using virtual mode, don't expect a given TListItem
to remain intact after you have accessed a different TListItem
. In fact, you should really avoid using any TListItem
operations as much as possible. If you need to access information about a particular list item, and that information is not already available in your own data source, then you should query the underlying ListView
control using the Win32 API directly, so as not to affect the TListView
's sole TListItem
object, eg:
procedure TForm1.ListView1SelectItem(Sender: TObject;
Item: TListItem; Selected: Boolean);
var
one, two: string;
begin
if Item <> nil then
one := IntToStr(Item.Index)
else
one := 'nil';
//if ListView1.TopItem <> nil then; // Item is affected!
ListView_GetTopIndex(ListView1.Handle); // Item is not affected!
if Item <> nil then
two := IntToStr(Item.Index)
else
two := 'nil';
Caption := one + '/' + two;
end;