I just realized i have two initalize methods that are in my controller class.
One is created by Scene Builder and makes no requirement on class to implement Initializable interface.
#FXML
void initialize()
{
assert birthDate != null
: "fx:id=\"birthDate\" was not injected: check your FXML file 'baptismEntryView.fxml'.";
assert baptismDate != null
: "fx:id=\"baptismDate\" was not injected: check your FXML file 'baptismEntryView.fxml'.";
...
}
Other one with two parameters needs to implement Initalizable interface
#Override
public void initialize(URL location, ResourceBundle resources)
{
...
}
I see that there is difference, that initialize that requires to implement Initializable interface allows me to access ResourceBundle during creation of controller for example.
What i want to ask is in what order they are called and what everything I am supposed to do in them (maybe some docs/tutorial reference?).
You should not have both. I can't find documentation on this, but it appears that if the controller implements Initializable then the no-arg initialize() method is never invoked.
It's actually recommended you don't implement Initializable:
NOTE This interface has been superseded by automatic injection of location and resources properties into the controller. FXMLLoader will now automatically call any suitably annotated no-arg initialize() method defined by the controller. It is recommended that the injection approach be used whenever possible.
If you still need access to the ResourceBundle (and/or location URL) then you can do the following:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
public class Controller {
// you don't need to declare both 'location' and 'resources'
#FXML private URL location;
#FXML private ResourceBundle resources;
#FXML
private void initialize() {
// ...
}
}
Related
I am trying to build my first 'real' application with java using JavaFX. I'm using FXML to design the whole thing and so I decided to divide up some parts into different .fxml files and link those to different controllers. I'm using one MainController linked to main.fxml, and within main.fxml I call some other fxml files using fx:include. All includes have their own controller.
The issue is that the controllers don't operate 100% independently, sometimes you want to press a button linked to one controller and have that so something to another controller. I have a solution that works, but I'm not sure if it is the best way to do it.
My solution is this: Have one abstract SubController class, which has protected static fields for each of the subcontrollers. Upon initialization MainController fills all of these fields with the controller classes it gets from main.fxml, as well as one field for MainController itself. Each of the sub-controllers inherit from SubController so that they can access the static fields and call the public methods of the other controllers.
public abstract class SubController implements Initializable {
protected static MainController mainController;
protected static MenuBarController menuBarController;
protected static VideoPanelController videoPanelController;
public static void setMainController(MainController mainController) {
SubController.mainController = mainController;
}
public static void setMenuBarController(MenuBarController menuBarController) {
SubController.menuBarController = menuBarController;
}
public static void setVideoPanelController(VideoPanelController videoPanelController) {
SubController.videoPanelController = videoPanelController;
}
}
public class MainController implements Initializable {
#FXML private MenuBarController menuBarController;
#FXML private VideoPanelController videoPanelController;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
SubController.setMainController(this);
SubController.setMenuBarController(menuBarController);
SubController.setVideoPanelController(videoPanelController);
}
This way when a button is clicked in the menu bar, it can use a public method of the video panel controller:
public class MenuBarController extends SubController {
public void openVideo() {
File selectedFile = chooseFiles(VIDEO_EXTENSIONS);
if (selectedFile == null) return;
videoPanelController.loadVideo(selectedFile);
}
}
This works quite well, but I still feel like there's probably an easier way to do it. Is this a good way to go about it and if not, how else should I do it?
My Application class looks like this:
public class Test extends Application {
private static Logger logger = LogManager.getRootLogger();
#Override
public void start(Stage primaryStage) throws Exception {
String resourcePath = "/resources/fxml/MainView.fxml";
URL location = getClass().getResource(resourcePath);
FXMLLoader fxmlLoader = new FXMLLoader(location);
Scene scene = new Scene(fxmlLoader.load(), 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The FXMLLoader creates an instance of the corresponding controller (given in the FXML file via fx:controller) by invoking first the default constructor and then the initialize method:
public class MainViewController {
public MainViewController() {
System.out.println("first");
}
#FXML
public void initialize() {
System.out.println("second");
}
}
The output is:
first
second
So, why does the initialize method exist? What is the difference between using a constructor or the initialize method to initialize the controller required things?
Thanks for your suggestions!
In a few words: The constructor is called first, then any #FXML annotated fields are populated, then initialize() is called.
This means the constructor does not have access to #FXML fields referring to components defined in the .fxml file, while initialize() does have access to them.
Quoting from the Introduction to FXML:
[...] the controller can define an initialize() method, which will be called once on an implementing controller when the contents of its associated document have been completely loaded [...] This allows the implementing class to perform any necessary post-processing on the content.
The initialize method is called after all #FXML annotated members have been injected. Suppose you have a table view you want to populate with data:
class MyController {
#FXML
TableView<MyModel> tableView;
public MyController() {
tableView.getItems().addAll(getDataFromSource()); // results in NullPointerException, as tableView is null at this point.
}
#FXML
public void initialize() {
tableView.getItems().addAll(getDataFromSource()); // Perfectly Ok here, as FXMLLoader already populated all #FXML annotated members.
}
}
In Addition to the above answers, there probably should be noted that there is a legacy way to implement the initialization. There is an interface called Initializable from the fxml library.
import javafx.fxml.Initializable;
class MyController implements Initializable {
#FXML private TableView<MyModel> tableView;
#Override
public void initialize(URL location, ResourceBundle resources) {
tableView.getItems().addAll(getDataFromSource());
}
}
Parameters:
location - The location used to resolve relative paths for the root object, or null if the location is not known.
resources - The resources used to localize the root object, or null if the root object was not localized.
And the note of the docs why the simple way of using #FXML public void initialize() works:
NOTE This interface has been superseded by automatic injection of location and resources properties into the controller. FXMLLoader will now automatically call any suitably annotated no-arg initialize() method defined by the controller. It is recommended that the injection approach be used whenever possible.
I have a question from a tutorial by MVP Integrating Spring Boot with JavaFX
GitHub: https://github.com/mvpjava
Youtube: https://www.youtube.com/watch?v=hjeSOxi3uPg
where there is a public method named initialization() that I don't know how it is invoked after spring boot application starts up. I looked everywhere in the classes and fxml files that it could have a reference to it, and I found none.
I want to know how this method is invoked since I also want to do some initialization of JavaFX controls. I tried using #PostConstruct but this is wrong since all beans are created before any JavaFX controls are created thus I get null pointer exception.
I will be very grateful if someone will enlighten me on this matter.
here is the class at which the public method initialization() I mentioned.
#Component
public class ConsoleTabController {
#FXML private TextArea missionOverviewText;
#FXML private ListView<String> missionsList;
#Autowired #Qualifier("stringPrintWriter")
private PrintWriter stackTraceWriter;
#Autowired MissionsService service;
private TabPaneManger tabManager;
public void initialize() {
ObservableList<String> missions = FXCollections.observableArrayList("Apollo", "Shuttle", "Skylab");
missionsList.setItems(missions);
}
#FXML
private void onMouseClicked(MouseEvent event) {
missionOverviewText.clear();
final String selectedItem = missionsList.getSelectionModel().getSelectedItem();
missionOverviewText.positionCaret(0);
missionOverviewText.appendText(getInfo(selectedItem));
}
#Autowired
private void setTabManager(TabPaneManger tabManager){
this.tabManager = tabManager;
}
public String getInfo(String selectedItem) {
String missionInfo = null ;
try {
missionInfo = service.getMissionInfo(selectedItem);
getLog().appendText("Sucessfully retrieved mission info for " + selectedItem + "\n");
} catch (IOException exception) {
exception.printStackTrace (stackTraceWriter);
getLog().appendText(stackTraceWriter.toString() + "\n");
}
return missionInfo;
}
public TextArea getMissionOverviewText() {
return missionOverviewText;
}
public ListView<String> getMissionsList() {
return missionsList;
}
private TextArea getLog(){
return tabManager.getVisualLog();
}
}
The initialize() method (which is what I think you mean) is invoked by the FXMLLoader. In general, for an FXMLLoader, the order of execution is:
FXMLLoader loads the FXML file and parses it
If the root element in the FXML file has a fx:controller attribute, it gets a reference to an instance of that class; it does this by passing the controller class to the controllerFactory, if one is set, or by calling the controller class's default constructor otherwise.
Any elements with fx:id attributes are injected into the controller fields with matching #FXML annotations
The FXMLLoader invokes the controller's initialize() method, if it has one.
In your case, I assume you are setting the controller factory of the FXMLLoader to delegate to the Spring application context, i.e. I assume you have something like
ApplicationContext appContext = ... ; // Spring bean factory
FXMLLoader loader = new FXMLLoader();
loader.setLocation(...);
loader.setControllerFactory(appContext::getBean);
Parent ui = loader.load();
This means that the controller instances will be created by passing the controller class to the Spring bean factory's getBean(...) method. So if the FXML file has fx:controller="ConsoleTabController", the FXMLLoader essentially calls
Object controller = appContext.getBean(ConsoleTabController.class);
in step 2 above. The Spring application context creates a ConsoleTabController instance (assuming you have configured the controller bean as having prototype scope, which you should), injects any #AutoWired-annotated properties, calls any #PostConstruct-annotated methods, and then provides the controller to the FXMLLoader. So the overall order of execution when you use the Spring bean factory as the controller factory is
FXMLLoader loads the FXML file and parses it
If the root element in the FXML file has a fx:controller attribute:
The Spring bean factory creates an instance of the controller class
The Spring bean factory injects any #Autowired-annotated properties into the controller instance
The Spring bean factory invokes any #PostConstruct-annotated methods on the controller instance
The controller instance is returned to the FXMLLoader
Any elements with fx:id attributes are injected into the controller fields with matching #FXML annotations by the FXMLLoader
The FXMLLoader invokes the controller's initialize() method, if it has one.
Note that there are essentially two different kind of injected fields, and two different kinds of "initialization" methods in this scenario. The "Spring injected fields", annotated #Autowired (or #Inject) are injected first, then the "Spring initialization methods" (annotated #PostConstruct) are invoked. After that, the "FXML injected fields" (annotated #FXML) are injected, and then the "FXML initialization method" (the one called initialize()) is invoked. You don't have any #PostConstruct methods in your sample code, but the one thing to be careful of is that those methods would be invoked before the #FXML-annotated fields are initialized.
My Application class looks like this:
public class Test extends Application {
private static Logger logger = LogManager.getRootLogger();
#Override
public void start(Stage primaryStage) throws Exception {
String resourcePath = "/resources/fxml/MainView.fxml";
URL location = getClass().getResource(resourcePath);
FXMLLoader fxmlLoader = new FXMLLoader(location);
Scene scene = new Scene(fxmlLoader.load(), 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The FXMLLoader creates an instance of the corresponding controller (given in the FXML file via fx:controller) by invoking first the default constructor and then the initialize method:
public class MainViewController {
public MainViewController() {
System.out.println("first");
}
#FXML
public void initialize() {
System.out.println("second");
}
}
The output is:
first
second
So, why does the initialize method exist? What is the difference between using a constructor or the initialize method to initialize the controller required things?
Thanks for your suggestions!
In a few words: The constructor is called first, then any #FXML annotated fields are populated, then initialize() is called.
This means the constructor does not have access to #FXML fields referring to components defined in the .fxml file, while initialize() does have access to them.
Quoting from the Introduction to FXML:
[...] the controller can define an initialize() method, which will be called once on an implementing controller when the contents of its associated document have been completely loaded [...] This allows the implementing class to perform any necessary post-processing on the content.
The initialize method is called after all #FXML annotated members have been injected. Suppose you have a table view you want to populate with data:
class MyController {
#FXML
TableView<MyModel> tableView;
public MyController() {
tableView.getItems().addAll(getDataFromSource()); // results in NullPointerException, as tableView is null at this point.
}
#FXML
public void initialize() {
tableView.getItems().addAll(getDataFromSource()); // Perfectly Ok here, as FXMLLoader already populated all #FXML annotated members.
}
}
In Addition to the above answers, there probably should be noted that there is a legacy way to implement the initialization. There is an interface called Initializable from the fxml library.
import javafx.fxml.Initializable;
class MyController implements Initializable {
#FXML private TableView<MyModel> tableView;
#Override
public void initialize(URL location, ResourceBundle resources) {
tableView.getItems().addAll(getDataFromSource());
}
}
Parameters:
location - The location used to resolve relative paths for the root object, or null if the location is not known.
resources - The resources used to localize the root object, or null if the root object was not localized.
And the note of the docs why the simple way of using #FXML public void initialize() works:
NOTE This interface has been superseded by automatic injection of location and resources properties into the controller. FXMLLoader will now automatically call any suitably annotated no-arg initialize() method defined by the controller. It is recommended that the injection approach be used whenever possible.
I have a JavaFX application that uses FXML alongside a controller class written in Java. In the Java controller I need to take care not to operate on an FXML Node element until it's been initialized (otherwise I'll get a NullPointerException), which isn't guaranteed until the initialize method is run. So I find myself doing this a lot:
The controller is set in the FXML file like this:
<Pane fx:controller="Controller" ...>
...
</Pane>
And then here's the controller in the Java file.
class Controller{
#FXML
Pane aPane;
int globalValue;
public void setSomething(int value){
globalValue = value;
if(!(aPane == null)){ //possibly null if node not initialized yet
aPane.someMethod(globalValue)
}
}
#FXML
void initialize(){
aPane.someMethod(globalValue) //guaranteed not null at this point
}
}
This works, but it's clunky and repetitive. I have to create the globalValue attribute just in case the setSomething method is called before initialize has been called, and I have to make sure the operations in my setSomething method are identical to the operations in initialize.
Surely there's a more elegant way to do this. I know that JavaFX has the Platform.runlater(...) method that guarantees something will be run on the main application thread. Perhpas there's something like Platform.runAfterInitialize(...) that waits until initialization, or runs immediately if initialization already happened? Or if there's another way to do it I'm open to suggestions.
If you specify the controller in the FXML file with fx:controller="Controller", then when you call FXMLLoader.load(...), the FXMLLoader:
parses the FXML file
creates an instance of Controller by (effectively) calling its no-arg constructor (or, in advanced usage, by invoking the controller factory if you set one)
creates the UI elements corresponding to the elements in the FXML file
injects any elements with an fx:id into matching fields in the controller instance
registers event handlers
invokes initalize() on the controller instance (if such a method is defined)
returns the UI element corresponding to the root of the FXML hierarchy
Only after load() completes (i.e. after the #FXML-annotated fields are injected) can you get a reference to the controller with loader.getController(). So it is not possible (aside from doing something extremely unusual in a controller factory implementation) for you to invoke any methods on the controller instance until after the #FXML-injected fields are initialized. Your null checks here are redundant.
On the other hand, if you use FXMLLoader.setController(...) to initialize your controller, in which case you must not use fx:controller, you can pass the values to the constructor. Simply avoiding calling a set method on the controller before passing the controller to the FXMLLoader means you can assume any #FXML-annotated fields are initialized in the controller's public methods:
class Controller{
#FXML
Pane aPane;
int globalValue;
public Controller(int globalValue) {
this.globalValue = globalValue ;
}
public Controller() {
this(0);
}
public void setSomething(int value){
globalValue = value;
aPane.someMethod(globalValue)
}
#FXML
void initialize(){
aPane.someMethod(globalValue) //guaranteed not null at this point
}
}
and
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
Controller controller = new Controller(42);
loader.setController(controller);
Node root = loader.load();