【问题标题】:Accessing Children in Custom FXML Component Init在自定义 FXML 组件初始化中访问子项
【发布时间】:2020-02-07 03:00:07
【问题描述】:

如果我有这样的自定义 JavaFX 组件(例如):

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

使用此 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>

我在另一个 FXML 文件中使用这样的自定义组件:

<MenuWidget>
    <menus>
        <fx:include source="FirstMenu.fxml" />
        <fx:include source="SecondMenu.fxml" />
    </menus>            
</MenuWidget>

为什么 MenuWidget 中的 Initialize() 方法会打印 0?本质上,我需要在构建 MenuWidget 时访问堆栈窗格的子项,以便我可以设置顶级菜单的其他菜单控件(我已从本示例中删除)。 FXMLLoader 不应该在调用 init 方法之前使用其所有属性填充控制器(MenuWidget)吗?

编辑:发现在构造函数完成之前调用了 init,因此尝试将 init 代码移动到构造函数中(在 fmxmlLoader.load() 调用之后),但它仍然不起作用。

【问题讨论】:

  • 更好的解决方案可能是将构造函数代码移动到initialize,完成后,再次执行 println 看看会发生什么
  • 好主意!试过了,现在又发现了另一个问题。首先,我遇到了一个强制错误(它想将其中一个子菜单强制到 StackPane)。然后我将 标签添加到 并得到“父元素不支持属性元素”。
  • 您的MenuWidget 类没有定义任何名为menus 的列表属性,因此FXML 文件中的&lt;menus&gt; 元素应该会导致错误。
  • 啊,我明白了。我想将子菜单添加为 StackPane 的子菜单(MenuWidget 类中有一个 StackPane 属性)。由于 是 FXML 中的“默认”属性,我认为将它们添加到 中会将它们作为子项添加到 StackPane 菜单属性中。这样不行吗?
  • 如答案中所述,MenuWidget.initialize() 在加载MenuWidget.fxml 的过程中被调用,这发生在MenuWidget 构造函数调用期间(在将项目添加到菜单列表之前)。您的initialize 方法可以向menus.getChildren() 添加一个侦听器,并在添加项目时更新您需要更新的任何内容(但请注意,您不能在这样的侦听器中修改相同的menus.getChildren() 列表)。这可能根本不是正确的解决方案;如果没有更多关于您要达到的目标的详细信息,就很难知道。

标签: java javafx


【解决方案1】:

MenuWidget 类及其关联的 FXML 文件是完全独立的。您没有在其中包含任何内容,也没有向StackPane 添加任何子项。换句话说,这是:

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

加载这个:

<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>

一旦完成,initialize 方法就会被调用。没有向menus 添加任何内容,所以调用menus.getChildren().size() 的结果当然是0

您正在其他地方加载此内容:

<MenuWidget>
    <menus>
        <fx:include source="FirstMenu.fxml" />
        <fx:include source="SecondMenu.fxml" />
    </menus>            
</MenuWidget>

这会导致 MenuWidget 被实例化,这涉及调用 MenuWidget#initialize 方法,然后尝试将子级添加到 menus。换句话说,如果这是有效且有效的 FXML,则将在创建和初始化 MenuWidget 实例之后添加子代。

但是,&lt;menus&gt; 元素应该会导致您的应用程序抛出异常。 MenuWidget 类未定义名为 menusread-only list property。如果您想使用&lt;menus&gt;,并且希望将该列表的元素添加到menus 堆栈窗格的子项中,则将您的MenuWidget 类修改为:

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

但这在概念上似乎是错误的(至少对我而言)。我不确定您要做什么,但也许您应该将其他 FXML 文件直接写入 MenuWidget FXML 文件,而不是您当前正在执行的操作。这样您就可以将控制器和/或视图(参见nested controllers)注入MenuWidget 类。根据您向我们展示的内容,我也不确定在这种情况下是否完全有必要使用 fx:root。从VBox 继承似乎并没有为您的代码增加任何好处(即您没有添加任何功能)——尤其是因为您只向其中添加了一个子项(然后向该子项添加子项)。也许标准的 FXML 文件 + 控制器会更合适。

【讨论】:

  • 感谢您的广泛回复。为了简单起见,我删除了很多周围的代码。 MenuWidget 是一个自定义组件,用于显示一组子菜单。 MenuWidget 组件具有用于在菜单中导航的其他控件。这个想法是 MenuWidgets 可以在多个地方/项目中使用,并在其他地方添加自定义菜单。 MenuWidget 内部的唯一逻辑是在菜单之间切换的逻辑。我假设 MenuWidget#Initialise() 在 FXML 图形完全加载之后被调用(即在所有孩子都被添加到它之后)(正如你所解释的那样是错误的)。
  • 如果我是迂腐的,initialize 方法 在整个 FXML 图形完全加载后加载。只是由于fx:root 的性质,您有两次 加载。
猜你喜欢
  • 1970-01-01
  • 2010-09-12
  • 2015-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
相关资源
最近更新 更多