如何创建动态呈现其子项的自定义 FX 组件?

How to create a custom FX component that renders it's children dinamically?

我有一个代表表单域的自定义组件。它有一个标签、一个文本字段和一条可能在输入验证后显示的错误消息。

<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="109.0" prefWidth="512.0" spacing="10.0" styleClass="vbox" type="VBox" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Label fx:id="fieldLabel" text="Lorem ipsum dolor sit amet"></Label>
      <TextField fx:id="textField" promptText="Lorem ipsum dolor sit amet"></TextField>
      <Label fx:id="errorLabel" text=""></Label>
   </children>
</fx:root>
public class FormField extends VBox {

    @FXML private TextField textField;
    @FXML private Label fieldLabel;
    @FXML private Label errorLabel;

    private String fieldLabelText;
    private String promptText;

    public FormField(@NamedArg("fieldLabelText") String fieldLabelText,
                     @NamedArg("promptText") String promptText,
                     @NamedArg("isPasswordField") boolean isPasswordField) {

        FXMLLoader loader = new FXMLLoader(getClass().getResource("../../resources/fxml/form-field.fxml"));
        loader.setRoot(this);
        loader.setController(this);

        this.fieldLabelText = fieldLabelText;
        this.promptText = promptText;

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

    @FXML
    public void initialize() {
        this.fieldLabel.setText(fieldLabelText);
        this.textField.setPromptText(promptText);
    }

现在我想知道的是,我将如何着手扩展这个具有 PasswordField 而不是 TextField 的组件?或者传递一个参数,例如 boolean isPasswordField 让我们 FormField 决定它应该呈现 TextField 还是 PasswordField?如果 TextField 在它的 API 中有一个 obscureText(true) 方法,那就足够了,因为这就是我要找的,但我找不到任何方法。

我能找到的关于 JavaFX 继承的全部内容是通过向对象添加新组件而不是更改其现有元素来“扩展”对象。

一个选项是将 TextFieldPasswordField 都放在 UI 中,在 StackPane 中彼此堆叠,只有一个 StackPane他们可见:

<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="109.0" prefWidth="512.0" spacing="10.0" styleClass="vbox" type="VBox" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Label fx:id="fieldLabel" text="Lorem ipsum dolor sit amet"></Label>
      <StackPane>
        <TextField fx:id="textField" promptText="Lorem ipsum dolor sit amet"></TextField>
        <PasswordField fx:id="passwordField" visible="false"/>
      </StackPane>
      <Label fx:id="errorLabel" text=""></Label>
   </children>
</fx:root>

然后在控制器中根据传递的参数决定哪个是可见的:

public class FormField extends VBox {

    @FXML private TextField textField;
    @FXML private PasswordField passwordField;
    @FXML private Label fieldLabel;
    @FXML private Label errorLabel;

    private String fieldLabelText;
    private String promptText;
    private boolean isPasswordField;

    public FormField(@NamedArg("fieldLabelText") String fieldLabelText,
                     @NamedArg("promptText") String promptText,
                     @NamedArg("isPasswordField") boolean isPasswordField) {

        // path is copied from OP, but is incorrect:
        FXMLLoader loader = new FXMLLoader(getClass().getResource("../../resources/fxml/form-field.fxml"));
        loader.setRoot(this);
        loader.setController(this);

        this.fieldLabelText = fieldLabelText;
        this.promptText = promptText;
        this.isPasswordField = passwordField;

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

    @FXML
    public void initialize() {
        this.fieldLabel.setText(fieldLabelText);
        this.textField.setPromptText(promptText);
        this.passwordField.promptTextProperty().bind(
            this.textField.promptTextProperty());
        this.passwordField.setVisible(isPasswordField);
        this.textField.setVisible(!isPasswordField);
    }

}

其他变化也是可能的,例如您的 FXML 可以只定义没有内容的 StackPane;然后在控制器代码中根据需要添加 TextFieldPasswordField