Måns Thörnvik Måns Thörnvik - 1 year ago 166
Java Question

JavaFX 8 - Tabpanes and tabs with separate FXML and controllers for each tab

I hope to get some answers regarding having fx:include statements for each tab in a tabpane. I have managed with ease to get content to show up BUT referencing methods of the associated controller class simply gives me a nullpointerreference exception no matter how I structure it. The controllers of the included FXML layouts do not have neither constructor not initialize() methods, are they needed? I tried some different things but always got the same exception.

What I simply did was add a change listener to the tabpane and when a tab was pressed I wanted to populate some textfields with some values gotten from a globally accessible arraylist. Note: the arraylist is not the issue, performing this operation using the main controller works fine.

I'm going to add a code example shortly but cannot right now. Please let me know if you need more info, otherwise I'll post the code later today.

*Edit, here is my code example, taken from another thread here on StackOverflow.
JavaFX TabPane - One controller for each tab

TestApp.java:

public class TestApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(new StackPane());

FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/MainTestWindow.fxml"));
scene.setRoot(loader.load());
MainTestController controller = loader.getController();
controller.init();

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

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


Main controller, where I want to reference the sub controllers.

public class MainTestController {

@FXML private TabPane tabPane;
// Inject tab content.
@FXML private Tab fooTabPage;
// Inject controller
@FXML private FooTabController fooTabPageController;

// Inject tab content.
@FXML private Tab barTabPage;
// Inject controller
@FXML private BarTabController barTabPageController;

public void init() {
tabPane.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends Tab> observable,
Tab oldValue, Tab newValue) -> {
if (newValue == barTabPage) {
System.out.println("Bar Tab page");
barTabPageController.handleButton();
} else if (newValue == fooTabPage) {
System.out.println("Foo Tab page");
fooTabPageController.handleButton();
}
});
}
}


Main view's .fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.Tab?>

<TabPane fx:id="tabPane" fx:controller="controller.MainTestController" xmlns="http://javafx.com/javafx/8.0.40"
xmlns:fx="http://www.w3.org/2001/XInclude">
<tabs>
<Tab fx:id="fooTabPage" text="FooTab">
<fx:include source="fooTabPage.fxml"/>
</Tab>
<Tab fx:id="barTabPage" text="BarTab">
<fx:include source="barTabPage.fxml"/>
</Tab>
</tabs>
</TabPane>


FooTab:

public class FooTabController {
@FXML private Label lblText;

public void handleButton() {
lblText.setText("Byebye!");
}
}


FooTab's .fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>

<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://www.w3.org/2001/XInclude" fx:controller="controller.FooTabController">
<Label fx:id="lblText" text="Helllo"/>
<Button fx:id="btnFooTab" onAction="#handleButton" text="Change text"/>
</VBox>


BarTab:

public class BarTabController {
@FXML private Label lblText;

public void handleButton() {
lblText.setText("Byebye!");
}
}


BarTab's .fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>

<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://www.w3.org/2001/XInclude" fx:controller="controller.BarTabController">
<Label fx:id="lblText" text="Helllo" />
<Button fx:id="btnBarTab" onAction="#handleButton" text="Change text"/>
</VBox>


The above onAction for both FooTab and BarTab works with their respective buttons. When this method (handleButton) is references from the Main controller, that's when I get an exception.

Answer Source

To inject a controller for an included FMXL file, you need an fx:id attribute on the <fx:include> element. The controller will be injected to a field with "Controller" appended to the fx:id value.

If you want to inject the actual Tab too, you need a separate fx:id for that.

So:

<tabs>
    <Tab fx:id="fooTab" text="FooTab">
        <fx:include fx:id="fooTabPage" source="fooTabPage.fxml"/>
    </Tab>
    <Tab fx:id="barTab" text="BarTab">
        <fx:include fx:id="barTabPage" source="barTabPage.fxml"/>
    </Tab>
</tabs>

and

@FXML private TabPane tabPane;
// Inject tab content.
@FXML private Tab fooTab;
// Inject controller
@FXML private FooTabController fooTabPageController;

// Inject tab content.
@FXML private Tab barTab;
// Inject controller
@FXML private BarTabController barTabPageController;
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download