JavaFX - 将子舞台居中到父舞台

6
我是JavaFX的新手,我在设置新窗口位置时遇到了问题,与父窗口位置有关。我想将新窗口打开在父窗口的中心。
我尝试了以下两种方法:
1)初始化所有者 - 我认为初始化所有者会将我的窗口打开在父窗口的中心,但它并没有这样做。
FXMLLoader fxmlLoader = new     FXMLLoader(AddUserController.class.getResource("/view/addUserStage.fxml"));
Parent root = fxmlLoader.load();
Stage stage = new Stage();
stage.initOwner(parentStage);
stage.showAndWait();

2) 当窗口显示时重新定位 - 它能够工作,但是当窗口显示后,我会看到它最初的位置,然后我才会看到它被移动到新计算的位置。我不能接受这样的结果。

FXMLLoader fxmlLoader = new     FXMLLoader(AddUserController.class.getResource("/view/addUserStage.fxml"));
Parent root = fxmlLoader.load();
Stage stage = new Stage();
stage.initOwner(parentStage);
stage.setOnShown(event -> controller.onWindowShown());
stage.showAndWait();

在onWindowShown函数中,我设置了窗口的新X和Y位置,因为只有在显示后,我才能知道窗口的宽度和高度,并且可以轻松计算新的位置。

如何在调用showAndWait函数之前将子窗口的位置设置为父窗口的中心位置?

3个回答

9
如果想显示一个Stage,你需要将从FXML文件加载的内容放到一个Scene中,并将这个Scene设置在Stage上。

不幸的是,在Stage渲染之前无法确定其大小(正如你所提到的),但要将其重新定位到父Stage的中心,你必须了解其大小。

一个可行的技巧是在显示Stage之前隐藏它,进行重新定位,然后在新位置上再次显示它。

示例
public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            BorderPane root = new BorderPane();
            Scene scene = new Scene(root, 400, 400);

            Button button = new Button("Open another Stage");
            button.setOnAction(e -> {

                Stage popUpStage = new Stage();

                // Here add the FXML content to the Scene
                Scene popUpScene = new Scene(new Button());
                popUpStage.setScene(popUpScene);

                // Calculate the center position of the parent Stage
                double centerXPosition = primaryStage.getX() + primaryStage.getWidth()/2d;
                double centerYPosition = primaryStage.getY() + primaryStage.getHeight()/2d;

                // Hide the pop-up stage before it is shown and becomes relocated
                popUpStage.setOnShowing(ev -> popUpStage.hide());

                // Relocate the pop-up Stage
                popUpStage.setOnShown(ev -> {
                    popUpStage.setX(centerXPosition - popUpStage.getWidth()/2d);
                    popUpStage.setY(centerYPosition - popUpStage.getHeight()/2d);
                    popUpStage.show();
                });

                popUpStage.showAndWait();
            });

            root.setCenter(button);


            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

8
正如其他答案正确地指出的那样,JavaFX在窗口显示时计算舞台的宽度和高度。onShowing()属性在计算发生之前被调用,并且在窗口设置为可见之前被调用,因此在那里放置一个stage.hide()命令实际上什么也不做。正如问题所述,看到窗口重新定位之前屏幕上的闪烁是一个不太理想的结果。
我找到的唯一解决这个问题的方法(虽然我很想知道是否存在更好的方法)是向舞台的宽度和高度属性添加侦听器,并让它们触发事件以更改窗口的位置。然后,一旦窗口可见,就删除侦听器。
例如:
//Let's say we want to center the new window in the parent window

ChangeListener<Number> widthListener = (observable, oldValue, newValue) -> {
        double stageWidth = newValue.doubleValue();
        stage.setX(parentStage.getX() + parentStage.getWidth() / 2 - stageWidth / 2);
};
ChangeListener<Number> heightListener = (observable, oldValue, newValue) -> {
        double stageHeight = newValue.doubleValue();
        stage.setY(parentStage.getY() + parentStage.getHeight() / 2 - stageHeight / 2);   
};

stage.widthProperty().addListener(widthListener);
stage.heightProperty().addListener(heightListener);

//Once the window is visible, remove the listeners
stage.setOnShown(e -> {
    stage.widthProperty().removeListener(widthListener);
    stage.heightProperty().removeListener(heightListener);
});

stage.show();

2
这对我很管用。我无法相信他们没有在Swing中包含像- setLocationRelativeTo(something) -这样的方法。 - Wesos de Queso

0

在显示Stage之前,您可以布置Scene并根据Scene的大小确定位置。但是,这不会考虑窗口装饰,因此位置可能会略微偏离,除非您有一个具有相同类型装饰的窗口以考虑这些装饰的大小:

@Override
public void start(Stage primaryStage) {
    Button btnMain = new Button("show");
    btnMain.setOnAction(evt -> {
        Stage stage = new Stage();
        Button btn = new Button("Close");
        btn.setOnAction((ActionEvent event) -> {
            stage.close();
        });

        StackPane root = new StackPane();
        root.getChildren().add(btn);

        Bounds mainBounds = primaryStage.getScene().getRoot().getLayoutBounds();
        Scene scene = new Scene(root, 400, 400);

        stage.setScene(scene);

        // layout new scene
        scene.getRoot().applyCss();
        scene.getRoot().layout();

        Bounds rootBounds = scene.getRoot().getLayoutBounds();
        stage.setX(primaryStage.getX() + (mainBounds.getWidth() - rootBounds.getWidth()) / 2);
        stage.setY(primaryStage.getY() + (mainBounds.getHeight() - rootBounds.getHeight()) / 2);
        stage.showAndWait();
        System.out.println("done");
    });
    Scene mainScene = new Scene(new StackPane(btnMain), 600, 600);
    primaryStage.setScene(mainScene);
    primaryStage.show();
}

我不知道为什么,即使在获取边界之前执行了布局函数,我仍然会得到根边界中的零。 - scoozi17

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接