scoozi17
scoozi17

Reputation: 65

JavaFX - Center Child Stage to Parent Stage

I'm new in JavaFX and I have problem with setting position of my new window, related to parent window position. I want to open new window at the center of parent window.

I tried these two ways:

1) Init owner - i thought that init owner will open my window at the center of parent but it doesn't

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

2) Reposition when windows is shown - it works but after window is shown I can see its first position and then I see it is moved to new calculated position. I can't accept that.

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();

In onWindowShown function I'm setting new X and Y position of my window, because only after shown width and height of my window are known and i can easly calculate new position.

How can I can set child window position at the center of parent window before showAndWait function?

Upvotes: 6

Views: 12650

Answers (3)

Daryl
Daryl

Reputation: 1177

As the other answer correctly states, JavaFX calculates the stage width and height as the window is being shown. The onShowing() property is called before the calculation takes place, and before the window is set visible, so placing a stage.hide() command there effectively does nothing. And as the question states, seeing a blip on the screen just before the window is relocated is a less-than-desirable outcome.

The only way I have found to solve this problem (though I would love to know if a better way exists) is to add Listeners to the width and height properties of the stage, and let them fire the event to change the window's position. Then once the window is visible, remove the Listeners.

for example:

//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();

Upvotes: 8

fabian
fabian

Reputation: 82471

You could layout the Scene before showing the Stage and determine the position based on the Scene size. This will not take the window decorations into account however, so the position could be slightly off unless you have a window with the same kind of decorations to take the size of those decorations into account:

@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();
}

Upvotes: 0

DVarga
DVarga

Reputation: 21809

If you want to show a Stage you have to put the content loaded from the FXML file to a Scene and this Scene will be set on the Stage.

Unfortunately the size of the Stage is unknown until it is rendered (as you mentioned), but to relocate it to the center of the parent Stage, you have to know the size.

One trick that could work is to hide the Stage before it is shown, relocate it, and then make it visible again on the new position.

Example

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

Upvotes: 9

Related Questions