user3573403
user3573403

Reputation: 1822

JavaFX position dialog and stage in center of screen

The following codes demonstrates centering of a dialog and the stage in the center of the screen. The dialog is supposed to be displayed first for the user to enter the login credentials. After successful login, the main window (stage) is then displayed. I found the solution of centering the dialog and stage from this web site, but it doesn't seem very ideal. For both the dialog and stage, they have to be displayed first before we can calculate the coordinates and then positioning them in the center. This means that we can see the dialog and the main window moving to the center after they are displayed. Is there a better way? Ideally, they should be positioned in the center before they are displayed.

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.Window;

public class Demo extends Application {

    private Stage primaryStage;
    private Dialog<String> dialog;
    private Button createUserButton = new Button("Create User");

    @Override
    public void start(Stage primaryStage) throws Exception {
        this.primaryStage = primaryStage;
        Text usersLabel = new Text("Current Users:");
        TableColumn<User, String> indexColumn = new TableColumn<User, String>("No.");
        indexColumn.setMaxWidth(1f * Integer.MAX_VALUE * 10);
        indexColumn.setCellValueFactory(p -> p.getValue().indexProperty());
        TableColumn<User, String> userNameColumn = new TableColumn<User, String>("User Name");
        userNameColumn.setMaxWidth(1f * Integer.MAX_VALUE * 60);
        userNameColumn.setCellValueFactory(p -> p.getValue().userNameProperty());
        TableColumn<User, String> roleColumn = new TableColumn<User, String>("Role");
        roleColumn.setMaxWidth(1f * Integer.MAX_VALUE * 30);
        roleColumn.setCellValueFactory(p -> p.getValue().roleProperty());
        TableView<User> tableView = new TableView<User>();
        tableView.getColumns().add(indexColumn);
        tableView.getColumns().add(userNameColumn);
        tableView.getColumns().add(roleColumn);
        tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        Text dummyLabel = new Text("");
        VBox leftPane = new VBox(5);
        leftPane.getChildren().addAll(usersLabel, tableView);
        VBox rightPane = new VBox(20);
        rightPane.setFillWidth(true);
        rightPane.getChildren().addAll(dummyLabel, createUserButton);
        GridPane mainPane = new GridPane();
        mainPane.setPadding(new Insets(10, 0, 0, 10));
        mainPane.setHgap(20);
        mainPane.add(leftPane, 0, 0);
        mainPane.add(rightPane, 1, 0);
        Scene scene = new Scene(mainPane);
        primaryStage.setScene(scene);
        primaryStage.setResizable(false);
        showDialog();
    }

    private void showDialog() {
        dialog = new Dialog<>();
        dialog.setTitle("Login");
        dialog.setHeaderText("Please enter User Name and Password to login.");
        dialog.setResizable(false);
        Label userNameLabel = new Label("User Name:");
        Label passwordLabel = new Label("Password:");
        TextField userNameField = new TextField();
        PasswordField passwordField = new PasswordField();
        GridPane grid = new GridPane();
        grid.setAlignment(Pos.CENTER);
        grid.setHgap(10);
        grid.setVgap(10);
        grid.setPadding(new Insets(20, 35, 20, 35));
        grid.add(userNameLabel, 1, 1);
        grid.add(userNameField, 2, 1);
        grid.add(passwordLabel, 1, 2);
        grid.add(passwordField, 2, 2);
        dialog.getDialogPane().setContent(grid);
        dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
        Button okButton = (Button) dialog.getDialogPane().lookupButton(ButtonType.OK);
        okButton.addEventFilter(ActionEvent.ACTION, event -> {
            createUser(userNameField.getText().trim(), passwordField.getText());
            event.consume();
        });
        dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
        Platform.runLater(() -> {
            Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
            Window window = dialog.getDialogPane().getScene().getWindow();
            window.setX((screenBounds.getWidth() - window.getWidth()) / 2);
            window.setY((screenBounds.getHeight() - window.getHeight()) / 2);
        });
        dialog.showAndWait();
    }

    private void createUser(String userName, String password) {
        dialog.getDialogPane().setDisable(true);
        dialog.getDialogPane().getScene().setCursor(Cursor.WAIT);
        Task<Boolean> task = new Task<Boolean>() {
            @Override
            public Boolean call() {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException exception) {
                }
                return Boolean.TRUE;
            }
        };
        task.setOnSucceeded(e -> {
            Boolean success = task.getValue();
            dialog.getDialogPane().setDisable(false);
            dialog.getDialogPane().getScene().setCursor(Cursor.DEFAULT);
            if (success.booleanValue()) {
                Platform.runLater(() -> {
                    dialog.close();
                    primaryStage.show();
                    Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
                    primaryStage.setX((screenBounds.getWidth() - primaryStage.getWidth()) / 2);
                    primaryStage.setY((screenBounds.getHeight() - primaryStage.getHeight()) / 2);
                });
            } else {
                Alert alert = new Alert(AlertType.ERROR);
                alert.setTitle("Login Error");
                alert.setHeaderText("Unable to login.");
                alert.showAndWait();
            }
        });
        new Thread(task).start();
    }

    public static void main(String[] arguments) {
        Application.launch(arguments);
    }

}

class User {

    private StringProperty index;

    private StringProperty userName;

    private StringProperty role;

    public String getIndex() {
        return indexProperty().get();
    }

    public StringProperty indexProperty() {
        if (index == null) {
            index = new SimpleStringProperty(this, "index");
        }
        return index;
    }

    public void setIndex(String index) {
        indexProperty().set(index);
    }

    public String getUserName() {
        return userNameProperty().get();
    }

    public StringProperty userNameProperty() {
        if (userName == null) {
            userName = new SimpleStringProperty(this, "userName");
        }
        return userName;
    }

    public void setUserName(String userName) {
        userNameProperty().set(userName);
    }

    public String getRole() {
        return roleProperty().get();
    }

    public StringProperty roleProperty() {
        if (role == null) {
            role = new SimpleStringProperty(this, "role");
        }
        return role;
    }

    public void setRole(String role) {
        roleProperty().set(role);
    }

}

Below is solution by setting custom dimensions to stage and dialog. It works for the stage but it doesn't work for the dialog.

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;

public class Demo extends Application {

    private Stage primaryStage;
    private Dialog<String> dialog;
    private Button createUserButton = new Button("Create User");

    @Override
    public void start(Stage primaryStage) throws Exception {
        this.primaryStage = primaryStage;
        Text usersLabel = new Text("Current Users:");
        TableColumn<User, String> indexColumn = new TableColumn<User, String>("No.");
        indexColumn.setMaxWidth(1f * Integer.MAX_VALUE * 10);
        indexColumn.setCellValueFactory(p -> p.getValue().indexProperty());
        TableColumn<User, String> userNameColumn = new TableColumn<User, String>("User Name");
        userNameColumn.setMaxWidth(1f * Integer.MAX_VALUE * 60);
        userNameColumn.setCellValueFactory(p -> p.getValue().userNameProperty());
        TableColumn<User, String> roleColumn = new TableColumn<User, String>("Role");
        roleColumn.setMaxWidth(1f * Integer.MAX_VALUE * 30);
        roleColumn.setCellValueFactory(p -> p.getValue().roleProperty());
        TableView<User> tableView = new TableView<User>();
        tableView.getColumns().add(indexColumn);
        tableView.getColumns().add(userNameColumn);
        tableView.getColumns().add(roleColumn);
        tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        Text dummyLabel = new Text("");
        VBox leftPane = new VBox(5);
        leftPane.getChildren().addAll(usersLabel, tableView);
        VBox rightPane = new VBox(20);
        rightPane.setFillWidth(true);
        rightPane.getChildren().addAll(dummyLabel, createUserButton);
        GridPane mainPane = new GridPane();
        mainPane.setPadding(new Insets(10, 0, 0, 10));
        mainPane.setHgap(20);
        mainPane.add(leftPane, 0, 0);
        mainPane.add(rightPane, 1, 0);
        float width = 372f;
        float height = 470f;
        Scene scene = new Scene(mainPane, width, height);
        primaryStage.setScene(scene);
        primaryStage.setResizable(false);
        Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
        primaryStage.setX((screenBounds.getWidth() - width) / 2);
        primaryStage.setY((screenBounds.getHeight() - height) / 2);
        showDialog();
    }

    private void showDialog() {
        dialog = new Dialog<>();
        dialog.setTitle("Login");
        dialog.setHeaderText("Please enter User Name and Password to login.");
        dialog.setResizable(false);
        Label userNameLabel = new Label("User Name:");
        Label passwordLabel = new Label("Password:");
        TextField userNameField = new TextField();
        PasswordField passwordField = new PasswordField();
        GridPane grid = new GridPane();
        grid.setAlignment(Pos.CENTER);
        grid.setHgap(10);
        grid.setVgap(10);
        grid.setPadding(new Insets(20, 35, 20, 35));
        grid.add(userNameLabel, 1, 1);
        grid.add(userNameField, 2, 1);
        grid.add(passwordLabel, 1, 2);
        grid.add(passwordField, 2, 2);
        dialog.getDialogPane().setContent(grid);
        dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
        Button okButton = (Button) dialog.getDialogPane().lookupButton(ButtonType.OK);
        okButton.addEventFilter(ActionEvent.ACTION, event -> {
            login(userNameField.getText().trim(), passwordField.getText());
            event.consume();
        });
        dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
        float width = 509f;
        float height = 168f;
        dialog.setWidth(width);
        dialog.setHeight(height);
        Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
        dialog.setX((screenBounds.getWidth() - width) / 2);
        dialog.setY((screenBounds.getHeight() - height) / 2);
        dialog.showAndWait();
    }

    private void login(String userName, String password) {
        dialog.getDialogPane().setDisable(true);
        dialog.getDialogPane().getScene().setCursor(Cursor.WAIT);
        Task<Boolean> task = new Task<Boolean>() {
            @Override
            public Boolean call() {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException exception) {
                }
                return Boolean.TRUE;
            }
        };
        task.setOnSucceeded(e -> {
            Boolean success = task.getValue();
            dialog.getDialogPane().setDisable(false);
            dialog.getDialogPane().getScene().setCursor(Cursor.DEFAULT);
            if (success.booleanValue()) {
                Platform.runLater(() -> {
                    primaryStage.show();
                });
            } else {
                Alert alert = new Alert(AlertType.ERROR);
                alert.setTitle("Login Error");
                alert.setHeaderText("Unable to login.");
                alert.showAndWait();
            }
        });
        new Thread(task).start();
    }

    public static void main(String[] arguments) {
        Application.launch(arguments);
    }

}

class User {

    private StringProperty index;

    private StringProperty userName;

    private StringProperty role;

    public String getIndex() {
        return indexProperty().get();
    }

    public StringProperty indexProperty() {
        if (index == null) {
            index = new SimpleStringProperty(this, "index");
        }
        return index;
    }

    public void setIndex(String index) {
        indexProperty().set(index);
    }

    public String getUserName() {
        return userNameProperty().get();
    }

    public StringProperty userNameProperty() {
        if (userName == null) {
            userName = new SimpleStringProperty(this, "userName");
        }
        return userName;
    }

    public void setUserName(String userName) {
        userNameProperty().set(userName);
    }

    public String getRole() {
        return roleProperty().get();
    }

    public StringProperty roleProperty() {
        if (role == null) {
            role = new SimpleStringProperty(this, "role");
        }
        return role;
    }

    public void setRole(String role) {
        roleProperty().set(role);
    }

}

JKostikiadis's solution:

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Screen;
import javafx.stage.Stage;

public class TestApp extends Application {

    private static final double WIDTH = 316.0;
    private static final double HEIGHT = 339.0;

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

    @Override
    public void start(Stage stage) throws Exception {

        HBox pane = new HBox();
        pane.setAlignment(Pos.CENTER);

        Button b = new Button("click me");
        b.setOnAction(e -> {
            showDialog();
        });

        pane.getChildren().add(b);

        Scene scene = new Scene(pane, 300, 300);

        stage.setScene(scene);

        centerStage(stage, WIDTH, HEIGHT);
        stage.show();


    }

    private void showDialog() {
        Alert dialog = new Alert(AlertType.ERROR);
        dialog.setTitle("Error Dialog");
        dialog.setHeaderText("Look, an Error Dialog");
        dialog.setContentText("Ooops, there was an error!\nOoops, there was an error!");

        Stage stage = (Stage) dialog.getDialogPane().getScene().getWindow();
        centerStage(stage, -10000, -10000);
        dialog.show();
        System.out.println(stage.getWidth() + " " + stage.getHeight());
        dialog.hide();
        centerStage(stage, stage.getWidth(), stage.getHeight());        
        dialog.showAndWait();

    }

    private void centerStage(Stage stage, double width, double height) {
        Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
        stage.setX((screenBounds.getWidth() - width) / 2);
        stage.setY((screenBounds.getHeight() - height) / 2);
    }
}

Upvotes: 2

Views: 13409

Answers (3)

purring pigeon
purring pigeon

Reputation: 4209

You can center a stage on another stage before rendering it by applying the css which will provide you with the width/height.

For example.

From where you create the stage:

WindowHelper.centerChildWindowOnStage(stage, primaryStage);  //assuming primary is the stage you want to center on
stage.show();

below is the code to center the unshown window (assume this is on a WindowHelper class to be reused in the app).

public static void centerChildWindowOnStage(Stage stage, Stage primaryStage ) {

    if(primaryStage == null){
        return;
    }

    double x = stage.getX();
    double y = stage.getY();

    // Firstly we need to force CSS and layout to happen, as the dialogPane
    // may not have been shown yet (so it has no dimensions)
    stage.getScene().getRoot().applyCss();
    stage.getScene().getRoot().layout();

    final Scene ownerScene = primaryStage.getScene();
    final double titleBarHeight = ownerScene.getY();

    // because Stage does not seem to centre itself over its owner, we
    // do it here.

    // then we can get the dimensions and position the dialog appropriately.
    final double dialogWidth = stage.getScene().getRoot().prefWidth(-1);
    final double dialogHeight = stage.getScene().getRoot().prefHeight(dialogWidth);

    final double ownerWidth = primaryStage.getScene().getRoot().prefWidth(-1);
    final double ownerHeight = primaryStage.getScene().getRoot().prefHeight(ownerWidth);

    if(dialogWidth < ownerWidth){
        x = primaryStage.getX() + (ownerScene.getWidth() / 2.0) - (dialogWidth / 2.0);
    }else {
        x = primaryStage.getX();
        stage.setWidth(dialogWidth);
    }

    if(dialogHeight < ownerHeight){
        y = primaryStage.getY() + titleBarHeight / 2.0 + (ownerScene.getHeight() / 2.0) - (dialogHeight / 2.0);
    }else {
        y = primaryStage.getY();
    }

    stage.setX(x);
    stage.setY(y);
}

Upvotes: 2

JKostikiadis
JKostikiadis

Reputation: 2917

Well because you ask me in the commends I am going to provide an example of setting the Stage ( main application or dialog ) to the center of the screen by early initialization of their dimensions.

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Screen;
import javafx.stage.Stage;

public class TestApp extends Application {

    private static final double WIDTH = 316.0;
    private static final double HEIGHT = 339.0;

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

    @Override
    public void start(Stage stage) throws Exception {

        HBox pane = new HBox();
        pane.setAlignment(Pos.CENTER);

        Button b = new Button("click me");
        b.setOnAction(e -> {
            showDialog();
        });

        pane.getChildren().add(b);

        Scene scene = new Scene(pane, 300, 300);

        stage.setScene(scene);

        centerStage(stage, WIDTH, HEIGHT);
        stage.show();

        System.out.println(stage.getWidth() + " " + stage.getHeight());
    }

    private void showDialog() {
        Alert dialog = new Alert(AlertType.ERROR);
        dialog.setTitle("Error Dialog");
        dialog.setHeaderText("Look, an Error Dialog");
        dialog.setContentText("Ooops, there was an error!");

        Stage stage = (Stage) dialog.getDialogPane().getScene().getWindow();
        centerStage(stage, 366, 175);

        dialog.showAndWait();

    }

    private void centerStage(Stage stage, double width, double height) {
        Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
        stage.setX((screenBounds.getWidth() - width) / 2);
        stage.setY((screenBounds.getHeight() - height) / 2);
    }
}

In the example above you will see that I have specify the application dimensions to 300,300 but I am using for width = 316.0 and height = 339.0 and you might wondering why. It's because the stage size will always be a little bigger than the Scene ( borders + Title bar etc ) so in order to find the real width and height of the Stage you will have to print the dimensions of the stage after you show it. The same logic is happening to the Dialog.

Important : Of course you could forget all about the above and just do :

stage.setWidth(300); // or a variable here 
stage.setHeight(300);

But this will affect your internal components cause if previously the scene's components had a size of 300,300 now they are going to be squeezed to something less in order to make the stage to fix the size of 300,300 so in that case yes it might affect the way your application looks like.

In the past I was searching for a way to find the dimension of a label before I show it. I found out that it was possible to get it's dimensions by adding it to the Scene and then call

labe.impl_processCSS(true);
System.out.println(labe.prefWidth(-1) + "/" + labe.prefHeight(-1));

Now If I try to do the same for the main pane in the above application it shows 59/25 which are the dimensions of the button itself so this approach is not going to work in case of someone wondering about it.

Edit :

I don't really want to show this "hack" cause I find it stupid and i am sure there is a better way, but until I find it here you go :

private void showDialog() {
    Alert dialog = new Alert(AlertType.ERROR);
    dialog.setTitle("Error Dialog");
    dialog.setHeaderText("Look, an Error Dialog");
    dialog.setContentText("Ooops, there was an error!\nOoops, there was an error!");

    Stage stage = (Stage) dialog.getDialogPane().getScene().getWindow();
    centerStage(stage, -10000, -10000);
    dialog.show();
    centerStage(stage, stage.getWidth(), stage.getHeight());   
}

Upvotes: 0

shaka-b
shaka-b

Reputation: 116

Unfortunately, you have to wait for the width/height of the Window (or Dialog) to be computed as well as for the Window to be shown. Since the Window is visible you will always notice the window moving when updating the xy-position.

Doing the update when the WindowEvent.WINDOW_SHOWN event is fired might provide a better result:

   final Window window = dialog.getDialogPane().getScene().getWindow();

    window.addEventHandler(WindowEvent.WINDOW_SHOWN, new EventHandler<WindowEvent>() {
        @Override
        public void handle(WindowEvent event) {
            Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
            window.setX((screenBounds.getWidth() - window.getWidth()) / 2);
            window.setY((screenBounds.getHeight() - window.getHeight()) / 2);

        }
    });

And for the primaryStage

    primaryStage.addEventHandler(WindowEvent.WINDOW_SHOWN, new EventHandler<WindowEvent>() {

    @Override
    public void handle(WindowEvent event) {
        Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
        primaryStage.setX((screenBounds.getWidth() - primaryStage.getWidth()) / 2);
        primaryStage.setY((screenBounds.getHeight() - primaryStage.getHeight()) / 2);
    }
});
primaryStage.show();

But as mentioned by JKostikiadis, a better and proper solution might be to compute your own dimension with respect to the current screen size.

Here is the small improvement I can see. When running your demo on my machine, the movement is erratic:

enter image description here

I can see a small improvement when using WindowEvent.WINDOW_SHOWN (without usingPlatform.runLater for the first Dialog):

enter image description here

Anyway, I don't think using Platform.runLater for displaying the first window is ideal as there is no guarantee that showAndWait() will always be executed before the Runnable

Upvotes: 5

Related Questions