Virtual TListView Item->SubItems->Assign() during OnData triggers refresh and then never ending updates

Virtual TListView Item->SubItems->Assign() during OnData triggers refresh and hence never ending updates

使用 C++ Builder 2009 维护旧项目

TListView 实例 (ViewStyle = vsReport) 中,设置为运行虚拟 (OwnerData = true) 我想尝试尽可能提高速度。每一点点都有帮助。在 OnData 事件中,我注意到 Item->SubItems->Capacity = 0 开始,随着子项目的添加,它每增加 4 个。我在文档中读到 Capacity 是只读的,但我想尽可能避免 TStrings' 内部重新分配。因为我还需要进行缓存,所以我想我会使用 TStringList 作为已经增长到所需容量的缓存。我 assume TStrings Assign() 然后会立即分配一个足够大的数组来存储所需数量的字符串 ?

Item->SubItems->Assign(Cache.SubItems) ;

虽然这有效,但我注意到这会触发 ListView 一次又一次地调用 OnData 并且......导致它永远不会停止。

通过这样做很容易修复:

for (int x = 0 ; x < Cache.SubItems->Count ; x++)
    Item->SubItems->Add(Cache.SubItems->Strings[x]) ;

当然,重点是能够从一开始就告诉 SubItems 字符串的数量。

我意识到我可能 运行 反对旧的 VCL 问题?那早就解决了?或者我现在不明白这种行为有什么意义吗?

有没有办法 'enable' Capacity 接受输入?以便它为将要添加的字符串分配足够的 space ?

只要 ListView 需要给定列表项的数据,例如(但不限于)绘图操作,就会触发 TListView::OnData 事件。

注意当OnData事件被触发时,TListItem::SubItems已经预先Clear()了。 TStringList::Clear()Capacity 设置为 0,释放其当前 string 数组。这就是输入 OnData 处理程序时 CountCapacity 始终为 0 的原因。

SubItems 属性 作为 TSubItems 对象实现,它派生自 TStringListTStrings::Capacity 属性 setter 在 TStringList 中实现,并按照您的预期预分配 string 数组。但仅此而已——为数组分配 VCL 内存,仅此而已。在 Win32 API 层仍然存在更新 ListView 子项本身的方面,并且必须单独完成每个 string 被添加到 SubItems.

当您的 OnData 处理程序调用 SubItems->Assign() 时,您正在调用 TStrings::Assign()(因为 TStringListTSubItems 不会覆盖它)。但是,TStrings::Assign() 不会string 数组预分配到源 TStrings 对象的大小,正如人们所期望的那样(至少,不在 CB2009 中,我不知道现代版本是否有)。在内部,Assign() 仅调用 Clear(),然后调用 TStrings::AddStrings()TStringListTSubItems 都不会重写以预分配数组)。 AddStrings() 仅在循环中调用 TStrings::AddObject()TStringListTSubItems 都覆盖)。

所有这些清除和添加逻辑都包含在一对 TStrings::(Begin|End)Update() 调用中。这一点很重要,因为 TSubItems 对更新计数器做出反应。当计数器降为 0 时,TSubItems 触发 TListView 进行一些内部更新,其中包括调用 Invalidate() 自身,这会触发整个重绘,从而触发一系列新的 OnData 需要重新绘制的列表项的事件。

另一方面,当您在自己的手动循环中调用 SubItems->Add() 并省略 (Begin|End)Update() 调用时,您将跳过整个 ListView 的重绘。 TSubItems 覆盖 TStrings::Add/Object() 以(除其他外)仅更新它链接到的特定 ListView 项目。它不会重新绘制整个 ListView。

因此,如果您真的想要,您应该能够在进入手动循环之前设置 Capacity

Item->SubItems->Capacity = Cache.SubItems->Count;
for (int x = 0; x < Cache.SubItems->Count; ++x)
    Item->SubItems->Add(Cache.SubItems->Strings[x]);

在这种情况下,您可以使用 AddStrings() 而不是手动循环:

Item->SubItems->Capacity = Cache.SubItems->Count;
Item->SubItems->AddStrings(Cache.SubItems);