Dynamic buttons

Hello,
I’m trying to create a set of buttons based on the files/folders in a folder. So when I click a folder name, it will open the folder and display a new set of buttons based on the selected folder.
At the moment i’m doing this to do just that

  
  
for (int i=0; i < (int) dir.size(); i++)  
	{  
		string file = dir.getName(i);  
		buttons[i].setup(word);  
		gui.add(buttons[i].setup(file));  
		buttons[i].addListener(this,&testApp::ButtonPressed);  
	}  
  

Although since its dynamic I can’t figure out a way of checking which button is pressed in the ButtonPressed function.
Is there a way to check which button was pressed, or pass the clicked button as a parameter to ButtonPressed?

I forgot to mention i’m using ofxGui

if buttons is a vector of ofxButton you can create your listener like:

  
  
void testApp::buttonPressed(const void * sender){  
    ofxButton * button = (ofxButton*)sender;  
    button->getName();  
}  
  

and then from the name know which button has been pressed

if it’s not an ofxButton but an ofxToggle for example the listener would be:

  
  
void testApp::togglePressed(const void * sender, bool & pressed){  
...  
}  
  

1 Like

I tried with your solution Arturo but when I print the button name, throws an error Thread 1: EXEC_BAD_ACCESS …
Any idea?

Hi, did someone find a solution to this?

Hi,
For ofxButton I don’t know, but for ofxToggle I just found this:

void ofApp::togglePressed( const void * sender, bool & pressed )
{
    ofParameter<bool> * v = (ofParameter<bool> *)sender;
    const string & name = v->getName();
    ...
}

Because the sender is not the ofxToggle, but the ofParameter which carry the toggle value. Luckily the ofParameter has the same name as the toggle.

Arturo’s solution worked fine for me

Hi! Trying to do this I only get crashes. The debugger shows getName() being evaluated in ofxBaseGui.cpp, and then closes with no info.

//.h
vector<ofxButton> buttons;
ofxPanel fileSelector;

//.cpp
void ofApp::setup() {
    buttons.push_back(ofxButton());
    fileSelector.add(buttons.back().setup("here"));
    buttons.back().addListener(this, &ofApp::onPressLoadVideo);
}
void ofApp::onPressLoadVideo(const void * sender) {
    ofxButton * button = (ofxButton *)sender;
    string name = button->getName();
    cout << name << endl;
}

I think the sender pointer is not valid. If I remove getName() it does not crash. If I replace it with getWidth() I get 4.79128e+10.

I tried tons of variations with push_back(), move(), not using move()… nothing helped.

How do you debug such an issue where there is no errors on the console?

I think if you are using nightly then the sender is not the button itself anymore but an ofParameter<void>

I would recomend not to use gui controls directly. The easiest and safest way to create guis is to use ofParameters and listen to events on those instead of in the gui control directly. ofParameters won’t have this problem since they allocate in the heap so even if you put them on a vector or any other container the listener address will always be the same.

their syntax is also much more convenient, you can use ofParameters like:

//.h
ofParameter<void> buttonParam{"here"};
ofParameterGroup parameters{
    "settings",
    buttonParam,
};
ofxPanel gui{parameters};

//.cpp
void ofApp::setup(){
    buttonParams.addListener(this, &ofApp::onPressLoadVideo);
}

or if you are using nightly or OF from github even:

//.h
ofParameter<void> buttonParam{"here"};
ofParameterGroup parameters{
    "settings",
    buttonParam,
};
ofxPanel gui{parameters};
ofEventListener buttonListener;

//.cpp
void ofApp::setup(){
   buttonListener = buttonParams.newListener([this](const void * sender){
        ofParameter<void> * p = (ofParameter<void> *)sender;
        string name = p->getName();
        cout << name << endl;
   });
}

I’ll switch to ofParameter. I’m very thankful for your help :vulcan: :medal:

This is how I got to write my initial code:

  • I went to http://openframeworks.cc/documentation/ and searched for GUI.
  • In ofxGui I found ofxButton, which sounded like what I was looking for.
  • I went to the examples/gui folder to see what I find there, and the first example, guiExample, does not use ofParameter and does use gui controls directly.

Should the guiExample be updated to use ofParameter?
Should ofParameter be mentioned in http://openframeworks.cc/documentation/ofxGui/ ?

yes to both, there’s a guiFromParameterExample that should really be the default. When we started ofxGui it wasn’t based on ofParameter but now that it is and that it’s well tested i think we should just remove the first example or at least give it a name that doesn’t look like what you should be looking at by default. And yes the module documentation for ofxGui should mention ofParameter or at least point to the parameter example

I’ve been figuring out how to use ofEventListener with lambdas, and found this post really helpful, along with https://forum.openframeworks.cc/t/ofparameter-listeners/23843/5.

So to add an alternative to @arturo 's code above, the lamba can also call a function in ofApp:

void ofApp::setup(){
   // most of the setup code as above, then
   buttonListener = buttonParams.newListener([this](const void * sender){aFunction(sender)});
}

// and an ofApp function for the lambda to call:
void ofApp::aFunction(const void* v)
{
    ofParameter<void>* p = (ofParameter<void>*) v;
    std::string name = p->getName();
    std::cout << name << std::endl;
}

This is really helpful if different listeners need to call the same function, or if the function is a bit long to include with the lambda.

And then finally I figured out (I think) how to just call a function without capturing any args:

buttonListener = buttonParams.newListener([this](){anotherFunction();});

void ofApp::anotherFunction()
{
    // do something
}

So thanks to all who posted; its really helped me to figure out how to use a lambda with ofEventListener.

you don’t need a lambda to call a member function, you can just do:

buttonListener = buttonParams.newListener(this, ofApp::aFunction);

which will work for both the case with the sender parameter and without

2 Likes

Hey I tried this out with a really basic example, which I won’t bother to post (unless it becomes relevant). But just to confirm, calling ofApp::aFunction in an ofEventListener needs to be a reference to the function, right? Like this:

buttonListener = buttonParams.newListener(this, &ofApp::aFunction);

Because without a reference, I had compile errors. Also, the function needs to have args (by reference) of the same type as the ofParameter. So, for a void-type ofParameter, aFunction() must be defined (“void” arguments). Or for an int-type ofParameter, aFunction(int& i) must be defined.

I also discovered that a lambda can pass both the “name” and the value of a non-void ofParameter to a function. So, for example:

paramListener = param.newListener([this](const void* p, int& i) {aFunction(p, i);})

This compiles when param is an int-type ofParameter, and the listener will call aFunction(const void* p, int& i). I’m not sure if there is a way to do this without the lambda or not.

And I’ve finally figured out how to pass an ofParamter in a lambda (code coming soon)

And finally I figured out how to set up listeners in one class (ofApp) that are listening to ofParameters in a different class (again, code coming soon).

At any rate, maybe someday I’ll try and add to the documentation with a quick tutorial on ofEventListener and lambdas. They’re such powerful tools in oF when working together!

Hi,
what you describe is the behavior of ofEvents in general.
There is already documentation about this.
arturo wrote the following


and I wrote the following which is a bit older and a bit outdated, but a lot longer and with lots of code examples.

cheers

Did you ever post the sample code?

No. As for some reason this chapter still remains as an un-merged PR, which now needs to be updated.
There are several examples in the OF/examples. As well there are a lot of use cases, so if you point me what it is that you want to achieve I can help.