Multiple ofParameter boolean in GUI with only one activated at any time


#1

I want multiple (let’s say 4) ofParameter boolean in my GUI (I’m using ofxGui) which can be checked in order to draw different circles (e.g. first bool is for drawing a red filled circle, second bool is for drawing a blue filled circle, etc.).
in ofApp.h

ofParameter<bool> checkRedCircle;
ofParameter<bool> checkBlueCircle; ...

There is a default checkbox when launching the application (e.g. blue circle). When clicking on a new checkbox (e.g. red circle), I want the previous checkbox to go to false. Also, when clicking on a checkbox that is already to true (which means it will go to false), the default checkbox should come back as the only checkbox set to true. What would be the best approach the create this behavior with checkboxes?


#2

Hi,
I usually use ofxButton, instead of ofParameter.
I tried to simulate what your’e doing with ofParameter, but didn’t get it right to add a listener to the ofParameter-bool.
So I suggest to use an ofxButton, and add a listener function to each of them (not the best solution… But if there aren’t too many buttons, it might be ok) so you know which button is clicked.
I usually use ofxDatGui, which sends a pointer of the clicked button to the listener, so you can use one listener-function :), anyway:
In each listener function you should check:

  • If all buttons are false: set default button to true;
  • Else: set all to false, except the one clicked (you know which, because every button calls its own function).
    Well. Some quick thoughts to get it working. Good luck!

#3

heyho, sorry i did not test this, so i probably missed something. But i think a lambda function could be very useful here. Hope that works and helps. thomas

_firstBoolParam.newListener([this](bool & value){if(value){ofLogNotice() << "TODO: unset the other parameters";};});
_secondBoolParam.newListener([this](bool & value){if(value){ofLogNotice() << "TODO: unset the other parameters";};});

#4

Is that similar to this, which is what I have coded right now:
in ofApp.cpp, void setup()

        groupCircle.setup();
	checkRedCircle.addListener(this, &ofApp::redCircleChecked);
	checkBlueCircle.addListener(this, &ofApp::blueCircleChecked);...

	groupCircle.add(checkRedCircle.set("Red circle",false));
	groupCircle.add(checkBlueCircle.set("Blue circle",true));...

and the listener:

void ofApp::redCircleChecked(bool & checkRedCircle) {
	if (checkRedCircle) {
		checkBlueCircle = false;...
                //do other stuff
	}
}

Although my current implementation does work when switching between checkboxes, it does not work if I uncheck the currently checked checkbox.


#5

Hi!
I think this does it?

void ofApp::setup(){
    gui.setup("test");
    gui.add(blue.set("Blue", true));
    gui.add(red.set("Red", false));
    gui.add(green.set("Green", false));
    
    red.addListener(this, &ofApp::redCircleChecked);
    blue.addListener(this, &ofApp::blueCircleChecked);
    green.addListener(this, &ofApp::greenCircleChecked);
}
//--------------------------------------------------------------
void ofApp::redCircleChecked(bool & b){
    if(red.get() == false && blue.get() == false && green == false){
        blue.set(true);
    } else{
        if(b){
            blue.set(false);
            green.set(false);
        }
    }
}
//--------------------------------------------------------------
void ofApp::blueCircleChecked(bool & b){
    if(red.get() == false && blue.get() == false && green == false){
        blue.set(true);
    } else{
        if(b){
            red.set(false);
            green.set(false);
        }
    }
}
//--------------------------------------------------------------
void ofApp::greenCircleChecked(bool & b){
    if(red.get() == false && blue.get() == false && green == false){
        blue.set(true);
    } else{
        if(b){
            red.set(false);
            blue.set(false);
        }
    }
}

Didn’t get the .newListener function working… (Function is not getting called)


#6

Thank you, this does work. I just have do declare a lot of circleColor.set(false). It would be nice if I could specify a group of checkboxes to go to false instead of setting them individually.


#7

Yes, putting all the ofParameters in a std::vector would be nice.
Haven’t tested this, but think it should look something like this.
In this example I’m using the newListener-function @thomasgeissl mentioned, which I didn’t get working last time, but he already mentioned that it was untested.

for(int i=0; i<params.size(); i++){ // Add listener functions to all ofParameters
    params[i].newListener([this](bool &amp; value){
        bool bAllFalse = true;
        for(int j=0; j<params.size(); j++){ // Check if all ofParameters are set to false
            if(params[j] == true)
                bAllFalse = false;
        }
        if(bAllFalse){ // If all set to false: set the default button
            params[0].set(true);
        }
        if(value){
            // Set all to false, except self
            for(int j=0; j<params.size(); j++){
                if(params[j] != params[i])
                    params[j].set(false);
            }
        };
    });
}

So not really complete, but hopefully you can use it :wink:


#8

I love using .newListener() with lambdas. I delcare an ofEventListener object for each ofParameter that I’m listening to. I’m not sure, but I think this is might be the only way that .newListener() works (with an ofEventListener object). Since the listener is an object, it gets destroyed when it goes out of scope. @arturo wrote a nice blog post about this, and @roymacdonald has included it in a draft chapter of ofBook on ofEvents.

ofParameter<bool> myBoolParam;
ofEventListener myBoolParamListener;
myBoolParamListener = myBoolParam.newListener([this](const ofParamter<bool>&) {this->myFunction();});

The ofParamter and ofEventListener can be put together in a struct or class too if needed, and instances of struct/class can be added to a vector or other container.


#9

The easiest way to do this, i think, is to use the version of the listener that receives a poitner to the origin of the event as in:

// .h
    ofParameter<bool> checkRedCircle{"red", false};
    ofParameter<bool> checkBlueCircle{"blue", true};
    ofParameterGroup parameters{
        "circles",
        checkRedCircle,
        checkBlueCircle,
    };
    ofxPanel gui{parameters};
    ofEventListeners listeners;
    bool attendingEvent = false;

// .cpp setup
    listeners.push(checkRedCircle.newListener(this, &ofApp::checkPressed));
    listeners.push(checkBlueCircle.newListener(this, &ofApp::checkPressed));


//.cpp checkPressed
void ofApp::checkPressed(const void * sender, bool & value){
    if(attendingEvent){
        return;
    }

    attendingEvent = true;
    auto param = (ofParameter<bool>*)sender;
    if(value == false){
        // Don't let this parameter be deactivated so there's always
        // one active
        param->set(true);
    }else{
        if(param->isReferenceTo(checkRedCircle)){
            checkBlueCircle.set(false);
        }
        if(param->isReferenceTo(checkBlueCircle)){
            checkRedCircle.set(false);
        }
    }
    attendingEvent = false;
}

You can of course generalize that to multiple parameters in a vector by checking in a for loop that the current parameter in the for loop is not the same as the sender.


#10

Hi,
@Jildert the code that you shared is wrong as you need to store the result of the newListener function in an ofEventListener object.
there is also the ofEventListeners class (notice the s at the end of the name) which is a collection of ofEventListener objects. In arturo’s post that just before this one he makes use of it.

I personally use both lambdas and pointers to functions. It really depends on the use case.