Alex
Alex

Reputation: 752

FXML object getting deleted before its used

Here is my code

@FXML
public ScrollPane mainScrollPane;
@FXML
public Label dateScrollerLabel;
@FXML
public HBox calendarContainer;

int x = 5;
@Override
public void start(Stage primaryStage) throws Exception {
    scene = JavaFXUtils.createScene(1000, 600, "Main.fxml", this);
    primaryStage.setScene(scene);
    primaryStage.getIcons().add(new Image(Main.class.getResourceAsStream("/assets/resources/icon/icon_256.png")));
    primaryStage.setTitle("HWP");
    primaryStage.show();

    //*****//
    scene.addEventHandler(KeyEvent.KEY_PRESSED, this::keyPressed);
}

public void initialize() {
    System.out.println(x); // not null
    System.out.println(calendarContainer); // not null
    currentSchedule = new Schedule(mainScrollPane, dateScrollerLabel, calendarContainer); // no nullpointer exception
}

private void keyPressed(KeyEvent keyEvent) {
    switch (keyEvent.getCode()) {
        case A:
            System.out.println(x); // not null
            System.out.println(calendarContainer); // null
            currentSchedule = new Schedule(mainScrollPane, dateScrollerLabel, calendarContainer); // nullpointer exception
            break;
    }
}  

I am able to run the following line without any errors in the initialize() method:

currentSchedule = new Schedule(mainScrollPane, dateScrollerLabel, calendarContainer);

However, when I run the same exact code afterward, in the keyPressed() method, there is a nullpointer exception thrown. It seems as if calendarContainer became null at some point between initialize() and keyPressed. I checked my code and at no point in my program do I change the value of calendarContainer or re-assign it, it is only created in the FXML file.

<HBox fx:id="calendarContainer" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" styleClass="calendarContainer" />

Why does the FXML object get deleted (maybe a trigger-happy garbage collecter?) but the int does not?

Upvotes: 0

Views: 105

Answers (1)

James_D
James_D

Reputation: 209653

Don't use the Application class as the controller class: it makes it too difficult to keep track of which fields are initialized in which instances of the class.

Why does the FXML object get deleted

It doesn't.

Fields annotated @FXML are only initialized in the controller. When you load the FXML file, the FXMLLoader creates an instance of the class specified by the fx:controller attribute, creates the objects corresponding to the elements in the FXML, and then sets the fields in the controller to the objects it created. Finally, it calls initialize() on the controller.

So in this case, calendarContainer is initialized in the controller (which is why you see a non-null value in your initialize() method), but it is never initialized in the instance of the Application class, on which start() is called. So it doesn't suddenly become null at some point: it was always null.

You should create a separate class for the controller:

public class Controller {

    @FXML
    private ScrollPane mainScrollPane;
    @FXML
    private Label dateScrollerLabel;
    @FXML
    private HBox calendarContainer;

    public void initialize() {
        System.out.println(x); // not null
        System.out.println(calendarContainer); // not null
        currentSchedule = new Schedule(mainScrollPane, dateScrollerLabel, calendarContainer); // no nullpointer exception
    }

    public void keyPressed(KeyEvent keyEvent) {
        switch (keyEvent.getCode()) {
            case A:
                System.out.println(x); // not null
                System.out.println(calendarContainer); // null
                currentSchedule = new Schedule(mainScrollPane, dateScrollerLabel, calendarContainer); // nullpointer exception
                break;
        }
    }  

}

(and update the fx:controller attribute in the FXML file).

Now you Application class can do:

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        // Not sure what this does, but you probably can't use it without some
        // modification.
        // scene = JavaFXUtils.createScene(1000, 600, "Main.fxml", this);

        // assuming path is correct:
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml"));
        Scene scene = new Scene(loader.load(), 1000, 600);

        Controller controller = loader.getController();
        scene.addEventHandler(KeyEvent.KEY_PRESSED, controller::keyPressed);

        primaryStage.setScene(scene);
        primaryStage.getIcons().add(new Image(Main.class.getResourceAsStream("/assets/resources/icon/icon_256.png")));
        primaryStage.setTitle("HWP");
        primaryStage.show();


    }

}

Upvotes: 2

Related Questions