adding elements defined in FXML to list with loop

2019-02-20 20:32发布

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!

3条回答
smile是对你的礼貌
2楼-- · 2019-02-20 20:34

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.

查看更多
萌系小妹纸
3楼-- · 2019-02-20 20:46

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);
        }
    }
}
查看更多
冷血范
4楼-- · 2019-02-20 20:46

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.

查看更多
登录 后发表回答