2017-09-12 82 views
3

需要在TComboBox中添加很多項目(超過10k)(我知道TComboBox不應該容納很多項目,但它不是由我來改變它)而不添加重複項。 所以我需要在添加之前搜索完整列表。我想避免TComboBox.items.indexof,因爲我需要二進制搜索,但二進制查找在TStrings中不可用。在Delphi中有效地填充組合框

所以我創建了一個臨時的Tstringlist,將其設置爲true並使用find。但現在分配臨時的TStringList回TComboBox.Items

(myCB.Items.AddStrings(myList)) 

是因爲它拷貝整個列表很慢。有什麼辦法可以移動列表而不是複製它?或者任何其他方式來有效地填充我的TComboBox?

+1

只需修復底層問題。組合框中的10k項是荒謬的。什麼版本的delphi? –

+0

@TomBrunberg Assign比AddStrings更慢 – siwmas

+0

@J ...同意10k很荒謬,但這並不能改變這樣一個事實,即爲這樣的場景添加項目到combobox看起來效率很低?除非我錯過了什麼.. Delphi版本 - > XE3 – siwmas

回答

2

隨着魯迪Velthuis已經在評論中提到的,並假設你使用VCL,則CB_INITSTORAGE消息可能是一個選項:

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

其中20是一般的字符串長度。

結果(在i5-7200U和20K項與隨機長度介乎1和50個字符):

  • 而不CB_INITSTORAGE:〜265ms
  • CB_INITSTORAGE:〜215ms

所以雖然可以通過預先分配內存來加快速度,但更大的問題似乎是糟糕的用戶體驗。用戶如何在一個組合框中找到合適的元素以及這麼多項目?

+0

使用[TStopWatch](http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.Diagnostics.TStopwatch)來測量時間。 – ventiseis

6

沒有辦法將列表「移動」到組合框中,因爲組合框的存儲屬於內部Windows控件實現。它不知道任何直接消耗你的Delphi對象的方法。它提供的一個命令是將一個項目添加到列表中,然後TComboBox用於將每個項目從字符串列表中逐一複製到系統控件中。避免將數以千計的項目複製到組合框的唯一方法是完全避免此問題,例如通過使用不同類型的控件或減少需要添加的項目數量。

列表視圖有一個「虛擬」的模式,在這裏你只要告訴它應該多少項目有,然後它時,它需要知道什麼是在屏幕上可見的細節回調到您的程序。不可見的項目不佔用列表視圖實現中的任何空間,因此您避免了複製。但是,system combo boxes don't have a "virtual" mode。您可能能夠找到一些提供該功能的第三方控件。

減少組合框中需要放置的項目的數量是您的下一個最佳選擇,但只有您和您的同事擁有必要的領域知識才能找出實現這一目標的最佳方法。

+1

確實,VCL的'TComboBox'沒有虛擬模式,但它確實擁有所有者繪製模式,因此您不必將實際字符串加載到其中,只需加載空白佔位符,然後繪製實際需要的字符串。否則,ComboBox只是一個美化的Edit + ListBox組合,您可以在自定義的'TForm'中手動模擬,並且'TListBox'確實具有虛擬模式。 –

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; 

但添加每個後續項目是非常快的< 1ms。這是一個笨拙的實現,但您可以輕鬆地將此行爲包裝到自定義類中。這個想法是保持你的數據模型儘可能與可視化組件分開 - 在添加或刪除項目時保持它們同步,但是在查找速度很快的字典上執行大量搜索。刪除項目仍然依賴於.IndexOf