1

Using Fxml, I have my main screen with a ListView on it.

I have then made a separate Fxml. This is called a card and displays financial stock information (Stock symbol and current price with 2 labels)

How would I create multiple instances of the card so that they each have a different stock name and price, and the price can be updated?

public ListView searchList;
public Button searchButton;

@FXML
public void handleSearchButton(ActionEvent event) throws IOException {
    loadFxml();
}

public void loadFxml () throws IOException {
    Pane newLoadedPane = FXMLLoader.load(getClass().getResource("card.fxml"));
    searchList.getItems().add(newLoadedPane); 
}  

Currently that code will add a hard coded version of the card to the ListView. And I believe they are all the same, so if I were to update the text, they would all update.

0

1 Answer 1

3

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);
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.