gradlejavafxfxml

Using external fxml as a library/tag in parent fxml


Using FXML, can I only put a child.fxml into the parent.fxml, and then fill out the values from the parent? For example:

child.fxml:

<HBox xmlns="http://javafx.com/javafx"
      xmlns:fx="http://javafx.com/fxml"
      spacing="10" alignment="CENTER"
      >
    <padding>
        <Insets top="10" right="25" bottom="10" left="10"/>
    </padding>
    <Text text="$text" textAlignment="CENTER" lineSpacing="2"/>
    <TextField text="0" fx:id="number" />
</HBox>

parent.fxml

<VBox xmlns:fx="http://javafx.com/fxml"
      fx:controller="my.package.MainController"
      spacing="20">
    <Label text="Main"/>
    <fx:include fx:id="subComponent" source="child.fxml"/>
</VBox>

Here, if you look into the child.fxml, there is a Text field with a text attribute, I want to fill this dynamically, i.e, something like this

  <fx:include fx:id="subComponent" source="child.fxml" text="mytext"/>

And then the child component should be rendered with myText as its text. Is it possible? If yes, how?

NOTE <fx:include> is not what I want


Solution

  • As far as I am aware, there is no way to directly pass parameters to included FXML files using the <fx:include> element. You can create a controller for the included FXML, and pass the data from the parent controller to the child controller.

    A more convenient approach that is closer to what you're trying to achieve in this code is instead to use the FXML Custom Component pattern.

    In this pattern, you essentially create a wrapper class for the FXML file, specifying it both as the controller and as a dynamic root for the FXML:

    child.fxml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.geometry.Insets?>
    <?import javafx.scene.control.TextField?>
    <?import javafx.scene.layout.HBox?>
    <?import javafx.scene.text.Text?>
    <fx:root type="HBox" xmlns="http://javafx.com/javafx"
             xmlns:fx="http://javafx.com/fxml"
             spacing="10" alignment="CENTER"
    >
        <padding>
            <Insets top="10" right="25" bottom="10" left="10"/>
        </padding>
        <Text fx:id="textComponent" textAlignment="CENTER" lineSpacing="2"/>
        <TextField text="0" fx:id="number" />
    </fx:root>
    

    Child.java:

    package org.jamesd.examples.customchild;
    
    import javafx.beans.property.StringProperty;
    import javafx.fxml.FXML;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.layout.HBox;
    import javafx.scene.text.Text;
    
    import java.io.IOException;
    
    public class Child extends HBox {
    
    
        @FXML
        private Text textComponent;
    
        public Child() throws IOException {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("child.fxml"));
            loader.setController(this);
            loader.setRoot(this);
            loader.load();
        }
    
        public StringProperty textProperty() {
            return textComponent.textProperty();
        }
    
        public final String getText() {
            return textProperty().get();
        }
    
        public final void setText(String text) {
            textProperty().set(text);
        }
    }
    

    Note we can create properties in the wrapper class, as it is just regular Java. In this case we just expose the existing text property of the Text UI component as a JavaFX property.

    Now we can refer to the custom component just like any other UI element:

    parent.fxml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.layout.VBox?>
    <?import org.jamesd.examples.customchild.Child?>
    <VBox xmlns:fx="http://javafx.com/fxml"
          fx:controller="org.jamesd.examples.customchild.MainController"
          spacing="20">
        <Label text="Main"/>
        <Child fx:id="subComponent" text="Hello World"/>
    </VBox>
    

    For completeness, here is a (trivial) controller and application class:

    MainController.java:

    package org.jamesd.examples.customchild;
    
    public class MainController {
    }
    

    and

    HelloApplication.java

    package org.jamesd.examples.customchild;
    
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    import java.io.IOException;
    
    public class HelloApplication extends Application {
        @Override
        public void start(Stage stage) throws IOException {
            FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("parent.fxml"));
            Scene scene = new Scene(fxmlLoader.load(), 320, 240);
            stage.setTitle("Hello!");
            stage.setScene(scene);
            stage.show();
        }
    
        public static void main(String[] args) {
            launch();
        }
    }