How to Access Class Properties in While Loop

Hi, I’ve got a sketch with a separate class file for buttons. I’m making an app, and I want to be able to click a button and run a void for serial communication. The void needs to be in its own loop so it can wait for serial data, or respond to the user exiting the function with a button click.

When you run the sketch and click button 1, the top button, it enters the loop as expected. But it never “hears” the update to the class property isClicked once it’s in the loop. In fact it never even finishes the class routines as it does not change the color to blue as the other buttons do.

I am a newbie here, so it might be an obvious dumb mistake, but how can I get out of the loop by pressing button 1 again?

I want to click button 1 to enter the loop, and click button 1 to exit the same loop. A toggle button.

Thanks for any insights.

Main Sketch snippet:

Button[] buttons = new Button[5];
boolean displayed = false;
int LightGray = 209;
int DarkGray = 45;
void setup(){
  size(800,600);
  
  AddButtons();
    
}

void draw(){
  background(100);
  
    for (int i=0; i<buttons.length; i++){
      buttons[i].display();
    } 
    
    EventHandler();
}

void AddButtons(){
  
  byte labelBoxWidth = 95;//95
  byte BoxHeight = 60;//60
    
  int xBoxLabelStart = 57;
  int yBoxLabelStart = 157;
  byte boxDistance = 75;//75
  
  byte xLabelStartOffset = 12;
  byte yLabelStartOffset = 42;
  
  byte labelFontSize = 32;
  byte sceneFontSize = 44;
  
  //color SelectedFill = color(38, 125, 247);//color(209, 34, 200);

  buttons[0] = new Button(xBoxLabelStart, 80, 60, 60, "1", 10, 20, sceneFontSize, true, DarkGray, LightGray);
  buttons[1] = new Button(xBoxLabelStart, yBoxLabelStart, labelBoxWidth, BoxHeight, "Tst1", xLabelStartOffset + 5, yLabelStartOffset, labelFontSize, false, DarkGray, LightGray);
  buttons[2] = new Button(xBoxLabelStart, yBoxLabelStart + boxDistance, labelBoxWidth,BoxHeight, "Tst2", xLabelStartOffset - 6, yLabelStartOffset, labelFontSize - 2, false, DarkGray, LightGray);
  buttons[3] = new Button(xBoxLabelStart, yBoxLabelStart + 2 * boxDistance, labelBoxWidth, BoxHeight, "Tst3", xLabelStartOffset - 2, yLabelStartOffset, labelFontSize, false, DarkGray, LightGray);
  buttons[4] = new Button(xBoxLabelStart, yBoxLabelStart + 3 * boxDistance, labelBoxWidth, BoxHeight, "Tst4", xLabelStartOffset + 4, yLabelStartOffset, labelFontSize, false, DarkGray, LightGray);
  
}

void EventHandler(){
  if(buttons[0].isClicked){
    buttons[0].isClicked = false;
    test(); 
  }        
}
  
  void test(){
  boolean exitLoop = false;

  while (!exitLoop) {
    
    if(buttons[0].isClicked) exitLoop = true;
       
    println("in the loop!");
  }//while
  
  println("out of loop!");
  
}//void changeScene

Separate tab named Button, with the following code:

class Button{
  
  int x; //x coordinate
  int y; //y coordinate
  int w; //button width
  int h; //button height
  int cr; //corner radius
  String label; //text on button  
  PFont labelFont;//the font to use
  PFont sceneNumberFont; //the font for the scene number
  int textX;//offset for the text X value
  int textY;//offset for text Y value
  int fillUp;//fill color when button is up
  int fillOver; //fill color when button is over
  int lightGray;//stroke color
  int fontSize; //font size
  boolean sceneNumber;//is the label the scene number?
  boolean isClicked;
  boolean on;
  boolean isMouseOver;
  boolean changed;
  int ScnNumY = 46;
  int ScnNumX = 2;
  color selectedFill = color(38, 125, 247);//color(209, 34, 200);
  
  Button(int x_, int y_, int w_, int h_, String label_, int textX_, int textY_, int fontSize_, boolean sceneNumber_, int fillUp_, int fillOver_){ 
    x = x_;
    y = y_;
    w = w_;
    h = h_;
    label = label_;
    textX = textX_;//additional offset
    textY = textY_;
    fontSize = fontSize_;
    fillUp = fillUp_;
    fillOver = fillOver_;
    lightGray = 209;    
    labelFont = loadFont("NotoSans-Regular-32.vlw");
    sceneNumberFont = loadFont("Ramabhadra-44-Basic.vlw");
    sceneNumber = sceneNumber_;
    isClicked = false;
    on = false;
    isMouseOver = false;
    changed = false;
  }
  
  void display(){
    pushMatrix();
    translate(x, y);
    
    stroke(lightGray);
    strokeWeight(3);
    
    mouseOver();
    
    if (isClicked) { 
      On();
    }
    else {
      Off();
    }
    
    popMatrix();
  
  }
  
  boolean isMouseOver(){    
    if((mouseX>=x) && (mouseX<=x+w) && (mouseY>=y) && (mouseY<=y+h)){
      //MouseOver = true;
      return true;
    }else{
      //MouseOver = false;
      return false;
    }
  }//end isMouseOver
  
  void mouseOver() {
  if (!isMouseOver()) {     
      isMouseOver = false;
    }
    else {      
      isMouseOver = true;
    }
  }
  
  void On() { 
    
    if (!sceneNumber) fill(selectedFill);
    else fill(selectedFill);

    rect(0, 0, w, h, 10); 
    
    if (!sceneNumber) {
      fill(fillOver);
      textFont(labelFont, fontSize);
      text(label, textX, textY); 
    }
    else {
      fill(fillOver);
      textFont(sceneNumberFont, 44);
      if (Integer.parseInt(label) < 10 )text(Integer.parseInt(label), ScnNumX + 16, ScnNumY); 
      else if (Integer.parseInt(label) < 20) text(Integer.parseInt(label), ScnNumX + 4, ScnNumY);
      else text(Integer.parseInt(label), ScnNumX + 5, ScnNumY);  
      
    }    
  }//end void On
  
  void Off(){        
    
    if (!isMouseOver) {
      if (!sceneNumber) fill(fillUp);
      else fill(fillOver);
    }
    else {
      fill(70);  
    }
    rect(0, 0, w, h, 10);
    
    if (!sceneNumber) {
      fill(fillOver);
      textFont(labelFont, fontSize);
      text(label, textX, textY); 
    }
    else {
      if (!isMouseOver) fill(fillUp);
      else fill(fillOver);
      textFont(sceneNumberFont, 44);
      if (Integer.parseInt(label) < 10 )text(Integer.parseInt(label), ScnNumX + 16, ScnNumY); 
      else if (Integer.parseInt(label) < 20) text(Integer.parseInt(label), ScnNumX + 4, ScnNumY);
      else text(Integer.parseInt(label), ScnNumX + 5, ScnNumY);  
      
    }
  }//end void Off 
  
}//end class

void mouseReleased(){
  int i = 0;
  int buttonClicked = 0;
  
  //figure out which button is currently clicked
  for( Button buttons : buttons ){
      if (buttons.isClicked) {
        buttonClicked = i;
      }
      i++;
    }
    //println("Button clicked is: " + buttonClicked);
    
    //Select the one clicked, and deselect the one that is already selected! Works a treat!!! :)
    for (int j=0; j<buttons.length; j++){
      if(buttons[j].isMouseOver) {        
        buttons[j].isClicked = !buttons[j].isClicked;
        if (buttonClicked != j) buttons[buttonClicked].isClicked = false;
        println("button " + j +": " + buttons[j].isClicked);
      }      
    }   
  }
  

Finally, I am using a syntax I found that I don’t fully understand. Can anyone explain this to me? I believe it iterates through all the button objects, but I can’t seem to find a reference to explain it clearly. And I hate using code I don’t understand (but it works).

for( Button buttons : buttons ){
}

Thank you kind internet geniuses!

Mike

Hi @Thundercat,

first change this

void EventHandler(){
  if(buttons[0].isClicked){
    buttons[0].isClicked = false;    
    test(); 
  }        
}

to

void EventHandler(){
  if(buttons[0].isClicked){
    test(); 
    buttons[0].isClicked = false;    
  }        
}

otherwise you’ll end up in an endless loop.


I’ll will not go to any deeper analysis for your code, but you can read about a thing
called Observer Pattern, which is very common in such a use case …

Hope that helps!

Cheers
— mnse

EDITED: code switched … meant the other way around …

Hi, thank you so much for your help.

I’m a little confused; you suggested changing the code to what I already had. Maybe you meant the other way around?

Both versions do not work by the way. In the second way you suggested, you do go into an endless loop.

In the first way, clicking the button takes you in and then immediately out of the loop, because it’s true, so it enters and exits the loop immediately.

Neither works. I need to be able to click the button and enter the loop. Then when I click the button, leave the loop.

To do that, I need to know if there’s a way to access the class property isClicked when I am in the while loop of the test void. Because currently I cannot seem to get the updated isClicked when in the while loop of test().

I thought the purpose of the class was to make accessing things like this easy; it’s been very frustrating.

Thank you again so much for your time.

Mike

Hi @Thundercat,

edited my code … sure, other way around …
In your inital version it stucks in an endless loop, so I just want to get rid of that.

Your question imo leads you in the wrong direction…

If you’re in the while loop on a single thread, nothing would can’t change the attribute, so it never gets true and hence nothing in you program would proceed further code.
The question is, do you really need that while loop and why ? (maybe the while loop is the draw function in your case, if it is meant as ie. a game loop with event handling)

s.th similar like this …

void draw() {
...
for (Button b:buttons) {
   if (mousePressed && b.isPressed(mouseX,mouseY)) {
    // do something for button b
   }
}
...
}

If you want an event driven approach, you should implement s.th like mentioned above by observer pattern, which notifies (calls a function) depending on the event…

Cheers
— mnse

Thank you mnse. I absolutely do need this functionality. The while loop is absolutely necessary - I’m waiting for serial response from an external device. Without the while loop, it will never receive the message.

And I can’t stick everything into the draw() method; it would be incredibly messy and hard to maintain. I want discrete blocks of code.

This is how I’ve done it, successfully, on a large app in the Arduino environment, which also has the “loop()” function. I make other while loops in other voids, and I’m able to exit the loops easily with changes in variables.

But from what you’re saying, I can’t do that with Processing?

I am starting to think that, as cool as processing is, it’s not meant for really making robust applications. I’m starting to think it’s just a learning tool. Which it excels at.

I tried to read the observer pattern link, but it’s incredibly complicated. If I need to do all this, I’m starting to think I may as well use full blown java, which boasts the swing and javafx libraries to handle mundane and incredibly basic things like buttons and sliders and event handling.

And yes, I’ve looked into G4P and Control5P. Both are great; I think G4P is incredibly great actually. But I wanted some functionality that it does not provide easily. Well, I can think of an incredibly clunky way to get what I need, but I’'m loathe to implement it. Using G4P toggle buttons with 32 states - and 32 images of numbers so the font is preserved. Ug! Ugly…

If anyone knows another way to get around the issue I’ve shared here, please let me know.

Thanks,

Mike

You should get rid of the while loop and call a function from draw.

Then your draw is short and nice and you have
discrete blocks of code.

like here

[edited]



Button[] buttons = new Button[5];
boolean displayed = false;
int LightGray = 209;
int DarkGray = 45;

boolean exitLoop = false;

// ------------------------------------------------------------------

void setup() {
  size(800, 600);

  AddButtons();
}

void draw() {
  background(100);

  for (int i=0; i<buttons.length; i++) {
    buttons[i].display();
  }

  EventHandler();
  test();
}

// ------------------------------------------------------------------

void AddButtons() {

  byte labelBoxWidth = 95;//95
  byte BoxHeight = 60;//60

  int xBoxLabelStart = 57;
  int yBoxLabelStart = 157;
  byte boxDistance = 75;//75

  byte xLabelStartOffset = 12;
  byte yLabelStartOffset = 42;

  byte labelFontSize = 32;
  byte sceneFontSize = 44;

  //color SelectedFill = color(38, 125, 247);//color(209, 34, 200);

  buttons[0] = new Button(xBoxLabelStart, 80, 60, 60, "1", 10, 20, sceneFontSize, true, DarkGray, LightGray);
  buttons[1] = new Button(xBoxLabelStart, yBoxLabelStart, labelBoxWidth, BoxHeight, "Tst1", xLabelStartOffset + 5, yLabelStartOffset, labelFontSize, false, DarkGray, LightGray);
  buttons[2] = new Button(xBoxLabelStart, yBoxLabelStart + boxDistance, labelBoxWidth, BoxHeight, "Tst2", xLabelStartOffset - 6, yLabelStartOffset, labelFontSize - 2, false, DarkGray, LightGray);
  buttons[3] = new Button(xBoxLabelStart, yBoxLabelStart + 2 * boxDistance, labelBoxWidth, BoxHeight, "Tst3", xLabelStartOffset - 2, yLabelStartOffset, labelFontSize, false, DarkGray, LightGray);
  buttons[4] = new Button(xBoxLabelStart, yBoxLabelStart + 3 * boxDistance, labelBoxWidth, BoxHeight, "Tst4", xLabelStartOffset + 4, yLabelStartOffset, labelFontSize, false, DarkGray, LightGray);
}

void EventHandler() {
  if (buttons[0].isClicked) {
    // test();
    //  println("here");
    //buttons[0].isClicked =
    //  ! buttons[0].isClicked;

    exitLoop = false;
  } else exitLoop = true;
}

void test() {

  if (exitLoop)
    return;

  // do sth
  print(".");
}//func

// ================================================

class Button {

  int x; //x coordinate
  int y; //y coordinate
  int w; //button width
  int h; //button height

  int cr; //corner radius
  String label; //text on button
  PFont labelFont;//the font to use
  PFont sceneNumberFont; //the font for the scene number
  int textX;//offset for the text X value
  int textY;//offset for text Y value
  int fillUp;//fill color when button is up
  int fillOver; //fill color when button is over
  int lightGray;//stroke color
  int fontSize; //font size
  boolean sceneNumber;//is the label the scene number?
  boolean isClicked;
  boolean on;
  boolean isMouseOver;
  boolean changed;
  int ScnNumY = 46;
  int ScnNumX = 2;
  color selectedFill = color(38, 125, 247);//color(209, 34, 200);

  Button(int x_, int y_, int w_, int h_, String label_, int textX_, int textY_, int fontSize_, boolean sceneNumber_, int fillUp_, int fillOver_) {
    x = x_;
    y = y_;
    w = w_;
    h = h_;
    label = label_;
    textX = textX_;//additional offset
    textY = textY_;
    fontSize = fontSize_;
    fillUp = fillUp_;
    fillOver = fillOver_;
    lightGray = 209;
    labelFont = createFont("NotoSans-Regular-32.vlw", 14);
    sceneNumberFont = createFont("Ramabhadra-44-Basic.vlw", 14);
    sceneNumber = sceneNumber_;
    isClicked = false;
    on = false;
    isMouseOver = false;
    changed = false;
  }

  void display() {
    pushMatrix();
    translate(x, y);

    stroke(lightGray);
    strokeWeight(3);

    mouseOver();

    if (isClicked) {
      On();
    } else {
      Off();
    }

    popMatrix();
  }

  boolean isMouseOver() {
    if ((mouseX>=x) && (mouseX<=x+w) && (mouseY>=y) && (mouseY<=y+h)) {
      //MouseOver = true;
      return true;
    } else {
      //MouseOver = false;
      return false;
    }
  }//end isMouseOver

  void mouseOver() {
    if (!isMouseOver()) {
      isMouseOver = false;
    } else {
      isMouseOver = true;
    }
  }

  void On() {

    if (!sceneNumber) fill(selectedFill);
    else fill(selectedFill);

    rect(0, 0, w, h, 10);

    if (!sceneNumber) {
      fill(fillOver);
      textFont(labelFont, fontSize);
      text(label, textX, textY);
    } else {
      fill(fillOver);
      textFont(sceneNumberFont, 44);
      if (Integer.parseInt(label) < 10 )text(Integer.parseInt(label), ScnNumX + 16, ScnNumY);
      else if (Integer.parseInt(label) < 20) text(Integer.parseInt(label), ScnNumX + 4, ScnNumY);
      else text(Integer.parseInt(label), ScnNumX + 5, ScnNumY);
    }
  }//end void On

  void Off() {

    if (!isMouseOver) {
      if (!sceneNumber) fill(fillUp);
      else fill(fillOver);
    } else {
      fill(70);
    }
    rect(0, 0, w, h, 10);

    if (!sceneNumber) {
      fill(fillOver);
      textFont(labelFont, fontSize);
      text(label, textX, textY);
    } else {
      if (!isMouseOver) fill(fillUp);
      else fill(fillOver);
      textFont(sceneNumberFont, 44);
      if (Integer.parseInt(label) < 10 )text(Integer.parseInt(label), ScnNumX + 16, ScnNumY);
      else if (Integer.parseInt(label) < 20) text(Integer.parseInt(label), ScnNumX + 4, ScnNumY);
      else text(Integer.parseInt(label), ScnNumX + 5, ScnNumY);
    }
  }//end void Off
}//end class

void mouseReleased() {
  int i = 0;
  int buttonClicked = 0;

  //figure out which button is currently clicked
  for ( Button buttons : buttons ) {
    if (buttons.isClicked) {
      buttonClicked = i;
    }
    i++;
  }
  //println("Button clicked is: " + buttonClicked);

  //Select the one clicked, and deselect the one that is already selected! Works a treat!!! :)
  for (int j=0; j<buttons.length; j++) {
    if (buttons[j].isMouseOver) {
      buttons[j].isClicked = !buttons[j].isClicked;
      if (buttonClicked != j) buttons[buttonClicked].isClicked = false;
      println("button " + j +": " + buttons[j].isClicked);
    }
  }
}

1 Like

it’s called enhanced for-loop

2 Likes

Hi @Thundercat,

here is an approach for a simple button handling …

class Button {
  int x,y,w,h;
  String label;
  boolean mouse_over, mouse_pressed, mouse_pressed_state; 
  
  public Button(int x,int y,int w,int h, String label) {
    this.x=x;
    this.y=y;
    this.w=w;
    this.h=h;
    this.label=label;
  }

  public boolean hover(int mX, int mY) {
    mouse_over = (mouseX > x && mouseY < x+w && mouseY > y && mouseY < y+h);
    return mouse_over; 
  }
   
  public void update(int mX, int mY, boolean state) {
    if (hover(mX,mY))
        mouse_pressed=state;
  }
  
  public boolean isPressed() {
    if (mouse_pressed) {
      mouse_pressed=false;
      return true;
    }
    return false;
  }

  public void render() {
    push();
    noFill();
    stroke(mouse_pressed ? color(255,0,0) : mouse_over ? color(0,255,0) : color(255));
    translate(x,y);
    rect(0,0,w,h);
    fill(mouse_pressed ? color(255,0,0) : mouse_over ? color(0,255,0) : color(255));
    translate(x,h/2);
    text(label,x+10,0);
    pop();
    mouse_pressed=false;    
  }
}

Button[] buttons;

void mousePressed() {
  for (Button b:buttons) {
    b.update(mouseX, mouseY,true);
  }
}

void mouseMoved() {
  for (Button b:buttons) {
    b.update(mouseX, mouseY,false);
  }
}

void mouseReleased() {
  for (Button b:buttons) {
    b.update(mouseX, mouseY,false);
  }
}

void setup() {
  size(400,400);
  buttons = new Button[2];
  buttons[0] = new Button(10,10,100,40,"Button 1"); 
  buttons[1] = new Button(10,60,100,40,"Button 2");
}

void draw() {
  background(0);
  for (Button b:buttons) {
    if (b.isPressed()) {
      println("Button " + b.label + " is pressed");      
    }
    b.render();
  }
}

Hi @Thundercat,

in this case you should consider a multi-threaded approach.
The thread checks the input from device and triggers a notification to the main thread if s.th relevant happens …

Cheers
— mnse

1 Like

Thank you. But if I call test() from draw, it takes me into a loop that it shouldn’t yet. And bypassing the whole point of the EventHandler, which is only to respond when the person clicks a button.

It should only enter the loop if the user clicks the button, as dictated in the EventHandler.

Plus still, even if you put it directly in draw(), you’d be trapped in the loop. So this doesn’t work I’m afraid.

Thank you so much Chrisir! I didn’t even know what to google to find it.

Awesome!

1 Like

Terrific.

Processing is a single threaded program though isn’t it? I know it’s built on java, but from my readings it’s single thread, yeah?

So are you suggesting to use enhanced java techniques in Processing, or to just switch to java full blown?

Thanks kindly,

Mike

Did you test it?

These lines

  // do sth
  print(".");

are executed only when the button is ON.

1 Like

the thread() command is part of standard processing

see thread() / Reference / Processing.org

3 Likes

Hi Chrisir, thank you, yes I did test it.

If you try it, you go into the loop immediately when you run the sketch - this is not desired behavior.

The point is to have the button determine when to enter the loop (for serial communications), but at the moment, then you are trapped in the loop, no way out.

The goal is this:

Run the sketch, but do NOT run test().

Wait until the user clicks 1, and then enter the loop.

Stay in the loop until the user clicks 1 again, and then exit the loop.

I appreciate your time, thank you so much.

“the thread() command is part of standard processing”

Thank you again. I really appreciate your knowledge of processing.

I read the reference doc. I’m not sure this can work here though, and they mention it is a “complex endeavor to extend java” - sorry to be dense, but can you show how I could use this here?

I appreciate your time Chrisir, I see you on many of the threads, and I’m grateful for your expertise.

Mike

1 Like

Maybe this is getting philosophical, but this is exactly what my code does.

(Okay, granted, exitLoop should initially be true)

  1. test() is called but exits immediately via return command. (same thing as doing the if in draw() but nicer imho)

  2. the loop is running, using the fact that draw() loops automatically (and calls test())

  3. when exitLoop is true again, test() is left immediately again

1 Like

not sure, I never really worked with thread() command

2 Likes

By default, a Processing sketch runs under a Thread called “Animation Thread”.

We can check that out by placing this statement on setup() or draw() callbacks:
println(Thread.currentThread());

Besides that 1, there’s another Thread that collects all mouse & keyboard events.

We can also create our own threads via thread():

Or we can directly use Java’s standard API for it:

However, other Processing libraries can create their own Thread.

For example, Serial’s callback serialEvent() happens in its own Thread:

You can place code there which could, for example, invoke a method from your Button class; or modify some variable that Button could check.

3 Likes

Thank you GoToLoop.

Hey do you ever sleep? :smiley:

I don’t really know how to do all this, but I’ll look into it.

If anyone has some concrete examples of how this could be done in this example, I would be grateful.

Thanks everyone for your wisdom and support.

Mike