Maintaining an older project using c++ Builder 2009
In a TListView
instance (ViewStyle = vsReport
), setup to operate virtual (OwnerData = true
) I wanted to try and improve speed as much as possible. Every tiny bit helps. In the OnData
event I noticed that Item->SubItems->Capacity
= 0 to start with, and it increases per 4 as sub items are added. I read in the docs that Capacity
is read only, yet I want to avoid TStrings
' internal reallocating as much as possible. Since I also need to do caching I figured I'd use a TStringList
as cache that has already grown to the required capacity. I assume TStrings
Assign()
will then immediately allocate an array big enough to store the required amount of Strings in ?
Item->SubItems->Assign(Cache.SubItems) ;
While this works I noticed this triggers ListView to call OnData again, and again and ... causing it to never stop.
Easily fixed again by doing this:
for (int x = 0 ; x < Cache.SubItems->Count ; x++)
Item->SubItems->Add(Cache.SubItems->Strings[x]) ;
But of course the whole point was to be able to tell SubItems
the amount of strings from the start.
I realize I may be running against an old VCL issue ? That has long been resolved ? Or is there a point to this behavior that I don't understand right now ?
Is there a way to 'enable' Capacity
to accept input ? So that it allocates just enough space for the strings that will be added ?
The TListView::OnData
event is triggered whenever the ListView needs data for a given list item, such as (but not limited to) drawing operations.
Note that when the OnData
event is triggered, the TListItem::SubItems
has already been Clear()
'ed beforehand. TStringList::Clear()
sets the Capacity
to 0, freeing its current string
array. That is why the Count
and Capacity
are always 0 when entering your OnData
handler.
The SubItems
property is implemented as a TSubItems
object, which derives from TStringList
. The TStrings::Capacity
property setter is implemented in TStringList
, and does what you expect to pre-allocate the string
array. But that is all it does - allocate VCL memory for the array, nothing more. There is still the aspect of updating the ListView subitems themselves at the Win32 API layer, and that has to be done individually as each string
is added to the SubItems
.
When your OnData
handler calls SubItems->Assign()
, you are calling TStrings::Assign()
(as TStringList
and TSubItems
do not override it). However, TStrings::Assign()
DOES NOT pre-allocate the string
array to the size of the source TStrings
object, as one would expect (at least, not in CB2009, I don't know if modern versions do or not). Internally, Assign()
merely calls Clear()
and then TStrings::AddStrings()
(which neither TStringList
nor TSubItems
override to pre-allocate the array). AddStrings()
merely calls TStrings::AddObject()
in a loop (which both TStringList
and TSubItems
do override).
All of that clearing and adding logic is wrapped in a pair of TStrings::(Begin|End)Update()
calls. This is important to note, because TSubItems
reacts to the update counter. When the counter falls to 0, TSubItems
triggers TListView
to make some internal updates, which includes calling Invalidate()
on itself, which triggers a whole repaint, and thus triggering a new series of OnData
events for list items that need re-drawing.
On the other hand, when you call SubItems->Add()
in your own manual loop, and omit the (Begin|End)Update()
calls, you are skipping the repaint of the whole ListView. TSubItems
overrides TStrings::Add/Object()
to (among other things) update only the specific ListView item that it is linked to. It does not repaint the whole ListView.
So, you should be able to set the Capacity
before entering your manual loop, if you really want to:
Item->SubItems->Capacity = Cache.SubItems->Count;
for (int x = 0; x < Cache.SubItems->Count; ++x)
Item->SubItems->Add(Cache.SubItems->Strings[x]);
In which case, you can use AddStrings()
instead of a manual loop:
Item->SubItems->Capacity = Cache.SubItems->Count;
Item->SubItems->AddStrings(Cache.SubItems);