1

I'm new to JavaFX. This was easy to do without FXML, but the FXML controllers are stumping me.

What I'm trying to do: Set up a main window that has a button. When clicked, the button launches a second popup window in which the user submits a value. Upon closing the second window (done currently with a button click on the pop-up), I'd like the user's input to be passed back to the main controller-- the main window that is already open.

So far, I've got 2 .fxml files(one for a main window the other for a popup), and the corresponding controllers: MainWindowController:

public class MainController implements Initializable {

@FXML
public Label label;
@FXML
private Button button;


@FXML
private void popBtnClick(ActionEvent event) throws IOException {
    //creates new pop-up window
    Stage popupSave = new Stage();
    popupSave.initModality(Modality.APPLICATION_MODAL);
    popupSave.initOwner(ComWins.stage);

    FXMLLoader loader = new FXMLLoader();
    loader.setLocation(getClass().getResource("PopUp.fxml"));
    Parent root = loader.load();

    PopUpController controller = loader.getController();

    //calls a method in the PopUpController, and uses it to pass data to 
    //the Popup window.
    controller.dataToPopUp(7);

    Scene scene = new Scene(root);
    popupSave.setScene(scene);
    popupSave.showAndWait();
}

I also tried calling this method from the popup window with no success in 
changing Main's label.
public void dataPass(String name){
    label.setText(name);
}


@Override
public void initialize(URL url, ResourceBundle rb) {
    // TODO
}    

}

And PopUpController:

public class PopUpController implements Initializable {

@FXML
private Button ok_btn; 
@FXML
public TextField input_tf;
@FXML
private String input;




@FXML
private void okBtnClick() throws IOException {
    input = input_tf.getText();

    /*my attempt to pass the variable-- using a loader to get the 
     controller and then referencing the public label. */ 
    FXMLLoader loader = new FXMLLoader();
    loader.setLocation(getClass().getResource("Main.fxml"));
    Parent root = loader.load();


    FXMLDocumentController controller = loader.getController();

    //this line works, and retrieves the label's text.
    String temp = controller.label.getText();

    //but this line does not work. Why? 
    controller.label.setText(input);


    //closes this pop-up
    Stage stage = (Stage)input_tf.getScene().getWindow();
    stage.close();


}

//this method is called in the maincontroller and used to pass data to 
//the popup's textfield.
public void dataToPopUp(int x){
    input_tf.setText(Integer.toString(x));
}


 @Override
public void initialize(URL url, ResourceBundle rb) {
    // TODO
}    

}

Using the above, Main passes ('7') into the PopUp's textfield. But if the user enters something else into the textfield, I cannot seem to get that data back to Main. This is like having a Settings Pop-up window, and then passing the user's selections from the Settings popup back to the main window. I just cannot figure out how to pass things back to the main window.

I am not using SpringBoot, but thanks for the suggestion.

Thank you in advance!

FStone
  • 21
  • 6

3 Answers3

1

If you are using Spring Boot, MPV Java on YouTube has great examples of connecting your controllers together allowing you to pass information cleanly and easily between them.
In my application I've been able to implement these examples. Each controller is registered as a bean using @Component which means you can @Autowire the controller into another controller. In your main controller I would recommend setting up some basic getters/setters to allow outside interaction with your fields so other controllers can "talk" to the main controller.

A really basic example would be:

@Component
public class MyMainController {
    @FXML private TextField exampleTextField;
    ...
    ...
    /* Get the text of a field from this controller: can be accessed from another controller */
    public String getExampleTextField() {
        exampleTextField.getText();
    }
    /* Set the text of a field on this controller: can be accessed from another controller */
    public void setExampleTextField(String text) {
        exampleTextField.setText(text);
    }
}
@Component
public class AnotherController {
    @Autowired private MyMainController myMainController;
    ...
    ...
    public void someMethod(String newText) {
        // Do some work here and set some text back to the main controller
        myMainController.setExampleTextField(newText);
    }
}

MPV Java does a much better job of explaining this concept.
https://www.youtube.com/watch?v=hjeSOxi3uPg

1

I reviewed the suggestions and was unable to get anything to work for me-- many of the concepts were over my head, as I'm new to Java. After several attempts, I was able to get a fairly simple solution to the problem. It is likely far from best practices, but it works:

In the main window's controller, use the popup's controller to call the Pop Up's string variable and set it as the label's text (label.setText(controller.test)). The string variable has to be public, and is set once the pop-up is closed by the button click. See the code below:

Main.fxml:

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="switchingpass.MainController">
    <children>
        <Button fx:id="button" layoutX="126" layoutY="90" onAction="#popBtnClick" text="Click Me!" />
        <Label fx:id="label2" layoutX="126" layoutY="120" minHeight="16" minWidth="69" />
      <Label fx:id="label" layoutX="143.0" layoutY="38.0" text="Label" />
    </children>
</AnchorPane>

MainController:

public class MainController implements Initializable {

@FXML
public Label label;
@FXML
private Button button;

@FXML
private void popBtnClick(ActionEvent event) throws IOException {
    //creates new pop-up window
    Stage popupSave = new Stage();
    popupSave.initModality(Modality.APPLICATION_MODAL);
    popupSave.initOwner(SwitchingPass.stage);

    FXMLLoader loader = new FXMLLoader();
    loader.setLocation(getClass().getResource("PopUp.fxml"));
    Parent root = loader.load();

    PopUpController controller = loader.getController();

    Scene scene = new Scene(root);
    popupSave.setScene(scene);
    popupSave.showAndWait();

    //after the popup closes, this will run, setting the label's text to the popup's test variable, which is public.
    label.setText(controller.test);
}

@Override
public void initialize(URL url, ResourceBundle rb) {
    // TODO
}    

}

PopUp.fxml:

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="switchingpass.PopUpController">
   <children>
      <TextField fx:id="input_tf" layoutX="207.0" layoutY="65.0" />
      <Button fx:id="close_btn" layoutX="268.0" layoutY="185.0" mnemonicParsing="false" onAction="#closeBtnClick" text="Close" />
   </children>
</AnchorPane>

PopUpController:

public class PopUpController implements Initializable {

@FXML
public Button close_btn; 
@FXML
public TextField input_tf;
@FXML
public String test;


@FXML
private void closeBtnClick() throws IOException {

    //stores textfield input as a string    
    test = input_tf.getText();

    Stage stage = (Stage)input_tf.getScene().getWindow();
    stage.close();
}

 @Override
public void initialize(URL url, ResourceBundle rb) {
    // TODO
}    

}

Please note that the code's main class that extends Application must declare the stage variable as a public static variable:

public class SwitchingPass extends Application {

    //this is necessary to initialize the owner of the popup
    public static Stage stage;

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));

        Scene scene = new Scene(root);

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

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

}

Again, probably not the best way to accomplish this, but it works, and perhaps it is helpful to someone else.

FStone
  • 21
  • 6
  • As soon as the stage closes the controller's variable is set to null. At least, on my version that's the case (Java 11.0 & FXML 15.0.1) – crapolantern Feb 15 '21 at 19:20
0

I did that this way. First create an interface

public interface DataSender {
 void send(String data);
}

and then implement that interface to your Main Window Controller

public class FXMLDocumentController implements Initializable,DataSender {

@FXML
private Label label;

@FXML
private void handleButtonAction(ActionEvent event) {

    try {
        Stage popupSave = new Stage();
        popupSave.initModality(Modality.NONE);
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("PopUp.fxml"));
        Parent root = loader.load();

        PopUpController controller = loader.getController();
        controller.setX(7);
        //this is the line use to get data from popup
        controller.setSendDataSender(this);

        Scene scene = new Scene(root);
        popupSave.setScene(scene);
        popupSave.show();
    } catch (IOException ex) {
        Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
    }
}


//this method can call from popus controller
@Override
public void send(String data) {
    label.setText(data);
}

@Override
public void initialize(URL url, ResourceBundle rb) {
    // TODO
}
}

and then override implemented interface method and inside the method you can change Main window UI data I changed label text.

and then create variable in pupup controller with type DataSender(created interface befor) and create method to set that interface

public class PopUpController implements Initializable {

private int x;

//this is the variable
private DataSender dataSender;


@FXML
private Button btnOk;
@FXML
private TextField txtText;

@Override
public void initialize(URL url, ResourceBundle rb) {
    btnOk.setOnAction(e->{
        //When Click this button will call Main Window send method
        dataSender.send(txtText.getText());
    });
}

public void setX(int x){
    this.x=x;
}

//this is the method to set variable value from Main Window
public void setSendDataSender(DataSender ds){
    this.dataSender=ds;
}

}

and then when popup window button clicked then call send method with data and then will change main window label text. This solution is work for me.hope this useful for you also .

  • I tried this by creating the interface in its own class (i'm new to using interfaces), so please tell if that was correct. I made the other changes accordingly and I get an error on: <> (this) is causing an incompatible types error: FXMLDocumentController cannot be converted to DataSender I've no experience using "this" so I'm at a lost to troubleshoot it myself, reading up on it now. any idea what I may be doing wrong? – FStone Jan 30 '20 at 20:48
  • create interface in separate file.Don't create it in own class and make sure you implemented that interface to the class otherwise it is not working. [public class FXMLDocumentController implements Initializable,DataSender] – Prabath Madushan Feb 03 '20 at 16:36
  • Keyword 'THIS' in Java is a reference variable that refers to the current object.in this code setSendDataSender(DataSender ds); method request DataSender object when we implement the DataSender interface to FXMLDocumentController it become a DataSender so then we can use this key word as a DataSender because FXMLDocumentController is a DataSender. – Prabath Madushan Feb 03 '20 at 16:40