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.

	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:

/*
 * 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.

	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.

No Comments

  1. DavidGH says:

    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 );

  2. doggy says:

    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!

Leave a Reply

How to post code in comments?