【问题标题】:Efficiently populate combobox in Delphi在 Delphi 中有效地填充组合框
【发布时间】:2018-02-21 02:50:13
【问题描述】:

需要在 TComboBox 中添加许多项目(超过 10k)(我知道 TComboBox 不应该包含许多项目,但我不能更改它)而不添加重复项。 所以我需要在添加之前搜索完整列表。我想避免使用 TComboBox.items.indexof,因为我需要二进制搜索,但二进制查找在 TStrings 中不可用。

所以我创建了一个临时的 Tstringlist,将 sorted 设置为 true 并使用 find。但现在将临时 Tstringlist 分配回 TComboBox.Items

(myCB.Items.AddStrings(myList)) 

真的很慢,因为它会复制整个列表。有没有办法移动列表而不是复制它?或者任何其他有效填充我的 TComboBox 的方法?

【问题讨论】:

  • 只要解决根本问题。组合框中的 10k 个项目是荒谬的。什么版本的delphi?
  • @TomBrunberg Assign 甚至比 AddStrings 还要慢
  • 主要问题是组合框的项目数量不合理。对于 10k 个项目,我认为半秒并没有那么慢,很难让它更快。您应该使用只允许加载可见项目的组件,我认为 TComboBox 没有这样的功能。
  • 听起来你将不得不放弃。你不会解决根本问题,所以游戏就结束了。
  • 我现在无法尝试,但您可以检查发送 CB_INITSTORAGE message 是否有帮助。请注意,他们 (MSDN) 将 100 称为一个很大的数字,这表明 10,000 个项目的数量对于组合框来说确实过高。

标签: delphi combobox delphi-xe3


【解决方案1】:

无法将列表“移动”到组合框中,因为组合框的存储属于内部 Windows 控件实现。它不知道直接使用您的 Delphi TStringList 对象的任何方法。它提供的只是一个将一项添加到列表中的命令,TComboBox 然后使用该命令将字符串列表中的每个项逐个复制到系统控件中。避免将数千个项目复制到组合框中的唯一方法是完全避免该问题,例如使用不同类型的控件或减少需要添加的项目数量。

列表视图具有“虚拟”模式,您只需告诉它应该有多少个项目,然后当它需要了解有关在屏幕。不可见的项目在列表视图的实现中不会占用任何空间,因此您可以避免复制。但是,system combo boxes don't have a "virtual" mode。您也许可以找到一些提供该功能的第三方控件。

减少您需要放入组合框中的项目数量是您的下一个最佳选择,但只有您和您的同事拥有必要的领域知识来找出最佳方法。

【讨论】:

  • VCL 的TComboBox 确实没有虚拟模式,但它确实有所有者绘制模式,所以你不必将实际的字符串加载到其中,你可以加载空占位符,然后在需要时绘制实际的字符串。否则,ComboBox 只是一个美化的 Edit+ListBox 组合,您可以在自定义 TForm 中手动模拟它,而 TListBox 确实具有虚拟模式。
【解决方案2】:

正如 Rudy Velthuis 在 cmets 中已经提到的,假设您使用的是 VCLCB_INITSTORAGE 消息可能是一个选项:

SendMessage(myCB, CB_INITSTORAGE, myList.Count, 20 * myList.Count*sizeof(Integer));

20 是您的平均字符串长度。

结果(在 i5-7200U 和 20K 项目上,随机长度介于 1 到 50 个字符之间):

  • 没有CB_INITSTORAGE: ~ 265ms
  • CB_INITSTORAGE: ~215ms

因此,虽然您可以通过预分配内存来稍微加快速度,但更大的问题似乎是糟糕的用户体验。用户如何在包含这么多项目的组合框中找到正确的元素?

【讨论】:

【解决方案3】:

尽管将 10k 项保存在 TComboBox 中很疯狂,但这里的有效策略是将缓存保存在单独的对象中。例如,声明:

    { use a TDictionary just for storing a hashmap }        
    FComboStringsDict : TDictionary<string, integer>;

在哪里

procedure TForm1.FormCreate(Sender: TObject);
var
  i : integer;
  spw : TStopwatch;
begin
  FComboStringsDict := TDictionary<string, integer>.Create;
  spw := TStopwatch.StartNew;
  { add 10k random items }
  for i := 0 to 10000 do begin
    AddComboStringIfNotDuplicate(IntToStr(Floor(20000*Random)));
  end;
  spw.Stop;
  ListBox1.Items.Add(IntToStr(spw.ElapsedMilliseconds));
end;

function TForm1.AddComboStringIfNotDuplicate(AEntry: string) : boolean;
begin
  result := false;
  if not FComboStringsDict.ContainsKey(AEntry) then begin
    FComboStringsDict.Add(AEntry, 0);
    ComboBox1.Items.Add(AEntry);
    result := true;
  end;
end;

以这种方式最初添加 10k 个项目大约需要 0.5 秒。

{ test adding new items }
procedure TForm1.Button1Click(Sender: TObject);
var
  spw : TStopwatch;
begin
  spw := TStopwatch.StartNew;
  if not AddComboString(IntToStr(Floor(20000*Random))) then
    ListBox1.Items.Add('Did not add duplicate');
  spw.Stop;
  ListBox1.Items.Add(IntToStr(spw.ElapsedMilliseconds));
end;

但是添加每个后续项目非常快.IndexOf。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-22
    • 2011-11-20
    • 2013-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多