How to open an additional window in a JavaFX FXML app?

10,235

Solution 1

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>

Solution 2

In your first sentence you describe a scenario which looks like it is an ideal candidate for using Dialogs. Did you have a look at the Dialog class? Of course it is possible to open as many windows (aka stages) in JavaFX as you like but for the scenario you describe Dialogs seem to be the easier and better fitting solution.

Share:
10,235
Sargon1
Author by

Sargon1

On my way to become a paid programmer.

Updated on June 15, 2022

Comments

  • Sargon1
    Sargon1 about 2 years

    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?