【问题标题】:Javafx adding dynamically pane to vbox Duplicate Children errorJavafx 将动态窗格添加到 vbox Duplicate Children 错误
【发布时间】:2018-06-30 21:07:36
【问题描述】:

我在 fxml 文件的 VBox 中有一个带有标签、文本字段和组合框的窗格。我们称之为 tempPane。 在同一阶段,我有一个按钮。 按下按钮后,我需要向 VBox 添加与 tempPane 完全相同的窗格。也就是说,向 VBOX 动态添加一个窗格。 我可以向 VBox 添加单独的控件,例如按钮或标签或文本字段,但尝试添加此新窗格时无法获得相同的结果。

部分控制器代码:

@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);
    ...
}

我得到的错误是:

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)

那么,为什么我会收到这个重复的子级错误?我在将 newPane ID 添加到 VBox 之前更改它。

【问题讨论】:

    标签: javafx duplicates children pane vbox


    【解决方案1】:
    Pane newPane = new Pane();
    newPane = tempPane;
    ...
    vBox.getChildren().add(newPane);
    

    这段代码确实创建了一个新的Pane(第一行),但立即通过用旧实例覆盖它来删除新实例(第二行)。

    发生错误是因为Node 的合同不允许它在一个场景中放置两次,并且您要再次添加已经是vBox 的子级的Pane。修改 id 属性不会改变这一事实。

    如果这应该可以工作,您需要创建一个以tempPane 为根的子场景的新副本。

    您可以为此场景创建自定义Pane

    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();
        }
    }
    

    旧的 fxml

    一定要导入 MyPane。

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

    旧控制器

    @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);
        ...
    }
    

    【讨论】:

    • 我不断收到“类型不匹配:无法从 URL 转换为 FXMLLoader”错误,因此我无法测试此代码。
    • 是的,我认为 id 与 fxml 窗格中的 fx:id 有关,但它确实是 CSS id。
    • 另一方面,我需要一个唯一的按钮来添加过滤器,并且必须在主阶段,而不是在添加的每个面板中。问题是我仍然无法针对我得到的不匹配类型错误测试此代码。好的,刚刚添加了 FXMLLoader loader = new FXMLLoader(getClass().getResource("subFXML.fxml"));它有效......让我们看看我是否可以测试它。谢谢!
    • 太棒了!,这行得通。我不得不采取一些变通方法来修复舞台上唯一的“一个按钮”,但效果很好。谢谢!
    【解决方案2】:

    它已经写在你的 cmets 中,为什么你会得到重复的 ID。

    //新ID设置为newPane,这个String(NewID)应该是

    //每次按下按钮都不一样

    您传递的字符串与参数相同。

    newPane.setId("NewID");
    

    尝试为每个窗格使用动态生成的唯一 ID。

    String newId; //generate the id by user input or internally
        newPane.setId(newId);
    

    【讨论】:

    • 是的,我明白了,我会在问题中将“newID”更改为 newID,这样就不会造成混淆。但即使是第一次添加窗格时,也会出现相同的错误。所以这似乎不是问题。
    • 'newPane = tempPane;'通过这一行,您引用的是同一个旧窗格而不是新窗格。我认为应该反过来。
    • 但我想要的是有一个与 FXML 中的 tempPane 相同的窗格。我已经在控制台中打印了 tempPane 和 newPane 的 ID,并且有一些有趣的东西。在 setID 之前,tempPane ID 是正确的,在 setID 之后,两个窗格都更改了它们的 ID...
    • 我不认为您完全了解它是如何工作的。 newPane = tempPane 通过这一行,您只是一次又一次地复制同一对象的引用。是的,因为它们现在都指向同一个对象,如果您更改其中一个的 id,两者都会更改。
    • 好的,谢谢你的解释。那么,如何创建一个包含 tempPane 的所有元素的新窗格?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多