JavaFX TableView Cell color change depending on text value


Keywords:javafx 


Question: 

I have a JavaFX desktop app with a TableView. I populate the data using a POJO named Orders which ultimately comes from a Firebird SQL database. Image of what I have now

What I am looking to do is change the background fill color of each cell in the first column 'Status' depending on the text value. So if the text value is 'READY' then green, 'STARTED' will be yellow and 'DONE' will be gray. Image of what I would like

Here is the code portion I use to populate the TableView:

`
@FXML private TableView<Orders> tblOrders;
@FXML private TableColumn<Orders, Integer> clmStatus;
@FXML private TableColumn<Orders, String> clmStartDateTime;
@FXML private TableColumn<Orders, String> clmShopOrder;
@FXML private TableColumn<Orders, String> clmRotation;
@FXML private TableColumn<Orders, String> clmGMIECode;
@FXML private TableColumn<Orders, String> clmSAPCode;
@FXML private TableColumn<Orders, Integer> clmLineName;
@FXML private TableColumn<Orders, Integer> clmOrderProductionNr;
private ObservableList<Orders> list;

public void initialize(URL location, ResourceBundle resources) {
    populateTable();
}

private void populateTable() {
    log.appLog("Populating table\r\n");
    clmStatus.setCellValueFactory(new PropertyValueFactory<>("status"));
    clmStartDateTime.setCellValueFactory(new PropertyValueFactory<>
        ("startDateTime"));
    clmShopOrder.setCellValueFactory(new PropertyValueFactory<>("extra1"));
    clmRotation.setCellValueFactory(new 
        PropertyValueFactory<("batchLotNr"));
    clmGMIECode.setCellValueFactory(new PropertyValueFactory<>("wareNr"));
    clmSAPCode.setCellValueFactory(new PropertyValueFactory<>
        ("serviceDescription"));
    clmLineName.setCellValueFactory(new PropertyValueFactory<>
        ("productionLineNr"));
    clmOrderProductionNr.setCellValueFactory(new PropertyValueFactory<>
        ("orderProductionNr"));
    tblOrders.setItems(list);
}
`

Code sample of my Orders POJO:

`
public class Orders {
    private final SimpleStringProperty status;
    private final SimpleStringProperty startDateTime;
    private final SimpleStringProperty extra1;
    private final SimpleStringProperty batchLotNr;
    private final SimpleStringProperty wareNr;
    private final SimpleStringProperty serviceDescription;
    private final SimpleStringProperty productionLineNr;
    private final SimpleIntegerProperty orderProductionNr;

    Orders(String status, String startDateTime, String extra1, String batchLotNr, String wareNr, String serviceDescription, String productionLineNr, int orderProductionNr) {
        this.status = new SimpleStringProperty(status);
        this.startDateTime = new SimpleStringProperty(startDateTime);
        this.extra1 = new SimpleStringProperty(extra1);
        this.batchLotNr = new SimpleStringProperty(batchLotNr);
        this.wareNr = new SimpleStringProperty(wareNr);
        this.serviceDescription = new SimpleStringProperty(serviceDescription);
        this.productionLineNr = new SimpleStringProperty(productionLineNr);
        this.orderProductionNr = new SimpleIntegerProperty((orderProductionNr));
    }

    public String getStatus() {
        return status.get();
    }

    public String getStartDateTime() {return startDateTime.get(); }

    public String getExtra1() {
        return extra1.get();
    }

    public String getBatchLotNr() {
        return batchLotNr.get();
    }

    public String getWareNr() {
        return wareNr.get();
    }

    public String getServiceDescription() {
        return serviceDescription.get();
    }

    public String getProductionLineNr() {
        return productionLineNr.get();
    }

    int getOrderProductionNr() {return orderProductionNr.get();}
}
`

I have tried using a callback but I have never used callbacks before and don't properly understand how I can fit my needs into a callback. Any help will be important to my learning. Thanks SO.


2 Answers: 

I finally found the solution without having to use any extra classes, just a callback in my controller class with the help of this SO link: StackOverFlow Link

`
private void populateTable() {
        log.appLog("Populating table\r\n");
        //clmStatus.setCellValueFactory(new PropertyValueFactory<>("status"));

        clmStatus.setCellFactory(new Callback<TableColumn<Orders, String>,
                TableCell<Orders, String>>()
        {
            @Override
            public TableCell<Orders, String> call(
                    TableColumn<Orders, String> param) {
                return new TableCell<Orders, String>() {
                    @Override
                    protected void updateItem(String item, boolean empty) {
                        if (!empty) {
                            int currentIndex = indexProperty()
                                    .getValue() < 0 ? 0
                                    : indexProperty().getValue();
                            String clmStatus = param
                                    .getTableView().getItems()
                                    .get(currentIndex).getStatus();
                            if (clmStatus.equals("READY")) {
                                setTextFill(Color.WHITE);
                                setStyle("-fx-font-weight: bold");
                                setStyle("-fx-background-color: green");
                                setText(clmStatus);
                            } else if (clmStatus.equals("STARTED")){
                                setTextFill(Color.BLACK);
                                setStyle("-fx-font-weight: bold");
                                setStyle("-fx-background-color: yellow");
                                setText(clmStatus);
                            } else if (clmStatus.equals("DONE")){
                                setTextFill(Color.BLACK);
                                setStyle("-fx-font-weight: bold");
                                setStyle("-fx-background-color: gray");
                                setText(clmStatus);
                            } else {
                                setTextFill(Color.WHITE);
                                setStyle("-fx-font-weight: bold");
                                setStyle("-fx-background-color: red");
                                setText(clmStatus);
                            }
                        }
                    }
                };
            }
        });

        clmStartDateTime.setCellValueFactory(new PropertyValueFactory<>("startDateTime"));
        clmShopOrder.setCellValueFactory(new PropertyValueFactory<>("extra1"));
        clmRotation.setCellValueFactory(new PropertyValueFactory<>("batchLotNr"));
        clmGMIECode.setCellValueFactory(new PropertyValueFactory<>("wareNr"));
        clmSAPCode.setCellValueFactory(new PropertyValueFactory<>("serviceDescription"));
        clmLineName.setCellValueFactory(new PropertyValueFactory<>("productionLineNr"));
        clmOrderProductionNr.setCellValueFactory(new PropertyValueFactory<>("orderProductionNr"));
        tblOrders.setItems(list);
    }
`
 

You have to define a custom TableCell for your status column like this:

public class ColoredStatusTableCell extends TableCell<TableRow, Status> {

    @Override
    protected void updateItem(Status item, boolean empty) {
        super.updateItem(item, empty);
        if (empty || getTableRow() == null) {
            setText(null);
            setGraphic(null);
        } else {
            TableRow row = (TableRow) getTableRow().getItem();
            setText(item.toString());
            setStyle("-fx-background-color: " + row.getColorAsString());
            // If the statis is changing dynamic you have to add the following:
            row.statusProperty()
                .addListener((observable, oldValue, newValue) -> 
                        setStyle("-fx-background-color: " + row.getColorAsString()));
        }
    }
}

Where TableRow:

public class TableRow {

    private ObjectProperty<Status> status;
    private Map<Status, Color> statusColor;

    public TableRow(Status status, Map<Status, Color> statusColor) {
        this.status = new SimpleObjectProperty<>(status);
        this.statusColor = statusColor;
    }

    public Status getStatus() {
        return status.get();
    }

    public ObjectProperty<Status> statusProperty() {
        return status;
    }

    public Color getStatusColor() {
        return statusColor.get(status.get());
    }

    public String getColorAsString() {
        return String.format("#%02X%02X%02X",
                (int) (getStatusColor().getRed() * 255),
                (int) (getStatusColor().getGreen() * 255),
                (int) (getStatusColor().getBlue() * 255));
    }
}

Status:

public enum Status {
    READY, STARTED, DONE
}

and the controller:

public class TestController {

    @FXML
    private TableView<TableRow> table;
    @FXML
    private TableColumn<TableRow, Status> column;

    private ObservableList<TableRow> data = FXCollections.observableArrayList();

    @FXML
    public void initialize() {
        column.setCellValueFactory(data -> data.getValue().statusProperty());
        column.setCellFactory(factory -> new ColoredStatusTableCell());
        Map<Status, Color> statusColor = new HashMap<>();
        statusColor.put(Status.READY, Color.GREEN);
        statusColor.put(Status.STARTED, Color.YELLOW);
        statusColor.put(Status.DONE, Color.GRAY);

        TableRow ready = new TableRow(Status.READY, statusColor);
        TableRow started = new TableRow(Status.STARTED, statusColor);
        TableRow done = new TableRow(Status.DONE, statusColor);

        data.addAll(ready, started, done);

        table.setItems(data);
    }

}

I chose to set the status as an enum because it is easier to handle it, then I have used a map to each status-color combination, then in the cell you can set its background color to the matched color of the status.

If you want of course instead of Color.YELLOW and so on you can use a custom Color.rgb(red,green,blue)