0

I start exploring the JavaFX FXML application technology.

I use one main Stage accessed in Main class with Main.getStage() that is invoked in the start of application with the overriden method public void start(Stage stage). Having two public static Scene inside to keep the persistence while switching them.

@Override
public void start(Stage stage) throws Exception {
    STAGE = stage;
    LOGIN = new Scene(FXMLLoader.load(getClass().getResource("Login.fxml")));
    REGISTER = new Scene(FXMLLoader.load(getClass().getResource("Register.fxml")));

    STAGE.setScene(LOGIN);
    STAGE.setTitle("FXApplication");
    STAGE.show();
}

public static Stage getStage() {
    return STAGE;
}

Both Scenes have the same controller class called MainController. Using:

  • Button with fx:id="buttonLoginRegister" to go to the REGISTER Scene
  • Button with fx:id="buttonRegisterBack" to go back to the LOGIN one.

and both having the same onClick event handleButtonAction(ActionEvent event). The TextFields are fields for a username to log in/register.

@FXML private Button buttonLoginRegister;
@FXML private Button buttonRegisterBack;
@FXML private TextField fieldLoginUsername;
@FXML private TextField fieldRegisterUsername;

@FXML
private void handleButtonAction(ActionEvent event) throws IOException {

    Stage stage = Main.getStage();

    if (event.getSource() == buttonLoginRegister) {     
        stage.setScene(Main.REGISTER);
        stage.show();

        // Setting the text, the working way
        TextField node = (TextField) stage.getScene().lookup("#fieldRegisterUsername");
        node.setText(fieldLoginUsername.getText());

        // Setting the text, the erroneous way
        // fieldRegisterUsername.setText(fieldLoginUsername.getText());

    } else {
        stage.setScene(Main.LOGIN);
        stage.show();
    }
}

My goal is to copy the value from the LOGIN TextField to the one in the REGISTER scene. It works well using the code above. However firstly I tried to access the element in the another Scene with:

fieldRegisterUsername.setText(fieldLoginUsername.getText());

And it's erroneous. To be exact, the fieldRegisterUsername is null.

Why are some elements found with the lookup(String id) method and not with @FXML annotation?

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • Sharing controller for different FXMLs (views) is usually discouraged. I'd especially advise against it if you're only starting with JavaFX. Have a look at [this question](http://stackoverflow.com/questions/17914254/javafx-multiple-fxml-and-1-shared-controller). – Itai Jun 12 '16 at 17:00
  • It's arguable, lots of developers use this way and the same number is agianst. Personally I see nothing bad of shared controller in the case of 2 small Scenes. I have read the linked question before, however it is in conflict with my need of persistence. – Nikolas Charalambidis Jun 12 '16 at 17:08
  • Not sure I understand what you mean by "need of persistence". See my answer for explanation of why `lookup` worked in your case. Keep in mind that unless you have set the controller for your `FXMLLoader`, every call to `load` will create a new instance of the controller class. – Itai Jun 12 '16 at 17:15
  • I mean by `persistence` the keeping the content of TextFields same while switching the Scenes and back. – Nikolas Charalambidis Jun 12 '16 at 17:19
  • But you have different fields in the different scenes, don't you? Or do you mean keeping the text in each scene while switching to the other? – Itai Jun 12 '16 at 17:22
  • It's not necessarily in conflict, you could use a model, or possibly pass the value from fieldLoginUsername.getText() as parameter. As a side note, if you'd use two controllers, you could make a method for each button instead of the `if ... else ...`. I can see now why sharing controllers is usually discouraged, it turns ugly rather quickly. – Arjan Jun 12 '16 at 17:22
  • 3
    Passing values, http://stackoverflow.com/questions/28946651/javafx-pass-parameter-and-values-from-one-controller-to-another and http://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml – Arjan Jun 12 '16 at 17:26
  • @NikolasCharalambidis Can you justify your first comment? I've never seen any reputable source that recommends (or even suggests) sharing controllers among multiple FXML files. It frankly just sounds like an anti-pattern. Among other things, you would end up with a method called `initialize` that is invoked more than once on the same object; this is pretty clearly not the intended use. – James_D Jun 12 '16 at 22:20
  • 1
    @James_D I think you're right, [JavaFX' Greg Brown](https://bugs.openjdk.java.net/browse/JDK-8101103): *"The 1:1 association between an FXML document and its controller is by design."* It's from a different context, in reply to Adam Bien (the afterburner.fx guy) who wanted multiple controllers for 1 fxml file. Nonetheless, it clearly states it's 1:1 by design. So it shouldn't be a surprise that doing anything else will lead to problems and ugliness. (Though implementation of Initializable in the controller is optional). Btw, thanks for your answers to JavaFX related questions! – Arjan Jun 14 '16 at 18:15

1 Answers1

2

As mentioned in my comment, sharing a controller between different views is rarely a good idea, and I'd strongly advise you to make a separate controller for each view.

As to your problem itself - you have two instances of your controller class, one for each time you call FXMLLoader.load. Presumably, one view has the fieldLoginUsername TextField, while the other has fieldRegisterUsername.
If the condition of the if statement is met, it means the active scene was the Login scene, thus the controller handling it is the one which has fieldLoginUsername, so naturally fieldRegisterUsername will be null.

But on the first line inside the if clause you change the active scene to the Register one, so by the time you call scene#lookup you are referring to the scene whose controller is the Register controller, the one that does have fieldRegisterUsername.

If you were to call scene#lookup before changing the active scene you would find it returns null as well.

If you must use the same class for controller, you probably want to make sure you only have one instance of that class. That would necessitate using FXMLLoader#setController.

Itai
  • 6,641
  • 6
  • 27
  • 51
  • Thanks for the explanation. If the usage of one controller between different Scanes is a bad idea, would you advice me the way to achieve the same effect using the two separated controllers? I have no idea of passing values easily. – Nikolas Charalambidis Jun 12 '16 at 17:22
  • Arjan linked two excellent answers in a comment to the original question. – Itai Jun 12 '16 at 17:37