2
votes

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?

2
Can you show what you have so far? Are your menus and menu items defined in FXML? Do you have a controller class? Do you have a handler method for the relevant menu item? There's really nothing different here to the things you say you've already seen, so without showing a basic structure for how far you've got, it's really hard for anyone to know which part you are missing.James_D
Oh, btw, the tutorial you linked is pretty full of errors and statements that simply aren't true: my advice is to ignore it (I have pointed these out to the author and he has ignored them, so...).James_D
"The stage can only show one scene at a time" is a true statement though, but I don't understand why you think it means you can't open a second window. In JavaFX, a Stage is a window. Each one has only one scene. But you can, obviously, create and show as many stages as you want.James_D

2 Answers

5
votes

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>
2
votes

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.