G4P java Concurrent Modification Exception Error when exit() called

Hi all,

Just getting a strange java.util.ConcurrentModificationException message when using multiple windows in G4P.

It all seems to work fine until I try to use an “Exit” button (calling exit()), and then I get the following:

java.util.ConcurrentModificationException
at java.base/java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:970)
at java.base/java.util.LinkedList$ListItr.next(LinkedList.java:892)
at g4p_controls.GWindowImpl.dispose(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at processing.core.PApplet$RegisteredMethods.handle(PApplet.java:1309)
at processing.core.PApplet.handleMethods(PApplet.java:1456)
at processing.core.PApplet.dispose(PApplet.java:3420)
at processing.core.PSurfaceNone$AnimationThread.run(PSurfaceNone.java:405)
ConcurrentModificationException

I’ve been googling this and it seems to be related to trying to remove items in an Arraylist while still iterating over the list, but I’m not doing that explicitly (although I’m not sure what’s happening behind the scenes in java or the library). I’m just trying to quit the whole app with exit().

Is there any guidance on how to avoid this error?

Thanks,

Mike

I suggest that you use the close() or forceClose() method for all secondary windows before exiting the app.

To make this automatic then you might try adding this method to your application (no promises that it will work out of the box) :grin:

// This assumes you have 2 windows win1 and win2
public void exit(){
  win1.close(); // or forceClose()
  win2.close(); // or forceClose()
  super.exit();
}
2 Likes

Wonderful Peter. I will give it a shot x.

:pray::two_hearts:

That did it!

I had to first check if the windows were null, and then also I had to use the forceClose() instead of close - but it worked!

Many thanks Peter!!!

Mike

By the way, I’m now getting an error about a missing GWindowAWT method, but I am not actively using it:

The GWindowAWT class cannot find this method 
	public void null(PApplet applet, GWinData windata) { /* code */ }

Any idea why? I tried just copy/pasting the code but it won’t accept it.

Thx for any thoughts. Seems to be a long process to get this right.

Mike

I have no idea because in the 15 years that G4P has been available this error message has never been reported before and there is insufficient information for me to even start looking for a solution.

For instance

  • did the error occur when a window was being created?
  • did the error occur when a window was being closed?
  • what code were you copying and pasting?
  • are you using GUI Builder?
  • do you know the source code being executed just prior to the error message?

If I am to emulate Sherlock Holmes and find a solution I need information, I need detail :grin:

2 Likes

Of course that’s not enough info. Sorry.

Can I ask where GWindowAWT class is located? I have not explicitly called it from the code anywhere. So there’s no place for me to put the suggested fix line.

Here is the code for all the additional optional window. Yes it’s a hot mess compared to the elegance with which you would do it I’m sure. If it gives no ideas to you, that’s fine too. I don’t expect you to write my code. But if you see anything with a glance, that would be helpful.


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                 GLOBAL COLOR THEME BOX                                                         //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


public void globalCTDraw(PApplet app, GWinData data){
  app.background(127);
  globalCT.image(editIcon, 150, 10);    
}
  
void showGlobalCT() {
  int windowWidth = 350;
  int windowHeight = 245;
  if (globalCT == null) {
    globalCT = GWindow.getWindow(this, "MusioTech", width/2 + 120, height/2 + 65, windowWidth, windowHeight, JAVA2D);
    globalCT.setActionOnClose(G4P.HIDE_WINDOW);
    globalCT.addMouseHandler(this, "globalCTMouse");
    globalCT.addKeyHandler(this, "globalCTKey");
    globalCT.addDrawHandler(this, "globalCTDraw");    
    
    lblMessage = new GLabel(globalCT, 40, 65, 260, 50);
    lblMessage.setText("Global Color Theme:");
    lblMessage.setLocalColorScheme(GCScheme.SCHEME_15);
    lblMessage.setOpaque(false);
    lblMessage.setTextAlign(GAlign.CENTER, GAlign.MIDDLE);
    lblMessage.setFont(new Font("Noto Sans Bold", G4P.PLAIN, 24));
    
    ddlEditGlobalCT = new GDropList(globalCT, 50, 110, 250, 132, 3, 10);
    //ddlEditGlobalCT.setItems(loadStrings("list_819097"), 0);
    ddlEditGlobalCT.setLocalColorScheme(GCScheme.SCHEME_9);
    ddlEditGlobalCT.addEventHandler(this, "ddlEditGlobalCT_click");
    ddlEditGlobalCT.setFont(new Font("Noto Sans Bold", G4P.PLAIN, 24));
    for (byte i = 0; i < (presetColorSetCount + customColorSetCount); i++) {
      ddlEditGlobalCT.addItem(colorSetNames[i]); 
    } 
    
    btnOKglobalCT = new GButton (globalCT, 90, 175, 80, 30, "OK");
    btnOKglobalCT.addEventHandler(this, "btnOKglobalCT_clicked");
    btnOKglobalCT.setLocalColorScheme(GCScheme.SCHEME_9);
    
    btnCancelglobalCT = new GButton (globalCT, 190, 175, 80, 30, "Cancel");
    btnCancelglobalCT.addEventHandler(this, "btnCancelglobalCT_clicked");
    btnCancelglobalCT.setLocalColorScheme(GCScheme.SCHEME_9);
    
  }//if (messageBox != null) {
  else {
    globalCT.setVisible(true); 
  } 

}//end void showGlobalCT

public void globalCTMouse(PApplet app, GWinData data, MouseEvent event) {
  //float e = event.getCount();
  //colorThemeSelected += e;
  
  //if (ddlEditGlobalCT.hasFocus()) {
  //  if (colorThemeSelected > presetColorSetCount + customColorSetCount) colorThemeSelected = presetColorSetCount + customColorSetCount - 1;
  //  if (colorThemeSelected < 0) colorThemeSelected = 0;
  //  ddlEditGlobalCT.setSelected(colorThemeSelected);
  //}
}

public void globalCTKey(PApplet app, GWinData data, KeyEvent event) {
  if (event.getKey() == RETURN || event.getKey() == ENTER) globalCT.setVisible(false);
}

//public class MyMsgData extends GWinData {
//    // The variables can be anything you like.
//    public int lastClickX,lastClickY;
//}

public void btnOKglobalCT_clicked(GButton source, GEvent event) {
  rainbow.getRainbowColors();//rainbow.setRainbowColors(ddlEditGlobalCT.getSelectedIndex());
  rainbow.display();
  lblGlobalColorTheme.setText("Global: " + colorSetNames[globalColorSetRow]);
  globalCT.setVisible(false);  
}

public void btnCancelglobalCT_clicked(GButton source, GEvent event) {
  globalCT.setVisible(false);  
}

public void ddlEditGlobalCT_click(GDropList source, GEvent event) { //_CODE_:dropList1:306266:
  println("ddlEditGlobalCT - GDropList >> GEvent." + event + " @ " + millis());
  globalColorSetRow = source.getSelectedIndex();
} //_CODE_:dropList1:306266:

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                            LOADING BOX                                                         //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

public void loadingBoxDraw(PApplet app, GWinData data){
  app.background(127);    
}
  
void showLoadingBox(String strTitle, String strMessage) {
  if (loadingBox == null) {
    loadingBox =  GWindow.getWindow(this, strTitle, width/2 + 120, height/2 + 65, 240, 130, JAVA2D);
    loadingBox.setActionOnClose(G4P.HIDE_WINDOW);
    loadingBox.addDrawHandler(this, "loadingBoxDraw");
    //loadingBox.addMouseHandler(this, "loadingBoxMouse");
    loadingBox.addKeyHandler(this, "loadingBoxKey");
    
    GAnimIcon loadingIcon = new GAnimIcon(loadingBox, Loading, 8, 1, 150); 
    loadingLabel = new GLabel(loadingBox, 10, 20, 80, 80, "");
    loadingLabel.setLocalColorScheme(GCScheme.SCHEME_15);//SCHEME_15
    loadingLabel.setTextAlign(GAlign.CENTER, null);
    loadingLabel.setIcon(loadingIcon, GAlign.NORTH, null, null);
    loadingLabel.getIcon().animate();  
    
    lblMessage = new GLabel(loadingBox, 80, 45, 120, 30);
    lblMessage.setText(strMessage);
    lblMessage.setLocalColorScheme(GCScheme.SCHEME_15);
    lblMessage.setOpaque(false);
    lblMessage.setTextAlign(GAlign.LEFT, GAlign.MIDDLE);
    lblMessage.setFont(new Font("Noto Sans Bold", G4P.PLAIN, 24));
  }// if (loadingBox == null) }
  else {    
    lblMessage.setText(strMessage);
    loadingBox.setVisible(true);
  }
}//end void showLoadingBox
  
//public void preferencesMouse(PApplet app, GWinData data, MouseEvent event) {
//    // Saves doing it for every variable in MyData class
//    MyData myData = (MyData) data;
//    if(event.getAction() == MouseEvent.CLICK){
//      myData.lastClickX = mouseX;
//      myData.lastClickY = mouseY;
//    }
//}

public void loadingBoxKey(PApplet app, GWinData data, KeyEvent event) {


}

//public class MyData extends GWinData {
//    // The variables can be anything you like.
//    public int lastClickX,lastClickY;
//}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                           MESSAGE BOX                                                          //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

public void msgBoxYesDraw(PApplet app, GWinData data){
  app.background(127);
  msgBox.image(yesIcon, 122, 5);    
}

public void msgBoxNoDraw(PApplet app, GWinData data){
  app.background(127);
  msgBox.image(noIcon, 122, 5);    
}

public void msgBoxInfoDraw(PApplet app, GWinData data){
  app.background(127);
  msgBox.image(infoIcon, 122, 5);    
}

public void msgBoxWarningDraw(PApplet app, GWinData data){
  app.background(127);
  msgBox.image(warningIcon, 122, 5);    
}
public void msgBoxCriticalDraw(PApplet app, GWinData data){
  app.background(127);
  msgBox.image(criticalIcon, 122, 5);    
}

public void msgBoxBackupOKDraw(PApplet app, GWinData data){
  app.background(127);  
  msgBox.image(backupOKIcon, 122, 5);  
}
  
void showMsgBox(int boxStyle, String strTitle, String strMessage) {
  if (msgBox == null) {
    msgBox =  GWindow.getWindow(this, strTitle, width/2 + 120, height/2 + 65, 300, 160, JAVA2D);
    msgBox.setActionOnClose(G4P.HIDE_WINDOW);
    msgBox.addMouseHandler(this, "msgBoxMouse");
    msgBox.addKeyHandler(this, "msgBoxKey");
    
    lblMessage = new GLabel(msgBox, 25, 55, 250, 50);
    lblMessage.setText(strMessage);
    lblMessage.setLocalColorScheme(GCScheme.SCHEME_15);
    lblMessage.setOpaque(false);
    lblMessage.setTextAlign(GAlign.CENTER, GAlign.MIDDLE);
    lblMessage.setFont(new Font("Noto Sans Bold", G4P.PLAIN, 14));
    
    btnOKmsgBox = new GButton (msgBox, 112, 115, 80, 30, "OK");
    btnOKmsgBox.addEventHandler(this, "btnOKmsgBox_clicked");
    btnOKmsgBox.setLocalColorScheme(GCScheme.SCHEME_9);
    
    if (boxStyle == YES) {
      msgBox.addDrawHandler(this, "msgBoxYesDraw");    
    }
    else if (boxStyle == NO) {
      msgBox.addDrawHandler(this, "msgBoxNoDraw");    
    }
    else if (boxStyle == INFO) {
      msgBox.addDrawHandler(this, "msgBoxInfoDraw");     
    }
    else if (boxStyle == WARNING) {
      msgBox.addDrawHandler(this, "msgBoxWarningDraw");  
    } 
    else if (boxStyle == CRITICAL) {
      msgBox.addDrawHandler(this, "msgBoxCriticalDraw");  
    } 
    
    else if (boxStyle == BACKUPOK) {
      msgBox.addDrawHandler(this, "msgBoxBackupOKDraw");    
    }
  }//if (messageBox != null) {
  else {
    lblMessage.setText(strMessage);
    msgBox.setTitle(strTitle);
    msgBox.setVisible(true);  
  }
  msgBox.addDrawHandler(this, null);    
  if (boxStyle == YES) {
    msgBox.addDrawHandler(this, "msgBoxYesDraw");    
  }
  else if (boxStyle == NO) {
    msgBox.addDrawHandler(this, "msgBoxNoDraw");    
  }
  else if (boxStyle == INFO) {
    msgBox.addDrawHandler(this, "msgBoxInfoDraw");     
  }
  else if (boxStyle == WARNING) {
    msgBox.addDrawHandler(this, "msgBoxWarningDraw");  
  } 
  else if (boxStyle == CRITICAL) {
    msgBox.addDrawHandler(this, "msgBoxCriticalDraw");  
  } 
  else if (boxStyle == BACKUPOK) {
    msgBox.addDrawHandler(this, "msgBoxBackupOKDraw");    
  }
}

public void msgBoxMouse(PApplet app, GWinData data, MouseEvent event) {
    // Saves doing it for every variable in MyData class
    //MyMsgData myData = (MyMsgData) data;
    //if(event.getAction() == MouseEvent.CLICK){
    //  MyMsgData.lastClickX = mouseX;
    //  MyMsgData.lastClickY = mouseY;
    //}
}

public void msgBoxKey(PApplet app, GWinData data, KeyEvent event) {
  if (event.getKey() == RETURN || event.getKey() == ENTER) msgBox.setVisible(false);
}

//public class MyMsgData extends GWinData {
//    // The variables can be anything you like.
//    public int lastClickX,lastClickY;
//}

public void btnOKmsgBox_clicked(GButton source, GEvent event) {  
  msgBox.setVisible(false);  
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                QUESTION BOX                                                    //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

public void qBoxDraw(PApplet app, GWinData data){
  app.background(127);
  app.image(questionIcon, 122, 5);    
}
  
void showQBox(String strTitle, String strMessage, int action) {
  int windowWidth = 300;
  int windowHeight = 160;
  if (qBox == null) {
    qBox =  GWindow.getWindow(this, strTitle, width/2 + 120, height/2 + 65, 300, 160, JAVA2D);
    qBox.setActionOnClose(G4P.HIDE_WINDOW);
    qBox.addMouseHandler(this, "qBoxMouse");
    qBox.addKeyHandler(this, "qBoxKey");
    qBox.addDrawHandler(this, "qBoxDraw");  
    
    lblMessage = new GLabel(qBox, 25, 55, 250, 50);
    lblMessage.setText(strMessage);
    lblMessage.setLocalColorScheme(GCScheme.SCHEME_15);
    lblMessage.setOpaque(false);
    lblMessage.setTextAlign(GAlign.CENTER, GAlign.MIDDLE);
    lblMessage.setFont(new Font("Noto Sans Bold", G4P.PLAIN, 14));
    
    btnYesQBox = new GButton (qBox, windowWidth/2 + 20, windowHeight - buttonYOffset, 80, 30, "Yes");
    btnYesQBox.setLocalColorScheme(GCScheme.SCHEME_9);
    switch (action) {
      case 0://backup
        btnYesQBox.addEventHandler(this, "qBoxBackupDevice_click");
        break;
      case 1://restore
        btnYesQBox.addEventHandler(this, "qBoxRestoreDevice_click");
        break;      
    }
    
    btnNoQBox = new GButton (qBox,  windowWidth/2 - buttonWidth - 20, windowHeight - buttonYOffset, 80, 30, "No");
    btnNoQBox.addEventHandler(this, "btnNoQBox_click");
    btnNoQBox.setLocalColorScheme(GCScheme.SCHEME_9);
  }//if (qBox != null) {
  else {
     btnYesQBox.addEventHandler(this, null);
    switch (action) {
      case 0://backup
        btnYesQBox.addEventHandler(this, "qBoxBackupDevice_click");
        break;
      case 1://restore
        btnYesQBox.addEventHandler(this, "qBoxRestoreDevice_click");
        break;      
    }
    lblMessage.setText(strMessage);
    qBox.setTitle(strTitle);
    qBox.setVisible(true);  
  }    
}

public void qBoxMouse(PApplet app, GWinData data, MouseEvent event) {
    // Saves doing it for every variable in MyData class
    //MyMsgData myData = (MyMsgData) data;
    //if(event.getAction() == MouseEvent.CLICK){
    //  MyMsgData.lastClickX = mouseX;
    //  MyMsgData.lastClickY = mouseY;
    //}
}

public void qBoxKey(PApplet app, GWinData data, KeyEvent event) {
  //if (event.getKey() == RETURN || event.getKey() == ENTER) msgBox.setVisible(false);
}

//public class MyMsgData extends GWinData {
//    // The variables can be anything you like.
//    public int lastClickX,lastClickY;
//}

public void btnNoQBox_click(GButton source, GEvent event) {  
  qBox.setVisible(false);    
}

public void qBoxBackupDevice_click(GButton source, GEvent event) {
  qBox.setVisible(false);  
    
}
public void qBoxRestoreDevice_click(GButton source, GEvent event) {
  qBox.setVisible(false);  
    
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                             OPTIONS BOX                                                        //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


public void optionsDraw(PApplet app, GWinData data){
    app.background(127);
    app.image(optionsIcon, 123, 10);   
    //app.stroke(100);
    //app.strokeWeight(2);
    //app.fill(127);
    //app.rect(15, 70, 270, 70, 5);
  }
  
void createOptionsWindow() {
  int windowWidth = 300;
  int windowHeight = 200;
  options =  GWindow.getWindow(this, "Options", width/2 + 120, height/2 + 70, windowWidth, windowHeight, JAVA2D);
  options.setActionOnClose(G4P.HIDE_WINDOW);
  options.addDrawHandler(this, "optionsDraw");
  options.addMouseHandler(this, "optionsMouse");
  options.addKeyHandler(this, "optionsKey");
  //options.addData(new MyData());
  cbShowToolTips = new GCheckbox(options, 22, 80, 120, 20, "Show Tooltips");
  cbShowToolTips.setLocalColorScheme(GCScheme.SCHEME_15);
  cbShowToolTips.setSelected(blnShowToolTip);
  cbShowToolTips.addEventHandler(this, "cbShowToolTips_clicked");
  
  btnCancelOptions = new GButton (options, windowWidth/2 - buttonWidth - 15, windowHeight - buttonYOffset, buttonWidth, buttonHeight, "Cancel");
  btnCancelOptions.addEventHandler(this, "btnCancelOptions_clicked");
  btnCancelOptions.setLocalColorScheme(GCScheme.SCHEME_9);
  
  btnOKOptions = new GButton (options, windowWidth/2 + 15, windowHeight - buttonYOffset, buttonWidth, buttonHeight, "OK");
  btnOKOptions.addEventHandler(this, "btnOKOptions_clicked");
  btnOKOptions.setLocalColorScheme(GCScheme.SCHEME_9);
}
  
public void optionsMouse(PApplet app, GWinData data, MouseEvent event) {
    // Saves doing it for every variable in MyData class
    //MyData myData = (MyData) data;
    //if(event.getAction() == MouseEvent.CLICK){
    //  myData.lastClickX = mouseX;
    //  myData.lastClickY = mouseY;
    //}
}

public void optionsKey(PApplet app, GWinData data, KeyEvent event) {
  if (event.getKey() == RETURN || event.getKey() == ENTER) options.setVisible(false);
}

public class MyData extends GWinData {
    // The variables can be anything you like.
    public int lastClickX,lastClickY;
}

public void cbShowToolTips_clicked(GCheckbox source, GEvent event) {
  if (cbShowToolTips.isSelected()) blnShowToolTip = true;
  else blnShowToolTip = false;
  showHideToolTips();  
}

void showHideToolTips() { 
    
  if (blnShowToolTip) toolTipGap = 0;
  else toolTipGap = 1000; 
  setTooltips();  
}

public void btnCancelOptions_clicked(GButton source, GEvent event) {
  println("CANCEL CLICKED!");
  if (options != null) options.setVisible(false);  
}

public void btnOKOptions_clicked(GButton source, GEvent event) {
  println("OK CLICKED!");
  try{
    optString[1] = str(blnShowToolTip);
    saveStrings(dataPath("") +"/options.txt", optString);
  }
  catch(Exception e) {
    e.printStackTrace();
  }
  if (options != null) options.setVisible(false);  
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                             BACKUP & RESTORE                                                        //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

public void backupAndRestoreDraw(PApplet app, GWinData data){
    app.background(127);
    app.image(backupIcon, 123, 10);   
    //app.stroke(100);
    //app.strokeWeight(2);
    //app.fill(127);
    //app.rect(15, 70, 270, 70, 5);
  }
  
void showbackupAndRestoreWindow() {
  int windowWidth = 300;
  int windowHeight = 200;
  if (backupAndRestore == null) {
    backupAndRestore =  GWindow.getWindow(this, "Backup & Restore", width/2 + 120, height/2 + 70, windowWidth, windowHeight, JAVA2D);
    backupAndRestore.setActionOnClose(G4P.HIDE_WINDOW);
    backupAndRestore.addDrawHandler(this, "backupAndRestoreDraw");
    backupAndRestore.addMouseHandler(this, "backupAndRestoreMouse");
    backupAndRestore.addKeyHandler(this, "backupAndRestoreKey");  
    
    imgBtnBackupBackup = new GImageButton(backupAndRestore, windowWidth/2 - buttonWidth - 5, windowHeight - 130, 53, 53, new String[] { "backupButtonLarger3Up.png", "backupButtonLarger3Over.png", "backupButtonLarger3Pressed.png" } );
    imgBtnBackupBackup.addEventHandler(this, "imgBtnBackupBackup_click");
    imgBtnBackupBackup.setTip("Backup", GAlign.CENTER, GAlign.NORTH, toolTipGap);
    
    imgBtnRestoreBackup = new GImageButton(backupAndRestore, windowWidth/2 + 31, windowHeight - 130, 53, 53, new String[] { "restoreButtonLarger3Up.png", "restoreButtonLarger3Over.png", "restoreButtonLarger3Pressed.png" } );
    imgBtnRestoreBackup.addEventHandler(this, "imgBtnRestoreBackup_click");
    imgBtnRestoreBackup.setTip("Restore", GAlign.CENTER, GAlign.NORTH, toolTipGap);
    
    btnOKBackup = new GButton (backupAndRestore, windowWidth/2 - buttonWidth/2, windowHeight - buttonYOffset, buttonWidth, buttonHeight, "OK");
    btnOKBackup.addEventHandler(this, "btnOKBackup_clicked");
    btnOKBackup.setLocalColorScheme(GCScheme.SCHEME_9);
  }
  else {
    imgBtnBackupBackup.setTip("Backup", GAlign.CENTER, GAlign.NORTH, toolTipGap);
    imgBtnRestoreBackup.setTip("Restore", GAlign.CENTER, GAlign.NORTH, toolTipGap);
    backupAndRestore.setVisible(true);
  }
}
  
public void backupAndRestoreMouse(PApplet app, GWinData data, MouseEvent event) {
    // Saves doing it for every variable in MyData class
    //MyData myData = (MyData) data;
    //if(event.getAction() == MouseEvent.CLICK){
    //  myData.lastClickX = mouseX;
    //  myData.lastClickY = mouseY;
    //}
}

public void backupAndRestoreKey(PApplet app, GWinData data, KeyEvent event) {
  if (event.getKey() == RETURN || event.getKey() == ENTER) options.setVisible(false);
}

//public class MyData extends GWinData {
//    // The variables can be anything you like.
//    public int lastClickX,lastClickY;
//}

public void imgBtnBackupBackup_click(GImageButton imagebutton, GEvent event) {
  println("BACKUP CLICKED!"); 
  showQBox("Backup", "Backup all device data?", performBackup);
  if (backupAndRestore != null) backupAndRestore.setVisible(false);  
}

public void imgBtnRestoreBackup_click(GImageButton imagebutton, GEvent event) {
  println("RESTORE CLICKED!");  
  showQBox("Restore", "Restore all device data?", performRestore);
  if (backupAndRestore != null) backupAndRestore.setVisible(false);
}


//public void btnCancelBackup_clicked(GButton source, GEvent event) {
//  println("CANCEL CLICKED!");
//  if (backupAndRestore != null) backupAndRestore.setVisible(false);  
//}

public void btnOKBackup_clicked(GButton source, GEvent event) {
  println("OK CLICKED!");

  if (backupAndRestore != null) backupAndRestore.setVisible(false);  
}
//public void null(PApplet applet, GWinData windata) { /* code */ }

Also here’s the declaration of the windows, prior to setup as globals:

///// Custom Controls /////

GWindow options = null;
GWindow loadingBox = null;
GWindow msgBox = null;
GWindow globalCT = null;
GWindow backupAndRestore = null;
GWindow backup = null;
GWindow restore = null;
GWindow qBox = null;

GCheckbox cbShowToolTips;
GButton btnOKOptions;
GButton btnCancelOptions;
GButton btnOKmsgBox;
GButton btnOKglobalCT;
GButton btnCancelglobalCT;
GLabel loadingLabel;
GButton loadingButton;
GLabel lblMessage;
GDropList ddlEditGlobalCT;
GButton btnCancelBackup;
GButton btnOKBackup;
GButton btnBackup;
GButton btnRestore;
GImageButton imgBtnBackupBackup;
GImageButton imgBtnRestoreBackup;
GButton btnYesQBox;
GButton btnNoQBox;

PImage Loading;
PImage warningIcon;
PImage criticalIcon;
PImage infoIcon;
PImage editIcon;
PImage optionsIcon;
PImage backupIcon;
PImage yesIcon;
PImage noIcon;
PImage questionIcon;
PImage backupOKIcon;

int PLAIN = 0;
int INFO = 1;
int WARNING = 2;
int CRITICAL = 3;
int YES = 4;
int NO = 5;
int BACKUPOK = 6;

int buttonWidth = 80;
int buttonHeight = 30;
int buttonXOffset = 100;
int buttonYOffset = 50;

int performBackup = 0;
int performRestore = 1;

//////////////////////////

Also I did not answer your questions; sorry.

  • did the error occur when a window was being created?
    LIKELY. There are some error window boxes if data doesn’t load, but since there is no indication of WHY this is happening, or WHEN or HOW, and since I’m not referencing that code at all, it’s a mystery.
  • did the error occur when a window was being closed?
    No, for sure, I hadn’t closed any windows
  • what code were you copying and pasting?
public void null(PApplet applet, GWinData windata) { /* code */ }
  • are you using GUI Builder?
    No.
  • do you know the source code being executed just prior to the error message?
    It seems to be happening when the CRITICAL version of the showMsgBox void is called, but it’s hard to tell for sure. However definitely it’s connected with showing a messagebox. And that message is new.

Feel free to disregard this issue. I totally understand how obtuse and ridiculous it is for me to ask without a clear request and clear code for you to follow.

No worries mate; I appreciate you and your library and I don’t want to burn you out.

mike

EDIT: Found the issue. I was using different draw handlers depending on which icon/message to use, and I had read in java to remove an event handler to set it to null.

But clearly it’s different in processing.

anyway, this was the problem line I think:

msgBox.addDrawHandler(this, null);

I’m not sure how to handle using a different event handler after it’s been assigned, but I’ll see what happens.

Thanks again for your kindness Peter. I never should have posted before spending a bit more time working the issue; it’s been a bit stressful lately so thanks for your grace.

Well done that is the offending line.

In this line you are naming the function that will be the draw method for this window.

IMPORTANT NOTES:

  1. the draw method is not the same as an event handler
  2. every window must have its own draw method
  3. windows cannot share a draw method

To create a new window in G4P we use the GWindow.getWindow(...) method. The type of window depends on the last parameter passed - JAVA2D, P2D or P3D

JAVA2D

  • uses Java AWT (Abstract Window Toolkit)
  • a Java Swing frame (JFrame) is used for the window
  • G4P class GWindowAWT

P2D, P3D

  • based on OpenGL it uses NEWT (Native Event Window Toolkit)
  • an OpenGL window is created
  • G4P class GWindowNEWT

The classes GWindowAWT and GWindowNEWT both inherit from GWindow which allows a common API for all G4P windows through polymorphism.

3 Likes

Fantastic info Peter! Thank you!

To add to your IMPORTANT NOTES -

Can I change a window’s draw handler when I show/hide it? Or that not allowed?

Thank you again, I really appreciate your expertise, and most of all, your precious time.

Mike

Why would you want to do that?

1 Like

Because I’m trying to show different icons for one dialog box depending on state.

Additionally, I’d like to be able to change the event handler on show/hide of visibility.

I’m probably going about all this totally wrong aren’t I…

I believe so… :smile:

If by dialog box you mean the dialog boxes created with the G4P.showMessage method or one of the G4P.select???methods then they use either Swing or OS dialog boxes and this has been covered in this discussion so I have nothing to add.
If on the other hand you are talking about your own dialog boxes created with GWindow then which icons are you talking about?

Which event handler are you talking about? Why do you need a different event handler when the window is invisible? Doesn’t make sense to me.

I’m trying to have an “all purpose” message box that will show various icons whether the message is “info” or “warning” etc. Using the dialog boxes I created, not using Swing or anything else. Just your library only.

I’m not trying to change event handlers and draw handlers when the box is hidden - only when I show it again.

Because, for instance, I create the box with a given event and draw handler set. Then I hide it.

Next time I want to show it again, but this time use a different icon, and different button action.

I’m trying to make it so I don’t have to have 47 different windows to handle the various tasks of the app, and make messaging streamlined.

Anyway, I do appreciate your insights, and I thank you for your time. Truly :slight_smile:

Hi Peter, I’m still getting this blasted concurrent modification error - not when I quit the app - this is solved with the forceclose() you shared (thanks!).

No, I’m getting this randomly on GWindows I’ve created, when I try to close them - GWindows that are message boxes, while the rest of the sketch runs.

I’m using the show/hide method. Would it be OK to use the forceclose() method on these windows? They are messaging windows and will be opened/closed a lot.

I suppose I can just try it, but I thought I’d get your opinion first Peter, if possible.

I can’t seem to recreate it in a simple version, but if I get frustrated enough I’ll spend the couple hours doing it.

In any case, if you have any ideas of a general nature I’m grateful.

Below is the error I’m getting when trying to close one of the GWindows using the .setVisible(false) method.

java.util.ConcurrentModificationException
	at java.base/java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:970)
	at java.base/java.util.LinkedList$ListItr.next(LinkedList.java:892)
	at g4p_controls.GWindowImpl.post(Unknown Source)
	at jdk.internal.reflect.GeneratedMethodAccessor10.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at processing.core.PApplet$RegisteredMethods.handle(PApplet.java:1309)
	at processing.core.PApplet.handleMethods(PApplet.java:1456)
	at processing.core.PApplet.handleDraw(PApplet.java:2119)
	at processing.awt.PSurfaceAWT$9.callDraw(PSurfaceAWT.java:1386)
	at processing.core.PSurfaceNone$AnimationThread.run(PSurfaceNone.java:356)
ConcurrentModificationException

I wouldn’t even care about this method but it totally crashes the sketch dead, and I cant’ seem to trap for it (yet).

thanks again for your thoughts.

Mike

I started to reply to your post on the 5th June, didn’t finish it and it never got posted :cry: not sure why - sorry about that. So I will start with that because it is relevant and then continue on.


=================================================================

Now … I understand what you are trying to do. I should have asked about your overall aim earlier.

A problem like this generally has more than one solution and it invariably pays to stop coding and think through the problem to find the best solution to implement.

Designing a solution for problems like this should start with an overall view which is then broken down into smaller and smaller problems until we can detail the bottom level tasks to be implemented. Initial thoughts…

Using GWindow(s) for dialog boxes
There are only 2 options

  1. Create a separate GWindow for each dialog box at startup then change visibility as and when required.
  2. Create the GWindow dialog box for each dialog box on demand then dispose of it when closed.

If you really have 47 dialogs then option 1 is not very realistic because each GWindow is effectively a separate ‘Processing sketch’ with its own event thread. It is possible to manage large numbers of GWindows - look at the Mandelbrot example but it is demanding of CPU cycles and challenging for the programmer.

In your situation I might go for option (2). Although creating / closing the GWindow is not ideal it should solve the problems of concurrent modification exceptions.

HISTORICAL NOTE:

Processing has always been a single window application - it was never designed to have multiple windows and dialog boxes were created with Java Swing. The reasoning is obvious, managing multiple windows is an advanced technique and Processing was designed for non-programmers. Libraries like controlP5 and G4P were created that provided secondary windows but it is impossible to ignore the fact that these are multiple threaded applications and will have concurrency issues to deal with.

An alternative to GWindows…

Using GPanel(s) for dialog boxes
The GPanel control was in the first version of G4P and is a little used but very useful control. It is an on-screen panel that can

  • be dragged to any position on the sketch window
  • contain other G4P controls
  • be collapsed so only the panel title bar is visible
  • have drag-able and collaspe-able properties can be enabled / disabled
  • have its own event handler (rarely needed)

and like all other can be

  • made invisible / visible
  • enabled / disabled

The advantage is that the GPanel can be created on first use (never used - never created) then hidden/shown when needed.
Like other controls the GPanel is managed on the main sketch thread avoiding all those nasty concurrency issues.

With regard to your last message

It is not safe to add / remove controls whilst the sketch is attempting to draw them or responding to mouse / keyboard events. The post method is executed after the draw() method so can be used to add / remove controls safely. Each window, including the main sketch window has its own post method so there maybe some conflict causing this exception.

3 Likes

Hi Peter, you’ve given me so much to think about! Thank you so much!

I especially loved your pearl of wisdom, to “stop coding and think about the problem!”

This is an absolute gem!

I can see there are 3 paths forward - all paths you’ve provided (thank you!).

Also, I hear that Processing was never designed for multiple windows. So I’m trying to use it against its own philosophy. I get that, and if I can make this work, I’ll continue with processing and your library. If not, I’ll just have to consider using full blown java and swing etc.

But since it seems there are remedies to the issue I’m having, I’d rather solve the problem than rewrite the whole app from scratch.

So, before discussing the 3 solutions you provided, let me share with you what I would ideally like.

IDEALLY, I’d love to have messaging windows, with an icon and sound effect, with either OK/CANCEL, YES/NO, or more complex windows with drop-downs and textfields.

And here’s the “wish list” part: WITH the ability to detect if the user clicked YES or NO - so a variable returned when the dialog is dismissed. This is what Visual Basic has, and I got used to that. It makes it so clean and easy to implement this kind of dialog box in a void, without resorting to putting part of the remaining void code in the dialog box coding. This is how I’ve done it here, and it’s a huge mess spaghetti code - not at all easy to maintain and not ideal. But I didn’t know how to do it, and I should have asked or tried harder to figure it out.

So, with that in mind, and I’m not asking you to solve this all for me or anything, but given the three solutions you shared, two of which are GWindows and one of which is a GPanel, do you think I can implement a variable to capture YES/NO or OK/CANCEL with any of these three, when the buttons are clicked to dismiss the boxes?

If so, would any of the three be easier to implement in that way?

Pending your response to this, and based on what you already shared, I’m leaning towards the second method, of using a GWindow and then disposing of it - mostly because I’ve already created a bunch of them and they are working. Kinda sorta. Not…because of the crashing.

If this is a good way to go, is there a link to how to create and then dispose of GWindows properly? I’m assuming I’d need to create independent windows for each usage? But then how could I call the same dialog over and over again…from my reading of disposing GWindows, it seems they cannot be used again once disposed? So then if the user performs a function and gets GWindow “A”, and dismisses it, what happens if they later do the same function, which should again call GWindow “A”? If I disposed of it when they clicked OK, then I have to make another one, GWindow “B”, to do the same task? If that’s the case, that’s completely untenable. I’d have to make a new GWindow somehow, I suppose with a different name, each time they called the same function? Surely I’m missing something…

In the meantime, I will look at the GPanels. You’ve really created such a thoughtful library; I don’t think people realize all the thought and care you’ve put into it.

Thank you so much for your time; I really can’t thank you enough and I’m so humbly grateful for your wisdom.

Mike

Hi Peter, one more question - how can I find out more about the “post” method?

Thank you again!

Mike

I wouldn’t go down that route. Processing provides a number of ways to execute callbacks at different stages of the sketch life cycle. These were provided for library developers and never intended for general use, although anyone can use them. I can say that G4P hooks into 5 of them - PRE, DRAW, POST, MOUSE & KEY. G4P already uses the post callback to add / remove windows and controls and we still have issues LOL

There are other paths which I have not included but might be better but it very much depends on the fine detail of what you need. You really need to step back from your application and focus completely on dialog boxes.

If we were to follow a different path the first and most important question is should the dialogs be executed asynchronous or synchronous.

All versions of Processing have used Java Swing dialogs and simply provided a number of wrapper methods to call them. Early versions of Processing used synchronous execution (modal) access but from version 2 Processing used the Java Swing dialogs asynchronously. It meant that V1 sketches using these methods failed in V2. It was at this point I included the G4P modal dialogs so I could run my old sketches.

The advantage of synchronous access is no more concurrent errors, the disadvantage is that the sketch stops running until the dialog is done with.

The second question is can I sort my 47 dialog windows into a msaller number of categories based on what functionality is needed e.g. what buttons and / or input controls are needed, whether we want icons /sound effects etc.

2 Likes