I created custom component TableBlock. It consists of a Label and TableView. TableViewcan have, for example, from 1 to 1000 rows. Number of rows is defined by parameter "rowsFromPrefs" in FXML file. This parameter is needed for creation of TableView. TableView is fully created by JAva code, in fxml is just its tag and parameter with a number of rows.
As i know, when JavaFX constructs FXML component, it first calls constructor, then @FXML annotated fields, then starts initialize() method.
In my case when initialize() starts, variable rowsFromPrefs still is null! But, if i try to get the value of rowsFromPrefs from other thread (not JavaFX-launcher), i see that it defined = "2" like it should be.
So i can`t understand at what moment Java assigns object paramters from FXML file. How can i pass parameter from fxml file to object when it is being created.
I saw @NamedArg annotation for constructor parameters. Is it the only one way of passing parameter when objects are creating?
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:
TableBlock.java
public class TableBlock extends VBox{
@FXML
private String rowsFromPrefs;
@FXML
private Label label;
public TableBlock() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("TableBlock.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
@FXML
public void initialize() {
this.table = createTable(rowsFromPrefs);
}
public String getRowsFromPrefs() {
System.out.println("getRowsFromPrefs");
return rowsFromPrefs;
}
public void setRowsFromPrefs(String rowsFromPrefs) {
this.rowsFromPrefs = rowsFromPrefs;
}
}
TableBlock.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import ru.laz.model.controls.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import ru.laz.model.controls.tableblock.*?>
<fx:root type="javafx.scene.layout.VBox" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label text="Label" />
</children>
</fx:root>
View.java
public class View extends Application {
Parent root = null;
private Scene scene;
@Override
public void init() {
try {
root = FXMLLoader.load(getClass().getResource("View.fxml"));
root.requestLayout();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void start(final Stage stage) throws Exception {
scene = new Scene(root, 640, 480, Color.LIGHTGRAY);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
View.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import ru.laz.model.controls.tableblock.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<TableBlock rowsFromPrefs="2" id="IDDQD"/>
</children>
</AnchorPane>
Best Answer
First, note that the
@FXML
annotation onrowsFromPrefs
is serving no purpose.@FXML
causes a value to be injected for the field when the FXML file for which the current object is the controller has an element with anfx:id
attribute whose value matches the field name. SinceTableBlock.fxml
has no element withfx:id="rowsFromPrefs"
, this annotation isn't doing anything.When the
FXMLLoader
that is loadingView.fxml
encounters the<TableBlock>
element, it creates aTableBlock
instance by calling its constructor. Then it will set the values specified by the attributes. So your FXML elementis essentially equivalent to
Of course, the constructor for
TableBlock
just does what the code says to do: it creates aFXMLLoader
, sets the root and controller for thatFXMLLoader
, and then callsload()
. The load process for thatFXMLLoader
will set the@FXML
-injected fields on the controller (theTableBlock
object whose constructor is executing), and then callinitialize()
.So
initialize()
is invoked as part of the call toFXMLLoader.load()
that is in theTableBlock
constructor; of course this all happens beforesetRowsFromPrefs("2");
is invoked.So in summary,
TableBlock.initialize()
is called afterTableBlock.fxml
has been parsed, and any elements defined there injected into their corresponding@FXML
-annotated fields, but this happens beforeView.fxml
has been loaded.One way to fix this is to pass
rowsFromPrefs
to theTableBlock
constructor. To do this, use the@NamedArg
annotation:Now your attribute in the FXML will be passed to the constructor instead of to a set method, so
rowsFromPrefs
will be initialized before you callfxmlLoader.load()
, as required.The other option, of course, would simply be to move the code from the
initialize()
method to thesetRowsFromPrefs(...)
method. I would use the option described above if you intendrowsFromPrefs
to be fixed for eachTableBlock
instance, and use the second option only if you want to be able to changerowsFromBlocks
during the lifecycle of an individualTableBlock
instance.