Risky_91
Risky_91

Reputation: 81

JavaFX TableView not Updating UI

I have a Tableview<ObservableList<Item>>, which is not updating when the underlying data is updated. Through debugging, I know that the underlying ObservableList<Item>> is being properly updated. I have ensured that all of Item's properties are visible, and in the format myFieldProperty().

Here is my table creation:

pattern= new TableView<>(mainApp.getItemList());

    for (ObservableList<Item> row : pattern.getItems()) {

        for (int i= pattern.getColumns().size(); i<row.size(); i++){
            final int columnIndex = i ;

            TableColumn<ObservableList<Item>, Color> column = new TableColumn<>();

            column.setCellValueFactory( rowData -> 
                    rowData.getValue()
                    .get(columnIndex).displayColorProperty()); // the Item for this cell

            column.setCellFactory(col -> {
                ItemCell cell = new ItemCell();

                cell.setOnMouseEntered( e -> {
                    if (cell.getItem() != null) {
                        @SuppressWarnings("unchecked")
                        ObservableList<Item> stitchRow = 
                        (ObservableList<Item>) cell.getTableRow().getItem();
                        mainApp.getRLController().setItemLabel(itemRow.get(columnIndex).toString());
                    }
                });

                cell.setOnMouseExited( e -> {
                    mainApp.getRLController().setItemLabel(null);
                });

                cell.setOnMouseClicked((MouseEvent e) -> {
                    Item newItem = mainApp.getTBController().getSelectedItem();
                    if (e.getButton() == MouseButton.PRIMARY && newItem != null) {

                        ObservableList<Item> itemRow = 
                                (ObservableList<Item>) cell.getTableRow().getItem();
                        itemRow.set(columnIndex, newItem);  
                        mainApp.getRLController().setItemLabel(itemRow.get(columnIndex).toString());
                    }
                });

                return cell; 
                });

            column.setMinWidth(7);
            column.setPrefWidth(7);
            column.setMaxWidth(7);
            pattern.getColumns().add(column);
        }
    }
    pattern.setFixedCellSize(7);
    pattern.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);`

Code for my Custom Cell Factory:

public class ItemCell extends TableCell<ObservableList<Item>, Color> {

@Override 
protected void updateItem(Color color, boolean empty) {
    super.updateItem(color, empty); 

    if (empty || color == null) {
        setText(null);
        setStyle(null);
    } else {

        int r = (int) (color.getRed() * 255);
        int g = (int) (color.getGreen() * 255);
        int b = (int) (color.getBlue() * 255);

        this.setStyle("-fx-background-color: rgb(" + r + "," + g + "," + b + ");" 
             + "-fx-border-color: black; -fx-table-cell-border-color: black;");
    }
    }
}

Upvotes: 1

Views: 3392

Answers (1)

James_D
James_D

Reputation: 209684

The basic problem is that the object you are changing (the item which is an element of the list representing the row) is not the property that the cell is observing for changes (the displayColorProperty() belonging to the item). You need to arrange to change the value of a property that the cell is observing.

Three possible solutions:

If possible, just change the displayColor (and other data too) of the item displayed by the cell. I.e.

            cell.setOnMouseClicked((MouseEvent e) -> {
                if (e.getButton() == MouseButton.PRIMARY && newItem != null) {

                    ObservableList<Item> itemRow = 
                            (ObservableList<Item>) cell.getTableRow().getItem();
                    Item item = itemRow.get(columnIndex);
                    item.setDisplayColor(...);
                    item.set...(...);  
                    // ...
                    mainApp.getRLController().setItemLabel(item.toString());
                }
            });

Or, replace the entire row:

            cell.setOnMouseClicked((MouseEvent e) -> {
                Item newItem = mainApp.getTBController().getSelectedItem();
                if (e.getButton() == MouseButton.PRIMARY && newItem != null) {

                    ObservableList<Item> itemRow = 
                            (ObservableList<Item>) cell.getTableRow().getItem();
                    ObservableList<Item> newRow = FXCollections.observableArrayList(itemRow);
                    newRow.set(columnIndex, newItem);  
                    pattern.getItems().set(cell.getTableRow().getIndex(), newRow);
                    mainApp.getRLController().setItemLabel(newRow.get(columnIndex).toString());
                }
            });

Otherwise, you could make your table a TableView<ObservableList<ObjectProperty<Item>>>. This gets a little tricky but it's not too bad. This way you can just set the value of the object property to your new item.

Here's a complete example using the third technique:

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
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;

public class ColorTableExample extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<ObservableList<ObjectProperty<Item>>> table = new TableView<>();


        int NUM_ROWS = 20 ;
        int NUM_COLS = 15 ;

        ObservableList<ObservableList<ObjectProperty<Item>>> data = table.getItems() ;

        for (int y = 0 ; y < NUM_ROWS; y++) {

            ObservableList<ObjectProperty<Item>> row = FXCollections.observableArrayList();
            data.add(row);

            double saturation = (1.0 * y) / NUM_ROWS ;
            for (int x = 0 ; x < NUM_COLS; x++) {
                double hue = x * 360.0 / NUM_COLS ;

                Color color = Color.hsb(hue, saturation, 1.0);
                row.add(new SimpleObjectProperty<>(new Item(color)));
            }

        }

        for (ObservableList<ObjectProperty<Item>> row : table.getItems()) {
            for (int i = table.getColumns().size() ; i < row.size(); i++) {
                int columnIndex = i ;
                TableColumn<ObservableList<ObjectProperty<Item>>, Item> column = new TableColumn<>(Integer.toString(i+1));
                column.setCellValueFactory(rowData -> rowData.getValue().get(columnIndex));
                column.setCellFactory(c -> {
                    TableCell<ObservableList<ObjectProperty<Item>>, Item> cell = new TableCell<ObservableList<ObjectProperty<Item>>, Item>() {
                        @Override
                        public void updateItem(Item item, boolean empty) {
                            super.updateItem(item, empty);
                            if (empty) {
                                setStyle("");
                            } else {
                                Color color = item.getDisplayColor() ;
                                int r = (int) (color.getRed() * 255) ;
                                int g = (int) (color.getGreen() * 255) ;
                                int b = (int) (color.getBlue() * 255) ;
                                String style = String.format(
                                        "-fx-background-color: rgb(%d, %d, %d);"
                                        + "-fx-border-color: black ;"
                                        + "-fx-table-cell-border-color: black ;"
                                        ,r, g, b);
                                setStyle(style);
                            }
                        }
                    };

                    cell.setOnMousePressed(evt -> {
                        if (! cell.isEmpty()) {
                            ObservableList<ObjectProperty<Item>> rowData = (ObservableList<ObjectProperty<Item>>) cell.getTableRow().getItem();
                            Color currentColor = cell.getItem().getDisplayColor();
                            double newHue = ( currentColor.getHue() + 15 ) % 360 ;
                            Color newColor = Color.hsb(newHue, currentColor.getSaturation(), currentColor.getBrightness());
                            rowData.get(columnIndex).set(new Item(newColor));
                        }
                    });

                    return cell ;
                });
                table.getColumns().add(column);
            }
        }

        BorderPane root = new BorderPane(table, null, null, null, null);
        primaryStage.setScene(new Scene(root, 600, 400));
        primaryStage.show();
    }

    public static class Item {
        private final ObjectProperty<Color> displayColor = new SimpleObjectProperty<>() ;

        public Item(Color color) {
            this.displayColorProperty().set(color);
        }

        public final ObjectProperty<Color> displayColorProperty() {
            return this.displayColor;
        }

        public final javafx.scene.paint.Color getDisplayColor() {
            return this.displayColorProperty().get();
        }

        public final void setDisplayColor(final javafx.scene.paint.Color displayColor) {
            this.displayColorProperty().set(displayColor);
        }


    }

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

(At some point, it might be easier to refactor everything so that you have an actual class representing each row in the table, instead of using a list.)

There may also be a clever workaround using an extractor for the list, but I couldn't make that work.

Upvotes: 0

Related Questions