Dennis L
Dennis L

Reputation: 37

JavaFX Pop Up window throws exception when called again

I am currently trying to write a simple application in Java using JavaFX.

In the application I want to have a pop up window that prompts the user for input. This works fine, as long as the user doesn't try to open the pop up window again. If he does that the following error occurs:

Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Grid hgap=5.0, vgap=0.0, alignment=TOP_LEFTis already set as root of another scene

And here is the code that opens the window:

In the main view controller:

btAddChat.setOnAction(e -> lvItems.add(PopUpVC.display()));

And in the view controller for the pop up:

public class PopUpVC
{
    private static final GridPane root = new GridPane();
    private static final TextField tfInput = new TextField();
    private static final Button btOK = new Button("OK");
    private static String result;
    private static Stage primaryStage = new Stage();


public static String display()
{
    Scene scene = new Scene(root, 200, 50);
    MainVC mvc = new MainVC();
    root.setPadding(new Insets(10));

    root.setHgap(5);

    tfInput.setPrefWidth(scene.getWidth()*0.65);

    root.add(btOK, 0, 0, 1, 1);
    root.add(tfInput, 1, 0, 1, 1);

    btOK.setOnAction(e ->
    {
        if(!tfInput.getText().equals(""))
        {
            primaryStage.close();
        }
    });

    primaryStage.setResizable(false);
    primaryStage.setScene(scene);
    primaryStage.showAndWait();
    return tfInput.getText();
}

I only copied the most important part, the actual error is a lot longer. I know what is causing the error (trying to open a window with the same root throws an error because the root is already in use), I just can't figure out how to resolve it.

Here is a picture of the application:

The top left button opens the pop up

The top left button opens the pop up.

The pop up:

The pop up

If there are any suggestions on how to resolve this issue I would be very glad to hear them, if there should be any another way to open a pop up window that prompts the user for text input, I would also be glad to hear about those!

Thanks a lot in advance!

Upvotes: 0

Views: 350

Answers (1)

DaveB
DaveB

Reputation: 2143

The OP's solution:

I figured it out, easy thing really, I added a parameter to the start function so when I call it I just give it a new GridPane() and it works perfectly fine.

Is really the wrong approach. As @James_D pointed out, static is not a good idea for anything like this. Trying to keep with the original design as much as possible, I'd suggest this, which builds the PopUp just once, and re-displays it:

public class PopUp extends Application {

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

    @Override
    public void start(Stage primaryStage) {
        Button button = new Button("Click Me");
        PopUpVC popUpVC = new PopUpVC();
        button.setOnAction(evt -> {
            popUpVC.display();
        });
        primaryStage.setScene(new Scene(button));
        primaryStage.show();
    }

    public class PopUpVC {
        private final TextField tfInput = new TextField();
        private Stage primaryStage = new Stage();


        public PopUpVC() {
            GridPane root = new GridPane();
            Scene scene = new Scene(root, 200, 50);
            root.setPadding(new Insets(10));
            root.setHgap(5);
            tfInput.prefWidthProperty().bind(scene.widthProperty().multiply(0.65));
            Button btOK = new Button("OK");
            root.add(btOK, 0, 0, 1, 1);
            root.add(tfInput, 1, 0, 1, 1);
            btOK.setOnAction(e -> {
                if (!tfInput.getText().equals("")) {
                    primaryStage.close();
                }
            });
            primaryStage.setResizable(false);
            primaryStage.setScene(scene);
        }

        public String display() {
            primaryStage.showAndWait();
            return tfInput.getText();
        }
    }
}

When you have something that's completely static, like PopUpVC, that's a hint that it's just code that you've moved out of somewhere else. This is useful, and a good practice, if you are calling the methods from a variety of other classes, when it saves code duplication. However, you shouldn't have JavaFX elements as static fields in such a class.

In this case, you can do away with PopUpVC and move all of the code into a local method:

public class PopUp extends Application {

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

    @Override
    public void start(Stage primaryStage) {
        Button button = new Button("Click Me");
        button.setOnAction(evt -> {
            displayPopUpVC();
        });
        primaryStage.setScene(new Scene(button));
        primaryStage.show();
    }

    public String displayPopUpVC() {
        TextField tfInput = new TextField();
        Stage primaryStage = new Stage();
        GridPane root = new GridPane();
        Scene scene = new Scene(root, 200, 50);
        root.setPadding(new Insets(10));
        root.setHgap(5);
        tfInput.prefWidthProperty().bind(scene.widthProperty().multiply(0.65));
        Button btOK = new Button("OK");
        root.add(btOK, 0, 0, 1, 1);
        root.add(tfInput, 1, 0, 1, 1);
        btOK.setOnAction(e -> {
            if (!tfInput.getText().equals("")) {
                primaryStage.close();
            }
        });
        primaryStage.setResizable(false);
        primaryStage.setScene(scene);
        primaryStage.showAndWait();
        return tfInput.getText();
    }
}

This behaves slightly different from the first solution because it doesn't retain the text in the TextField between calls. But the default text could be passed in a parameter if you wanted.

But, also as @James_D has pointed out, TextInputDialog does exactly what you want. Plus it does it with about 4 lines of code:

public class PopUp extends Application {

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

    @Override
    public void start(Stage primaryStage) {
        Button button = new Button("Click Me");
        TextInputDialog dialog = new TextInputDialog();
        dialog.setHeaderText(null);
        dialog.setTitle("Chat");
        dialog.setGraphic(null);
        button.setOnAction(evt -> {
            dialog.showAndWait();
        });
        primaryStage.setScene(new Scene(button));
        primaryStage.show();
    }
}

Upvotes: 1

Related Questions