javajavafxtableview

In a TableView, can I format an entire rows cells based on one cell's value?


I have a TableView where I format the cells style based on the value (see code). I'm wondering if there's a way to format the entire row that cell belongs to based on that cells value or the value of the object (In my case step.getStatus()). This seems like it would be straight forward but I've struggled to find any reference to do it.

colStatus.setCellFactory(new Callback<TableColumn<Step, String>, TableCell<Step, String>>()
    {
        @Override
        public TableCell<Step, String> call(TableColumn<Step, String> param) {
            return new TableCell<Step, String>() {
                @Override
                protected void updateItem(String item, boolean empty) {
                    if (!empty) {
                        int currentIndex = indexProperty().getValue() < 0 ? 0 : indexProperty().getValue();
                        setText(param.getTableView().getItems().get(currentIndex).getStatus().name());
                        switch(param.getTableView().getItems().get(currentIndex).getStatus())
                        {
                            case Step.STATUS.Enabled:
                                setTextFill(Color.BLACK);
                                break;
                            case Step.STATUS.Disabled:
                                setTextFill(Color.GRAY);
                                break;
                            case Step.STATUS.Running:
                                setTextFill(Color.BLUEVIOLET);
                                break;
                            case Step.STATUS.Complete:
                                setTextFill(Color.LIME);
                                break;
                            case Step.STATUS.Error:
                                setTextFill(Color.RED);
                                setStyle("-fx-background-color: pink;");
                                break;
                        }                
                    }
                }
            };
        }
    });

enter image description here


Solution

  • Assuming you have a statusProperty() method defined in your Step class, and are using JavaFX 19 or later, you can use a table row factory. The following should give you the idea:

    table.setRowFactory( _ -> new TableRow() {
        private final List<String> statusClasses = List.of(
            "enabled",
            "disabled",
            "running",
            "complete",
            "error"
        );
        {
            itemProperty().flatMap(Step::statusProperty).map(String::toLowerCase).subscribe(status -> {
                for (String s : statusClasses) {
                    PsuedoClass pc = PseudoClass.getPseudoClass(s);
                    pseudoClassStateChanged(pc, s.equals(status));
                }
            });
        }
    });
    

    and then use an external stylesheet with

    .table-row-cell:enabled {
        -fx-text-fill: black;
    }
    
    .table-row-cell:disabled {
        -fx-text-fill: gray;
    }
    
    .table-row-cell:running {
        -fx-text-fill: blueviolet;
    }
    
    .table-row-cell:complete {
        -fx-text-fill: lime;
    }
    
    .table-row-cell:error {
        -fx-text-fill: red;
        -fx-background: pink;
    }
    
    .table-cell {
        -fx-text-fill: inherit;
    }
    

    Here is a minimal reproducible example demonstrating the idea. It looks slightly different as I implemented status as an enum, not a String. Use the CSS file above as style.css (in the same package as the application class).

    
    import javafx.application.Application;
    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.css.PseudoClass;
    import javafx.scene.Scene;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableRow;
    import javafx.scene.control.TableView;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    
    public class HelloApplication extends Application {
        @Override
        public void start(Stage stage) {
            TableView<Step> table = new TableView<>();
            TableColumn<Step, Step.Status> statusCol = new TableColumn<>("Status");
            statusCol.setCellValueFactory(data -> data.getValue().statusProperty());
            table.getColumns().add(statusCol);
    
            TableColumn<Step, String> descriptionCol = new TableColumn<>("Description");
            descriptionCol.setCellValueFactory(data -> data.getValue().descriptionProperty());
            table.getColumns().add(descriptionCol);
    
            table.setRowFactory( _ -> new TableRow<>() {
                {
                    itemProperty().flatMap(Step::statusProperty).subscribe(currentStatus -> {
                        for (Step.Status status : Step.Status.values()) {
                            String statusName = status.toString().toLowerCase();
                            PseudoClass pc = PseudoClass.getPseudoClass(statusName);
                            pseudoClassStateChanged(pc, status == currentStatus);
                        }
                    });
                }
            });
    
            for (int i = 0 ; i < 5; i++) {
                table.getItems().addAll(
    
                        new Step("Set stop offset Az to 0", Step.Status.ENABLED),
                        new Step("Set stop offset El to 0", Step.Status.ERROR),
                        new Step("Message", Step.Status.COMPLETE),
                        new Step("Message", Step.Status.RUNNING),
                        new Step("Disabled", Step.Status.DISABLED)
                );
            }
            Scene scene = new Scene(new BorderPane(table));
            scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
            stage.setScene(scene);
            stage.show();
        }
    
        public static class Step {
            public enum Status {ENABLED, DISABLED, RUNNING, COMPLETE, ERROR}
    
            private final ObjectProperty<Status> status = new SimpleObjectProperty<>();
            private final StringProperty description = new SimpleStringProperty();
    
            public final Status getStatus() {
                return status.get();
            }
    
            public ObjectProperty<Status> statusProperty() {
                return status;
            }
    
            public final void setStatus(Status status) {
                this.status.set(status);
            }
    
            public final String getDescription() {
                return description.get();
            }
    
            public StringProperty descriptionProperty() {
                return description;
            }
    
            public final void setDescription(String description) {
                this.description.set(description);
            }
    
            public Step(String description, Status status) {
                setDescription(description);
                setStatus(status);
            }
        }
    
        public static void main(String[] args) {
            launch();
        }
    }
    

    enter image description here