Paul Ruddlesdin
Paul Ruddlesdin

Reputation: 103

JavaFX TableView Cell color change depending on text value

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.

Upvotes: 2

Views: 11473

Answers (3)

PawDob
PawDob

Reputation: 49

I don't have badge to comment, but wanted to add some details. I wanted to format color of cell based on the boolean value which i have in my data set. I have reviewed this question and similar one provided already here: Stackoverflow link - style based on another cell in row What was missing in both for me is reseting style when there is no value as kleopatra mentioned.

This works for me:

public class TableCellColored extends TableCell<DimensionDtoFxBean, DimValVoFxBean> {

    private static final String DEFAULT_STYLE_CLASS = "table-cell";

    public TableCellColored() {
        super();
    }

    @Override
    protected void updateItem(DimValVoFxBean item, boolean empty) {
        super.updateItem(item, empty);

        if (empty || item == null) {
            setText("");
            resetStyle();
            return;
        }

        setText(Optional.ofNullable(item.getValue()).map(BigDecimal::toString).orElse(""));

        Boolean conversionFlag = Optional.ofNullable(item.getConversionFlag()).orElse(true);

        updateStyle(conversionFlag);

        item.conversionFlagProperty()
                .addListener((observable, oldValue, newValue) -> updateStyle(newValue));
    }

    private void updateStyle(Boolean conversionFlag) {
        if (!conversionFlag) {
            setStyle("-fx-background-color: red");
        } else {
            resetStyle();
        }
    }

    private void resetStyle() {
        setStyle("");
        getStyleClass().addAll(TableCellColored.DEFAULT_STYLE_CLASS);
    }
}

Since I have value object with value and boolean flag I can do it i seperate class and don't have add lambda in controller. Deafult styling of cell is transparent so if we use style to change color, we have to reset it when there is no value. Since direct styling has bigger priority than class it overrides default styling from css classes. To be on the safe side I also apply DEFAULT_STYLE_CLASS. Value taken from TableCell class.

Without listener and styles reset I red was staying in table during scrolling. After few scrolls all cells where red. So listener and styles reset is the must have for me.

Upvotes: 0

Paul Ruddlesdin
Paul Ruddlesdin

Reputation: 103

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);
    }
`

Upvotes: 2

Sunflame
Sunflame

Reputation: 3186

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)

Upvotes: 2

Related Questions