TornadoFx Undecorated window 从任务栏恢复时进入全屏

TornadoFx Undecorated window goes fullscreen when restored from task bar

我一直在试用 Tornadofx。尝试创建自定义 title-bar,这是我目前正在尝试的代码

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)
                }
            }
        }
    }
}

以下工作没有问题

但是当最大化的 window 最小化到任务栏,然后从任务栏打开时,它会全屏(隐藏任务栏)

我该如何解决这个问题,我的代码是否有任何错误、需要更改或需要包含的部分?

我的配置是 Windows 10 64 位,Java 11.0.2,Kotlin 1.4.21,JavaFx 11.0.2,TornadoFx 1.7.20

我认为这是 JavaFX 中的普遍问题(我的意思是不特定于 TornadoFX)。

根本原因是因为将舞台的maximized 属性设置为true。不确定 JavaFX 内部做了什么,但是当您从任务栏打开 window 并且如果最大值为真时,它会以全屏模式呈现。

您可以通过两种方式解决此问题。

方法 #1:

当从任务栏打开 window 时,图标 属性 将关闭,如果最大化为真,请将舞台尺寸再次设置为屏幕边界。

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

方法 #2:

不要依赖舞台的最大化 属性。我相信您需要 属性 来切换 window 维度。因此,改为维护一个实例变量来处理它。

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");
    }
});

下面是这两种方法的完整工作演示。您可以根据您的其他需求决定走哪条路。

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");
            }
        });
    }
}

解决问题需要进行两项更改

实际问题是,如果 isMaximized 设置为 true,即使 isFullScreen 属性 单独可用,应用程序在从任务(最小化)打开时会全屏显示

  1. 添加一个最大化的 属性 侦听器,这样我们就可以在 isMaximized 被其他方式修改时无效(比如双击 Linux 中的标题栏等)
// CHANGE 1
stage.maximizedProperty().addListener { _, _, newValue ->
    if (newValue) stage.isMaximized = false
}
  1. 通过单独最大化而不是使用 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"
}

奖励:使用 isFocusTraversable = false 制作不随键盘遍历聚焦的按钮

最终解决方案

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
        }
    }
}