3

I have a FXML file with 2 tabs. In each tab, I have the same list of Text elements. How to avoid having to duplicate each Text element?

Here is an extract of my FXML file:

<Tab>
    <GridPane>
        <columnConstraints>
            <ColumnConstraints />
        </columnConstraints>
        <rowConstraints>
            <RowConstraints />
            <RowConstraints />
            <RowConstraints />
            <RowConstraints />
        <RowConstraints />
        </rowConstraints>
        <children>
            <Text fx:id="text1" GridPane.rowIndex="1" />
            <Text fx:id="text2" GridPane.rowIndex="2" />
            <Text fx:id="text3" GridPane.rowIndex="3" />
            <Text fx:id="text4" GridPane.rowIndex="4" />
        </children>
    </GridPane>
</Tab>
<Tab>
    <GridPane>
        <columnConstraints>
            <ColumnConstraints />
        </columnConstraints>
        <rowConstraints>
            <RowConstraints />
            <RowConstraints />
            <RowConstraints />
            <RowConstraints />
        <RowConstraints />
        </rowConstraints>
        <children>
            <Text fx:id="text1" GridPane.rowIndex="1" />
            <Text fx:id="text2" GridPane.rowIndex="2" />
            <Text fx:id="text3" GridPane.rowIndex="3" />
            <Text fx:id="text4" GridPane.rowIndex="4" />
        </children>
    </GridPane>
</Tab>

If I put the same id in two Text elements (for example: fx:id="text1" in the two tabs), I have an error (Duplicate id reference).

Jeankowkow
  • 814
  • 13
  • 33
  • Possible duplicate of [adding elements defined in FXML to list with loop](http://stackoverflow.com/questions/34309993/adding-elements-defined-in-fxml-to-list-with-loop) – aw-think Jul 07 '16 at 14:44
  • @fabian To the extent that I can understand this question, your comment seems like the correct answer. Can you expand that into an actual answer? – James_D Jul 07 '16 at 23:10

3 Answers3

4

The fxml can be rewritten using fx:include and nested controllers.


Create a new fxml file sub.fxml containing the part of the scene that you repeat, e.g.

<Tab xmlns:fx="http://javafx.com/fxml/1" fx:controller="mypackage.SubController">
    <GridPane>
        <columnConstraints>
            <ColumnConstraints />
        </columnConstraints>
        <rowConstraints>
            <RowConstraints />
            <RowConstraints />
            <RowConstraints />
            <RowConstraints />
        <RowConstraints />
        </rowConstraints>
        <children>
            <Text fx:id="text1" GridPane.rowIndex="1" />
            <Text fx:id="text2" GridPane.rowIndex="2" />
            <Text fx:id="text3" GridPane.rowIndex="3" />
            <Text fx:id="text4" GridPane.rowIndex="4" />
        </children>
    </GridPane>
</Tab>

Create the SubController class and make the relevant parts accessible:

public class SubController {
    @FXML
    private Text text1;
    ...
    @FXML
    private Text text4;

    public void setText1(String text) {
        this.text1.setText(text);
    }

    ...
}

Now change the "main" fxml to use the included fxmls:

<fx:include source="sub.fxml" fx:id="tab1" />
<fx:include source="sub.fxml" fx:id="tab2" />

And create fields in the parent controller for injection of the SubControllers:

@FXML
private SubController tab1Controller;
@FXML
private SubController tab2Controller;

You'll be able to access the controllers just the same way you'd access other injected elements. E.g. to set the text of text1 in the first Tab:

tab1Controller.setText1("Hello World!");
fabian
  • 80,457
  • 12
  • 86
  • 114
  • Thank you for this. It's possible to get this to work in Scene Builder too, and far preferable to the other two methods I tried first (which meant that either Scene Builder couldn't show the result, or meant that the code could only refer to the included objects as `Pane` rather than the actual controller object type). – Bobulous Mar 22 '19 at 21:27
1

Yes, each component must have a unique fx:id value. You should rename the second ones to something like text11, text12... for example.

mohsenmadi
  • 2,277
  • 1
  • 23
  • 34
  • But it will become duplicate objects. I would like that the first `Text` element of the first tab is the same that the first `Text` element of the second Tab and so on. – Jeankowkow Jul 07 '16 at 14:56
  • It is a limitation of the toolkit that a node can only appear in the scene graph once. You can bind all relevant properties between different nodes to achieve equivalent nodes, but you can't have _the same_ node in multiple places in the same program. – Itai Jul 07 '16 at 16:00
  • You can't have the same object displayed in two different tabs! And if scene graph allowed that, then will have circular references that could be very hard to untangle and debug, so it's not a limitation. But, indeed, you can bind properties of different components to each other's such that values/changes of one component are an exact replica of the other. – mohsenmadi Jul 07 '16 at 16:37
  • That who demoted me, thank you. But, would you have the courtesy and knowledge to share with us why the answer is bad enough to warrant a demotion? – mohsenmadi Jul 07 '16 at 18:00
0

Ok, I based on this solution which explains how to load items from the Java code. And if I load 2 times the same item (one time in the first tab and a second time in the second tab), it's only displayed on the second tab... So an item can only be displayed once. So I just create 2 identicals items.

Here is a part of my FXML code:

<Tab>
    <GridPane>
        <columnConstraints>
            <ColumnConstraints />
        </columnConstraints>
        <rowConstraints>
            <RowConstraints />
            <RowConstraints />
            <RowConstraints />
            <RowConstraints />
        <RowConstraints />
        </rowConstraints>
        <children>
            <!-- I add my Text elements from JavaFX code -->
        </children>
    </GridPane>
</Tab>
<Tab>
    <GridPane>
        <columnConstraints>
            <ColumnConstraints />
        </columnConstraints>
        <rowConstraints>
            <RowConstraints />
            <RowConstraints />
            <RowConstraints />
            <RowConstraints />
        <RowConstraints />
        </rowConstraints>
        <children>
            <!-- I add my Text elements from JavaFX code -->
        </children>
    </GridPane>
</Tab>

And here is a part of my Java code:

@FXML private GridPane gridPaneFirstTab;
@FXML private GridPane gridPaneSecondTab;

private List<Text> textCallbacksList1 = new ArrayList<>();
private List<Text> textCallbacksList2 = new ArrayList<>();
private final List<String> callbackNames = Arrays.asList(
    "text1",
    "text2",
    "text3"
);

Text text1, text2;
for (int i = 0; i < callbackNames.size(); ++i) {
    text1 = new Text(MessagesBundle.getString(callbackNames.get(i)));
    text2 = new Text(MessagesBundle.getString(callbackNames.get(i)));
    textCallbacksList1.add(text1);
    textCallbacksList2.add(text1);

    gridPaneFirstTab.getChildren().add(text1);
    gridPaneSecondTab.getChildren().add(text2);

    gridPaneFirstTab.setConstraints(text1, 0, i + 1);
    gridPaneSecondTab.setConstraints(text2, 0, i + 1);
}
Community
  • 1
  • 1
Jeankowkow
  • 814
  • 13
  • 33