Two approaches here.
Passing the model to nested controllers
You can inject the controller from the included FXML into the controller for the including FXML using the nested controllers technique. Then the "outer controller" can propagate the model to the "nested controller".
So, e.g. if you have
<SplitPane xmlns="..." fx:controller="com.mycompany.MainController">
<items>
<TableView prefHeight="200.0" prefWidth="200.0">
<columns>
<TableColumn prefWidth="75.0" text="User" />
<TableColumn prefWidth="75.0" text="Pass" />
</columns>
</TableView>
<fx:include fx:id="container" source="Container.fxml"/>
</items>
</SplitPane>
Suppose the controller class for Container.fxml
is ContainerController
. Then you would do:
public class MainController {
private Model model ;
@FXML
private ContainerController containerController ; // name is fx:id with "Controller" appended
public void setModel(Model model) {
this.model = model ;
containerController.setModel(model);
// ...
}
// ...
}
and of course
public class ContainerController {
private Model model ;
public void setModel(Model model) {
this.model = model ;
// ...
}
// ...
}
Using a controller factory
If you have a lot of included FXML files and controllers, etc, this can start to get unmaintainable. In that case, a better approach might be to initialize the model in the controllers' constructors, and to use a controllerFactory
on the FXMLLoader
to create controllers with a model passed to the constructor. So now you controllers look like:
public class MainController {
private final Model model ;
public MainController(Model model) {
this.model = model ;
}
public void initialize() {
// bind controls to model here...
}
}
and similarly for ContainerController
, etc. Note this is much cleaner than the previous versions, where you had to worry about the model being set at an arbitrary time. Here you're guaranteed it is set when any code in the controller is executed.
Now you need a bit of magic for the FMXLLoader
to create the controllers correctly when the FXML is loaded:
Model model = new Model();
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
Callback<Class<?>, Object> controllerFactory = (Class<?> type) -> {
try {
for (Constructor<?> c : type.getConstructors()) {
if (c.getParameterCount() == 1 && c.getParameterTypes()[0].equals(Model.class)) {
return c.newInstance(model);
}
}
// couldn't find constructor taking a model, just use default:
return type.newInstance();
} catch (Exception exc) {
exc.printStackTrace();
return null ;
}
};
loader.setControllerFactory(controllerFactory);
Parent root = loader.load();
// ...
Note that FXML files loaded via an <fx:include>
tag will use the same controller factory as the "including" FXML file. So this will automatically pass the same model to the nested controller.
Using DI frameworks
Finally, if you really do a lot of this, you might want to consider using a dependency inject framework to manage the dependencies on the model for you. Afterburner.fx is a dedicated JavaFX DI framework, and then everything is as simple as
public class MainController {
@Inject
private Model model ;
public void initialize() {
// bind UI elements to model...
}
}
You can also use Spring or Guice. E.g. with Spring, configure the controllers as prototype beans, the model as a singleton bean, and write the controllers to inject the model as with afterburner.fx. Then you can tell an FXMLLoader
to use Spring to create the controllers with
// Spring application context:
ApplicationContext appContext = ... ;
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
loader.setControllerFactory(appContext::getBean);
Parent root = loader.load();