Subra
Subra

Reputation: 105

How to get a reference to the nodes described via fx:id in javafx?

I have a reduced example of my problem where the code references from fx:id are not null when the initialize is called but then go to null right after the function call. What is the correct way to get such references? This is sample.fxml

<GridPane alignment="center" hgap="10" vgap="10" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="sample.Main">
   <columnConstraints>
      <ColumnConstraints />
   </columnConstraints>
   <rowConstraints>
      <RowConstraints />
   </rowConstraints>
   <children>
      <Text fx:id="textRef" strokeType="OUTSIDE" strokeWidth="0.0" text="Hello world" />
   </children>
</GridPane>

And this is the Main.java which is declared as its controller.

public class Main extends Application implements Initializable{
    @FXML
    public Text textRef;

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();

        this.someNewFunction();
    }

    private void someNewFunction() {
        this.textRef.setText("Changed in somNewFunction");
    }


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

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        this.textRef.setText("Changed at initialize");
    }
}

The text ref is valid inside the initialize call but throws a nullpointerexception when inside the someNewFunction.

Upvotes: 2

Views: 1669

Answers (2)

fabian
fabian

Reputation: 82461

The Main instance that is launched is a different object than the Main instance created by the FXMLLoader to be used as controller.

IMHO it would be better to get the controller from the FXMLLoader after loading the fxml and also use a class different to the Application as controller:

public class MainController implements Initializable {

    ...   

}
<GridPane alignment="center" hgap="10" vgap="10" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="sample.MainController">
   ...
</GridPane>
@Override
public void start(Stage primaryStage) throws Exception {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
    Parent root = loader.load();
    primaryStage.setTitle("Hello World");
    primaryStage.setScene(new Scene(root, 300, 275));
    primaryStage.show();

    MainController controller = loader.getController();

    controller.someNewFunction();
}

However you could also specify the controller that should be used with the fxml:

<GridPane alignment="center" hgap="10" vgap="10" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
   ...
</GridPane>
@Override
public void start(Stage primaryStage) throws Exception {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
    loader.setController(this);
    Parent root = loader.load();
    primaryStage.setTitle("Hello World");
    primaryStage.setScene(new Scene(root, 300, 275));
    primaryStage.show();

    this.someNewFunction();
}

Upvotes: 4

Francisco Valle
Francisco Valle

Reputation: 643

You are executing the someNewFunction outside the javafx loop. I mean, after the show method ends, the main thread takes control and the javafx components no longer exists (they are destroyed as is supposed that they are no longer needed). You have to bind the someNewFunction to some event attached to some FXML element reference (i.e. a Button via the setOnAction on SceneBuilder, or via code), place the method call inside the initialize method, or simply move the call before the primaryStage.show() line.

Hopes this helps

Upvotes: 0

Related Questions