Before all of the commenters start saying "Just don't mix Swing and JavaFX", yeah I know, I know. It has caused us a lot of problems over the years but my team has a ton of swing code and we really don't have budget to re-write it all in JavaFX. We're trying to gradually transition portions of our code to JavaFX over time.
Years ago I wrote a nice JavaFX utility that I use in several different applications that shows a little popup warnings in the bottom right corner of an application. It has a lot of cool features, which are pretty JavaFX dependent.
I also wrote some code that allowed our team to use these warnings in Swing applications. It does this by making an invisible JavaFX stage to own the popups (since popups can't display without an owner window).
I've been happy with the code for years, but only recently tried running it on Linux for the first time. It turns out that on Linux, my invisible stage is not invisible and I can't find any way to make it go away. I guess it's not the end of the world if there's a little empty stage, but it's pretty annoying and I'd like to get rid of it. I've tested on Ubuntu with both LXDE and Gnome.
The full code for my warning popups is more than I'd like to post here, but here's a minimum reproducible example of displaying a JavaFX popup in a Swing app. It works fine in Windows, but in Linux, the dummy stage is visible and has its own icon in the task bar.
import java.awt.Dimension;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.stage.Popup;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class FxOnSwingProblemDemo extends JFrame {
private Stage dummyStage;
public FxOnSwingProblemDemo() {
initializeFxToolkit();
JButton button = new JButton("Show popup");
button.addActionListener(e ->
Platform.runLater(() -> {
Popup popup = new Popup();
popup.getContent().add(new Label("Hey look, a popup!"));
popup.show(getDummyStage());
})
);
JPanel root = new JPanel();
root.add(button);
this.add(root);
this.setPreferredSize(new Dimension(500, 500));
this.pack();
//Note exit on close is important here to kill the FX Platform thread
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setVisible(true);
}
private void initializeFxToolkit() {
Platform.setImplicitExit(false);
try {
SwingUtilities.invokeAndWait(JFXPanel::new);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private Stage getDummyStage() {
if(dummyStage == null) {
//Utility prevents icon from showing up on windows task bar
dummyStage = new Stage(StageStyle.UTILITY);
dummyStage.setScene(new Scene(new Pane()));
//Note, setting the width and height to very small numbers (so far tested with 0.1, 1, and 2)
//will result in GTK warnings that can't be suppressed printing to stderr on at least
//some linux environments. So we'll set it to a larger size. It really doesn't matter
//what size we pick as long as it is large enough to avoid the GTK warnings.
dummyStage.setWidth(100);
dummyStage.setHeight(100);
//Make it invisible
dummyStage.setOpacity(0.0);
//Don't allow closing
dummyStage.setOnCloseRequest(e -> e.consume());
dummyStage.show();
}
return dummyStage;
}
public static void main(String[] args) {
new FxOnSwingProblemDemo();
}
}
I tried everything I can think of to make the window invisible, but haven't found a way yet. I also tried seeing if there's a way I can display a Popup without a real window by making my own subclass of Window that doesn't really display anything, but I haven't gotten very far since a lot Window's methods are final. Open to any ideas. If I have to just write a swing version of the warnings, then I guess I will, but I'd like to avoid it if I can.
The comments from VGR and Slaw were both very helpful so I'm going to expand on them here for anyone else who comes looking at this question.
This was based on VGR's suggestion and it is what I ended up doing. This could be considered a workaround more than an answer to the actual question in the title since it does not actually display a popup, but in my particular case, this was actually the better solution.
Basically, my implementation boiled down to:
Create an AnchorPane to hold all of the warnings
make background transparent with warningsPane.setStyle("-fx-background-color: transparent");
Create a Scene with the warnings pane as it's root and set it transparent as well with scene.setFill(javafx.scene.paint.Color.TRANSPARENT);
Create a JFXPanel and setting it to use that new scene
contains
method on the JFXPanel so that it only returned true if the mouse position was within one of the "popups".Set the JFrame's glass pane to my JFXPanel (jFrame.setGlassPane(fxPanel); jFrame.getGlassPane().setVisible(true);
)
Then I modified all of my "popup" code to add nodes to that warningsPane
object instead of actually producing popups. In my cases this was fairly significant re-work, but it actually resulted in a simpler product because before I had to do a lot of work to handle window moving and re-sizing but now everything in the AnchorPane
just moves around with the window easily
For the JavaFX apps that use this same utility, I added a StackPane and put my warningsPane
on the top. I called warningPane.setPickOnBounds(false);
which allowed mouse events that weren't targeted on the warnings to pass through to the underlying controls.
This isn't the approach I used but I did test it out just to see. Slaw suggested that I try using Popup.show(Node, double, double)
instead of Popup.show(Window)
. The documentation says that this method will throw an IllegalArgumentException
if the specified Node is not associated with a Window
but I suppose the interpretation of "Window" there is up to some interpretation. I found that if I created a Node and didn't add it to anything, then I would get an IllegalArgumentException. However, if I added that node to a JFXPanel
and added that panel somewhere in my Swing app, then the Popup.show(Node, double, double)
method works just fine. I don't know if that's guaranteed to work in every version of JavaFX with every JRE, but in my case it seemed to work fine.