2

I need to change the style of arbitrary cells in a TableView which has an variable number of columns. The code below shows the basic problem.

The ExampleRow class is proxy for the real data which comes from a spreadsheet, it's other function is to hold the highlighting information. Since I can't know how many columns there will be I just hold a list of columns that should be highlighted (column re-arrangement won't be supported). The ExampleTableCell class just sets the text for the cell and applies the highlight if needed.

If I set a highlight before the table gets drawn [cell (2,2)] then the cell correctly gets displayed with red text when the application starts. The problem is clicking the button sets cell (1,1) to be highlighted but the table doesn't change. If I resize the application window to nothing then open it back up again the highlighting of cell (1,1) is correctly drawn - presumably because this process forces a full redraw.

What I would like to know is how can I trigger the table to redraw newly highlighted cells (or all visible cells) so the styling is correct?

TIA

package example;

import java.util.HashSet;
import java.util.Set;
import javafx.application.Application;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;

public class CellHighlightExample extends Application {

    private final int columnCount = 4;
    private final int rowCount = 5;
    private TableView<ExampleRow> table = new TableView<>();

    @Override
    public void start(Stage stage) {
        BorderPane root = new BorderPane();
        Scene scene = new Scene(root);

        Callback<TableColumn.CellDataFeatures<ExampleRow, String>, ObservableValue<String>> cellValueFactory = new Callback<TableColumn.CellDataFeatures<ExampleRow, String>, ObservableValue<String>>() {
            @Override
            public ObservableValue<String> call(TableColumn.CellDataFeatures<ExampleRow, String> p) {
                int row = p.getValue().getRow();
                int col = p.getTableView().getColumns().indexOf(p.getTableColumn());
                return new SimpleObjectProperty<>("(" + row + ", " + col + ")");
            }
        };

        Callback<TableColumn<ExampleRow, String>, TableCell<ExampleRow, String>> cellFactory = new Callback<TableColumn<ExampleRow, String>, TableCell<ExampleRow, String>>() {
            @Override
            public TableCell<ExampleRow, String> call(TableColumn<ExampleRow, String> p) {
                return new ExampleTableCell<>();
            }
        };

        for (int i = 0, n = columnCount; i < n; i++) {
            TableColumn<ExampleRow, String> column = new TableColumn<>();
            column.setCellValueFactory(cellValueFactory);
            column.setCellFactory(cellFactory);
            table.getColumns().add(column);
        }

        ObservableList<ExampleRow> rows = FXCollections.observableArrayList();
        for (int i = 0, n = rowCount; i < n; i++) {
            ExampleRow row = new ExampleRow(i);
            //Force a cell to be highlighted to show that highlighting works.
            if (i == 2) { row.addHighlightedColumn(2); }
            rows.add(row);
        }
        table.setItems(rows);

        Button b = new Button("Click to Highlight");
        b.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent t) {
                ExampleRow row = table.getItems().get(1);
                row.addHighlightedColumn(1);
                //How to trigger a redraw of the table or cell to reflect the new highlighting?
            }
        });

        root.setTop(b);
        root.setCenter(table);
        stage.setScene(scene);
        stage.show();
    }

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

    private class ExampleTableCell<S extends ExampleRow, T extends String> extends TableCell<S, T> {

        @Override
        public void updateItem(T item, boolean empty) {
            super.updateItem(item, empty);

            if (item == null) {
                setText(null);
                setGraphic(null);
            } else {
                setText(item);
                int colIndex = getTableView().getColumns().indexOf(getTableColumn());
                ExampleRow row = getTableView().getItems().get(getIndex());
                if (row.isHighlighted(colIndex)) {
                    setTextFill(Color.RED);
                }
            }
        }
    }

    private class ExampleRow {

        private SimpleIntegerProperty row;
        private Set<Integer> highlightedColumns = new HashSet<>();

        public ExampleRow(int row) {
            this.row = new SimpleIntegerProperty(row);
        }

        public int getRow() { return row.get(); }
        public void setRow(int row) { this.row.set(row); }
        public SimpleIntegerProperty rowProperty() { return row; }

        public boolean isHighlighted(int col) {
            if (highlightedColumns.contains(col)) {
                return true;
            }
            return false;
        }

        public void addHighlightedColumn(int col) {
            highlightedColumns.add(col);
        }
    }
}
wobblycogs
  • 4,083
  • 7
  • 37
  • 48

1 Answers1

7

There are lots of discussions about this problem, namely refreshing tableview after altering the item(s).
See
JavaFX 2.1 TableView refresh items
Issues
http://javafx-jira.kenai.com/browse/RT-21822
http://javafx-jira.kenai.com/browse/RT-22463
http://javafx-jira.kenai.com/browse/RT-22599

The solution is to trigger internal tableview update method. Some suggests to remove tableview items and add them again vs.. but the simplest workaround for your case seems to:

b.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent t) {
        ExampleRow row = table.getItems().get(1);
        row.addHighlightedColumn(1);
        //How to trigger a redraw of the table or cell to reflect the new highlighting?
        // Workaround
        table.getColumns().get(0).setVisible(false);
        table.getColumns().get(0).setVisible(true);
    }
});

which found in issue comments linked above. Is this a really workaround or illusion of it? You need to dig deeper yourself.

Community
  • 1
  • 1
Uluk Biy
  • 48,655
  • 13
  • 146
  • 153