Reputation: 57
If I have a custom JavaFX component like this (for e.g.):
public class MenuWidget extends VBox implements Initializable {
@FXML
StackPane menus;
public MenuWidget() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/resources/MenuWidget.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println(menus.getChildren().size());
}
}
With this FXML:
<fx:root type="javafx.scene.layout.VBox" prefWidth="300.0" xmlns:fx="http://javafx.com/fxml/1">
<StackPane fx:id="menus">
<padding>
<Insets top="5" left="5" bottom="5" right="5"></Insets>
</padding>
</StackPane>
</fx:root>
And I use the custom component like this in another FXML file:
<MenuWidget>
<menus>
<fx:include source="FirstMenu.fxml" />
<fx:include source="SecondMenu.fxml" />
</menus>
</MenuWidget>
Why does the Initialize() method in MenuWidget print 0? Essentially I need to access the children of the stackpane when the MenuWidget is constructed so that I can setup other menu controls of the top level menu (which I've removed from this example). Shouldn't the FXMLLoader populate the controller (the MenuWidget) with all its properties before the init method is called?
EDIT: Figured out the init is called before constructor finishes, so tried moving init code into the constructor (after the fmxmlLoader.load() call) and it still doesn't work.
Upvotes: 0
Views: 555
Reputation: 46255
The MenuWidget
class and its associated FXML file are entirely self-contained. You aren't including anything there and you are not adding any children to the StackPane
. In other words, this:
public class MenuWidget extends VBox implements Initializable {
@FXML
StackPane menus;
public MenuWidget() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/resources/MenuWidget.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println(menus.getChildren().size());
}
}
Loads this:
<fx:root type="javafx.scene.layout.VBox" prefWidth="300.0" xmlns:fx="http://javafx.com/fxml/1">
<StackPane fx:id="menus">
<padding>
<Insets top="5" left="5" bottom="5" right="5"></Insets>
</padding>
</StackPane>
</fx:root>
And once it's finished with that, the initialize
method is invoked. Nothing there added anything to menus
so the result of calling menus.getChildren().size()
is of course 0
.
Somewhere else you are loading this:
<MenuWidget>
<menus>
<fx:include source="FirstMenu.fxml" />
<fx:include source="SecondMenu.fxml" />
</menus>
</MenuWidget>
Which causes a MenuWidget
to be instantiated, which involves calling the MenuWidget#initialize
method, and then attempts to add children to menus
. To put it another way, if this was valid and working FXML, then the children would be added after the MenuWidget
instance was created and initialized.
However, the <menus>
element should be causing your application to throw an exception. The MenuWidget
class does not define a read-only list property named menus
. If you want to uses <menus>
, and you want elements of that list to be added to the children of the menus
stack pane, then modify your MenuWidget
class to be:
public class MenuWidget extends VBox implements Initializable {
@FXML
StackPane menus;
public MenuWidget() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/resources/MenuWidget.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println(menus.getChildren().size());
}
// add read-only list property (the "property" is read-only, not
// the list itself) named "menus"
public final ObservableList<Node> getMenus() {
return menus.getChildren();
}
}
But that seems conceptually wrong (at least to me). I'm not exactly sure what you're trying to do, but maybe you should be fx:include
-ing the other FXML files directly into the MenuWidget
FXML file, rather than what you're currently doing. That way you could inject the controllers and/or views (see nested controllers) into the MenuWidget
class. I'm also not sure if the use of fx:root
is entirely warranted in this case, based on what you've shown us. Inheriting from VBox
does not seem to be adding any benefit to your code (i.e. you're not adding any functionality)—especially since you're only adding a single child to it (then adding children to that child). Perhaps a standard FXML file + controller would be more appropriate.
Upvotes: 3