Ondřej Doněk
Ondřej Doněk

Reputation: 529

How to set cell background color (JavaFX, tableview)

I have TableView which is used for displaying data made from CostDataRow objects. The most of cells in table view are editable and holds integer data so here is excerpt how I initialize the table and one of its editable numeric columns:

// `data` is `ObservableList<CostDataRow>` made by 
// using `FXCollections.<CostDataRow>observableArrayList(...)`.
FilteredList<CostDataRow> filteredData = new FilteredList<>(data, n -> true);
table.setEditable(true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.getSelectionModel().setCellSelectionEnabled(true);
table.setItems(filteredData);
// ...
// Below is "wireWeight" column (Integer data)
wireWeightTCol.setCellValueFactory(new PropertyValueFactory<>("wireWeight"));
wireWeightTCol.setCellFactory(TextFieldTableCell.<CostDataRow, Integer>forTableColumn(new StringConverter<Integer>() {
    @Override
    public String toString(final Integer value) {
        return value.toString() + " kg";
    }
    @Override
    public Integer fromString(final String s) {
        return Integer.parseInt(s.replace(" kg", ""));
    }
}));
wireWeightTCol.setOnEditCommit(
    new EventHandler<CellEditEvent<CostDataRow, Integer>>() {
        @Override
        public void handle(CellEditEvent<CostDataRow, Integer> t) {
            CostDataRow row = (CostDataRow) t.getTableView().getItems().get(t.getTablePosition().getRow());
            int weight = t.getNewValue();
            row.setWireWeight(t.getNewValue());
            // Calculate wire price
            row.setWirePrice((int)(weight * getWirePriceConstant()));
            // Refresh table view
            t.getTableView().refresh();
        }
    }
);

Now I need to set background color on selected cells after user clicked on one toolbar's button - here is the action's handler:

@FXML
private void handleYellowButtonAction(ActionEvent event) {
    table.getSelectionModel().getSelectedCells().forEach(pos -> {
        int row = pos.getRow();
        int col = pos.getColumn();
        // TODO Now I need to set background color of cell with 
        //      given row and column index.
        // ...
    });
}

Colors should be saved/loaded from same data model as data self.

Thanks for help!

Upvotes: 3

Views: 13082

Answers (4)

James_D
James_D

Reputation: 209694

In my experience/opinion, the trick to anything like this is properly representing the data you need in the model. In this case, it looks like you need some (or perhaps all) of the properties in the table model to carry an associated attribute. I would create a specific class for those properties that carry the attribute. For example, if you are wanting the user to be able to verify values in the table, you basically want each cell in the table to show both the value and the current "verification state" of that value. Hence you need a class that encapsulates a value and a verification state, which should be observable. So you would do something like:

public class VerifiableValue<T> {

    public enum VerificationState { UNVERIFIED, VERIFIED, ERROR }

    private final T value ;
    private final ObjectProperty<VerificationState> verificationState = new SimpleObjectProperty<>(VerificationState.UNVERIFIED) ;

    public VerifiableValue(T value) {
        this.value = value ;
    }

    public VerifiableValue(T value, VerificationState verificationState) {
        this(value);
        setVerificationState(verificationState);
    }



    public T getValue() {
        return value ;
    }

    public final ObjectProperty<VerificationState> verificationStateProperty() {
        return this.verificationState;
    }


    public final VerifiableValue.VerificationState getVerificationState() {
        return this.verificationStateProperty().get();
    }


    public final void setVerificationState(
            final VerifiableValue.VerificationState verificationState) {
        this.verificationStateProperty().set(verificationState);
    }


}

Now create table cells that observe the verification state of the current item in the table. So for example, given a TableColumn<Product, VerifiableValue<Double>> weightColumn, you might do:

weightColumn.setCellFactory(tc -> {
    TextFieldTableCell<Product, VerifiableValue<Double>> cell = new TextFieldTableCell<>();

    cell.setConverter(new StringConverter<VerifiableValue<Double>>() {

        @Override
        public String toString(VerifiableValue<Double> item) {
            return item == null ? "" : String.format("%.3f Kg", item.getValue());
        }

        @Override
        public VerifiableValue<T> fromString(String string) {
            T value = new Double(string.replace("Kg","").trim());
            VerifiableValue.VerificationState verificationState = cell.getItem() == null ? VerifiableValue.VerificationState.UNVERIFIED : cell.getItem().getVerificationState() ;
            return new VerifiableValue<>(value, verificationState);
        }

    });

    ChangeListener<VerifiableValue.VerificationState> verifiedListener = (obs, oldState, newState) -> {
        if (newState == null || newState == VerifiableValue.VerificationState.UNVERIFIED) {
            cell.setStyle("");
        } else if (newState == VerifiableValue.VerificationState.VERIFIED) {
            cell.setStyle("-fx-background-color: yellow ;");
        } else if (newState == VerifiableValue.VerificationState.ERROR) {
            cell.setStyle("-fx-background-color: red ;");
        }
    };


    cell.itemProperty().addListener((obs, oldItem, newItem) -> {
        if (oldItem != null) {
            oldItem.verificationStateProperty().removeListener(verifiedListener);
        }
        if (newItem == null) {
            cell.setStyle("");
        } else {
            if (newItem.getVerificationState() == null || newItem.getVerificationState() == VerifiableValue.VerificationState.UNVERIFIED) {
                cell.setStyle("");
            } else if (newItem.getVerificationState() == VerifiableValue.VerificationState.VERIFIED) {
                cell.setStyle("-fx-background-color: yellow ;");
            } else if (newItem.getVerificationState() == VerifiableValue.VerificationState.ERROR) {
                cell.setStyle("-fx-background-color: red ;");
            }               
            newItem.verificationStateProperty().addListener(verifiedListener);
        }
    });

    return cell ;
});

Here's a SSCCE. I moved the most important parts to the top of the code (so things are in an unusual order), and move the creation of the table cell to a method to reduce repetitive code. In real life I would probably roll my own table cell for this, so the labels display "Kg" but they don't appear in the text field, and use a text formatter on the text field to prevent invalid input. I would also move the style out of the cell implementation code, and use CSS PseudoClasses to represent the "view state" of the cell, and an external style sheet to actually map those states to colors.

import java.util.Random;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;

public class CellHighlightingTable extends Application {

    public static class VerifiableValue<T> {

        public enum VerificationState { UNVERIFIED, VERIFIED, ERROR }

        private final T value ;
        private final ObjectProperty<VerificationState> verificationState = new SimpleObjectProperty<>(VerificationState.UNVERIFIED) ;

        public VerifiableValue(T value) {
            this.value = value ;
        }

        public VerifiableValue(T value, VerificationState verificationState) {
            this(value);
            setVerificationState(verificationState);
        }



        public T getValue() {
            return value ;
        }

        public final ObjectProperty<VerificationState> verificationStateProperty() {
            return this.verificationState;
        }


        public final CellHighlightingTable.VerifiableValue.VerificationState getVerificationState() {
            return this.verificationStateProperty().get();
        }


        public final void setVerificationState(
                final CellHighlightingTable.VerifiableValue.VerificationState verificationState) {
            this.verificationStateProperty().set(verificationState);
        }


    }

    private <T> TableCell<Product, VerifiableValue<T>> createTableCell(String format, Function<String, T> supplier) {
        TextFieldTableCell<Product, VerifiableValue<T>> cell = new TextFieldTableCell<>();

        cell.setConverter(new StringConverter<VerifiableValue<T>>() {

            @Override
            public String toString(VerifiableValue<T> item) {
                return item == null ? "" : String.format(format, item.getValue());
            }

            @Override
            public VerifiableValue<T> fromString(String string) {
                T value = supplier.apply(string);
                VerifiableValue.VerificationState verificationState = cell.getItem() == null ? VerifiableValue.VerificationState.UNVERIFIED : cell.getItem().getVerificationState() ;
                return new VerifiableValue<>(value, verificationState);
            }

        });

        ChangeListener<VerifiableValue.VerificationState> verifiedListener = (obs, oldState, newState) -> {
            if (newState == null || newState == VerifiableValue.VerificationState.UNVERIFIED) {
                cell.setStyle("");
            } else if (newState == VerifiableValue.VerificationState.VERIFIED) {
                cell.setStyle("-fx-background-color: yellow ;");
            } else if (newState == VerifiableValue.VerificationState.ERROR) {
                cell.setStyle("-fx-background-color: red ;");
            }
        };


        cell.itemProperty().addListener((obs, oldItem, newItem) -> {
            if (oldItem != null) {
                oldItem.verificationStateProperty().removeListener(verifiedListener);
            }
            if (newItem == null) {
                cell.setStyle("");
            } else {
                if (newItem.getVerificationState() == null || newItem.getVerificationState() == VerifiableValue.VerificationState.UNVERIFIED) {
                    cell.setStyle("");
                } else if (newItem.getVerificationState() == VerifiableValue.VerificationState.VERIFIED) {
                    cell.setStyle("-fx-background-color: yellow ;");
                } else if (newItem.getVerificationState() == VerifiableValue.VerificationState.ERROR) {
                    cell.setStyle("-fx-background-color: red ;");
                }               
                newItem.verificationStateProperty().addListener(verifiedListener);
            }
        });

        return cell ;
    }

    @Override
    public void start(Stage primaryStage) {
        TableView<Product> table = new TableView<>();
        table.getSelectionModel().setCellSelectionEnabled(true);
        table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        table.setEditable(true);

        TableColumn<Product, String> productCol = new TableColumn<>("Product");
        productCol.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getName()));

        TableColumn<Product, VerifiableValue<Integer>> quantityColumn = new TableColumn<>("Quantity");
        quantityColumn.setCellValueFactory(cellData -> cellData.getValue().quantityProperty());

        quantityColumn.setCellFactory(tc -> createTableCell("%,d", Integer::new));

        TableColumn<Product, VerifiableValue<Double>> weightColumn = new TableColumn<>("Weight");
        weightColumn.setCellValueFactory(cellData -> cellData.getValue().weightProperty());

        weightColumn.setCellFactory(tc -> createTableCell("%.3f Kg", Double::new));

        table.getColumns().add(productCol);
        table.getColumns().add(quantityColumn);
        table.getColumns().add(weightColumn);

        Button verifySelected = new Button("Verify Selected");
        verifySelected.setOnAction(e -> {
            for (TablePosition<?, ?> pos : table.getSelectionModel().getSelectedCells()) {
                if (pos.getTableColumn() == quantityColumn) {
                    Product product = table.getItems().get(pos.getRow());
                    product.getQuantity().setVerificationState(VerifiableValue.VerificationState.VERIFIED);
                } else if (pos.getTableColumn() == weightColumn) {
                    Product product = table.getItems().get(pos.getRow());
                    product.getWeight().setVerificationState(VerifiableValue.VerificationState.VERIFIED);
                }
            }
        });
        verifySelected.disableProperty().bind(Bindings.isEmpty(table.getSelectionModel().getSelectedCells()));

        Button errorSelected = new Button("Mark all selected as error");
        errorSelected.setOnAction(e -> {
            for (TablePosition<?, ?> pos : table.getSelectionModel().getSelectedCells()) {
                if (pos.getTableColumn() == quantityColumn) {
                    Product product = table.getItems().get(pos.getRow());
                    product.getQuantity().setVerificationState(VerifiableValue.VerificationState.ERROR);
                } else if (pos.getTableColumn() == weightColumn) {
                    Product product = table.getItems().get(pos.getRow());
                    product.getWeight().setVerificationState(VerifiableValue.VerificationState.ERROR);
                }
            }
        });
        errorSelected.disableProperty().bind(Bindings.isEmpty(table.getSelectionModel().getSelectedCells()));


        Button unverifyAll = new Button("Remove all verification");
        unverifyAll.setOnAction(e -> {
            for (Product product : table.getItems()) {
                product.getQuantity().setVerificationState(VerifiableValue.VerificationState.UNVERIFIED);
                product.getWeight().setVerificationState(VerifiableValue.VerificationState.UNVERIFIED);
            }
        });

        HBox buttons = new HBox(5, verifySelected, errorSelected, unverifyAll);
        buttons.setAlignment(Pos.CENTER);
        buttons.setPadding(new Insets(5));

        // random data:
        Random rng = new Random();
        for (int i = 0 ;  i < 100; i++) {
            Product product = new Product("Item "+(i+1));
            product.setQuantity(new VerifiableValue<>(rng.nextInt(1200)));
            product.setWeight(new VerifiableValue<>(rng.nextDouble() * 1000));
            table.getItems().add(product);
        }

        BorderPane root = new BorderPane(table);
        root.setBottom(buttons);

        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }




    public static class Product {

        private ObjectProperty<VerifiableValue<Double>> weight = new SimpleObjectProperty<>();
        private ObjectProperty<VerifiableValue<Integer>> quantity = new SimpleObjectProperty<>();

        private final String name ;

        public Product(String name) {
            this.name = name ;
        }

        public String getName() {
            return name ;
        }

        public final ObjectProperty<VerifiableValue<Double>> weightProperty() {
            return this.weight;
        }

        public final CellHighlightingTable.VerifiableValue<java.lang.Double> getWeight() {
            return this.weightProperty().get();
        }

        public final void setWeight(final CellHighlightingTable.VerifiableValue<java.lang.Double> weight) {
            this.weightProperty().set(weight);
        }

        public final ObjectProperty<VerifiableValue<Integer>> quantityProperty() {
            return this.quantity;
        }

        public final CellHighlightingTable.VerifiableValue<java.lang.Integer> getQuantity() {
            return this.quantityProperty().get();
        }

        public final void setQuantity(final CellHighlightingTable.VerifiableValue<java.lang.Integer> quantity) {
            this.quantityProperty().set(quantity);
        }


    }

    public static void main(String[] args) {
        launch(args);
    }
}

Upvotes: 6

n247s
n247s

Reputation: 1912

There is no way to retrieve a cell instance from a TableView. A solution to the problem though is using a custom TableCell class, and program it to change based on the conditions you specify.

Note howeever that a Cell instance may be used for multiple Items (e.g. they are reused to enhance performance).

When extending the TableCell class, the most important method to override is the updateItem() method. The following is an example of how to change style based on a condition.

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

        if (item == null || empty) {
            setText(null);
            setStyle(""); // set cell style
        } else {
            if (checkCondition) {
                setTextFill(Color.CHOCOLATE);
                setStyle("-fx-background-color: red;");// set your css style here if condition is true
            } else {
                setTextFill(Color.BLACK);
                setStyle(""); // reser style if condition is false.
            }
        }
    }

Upvotes: 1

user7291698
user7291698

Reputation: 1990

If you have a css, you can use this code:

.table-row-cell:selected {
   -fx-background-color: yellow;
}

Upvotes: 1

Lesleyvdp
Lesleyvdp

Reputation: 323

I browsed for a bit and found:

setStyle("-fx-background-color: x");

Is this what you are trying to do?

Upvotes: 0

Related Questions