简体   繁体   中英

TornadoFx Undecorated window goes fullscreen when restored from task bar

I've been trying out Tornadofx. trying to create a custom title-bar, here's the code I'm currently trying

fun main(args: Array<String>) {
    launch<MyApp>(args)
}

class MyApp : App(Title::class) {
    override fun start(stage: Stage) {
        stage.initStyle(StageStyle.UNDECORATED)
        stage.minWidth = 600.0
        stage.minHeight = 450.0
        stage.isMaximized = false
        super.start(stage)
    }
}

class Title : View() {
    private var xOffset = 0.0
    private var yOffset = 0.0
    private var screenBounds: Rectangle2D = Screen.getPrimary().visualBounds
    private var originalBounds: Rectangle2D = Rectangle2D.EMPTY
    
    init {
        primaryStage.isMaximized = false
    }
    
    override val root = borderpane {
        onMousePressed = EventHandler { ev ->
            xOffset = primaryStage.x - ev.screenX
            yOffset = primaryStage.y - ev.screenY
        }
        
        onMouseDragged = EventHandler { ev ->
            primaryStage.x = xOffset + ev.screenX
            primaryStage.y = yOffset + ev.screenY
        }
        
        center = label("Forms")
        
        right = hbox {
            button("Mi") {
                action {
                    with(primaryStage) { isIconified = true }
                }
            }

            button("Ma") {
                action {
                    if (primaryStage.isMaximized) {
                        with(primaryStage) {
                            x = originalBounds.minX
                            y = originalBounds.minY
                            width = originalBounds.width
                            height = originalBounds.height
                            isMaximized = false
                        }
                        text = "Ma"
                    } else {
                        with(primaryStage) {
                            originalBounds = Rectangle2D(x, y, width, height)
                            x = screenBounds.minX
                            y = screenBounds.minY
                            width = screenBounds.width
                            height = screenBounds.height
                            isMaximized = true
                        }
                        text = "Re"
                    }
                }
            }

            button("X") {
                action {
                    app.stop()
                    println("exiting")
                    exitProcess(0)
                }
            }
        }
    }
}

the following work without problems

  • close
  • maximize, restore
  • restored window minimized, then open from taskbar

but when a maximized window is minimized to taskbar, then open from taskbar, it goes full screen(taskbar is hidden)

how do i fix this behavior, is there any part of my code that is wrong, needs change, or in need of any inclusions?

my configuration is Windows 10 64bit, Java 11.0.2, Kotlin 1.4.21, JavaFx 11.0.2, TornadoFx 1.7.20

I think this is a general problem in JavaFX (I mean not specific with TornadoFX).

The root cause for this is because of setting the maximized property of stage to true. Not sure what JavaFX internally does, but when you open the window from task bar and if the maximized value is true, then it renders in full screen mode.

You can fix this in two ways.

Approach #1:

When the window is opened from task bar, the iconfied property will turn off, set the stage dimensions again to screen bounds if maximized is true.

primaryStage.iconifiedProperty().addListener((obs,old,iconified)->{
    if(!iconified && primaryStage.isMaximized()){
        primaryStage.setWidth(screenBounds.getWidth());
        primaryStage.setHeight(screenBounds.getHeight());
    }
});

Approach #2:

Don't rely on the maximized property of the Stage. I believe you need that property to toggle the window dimensions. So instead maintain a instance variable to handle that.

boolean maximized = false;
ma.setOnAction(e -> {
    if (maximized) {
        // Set stage to original bounds
        maximized = false;
        ma.setText("Ma");
    } else {
        // Set stage to screen bounds
        maximized = false;
        ma.setText("Re");
    }
});

A full working demo is below with both the approaches. You can decide which way to go based on your other requirments.

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class UndecoratedWindowFullScreenDemo extends Application {
    private double xOffset = 0.0;
    private double yOffset = 0.0;
    private Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
    private Rectangle2D originalBounds = Rectangle2D.EMPTY;
    private boolean maximized = false;

    @Override
    public void start(Stage primaryStage) throws Exception {
        BorderPane root = new BorderPane();
        root.setStyle("-fx-background-color:pink;");
        Scene scene = new Scene(root, 600, 450);
        primaryStage.setScene(scene);

        Label label = new Label("Forums");
        Button mi = new Button("Mi");
        Button ma = new Button("Ma");
        Button x = new Button("X");
        HBox pane = new HBox(mi, ma, x);
        pane.setPadding(new Insets(3));
        pane.setSpacing(5);
        root.setCenter(label);
        root.setRight(pane);

        primaryStage.initStyle(StageStyle.UNDECORATED);
        primaryStage.setMinWidth(600);
        primaryStage.setMinHeight(450);
        primaryStage.setMaximized(false);
        primaryStage.show();

        root.setOnMousePressed(e -> {
            xOffset = primaryStage.getX() - e.getScreenX();
            yOffset = primaryStage.getY() - e.getScreenY();
        });
        root.setOnMouseDragged(e -> {
            primaryStage.setX(xOffset + e.getScreenX());
            primaryStage.setY(yOffset + e.getScreenY());
        });
        mi.setOnAction(e -> primaryStage.setIconified(true));

        /* Use this approach if you want to go with the Stage maximized property */
        // approach1(primaryStage, ma);

        /* Use this approach if you want to avoid Stage maximized property and maintain a instance variable */
        approach2(primaryStage, ma);
    }

    private void approach1(Stage primaryStage, Button ma) {
        primaryStage.iconifiedProperty().addListener((obs, old, iconified) -> {
            if (!iconified && primaryStage.isMaximized()) {
                primaryStage.setWidth(screenBounds.getWidth());
                primaryStage.setHeight(screenBounds.getHeight());
            }
        });

        ma.setOnAction(e -> {
            if (primaryStage.isMaximized()) {
                primaryStage.setX(originalBounds.getMinX());
                primaryStage.setY(originalBounds.getMinY());
                primaryStage.setWidth(originalBounds.getWidth());
                primaryStage.setHeight(originalBounds.getHeight());
                primaryStage.setMaximized(false);
                ma.setText("Ma");
            } else {
                originalBounds = new Rectangle2D(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight());
                primaryStage.setX(screenBounds.getMinX());
                primaryStage.setY(screenBounds.getMinY());
                primaryStage.setWidth(screenBounds.getWidth());
                primaryStage.setHeight(screenBounds.getHeight());
                primaryStage.setMaximized(true);
                ma.setText("Re");
            }
        });
    }

    private void approach2(Stage primaryStage, Button ma) {
        ma.setOnAction(e -> {
            if (maximized) {
                primaryStage.setX(originalBounds.getMinX());
                primaryStage.setY(originalBounds.getMinY());
                primaryStage.setWidth(originalBounds.getWidth());
                primaryStage.setHeight(originalBounds.getHeight());
                maximized = false;
                ma.setText("Ma");
            } else {
                originalBounds = new Rectangle2D(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight());
                primaryStage.setX(screenBounds.getMinX());
                primaryStage.setY(screenBounds.getMinY());
                primaryStage.setWidth(screenBounds.getWidth());
                primaryStage.setHeight(screenBounds.getHeight());
                maximized = true;
                ma.setText("Re");
            }
        });
    }
}

There are two changes that were needed to solve the problem

The actual problem was that if isMaximized is set to true the app goes full screen when being open from task(minimized) even though isFullScreen property is separately available

  1. Adding a maximized property listener so that we can invalidate if the isMaximized were to be ever modified by other means(like double clicking on title bar in Linux etc)
// CHANGE 1
stage.maximizedProperty().addListener { _, _, newValue ->
    if (newValue) stage.isMaximized = false
}
  1. by having a separate maximized instead of using isMaximized
// CHANGE 2
private var maximized: Boolean = false // <- here

if (maximized) { // <- here
    // restore the window by setting bounds of original size
    maximized = false // <- here
    text = "Ma"
} else {
    // maximize window by setting bounds from screen size
    maximized = true // <- and here
    text = "Re"
}

Bonus: use isFocusTraversable = false to make buttons that don't focus with keyboard traversal

Final solution

fun main(args: Array<String>) {
    launch<MyApp>(args)
}

class MyApp : App(Window::class, Styles::class) {
    override fun start(stage: Stage) {
        stage.initStyle(StageStyle.UNDECORATED)
        stage.minWidth = 600.0
        stage.minHeight = 450.0
        stage.width = 600.0
        stage.height = 450.0

        // CHANGE 1
        stage.maximizedProperty().addListener { _, _, newValue ->
            if (newValue) stage.isMaximized = false
        }
        stage.isMaximized = false

        super.start(stage)
    }
}

class Window : View() {
    override val root = borderpane {
        top = Title().root
    }
}

class Title : View() {
    // CHANGE 2
    private var maximized: Boolean = false // <- here
    private var xOffset = 0.0
    private var yOffset = 0.0
    private var screenBounds: Rectangle2D = Screen.getPrimary().visualBounds
    private var originalBounds: Rectangle2D = Rectangle2D.EMPTY
    
    init {
        primaryStage.isMaximized = false
    }
    
    override val root = hbox {
        hgrow = Priority.ALWAYS
        
        onMousePressed = EventHandler { ev ->
            xOffset = primaryStage.x - ev.screenX
            yOffset = primaryStage.y - ev.screenY
        }
        
        onMouseDragged = EventHandler { ev ->
            primaryStage.x = xOffset + ev.screenX
            primaryStage.y = yOffset + ev.screenY
        }
        
        val l1 = hbox {
            hgrow = Priority.ALWAYS
            alignment = Pos.CENTER
            label("Forms")
        }
        add(l1)
        l1.requestFocus()
        
        button("Mi") {
            id = "min"
            action {
                with(primaryStage) { isIconified = true }
            }
            isFocusTraversable = false
        }
        
        button("Ma") {
            id = "max"
            action {
                if (maximized) { // <- here
                    with(primaryStage) {
                        x = originalBounds.minX
                        y = originalBounds.minY
                        width = originalBounds.width
                        height = originalBounds.height
                        maximized = false // <- here
                    }
                    text = "Ma"
                } else {
                    with(primaryStage) {
                        originalBounds = Rectangle2D(x, y, width, height)
                        x = screenBounds.minX
                        y = screenBounds.minY
                        width = screenBounds.width
                        height = screenBounds.height
                        maximized = true // <- and here
                    }
                    text = "Re"
                }
                l1.requestFocus()
            }
            isFocusTraversable = false
        }
        button("X") {
            id = "close"
            action {
                app.stop()
                println("exiting")
                exitProcess(0)
            }
            isFocusTraversable = false
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM