Swing Components in Default Processing Window_py5

To use Swing components in a default Processing window in py5 it is necessary to remove part or all of the canvas. By dropping the canvas down a little bit it is possible to use the top portion for Swing components and the bottom portion for conventional drawing using draw(). Please note that this demo was created in a Thonny editor with a py5 plugin. The venerable DotView is shown below using the imported mode of py5:

# Use Imported mode for py5

from javax.swing import *
from java.awt import *

_wndW = 600
_wndH = 660

myColor = Color.GREEN

def myBtnAction(event):
    global myColor
    panel = JPanel()
    myColor = JColorChooser.showDialog(panel,"Choose color",Color.GREEN)
  
def setup():
    global frame
    global slider
    
    size(_wndW, _wndH)
    wnd = get_surface()    
    canvas = wnd.get_native()
    frame = canvas.getFrame()
    wnd.set_title('Default Processing Window')
    canvas.setBounds(0,60,_wndW,_wndH - 60)
          
    slider = JSlider(JSlider.HORIZONTAL, 0, 500, 100)
    slider.setBounds(180, 20, 200, 24)
    slider.setToolTipText("size");
    frame.add(slider)
    slider.repaint()
    
    btn = JButton("Color")
    btn.setBounds(400,20,80,24)
    frame.add(btn)
    btn.repaint()
    btn.addActionListener(myBtnAction)

def draw():
    background(209)
    fill(myColor.getRed(), myColor.getGreen(), myColor.getBlue())
    circle(300,300,slider.getValue())

Output:

1 Like

Converted your py5 Swing demo to “imported mode”, which can now be run outside Thonny:

import py5

from java.awt import Color
from javax.swing import JSlider, JButton, JColorChooser

fill = Color.GREEN

def setup():
    py5.size(600, 660)
    py5.fill(fill.getRGB())

    global cx, cy, slider
    cx, cy = py5.width >> 1, py5.height >> 1

    slider = JSlider(JSlider.HORIZONTAL, 0, 500, 100)
    slider.setBounds(180, 20, 200, 24)
    slider.setToolTipText('circle diameter')

    btn = JButton('Color')
    btn.setBounds(400, 20, 80, 24)
    btn.addActionListener(button_action)

    window = py5.get_surface()
    window.set_title('py5 Swing Demo')

    canvas = window.get_native()
    canvas.setBounds(0, 60, py5.width, py5.height - 60)

    frame = canvas.getFrame()
    frame.add(slider)
    frame.add(btn)

    slider.repaint()
    btn.repaint()


def draw():
    py5.background(0o320)
    py5.circle(cx, cy, slider.getValue())


def button_action(_, TITLE = 'Choose color:'):
    global fill
    fill = JColorChooser.showDialog(None, TITLE, fill) or fill
    py5.fill(fill.getRGB())


__name__ == '__main__' and py5.run_sketch()
1 Like

A more Java-ish version using this = py5._instance, so it’ more in line w/ both Java & Processing’s API lowerCamelCase naming convention: :coffee:

"""
 * py5 Swing Demo (v1.0.0)
 * GoToLoop & Svan (2024/May/25)
 *
 * https://Discourse.Processing.org/t/
 * swing-components-in-default-processing-window-py5/3
"""

import py5

from java.awt.Color import GREEN
from javax.swing import JSlider, JLabel, JButton, JColorChooser

DIAM = 'Diameter: %d'

this = py5._instance
fill = GREEN

def settings(): this.size(600, 660)

def setup():
    this.noLoop()
    this.fill(fill.getRGB())

    global cx, cy, slider, label

    w, h = this.width, this.height
    cx, cy = w >> 1, h >> 1

    slider = JSlider(0, w - 60, cx)
    slider.setBounds(180, 20, 200, 24)
    slider.addChangeListener(slideChange)
    slider.setToolTipText('circle diameter')

    (label := JLabel(DIAM % cx)).setBounds(75, 20, 80, 24)

    btn = JButton('Color')
    btn.setBounds(400, 20, 80, 24)
    btn.addActionListener(btnAction)

    (window := this.getSurface()).setTitle('py5 Swing Demo')
    (canvas := window.getNative()).setBounds(0, 60, w, h - 60)

    frame = canvas.getFrame()
    frame.add(slider); frame.add(label); frame.add(btn)
    slider.repaint(); label.repaint(); btn.repaint()


def draw():
    this.background(0o320)
    this.circle(cx, cy, slider.getValue())


def mouse_wheel(e): slider.setValue(slider.getValue() - e.get_count() * 10)

def slideChange(_):
    label.setText(DIAM % slider.getValue())
    this.redraw()


def btnAction(_, TITLE = 'Choose color:'):
    global fill
    fill = JColorChooser.showDialog(None, TITLE, fill) or fill

    this.fill(fill.getRGB())
    this.redraw()


__name__ == '__main__' and py5.run_sketch()
1 Like

It was already written using “Imported mode for py5” using the Thonny editor with plugin. By adding “import py5” at the top you converted it to “Module Mode” if I understand correctly. Reference is here: https://py5coding.org/content/py5_modes.html for the sake of others viewing this thread. All those ‘modes’ are confusing to me, but it appears that by using the plugin and imported mode the authors were trying to make it easier for rookies like me. Personally I don’t like having to add the py5. prefix to everything and your adaptation using this. is more like what I am accustomed to using. With the addition of JavaFX there should now be controls for py5 users to take advantage of. I’m assuming that you ran this code in the Windows environment, which is good if you did because it demonstrates that it will run across platforms. Should run in Linux too (confirmed by another user). Thanks for taking the time to look at it.

1 Like

Yup, inside “cmd.exe” command prompt. But it should work on any terminal from any OS where both Java & Python are installed.

BtW, we can have multiple instances of the same Python file running at the same time.

Tip: Use pythonw sketch_name.py or pyw sketch_name.py to run a Python file w/o blocking the terminal’s command prompt.

Unfortunately I couldn’t make py5 run your other JavaFX demo. It throws:

java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
at com.sun.javafx.tk.quantum.QuantumRenderer.getInstance(QuantumRenderer.java:283)
at javafx.scene.Node.(Node.java:417)

I’ve copied all the “.jar” files into subfolder “jars/” btW.

1 Like

Is javafx.jar in there with all the other javafx.xxxxx.jar files as shown below. I’m still trying to figure out the importance of those lib files.

I really would like to have that run on a Windows box. I have confirmation that it runs on Linux.

I ran that demo in the Thonny editor with py5 plugin added and ‘Import mode for py5’ on the menubar checked. We have not been able to run it from the cmd-line yet. I don’t know how to include the javafx jar files with python3 … filename.py

./jars
├── javafx.base.jar
├── javafx.controls.jar
├── javafx.fxml.jar
├── javafx.graphics.jar
├── javafx.jar
├── javafx.media.jar
├── javafx.swing.jar
├── javafx.web.jar

1 Like

Java Mode version sketch for Processing 3 & 4: :coffee:

/**
 * Processing Java Mode Swing Demo (v1.0.2)
 * GoToLoop (2024/Jun/03)
 *
 * https://Discourse.Processing.org/t/
 * swing-components-in-default-processing-window-py5/9
 */

import java.awt.Color;
import java.awt.Frame;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;

import javax.swing.JSlider;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JColorChooser;

import processing.awt.PSurfaceAWT.SmoothCanvas;

static final String TITLE = "Choose color:", DIAM = "Diameter: ";

Color fill = Color.GREEN;

JSlider slider;
JLabel label;

int cx, cy;

void settings() {
  size(600, 660);
  noLoop();
}

void setup() {
  fill(fill.getRGB());

  cx = width  >> 1;
  cy = height >> 1;

  slider = new JSlider(0, width - 60, cx);
  slider.setBounds(180, 20, 200, 24);
  slider.addChangeListener(slideChange);
  slider.setToolTipText("circle diameter");

  (label = new JLabel(DIAM + cx)).setBounds(75, 20, 80, 24);

  final JButton btn = new JButton("Color");
  btn.setBounds(400, 20, 80, 24);
  btn.addActionListener(btnAction);

  final PSurface window = getSurface();
  window.setTitle("Processing Java Mode Swing Demo");

  final SmoothCanvas canvas = (SmoothCanvas)window.getNative();
  canvas.setBounds(0, 60, width, height - 60);

  final Frame frame = canvas.getFrame();
  frame.add(slider);
  frame.add(label);
  frame.add(btn);

  slider.repaint();
  label.repaint();
  btn.repaint();
}

void draw() {
  background(0320);
  circle(cx, cy, slider.getValue());
}

void mouseWheel(final MouseEvent e) {
  slider.setValue(slider.getValue() - e.getCount() * 10);
}

final ChangeListener slideChange = new ChangeListener() {
  final void stateChanged(final ChangeEvent __) {
    label.setText(DIAM + slider.getValue());
    redraw();
  }
};

final ActionListener btnAction = new ActionListener() {
  final void actionPerformed(final ActionEvent __) {
    final Color chosenColor = JColorChooser.showDialog(null, TITLE, fill);
    if (chosenColor == null) return;

    fill((fill = chosenColor).getRGB());
    redraw();
  }
};

P.S.: The sketch can be refactored to be less verbose for Processing 4.

1 Like

And for completeness’ sake, also a Python Mode (Jython) for Processing 3: :snake:

"""
 * Processing Python Mode Swing Demo (v1.0.2)
 * GoToLoop (2024/Jun/03)
 *
 * https://Discourse.Processing.org/t/
 * swing-components-in-default-processing-window-py5/10
"""

from java.awt.Color import GREEN
from javax.swing import JSlider, JLabel, JButton, JColorChooser

DIAM = 'Diameter: %d'
c = GREEN

def setup():
    size(600, 660)
    noLoop()
    fill(c.RGB)

    global cx, cy, slider, label

    w, h = width, height
    cx, cy = w >> 1, h >> 1

    slider = JSlider(0, w - 60, cx, bounds = (180, 20, 200, 24))
    slider.addChangeListener(slideChange)
    slider.toolTipText = 'circle diameter'

    label = JLabel(DIAM % cx, bounds = (75, 20, 80, 24))

    btn = JButton('Color', bounds = (400, 20, 80, 24))
    btn.addActionListener(btnAction)

    window = this.surface
    window.title = 'Processing Python Mode Swing Demo'

    canvas = window.native
    canvas.bounds = 0, 60, w, h - 60

    frame = canvas.frame
    frame.add(slider); frame.add(label); frame.add(btn)
    slider.repaint(); label.repaint(); btn.repaint()


def draw(): background(0320); circle(cx, cy, slider.value)

def mouseWheel(e): slider.value -= e.count * 10

def slideChange(_): label.text = DIAM % slider.value; redraw()

def btnAction(_, TITLE = 'Choose color:'):
    global c
    c = JColorChooser.showDialog(None, TITLE, c) or c
    fill(c.RGB); redraw()
1 Like

For those not interested in the py5 Python version and no regard for backward compatibility, the following is my Processing 4 version of DotView:

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

javax.swing.JFrame frame;
java.awt.Canvas canvas;

int diameter = 150;
Color dotColor = Color.GREEN;

int _wndW = 700;
int _wndH = 600;

void colorBtn(int x, int y, int w, int h) {
  JButton btn = new JButton("Color");
  btn.setBounds(x, y, w, h);
  frame.add(btn);
  btn.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent actionEvent) {
      dotColor = JColorChooser.showDialog(null, "Choose color", Color.GREEN);
    }
  }
  );
}

void sizeSlider(int x, int y, int w, int h) {
  // orientation, min, max, value
  JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 450, 150);
  slider.setBounds(x, y, w, h);
  frame.add(slider);
  slider.addChangeListener(new ChangeListener() {
    void stateChanged(ChangeEvent changeEvent) {
      diameter = slider.getValue();
    }
  }
  );
}

void buildWnd() {
  sizeSlider(180, 20, 200, 24);
  colorBtn(390, 20, 80, 24);
  frame.setVisible(true);
}

void setup() {
  size(_wndW, _wndH); // Makes it possible to use draw();
  frame = (javax.swing.JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame();
  canvas = (processing.awt.PSurfaceAWT.SmoothCanvas) ((processing.awt.PSurfaceAWT)surface).getNative();
  frame.setBounds(900, 300, _wndW, _wndH); // Makes it possible to add swing components
  frame.setLayout(null);
  canvas.setBounds(0, 60, _wndW, _wndH-60); // For use with draw()
  javax.swing.SwingUtilities.invokeLater(()-> {
      buildWnd(); // Builds components on EventDispatchThread
  }
  );
}

void draw() {
  background(209);
  fill(dotColor.getRed(),dotColor.getGreen(),dotColor.getBlue());
  stroke(0);
  strokeWeight(2.0);
  circle(_wndW/2, _wndH/2 - 50, diameter);
}

Addendum:
I note that you did not use a separate EventDispatchThread in your demo. There’s only two components so it might not be an issue. I have no idea what behaviour is seen if it is an issue.

Made the minimum required changes to your latest code so it runs on Processing 3 as well: :wink:

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

javax.swing.JFrame frame;
java.awt.Canvas canvas;

int diameter = 150;
Color dotColor = Color.GREEN;

void colorBtn(int x, int y, int w, int h) {
  JButton btn = new JButton("Color");
  btn.setBounds(x, y, w, h);
  frame.add(btn);
  btn.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent actionEvent) {
      dotColor = JColorChooser.showDialog(null, "Choose color", Color.GREEN);
    }
  }
  );
}

void sizeSlider(int x, int y, int w, int h) {
  // orientation, min, max, value
  final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 450, 150);
  slider.setBounds(x, y, w, h);
  frame.add(slider);
  println(frameCount);
  slider.addChangeListener(new ChangeListener() {
    public void stateChanged(ChangeEvent changeEvent) {
      diameter = slider.getValue();
    }
  }
  );
}

void buildWnd() {
  sizeSlider(180, 20, 200, 24);
  colorBtn(390, 20, 80, 24);
  frame.setVisible(true);
}

void setup() {
  size(700, 600); // Makes it possible to use draw();
  frame = (javax.swing.JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame();
  canvas = (processing.awt.PSurfaceAWT.SmoothCanvas) ((processing.awt.PSurfaceAWT)surface).getNative();
  frame.setBounds(900, 300, width, height); // Makes it possible to add swing components
  frame.setLayout(null);
  canvas.setBounds(0, 60, width, height-60); // For use with draw()
  javax.swing.SwingUtilities.invokeLater(new Runnable() {
    public void run() {
      buildWnd(); // Builds components on EventDispatchThread
    }
  }
  );
}

void draw() {
  background(209);
  fill(dotColor.getRed(), dotColor.getGreen(), dotColor.getBlue());
  stroke(0);
  strokeWeight(2.0);
  circle(width/2, height/2 - 50, diameter);
}

Basically replaced the lambda -> thin arrow syntax w/ a new Runnable callback.

Also replaced _wndW & _wndH w/ width & height respectively, so no settings() callback is required for size().

And lastly, added keywords public & final where they were required for the listeners’ callbacks.

1 Like

Do you mean no callback for SwingUtilities.invokeLater()?
Well, you haven’t added 1 for your original py5 sketch either!
And all of my sketches here are based on your 1st py5 sketch!

Although it’s indeed highly recommended to use invokeLater() to do anything related to Swing components, sketches that directly create and add simple components within setup() had no issues since Processing 1!

A Processing sketch by default has only 2 active threads:
The “Animation Thread” & the “AWT-EventQueue-0” (EDT) for the JAVA2D renderer.

Code running within setup() and on is already under the “Animation Thread” btW.

The reason why code running under the “Animation Thread” can manipulate AWT/Swing GUI w/o any repercussions is b/c there’s no other thread doing the same at the same time.

The “Animation Thread” only deals w/ the Swing JFrame when it updates its canvas after draw() is done.

So any code running under the “Animation Thread” is guaranteed not to mutate the GUI while Processing is busy repainting/updating its canvas.

1 Like

Your second demo was a conversion of a py5 demo to a Processing app, and I use a separate EventDispatchThread in Processing apps at the recommendation of others in this forum:
https://discourse.processing.org/t/swing-components-in-default-processingwindow/35483
However, I have not been using a separate thread for Swing components in py5 simply because it has never caused any problems as far as I’m aware and I am unable to find any good references on how to add it in Python. Py5 appears to have insulated us from this problem, but I am not aware of how this has been accomplished under the hood; I have not dissected the source code. The issue has recently come up in their forum with regard to using Swing components without using py5 and here a separate thread seems to be necessary also. Furthermore, source code posted in JPype documentation which deals with Swing components and a separate EventDispatchThread won’t run on a Mac for some reason and as far I know there is no published fix for Macs: https://jpype.readthedocs.io/en/latest/userguide.html#awt-swing. It’s in the AWT/Swing section. I would love for somebody to show me how it’s done or find it published somewhere, but I can’t find it. The only reason I use it in Processing is because I was cautioned when I posted the first demo here some time back (referenced above), and it seems pretty well accepted that Swing is not ‘thread safe’, whatever that entails. I respect the experiences of others. When I use Swing components in Processing I frequently remove the canvas, so there is no draw() available to use. Perhaps it’s still being used by the runtime, I don’t know about that. DotView, recently published, preserves part of the canvas so draw() is functional in this instance.

AFAIK, there’s nothing in py5 or JPype that would make Java threads any safer!

If you feel the need to use SwingUtilities.invokeLater() in Processing’s Java Mode, you should also take the same precautions on py5 too!

“py5 Swing Demo II” is using SwingUtilities.invokeLater() now:

"""
 * py5 Swing Demo II (v1.0.0)
 * GoToLoop & Svan (2024/Jun/04)
 *
 * https://Discourse.Processing.org/t/
 * swing-components-in-default-processing-window-py5/15
"""

import py5

from java.awt.Color import GREEN
from javax.swing import JSlider, JLabel, JButton, JColorChooser, SwingUtilities

DIAM = 'Diameter: %d'
USE_INVOKE_LATER = True

this = py5._instance
fill = GREEN

def settings(): this.size(600, 660)

def setup():
    this.noLoop()
    this.fill(fill.getRGB())

    global w, h, cx, cy
    w, h = this.width, this.height
    cx, cy = w >> 1, h >> 1

    if USE_INVOKE_LATER: # delegate GUI creation to run on the EDT thread
        SwingUtilities.invokeLater(createGUI)
        while 'slider' not in globals(): this.delay(100)

    else: createGUI() # keep Swing GUI creation on the Animation thread


def draw():
    this.background(0o320)
    this.circle(cx, cy, slider.getValue())


def mouse_wheel(e): slider.setValue(slider.getValue() - e.get_count() * 10)

def createGUI():
    global slider, label

    slider = JSlider(0, w - 60, cx)
    slider.setBounds(180, 20, 200, 24)
    slider.addChangeListener(slideChange)
    slider.setToolTipText('circle diameter')

    (label := JLabel(DIAM % cx)).setBounds(75, 20, 80, 24)

    btn = JButton('Color')
    btn.setBounds(400, 20, 80, 24)
    btn.addActionListener(btnAction)

    (window := this.getSurface()).setTitle('py5 Swing Demo')
    (canvas := window.getNative()).setBounds(0, 60, w, h - 60)

    frame = canvas.getFrame()
    frame.add(slider); frame.add(label); frame.add(btn)
    slider.repaint(); label.repaint(); btn.repaint()


def slideChange(_):
    label.setText(DIAM % slider.getValue())
    this.redraw()


def btnAction(_, TITLE = 'Choose color:'):
    global fill
    fill = JColorChooser.showDialog(None, TITLE, fill) or fill

    this.fill(fill.getRGB())
    this.redraw()


__name__ == '__main__' and py5.run_sketch()

This new version is exactly the same as previous “py5 Swing Demo”, but w/ a constant USE_INVOKE_LATER, which we can toggle True or False before running the code.

When USE_INVOKE_LATER is False, it behaves exactly like its previous original version btW; using Processing’s Animation thread to build its Swing GUI components.

Also notice I had to pause the sketch execution inside setup() by invoking delay() inside a while loop, awaiting for createGUI() under EDT thread, so draw() doesn’t start running until global variable slider exists:

    if USE_INVOKE_LATER: # delegate GUI creation to run on the EDT thread
        SwingUtilities.invokeLater(createGUI)
        while 'slider' not in globals(): this.delay(100)

As you can notice, we can pass any Python function as an argument for SwingUtilities.invokeLater(), just like other listener methods such as addChangeListener() or addActionListener().

2 Likes

As I’ve mentioned before as well, the safest way to deal w/ Swing components is by delegating such code to its EDT thread.

However, for simple GUI building, Processing has a long history of being able to do that under its Animation thread in setup() w/o any issues.

We can also increase the odds even more by moving all the add() method calls to the very end of the GUI building process:

frame.add(slider); frame.add(label); frame.add(btn)
slider.repaint(); label.repaint(); btn.repaint()

This way, the chances of a user starting using those components too early before everything is fully in place is very slim.

Whether going safer w/ invokeLater() or taking the risk of keeping everything under the Animation thread in setup() is a personal decision.

But whatever path we take, apply it equally both on Processing’s Java Mode & py5/JPype versions.

1 Like

Thanks for the code on SwingUtilities; it’s a lot easier than I ever thought it would be, but that’s the ‘Python way’. Your code runs ok in Thonny. What’s up with using underscores in the actionEvents? I have seen myBtnAction(event) used, but not an underscore. The following demo is a modification of my original post to use an EventDispatchThread; I prefer to use ‘Import mode for py5’ to avoid having to use prefixes. It’s more like regular Processing code that way.

# Uses Imported mode for py5

from javax.swing import *
from java.awt import *

_wndW = 600
_wndH = 660

myColor = Color.GREEN
slider = JSlider()

def myBtnAction(event):
    global myColor
    panel = JPanel()
    myColor = JColorChooser.showDialog(panel,"Choose color",Color.GREEN)

def buildWnd():
    global slider
    slider = JSlider(JSlider.HORIZONTAL, 0, 500, 100)
    slider.setBounds(180, 20, 200, 24)
    slider.setToolTipText("size");
    frame.add(slider)
    slider.repaint()
    
    btn = JButton("Color")
    btn.setBounds(400,20,80,24)
    frame.add(btn)
    btn.repaint()
    btn.addActionListener(myBtnAction)
    
def setup():
    global frame
    size(_wndW, _wndH)
    wnd = get_surface()    
    canvas = wnd.get_native()
    frame = canvas.getFrame()
    wnd.set_title('Default Processing Window')
    canvas.setBounds(0,60,_wndW,_wndH - 60)
    SwingUtilities.invokeLater(buildWnd)

def draw():
    background(209)
    fill(myColor.getRed(), myColor.getGreen(), myColor.getBlue())
    circle(300,300,slider.getValue())

While we’re on the subject of EDT for Swing components, do you have any insight as to why this doesn’t run? Silently fails; no error messages in Thonny. There is no py5.

import jpype
import jpype.imports

jpype.startJVM()
import java
import javax
from javax.swing import *

def buildWnd():
    print("Got to here.")
    frame = JFrame("Hello World Swing")
    print("Never gets to here.")
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
    frame.setBounds(100,100,400,400)
    frame.setVisible(True)
    print("Done.")
    
SwingUtilities.invokeLater(buildWnd)    

Addendum:
I think it has to have two threads: a main one and EDT for Swing (but I still don’t see a window):

import jpype
import jpype.imports

jpype.startJVM()

import javax
from javax.swing import *

def buildWnd():
    print("buildWnd called.")
    
def setup():
    print("Got to here.")
    frame = javax.swing.JFrame("Swing Demo")
    frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE)
    frame.setBounds(100,100,400,400)
    frame.setVisible(True)
    print("frame =",frame)
    print("Done.")
    
    javax.swing.SwingUtilities.invokeLater(buildWnd)

if __name__ == '__main__':
    setup()

Console output:

Got to here.
buildWnd called.
frame = javax.swing.JFrame[frame0,100,100,400x400,layout=java.awt.BorderLayout,title=Swing Demo,resizable,normal,defaultCloseOperation=EXIT_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,28,400x372,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
Done.

New Console Output: EDT called later after indenting SwingUtilities call

Got to here.
frame = javax.swing.JFrame[frame0,100,100,400x400,layout=java.awt.BorderLayout,title=Swing Demo,resizable,normal,defaultCloseOperation=EXIT_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,28,400x372,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
Done.
>>> buildWnd called.

Addendum 2:
AppHelper is your friend. This runs on a Mac in Thonny editor. @GoToLoop Can you run this on your Windows system?

import jpype
import jpype.imports

jpype.startJVM()

import java
import javax
from javax.swing import *
from PyObjCTools import AppHelper

def buildWnd():
    print("buildWnd called.")
    print("frame in buildWnd =",frame)
    btn = javax.swing.JButton("Button")
    btn.setBounds(30,60,100,24)
    frame.add(btn)
    frame.setVisible(True)
    print("btn =",btn)
    print("EDT =",javax.swing.SwingUtilities.isEventDispatchThread())
    print("Done in buildWnd.")
    
def main():
    global frame

    frame = javax.swing.JFrame("Swing EDT")
    frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE)
    frame.setBounds(100,100,400,400)
    frame.setLayout(None)

    javax.swing.SwingUtilities.invokeLater(buildWnd)
    AppHelper.runEventLoop()

main()

Output:

Console output:

buildWnd called.
frame in buildWnd = 2024-06-05 21:42:40.868 Python[40240:1751568] WARNING: Secure coding is not enabled for restorable state! Enable secure coding by implementing NSApplicationDelegate.applicationSupportsSecureRestorableState: and returning YES.
javax.swing.JFrame[frame0,100,100,400x400,invalid,hidden,layout=java.awt.BorderLayout,title=Swing EDT,resizable,normal,defaultCloseOperation=EXIT_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
btn = javax.swing.JButton[,30,60,100x24,alignmentX=0.0,alignmentY=0.5,border=com.apple.laf.AquaButtonBorder$Dynamic@457e5465,flags=288,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIResource[top=0,left=2,bottom=0,right=2],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=false,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=Button,defaultCapable=true]
EDT = True
Done in buildWnd.

I don’t have package PyObjCTools installed. Also, it seems it’s MacOS-only! :apple:

I’ve replaced your AppHelper.runEventLoop() w/ a while True: loop plus time.sleep(), so it can run on Windows, even though the code works OK w/o the infinite loop: :window:

import jpype
import jpype.imports

from time import sleep

jpype.startJVM()

from javax.swing import JFrame, JButton, SwingUtilities

def buildWnd():
    btn = JButton("Button")
    btn.setBounds(30, 60, 100, 24)
    btn.addActionListener(lambda evt: print(evt, '\n'))

    frame.add(btn)
    frame.setVisible(True)

    print("JButton:", btn)
    print("EDT:", SwingUtilities.isEventDispatchThread(), '\n')


def main():
    global frame

    frame = JFrame("Swing EDT")
    frame.setBounds(100, 100, 400, 400)
    frame.setLayout(None)
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)

    SwingUtilities.invokeLater(buildWnd)


if __name__ == '__main__':
    main()

    while True: # AppHelper.runEventLoop() replacement.
        try: sleep(1) # Sleep to avoid burning the CPU!
        except KeyboardInterrupt: break # Exit loop on Ctrl+C.
1 Like

Thanks for trying; I should have known it wouldn’t work cross platform. The following should also run on Windows in addition to what you posted; it came from the JPype website but won’t run on a Mac.

import jpype
import jpype.imports

jpype.startJVM()
import java
import javax
from javax.swing import *

def createAndShowGUI():
    frame = JFrame("HelloWorldSwing")
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
    frame.setBounds(100, 100, 400, 400)
    label = JLabel("Hello World")
    frame.getContentPane().add(label)
    frame.pack()
    frame.setVisible(True)

# Start an event loop thread to handling gui events
@jpype.JImplements(java.lang.Runnable)
class Launch:
    @jpype.JOverride
    def run(self):
        createAndShowGUI()
javax.swing.SwingUtilities.invokeLater(Launch())

As noted above, on Windows it can be run with just the EventDispatchThread:

import jpype
import jpype.imports

jpype.startJVM()
import java
import javax
from javax.swing import *

def createAndShowGUI():
    frame = JFrame("HelloWorldSwing")
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
    frame.setBounds(100, 100, 400, 400)
    label = JLabel("Hello World")
    frame.getContentPane().add(label)
    frame.pack()
    frame.setVisible(True)

javax.swing.SwingUtilities.invokeLater(createAndShowGUI)

@GoToLoop Will this make it cross-platform? I think Mac would have to use PyObjc plugin for Thonny to get Cocoa framework; not sure.

import jpype
import jpype.imports

jpype.startJVM()
import java
import javax
import platform
from javax.swing import *
if(platform.system() == "Darwin"):
    from Cocoa import NSApp

def createAndShowGUI():
    frame = JFrame("HelloWorldSwing")
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
    label = JLabel("Hello World")
    frame.getContentPane().add(label)
    frame.pack()
    frame.setVisible(True)

javax.swing.SwingUtilities.invokeLater(createAndShowGUI)
print(platform.system())
if(platform.system() == "Darwin"):    
    NSApp.run()