Swing works until

I’m using a swing gui to control some variables and if I just insert that into a sketch with nothing except a setup() and the code to get swing gui to launch, I can edit text and use that to update a slider. I can also adjust the slider and the text updates. It gets weird when I add draw() and now the text is no longer editable even though the slider still updates the text. in draw() I’m using the swing adjusted variables to draw on an image.

Any ideas what would cause that?

One complication is that I’m forced to use processing-2.2.1 because processing-3.5.3 no longer works on my chromebook. here’s the stripped down gui code:


//import java.awt.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.text.DecimalFormat;

MyPanel controlPanel;
public String text;


double GHA = Math.toRadians(-51.16667f); //(-128.83333f);
double GHA1 = Math.toRadians(51.16667f);

//double alt = Math.toRadians(62.6f);

int timer=0;

boolean Star1;



public class MyPanel extends JPanel {
    private TextDemo jcomp1;
    private JLabel jcomp2;
    private JButton jcomp3;

    //private JButton jcomp9;
    private JButton jcomp4;
    private JSlider jcomp5;


    public MyPanel() {
        //construct components
        jcomp1 = new TextDemo(); //JTextArea (1, 16);
        jcomp2 = new JLabel ("GHA : DDD.MMSS");
        jcomp3 = new JButton ("GHA");
        jcomp4 = new JButton("Star1");
        jcomp5 = new JSlider(0,360,(int)(Math.toDegrees(-GHA)));  //,(int)Math.toDegrees(GHA));

        jcomp3.addActionListener(new Button1Click());
        jcomp4.addActionListener(new Button2Click());
        jcomp5.addChangeListener(new HSlider1Change());
        
        jcomp5.setOrientation (JSlider.HORIZONTAL);
        jcomp5.setMinorTickSpacing (10);
        jcomp5.setMajorTickSpacing (60);
        jcomp5.setPaintTicks (true);
        jcomp5.setPaintLabels (true);
               
        //adjust size and set layout
        setPreferredSize (new Dimension (544, 756));
        setLayout (null);

        //add components
        add (jcomp1);
        add (jcomp2);
        add (jcomp3);
        add (jcomp4);
        add (jcomp5);


        //set component bounds (only needed by Absolute Positioning)
        jcomp1.setBounds (70, 35, 205, 25);
        jcomp2.setBounds (285, 35, 175, 25);
        jcomp3.setBounds (70, 80, 100, 25);
        jcomp4.setBounds (70,320,100,25);
        jcomp5.setBounds(70,420,400,50);
        
 }
 
    
class Button1Click implements ActionListener
{
  public void actionPerformed(ActionEvent e)
  {
    JButton b = (JButton)e.getSource();
    GHA = Double.parseDouble(text);
            jcomp5.setValue((int)Double.parseDouble(text));
    jcomp1.textField.setText(text);

    println("GHA: " + GHA);
    timer=millis()+1000;

  }
}


class Button2Click implements ActionListener
{
  public void actionPerformed(ActionEvent e)
  {
    JButton b = (JButton)e.getSource();

    GHA1 = GHA;
    println("GHA1: " + GHA1); 
    //  dec1 = dec;
    //  alt1 = alt;
    // Globe = true;
    //  Map = false;
    Star1 = true;
    timer=millis()+1000;
  }
}


class HSlider1Change implements ChangeListener
{
          
  public void stateChanged(ChangeEvent e)
  {
    JSlider source = (JSlider)e.getSource();
     if (source.getValue() < 180){
        GHA = radians( (- source.getValue()));
        jcomp1.textField.setText(Double.toString(Math.toDegrees(-GHA))); 
     }
     if (source.getValue() > 180) {
        GHA = radians((360 - source.getValue())); 
        jcomp1.textField.setText(Double.toString(360-(Math.toDegrees(GHA))));
     }
     timer=millis()+1000; 
 
     println("slider: " + source.getValue() + " GHA: " + Math.toDegrees(GHA));
  }
}


}


void setup()
{

  JFrame frame =new JFrame("Controls");
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  controlPanel = new MyPanel();
  controlPanel.setOpaque(true); //content panes must be opaque
  frame.setContentPane(controlPanel);
//frame.add(new TextDemo());
  //Display the window.
  frame.pack();
  frame.setVisible(true);
}




public class TextDemo extends JPanel implements ActionListener {
    protected JTextField textField;
    protected JTextArea textArea;
    private final static String newline = "\n";
 
    public TextDemo() {

        textField = new JTextField(20);
        textField.addActionListener(this);
        textField.setHorizontalAlignment(textField.CENTER);
        textArea = new JTextArea(2, 20);
        textArea.setEditable(false);

        add(textField);
        add(textArea);
    }
 
    public void actionPerformed(ActionEvent evt) {

        text = textField.getText();
        textArea.append(text + newline);
        textField.selectAll();
        GHA = Double.parseDouble(text);
        textField.setText(Double.toString(GHA));
        
        
        println(GHA);
 
        //Make sure the new text is visible, even if there
        //was a selection in the text area.

    }
}

Swing isn’t thread safe, and all Swing code has to be done on the Swing event thread, not Processing’s animation thread. Doing anything with Swing on other threads is likely to cause strange bugs quite quickly.

2 Likes

Strange bugs seems to be what I’m getting. Thanks for the reply. I had some success with Swing way back when processing-3.5.3 was still working for me.

1 Like

I did the obvious test by trying it on a computer that can run processing-3.5.3 and it works fine. Thanks again, I’ll avoid Swing in processing. I know there are alternatives like in Quark’s shapes3d library.

1 Like

You need to know which thread is acting on each piece of code. Tricky and not fun.
Anyway calling processing functions from Swing and vice-versa is not likely to go too well.
Nevertheless it is quite intriguing to use AWT/Swing with processing. You just need to figure out safe ways to pass data between the 2.

2 Likes

One way is quite easy with EventQueue::invokeLater. Unfortunately, Processing doesn’t have the same facility built in. You can set up a ConcurrentLinkedQueue<Runnable> and drain it somewhere in draw() to replicate.

If you keep the shared data thread safe using synchronized and volatile, processing can read the totality of shared data every draw cycle.
Swing knows the shared data it changed itself but needs to be told if processing has changed the shared data via EventQueue.invokeLater(…).
I presume something like this is okay, however my goal is only to get (back) to a reasonable Java version 1.2 level as a hobby and go no further.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
Frame frame;

void setup() {
  size(200, 100);
  fill(255);
  frame=new Frame();
}

void draw() {
  background(0);
  frame.setProcessingCount(frameCount);
  EventQueue.invokeLater(frame); // Tell Swing to look at new Processing value
  text("Swing Count: "+frame.getSwingCount(), 10, 20); // Look at Swing value
}


class Frame extends JFrame implements Runnable,ActionListener {
  // Variables shared between Processing and Swing.
  // Since Frame is an inner class the variables could also be placed
  // in the processing code
  volatile int processingCount;
  volatile int swingCount;
  //
  JPanel pan=new JPanel();
  JLabel lbA=new JLabel("Processing Frame Count: ");
  JLabel lbB=new JLabel("*******");
  JButton btA=new JButton("Swing Count");
  Frame() {
    btA.addActionListener(this);
    pan.add(lbA);
    pan.add(lbB);
    pan.add(btA);
    add(pan);
    pack();
    setVisible(true);
  }

  void run() { // Display Processing's count value
    lbB.setText(Integer.toString(getProcessingCount()));
  }
  
   public void actionPerformed(ActionEvent e) {
      int ct=getSwingCount();
      setSwingCount(ct+1);
    }

  // Only allow a single thread access to shared variables using synchronized.
  synchronized void setProcessingCount(int count) {
    processingCount=count;
  }
  synchronized int getProcessingCount() {
    return processingCount;
  }
  synchronized void setSwingCount(int count) {
    swingCount=count;
  }
  synchronized int getSwingCount() {
    return swingCount;
  }
}

1 Like

You only need one or the other. If all accesses are from synchronized methods you don’t need volatile.

You’re still better posting tasks back and forth, but this is probably OK. It doesn’t scale well!

I doubt any of it is actually safe, now that I think of it. Very likely Processing and Swing share the same ATW background threads and AWT state. A mixture that might interact badly.
You could get some insight printing out Thread.currentThread() in various places.
I’ll move over to Dr.Java for my retro-Java GUI experiments, while still using Processing for the other aspects.

I think you are right, you don’t need volatile with data surrounded by a synchronized. Also volatile 32 bit or smaller primitives don’t need to be surrounded by synchronized, but for anything larger volatile on its own isn’t enough. I’ll read up on it again.

“Writes and reads of volatile long and double values are always atomic.” - see https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7

1 Like

All the replies are over my head although I get the impression they are very relevant to what I am seeing. If only I could incorporate them. What I tried today was to export to application.windows32 and since I’m using 2.2.1, all I got was a lib folder and some XML that I can’t figure out how to launch. When I tried to run it on windows10 with 3.3.5, it was completely screwed up. The application.linux64 runs fine.

I’m playing again. Thanks for the example, I’m about to find out what it really means. I’ve added some of what I was trying to do with the code I posted and it hasn’t choked yet. Thanks.

Somewhere up there was a comment about “scaling” and I believe I’m starting to see that this mix doesn’t scale well.

Despite the scaling difficulty, I keep trying.

I just tried something crazy. I have multiple frames, but right now each frame is identical. The result is good though because I was able to manipulate the slider and text in each frame and get separation(not sure what to call it). The original problem I had was I’d change a slider and hit a button and it would affect values I had previously set with other slider and button. I’ll keep playing with it.