shineworld 79 Posted February 19, 2024 A native VCL, and not Windows-based, TComboBox cotrol. In one application I have hundreds and hundreds of TComboBoxes with values None and then from 1 to 256. I have used many strategies to enter values in these combos but the time required is enormous. The fastest system was to leave them empty, at FormCreate create the fields by hand by searching all TComboBox components ...... and on some PCs it takes almost 3 or more seconds.... // creates components hash and binds component events to code and adds inputs/outputs items C := ComponentCount - 1; for I := 0 to C do begin Component := Components[I]; FComponentsHashManager.Add(Component); if Component is TComboBox then begin with TComboBox(Component) do begin if Pos('InputNum', Name) <> 0 then begin SendMessage(Handle, WM_SETREDRAW, Ord(False), 0); SendMessage(Handle, CB_INITSTORAGE, I_O.MAX_DIGITAL_INPUTS + 1, COMBOBOX_MAX_TEXT_LENGTH * (I_O.MAX_DIGITAL_INPUTS + 1)); for J := 0 to I_O.MAX_DIGITAL_INPUTS do Items.Insert(J, InputsList.Strings[J]); SendMessage(Handle, WM_SETREDRAW, Ord(True), 0); Continue; end; if Pos('OutNum', Name) <> 0 then begin SendMessage(Handle, WM_SETREDRAW, Ord(False), 0); SendMessage(Handle, CB_INITSTORAGE, I_O.MAX_DIGITAL_OUTPUTS + 1, COMBOBOX_MAX_TEXT_LENGTH * (I_O.MAX_DIGITAL_OUTPUTS + 1)); for J := 0 to I_O.MAX_DIGITAL_OUTPUTS do Items.Insert(J, OutputsList.Strings[J]); SendMessage(Handle, WM_SETREDRAW, Ord(True), 0); Continue; end; end; end; ... Share this post Link to post
Lars Fosdal 1804 Posted February 19, 2024 Not sure why this was attached to a different thread, so I split it off, @shineworld 1 Share this post Link to post
JonRobertson 76 Posted February 19, 2024 20 minutes ago, Lars Fosdal said: Not sure why this was attached to a different thread, so I split it off Perhaps it was a request (in Delphi 13) for a new VCL combo box control that is not a subclass of the Win32API COMBOBOX class. 1 Share this post Link to post
Lars Fosdal 1804 Posted February 19, 2024 That's possible, @JonRobertson, but adding context to code is probably smart. Anyways, @shineworld should do a proper write-up and perhaps include a demo app with a comparison that shows the benefits of doing it his way, and post it on github and the QualityPortal when it comes back online. Share this post Link to post
Remy Lebeau 1493 Posted February 19, 2024 (edited) 2 hours ago, shineworld said: SendMessage(Handle, WM_SETREDRAW, Ord(False), 0); SendMessage(Handle, CB_INITSTORAGE, I_O.MAX_DIGITAL_INPUTS + 1, COMBOBOX_MAX_TEXT_LENGTH * (I_O.MAX_DIGITAL_INPUTS + 1)); for J := 0 to I_O.MAX_DIGITAL_INPUTS do Items.Insert(J, InputsList.Strings[J]); SendMessage(Handle, WM_SETREDRAW, Ord(True), 0); Some suggestions: don't use WM_SETREDRAW directly. Use the TComboBox.Items.(Begin|End)Update() instead, which will use WM_SETREDRAW internally for you. CB_INITSTORAGE helps, but simply don't waste time putting actual string values in so many TComboBoxes to begin with. Instead, fill them with blank strings, and set their Style property to csOwnerDrawFixed and use their OnDrawItem event to render the actual text whenever a drop-down list is visible. consider using a different UI control, such as a TListBox, or a TListView in vsReport mode. Both have true virtual modes that can handle a lot of items very quickly. Edited February 19, 2024 by Remy Lebeau 1 Share this post Link to post
Brian Evans 111 Posted February 19, 2024 (edited) Common to populate the list in OnDropDown. procedure TForm1.ComboBox1DropDown(Sender: TObject); begin ComboBox1.Items.BeginUpdate; Try ComboBox1.Items.Clear; ComboBox1.Items.Add('None'); for var I:integer := 1 to 256 do ComboBox1.Items.Add(IntToStr(I)); Finally ComboBox1.Items.EndUpdate; End; end; Can also clear the list in OnExit. procedure TForm1.ComboBox1DropDown(Sender: TObject); begin Var AComboBox := TComboBox(Sender); If AComboBox.Items.Count = 0 then begin AComboBox.Items.BeginUpdate; Try AComboBox.Items.Add('None'); for var I:integer := 1 to 256 do AComboBox.Items.Add(IntToStr(I)); Finally AComboBox.Items.EndUpdate; End; end; end; procedure TForm1.ComboBox1Exit(Sender: TObject); begin TComboBox(Sender).Items.Clear; end; Edited February 19, 2024 by Brian Evans 2 Share this post Link to post
Vincent Parrett 804 Posted February 19, 2024 5 hours ago, shineworld said: In one application I have hundreds and hundreds of TComboBoxes Perhaps a combo box isn't the best option here - a numeric input might be a better option - use 0 for none or create your own custom control. 1 Share this post Link to post
DelphiUdIT 209 Posted February 19, 2024 (edited) Seems that you populate your controls for an automation / building application. It' necessary to view the values with a combobox ? You can use a label (or other simple control) for every field and when a user click in one of them you can propose a combobox to change the present value or numeric keyboard or others custom control. Edited February 19, 2024 by DelphiUdIT 1 Share this post Link to post
shineworld 79 Posted February 20, 2024 I've used a TComboBox set as List because arguments start from None, 1, 2 .. 256 and use can fastly move to any using key: However, I guess I to move them to the Edit field and check the entered data as suggested. In the past I've used old SweedControls combobox made by zero with Delphi code but moving to 64bit the old and not supported library does not work fine. Share this post Link to post
Kas Ob. 125 Posted February 20, 2024 1 minute ago, shineworld said: I guess I to move them to the Edit field and check the entered data as suggested. Will work, but will need lot of details to be ugly. I am suggesting as Remy suggested, switch all the CobmoBoxes to custom draw (virtual mode) and solve this for good. Share this post Link to post
Pat Foley 53 Posted February 20, 2024 Here is a sample of runtime created controls that use same string. Plus assigns event handler. const InputPLC256 = 'x0,x1,x32,pin64,pin128,pin255,None'; procedure TForm1.Button4Click(Sender: TObject); begin for var I := 1 to 100 do begin var P := TPanel.Create(Self); with P do begin Name := 'TagInputEdit_' + I.ToString; SetBounds(280 - 5, 17 + I * 34, 204, 30); Parent := Self; Caption := ''; end; var L := TLabel.Create(Self); with L do begin SetBounds(80, 6, 64, 22); Parent := P; end; var CB := TComboBox.Create(Self); with CB do begin SetBounds(5, 3, 65, 22); Parent := P; Show; onchange := TagUpdater; Tag := NativeInt(L); items.CommaText := InputPLC256;//memo1.Lines; Text := Items[Random(items.Count)-1]; L.Caption := Text; end; end; end; procedure TForm1.TagUpdater(Sender: Tobject); begin var S := Sender as TComboBox; var L := TLabel(S.Tag); var N := S.Parent.Name; L.Caption := Format('%s %s', [N, S.Text]); end; 1 Share this post Link to post
Stano 144 Posted February 20, 2024 I will note that most programmers completely reject the "with" construct. 3 Share this post Link to post
JonRobertson 76 Posted February 20, 2024 27 minutes ago, Stano said: I will note that most programmers completely reject the "with" construct. As does the Delphi debugger. 3 Share this post Link to post
Remy Lebeau 1493 Posted February 20, 2024 14 hours ago, shineworld said: I've used a TComboBox set as List because arguments start from None, 1, 2 .. 256 and use can fastly move to any using key: You say you have hundreds of TComboBoxes, but the screenshot above only shows 7 (presumably 11?). If this screen is similar for the rest of the app, where each item on the side list displays its own page with just a handful of ComboBoxes on it, then a simple solution would be to NOT load every page at one time, but instead to load one page at a time only when it is made visible to the user, and then unload it when it is no longer visible to the user. That will greatly speed up your load times, and then you don't have to resort to using hacks like owner-drawing, OnDropDown refreshes, etc. You can move each page to its own TFrame that you create and destroy when needed, that way you still regain design-time support for your page layout. 4 Share this post Link to post
John R. 20 Posted February 22, 2024 Alternatively, your UI seems to be very similar to a table/grid layout so you could potentially use a grid component with support to input fields such as combo boxes. Those grids are usually highly optimized to handle thousands of rows with various data types and input options. They usually allow the dynamic instantiation of input controls (such as combo boxes) when a row is actually being edited to save resources. I highly recommend DevExpress' ExpressQuantumGrid: https://docs.devexpress.com/VCL/171093/ExpressQuantumGrid/vcl-data-grid Or their ExpressVerticalGrid: https://docs.devexpress.com/VCL/403720/ExpressVerticalGrid/concepts/vcl-vertical-grid Both of which I extensively use for this kind of tasks. 2 Share this post Link to post
Yaron 57 Posted Tuesday at 09:14 PM (edited) Beyond what you're already using, there is a slight speedup trick of setting the TComboBox's style to "csSimple" when adding the items and then restoring the style afterwards. And since you're inserting the same data into the controls, might be slightly faster to do it only once, keep a pointer to the first object and then just do Assign on the items. But I'm afraid the speed difference between adding items to a TStringList compared to a TComboBox is ridiculous and it might just be better to write a replacement component (which I'm currently investigating). Edited Tuesday at 09:18 PM by Yaron Share this post Link to post
Yaron 57 Posted 5 hours ago I designed an open-source (MIT license) TComboBox replacement that's not based on WinAPI: https://github.com/bLightZP/ZPComboBox My software (Zoom Player) uses a similar options dialog design to your own and using this component cut the form's create -> show time in half. P.S. I see you're using TTreeView, if you're restoring the last TreeView selected item when the form opens incorrectly, it can incur ~700ms penalty, something worth checking out. 1 Share this post Link to post
Die Holländer 66 Posted 4 hours ago If the issue is that these comboboxes should always contain these values in your forms I would try to create a simple *.dfm text reader and add the "items" property and the values to the comboboxes. object InputNum_1: TComboBox Left = 8 Top = 16 Width = 217 Height = 23 TabOrder = 0 end end object InputNum_1: TComboBox Items.Strings = ( 'None' '1' '2' '3' '4' '5' '6' '7' '8' '9' '10') Left = 8 Top = 16 Width = 217 Height = 23 TabOrder = 0 Share this post Link to post
Yaron 57 Posted 4 hours ago Just now, Die Holländer said: If the issue is that these comboboxes should always contain these values in your forms I would try to create a simple *.dfm text reader and add the "items" property and the values to the comboboxes. This doesn't solve the performance issue. TComboBox is inherently slow because it uses SendMessage for every item you add. Share this post Link to post
Die Holländer 66 Posted 4 hours ago 5 minutes ago, Yaron said: This doesn't solve the performance issue. TComboBox is inherently slow because it uses SendMessage for every item you add. I don't understand. Sendmessages? The *.dfm and *.pas form files are compiled into the *.dcu and in you executable. Share this post Link to post
Yaron 57 Posted 3 hours ago 54 minutes ago, Die Holländer said: I don't understand. Sendmessages? The *.dfm and *.pas form files are compiled into the *.dcu and in you executable. TComboBox is a wrapper for a WinAPI component. When you call ComboBox.Items.Add, it triggers a WinAPI SendMessage call, which is very very slow (relative to a standard TStringList.Add), I wrote about it here if you're interested: https://www.reddit.com/r/ZoomPlayer/comments/1iu0lm3/low_level_code_optimization_or_how_i_made_zoom/ Share this post Link to post