Skip to content

EasyBind.map() bug when binding to Pane children and using remove() #82

@credmond

Description

@credmond

A "Children: duplicate children added: parent" exception occurs whenever remove(x) is called on a source observable list.

Very easy to reproduce:

public class EasyBindMapBug extends Application {

    private final ObservableList<String> personList = FXCollections.observableArrayList("a", "b", "c");

    @Override
    public void start(Stage primaryStage) {
        final ObservableList<Text> textList = EasyBind.map(personList, str -> {
                    System.out.println("XXX: " + str);

                    return new Text(str) {
                // If you override hashCode/equals to prevent duplications, the problem does NOT occur
                //  @Override
                //  public int hashCode() {
                //      return str.hashCode();
                //  }
                //
                //  @Override
                //  public boolean equals(Object other) {
                //      return ((Text) other).getText().equals(this.getText());
                //  }
                    };
                }
        );

        // No problem for controls, this will work just fine
        // ListView<Text> node = new ListView<>();
        // Bindings.bindContent(node.getItems(), textList);

        final VBox node = new VBox();
        Bindings.bindContent(node.getChildren(), textList);

        primaryStage.setScene(new Scene(node, 300, 200));
        primaryStage.show();

        // This will throw a "Children: duplicate children added: parent" error, all the time
        personList.remove(1); // or 0, or 2, etc...
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Stacktrace:

Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Children: duplicate children added: parent = VBox@62f1ef9f[styleClass=root]
	at javafx.graphics@21.0.3/javafx.scene.Parent$3.onProposedChange(Parent.java:562)
	at javafx.base@21.0.3/com.sun.javafx.collections.VetoableListDecorator$VetoableSubListDecorator.clear(VetoableListDecorator.java:544)
	at com.tobiasdiez.easybind@2.2.1-SNAPSHOT/com.tobiasdiez.easybind.ListContentBinding.onChanged(ListContentBinding.java:35)
	at javafx.base@21.0.3/com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:162)
	at javafx.base@21.0.3/com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:71)
	at javafx.base@21.0.3/javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:246)
	at com.tobiasdiez.easybind@2.2.1-SNAPSHOT/com.tobiasdiez.easybind.MappedList.sourceChanged(MappedList.java:41)
	at javafx.base@21.0.3/javafx.collections.transformation.TransformationList.lambda$getListener$0(TransformationList.java:105)
	at javafx.base@21.0.3/javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
	at javafx.base@21.0.3/com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:162)
	at javafx.base@21.0.3/com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:71)
	at javafx.base@21.0.3/javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:246)
	at javafx.base@21.0.3/javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
	at javafx.base@21.0.3/javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
	at javafx.base@21.0.3/javafx.collections.ObservableListBase.endChange(ObservableListBase.java:210)
	at javafx.base@21.0.3/javafx.collections.ModifiableObservableListBase.remove(ModifiableObservableListBase.java:228)
	at com.certak.xxx/com.certak.xxx.gui.aaa.EasyBindMapBug.start(EasyBindMapBug.java:48)
	at javafx.graphics@21.0.3/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:839)
	at javafx.graphics@21.0.3/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:483)
	at javafx.graphics@21.0.3/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:456)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at javafx.graphics@21.0.3/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:455)
	at javafx.graphics@21.0.3/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
	at javafx.graphics@21.0.3/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
	at javafx.graphics@21.0.3/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:185)
	at java.base/java.lang.Thread.run(Thread.java:833)

As per the comments, binding to a control's items, for example, is fine. Also, mapping to something that has sensible hashCode/equals may also fix the problem. It's still a bug though, and could do with a documentation fix.

Using mapBacked() would be okay too as the items are only generated once per change to source.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions