Java – How to open an additional window in a JavaFX FXML app

fxmljavajavafxscenewindow

In my JavaFX FXML app, I want a secondary window to pop up when the user clicks a menu item somewhere in the primary window so that the user can enter some input into it, which will be then fed to the application upon clicking a button, and the secondary window will be closed.

All the tutorials out there are slightly off the mark. They describe how to do it in pure JavaFX, which is apparently different from the way you'd use with FXML, or they explain how to switch Scenes, which closes the old Scene. I'd guess it would be simple enough, along the lines of defining the FXML layout and its Controller, creating a new Scene with them, and then calling something like

theStage.showScene(userInputWindow);

but a working solution seems much more complicated, and the reasoning behind it different from my assumptions. For example in this tutorial, I don't really understand why did they put that cast in there, what would the FXMLLoader() actually do, or indeed how would I adapt any of this to the task at hand. Also, the resource states the "the stage can only show 1 scene at a time". It seems extremely unlikely to me that a JavaFX app could lack such a trivial feature as showing a new window without closing the old one. Maybe I misunderstood something about what a Stage and a Scene are and what they can do. So I need to know:

  1. How to achieve the effect described above in code?

  2. What is the reasoning behind the solution; what do all the things involved do there?

Best Answer

You can only show one scene in a Stage, but you can create multiple stages. If you want to use fxml for your secondary window, you should get your hands on the controller instance and design the controller in a way that allows you to access the user's input. You can use Stage.showAndWait to "wait for the user to complete the input".

Example

Application start method

Note that here it's just a button that opens the new window, but you could use similar logic in the onAction event handler of a menu item. (You need to use someNode.getScene().getWindow() to get access to the parent window for Stage.initOwner in this case; someNode is a arbitrary Node in the parent window; you could get the node from the event (((Node)event.getTarget())) or use a node that you know is in the scene; in InputController.submit mealField is used for this purpose)

@Override
public void start(Stage primaryStage) {
    Button btn = new Button();
    btn.setText("Choose favorite meal");

    Label label = new Label("I don't know your favorite meal yet!");

    btn.setOnAction((ActionEvent event) -> {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("input.fxml"));
        Scene newScene;
        try {
            newScene = new Scene(loader.load());
        } catch (IOException ex) {
            // TODO: handle error
            return;
        }

        Stage inputStage = new Stage();
        inputStage.initOwner(primaryStage);
        inputStage.setScene(newScene);
        inputStage.showAndWait();

        String meal = loader.<InputController>getController().getMeal();

        label.setText(meal == null ? "C'mon, tell me your favourite meal already!" : "Your favourite meal is "+meal+". Interesting!");
    });

    VBox root = new VBox(label, btn);
    root.setSpacing(10);
    root.setPadding(new Insets(10));
    root.setPrefWidth(300);

    Scene scene = new Scene(root);

    primaryStage.setScene(scene);
    primaryStage.show();
}

controller

public class InputController {
    @FXML
    private TextField mealField;
    private boolean mealChosen;

    @FXML
    private void submit() {
        mealChosen = true;
        mealField.getScene().getWindow().hide();
    }

    public String getMeal() {
        return mealChosen ? mealField.getText() : null;
    }

}

fxml

<GridPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mypackage.InputController" vgap="10" hgap="10"  >
  <columnConstraints>
    <ColumnConstraints prefWidth="150.0" />
    <ColumnConstraints prefWidth="150.0" />
  </columnConstraints>
   <children>
      <TextField GridPane.columnIndex="1" fx:id="mealField" onAction="#submit" />
      <Button mnemonicParsing="false" text="Ok" GridPane.columnIndex="1" GridPane.rowIndex="1" onAction="#submit" />
      <Label text="Your favourite meal" />
   </children>
   <padding>
      <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
   </padding>
</GridPane>
Related Topic