JavaFX - Wrapping FXML to Java Class Controller
fxml, java, javafx, treeview
Solution
There are a couple of reasons why your application doesn't work:
- You need to make the Controller and the Application separate classes.
- You should allow the FXML system to inject the TreeView instance rather than creating a new instance (as Aaron points out in his answer).
The way you have your application currently structured what will happen is:
- The Java system will create an instance of `MyControllerClass` on startup (and invoke it's `start` method).
- The `FXMLLoader` will create another instance of `MyControllerClass` each time the `myInterface.fxml` file is loaded.
- The `FXMLLoader` will create a new `TreeView` instance and perform the FXML injection on the `locationTreeView` member of the new `MyControllerClass` instance it creates.
- The `FXMLLoader` will try to invoke the `initialize` method on the new `MyControllerClass` (of which you have none).
- The `FXMLLoader` will not invoke the `start` method on the new `MyControllerClass`.
- Your original `start` method invocation on your original `MyControllerClass` will continue processing and create another new `TreeView` instance which it will set the `locationTreeView` member of the old `MyControllerClass` instance to.
I updated your code to make the modifications I suggested above and the code now works. The updated code is available.
A sample screenshot from the running code is:
MyApplicationClass.java
import javafx.animation.*;
import javafx.application.Application;
import javafx.event.*;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.stage.*;
import javafx.util.Duration;
/** Sample application to demonstrate programming an FXML interface. */
public class MyApplicationClass extends Application {
@Override public void start(final Stage stage) throws Exception {
// load the scene fxml UI.
// grabs the UI scenegraph view from the loader.
// grabs the UI controller for the view from the loader.
final FXMLLoader loader = new FXMLLoader(getClass().getResource("myInterface.fxml"));
final Parent root = (Parent) loader.load();
final MyControllerClass controller = loader.<MyControllerClass>getController();
// continuously refresh the TreeItems.
// demonstrates using controller methods to manipulate the controlled UI.
final Timeline timeline = new Timeline(
new KeyFrame(
Duration.seconds(3),
new TreeLoadingEventHandler(controller)
)
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
// close the app if the user clicks on anywhere on the window.
// just provides a simple way to kill the demo app.
root.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent t) {
stage.hide();
}
});
// initialize the stage.
stage.setScene(new Scene(root));
stage.initStyle(StageStyle.TRANSPARENT);
stage.getIcons().add(new Image(getClass().getResourceAsStream("myIcon.png")));
stage.show();
}
/** small helper class for handling tree loading events. */
private class TreeLoadingEventHandler implements EventHandler<ActionEvent> {
private MyControllerClass controller;
private int idx = 0;
TreeLoadingEventHandler(MyControllerClass controller) {
this.controller = controller;
}
@Override public void handle(ActionEvent t) {
controller.loadTreeItems("Loaded " + idx, "Loaded " + (idx + 1), "Loaded " + (idx + 2));
idx += 3;
}
}
// main method is only for legacy support - java 8 won't call it for a javafx application.
public static void main(String[] args) { launch(args); }
}
MyControllerClass.java
import javafx.fxml.FXML;
import javafx.scene.control.*;
/** Sample controller class. */
public class MyControllerClass {
// the FXML annotation tells the loader to inject this variable before invoking initialize.
@FXML private TreeView<String> locationTreeView;
// the initialize method is automatically invoked by the FXMLLoader - it's magic
public void initialize() {
loadTreeItems("initial 1", "initial 2", "initial 3");
}
// loads some strings into the tree in the application UI.
public void loadTreeItems(String... rootItems) {
TreeItem<String> root = new TreeItem<String>("Root Node");
root.setExpanded(true);
for (String itemString: rootItems) {
root.getChildren().add(new TreeItem<String>(itemString));
}
locationTreeView.setRoot(root);
}
}
myInterface.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns:fx="http://javafx.com/fxml" fx:controller="test.MyControllerClass">
<TreeView fx:id="locationTreeView" layoutX="0" layoutY="0" prefHeight="193.0" prefWidth="471.0" />
</AnchorPane>
Problem
I have a FXML file that has a TreeView control: ``` <AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="500.0" xmlns:fx="http://javafx.com/fxml" fx:controller="test.MyControllerClass"> <TreeView fx:id="locationTreeView" layoutX="12.0" layoutY="158.0" prefHeight="193.0" prefWidth="471.0" /> ``` Then my Java Class Controller needs to wrap with this TreeView and add TreeItem's dynamically. That's the problem, it isn't loading those TreeItem's. That's the test code below, from my Controller: ``` public class MyControllerClass extends Application { @FXML private TreeView<String> locationTreeView; @Override public void start(Stage stage) throws Exception { stage.initStyle(StageStyle.TRANSPARENT); stage.getIcons().add(new Image(getClass().getResourceAsStream("myIcon.png"))); Parent root = FXMLLoader.load(getClass().getResource("myInterface.fxml")); Scene scene = new Scene(root); stage.setScene(scene); loadTreeItems(); stage.show(); } // Just a simple example that still doesn't works private void loadTreeItems() { try { TreeItem<String> root = new TreeItem<String>("Root Node"); root.setExpanded(true); root.getChildren().addAll( new TreeItem<String>("Item 1"), new TreeItem<String>("Item 2"), new TreeItem<String>("Item 3") ); locationTreeView = new TreeView<String>(root); } catch (Exception exc) { System.out.println("Error: " + exc.getMessage()); } } public static void main(String[] args) { launch(args); } } ``` Any ideas why its not working?