1

Am looking to disable a TableColumn<CustomObject, String> tableColumn based on a field value in the CustomObject only when the TableColumn<CustomObject, Boolean> tableColumnTwo checkbox is checked. I can disable the textbox inside public void updateItem(String s, boolean empty) however not sure how to check the state of checkbox inside updateItem Below is the relevant code snippet, would highly appreciate if anyone can shed light on this

@FXML 
private TableColumn<CustomObject, Boolean> tableColumnTwo;
@FXML 
private TableColumn<CustomObject, String> tableColumn;

tableColumn.setCellFactory(
                     new Callback<TableColumn<CustomObject, String>, TableCell<CustomObject, String>>() {

                         @Override
                         public TableCell<CustomObject, String> call(TableColumn<CustomObject, String> paramTableColumn) {
                             return new TextFieldTableCell<CustomObject, String>(new DefaultStringConverter()) {
                                 @Override
                                 public void updateItem(String s, boolean empty) {
                                     super.updateItem(s, empty);
                                     TableRow<CustomObject> currentRow = getTableRow();
                                     if(currentRow.getItem() != null && !empty) {
                                         if (currentRow.getItem().getPetrified() == false) { // Need to check if checkbox is checked or not
                                             setDisable(true);
                                             setEditable(false);
                                             this.setStyle("-fx-background-color: red");
                                         } else {
                                             setDisable(false);
                                             setEditable(true);
                                                                                             setStyle("");
                                         }
                                     }
                                 }
                             };
                         }

                     });
John C
  • 1,647
  • 3
  • 23
  • 40
  • https://stackoverflow.com/help/how-to-ask or in other words: please provide a runnable example that demonstrates what you are after and how you fail to reach your goal :) – kleopatra Sep 19 '17 at 07:11

2 Answers2

2

You can add a listener on the checkbox, which when checked will cause the table refresh.

data = FXCollections.observableArrayList(new Callback<CustomObject, Observable[]>() {

            @Override
            public Observable[] call(CustomObject param) {
                return new Observable[]{param.petrifiedProperty()};
            }
    });


data.addListener(new ListChangeListener<CustomObject>() {

        @Override
        public void onChanged(ListChangeListener.Change<? extends CustomObject> c) {
            while (c.next()) {
                if (c.wasUpdated()) {
                    tableView.setItems(null); 
                    tableView.layout(); 
                    tableView.setItems(FXCollections.observableList(data)); 
                }
            }
        }
    });

Your cellFactory would remain the same and would get called when a checkbox is checked/unchecked.

Piyush Mattoo
  • 14,524
  • 6
  • 49
  • 58
1

Usually, we expect cells being updated whenever they are notified about a change in the underlying data. To make certain that a notification is fired by the data on changing a property of an item, we need a list with an extractor on the properties that we are interested in, something like:

 ObservableList<CustomObject> data = FXCollections.observableArrayList(
      c ->  new Observable[] {c.petrifiedProperty()}
 );

With that in place the list fires a list change of type update whenever the pretified property changes.

Unfortunately, that's not enough due to a bug in fx: cells are not updated when receiving a listChange of type update from the underlying items. A dirty way around (read: don't use once the bug is fixed, it's using emergency api!) is to install a listener on the items and call table.refresh() when receiving an update.

An example:

import java.util.logging.Logger;

//import de.swingempire.fx.util.FXUtils;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.converter.DefaultStringConverter;

/**
 * CheckBoxTableCell: update editable state of one column based of  
 * the boolean in another column
 * https://stackoverflow.com/q/46290417/203657
 * 
 * Bug in skins: cell not updated on listChange.wasUpdated
 * 
 * reported as
 * https://bugs.openjdk.java.net/browse/JDK-8187665
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class TableViewUpdateBug extends Application {


    /**
     * TableCell that updates state based on another value in the row.
     */
    public static class DisableTextFieldTableCel extends TextFieldTableCell {

        public DisableTextFieldTableCel() {
            super(new DefaultStringConverter());
        }

        /**
         * Just to see whether or not this is called on update notification
         * from the items (it's not)
         */
        @Override
        public void updateIndex(int index) {
            super.updateIndex(index);
//            LOG.info("called? " + index);
        }

        /**
         * Implemented to change background based on 
         * visible property of row item.
         */
        @Override
        public void updateItem(Object item, boolean empty) {
            super.updateItem(item, empty);
            TableRow<TableColumn> currentRow = getTableRow();
            boolean editable = false;
            if (!empty && currentRow != null) {
                TableColumn column = currentRow.getItem();
                if (column != null) {
                    editable = column.isVisible();
                }
            }
            if (!empty) {
                setDisable(!editable);
                setEditable(editable);
                if (editable) {
                    this.setStyle("-fx-background-color: red");

                } else {
                    this.setStyle("-fx-background-color: green");
                }
            } else {
                setStyle("-fx-background-color: null");
            }
        }

    }

    @Override
    public void start(Stage primaryStage) {
        // data: list of tableColumns with extractor on visible property
        ObservableList<TableColumn> data = FXCollections.observableArrayList(
                c ->  new Observable[] {c.visibleProperty()});

        data.addAll(new TableColumn("first"), new TableColumn("second"));

        TableView<TableColumn> table = new TableView<>(data);
        table.setEditable(true);

        // hack-around: call refresh
        data.addListener((ListChangeListener) c -> {
            boolean wasUpdated = false;
            boolean otherChange = false;
            while(c.next()) {
                if (c.wasUpdated()) {
                    wasUpdated = true;
                } else {
                    otherChange = true;
                }

            }
            if (wasUpdated && !otherChange) {
                table.refresh();
            }
            //FXUtils.prettyPrint(c);
        });
        TableColumn<TableColumn, String> text = new TableColumn<>("Text");
        text.setCellFactory(c -> new DisableTextFieldTableCel()); 
        text.setCellValueFactory(new PropertyValueFactory<>("text"));

        TableColumn<TableColumn, Boolean> visible = new TableColumn<>("Visible");
        visible.setCellValueFactory(new PropertyValueFactory<>("visible"));
        visible.setCellFactory(CheckBoxTableCell.forTableColumn(visible));

        table.getColumns().addAll(text, visible);

        BorderPane root = new BorderPane(table);
        Scene scene = new Scene(root, 300, 150);

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

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

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(TableViewUpdateBug.class.getName());
}
kleopatra
  • 49,346
  • 26
  • 88
  • 189