A note of trial and error when trying to create a JavaFX application that doesn't appear in the taskbar, like desktop accessories, and has no borders and isn't square.
OS Windows 10
Java 1.8.0_162
package sample.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.initStyle(StageStyle.UTILITY);
Parent root = FXMLLoader.load(this.getClass().getResource("/main.fxml"));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
Execution result
Description
--If you specify StageStyle.UTILITY
with ʻinitStyle () of
Stage, you can create a window that is not displayed on the taskbar. --As it says ʻUTILITY
, this is originally for displaying an auxiliary window (such as a tool window).
――However, there seems to be no other way to create a window that does not use the taskbar in JavaFX.
main.fxml
package sample.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.initStyle(StageStyle.TRANSPARENT);
Parent root = FXMLLoader.load(this.getClass().getResource("/main.fxml"));
Scene scene = new Scene(root);
scene.setFill(Color.TRANSPARENT);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Execution result
Description
--To display a non-square window, first specify StageStyle.TRANSPARENT
with ʻinitStyle ()of
Stage. --If this is specified, the window frame disappears and the background of
Stagebecomes transparent. --However, in this case ** the taskbar is displayed ** --Specify
Color.TRANSPARENT with
setFill () of
Sceneto be placed on
Stage to make the background of
Scenetransparent. --Now, the root node on
Scene will determine the shape of the window, so if you change the shape of the frame with
-fx-background-radius` etc., you can change the shape of the window. Will be able to change to
UTILITY |
TRANSPARENT |
|
---|---|---|
Hide taskbar | OK | NG |
Non-rectangular window | NG | OK |
Like this, each has the functions you want and the functions you don't need, so if you stand up there, you can't stand up. As of April 2018, there seems to be no way to combine these two neatly.
The OpenJDK issue mentions this issue, but it was last updated in May 2017, and there seems to be no progress since then.
[JDK-8091566] Taskbar-less Undecorated Transparent Window - Java Bug System
As mentioned in the above Issue, you can forcibly combine the two.
Specifically, it is as follows.
--Set the primary stage to StageStyle.UTILITY
and move it to an invisible place outside the screen.
――Create another Stage
and
--Set StageStyle.TRANSPARENT
--Set the primary stage to owner
That is, the primary stage should only be used to use the ʻUTILITYfeature that hides the taskbar and should not be visible on the screen, and the actual visible window should be done by another
Stage with
TRANSPARENT`. The method.
The concerns of this method are explained in the Issue above as follows:
This is a dangerous hack because that UTILITY window could get placed back on the main screen if the computer screen resizes like from a remote desktop session connect and the OS decides to place all windows back on the visible bounds. Tests have shown this doesn't happen, but it feels like it could in certain cases.
(Translation) This is a dangerous hack. This is because the UTILITY window may return to the main window if the computer screen is resized, such as by connecting a remote desktop session, and the OS decides to return all window positions to the visible area. Tests say this doesn't happen, but I think it's likely to happen in certain cases.
It seems that it does not happen in the test, but it seems that it is not a clean solution. It's easy to imagine that if something goes wrong and the primary stage is visible, it's pretty ugly.
As a workaround, I also set the primary stage ʻopacityto
0. If ʻopacity
is supported, the primary stage will not be visible if it accidentally enters the visible region.
However, it doesn't make sense in an environment where ʻopacity` is not supported, so I think it will be necessary to move it off the screen.
In addition to the above-mentioned forced combination, if the implementation such as the system tray is included, the final result will be as follows.
main.fxml
Main.java
package sample.javafx;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javax.imageio.ImageIO;
import java.awt.TrayIcon;
import java.awt.SystemTray;
import java.awt.AWTException;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class Main extends Application {
private TrayIcon trayIcon;
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
Platform.setImplicitExit(false);
this.initPrimaryStage(primaryStage);
Stage mainStage = this.initMainStage(primaryStage);
this.initSystemTray(mainStage);
primaryStage.show();
mainStage.show();
}
private void initPrimaryStage(Stage primaryStage) {
primaryStage.initStyle(StageStyle.UTILITY);
primaryStage.setOpacity(0.0);
primaryStage.setX(-1000);
primaryStage.setY(-1000);
}
private Stage initMainStage(Stage primaryStage) throws IOException {
Stage mainStage = new Stage();
mainStage.initOwner(primaryStage);
mainStage.initStyle(StageStyle.TRANSPARENT);
FXMLLoader fxmlLoader = new FXMLLoader(this.getClass().getResource("/main.fxml"));
Parent root = fxmlLoader.load();
Scene scene = new Scene(root);
scene.setFill(Color.TRANSPARENT);
mainStage.setScene(scene);
MainController controller = fxmlLoader.getController();
controller.setStage(mainStage);
return mainStage;
}
private void initSystemTray(Stage mainStage) throws IOException, AWTException {
if (!SystemTray.isSupported()) {
return;
}
BufferedImage img = ImageIO.read(this.getClass().getResource("/img/open.png "));
this.trayIcon = new TrayIcon(img);
this.trayIcon.setImageAutoSize(true);
this.trayIcon.addActionListener(e -> {
Platform.runLater(() -> {
if (mainStage.isShowing()) {
mainStage.hide();
} else {
mainStage.show();
}
});
});
this.trayIcon.setToolTip("show/hide");
SystemTray systemTray = SystemTray.getSystemTray();
systemTray.add(trayIcon);
}
@Override
public void stop() {
if (SystemTray.isSupported() && this.trayIcon != null) {
SystemTray.getSystemTray().remove(this.trayIcon);
}
}
}
MainController.java
package sample.javafx;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
public class MainController {
private Stage stage;
private double mouseOffsetX;
private double mouseOffsetY;
public void setStage(Stage stage) {
this.stage = stage;
}
@FXML
public void close() {
Platform.exit();
}
@FXML
public void onMousePressed(MouseEvent event) {
this.mouseOffsetX = this.stage.getX() - event.getScreenX();
this.mouseOffsetY = this.stage.getY() - event.getScreenY();
}
@FXML
public void onMouseDragged(MouseEvent event) {
this.stage.setX(event.getScreenX() + this.mouseOffsetX);
this.stage.setY(event.getScreenY() + this.mouseOffsetY);
}
}
Execution result
It looks like you are dragging
It looks like it is in the system tray
Description
--Basically, by combining the story so far, we just implemented the use of the system tray and the dragging of the window.
--The only thing to note is Platform.setImplicitExit (false);
Main.java
@Override
public void start(Stage primaryStage) throws Exception {
Platform.setImplicitExit(false);
--This is a flag to implicitly terminate the JavaFX application when the last window is hidden, defaulting to true
.
--When using the system tray, you have to make sure that the application does not quit even if you hide the last window.
--Therefore, you need to switch this setting to false
** How to use system tray with JavaFX **
-Rab-Duck Software blog: Using Windows system tray with JavaFX
Recommended Posts