I want to fill a form at runtime with a lot of comboboxes with identical lists. They also get the same event handler, which is acting depending on the Sender
objects's Name. However, this takes a pretty long time and I was guessing I do something wrong.
I'm using XE2 Rad Studio C++ Builder and the VCL GUI.
Edit: Those boxes contain a different kinds of content and are distributed over a few tabPages within the form. however, it's necessary to display what it selected at at least 80 of them at a glance. Would it maybe be better to replace them with TLabels and create a TCombobox when clicking on the TLabel to select a different element?
The Code looks similar to this:
void __fastcall TForm::TForm(){
int i=0;
TStringList* targetlist = new TStringList();
targetlist->Add("Normal");
targetlist->Add("Inverted");
Vcl::Stdctrls::TComboBox **com = new Vcl::Stdctrls::TComboBox[512];
for(i=0;i<512;++i){
com[i]=new Vcl::Stdctrls::TComboBox(this);
com[i]->Parent=this;
com[i]->Name.printf(L"Combo_%d", i);
com[i]->SetBounds(10, 198 + 20 * i, 130, 200);
com[i]->Items = targetlist;
com[i]->ItemIndex = 0;
com[i]->Style = csDropDownList;
com[i]->OnChange = MyComboTriggerChange;
}
}
One iteration seems to take around 20ms on my machine (testedt with std::clock
), which make this part ~10s long. The pointers are deleted at the form's destruction. I just put their declarations here for simplifications.
Is there a better way to create multiple comboboxes? Maybe clone them?
You seriously need to redesign your UI. Using 512 TComboBox
controls on one screen with the same list of values makes no logical sense, and is a waste of time and resources. There are better ways to display 512 strings on a screen, such as a TListView
in report mode, or a TListBox
(both of them support a virtual mode so they can share common data without wasting memory). Or use a TValueListEditor
or TStringGrid
with an esPickList
inline editor. Or, if you are really adventurous, write a custom control from scratch so you use 1 efficient control instead of 512 separate controls. Anything is better than 512 TComboBox
controls.
That being said, TComboBox
does not support a virtual mode, like TListBox
and TListView
do, but there are a couple of optimizations you can still make to speed up your TComboBox
es a little:
don't make 512 copies of the same TStringList
content. Anything you add to the TComboBox::Items
is stored inside the TComboBox
's memory. You should strive to reuse your single TStringList
and let everything delegate to it as needed. In this case, you can set the TComboBox::Style
property to csOwnerDrawFixed
and use the TComboBox::OnDrawItem
event to draw the TStringList
strings on-demand. You still need to add strings to each TComboBox
, but they can be empty strings, at least.
subclass TComboBox
to override its virtual CreateParams()
method and it remove the CBS_HASSTRINGS
window style, then the TComboBox
does not actually need to store empty strings in its memory.
Try something like this:
class TMyComboBox : public Vcl::Stdctrls::TComboBox
{
typedef Vcl::Stdctrls::TComboBox inherited;
private:
TStrings *fSharedItems;
void __fastcall SetSharedItems(TStrings *Values)
{
if (fSharedItems != Values)
{
fSharedItems = Values;
Items->BeginUpdate();
try
{
Items->Clear();
if (fSharedItems)
{
for (int i = 0; i < fSharedItems->Count; ++i)
Items->Add(L"");
}
}
__finally
{
Items->EndUpdate();
}
}
}
protected:
virtual void __fastcall CreateParams(TCreateParams &Params)
{
inherited::CreateParams(Params);
Params.Style &= ~CBS_HASSTRINGS;
}
virtual __fastcall DrawItem(int Index, TRect Rect, TOwnerDrawState State)
{
// draw the items however you want...
if (fSharedItems)
Canvas->TextRect(Rect.Left, Rect.Top, fSharedItems->Strings[Index]);
}
public:
__fastcall TMyComboBox(TComponent *Owner)
: Vcl::Stdctrls::TComboBox(Owner)
{
Style = csOwnerDrawFixed;
}
__property TStrings* SharedItems = {read=fSharedItems, write=SetSharedItems};
};
class TMyForm : public TForm
{
...
private:
TStringList* targetlist;
TMyComboBox **com;
void __fastcall MyComboTriggerChange(TObject *Sender);
...
public:
__fastcall TMyForm(TComponent *Owner);
__fastcall ~TMyForm();
...
};
__fastcall TMyForm::TMyForm(TComponent *Owner)
: TForm(Owner)
{
targetlist = new TStringList;
targetlist->Add("Normal");
targetlist->Add("Inverted");
com = new TMyComboBox*[512];
for(int i=0;i<512;++i)
{
com[i] = new TMyComboBox(this);
com[i]->Parent = this;
com[i]->Name = String().sprintf(L"Combo_%d", i);
com[i]->SetBounds(10, 198 + 20 * i, 130, 200);
com[i]->SharedItems = targetlist;
com[i]->ItemIndex = 0;
com[i]->OnChange = &MyComboTriggerChange;
}
}
__fastcall TMyForm::~TMyForm()
{
delete targetlist;
delete[] com;
}
void __fastcall TMyForm::MyComboTriggerChange(TObject *Sender)
{
TMyComboBox *cb = static_cast<TMyComboBox*>(Sender);
// use targetlist->Strings[cb->ItemIndex] as needed...
}