Creating wxBitmapButtons dynamically and writing a function for each

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Wanderer82
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 675
Joined: Tue Jul 26, 2016 2:00 pm

Creating wxBitmapButtons dynamically and writing a function for each

Post by Wanderer82 »

Ok, what I try to do is create a wxDialog with elements that are dynamically built / placed into a wxFlexGridSizer. I have different products and for each product I want to have a line with some text, a picture, options and in the end a "cart" button to save the users choice. I managed it all but came across a problem with the wxBitmapButtons. I have to say that I use arrays e.g. for staticText etc. That's what I did for th wxBitmapButtons as well. But at first my code wouldn't show the button. I only got it to work when I have one part using the array with a variable that counts up with each product but the ID fixed to zero.

Code: Select all

BitmapButton[Durchlauf_fortlaufend] = new wxBitmapButton(Panel1, ID_BITMAPBUTTON[0], wxBitmap(wxImage(_T("C:\\Users\\Thomas\\Documents\\Programmprojekte\\Wall-Mat\\warenkorb.png"))), wxDefaultPosition, wxDefaultSize, wxNO_BORDER, wxDefaultValidator, _T("ID_BITMAPBUTTON[Durchlauf_fortlaufend]"));
If I put another number except zero, no button would show up. So far so good, I got my buttons but I also need the click events for each button dynamically built during the execution of my program. Here's where I got stuck because doing something like...

Code: Select all

void Geometrie::OnBitmapButton[Durchlauf_fortlaufend]Click(wxCommandEvent& event)
... won't work. Now I'm a bit lost. I have to have my ID set to zero for each button but I think the solution to my problem lies exactly in these IDs.

Thanks for any help.
Thomas
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by doublemax »

Code: Select all

void Geometrie::OnBitmapButton[Durchlauf_fortlaufend]Click(wxCommandEvent& event)
Something like this doesn't work. This is a method that is created at compile time and you can't mix it with runtime elements like a variable.

You should have one event handler for all bitmap buttons. In the event handler you must find out which one was clicked. There are a few methods to do that.

1a) event.GetId() returns the ID of the object that generated the event. You can search the ID_BITMAPBUTTON[] array until you find it.

1b) Assuming the "products" in your application have unique, sequential IDs and are not too many, there is an alternative to 1a). Use a fixed starting value for the bitmap button IDs, e.g. 10000. Then for each bitmap button, you use an ID of (10000 + product-id) to create it.
In the event handler you calculate (event.GetId()-10000) and you immediately have the corresponding product-id

2) event.GetEventObject() returns a (wxWindow *) pointer to the object that generated the event ( = the button that was clicked ). You could search the "BitmapButton[]" array until you find the pointer in there. (i wouldn't recommend this solution in this particular case).


One more thing: Please start replacing the absolute paths with paths calculated relative to the executable. There will be a time you have to do this and the later you start, the more work it'll be. You can use wxStandardPaths for this:
http://docs.wxwidgets.org/trunk/classwx ... paths.html


One more thing #2: I know you said you're a beginner and hobby programmer, but the program you're building is a textbook example for the use of a database. It may require a few extra days to learn about SQL databases, but i think you'd make up that time in the long run. Maybe you should consider that. sqlite is an embeddable database and with wxSQLite3 it's easy to use from within a wxWidgets app.
Use the source, Luke!
Wanderer82
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 675
Joined: Tue Jul 26, 2016 2:00 pm

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by Wanderer82 »

I'm always happy about these little hints with the relative paths. I'll have to look into this.

So to the other problem:

First, I'm still not that far into C++ to know what you mean by or how I can write an event handler. I was always looking at the code that was produced using the GUI of Code::Blocks and then change it to my needs. So I don't know how such an event handler would look like...

And then, you write that I could use a different ID for each button. But that's my problem... if I use another ID than "0" my buttons won't show up. So talking about this code again:

Code: Select all

BitmapButton[Durchlauf_fortlaufend] = new wxBitmapButton(Panel1, ID_BITMAPBUTTON[0], wxBitmap(wxImage(_T("C:\\Users\\Thomas\\Documents\\Programmprojekte\\Wall-Mat\\warenkorb.png"))), wxDefaultPosition, wxDefaultSize, wxNO_BORDER, wxDefaultValidator, _T("ID_BITMAPBUTTON[Durchlauf_fortlaufend]"));
You can see it says "ID_BITMAPBUTTON[0]". This is inside a loop that creates all the elements for each product. So I can only change "BitmapButton[Durchlauf_fortlaufend] but not the ID. If I put something like "ID_BITMAPBUTTON[Durchlauf_fortlaufend]" or "ID_BITMAPBUTTON[1]" no button would show up.

By the way... if I have different BitmapButtons... I mean it's an array... why can't I just use the number inside the array instead of the ID?

Oh, I've just seen that you suggested learning how to use wxSQLite3. I thought there would be another solution to my problem but now, as I've already written a big part of my program I'd rather stick with it. It's also a good exercise to get a bit more into "basic" C++.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by doublemax »

First, I'm still not that far into C++ to know what you mean by or how I can write an event handler. I was always looking at the code that was produced using the GUI of Code::Blocks and then change it to my needs. So I don't know how such an event handler would look like...
This is already an event handler:

Code: Select all

void Geometrie::OnBitmapButtonClick(wxCommandEvent& event)
{
   wxLogMessage( "you clicked button with id=", event.GetId() );
}
You can see it says "ID_BITMAPBUTTON[0]". This is inside a loop that creates all the elements for each product. So I can only change "BitmapButton[Durchlauf_fortlaufend] but not the ID. If I put something like "ID_BITMAPBUTTON[Durchlauf_fortlaufend]" or "ID_BITMAPBUTTON[1]" no button would show up.
ID_BITMAPBUTTON[] is an array of integers. How did you create it and are the values initialized?

For a test, just use "10000+Durchlauf_fortlaufend" for the ID and try to follow my 1b) proposal.
By the way... if I have different BitmapButtons... I mean it's an array... why can't I just use the number inside the array instead of the ID?
That would be solution 2) from my post above.

BTW: Going to bed now, so no more replies tonite :)
Use the source, Luke!
Wanderer82
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 675
Joined: Tue Jul 26, 2016 2:00 pm

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by Wanderer82 »

Me too, gonna look into this tomorrow. Thanks and good night.
Wanderer82
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 675
Joined: Tue Jul 26, 2016 2:00 pm

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by Wanderer82 »

Hm, your code (I mean putting "10+Durchlauf_fortlaufend") doesn't work, either. I also don't get the point in adding 10, because "Durchlauf_fortlaufend" is incremented in the loop. So it's the same result as when I put any number with the BITMAPBUTTONID. The button will only show up if I put a zero. I don't see where my mistake is because I do this the same way with all the other elements in this dialog.

I don't initialize this array. And the array is defined in the Geometrie.h file like with all the other elements:

Code: Select all

public:

		Geometrie(wxWindow* parent,wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize);
		virtual ~Geometrie();

		//(*Declarations(Geometrie)
		wxPanel* Panel1;
		wxStaticText* StaticText[50];
		wxStaticBitmap* StaticBitmap[50];
		wxSpinCtrl* SpinCtrl[50];
		wxBitmapButton* BitmapButton[50];
		//*)

	protected:

		//(*Identifiers(Geometrie)
		static const long ID_PANEL1;
		static const long ID_STATICTEXT[50];
		static const long ID_STATICBITMAP[50];
		static const long ID_SPINCTRL[50];
		static const long ID_BITMAPBUTTON[50];
		//*)
And in my mainApp file I use it like this:

Code: Select all

//(*IdInit(Geometrie)
const long Geometrie::ID_PANEL1 = wxNewId();
const long Geometrie::ID_STATICTEXT[] = {wxNewId()};
const long Geometrie::ID_STATICBITMAP[] = {wxNewId()};
const long Geometrie::ID_SPINCTRL[] = {wxNewId()};
const long Geometrie::ID_BITMAPBUTTON[] = {wxNewId()};
//*)
And then inside "Geometrie::Geometrie(wxWindow* parent,wxWindowID id,const wxPoint& pos,const wxSize& size)" as already posted:

Code: Select all

BitmapButton[Durchlauf_fortlaufend] = new wxBitmapButton(Panel1, ID_BITMAPBUTTON[Durchlauf_fortlaufend], wxBitmap(wxImage(_T("C:\\Users\\Thomas\\Documents\\Programmprojekte\\Wall-Mat\\warenkorb.png"))), wxDefaultPosition, wxDefaultSize, wxNO_BORDER, wxDefaultValidator, _T("ID_BITMAPBUTTON[Durchlauf_fortlaufend]"));
	FlexGridSizer2->Add(BitmapButton[Durchlauf_fortlaufend], 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
"Durchlauf_fortlaufend" is declared before the code above. So even if I didn't initialize the arrays, in the moment of "ID_BITMAPBUTTON[Durchlauf_fortlaufend] it will be filled with a value. But somehow I guess it's just a small mistake...
It seems to me that you can't use other IDs than 0. If I start the loop with "Durchlauf_fortlaufend" set to 0 instead of 1 the first button will show up but not the others.
Wanderer82
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 675
Joined: Tue Jul 26, 2016 2:00 pm

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by Wanderer82 »

Ok, after all it seems to work with this code:

Code: Select all

BitmapButton[Durchlauf_fortlaufend] = new wxBitmapButton(Panel1, Durchlauf_fortlaufend, wxBitmap(wxImage(_T("C:\\Users\\Thomas\\Documents\\Programmprojekte\\Wall-Mat\\warenkorb.png"))), wxDefaultPosition, wxDefaultSize, wxNO_BORDER, wxDefaultValidator, _T("ID_BITMAPBUTTON[Durchlauf_fortlaufend]"));
So I replaced "ID_BITMAPBUTTON[Durchlauf_fortlaufend]" with "Durchlauf_fortlaufend" only. But I really don't understand. With all other elements this works and I don't get the difference between these two usages of code. Why is the "ID" part even needed? And if I go this way using "Durchlauf_fortlaufend" only, will there be a problem with your suggestion for writing the handler?

And actually I don't understand the different occurences of "ID_BITMAPBUTTON" at all. First I declare a new button, that's the variable's name with the array value. But then I see "ID_BITMAPBUTTON" twice... once in the paranthesis of "new wxBitmpatButton" (at least in the case I didn't replace it with only "Durchlauf_fortlaufend") and once again at the end in "_T(ID_BITMAPBUTTON)". How are these two used / what do they do?
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by doublemax »

Code: Select all

const long Geometrie::ID_STATICTEXT[] = {wxNewId()};
This initializes only the first element of the array, the others will probably filled with zeroes, or - if you're really unlucky - with random memory.

The idea with the 10000 in "10000+Durchlauf_fortlaufend" is to make sure there are no collisions with other ids, which are usually much lower.

Code: Select all

_T("ID_BITMAPBUTTON[Durchlauf_fortlaufend]")
This is just a string. I don't know what you intend to do here, but whatever it is, it won't work :)
And if I go this way using "Durchlauf_fortlaufend" only, will there be a problem with your suggestion for writing the handler?
Then the IDs will start from zero, but user ids should start at wxID_HIGHEST+1 to avoid collisions with IDs used by wxWIdgets internally.

There is anoyther option:

Code: Select all

wxBitmapButton *button = new wxBitmapButton(Panel1, wxID_ANY, ... );
BitmapButton[Durchlauf_fortlaufend] = button;
ID_BITMAPBUTTON[Durchlauf_fortlaufend] = button->GetId();
wxID_ANY will tell wxWidgets to autogenerate an ID and then with GetId() to retrieve the "real" id. However, with this it will still be hard to make the connection between the clicked button-id and the product-id.
Use the source, Luke!
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by PB »

doublemax wrote: There is anoyther option:

Code: Select all

wxBitmapButton *button = new wxBitmapButton(Panel1, wxID_ANY, ... );
BitmapButton[Durchlauf_fortlaufend] = button;
ID_BITMAPBUTTON[Durchlauf_fortlaufend] = button->GetId();
wxID_ANY will tell wxWidgets to autogenerate an ID and then with GetId() to retrieve the "real" id. However, with this it will still be hard to make the connection between the clicked button-id and the product-id.
I just skimmed the thread but to me it seems that the simplest and safest solution would be creating a std::map which would map the button ID returned by GetId() to the actual product ID?

EditSimple code to demonstrate the suggested solution

Code: Select all

#include <wx/wx.h>
#include <vector>
#include <map>

class MyDlg : public wxDialog
{
public:    
    MyDlg() : wxDialog(NULL, wxID_ANY, "Products", wxDefaultPosition, wxSize(200, 300))
    {                
        std::vector<int> productIDs;

        productIDs.push_back(1);
        productIDs.push_back(150);
        productIDs.push_back(286);
        productIDs.push_back(9753);
        
        wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);

        for ( size_t i = 0; i < productIDs.size(); i++ )
        {
            wxButton* button = new wxButton(this, wxID_ANY, wxString::Format("Product with ID %d", productIDs[i]));                                    
            
            m_buttonIDToProductIDMap[button->GetId()] = productIDs[i];
            
            button->Bind(wxEVT_BUTTON, &MyDlg::OnProductButtonClicked, this);  
            sizer->Add(button, 0, wxALL | wxEXPAND, 5);
        }
        
        SetSizerAndFit(sizer);
    }

private:
    std::map<wxWindowID, int> m_buttonIDToProductIDMap;
        
    void OnProductButtonClicked(wxCommandEvent& evt) 
    {         
        wxLogMessage("Clicked button with product ID %d", m_buttonIDToProductIDMap[evt.GetId()]);
    }
};

class MyApp : public wxApp
{
public:	        
    bool OnInit()
    {
        MyDlg dlg;
        
        dlg.ShowModal();
        return false;
    }   
}; wxIMPLEMENT_APP(MyApp);
But perhaps I am missing something obvious here?
Wanderer82
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 675
Joined: Tue Jul 26, 2016 2:00 pm

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by Wanderer82 »

Yes, maybe you're missing something :D The fact, that I'm a beginner and I don't really understand all the context. That's why I'm happy about solutions that remain within the easier boundaries of C++ :oops:

So doublemax' solution seemed doable for me but now I'm not sure whether this won't work like you, doublemax, thought it would.

And @doublemax: My question was, what it the difference between using "ID_BITMAPBUTTON" and just using a number in this code:

Code: Select all

BitmapButton[10000 + Durchlauf_fortlaufend] = new wxBitmapButton(Panel1, [b]10000 + Durchlauf_fortlaufend,[/b] wxBitmap(wxImage(_T("C:\\Users\\Thomas\\Documents\\Programmprojekte\\Wall-Mat\\warenkorb.png"))), wxDefaultPosition, wxDefaultSize, wxNO_BORDER, wxDefaultValidator, _T("ID_BITMAPBUTTON[10000]"));
Because using just a number / int there, all buttons show up instead when using "ID_BITMAPBUTTON[10000 + Durchlauf_fortlaufend]".
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by PB »

Wanderer82 wrote:Yes, maybe you're missing something :D The fact, that I'm a beginner and I don't really understand all the context. That's why I'm happy about solutions that remain within the easier boundaries of C++ :oops:
I am barely at the lower-intermediate level at C++ myself, but I believe that using basic C++ containers is well within the "easier boundaries of C++". ;) But to each his own.

Anyway, mapping the button ID to product ID is not a difficult concept. Proof: Even I could come up with it. :D I believe it could be worth spending a minute or two looking into it...
Wanderer82
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 675
Joined: Tue Jul 26, 2016 2:00 pm

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by Wanderer82 »

Well I'm looking at it, but have to tell you that I don't know anything about pointers, I don't really know the concept behind these IDs and how they work and I have never used a vector or map. All I do is just looking at some code that was generated by Code::Blocks when I use the GUI to make these buttons and other elements and then I try to figure out how everything could go together. Sometimes it's quite easy and my basic C++ is enough to understand and sometimes it's just not. Maybe to say that I'm a beginner in C++ already goes too far ;-)
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4204
Joined: Sun Jan 03, 2010 5:45 pm

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by PB »

Wanderer82 wrote:Well I'm looking at it, but have to tell you that I don't know anything about pointers, I don't really know the concept behind these IDs and how they work and I have never used a vector or map.
JFYI, therre no pointers involved in the ID mapping. Nevertheless, if you are using pointers (and you must be when dealing with GUI controls) and know nothing about them, I strongly suggest to learn about them, assuming you plan on using C++. Otherwise prepare for a lot of frustration and wasted time. ;)
Wanderer82
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 675
Joined: Tue Jul 26, 2016 2:00 pm

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by Wanderer82 »

Well maybe I need to understand some more basics from my initial question to doublemax...

For example: why is it a problem to only initialize one value of the array? Because I don't use the array before I assign values.

And I still don't understand (I think my question was misunderstood) what's the difference in this code ...

Code: Select all

BitmapButton[xy] = new wxBitmapButton(Panel1, IT_BITMAPBUTTON, wxBitmap(wxImage(_T("C:\\Users\\Thomas\\Documents\\Programmprojekte\\Wall-Mat\\warenkorb.png"))), wxDefaultPosition, wxDefaultSize, wxNO_BORDER, wxDefaultValidator, _T("ID_BITMAPBUTTON"));
between "ID_BITMAPBUTTONxy" and just using a number? Because if I put any number there (without "ID_BITMAPBUTTON"), my buttons will show up, but if I use "ID_BITMAPBUTTON[xy]" only my first button will show up (and only if xy = 0). Why is the Bitmapbutton so different here than for example Statictext or Staticbitmap?

UPDATE:

So the funny thing is: If I use this code:

Code: Select all

BitmapButton[10000 + Durchlauf_fortlaufend] = new wxBitmapButton(Panel1, ID_BITMAPBUTTON[10000 + Durchlauf_fortlaufend], wxBitmap(wxImage(_T("C:\\Users\\Thomas\\Documents\\Programmprojekte\\Wall-Mat\\warenkorb.png"))), wxDefaultPosition, wxDefaultSize, wxNO_BORDER, wxDefaultValidator, _T("ID_BITMAPBUTTON[10000]"));
	FlexGridSizer2->Add(BitmapButton[10000 + Durchlauf_fortlaufend], 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
    Connect(ID_BITMAPBUTTON[10000 + Durchlauf_fortlaufend],wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&Geometrie::OnBitmapButtonClick);
and:

Code: Select all

void Geometrie::OnBitmapButtonClick(wxCommandEvent& event)
{
   wxLogMessage( "you clicked button with id=", (event.GetId()-10000));
}
I can't see the bitmaps, but I can click the places where they should appear and the handler jumps in and displays "you clicked button with id=" but won't show me an ID.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Creating wxBitmapButtons dynamically and writing a function for each

Post by doublemax »

For example: why is it a problem to only initialize one value of the array? Because I don't use the array before I assign values.
Sorry, i used the wrong array when i posted the code. The "critical" array is ID_BITMAPBUTTON[] because you pulled values out of the array and used them as IDs for the button creation. If this array is not initialized, the values are either zeroes or random.

I can't tell you exactly why the buttons don't show up, i can only assume it has to do with IDs not being unique. But i'd need some code to test this myself.
Use the source, Luke!
Post Reply