2 Replies Latest reply: Jul 9, 2013 4:42 PM by meri RSS

    TableView cell coloring on updates - noticing if sorting happened


      I am trying to make cells highlighted (colored) in a TableView every time they are updated. My solution is based on setting up cell factories and use Animation (Timeline) to control cell background opacity - it is working fine: every time a cell value is updated, the background color changes, then fades away nicely.


      The problem is, that if the table is being sorted by clicking on a column header, the underlying item list is changed (of course), and at the GUI level it is also understood as a change, and based on the per-cell circumstances (I flash a cell only if its new value is different than the previous), multiple cells indicate an update unnecessarily (flashing).


      I wonder how could I eliminate if a cell has been updated because of a "real" update (an item has been updated) from the case when the list sort column and/or order has been changed? I was thinking of catching the sorting event (by adding an InvalidationListener to the list of the sort orders) and ignore the updates while it is in progress, but I cannot really know when it is finished. I bet at the GUI level there is no way to decide the reason, so I would need a deeper understanding...


      Thanks for any hints.

        • 1. Re: TableView cell coloring on updates - noticing if sorting happened

          Register a change listener with the appropriate property to change the highlight color of the cell as appropriate. You need to do a bit of work to make sure the listener is registered and unregistered as appropriate as the cell is reused for different table items: one trick is to use

          Bindings.select(tableRowProperty(), "item")

          to get an observable of the current value for the row.




          Well, the previous example didn't work. For reasons I can't figure out, after sorting (or scrolling, in fact), the wrong cell was getting change notifications from the property.


          So here's an alternative strategy: keep track of the last known row value for the cell, and only do the highlighting if the same row value is still referred to after the update. I kind of prefer the strategy outlined above, but I couldn't seem to make it work.



          import java.text.NumberFormat;
          import java.util.Arrays;
          import java.util.List;
          import java.util.Random;
          import javafx.animation.FadeTransition;
          import javafx.animation.KeyFrame;
          import javafx.animation.Timeline;
          import javafx.application.Application;
          import javafx.beans.property.DoubleProperty;
          import javafx.beans.property.ReadOnlyStringProperty;
          import javafx.beans.property.ReadOnlyStringWrapper;
          import javafx.beans.property.SimpleDoubleProperty;
          import javafx.event.ActionEvent;
          import javafx.event.EventHandler;
          import javafx.geometry.Pos;
          import javafx.scene.Scene;
          import javafx.scene.control.Label;
          import javafx.scene.control.TableCell;
          import javafx.scene.control.TableColumn;
          import javafx.scene.control.TableView;
          import javafx.scene.control.cell.PropertyValueFactory;
          import javafx.scene.layout.BorderPane;
          import javafx.scene.layout.StackPane;
          import javafx.scene.paint.Color;
          import javafx.scene.shape.Rectangle;
          import javafx.stage.Stage;
          import javafx.util.Callback;
          import javafx.util.Duration;
          public class TableChangeHighlightExample extends Application {
            public void start(Stage primaryStage) {
              final Random rng = new Random();
            final TableView<StockValue> table = new TableView<>();
            TableColumn<StockValue, String> symbolColumn = new TableColumn<>("Symbol");
            TableColumn<StockValue, Double> priceColumn = new TableColumn<>("Price");
            symbolColumn.setCellValueFactory(new PropertyValueFactory<StockValue, String>("symbol"));
            priceColumn.setCellValueFactory(new PropertyValueFactory<StockValue, Double>("price"));
            priceColumn.setCellFactory(new Callback<TableColumn<StockValue, Double>, TableCell<StockValue, Double>>() {
                public TableCell<StockValue, Double> call(TableColumn<StockValue, Double> table) {
                  return new StockPriceCell();
            table.getColumns().addAll(Arrays.asList(symbolColumn, priceColumn));
            Timeline changeStockPricesRandomly = new Timeline(new KeyFrame(Duration.seconds(2), new EventHandler<ActionEvent>() {
              public void handle(ActionEvent event) {
                StockValue stockToChange = table.getItems().get(rng.nextInt(table.getItems().size()));
                double changeFactor = 0.9 + rng.nextInt(200) / 1000.0 ;
                double oldPrice = stockToChange.getPrice();
                double newPrice = oldPrice * changeFactor ;
          //     System.out.println("Changing price for "+stockToChange+" to "+newPrice);
                stockToChange.setPrice(newPrice) ;
            BorderPane root = new BorderPane();
            primaryStage.setScene(new Scene(root, 300, 400));
            private List<StockValue> createData(Random rng) {
              return Arrays.asList(
                new StockValue("ORCL", 20.0 + rng.nextInt(2000)/100.0),
                new StockValue("AAPL", 300.0 + rng.nextInt(20000)/100.0),
                new StockValue("GOOG", 500.0 + rng.nextInt(100000)/100.0),
                new StockValue("YHOO", 20.0 + rng.nextInt(2000)/100.0),
                new StockValue("FB", 20.0 + rng.nextInt(1000)/100.0)
            public static void main(String[] args) {
            public static class StockPriceCell extends TableCell<StockValue, Double> {
              private static final Color INCREASE_HIGHLIGHT_COLOR = Color.GREEN ;
              private static final Color DECREASE_HIGHLIGHT_COLOR = Color.RED ;
              private static final Duration HIGHLIGHT_TIME = Duration.millis(500);
              private static final NumberFormat formatter = NumberFormat.getCurrencyInstance();
              private final Label priceLabel ;
              private final Rectangle overlayRectangle ;
              private StockValue lastRowValue ;
              public StockPriceCell() {
                overlayRectangle = new Rectangle();
                this.priceLabel = new Label();
                StackPane pane = new StackPane();
                pane.getChildren().addAll(overlayRectangle, priceLabel);
              protected void updateItem(Double price, boolean empty) {
                Double oldPrice = getItem();
                super.updateItem(price, empty);
                StockValue currentRowValue = null ;
                if (getTableRow()!=null) {
                  currentRowValue = (StockValue) getTableRow().getItem();
                if (empty) {
                } else {
                  if (price != null && oldPrice != null && currentRowValue == lastRowValue) {
                    if (price.doubleValue() > oldPrice.doubleValue()) {
                    } else if (price.doubleValue() < oldPrice.doubleValue()) {
                    FadeTransition fade = new FadeTransition(HIGHLIGHT_TIME, overlayRectangle);
                lastRowValue = currentRowValue ;
           public static class StockValue {
              private final ReadOnlyStringWrapper symbol ;
              private final DoubleProperty price ;
              public StockValue(String symbol, double price) {
                this.symbol = new ReadOnlyStringWrapper(this, "symbol", symbol);
                this.price = new SimpleDoubleProperty(this, "price", price);
              public final ReadOnlyStringProperty symbolProperty() {
                return symbol.getReadOnlyProperty();
              public final String getSymbol() {
                return symbol.get();
              public final DoubleProperty priceProperty() {
                return price ;
              public final double getPrice() {
                return price.get();
              public final void setPrice(double price) {
              public String toString() {
                return symbol.get() + " : " + price.get() ;


          Message was edited by: James_D

          • 2. Re: TableView cell coloring on updates - noticing if sorting happened

            You simply rock James, thanks a lot.


            Your example was just running out of the box. After checking it, I managed to apply the necessary changes to my code.


            What I cannot see yet at the moment is that if I have multiple columns I'd like to be highlighted, what would be the easiest way to add/remove listeners in the select() part to the appropriate column properties... but let's leave something for tomorrow as well.