Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
794 views
in Technique[技术] by (71.8m points)

java - 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!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.g

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...