timm
timm

Reputation: 160

How to make non-modal stage appear always on top of JavaFx fullscreen stage

I have

The latter does not work, even if I use setAlwaysOnTop(true) for the secondary stages they will disappear behind the primary stage once the user clicks on the primary stage. This only happens when the primary stage is in full screen mode, everything works fine if the primary stage is not in fullscreen mode. How can I enable this concept of tools windows in front of a fullscreen stage? Example code:

public class Test extends Application {
    @Override
    public void start(Stage stage) {
        VBox vbox = new VBox();
        Scene scene = new Scene(vbox);
        stage.setScene(scene);

        Button button1 = new Button("New Tool Window");
        button1.setOnAction((e) -> {
            Stage toolStage = new Stage();
            Scene toolScene = new Scene(new Label("Am I on top?"), 300, 250);
            toolStage.setScene(toolScene);
            toolStage.initOwner(stage);
            toolStage.setAlwaysOnTop(true);
            toolStage.show();
        });

        Button button2 = new Button("Close");
        button2.setOnAction((e) -> System.exit(0));

        vbox.getChildren().addAll(button1, button2);
        stage.show();
        stage.setFullScreen(true);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Update 8/20/2016: Confirmed as a bug: JDK-8164210

Upvotes: 3

Views: 2649

Answers (4)

selami tastan
selami tastan

Reputation: 102

The trick is using setAlwaysOnTop when detecting primary stage on top. It could be used another separate thread to detect. I used jnativehook to detect.

    <!-- https://mvnrepository.com/artifact/com.1stleg/jnativehook -->
    <dependency>
        <groupId>com.1stleg</groupId>
        <artifactId>jnativehook</artifactId>
        <version>2.1.0</version>
    </dependency>

Your working example is here:

public class Test extends Application {
@Override
public void start(Stage stage) {
    VBox vbox = new VBox();
    Scene scene = new Scene(vbox);
    stage.setScene(scene);

    Button button1 = new Button("New Tool Window");
    button1.setOnAction((e) -> {
        Stage toolStage = new Stage();
        Scene toolScene = new Scene(new Label("Am I on top?"), 300, 250);
        toolStage.setScene(toolScene);
        toolStage.initOwner(stage);
        toolStage.setAlwaysOnTop(true);
        setAlwaysOnTop(toolStage);
        toolStage.show();
    });

    Button button2 = new Button("Close");
    button2.setOnAction((e) -> System.exit(0));

    vbox.getChildren().addAll(button1, button2);
    stage.show();
    stage.setFullScreen(true);
}

public static void setAlwaysOnTop(Stage stage){
    initialize();
    stageOnTop = stage;
}

private static boolean initialized = false;
private static Stage stageOnTop;
private static void initialize(){
    if(initialized){
        return;
    }
    /* Note: JNativeHook does *NOT* operate on the event dispatching thread.
     * Because Swing components must be accessed on the event dispatching
     * thread, you *MUST* wrap access to Swing components using the
     * SwingUtilities.invokeLater() or EventQueue.invokeLater() methods.
     */
    try {
        GlobalScreen.registerNativeHook();
    } catch (NativeHookException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    GlobalScreen.setEventDispatcher(new SwingDispatchService());

    GlobalScreen.addNativeMouseListener(new NativeMouseListener() {
        @Override
        public void nativeMouseClicked(NativeMouseEvent nativeMouseEvent) {}
        @Override
        public void nativeMousePressed(NativeMouseEvent nativeMouseEvent) {
            if(stageOnTop != null && stageOnTop.isShowing()){
                Platform.runLater(() -> {
                    stageOnTop.setAlwaysOnTop(false);
                    stageOnTop.setAlwaysOnTop(true);
                });
            }
        }
        @Override
        public void nativeMouseReleased(NativeMouseEvent nativeMouseEvent) {}
    });
    initialized = true;
}

public static void main(String[] args) {
    launch(args);
}
}

Upvotes: 0

DeznekCZ
DeznekCZ

Reputation: 1

I recently created a picture checker to show the application on top of taskbar. But it may cause issues with security with javafx.scene.robot.Robot, because snapshot may be not allowed.

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.image.WritableImage;
import javafx.scene.robot.Robot;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

import java.util.Timer;
import java.util.TimerTask;

public class Main extends Application {

    private Timer timer;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        BorderPane root = new BorderPane();
        root.setWidth(200);
        root.setHeight(45);
        primaryStage.initStyle(StageStyle.UNDECORATED);
        primaryStage.setScene(new Scene(root));
        primaryStage.setAlwaysOnTop(true);
        primaryStage.setX(Screen.getPrimary().getVisualBounds().getMaxX() - 500);
        primaryStage.setY(Screen.getPrimary().getVisualBounds().getMaxY());
        primaryStage.show();

        timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {

            final Robot robot = new Robot();

            @Override
            public void run() {
                Platform.runLater(this::runFX);
            }

            private void runFX() {
                final int width = (int) Math.round(primaryStage.getWidth());
                final int height = (int) Math.round(primaryStage.getHeight());

                WritableImage image = robot.getScreenCapture(new WritableImage(width, height),
                        new Rectangle2D(
                        primaryStage.getX(), primaryStage.getY(),
                        width, height
                ));

                WritableImage inner = primaryStage.getScene().snapshot(new WritableImage(
                        width,
                        height
                ));

                for (int x = 0; x < width; x++) {
                    for (int y = 0; y < height; y++) {
                        if (image.getPixelReader().getArgb(x, y) != inner.getPixelReader().getArgb(x, y)){
                            primaryStage.setAlwaysOnTop(false);
                            primaryStage.setAlwaysOnTop(true);
                            return;
                        }
                    }
                }
            }
        }, 0, 500);
    }
}

Upvotes: 0

Emmanuel Ogoma
Emmanuel Ogoma

Reputation: 582

you need to set initmodality after set initowner

toolStage.initOwner(stage);
toolStage.initModality(Modality.APPLICATION_MODAL);

Upvotes: 0

Eudy Contreras
Eudy Contreras

Reputation: 396

A way to bypass this limitation is to: Deactivate fullscreen mode Create a keyCombination for psuedo fullscreen Set the stage style undecorated and not resizable Se the screen to the size of the user screen and position it at 0,0.

It is easy to create your own border for minimizing and closing the program as shown here:

JavaFX Stage.setMaximized only works once on Mac OSX (10.9.5)

And here:

JavaFX 8 Taskbar Icon Listener

Upvotes: 0

Related Questions