Create a frameless non-rectangular window in JavaFX without a taskbar

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.

environment

OS Windows 10

Java 1.8.0_162

Window display without taskbar

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

javafx.jpg

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.

Displaying a non-rectangular window without a frame

main.fxml

javafx.jpg

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

javafx.jpg

Description

--To display a non-square window, first specify StageStyle.TRANSPARENT with ʻinitStyle ()ofStage. --If this is specified, the window frame disappears and the background of Stagebecomes transparent. --However, in this case ** the taskbar is displayed ** --SpecifyColor.TRANSPARENT with setFill () of Sceneto be placed onStage to make the background of Scenetransparent. --Now, the root node onScene 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

Combine the two

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

Forcibly combine

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 anotherStage with TRANSPARENT`. The method.

javafx.jpg

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 ʻopacityto0. 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.

Final form

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

javafx.jpg

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

javafx.gif

It looks like it is in the system tray

javafx.png

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

Create a frameless non-rectangular window in JavaFX without a taskbar
Create a database in a production environment
Create a Servlet program in Eclipse
Let's make a calculator application with Java ~ Create a display area in the window
[Android] Create a sliding menu without using NavigationView
Create a CSR with extended information in Java
Create a simple batch processing framework in Eclipse.
Try to create a bulletin board in Java
Let's create a custom tab view in SwiftUI 2.0
Let's create a super-simple web framework in Java
How to create a theme in Liferay 7 / DXP
How to easily create a pull-down in Rails
Create a native extension of Ruby in Rust
Docker command to create Rails project with a single blow in environment without Ruby