Search code examples
javafx-8jfoenix

TreeTableView cells does not get updated when underlying data changes


I am writing a Market Watch program which displays live quotes in a JFXTreeTableView, but when I update the data in the ObservableList, it doesn't get updated in the JFXTreeTableView. The changeDataDemo() method is actually tracking changes in the prices and is working fine (I'm using RethinkDB changefeeds to do it.) However I have removed the changefeed code here to increase readability. There is a method that adds rows to TreeTableView and is working perfectly but I have removed the code from here.

This is my code:

public class MainWindow implements Initializable {
    private ObservableList<MarketWatchEntry> marketWatchEntries = FXCollections.observableArrayList();
    private JFXTreeTableColumn<MarketWatchEntry, String> instrument_token_col;
    private JFXTreeTableColumn<MarketWatchEntry, String> exchange_col;
    private JFXTreeTableColumn<MarketWatchEntry, String> instrument_type_col;
    private JFXTreeTableColumn<MarketWatchEntry, String> symbol_col;
    private JFXTreeTableColumn<MarketWatchEntry, String> expiry_col;
    private JFXTreeTableColumn<MarketWatchEntry, Number> buy_qty_col;
    private JFXTreeTableColumn<MarketWatchEntry, Number> buy_price_col;
    private JFXTreeTableColumn<MarketWatchEntry, Number> seller_price_col;
    private JFXTreeTableColumn<MarketWatchEntry, Number> sell_qty_col;


    @FXML
    private JFXTreeTableView<MarketWatchEntry> MarketWatch;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        populateMarketWatch();
    }

    private void populateMarketWatch() {
        instrument_token_col = new JFXTreeTableColumn<>("Instrument Token");
        instrument_token_col.setPrefWidth(80);
        instrument_token_col.setVisible(false);
        instrument_token_col.setCellValueFactory((TreeTableColumn.CellDataFeatures<MarketWatchEntry, String> param) -> param.getValue().getValue().instrument_token);

        exchange_col = new JFXTreeTableColumn<>("Exchange");
        exchange_col.setPrefWidth(80);
        exchange_col.setCellValueFactory((TreeTableColumn.CellDataFeatures<MarketWatchEntry, String> param) -> param.getValue().getValue().exchange);

        instrument_type_col = new JFXTreeTableColumn<>("Instrument");
        instrument_type_col.setPrefWidth(80);
        instrument_type_col.setCellValueFactory((TreeTableColumn.CellDataFeatures<MarketWatchEntry, String> param) -> param.getValue().getValue().instrument_type);

        symbol_col = new JFXTreeTableColumn<>("Symbol");
        symbol_col.setPrefWidth(80);
        symbol_col.setCellValueFactory((TreeTableColumn.CellDataFeatures<MarketWatchEntry, String> param) -> param.getValue().getValue().trading_symbol);

        expiry_col = new JFXTreeTableColumn<>("Expiry");
        expiry_col.setPrefWidth(80);
        expiry_col.setCellValueFactory((TreeTableColumn.CellDataFeatures<MarketWatchEntry, String> param) -> param.getValue().getValue().expiry);

        buy_qty_col = new JFXTreeTableColumn<>("Buy Qty");
        buy_qty_col.setPrefWidth(80);
        buy_qty_col.setCellValueFactory((TreeTableColumn.CellDataFeatures<MarketWatchEntry, Number> param) -> param.getValue().getValue().buy_qty);

        buy_price_col = new JFXTreeTableColumn<>("Buyer Price");
        buy_price_col.setPrefWidth(80);
        buy_price_col.setCellValueFactory((JFXTreeTableColumn.CellDataFeatures<MarketWatchEntry, Number> param) -> param.getValue().getValue().buyer_price);

        seller_price_col = new JFXTreeTableColumn<>("Seller Price");
        seller_price_col.setPrefWidth(80);
        seller_price_col.setCellValueFactory((TreeTableColumn.CellDataFeatures<MarketWatchEntry, Number> param) -> param.getValue().getValue().seller_price);

        sell_qty_col = new JFXTreeTableColumn<>("Sell Qty");
        sell_qty_col.setPrefWidth(80);
        sell_qty_col.setCellValueFactory((TreeTableColumn.CellDataFeatures<MarketWatchEntry, Number> param) -> param.getValue().getValue().sell_qty);

      final TreeItem<MarketWatchEntry> root = new RecursiveTreeItem<>(marketWatchEntries, RecursiveTreeObject::getChildren);
        MarketWatch.getColumns().setAll(instrument_token_col, exchange_col, instrument_type_col, symbol_col, expiry_col,
                buy_qty_col, buy_price_col, seller_price_col, sell_qty_col, ltp_col, ltq_col, open_price_col, high_price_col,
                low_price_col, close_price_col, average_price_col, change_col, net_change_col);
        MarketWatch.setRoot(root);
        MarketWatch.setShowRoot(false);
    }

    private void changeDataDemo() {
        marketWatchEntries.get(0).buyer_price = new SimpleDoubleProperty(100);
    }
}

I have removed irrelevant code blocks to increase readability. My question is how to get TreeTableView to update all the cells when the collection is updated with new data.

MarketWatchEntry class

class MarketWatchEntry extends RecursiveTreeObject<MarketWatchEntry> {
        StringProperty instrument_token;
        StringProperty exchange;
        StringProperty instrument_type;
        StringProperty trading_symbol;
        StringProperty expiry;
        DoubleProperty buy_qty;
        DoubleProperty buyer_price;
        DoubleProperty seller_price;
        DoubleProperty sell_qty;
        DoubleProperty last_price;
        DoubleProperty last_qty;
        DoubleProperty open_price;
        DoubleProperty high_price;
        DoubleProperty low_price;
        DoubleProperty close_price;
        DoubleProperty average_price;
        DoubleProperty change;
        DoubleProperty net_change;

        public MarketWatchEntry(String instrument_token, String exchange, String instrument_type, String trading_symbol,
                                String expiry, double buy_qty, double buy_price, double sell_price, double sell_qty,
                                double last_price, double last_qty, double open_price, double high_price, double low_price,
                                double close_price, double average_price, double change, double net_change) {
            this.instrument_token = new SimpleStringProperty(instrument_token);
            this.exchange = new SimpleStringProperty(exchange);
            this.instrument_type = new SimpleStringProperty(instrument_type);
            this.trading_symbol = new SimpleStringProperty(trading_symbol);
            this.expiry = new SimpleStringProperty(expiry);
            this.buy_qty = new SimpleDoubleProperty(buy_qty);
            this.buyer_price = new SimpleDoubleProperty(buy_price);
            this.sell_qty = new SimpleDoubleProperty(sell_qty);
            this.seller_price = new SimpleDoubleProperty(sell_price);
            this.last_price = new SimpleDoubleProperty(last_price);
            this.last_qty = new SimpleDoubleProperty(last_qty);
            this.open_price = new SimpleDoubleProperty(open_price);
            this.high_price = new SimpleDoubleProperty(high_price);
            this.low_price = new SimpleDoubleProperty(low_price);
            this.close_price = new SimpleDoubleProperty(close_price);
            this.average_price = new SimpleDoubleProperty(average_price);
            this.change = new SimpleDoubleProperty(change);
            this.net_change = new SimpleDoubleProperty(net_change);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null || getClass() != obj.getClass()) {
                return false;
            }
            return this.instrument_token.getValue().equals(((MarketWatchEntry) obj).instrument_token.getValue());
        }

        @Override
        public int hashCode() {
            return 7 + 5 * Integer.valueOf(instrument_token.getValue());
        }


    }

Solution

  • The problem is in your changeDataDemo method:

    private void changeDataDemo() {
        marketWatchEntries.get(0).buyer_price = new SimpleDoubleProperty(100);
    }
    

    You are creating new instances of your properties instead of changing their values.

    Something like this works:

    private void changeDataDemo() {
        marketWatchEntries.get(0).buyer_price.set(100);
    }
    

    But it is recommended to provide setters/getters on your Entity class:

    class MarketWatchEntry extends RecursiveTreeObject<MarketWatchEntry> {
        private final DoubleProperty buyer_price;
        ...
    
        public MarketWatchEntry(String instrument_token, String exchange, String instrument_type, String trading_symbol,
                                String expiry, double buy_qty, double buy_price, double sell_price, double sell_qty) {
            ...
            this.buyer_price = new SimpleDoubleProperty(buy_price);
            ...  
        }
    
        public final DoubleProperty buyer_priceProperty() {
            return buyer_price;
        }
    
        public final double getBuyer_price() {
            return buyer_price.get();
        }
    
        public final void setBuyer_price(double value) {
            buyer_price.set(value);
        }
        ...
     }
    

    so you can just call:

    private void changeDataDemo() {
        marketWatchEntries.get(0).setBuyer_price(100);
    }