In this application a HBox mainView contains a treeView and an overView containing a Button "A". Pressing the button will make the overView disappear and ViewA appear instead.
main-view.fxml:
<?import javafx.scene.layout.HBox?>
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:id="mainView" fx:controller="com.desktop.controllers.MainController">
<children>
<fx:include source="tree-view.fxml" fx:id="treePane"/>
<fx:include source="overview-view.fxml" fx:id="overviewPane"/>
<fx:include source="A-view.fxml" visible="false" fx:id="APane"/>
</children>
</HBox>
overview-view.fxml:
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:id="overviewPane" fx:controller="com.desktop.controllers.OverviewController">
<children>
<Button fx:id="AButton" text="A" onAction="#handleSwitchToA"/>
</children>
</VBox>
a-view.fxml:
<?import javafx.scene.control.Label?>
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:id="aPane" fx:controller="com.desktop.controllers.AController">
<children>
<Label fx:id="aLabel" text="A"/>
</children>
</VBox>
Since the application is very small I do not want to create multiple scenes and pass the treeView as reference. Instead I want to maintain one scene and just toggle the visibility between the Views A ... etc from the overview. I tried to copy the injection of MainController like in this example
MainController:
public class MainController implements Initializable {
private Pane rootPane;
@FXML
private HBox mainView;
@FXML
private AController aController;
@FXML
private OverviewController overviewController;
@FXML
private VBox overviewPane;
@FXML
private Pane aPane;
@FXML
public void handleSwitchToA() {
overviewController.getOverviewPane().setVisible(false);
overviewController.getOverviewPane().setManaged(false);
aController.getAPane().setVisible(true);
aController.getAPane().setVisible(true);
}
public void setStartPane(Pane rootPane) {
this.rootPane = rootPane;
}
@Override
@FXML
public void initialize(URL location, ResourceBundle resources) {
assert overviewPane != null : "fx:id=\"overviewPane\" was not injected: check your FXML file 'main-view.fxml'.";
overviewController.setMainController(this);
aController.setMainController(this);
}
}
OverViewController:
public class OverviewController implements Initializable {
@FXML
private VBox overviewPane;
private MainController mainController;
public void setMainController(MainController mainController) {
this.mainController = mainController;
}
public VBox getOverviewPane() {
return overviewPane;
}
@FXML
void handleSwitchToA(ActionEvent actionEvent) {mainController.handleSwitchToA();}
@Override
public void initialize(URL location, ResourceBundle resources) {
}
}
TestApplication:
public class TestApplication extends Application {
private MainController mainController;
private TreeController treeController;
private Stage primaryStage;
Pane rootPane;
Scene rootScene;
@Override
public void start(Stage primaryStage) throws IOException {
this.primaryStage = primaryStage;
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/com/desktop/main-view.fxml"));
rootPane = fxmlLoader.load();
rootScene = new Scene(rootPane);
mainController = fxmlLoader.getController();
mainController.setStartPane(rootPane);
primaryStage.setScene(rootScene);
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
}
Unfortunately I run into an initialization issue here:
Caused by: java.lang.NullPointerException: Cannot invoke
"com.desktop.controllers.OverviewController.setMainController(com.desktop.controllers.MainController)" because "this.overviewController" is null
at com.madesktop/com.desktop.controllers.MainController.initialize(MainController.java:67)
at javafx.fxml@19/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2655)
... 12 more
I tried to track the issue by putting println("") into the initialize() methods of the controllers. The order in which the controllers get initialized is 1.OverViewController 2.AController 3.MainController
I cannot figure out why OverViewController is not initialized. Can anybody help out ?
When injecting a nested controller, the field name follows the pattern of <fx:id>Controller
. Since you have:
<fx:include source="overview-view.fxml" fx:id="overviewPane"/>
That means you need to have overviewPaneController
as the name of the field declared in MainController
.
You have a similar problem with aController
. In the FXML file, you have fx:id="APane"
. So, the field name actually needs to be APaneController
. Note the capital A
there, because that's what you have in the FXML file. I recommend fixing the name in the FXML file to aPane
and then use aPaneController
as the field name. That additionally makes it align with the corresponding node field:
@FXML private Pane aPane;
And it makes it so you're following standard Java naming conventions.