2

I have a lot of Objects with names a1, a2, a3, ... I need to put them into List, so that it was simplier to work with them, will I do it somehow with loop?

My attempt was:

List<SomeObject> list = new LinkedList<SomeObject>();
for (int i=0; i<1000; i++){
    String varName = "a"+i;
    list.add((SomeObject) varName);
}

Does anyone have suggestions in this case? Create variables inside loop is not a solution, because they are a part of .fxml document. Or give me an advice how to create that with loop, for it create lines in .fxml parallel to adding in loop new objects.

To be more understandable .fxml file looks like

  <SomeObject fx:id="a1" *other props* />
  <SomeObject fx:id="a2" *other props* />
  <SomeObject fx:id="a3" *other props* />
  <SomeObject fx:id="a4" *other props* />

Thanks a lot in advice!

Uluk Biy
  • 48,655
  • 13
  • 146
  • 153
Vitali
  • 549
  • 6
  • 16
  • why would you create **lot** of objects a1,a2,a3 and not put them in list in the first place? – zubergu Dec 16 '15 at 11:32
  • Are you really defining 1,000 items in an FXML file like that? Surely it would be better just to define them in Java (i.e. in your controller) in the first place, and add them into the UI there. – James_D Dec 16 '15 at 13:36
  • It takes just 10 copy-pastes, 2^10=1024 so yes, i did it like that in Scene builder=) James_D, will you give an example, to understand what you mean? – Vitali Dec 16 '15 at 15:44

3 Answers3

9

If you have that many items, it's probably best to initialize them using Java, rather than using FXML. For example, instead of:

<FlowPane fx:id="container" minWidth="..." minHeight="...">
    <Label fx:id="label1" text="Label 1"/>
    <Label fx:id="label2" text="Label 2"/>
    <Label fx:id="label3" text="Label 3"/>

    <!-- ... -->

    <Label fx:id="label1000" text="Label 1000"/>
</FlowPane>

and a controller

public class Controller {

    @FXML
    private FlowPane container ;
    @FXML
    private Label label1 ;
    @FXML
    private Label label2 ;
    // ...

    @FXML
    private Label label1000 ;

    // ...
}

I would do

<FlowPane fx:id="container" minWidth="..." minHeight="...">
</FlowPane>

and

public class Controller {

    @FXML
    private FlowPane container ;

    private List<Label> labels ;

    public void initialize() {
        labels = new ArrayList<>();
        for (int i = 1; i <= 1000; i++) {
            Label label = new Label("Label "+i);
            labels.add(label);
            container.getChildren().add(label);
        }
    }
}

As a variation on this idea, consider defining a custom component:

public class LabelFlow extends FlowPane {

    private List<Label> labels ;

    public LabelFlow(@NamedArg("numLabels") int numLabels) {
        labels = new ArrayList<>();
        for(int i = 1 ; i <= numLabels ; i++) {
            Label label = new Label("Label "+i);
            labels.add(label);
        }
        getChildren().addAll(labels);
    }

    public List<Label> getLabels() {
        return Collections.unmodifiableList(labels);
    }
}

Now in your FXML you do

<LabelFlow fx:id="labelFlow" numLabels="1000"/>

and in your controller

public class Controller {
    @FXML
    private LabelFlow labelFlow ;

    public void initialize() {
        for (Label label : labelFlow.getLabels()) {
            // do whatever you need with label....
        }
    }
}

You need to jump through a couple of hoops if you want to use a custom class like that in Scene Builder. See Adding a custom component to SceneBuilder 2.0

If you really want to define all those controls in FXML, which would be a maintenance nightmare imo, you can use reflection to access the variables. I don't recommend this, not just because it's hard to maintain, but also because reflection by its nature is error-prone (no compile-time checking) and complex.

But you could do

public class Controller {

    @FXML
    private FlowPane container ;
    @FXML
    private Label label1 ;
    @FXML
    private Label label2 ;
    // ...

    @FXML
    private Label label1000 ;

    private List<Label> labels ;

    public void initialize() throws Exception {
        labels = new ArrayList<>();
        for (int i = 1; i <= 1000; i++) {
            Field field = getClass().getDeclaredField("label"+i);
            boolean wasAccessible = field.isAccessible();
            field.setAccessible(true);
            Label label = (Label) field.get(this);
            field.setAccessible(wasAccessible);
            labels.add(label);
        }
    }
}
Community
  • 1
  • 1
James_D
  • 201,275
  • 16
  • 291
  • 322
5

You may prefer to put your objects into the list in the fxml directly:

<fx:define>
     <FXCollections fx:id="theList" fx:factory="observableArrayList">
         <SomeObject someProperty="SomeValue 1" />
         <SomeObject someProperty="SomeValue 2" />
         <SomeObject someProperty="SomeValue 3" />
         <SomeObject someProperty="SomeValue 4" />
      </FXCollections>
</fx:define>

and access it in the controller as:

@FXML
private ObservableList<SomeObject> theList;

@Override
public void initialize( URL url, ResourceBundle rb )
{
    // do whatever with the list
    System.out.println( "the list of some objects = " + theList);
}

if you are not accessing each SomeObject individually you can drop fx:ids for them. For more info about fxml features refer to Introduction to FXML.

Uluk Biy
  • 48,655
  • 13
  • 146
  • 153
3

Personally I'd prefer the other possibility, but just for the sake of completness:

You can also access Objects created by the FXMLLoader by the fx:id attribute after loading, using FXMLLoader.getNamespace():

Example

namespace.fxml

<AnchorPane xmlns:fx="http://javafx.com/fxml/1">
    <children>
        <Text fx:id="node1" text="42"/>
    </children>
</AnchorPane>

Loading

FXMLLoader loader = new FXMLLoader(getClass().getResource("namespace.fxml"));
loader.load();
System.out.println(((Text)loader.getNamespace().get("node1")).getText());

Prints the text from the Text element in the fxml (i.e. 42).

Of course you do not get access to the FXMLLoader instance by default in the controller, which means with this approach you need to pass the information to the controller yourself, if it's required there.

fabian
  • 80,457
  • 12
  • 86
  • 114