Update row color in TableView using Observable

Urle :

I would like to change row color to green when A and B column values are equal and to red if they differ. I completely don't know how to adapt examples found on SO. I add listener? Use Observable? I added whole code I prepared for further changes.

sample.fxml:

GridPane fx:controller="sample.Controller"
      xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">

    <TableView fx:id="tableView" GridPane.columnIndex="0" GridPane.rowIndex="0" editable="true">
        <columns>
            <TableColumn fx:id="colA" text="A">
                <cellValueFactory>
                    <PropertyValueFactory property="A"/>
                </cellValueFactory>
            </TableColumn>

            <TableColumn fx:id="colB" text="B">
                <cellValueFactory>
                    <PropertyValueFactory property="B"/>
                </cellValueFactory>
            </TableColumn>
        </columns>
        <items>
            <FXCollections fx:factory="observableArrayList"/>
        </items>
    </TableView>
</GridPane>

RowTest.java:

public class RowTest {

    private SimpleIntegerProperty a;
    private SimpleIntegerProperty b;
    private SimpleBooleanProperty validRow;

    public RowTest(int a, int b) {
        this.a = new SimpleIntegerProperty(a);
        this.b = new SimpleIntegerProperty(b);

        this.validRow = new SimpleBooleanProperty();

        this.validRow.bind(Bindings.createBooleanBinding(() -> this.a.get() == this.b.get()));
    }

    public int getA() {
        return a.get();
    }

    public SimpleIntegerProperty aProperty() {
        return a;
    }

    public void setA(int a) {
        this.a.set(a);
    }

    public int getB() {
        return b.get();
    }

    public SimpleIntegerProperty bProperty() {
        return b;
    }

    public void setB(int b) {
        this.b.set(b);
    }

    public boolean isValidRow() {
        return validRow.get();
    }

    public SimpleBooleanProperty validRowProperty() {
        return validRow;
    }

    public void setValidRow(boolean validRow) {
        this.validRow.set(validRow);
    }
}

Controller.java:

public class Controller implements Initializable {

    @FXML
    private TableView<RowTest> tableView;
    @FXML
    private TableColumn<RowTest, Integer> colB;
    @FXML
    private ObservableList<RowTest> data;


    @Override
    public void initialize(URL location, ResourceBundle resources) {

        data = FXCollections.observableArrayList();

        populateData();
        setColumnsEditable();
    }

    private void setColumnsEditable() {
        tableView.setRowFactory(tv -> new TableRow<RowTest>() {
            @Override
            public void updateItem(RowTest item, boolean empty) {
                super.updateItem(item, empty);
                if (item == null) {
                    setStyle("");
                } else if (item.isValidRow()) {
                    setStyle("-fx-background-color: LightGreen; -fx-text-fill: Black;");
                } else if (!item.isValidRow()) {
                    setStyle("-fx-background-color: Red; -fx-text-fill: Black;");
                } else {
                    setStyle("");
                }
            }
        });

        colB.setCellFactory(TextFieldTableCellAutoCmt.forTableColumn(new IntToStringConverter<Integer>()));
        colB.setOnEditCommit(
                (TableColumn.CellEditEvent<RowTest, Integer> t) -> {
                    ((RowTest) t.getTableView().getItems().get(
                            t.getTablePosition().getRow())
                    ).setB(t.getNewValue());
                });

        data.addListener(new ListChangeListener<RowTest>() {
            @Override
            public void onChanged(Change<? extends RowTest> c) {
                while (c.next()) {
                    if (c.wasUpdated()) {

                   }
                }
            }
        });
    }

    private void populateData() {
        data.add(new RowTest(1, 1));
        data.add(new RowTest(1, 0));

        tableView.setItems(data);
    }
}

IntToStringConverter.java:

public class IntToStringConverter<Integer> extends StringConverter<java.lang.Integer> {

    @Override
    public String toString(java.lang.Integer object) {
        return object.toString();
    }

    @Override
    public java.lang.Integer fromString(String string) {
        return java.lang.Integer.parseInt(string);
    }
}

Main.java:

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
        Parent root = loader.load();
        primaryStage.setTitle("Hello World");

        Scene scene = new Scene(root, 600, 275);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

TextFieldTableCellAutoCmt.java:

public class TextFieldTableCellAutoCmt<S, T> extends TextFieldTableCell<S, T> {

    protected TextField textField;
    protected boolean isEdit;

    public TextFieldTableCellAutoCmt() {
        this(null);
    }

    public TextFieldTableCellAutoCmt(final StringConverter<T> conv) {
        super(conv);
    }

    public static <S> Callback<TableColumn<S, String>, TableCell<S, String>> forTableColumn() {
        return forTableColumn(new DefaultStringConverter());
    }

    public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>> forTableColumn(final StringConverter<T> conv) {
        return list -> new TextFieldTableCellAutoCmt<S, T>(conv);
    }

    @Override
    public void startEdit() {
        super.startEdit();
        isEdit = true;
        if (updateTextField()) {
            textField.focusedProperty().addListener(this::onFocusChange);
            textField.setOnKeyPressed(this::onKeyPress);
        }
    }

    /**
     * @return whether {@link #textField} has been changed
     */
    protected boolean updateTextField() {
        final Node g = getGraphic();
        final boolean isUpd = g != null && textField != g;
        if (isUpd) {
            textField = g instanceof TextField ? (TextField) g : null;
        }
        return isUpd;
    }

    @Override
    public void commitEdit(final T valNew) {
        if (isEditing()) {
            super.commitEdit(valNew);
        } else {
            final TableView<S> tbl = getTableView();
            if (tbl != null) {
                final TablePosition<S, T> pos = new TablePosition<>(tbl, getTableRow().getIndex(), getTableColumn()); // instead of tbl.getEditingCell()
                final TableColumn.CellEditEvent<S, T> ev = new TableColumn.CellEditEvent<>(tbl, pos, TableColumn.editCommitEvent(), valNew);
                Event.fireEvent(getTableColumn(), ev);
            }
            updateItem(valNew, false);
            if (tbl != null) {
                tbl.edit(-1, null);
            }
            // TODO ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(tbl);
        }
    }

    public void onFocusChange(final ObservableValue<? extends Boolean> obs, final boolean v0, final boolean v1) {
        if (isEdit && !v1) {
            commitEdit(getConverter().fromString(textField.getText()));
        }
    }

    protected void onKeyPress(final KeyEvent e) {
        switch (e.getCode()) {
            case ESCAPE:
                isEdit = false;
                cancelEdit(); // see CellUtils#createTextField(...)
                e.consume();
                break;
            case TAB:
                if (e.isShiftDown()) {
                    getTableView().getSelectionModel().selectPrevious();
                } else {
                    getTableView().getSelectionModel().selectNext();
                }
                e.consume();
                break;
            case UP:
                getTableView().getSelectionModel().selectAboveCell();
                e.consume();
                break;
            case DOWN:
                getTableView().getSelectionModel().selectBelowCell();
                e.consume();
                break;
            default:
                break;
        }
    }
}

How from this place:

data.addListener(new ListChangeListener<RowTest>() {
    @Override
    public void onChanged(Change<? extends RowTest> c) {
        while (c.next()) {
            if (c.wasUpdated()) {

            }
        }
    }
});

get access to row? So, based on ObservableList item I want get its Row container. I don't know how to do it.

Edit:

This bind was wrong:

 this.validRow.bind(Bindings.createBooleanBinding(() -> this.a.get() == this.b.get()));

I changed to this:

this.validRow.bind(Bindings.createBooleanBinding(() -> this.a.get() == this.b.get(), this.aProperty(), this.bProperty()));
Urle :

This solves my problem, but I'm not sure if it's a proper solution.

data = FXCollections.observableArrayList(e -> new Observable[]{e.validRowProperty()});

Found and modified this code here:

Changing table row color using a property that would not be visible in any column

tableView.setRowFactory(tv -> new TableRow<RowTest>() {
    @Override
    protected void updateItem(final RowTest item, final boolean empty) {
        super.updateItem(item, empty);

        if (!empty && item != null) {
            this.styleProperty().bind(Bindings.createStringBinding(() -> {
                if (item.isValidRow()) {
                    return "-fx-background-color: green;";
                } else {
                    return "-fx-background-color: red;";
                }
            }, item.validRowProperty()));
        } else {
            setText(null);
            setGraphic(null);

            this.styleProperty().unbind();

            setStyle(" ");
        }
    }
});

and I changed bind in RowTest:

this.validRow.bind(Bindings.createBooleanBinding(() -> 
    this.a.get() == this.b.get(), this.aProperty(), this.bProperty()));

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=157800&siteId=1