Issue
I'm running into a strange issue when running the following code:
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
JFrame frame = new JFrame("Test");
frame.setLocationRelativeTo(null);
SwingUtilities.invokeAndWait(JFXPanel::new);
frame.add(new JFXPanel());
Platform.runLater(() -> {
JPanel panel = new JPanel();
System.exit(0);
});
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(200, 100);
frame.setVisible(true);
}
First of all, the code is compiled with OpenJDK-11 and OpenJFX-11, and in Windows this runs just fine (i.e. exits at the System.exit(0)
call).
However, if I run this on Linux (specifically Ubuntu 20.04), the call to new JPanel()
locks the thread, and the program never exits. Commenting out the UIManager.setLookAndFeel
call will get it to work normally again.
Am I just running into a bug in the com.sun.java.swing.plaf.gtk.GTKLookAndFeel
(which is what the SystemLookAndFeel returns) or am I doing something wrong/unexpected here?
Solution
am I doing something wrong/unexpected here?
Yes, you are doing something wrong: you're violating the Swing threading policy, which states
All Swing components and related classes, unless otherwise documented, must be accessed on the event dispatching thread.
This answer will show you how to refactor your code so it obeys that
(and other) threading rules. I don't have a Linux environment to test this on, so it's possible there are also bugs in the Linux implementation of JavaFX and/or Swing or in the GTKLookAndFeel
, but
this at a minimum gives you a correct way to test that.
Most of the code in your main
method should be executed on the AWT event dispatch thread. You can achieve this by wrapping it in SwingUtilities.invokeLater(...)
.
The exceptions to that are:
Platform.runLater(...)
, which schedules something to be run on the FX Application thread, can itself be called from anywhere. However, thenew JPanel()
which you call inside it should not be executed on the FX Application Thread, it should be executed on the AWT event dispatch thread.SwingUtilities.invokeAndWait(...)
must not be called from the AWT event dispatch thread. This schedules code to be executed on the AWT event dispatch thread, and pauses the current thread until it is completed. Thus callingSwingUtilities.invokeAndWait(...)
from the AWT event dispatch thread will result in deadlock. The codeSwingUtilities.invokeAndWait(JFXPanel::new);
schedules
new JFXPanel()
to be executed on the AWT event dispatch thread, and blocks the current thread until that has completed. If you are already on the AWT event dispatch thread, simply callingnew JFXPanel();
achieves the same thing.
I understand that your code is an attempt to create a minimum reproducible example, but it's not really clear what it's an example of, as there's code there whose purpose is completely unclear. For example, you create two JFXPanel
instances, the first of which you discard. And you create a JPanel
, which you similarly discard, deliberately on the incorrect thread.
I don't have a Linux environment to test this, but the closest "correct" version of the code you posted is something like this. If this code still hangs, I believe there is a bug in the library implementation somewhere.
public static void main(String[] args) throws Exception {
// We are not on the AWT event dispatch thread, so if we
// want to create and access Swing components, we must wrap
// that code inside SwingUtilities.invokeLater(...):
SwingUtilities.invokeLater(() -> {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
JFrame frame = new JFrame("Test");
frame.setLocationRelativeTo(null);
// We must not call SwingUtilities.invokeAndWait(...)
// from the event dispatch thread, so remove this:
// SwingUtilities.invokeAndWait(JFXPanel::new);
// To create new JFXPanel and block until the call is
// complete, we just create it in the usual way (since
// we're already on the event dispatch thread)
new JFXPanel();
frame.add(new JFXPanel());
Platform.runLater(() -> {
// This code block is executed on the FX Application Thread
// To create a swing component, we must wrap the
// code in SwingUtilities.invokeLater(...), so it's
// executed on the Event Dispatch Thread:
SwingUtilities.invokeLater(() -> {
JPanel panel = new JPanel();
System.exit(0);
});
});
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(200, 100);
frame.setVisible(true);
});
}
As an aside, note that the Look and Feel will only affect Swing components; it will not affect JavaFX controls, whose appearance and behavior are managed by JavaFX (by default by the default CSS stylesheet). So anything you place in the JFXPanel
will not be affected by the look and feel.
And finally, you should not mix multiple UI toolkits unless you have a very compelling reason to do so. As you can see, working with two independent single-threaded toolkits is challenging.
Answered By - James_D
Answer Checked By - Pedro (JavaFixing Volunteer)