To create multiple instances of the UI component defined by your FXML, you simply load the FXML file multiple times. If you want each to have different data, define methods in the controller to change the data. Then you can basically do
for (int i = 0 ; i < numberOfStocks ; i++) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("card.fxml"));
Pane stockView = loader.load();
StockController controller = loader.getController();
controller.setXXX(...);
// ...
}
I strongly do not recommend populating a ListView with UI nodes, as you show in the code in the question. UI elements are quite expensive objects to create (they have hundreds of properties, and are supported by CSS which may need to be parsed, etc). The data you are representing is pretty simple: a string and a double representing the stock symbol and price. A ListView is designed to be highly efficient, in that it only creates UI elements for the visible data, reusing those elements as, for example, the user scrolls through them. So your ListView should use only the data in its backing list, not the UI elements.
So create a simple class to represent a stock:
public class Stock {
private final String symbol ; // doesn't change
private final DoubleProperty price = new SimpleDoubleProperty() ; // mutable and observable
public Stock(String symbol, double price) {
this.symbol = symbol ;
setPrice(price);
}
public DoubleProperty priceProperty() {
return price ;
}
public final double getPrice() {
return priceProperty().get();
}
public final void setPrice(double price) {
priceProperty().set(price);
}
public String getSymbol() {
return symbol ;
}
}
This Stock class used JavaFX properties which will make it easy to bind to them later (so the UI can automatically update if the price changes).
Now you can define a simple FXML:
<!-- imports etc -->
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.StockController"/>
<Label fx:id="symbolLabel"/>
<Label fx:id="priceLabel" />
</VBox>
with its controller, which can implement the binding of the labels to the stock it is displaying:
public class StockController {
private Stock stock ;
@FXML
private Label symbolLabel ;
@FXML
private Label priceLabel ;
public void setStock(Stock stock) {
this.stock = stock ;
priceLabel.textProperty().unbind();
if (stock == null) {
symbolLabel.setText(null);
priceLabel.setText(null);
} else {
symbolLabel.setText(stock.getSymbol());
priceLabel.textProperty().bind(stock.priceProperty().asString("Price: $%.2f"));
}
}
}
Now you can create a ListView<Stock> and use its cell factory to display each stock using the FXML:
public class MainController {
@FXML
private ListView<Stock> searchList ;
public void initialize() {
searchList.setCellFactory(lv -> new StockListCell());
}
}
public class StockListCell extends ListCell<Stock> {
private final Pane stockView ;
private final StockController stockController ;
public StockListCell() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("card.fxml"));
stockView = loader.load();
stockController = loader.getController();
setGraphic(stockView);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} catch (IOException exc) {
// IOException here is fatal:
throw new UncheckedIOException(exc);
}
}
@Override
protected void updateItem(Stock item, boolean empty) {
super.updateItem(item, empty);
stockController.setStock(item);
}
}
Note that the ListView will not create many StockListCells (just one for each visible cell), so it's OK to do fairly heavy work (such as loading the FXML) in the constructor. The updateItem(...) method, by contrast, might be called quite frequently (e.g. as the user scrolls), and should do minimal work. Here we just update the stock that our UI is displaying, which just essentially changes the text of a couple of labels.
And, finally, you can simply populate the list view with Stocks as you expect:
for (int i = 0 ; i < numberOfStocks ; i++) {
String symbol = "Stock "+i ;
double price = 1000*Math.random();
Stock stock = new Stock(symbol, price) ;
searchList.getItems().add(stock);
}