Faster way to create TCombobox at Runtime - c++

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 TComboBoxes 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...
}

Related

What to pass as a Sender in a button OnClick method?

I have a function that creates a button dynamically
void createBtn(News obj,TForm *Form1){
TButton *spam = new TButton(Form1);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = "Spam";
}
I need to assign an OnClick event to it, so I added the following line to the function above:
spam->OnClick = spamClick;
The code for spamClick is:
void __fastcall TForm1::spamClick(TObject *Sender, News obj)
{
allUsers[userIndex].spamNews(obj);
ShowMessage("Done!");
}
The problem is, I need to pass the obj in the line, but the function requires 2 arguments, which are the Sender and the obj.
spam->OnClick = spamClick(obj); // error here
But I do not know what to pass. I've tried Form1, spam, and newsCard. But nothing works.
What do I pass as a Sender? Or, is there a way to assign an OnClick event to the button inside createBtn()?
Edit:
class News has the following definition
class News
{
public:
News(string, string, string, Dates);
string title;
string description;
Dates date;
int rate;
string category;
vector<Comment> comments;
int spamCount;
static int newsCount;
int newsID;
int numOfRatedUsers;
};
and spamNews is a function in the user class that pushes the obj.newsID into a vector in the user then increases the spamCount.
void user::spamNews(News& obj) {
//check that the news in not already spammed
if(!findNews(spammedNews,obj)){
spammedNews.push_back(obj.newsID);
obj.spamCount++;
}
}
Your second approach doesn't work, because you are trying to call spamClick() first and then assign its return value to the OnClick event.
Your first approach is the correct way, however you can't add parameters to the OnClick event handler.
TButton has Tag... properties for holding user-defined data. However, since the News object is not being passed around by pointer, the Tag... properties are not very helpful in this case (unless the News object is held in an array/list whose index can then be stored in the Tag).
Otherwise, I would suggest deriving a new class from TButton to hold the News object, eg:
class TMyButton : public TButton
{
public:
News NewsObj;
__fastcall TMyButton(TComponent *Owner, const News &obj)
: TButton(Owner), NewsObj(obj) {}
};
void TForm1::createBtn(const News &obj)
{
TMyButton *spam = new TMyButton(this, obj);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = _D("Spam");
spam->OnClick = &spamClick;
}
void __fastcall TForm1::spamClick(TObject *Sender)
{
MyButton *btn = static_cast<TMyButton*>(Sender);
allUsers[userIndex].spamNews(btn->NewsObj);
ShowMessage(_D("Done!"));
}
UPDATE: Since your News objects are being stored in a vector that you are looping through, then a simpler solution would be to pass the News object to createBtn() by reference and then store a pointer to that object in the TButton::Tag property, eg:
void TForm1::createBtn(News &obj)
{
TButton *spam = new TButton(this);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = _D("Spam");
spam->Tag = reinterpret_cast<NativeInt>(&obj);
spam->OnClick = &spamClick;
}
void __fastcall TForm1::spamClick(TObject *Sender)
{
TButton *btn = static_cast<TButton*>(Sender);
News *obj = reinterpret_cast<News*>(btn->Tag);
allUsers[userIndex].spamNews(*obj);
ShowMessage(_D("Done!"));
}
Or, using the TMyButton descendant:
class TMyButton : public TButton
{
public:
News *NewsObj;
__fastcall TMyButton(TComponent *Owner)
: TButton(Owner) {}
};
void TForm1::createBtn(News &obj)
{
TMyButton *spam = new TMyButton(this);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = _D("Spam");
spam->NewsObj = &obj;
spam->OnClick = &spamClick;
}
void __fastcall TForm1::spamClick(TObject *Sender)
{
MyButton *btn = static_cast<TMyButton*>(Sender);
allUsers[userIndex].spamNews(*(btn->NewsObj));
ShowMessage(_D("Done!"));
}

Why my code doesn't show my TButton's array?

I'm making a simple hangman game in c++ builder community edition and my game consists of buttons that represents letters and if the letter doesn't appear inside of the word you lose a life, and so on, and so on...
But I'd though that is a little repetitive my code if I make a TButton for every letter in the abcedary. So I decide to make an array of TButton my surprise was when I code everything and any of them appear in my form :c.
If someone can help me a little I'll be so happy haha.
Tgame class...
class Tgame : public TForm
{
__published: // IDE-managed Components
TText *word;
private: // User declarations
TButton* chars[23];
public: // User declarations
__fastcall Tgame(TComponent* Owner);
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
};
And the constructor implementation...
for(int i = 0; i < 23; ++i) {
this->chars[i] = new TButton(this);
this->chars[i]->Height = 33;
this->chars[i]->Width = 49;
this->chars[i]->Position->X = startX;
this->chars[i]->Position->Y = startY;
startX += difX;
startY += difY;
this->chars[i]->Opacity = 1;
this->chars[i]->Visible = true;
this->chars[i]->Text = "A";
}
You construct the TButton setting its owner (the component responsible for deleting it).
this->chars[i] = new TButton(this);
But you don't set its Parent, which is the component in which the TButton will appear visually. So, add this line:
this->chars[i]->Parent = this;
Note: The default values for Opacity and Visible are 1 and true so you don't need to set those explicitly.

How to check all check boxes in form application programatically?

I want to check all checkboxes when click on the button. All object are in form application of visual studio 2010 c++. The point of problem is that every object (checkbox) has various name, CheckBox1, CheckBox2, ... I make UnicodeString with value "CheckBox", and int value that begin with 1, and put it together in third variable to find object, and that's work, but don't have a clue how to check all those boxes, please help.
Windows 7, 64, Visual studio 2010(c++) or c++ builder 2010
I did something similar for another component, this is how I did using C++ Builder.
for (int i = 0; i < this->ComponentCount; i++)
{
TCheckBox *TempCheckBox = dynamic_cast<TCheckBox *>(this->Components[i]);
if (TempCheckBox)
{
TempCheckBox->Checked = true;
}
}
This will iterate through all the components on your form, if the component is a TCheckBox it will be checked.
Why dont you add everything to a vector containing checkboxes, and then iterate through them all when necessary? This will allow you to reference each checkbox individually, but yet all at once.
cliext::vector<System::Windows::Forms::CheckBox^> items;
items.push_back(checkbox1);
.
.
.
items.push_back(checkboxN);
It is important that you also include
#include <cliext/vector>
due to the fact that the normal vector in the standard library is currently unable to support this control.
In C++Builder, you can place all of your TCheckBox* pointers into an array or std::vector, which you can then loop through when needed, eg:
TCheckBox* cb[10];
__fastcall TForm1::TForm1(TComponent *Owner)
: TForm(Owner)
{
cb[0] = CheckBox1;
cb[1] = CheckBox2;
...
cb[9] = CheckBox10;
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{
for (int i = 0; i < 10; ++i)
cb[i]->Checked = true;
}
If you have a lot of checkboxes and do not want to fill in the entire array by hand, you can use a loop instead:
__fastcall TForm1::TForm1(TComponent *Owner)
: TForm(Owner)
{
for (int i = 0; i < 10; ++i)
cb[i] = (TCheckBox*) FindComponent("CheckBox" + IntToStr(i+1));
}

how to display large data in wxListCtrl with using concept of wxThread

I'm capable to fill the database table in wxListCtrl,
my problem is to handle high range of data, I want to do this with the help of thread concept , perhaps it will save to hang the frame because of high amount of data.
I'm new in thread concept so your single lines will be a book for me.
Update:
My question was- how to display large data in wxListCtrl with using concept of wxThread
so for this I used thread concept I add two more files thread.c and thread.cpp
my entry thread code is shown below
void *MyThread :: Entry()
{
int i=1,j,k=0;
while(i!=400)
{
long index=this->temp->data_list_control->InsertItem(i,wxT("amit"));
for(j=1; j<3; j++) {
this->temp->data_list_control->SetItem(index,j,wxT("pathak"));
}
k++;
if(k==30) {
this->Sleep(1000);
k=0;
}
i++;
}
}
It is sometimes working fine but when I try to increase the value of i, it shows an error like
-*showingdatainwxlistctrl: ../../src/XlibInt.c:595: _XPrivSyncFunction: Assertion `dpy->synchandler == _XPrivSyncFunction' failed.*
or sometime it gives error like
***[Xcb] xcb_io.c:378: _XAllocID: Assertion `ret != inval_id' failed***
Why it is happening to me?
You can define your own thread object in wxWidgets in the following way:
class MyThread : public wxThread
{
private:
wxListCtrl* m_pListCtrl;
public:
MyThread(wxListCtrl* pListCtrl, wxThreadKind kind = wxTHREAD_DETACHED) :
wxThread(kind), m_pListCtrl(pListCtrl) {
}
virtual ~MyThread() {
}
virtual void* Entry() {
// here you have to place your code that will be running in separate thread
// m_pListCtrl-> ...
}
};
And this is the way how you can start your thread (assume you have your pListCtrl pointer here):
MyThread * pMyThread = new MyThread (pListCtrl);
wxThreadError ThreadError = pMyThread->Create();
if (wxTHREAD_NO_ERROR!=ThreadError) {
wxLogError(L"Can not create thread, wxThreadError '%d'", (int)ThreadError);
delete pMyThread;
return false;
}
ThreadError = pMyThread->Run();
if (wxTHREAD_NO_ERROR!=ThreadError) {
wxLogError(L"Can not run thread, wxThreadError '%d'", (int)ThreadError);
delete pMyThread;
return false;
}
// here, everything is ok.
Anyway, this is not the best solution for your problem. As far as I've understood, you need to display large amount of data in your wxListCtrl. To do this, you can use virtual ctrl (created with flag wxLC_VIRTUAL) and provide data source:
class MyListCtrl : public wxListCtrl
{
public:
MyListCtrl( ...) { ... }
virtual ~MyListCtrl();
protected:
virtual int OnGetItemImage(long item) const {
// You need this only if you want to provide specific image for your item.
// If you do not need it, just do not overload this method.
}
virtual wxString OnGetItemText(long item, long column) const {
// This is where you have to provide data for [item, column].
// Suppose, you have matrix A[n,m] which represents actually the data
// you want to display. The elements of this matrix can be of any type
// (strings, doubles, integers etc).
// You should return here wxString object that contains
// representation of the matrix's element A[item, column].
return ToWxString(A[item, column]);
// where ToWxString is your method that converts data to string
// So, you do not need to load all the data from A to wxListCtrl.
// Instead of it, wxListCtrl will determine which rows of the matrix should be
// displayed based on sizes and scroll position of wxListCtrl, and will
// call this method to obtain corresponding strings.
}
};
To create, you may use:
m_pListCtrl = new MyListCtrl( ..., ..., wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_VIRTUAL | wxSUNKEN_BORDER | wxLC_VRULES | wxLC_HRULES);
Best regards!
When you are performing high range of data you are bound to use WXThread in your program
Firstly was trying to fill wxListCtrl from wxEntry point, it was wrong u can not hit any main thread control from entry point, it does not give error, but it is a wrong concept
Here u need to pass the data to handler, handler will use it to fill wxListCtrl
code look like this->
void *MyThread :: Entry()
{
int a;
Handler handler_obj;
char *database_name=DATABASE_NAME;
connection =handler_obj.handler(101,database_name);
if(connection==NULL)
{
wxMessageBox(wxT("CAN NOT CONNECT TO DATABASE"), wxT("Message"), wxOK | wxICON_INFORMATION, NULL, -1, -1);
}
else
{
List_Ctrl_Data list_ctrl_data_object;
table_data=list_ctrl_data_object.fetch_table(connection);
MYSQL_ROW row;
while((row=mysql_fetch_row(table_data))!=NULL)
{
wxCommandEvent event( wxEVT_COMMAND_TEXT_UPDATED, 100000 );
void *row_data;
row_data=(void *)row;
event.SetClientData(row_data);
temp->GetEventHandler()->AddPendingEvent( event );
this->Sleep(1000);
}
}
}
to handle the row data we will use
void Id_Search_Report::onNumberUpdate(wxCommandEvent& evt)
{
int j;
void* hold_row;
hold_row=(void *)evt.GetClientData();
MYSQL_ROW row;
row=(MYSQL_ROW)hold_row;
const char* chars1 = row[0];
wxString mystring1(chars1, wxConvUTF8);
long index=data_list_control->InsertItem(this->counter,mystring1);
this->counter++;
for(j=1;j<12;j++)
{
const char* chars2=row[j];
wxString mystring2(chars2,wxConvUTF8);
data_list_control->SetItem(index,j,mystring2);
}
}
thread is returning a row , this method will handle the row and fill ListCtrl , it is a proper way to fill wxListCtrl.

how to write a function Click() for dynamic created button?

Trying to write a simple VCL program for educating purposes (dynamicly created forms, controls etc). Have such a sample code:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TForm* formQuiz = new TForm(this);
formQuiz->BorderIcons = TBorderIcons() << biSystemMenu >> biMinimize >> biMaximize;
formQuiz->Position = TPosition::poDesktopCenter;
formQuiz->Width = 250;
formQuiz->Height = 250;
formQuiz->Visible = true;
TButton* btnDecToBin = new TButton(formQuiz);
btnDecToBin->Parent = formQuiz;
btnDecToBin->Left = 88;
btnDecToBin->Top = 28;
btnDecToBin->Caption = "Dec to Bin";
btnDecToBin->Visible = true;
}
I wonder how can i write a function for dynamic created button, so it would be called when the button is clicked. In this example i need a 'btnDecToBin->Click();' func but i don't know where should i place it.
Inside 'void __fastcall TForm1::Button1Click(TObject *Sender){}' ?
I will appreciate any input, some keywords for google too.
You could do two things, you could either create an action and associate it with the button, or you could make a function like so:
void __fastcall TForm1::DynButtonClick(TObject *Sender)
{
// Find out which button was pressed:
TButton *btn = dynamic_cast<TButton *>(Sender);
if (btn)
{
// Do action here with button (btn).
}
}
You bind it to the button instance by setting the OnClick property btnDecToBin->OnClick = DynButtonClick please note that the function is inside the form Form1. This will work due to the nature of closures (compiler specific addition). The problem comes if you delete Form1 before formQuiz without removing the reference to the click event. In many ways it might be a more clean solution to use an Action in this case.
Edit: On other way to do this, if you have a standard layout for your quizforms, you could make a custom TQuizForm class inheriting from TForm. In this way you wouldn't have to bind the event each time you create the form.
all buttons have the normal "events" you just need to reference them to the method you will deal with the event.
example:
...
btnDecToBin->OnClick = &Test;
-- and add a additional method to .cpp
void __fastcall TForm1::Test(TObject *Sender)
{
TButton *btn = dynamic_cast<TButton *>(Sender);
if (btn->name == "your_button_name"){
// Do action here with button (btn).
}
}
and on .h
void __fastcall TForm1::Test(TObject *Sender);
reference the button either by the tag or name. I usually use a array of buttons that I create dynamically. ALWAYS sanity check your "sender" by casting it. There are other ways to hack info from the object but they are a path to heartache... LOL.