如何根据是否在 JavaFX8 TableView 中选择了 CheckBoxTableCell 来设置 TextFieldTableCell 的可编辑性?

How to set the editability of a TextFieldTableCell based on whether or not a CheckBoxTableCell is selected in a JavaFX8 TableView?

我正在尝试设置 TextFieldTableCell 的可编辑性,具体取决于 CheckBoxTableCell(位于同一行)是否被勾选。因此,例如,如果如下所示勾选第二行中的复选框,则 "B" 处的文本应该是可编辑的。如果未选中该复选框,则 "B" 不应可编辑。

我的计划是在 CheckBoxTableCellsetCellFactory 的 "selected" 侦听器中设置 TextFieldTableCell 的可编辑性。或者,我可以在 TableViewListChangeListener.

中设置它

但是,无论哪种方式,我首先必须获得与单击的 CheckBoxTableCell 在同一行的 TextFieldTableCell 对象

我该怎么做?我已经被困了几天试图弄明白。

这是 CheckBoxTableCell 的 "selected" 侦听器的代码片段,它显示了我正在尝试做什么以及我被困在哪里:

selected.addListener((ObservableValue<? extends Boolean> obs, Boolean wasSelected, Boolean isSelected) -> {
    olTestModel.get(cbCell.getIndex()).setCheckbox(isSelected);
//=>TextFieldTableCell theTextFieldInThisRow = <HOW_DO_I_GET_THIS?>
    theTextFieldInThisRow.setEditable(isSelected);
});

我已经阅读并试验过 Make individual cell editable in JavaFX tableview, Javafx, get the object referenced by a TableCell and 。虽然我认为我理解它们,但我无法使它们适应我正在尝试做的事情。

这是上面示例的 MVCE。

我正在使用 JavaFX8 (JDK1.8.0_181)、NetBeans 8.2 和 Scene Builder 8.3。

package test24;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class Test24 extends Application {

    private Parent createContent() {

        //********************************************************************************************
        //Declare the TableView and its underlying ObservableList and change listener
        TableView<TestModel> table = new TableView<>();

        ObservableList<TestModel> olTestModel = FXCollections.observableArrayList(testmodel -> new Observable[] {
                testmodel.checkboxProperty()
        });

        olTestModel.addListener((ListChangeListener.Change<? extends TestModel > c) -> {
            while (c.next()) {
                if (c.wasUpdated()) {
                    boolean checkBoxIsSelected = olTestModel.get(c.getFrom()).getCheckbox().booleanValue();
                    //PLAN A:  Set editability here
                    //==>TextFieldTableCell theTextFieldInThisRow = <HOW_DO_I_GET_THIS?>
                    //theTextFieldInThisRow.setEditable(checkBoxIsSelected);
                } 
            }
        });

        olTestModel.add(new TestModel(false, "A"));
        olTestModel.add(new TestModel(false, "B"));
        olTestModel.add(new TestModel(false, "C"));

        table.setItems(olTestModel);

        //********************************************************************************************
        //Declare the text column whose editability needs to change depending on whether or
        //not the CheckBox is ticked
        TableColumn<TestModel, String> colText = new TableColumn<>("text");
        colText.setCellValueFactory(cellData -> cellData.getValue().textProperty());        
        colText.setCellFactory(TextFieldTableCell.<TestModel>forTableColumn());        
        colText.setEditable(false);

        //********************************************************************************************
        //Declare the CheckBox column
        TableColumn<TestModel, Boolean> colCheckbox = new TableColumn<>("checkbox");

        colCheckbox.setCellValueFactory(cellData -> cellData.getValue().checkboxProperty());

        colCheckbox.setCellFactory((TableColumn<TestModel, Boolean> cb) -> {

            final CheckBoxTableCell cbCell = new CheckBoxTableCell<>();
            final BooleanProperty selected = new SimpleBooleanProperty();

            cbCell.setSelectedStateCallback(new Callback<Integer, ObservableValue<Boolean>>() {
                @Override
                public ObservableValue<Boolean> call(Integer index) {
                    return selected;
                }
            });

            selected.addListener((ObservableValue<? extends Boolean> obs, Boolean wasSelected, Boolean isSelected) -> {
                //Set the value in the data model
                olTestModel.get(cbCell.getIndex()).setCheckbox(isSelected);
                //PLAN B:  Set editability here
                //Set the editability for the text field in this row
                //==>   TextFieldTableCell theTextFieldInThisRow = <HOW_DO_I_GET_THIS?>
                //theTextFieldInThisRow.setEditable(isSelected);
            });

            return cbCell;
        });        

        //********************************************************************************************
        //Column to show what's actually in the TableView's data model for the checkbox
        TableColumn<TestModel, Boolean> colDMVal = new TableColumn<>("data model value");
        colDMVal.setCellValueFactory(cb -> cb.getValue().checkboxProperty());
        colDMVal.setEditable(false);

        table.getSelectionModel().setCellSelectionEnabled(true);
        table.setEditable(true);

        table.getColumns().add(colCheckbox);
        table.getColumns().add(colDMVal);
        table.getColumns().add(colText);

        BorderPane content = new BorderPane(table);

        return content;

    }

    public class TestModel {

        private BooleanProperty checkbox;
        private StringProperty text;

        public TestModel() {
            this(false, "");
        }

        public TestModel(
            boolean checkbox,
            String text
        ) {
            this.checkbox = new SimpleBooleanProperty(checkbox);
            this.text = new SimpleStringProperty(text);
        }

        public Boolean getCheckbox() {
            return checkbox.get();
        }

        public void setCheckbox(boolean checkbox) {
            this.checkbox.set(checkbox);
        }

        public BooleanProperty checkboxProperty() {
            return checkbox;
        }

        public String getText() {
            return text.get();
        }

        public void setText(String text) {
            this.text.set(text);
        }

        public StringProperty textProperty() {
            return text;
        }

    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent()));
        stage.setTitle("Test");
        stage.setWidth(500);
        stage.show();
    }

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

}

在采纳了用户 kleopatra 的建议后,我成功了。

最简单的解决方案是在 CheckBoxTableCell 未勾选的情况下简单地忽略编辑。这在此处 的接受答案中有所描述,也是我在下面的 MVCE 中使用的内容。

或者,TextFieldTableCell 的可编辑性可以通过将其 editableProperty() 绑定到 CheckBoxTableCell 的值来设置。按照此处接受的答案 ,代码如下所示:

@Override
public void updateItem(String item, boolean empty) {
    super.updateItem(item, empty);
    doUpdate(item, getIndex(), empty);
}

@Override
public void updateIndex(int index) {
    super.updateIndex(index);
    doUpdate(getItem(), index, isEmpty());
}

private void doUpdate(String item, int index, boolean empty) {
    if ( empty || index == getTableView().getItems().size() ) {
        setText(null);
    } else {
        BooleanProperty checkboxProperty = getTableView().getItems().get(getIndex()).checkboxProperty();
        editableProperty().bind(checkboxProperty);
    }
}

虽然解决方案不是基于获取 TextFieldTableCell 对象 (这是我认为我需要做的),但它确实做了我想要的需要(根据复选框值设置文本字段的可编辑性)。谢谢 kleopatra 为我指明了正确的方向。

这是演示解决方案的 MVCE。

package test24;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.converter.DefaultStringConverter;

public class Test24 extends Application {

    private Parent createContent() {

        //********************************************************************************************
        //Declare the TableView and its underlying ObservableList and change listener
        TableView<TestModel> table = new TableView<>();

        ObservableList<TestModel> olTestModel = FXCollections.observableArrayList(testmodel -> new Observable[] {
                testmodel.checkboxProperty()
        });

        olTestModel.addListener((ListChangeListener.Change<? extends TestModel > c) -> {
            while (c.next()) {
                if (c.wasUpdated()) {
                    //...
                } 
            }
        });

        olTestModel.add(new TestModel(false, "A"));
        olTestModel.add(new TestModel(false, "B"));
        olTestModel.add(new TestModel(false, "C"));

        table.setItems(olTestModel);

        //********************************************************************************************
        //Declare the CheckBox column
        TableColumn<TestModel, Boolean> colCheckbox = new TableColumn<>("checkbox");
        colCheckbox.setCellValueFactory(cellData -> cellData.getValue().checkboxProperty());
        colCheckbox.setCellFactory(CheckBoxTableCell.forTableColumn(colCheckbox));

        //********************************************************************************************
        //Declare the text column whose editability needs to change depending on whether or
        //not the CheckBox is ticked
        TableColumn<TestModel, String> colText = new TableColumn<>("text");
        colText.setCellValueFactory(cellData -> cellData.getValue().textProperty());
        //Don't setEditable() to false here, otherwise updateItem(), updateIndex() and startEdit() won't fire
        colText.setEditable(true);
        colText.setCellFactory(cb -> {

            DefaultStringConverter converter = new DefaultStringConverter();
            TableCell<TestModel, String> cell = new TextFieldTableCell<TestModel, String>(converter) {

                @Override
                public void startEdit() {
                    boolean checkbox = getTableView().getItems().get(getIndex()).getCheckbox();
                    if ( checkbox == true ) {
                        super.startEdit();
                    }
                }

            };

            return cell;

        });

        //********************************************************************************************
        //Column to show what's actually in the TableView's data model for the checkbox
        TableColumn<TestModel, Boolean> colDMVal = new TableColumn<>("data model value");
        colDMVal.setCellValueFactory(cb -> cb.getValue().checkboxProperty());
        colDMVal.setEditable(false);

        table.getSelectionModel().setCellSelectionEnabled(true);
        table.setEditable(true);

        table.getColumns().add(colCheckbox);
        table.getColumns().add(colDMVal);
        table.getColumns().add(colText);

        BorderPane content = new BorderPane(table);

        return content;

    }

    public class TestModel {

        private BooleanProperty checkbox;
        private StringProperty text;

        public TestModel() {
            this(false, "");
        }

        public TestModel(
            boolean checkbox,
            String text
        ) {
            this.checkbox = new SimpleBooleanProperty(checkbox);
            this.text = new SimpleStringProperty(text);
        }

        public Boolean getCheckbox() {
            return checkbox.get();
        }

        public void setCheckbox(boolean checkbox) {
            this.checkbox.set(checkbox);
        }

        public BooleanProperty checkboxProperty() {
            return checkbox;
        }

        public String getText() {
            return text.get();
        }

        public void setText(String text) {
            this.text.set(text);
        }

        public StringProperty textProperty() {
            return text;
        }

    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent()));
        stage.setTitle("Test");
        stage.setWidth(500);
        stage.show();
    }

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

}