Javafx adding dynamically pane to vbox Duplicate Children error

LazyTurtle picture LazyTurtle · Jan 22, 2018 · Viewed 8.3k times · Source

I have a pane with a label, a text field and a combo box inside a VBox in fxml file. Let´s call it tempPane. In the same stage I have a button. Once the button is pressed I need to add to the VBox a pane exactly the same as tempPane. This is, adding dynamically a pane to the VBOX. I am able to add individual controls such as buttons or labels or text fields to the VBox, but I can´t obtain the same results when trying to add this new pane.

Part of the controller code:

@FXML
private Pane tempPane;

@FXML 
private Button btnAddNewPane;;

@FXML
private VBox vBox;

@FXML
void addNewPane(ActionEvent event) {

    ...
        Pane newPane = new Pane();
        newPane = tempPane;
        // New ID is set to the newPane, this String (NewID) should be 
        //different each time button is pressed
        newPane.setId(newID);
        vBox.getChildren().add(newPane);
    ...
}

And the error I´m getting is:

Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Children: duplicate children added: parent = VBox[id=filterBox]
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:580)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:206)
at com.sener.dbgui.controller.SearchController$1.run(SearchController.java:53)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$9(PlatformImpl.java:418)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:417)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:175)
at java.base/java.lang.Thread.run(Thread.java:844)

So, why am I getting this duplicate children error? I´m changing the newPane ID before adding it to the VBox.

Answer

fabian picture fabian · Jan 22, 2018
Pane newPane = new Pane();
newPane = tempPane;
...
vBox.getChildren().add(newPane);

This code does create a new Pane (first line) but immediately drops the new instance by overwriting it with the old one (second line).

The error happens since the contract of Node does not allow it to be placed twice in a scene and you're adding the same Pane that is already a child of vBox again. Modifying the id property does not change that fact.

You need to create a new copy of the subscene rooted at tempPane if this is supposed to work.

You could create a custom Pane for this scene:

subFXML.fxml

<fx:root xmlns:fx="http://javafx.com/fxml" type="javafx.scene.layout.Pane">
    <!-- content of tempPane from old fxml goes here -->
    ...
    <Button fx:id="btnAddNewPane" />
    ...
</fx:root>
public class MyPane extends Pane {

    public MyPane() {
        FXMLLoader loader = getClass().getResource("subFXML.fxml");
        loader.setRoot(this);
        loader.setController(this);

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }

    @FXML
    private Button btnAddNewPane;

    public void setOnAction(EventHandler<ActionEvent> handler) {
        btnAddNewPane.setOnAction(handler);
    }

    public EventHandler<ActionEvent> getOnAction() {
        return btnAddNewPane.getOnAction();
    }
}

old fxml

Be sure to import MyPane.

...
<VBox fx:id="vBox">
    <children>
        <!-- replace tempPane with MyPane -->
        <MyPane onAction="#addNewPane"/>
    </children>
</VBox>
...

old controller

@FXML
private VBox vBox;

@FXML
void addNewPane(ActionEvent event) {

    ...
        MyPane newPane = new MyPane();
        newPane.setId(newID); // Don't know why setting the CSS id is necessary here
        newPane.setOnAction(this::addNewPane); // set onAction property
        vBox.getChildren().add(newPane);
    ...
}