Using ofxGui slider values outside of ofApp


#1

I have a class “Wave” and inside I’d like to access the slider value ‘waveSpeed’ but it gives me linking errors.

z += ofApp::waveSpeed * deltaTime;

That gives me invalid use of non-static

If I make it a static variable it throws:

Undefined symbols for architecture i386:
  "ofApp::waveSpeed", referenced from:
      ofApp::setup() in ofApp.o
      Wave::update() in Wave.o
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)

#2

the easiest way to do this is either pass the value through to the wave object from ofApp, or in the .cpp file of wave (you can’t do this in the .h file) you can do:

#include "ofApp.h"    // note you can't do this in wave.h b/c it would be a recursive include...

then later in the cpp you can do:

((ofApp*) ofGetAppPtr())->waveSpeed

ofGetAppPtr() gets a pointer to the app that OF is running (ie, ofApp) and (ofApp*) casts it as an ofApp (it’s a pointer to an ofBaseApp). I use ofGetAppPtr() to get access to the main level of the ofApp inside of other classes, like what you are trying to do here…


Get slider value from another different class
#3

hello,

I don’t know if this is the way the gui slider is supposed to be used. I mean it is ok to modify a class slider from ofApp, but not the other way around.

maybe you should declare a listener that updates a Wave::speed variable every time the slider is updated in ofApp


#4

Thank you both.

This seems like functionality that would be used often, no? To have a class’ behavior modified from sliders?

Gallo, with your method, in the update() function, shall I do something to the effect of…?

Wave::speed = waveSpeed;

The only thing about this that strikes me as funny is that I have many wave objects on at a time, so isn’t it a waste for them to each have a speed var if it’s meant to be the same for all waves?


#5

I think you are using the :: wrong here. usually you use :: for static variable, here if you had a wave object, ie:

wave myWave

you’d access a variable called speed inside it like:

myWave.speed = .....

you wouldn’t say wave::speed

there are sometimes times you would use ::, to access static variables like:

ofColor::red   

but for generally accessing variables inside of instances of objects, you use . or -> if it’s a pointer.


#6

every wave should have a speed, you can update it by listening the slider in ofApp (although the best approach here would be to declare speed in wave as ofParameter then populate the ofApp gui with that parameter)

ofApp::ofApp() {
    slider.addListener(this, &Wave::changeSpeed);   
}

ofApp::~ofApp() {
    slider.removeListener(this, &Wave::changeSpeed);
}

then in your class, you automatically update the value when slider is dragged

void Wave::changeSpeed(float &speed) {
    waveSpeed = speed;
}

so you can use it

z += waveSpeed * deltaTime;

note : not tested but this is where i would look

note 2 : slider is declared as ofParameter


#7

the easiest, and cleanest way to use this is to use ofParameter to represent the speed and then pass that to a panel which will create a slider automatically. take a look at guiFromParametersExample but it would be something like:

class Wave{
public:
void setup(){
    speed.set("wave speed",0,10,100); // value, min, max
}
...
ofParameter<float> speed;
}

in your ofApp:

//.h
ofxPanel gui;
Wave wave;

//setup
wave.setup();
gui.add(wave.speed)

if you are using 0.9.0 you can also initialize parameters in the .h file directly like:

class Wave{
public:
...
ofParameter<float> speed{"wave speed",0,10,100}
}

#8

i think @arturo has the best approach… but how would you manage say 100 of waves ? i mean can you manage all the 100 waves with one slider ?


#9

you can create the parameter as a static variable like:

//.h
class Wave{
public:
...
static ofParameter<float> speed;
}

//wave.cpp
static ofParameter<float> Wave::speed{"wave speed",0,10,100};

in ofApp:

//.h
ofxPanel gui;
vector<Wave> wave;

//setup
wave.setup();
gui.add(Wave::speed)

#10

so is not recommended to create the ofxGui panel inside the classes? Should be always on the ofApp class?

I am trying to create some instances from a class with the gui panel included but only the last (?) created panel is usable. In the other panels, sliders and panel itself can’t be moved…

What’s the difference between to create the parameters into a setup() function besides the constructor?


#11

It depends on how many instances you plan to populate. And also how do you want to organize XML setting files.
Here is two working examples I made and tested on macOS. I personaly design it like #2.

  1. Panel in each class (gist link)
  2. Single panel (gist link)

NOTICE:
If you use vector for MyClass like #1 example above, please make sure MyClass’s destructor won’t be called.
This happen often for example when you push_back() new element to vector. When you add new element to your vector, it could be automatically change its memory size to prepare future addition. During this auto expansion process, destructor ofGui is called and lose callback pointer information. Resulting mouse event stop working. To avoid this, and makes really sure its pointer and memory is under your control always, one could use shared pointer like below

vector<shared_ptr<MyClass>> myClasses;

I think this is not a bug but not a best user experience because I also confused sometimes.

@arturo
May I ask to check if it’s correct? :sweat_smile:
Also is it doable to update ofxGui to be std::vector friendly class?


#12

What do you mean by this?

I use ofxGui a lot and there is no problem on using either approach. It just depends on how you manage the guis visibility.


#13

Yes, two example above works ok.
But if we use ofxGui like following way, it won’t respond to mouse click.
Tested on macOS, almost latest master.

// MyClass.h
ofxPanel gui;

// ofApp.h
vector<MyClass> myClasses;

// ofApp.cpp
void setup(){

  for(int i+0; i<3; i++){
    myClasses.push_back(MyClass(i));
  }
}

When we call .push_back(), it calls destructor of MyClass and ofxPanel, then unregisterMouseEvents() will be called. It should register again during copy process but somehow it won’t. I didn’t look inside deep enough so not sure what is happening. So I guess it’s not copy-friendly.

If you replace push_back(MyClass(i)) to emplace_back(i), it does not work either because of std::vector changes its size automatically after first or second insertion and call ofxGui destructor.


#14

Hi, I see what you mean.
I am assumming that you are initializing the gui in the constructor of your MyClass class. There are a few options.
use a setup method in myClass and call it after pushing it into the vector.

void setup(){

  for(int i+0; i<3; i++){
    myClasses.push_back(MyClass());
    myClasses.back().setup(i);
  }
}

or use smart pointers instead of regular object instances.

// ofApp.h
vector<shared_ptr<MyClass> > myClasses;

// ofApp.cpp
void setup(){

  for(int i+0; i<3; i++){
    myClasses.push_back(make_shared<MyClass>(i));
  }
}

#15

@hrs, can you post the complete code you are using for testing please?


#16

@roymacdonald, Sure, here it is.

  1. Multi panel, NOT working example (gist link)

#17

This does not work on macOS.
To be precise, 3rd gui slider works, but 1st and 2nd slider does not.
This is natural because it force vector to increase its size. If we call myClasses.reserve(3); it works.

With shared_ptr, no problem at all, but it’s not really user friendly.
C++ beginner or students does not know how to use shared_ptr also can not aware of cyclic reference issue even if they manage to use smart pointer.

@roymacdonald, Which machine do you use? Windows?


#18

nope I use macos but i did not really test that code I posted, I just wrote it straight out of my head into the forum.
Can you post the complete code you are using for testing please. There might be something in there.
The following code works for me but it does not work with push_back. Maybe reenabling the mouse events can work.

#pragma once

#include "ofMain.h"
#include "ofxGui.h"
class MyClass{
public:
	void setup(int id){
		gui.setup(ofToString(id));
		gui.setPosition(id*200 + 20, 20);
		gui.add(fParam.set("float param", 0, 0, 1));
		gui.add(color.set("color param", {0,0,0,0},{0,0,0,0},{255,255,255,255}));
	}
	ofxPanel gui;
	ofParameter<float> fParam;
	ofParameter<ofColor>color;
};

class ofApp : public ofBaseApp{

	public:
		void setup();
	void draw();
	
	vector<MyClass> classes;
		
};

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
	classes.resize(5);
	for(int i = 0; i < classes.size(); i++){
		classes[i].setup(i);
	}
}

//--------------------------------------------------------------
void ofApp::draw(){
	for(int i = 0; i < classes.size(); i++){	
		classes[i].gui.draw();
	}
}

#19

@roymacdonald, Hey, I posted, pls check above.


#20

well, certainly the problem is because the vector will reallocate its objects when using push, so internally the pointer to this object changes which is the one used for registering to the mouse events, hence it does not work.

I still think that the best solution is with the smart pointers. it might be a bit unfriendly at first but it is much easier to handle than regular pointers.

in the code you sent if you put the following ugly hack it works

void ofApp::setup(){
 for(int i=0; i<3; i++){
	 classes.push_back(i);
	 
 }
	for(auto& c : classes){
		c.gui.unregisterMouseEvents();
		c.gui.registerMouseEvents();
	}
}

So, avoid pushing into the vector. emplace_back will not work either I think.
std::vector::reserve does not seem to work either.
If you still want to avoid the smart pointers I’d prefer to use std::vector::resize as in the previous code I posted and setup everything on a setup function rather than with the constructor.
hope this helps.