How to create and use dynamic event handlers in wxWidgets for an array of buttons using the Connect() function?
Description
Ok, so I have an array of buttons. Well, actually it is a vector, and could be any size. So wxWidgets usually recommends static event handlers and using dynamic event handlers was really confusing.
So I have the wxWidgets book, I researched the website and found the Connect()
function.
So I have a class called wxDieFrm (because I was creating dice). This wxDieFrm object has the following code snippet to create dynamic events for each button. So I incorrectly figured I would use the wxButton.Connect()
method. Let me show what I did wrong and then how easily it was fixed.
1 2 3 4 5 6 7 8 | for ( int i = 0; i < mSetOfDice->getNumberOfDice(); i++) { // Some code here... wxButton *rollButton = new wxButton( this , *buttonID, wxT( "Roll" ), wxPoint(5, 15), wxSize(75, 25), 0, wxDefaultValidator, wxT( "buttonRoll" )); //The following line is incorrect and the cause of the problem rollButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxDieFrm::buttonRollClick) ); WxBoxSizer->Add(rollButton,0,wxALIGN_CENTER | wxALL,5); } |
Notice I commented just before the problem line, so you would know the cause.
Then the event functions is this:
01 02 03 04 05 06 07 08 09 10 11 | /* * buttonRollClick */ void wxDieFrm::buttonRollClick(wxCommandEvent& event) { wxButton *button = (wxButton*)event.GetEventObject(); int id = button->GetId() - 4001; int rollValue = mSetOfDice->getDie(id).roll(); wxBitmap *bmp = mBitmapVector->at(rollValue - 1); mDice->at(id)->SetBitmap(*bmp); } |
Problem
This didn’t work. I got all kinds of access violation errors, which was strange to me, because being in the wxDieFrm::buttonRollClick()
function, the entire wxDieFrm should have been accessible. But nothing I did could and no amount of debugging helped me figure out this. It took reading a bunch of different posts before I finally found the answer.
Cause
There was really only one problem. I was having the button call its Connect()
method. This was a problem because when the code went to the wxDieFrm::buttonRollClick()
in that it only allowed me access to my button object.
Resolution
The fix was simple, don’t call Connect() from the button, just call it using the wxDieFrm object.
1 2 3 4 5 6 7 8 9 | for ( int i = 0; i < mSetOfDice->getNumberOfDice(); i++) { // Some code here... wxButton *rollButton = new wxButton( this , *buttonID, wxT( "Roll" ), wxPoint(5, 15), wxSize(75, 25), 0, wxDefaultValidator, wxT( "buttonRoll" )); WxBoxSizer->Add(rollButton,0,wxALIGN_CENTER | wxALL,5); } //The following line is THE CORRECT VERSION and the RESOLUTION to the problem Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxDieFrm::buttonRollClick) ); |
Reference Material
The following are the resources and different articles I had to pore over to finally reach this understanding:
Chapter 2 of the WxWidgets book, the event handler section.
Chapter 7 of the WxWidgets book.
http://wiki.wxwidgets.org/Events#Using_Connect.28.29
http://wiki.wxwidgets.org/Example_Of_Using_Connect_For_Events
http://wxwidgets.blogspot.com/2007/01/in-praise-of-connect.html
http://wiki.wxwidgets.org/Using_Connect_To_Add_Events_To_An_Existing_Class
The funniest part is the last one has a big post that says THE REST OF THIS PAGE IS WRONG and so I passed over it a half dozen times, before finally reading it. It was actually the one that gave me the answer under the diagnosing the problem section, it reads:
So my experiment reveals a characteristic of wxEvtHandler::Connect that is not explicitly documented (though it may be obvious to those who actually know C++): the wxObjectEventFunction passed to Connect() will be called with
this
set to whatever called Connect().
So by calling wxButton.Connect()
instead of wxDieFrm.Connect()
(or this.Connect()
or just Connect()
) the value of this
(the code word this not the preposition) was the wxButton and not wxDieFrm. That is why i was getting access violations.
So as soon as I switched to wxDieFrm.Connect()
the value of this
because my wxDieFrm and my access violations went away and everything works. For a minute I considered dropping wxWidgets altogether, but now that I understand this features, I like wxWidgets much more than ever.
Your real problem was that you used Connect() with the wrong eventSink parameter. So when the handler was called it had an invalid 'this'.
A better cure is:
rollButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxDieFrm::buttonRollClick), NULL, this );
man.. you are my hero.
I've been dealing with this problem since 2am, and now it is 5am.
I just wish that I had come across your article sooner!