FXML: "Run after initialized" - java

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();

Related

NullPointerException when starting a Popupwindow with default values set, otherwise it works

When I open a Popup-window by a button event from the maincontroller
the popup appears and everything looks perfect.
But when I try to set data in comboboxes or textfields
PopUpPersController:
public PopUpPersController() {
initialize();
}
private void initialize() {
txtMa_LohnGesKum.setText("1200.12");
}
and press the button to open the popup, I get a null pointer exception.
The maincontroller is called Projektcontroller,
the associated FXML-file is called Projekt.fxml
The popupwindow controller is called "PopUpPersController",
the associated FXML-file called PersCalc.fxml.
All elements have fxId's.
I thought the filling of comboboxes or textfields with defaultdata would work the same way as in the Maincontroller...
I did not find an answer that covers this question.
I am quite new to Java, so thanks a lot for your help and best regards
Marcus
The code from the Main or ProjectController for the Button opening the popupwindow:
#FXML
void onAction_Test(ActionEvent event) {
try {
Parent root1 = FXMLLoader.load(getClass().getResource("/ui/fxml/PersCalc.fxml"));
Stage persStage = new Stage();
persStage.setTitle("Personalkosten-Rechner");
persStage.setScene(new Scene(root1));
persStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
I tried to identify the major errors:
javafx.fxml.LoadException in PersCalc.fxml
The code in this line:
<BorderPane xmlns="http://javafx.com/javafx/10.0.1"xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.controller.PopUpPersController">
2.From the button event in the ProjectController (see above)
The code in this line:
Parent root1=FXMLLoader.load(getClass().getResource("/ui/fxml/PersCalc.fxml"));
3.The assignment of data to the textfield
Caused by: java.lang.NullPointerException at
ui.controller.PopUpPersController.initialize(PopUpPersController.java:123)
The code in this line (see above initialize()):
txtMa_LohnGesKum.setText("1200.12");
Loading a fxml containing the fx:controller attribute results in FXMLLoader using the constructor taking 0 arguments to create an instance of the controller class. The constructor is invoked before any of the fields are injected resulting in a NullPointerException, even if the fields are accessible to FXMLLoader and the fxml file contains the proper fx:id attributes.
Assuming you did set up the field/fx:id properly, making the initialize method visible to FXMLLoader instead of invoking initialize from the constructor should fix the issue:
public PopUpPersController() {
}
#FXML // you need this annotation for non-public members to be visible to FXMLLoader
private void initialize() {
txtMa_LohnGesKum.setText("1200.12");
}
Make sure the fields FXMLLoader should inject to are also annotated with #FXML (or public) and required fx:id attributes are set in the fxml file.

How do I add injection of custom fields to the default ControllerFactory of FXMLLoader?

I want to set some non-UI fields in the controller before the initialize method of the controller gets called automatically upon creation. As I understand it, the way to do it is to provide custom ControllerFactory, since initialize() gets called after ControllerFactory returns the created object. I wanted to use the following code as per this answer:
FXMLLoader loader = new FXMLLoader(mainFXML); // some .fxml file to load
loader.setControllerFactory(param -> {
Object controller = null;
try {
controller = ReflectUtil.newInstance(param); // this is default behaviour
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
if (controller instanceof Swappable) {
((Swappable) controller).setSwapper(swapper); // this is what I want to add
}
return controller;
});
However, the ReflectUtil class (which is used in default setControllerFactory method) is part of com.sun.reflect.misc package, which I am not able to use, since compiling fails with error: package sun.reflect.misc does not exist.
As I understand it, I can't use sun packages, since this is not public API. So the question is: what do I do? I can't find any other examples of this, only the ones with ReflectUtil and, well, I want my ControllerFactory to comply with default workflow of JavaFX with #FXML annotations and all that, is this possible with some other DI framework like Jodd Petite, for example? Is there some other way to set the field? (other than to synchronize on it and wait in initialize() until the setter method gets called from other thread).
Full code on github for context.
If you want to create an instance via reflection then you need to use Class.getConstructor(Class...)1 followed by Constructor.newInstance(Object...).
FXMLLoader loader = new FXMLLoader(/* some location */);
loader.setControllerFactory(param -> {
Object controller;
try {
controller = param.getConstructor().newInstance();
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
if (controller instanceof Swappable) {
((Swappable) controller).setSwapper(swapper);
}
return controller;
}
This code requires that your controller class has a public, no-argument constructor. If you want to inject your dependencies through the constructor you could do something like:
FXMLLoader loader = new FXMLLoader(/* some location */);
loader.setControllerFactory(param -> {
Object controller;
try {
if (Swappable.class.isAssignableFrom(param)) {
controller = param.getConstructor(Swapper.class).newInstance(swapper);
} else {
controller = param.getConstructor().newInstance();
}
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
return controller;
}
This code assumes that all subclasses of Swappable have a public, single-argument constructor that takes a Swapper.
If you want to get a non-public constructor you'll need to use Constructor.getDeclaredConstructor(Class...). Then you'd need to call setAccessible(true) on the Constructor before invoking it.
Couple things to remember if using Jigsaw modules (Java 9+) and this controller factory code is not in the same module as the controller class. Let's say the controller factory code is in module foo and the controller class is in module bar:
If using a public controller with a public constructor then bar must exports the controller class' package to at least foo
If using a non-public controller and/or constructor then the same thing must happen but with opens instead of exports
Otherwise an exception will be thrown.
1. If using a no-argument (not necessarily public) constructor you can bypass getConstructor and call Class.newInstance() directly. However, please note that this method has issues and has be deprecated since Java 9.
Personally, using reflection for my own code is a sign of bad design.
Here's a suggestion that uses FXML mechanisms to inject a user instance of an object. For this purpose, an object is created that describes the context in which the application works. Object user entities are registered in this object. This imposes some constraint on users not to implement a direct interface but to inherit an abstract class that will implement the logic of registering the instance in the context.
public interface Swapper {
}
public abstract class AbstractSwapper implements Swapper {
public AbstractSwapper() {
ApplicationContext.getInstance().setSwapper(this);
}
}
public class ApplicationContext {
private static ApplicationContext instance;
private Swapper swapper;
private ApplicationContext() {
}
public synchronized static ApplicationContext getInstance() {
if(instance == null) {
instance = new ApplicationContext();
}
return instance;
}
public synchronized static Swapper swapperFactory() {
Swapper swapper = getInstance().getSwapper();
if(swapper == null) {
swapper = new AbstractSwapper() {
};
getInstance().setSwapper(swapper);
}
return swapper;
}
public Swapper getSwapper() {
return swapper;
}
public void setSwapper(Swapper swapper) {
this.swapper = swapper;
}
}
In this case, the FXML file can be used fx:factory to use the swapper instance registered in ApplicationContext. Thus, FXMLLoader will inject the instance directly into the controller.
<GridPane fx:controller="sample.Controller" xmlns:fx="http://javafx.com/fxml" >
<fx:define>
<ApplicationContext fx:factory="swapperFactory" fx:id="swapper"/>
</fx:define>
</GridPane>
and sample.Controller
public class Controller {
#FXML
private Swapper swapper;
}
Another solution is for the controller to initialize the fields using the ApplicationContext directly. So the swapper field does not bind to the FXML file.
public class Controller {
private Swapper swapper;
#FXML
private void initialize() {
swapper = ApplicationContext.swapperFactory();
}
}
In both versions, the user simply has to create an instance of AbstractSwapper before using FXMLLoader.
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
AbstractSwapper s = new AbstractSwapper() {
};
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Also, there is an option to use FXMLLoader to inject the object. In this case it goes through fx:reference or through fx:copy (if you have copy constructor)

FXMLLoader initialization and SpringBoot - #Configuration

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.

javaFX Load Exception and javaFX Null Pointer Exception [duplicate]

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.

Accessing FXML Controller Class from predefined Controller class

I implemented a simple application with the MVC-Pattern and used a console for the output. Now I tried to replace the console with a simple JavaFX-Approach to test the independence of my design.
The whole logic is covered in the Controller class Admin. It is has an object a_view which gets initialized at creation and implements the IView interface, which has a showWelcomeText() function.
I initialize the Scene and the controller in Main.java:
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader fxmlLoader = new FXMLLoader();
Parent root = fxmlLoader.load(getClass().getResource("/view/DesktopApp.fxml").openStream());
Scene scene = new Scene(root,800,500);
primaryStage.setScene(scene);
primaryStage.show();
IView a_view = (IView) fxmlLoader.getController(); // JavaFXGUI class connected to the root
//a_view.showWelcomeMessage(); // shows message
Admin secretary = new Admin(a_view);
secretary.manage(); // shows empty Form
} catch(Exception e) {
e.printStackTrace();
}
}
The JavaFXGUI-controller is passed as parameter and then initialized in the admin class.
public Admin(IView a_view){
this.a_view = a_view;
md_list = dao.MembersDAO.jaxbXMLToObject(); // read out of XML
}
public void manage(){
a_view.showWelcomeMessage();
However when I run the program it just shows me an empty form and does not display the welcome text. If I comment out the call of manage() and call a_view.showWelcomeMessage() directly it works fine and the message is shown in the form.
I thought it could be a problem to pass the variable(a_view) as parameter (e.g. Java does not pass reference), so I also tried to declare it as static public in the Admin class. It results in the same even both calls refer to the same object.
I searched a lot and amongst other stuff I saw this thread about how to access the controller Accessing FXML controller class
but couldn't make it work with the calls from the original controller class. Where is the difference between calling the public static (or private and pass as parameter) and calling it from the start method?
Kind regards

Categories

Resources