0

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.

5
  • A better solution might be to move the constructor code into initialize, and after that's done, do your println again and see what happens Commented Feb 7, 2020 at 3:20
  • Good idea! Tried that, and now another issue is uncovered. First I got a coercion error (it wanted to coerse one of the sub menus to a StackPane). Then I added <children> tags to <menus> and I get "Parent element does not support property elements". Commented Feb 7, 2020 at 3:33
  • Your MenuWidget class does not define any list property named menus, so the <menus> element in the FXML file should be causing errors. Commented Feb 7, 2020 at 3:38
  • Ah, I see. I wanted to add the sub-menus as children of the StackPane (there is a StackPane property in the MenuWidget class). Since <children> is the "default" property in FXML, I thought adding them inside <menus> would add them as children to the StackPane menus property. Does it not work that way? Commented Feb 7, 2020 at 3:50
  • As described in the answer, MenuWidget.initialize() is invoked during the process of loading MenuWidget.fxml, which happens during the MenuWidget constructor call (which is before items are added to the menu list). Your initialize method could add a listener to menus.getChildren() and update whatever it is you need to update as items are added (note, though, that you can't modify the same menus.getChildren() list in such a listener). This just might not be the right solution at all; it's difficult to know without more details of what you're trying to achieve. Commented Feb 7, 2020 at 19:45

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the extensive reply. I removed a lot of surrounding code for simplicity. MenuWidget is a custom component used for displaying a set of sub-menus. The MenuWidget component has other controls for navigating through the menus. The idea is that MenuWidgets can be used in multiple places/projects, with custom menus added to it elsewhere. The only logic inside MenuWidget is the logic for changing between menus. I had assumed that MenuWidget#Initialise() was called after the FXML graph was fully loaded (i.e. after all children had been added to it) (which as you've explained is wrong).
If I'm being pedantic, the initialize method is loaded after the entire FXML graph has been fully loaded. It's just you have two loads occurring due to the nature of fx:root.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.