【问题标题】:Bind CheckBoxTableCell to BooleanBinding将 CheckBoxTableCell 绑定到 BooleanBinding
【发布时间】:2020-06-16 19:00:39
【问题描述】:

我想将 TableViewCell 中的 CheckBox 绑定到 BooleanBinding。下面的示例由一个 TableView 组成,其中有一列 nameisEffectiveRequired。列中的复选框绑定到表达式: isRequired.or(name.isEqualTo("X"))

因此,当行中的项目是必需的或名称是 X 时,该项目是“有效必需的”,那么表达式应该为真。 不幸的是,CheckBox 没有反映更改。为了调试,我添加了一个文本字段,显示namePropertyrequiredProperty 和计算出的effectiveRequiredProperty

有趣的是,当只返回 isRequiredProperty 而不是绑定复选框时。

public ObservableBooleanValue effectiveRequiredProperty() {
     // Bindings with this work:
     // return isRequired;
     // with this not
     return isRequired.or(name.isEqualTo(SPECIAL_STRING));
}

那么就 CheckBox 而言,Property 和 ObservableValue 有什么区别?

public class TableCellCBBinding extends Application {

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        init(primaryStage);
        primaryStage.show();
    }

    private void init(Stage primaryStage) {
        primaryStage.setScene(new Scene(buildContent()));
    }

    private Parent buildContent() {
        TableView<ViewModel> tableView = new TableView<>();
        tableView.setItems(sampleEntries());
        tableView.setEditable(true);
        tableView.getColumns().add(buildRequiredColumn());
        tableView.getColumns().add(buildNameColumn());

        // Add a Textfield to show the values for the first item
        // As soon as the name is set to "X", the effectiveRequiredProperty should evaluate to true and the CheckBox should reflect this but it does not
        TextField text = new TextField();
        ViewModel firstItem = tableView.getItems().get(0);
        text.textProperty()
            .bind(Bindings.format("%s | %s | %s", firstItem.nameProperty(), firstItem.isRequiredProperty(), firstItem.effectiveRequiredProperty()));

        return new HBox(text, tableView);
    }

    private TableColumn<ViewModel, String> buildNameColumn() {
        TableColumn<ViewModel, String> nameColumn = new TableColumn<>("Name");
        nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
        nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
        nameColumn.setEditable(true);
        return nameColumn;
    }

    private TableColumn<ViewModel, Boolean> buildRequiredColumn() {
        TableColumn<ViewModel, Boolean> requiredColumn = new TableColumn<>("isEffectiveRequired");
        requiredColumn.setMinWidth(50);
        // This is should bind my BindingExpression from to ViewModel to the CheckBox
        requiredColumn.setCellValueFactory( p -> p.getValue().effectiveRequiredProperty());
        requiredColumn.setCellFactory( CheckBoxTableCell.forTableColumn(requiredColumn));
        return requiredColumn;
    }

    private ObservableList<ViewModel> sampleEntries() {
        return FXCollections.observableArrayList(
                new ViewModel(false, "A"),
                new ViewModel(true,  "B"),
                new ViewModel(false, "C"),
                new ViewModel(true,  "D"),
                new ViewModel(false, "E"));
    }

    public static class ViewModel {
        public static final String SPECIAL_STRING = "X";

        private final StringProperty name;
        private final BooleanProperty isRequired;

        public ViewModel(boolean isRequired, String name) {
            this.name = new SimpleStringProperty(this, "name", name);
            this.isRequired = new SimpleBooleanProperty(this, "isRequired", isRequired);
            this.name.addListener((observable, oldValue, newValue) -> System.out.println(newValue));
        }

        public StringProperty nameProperty() {return name;}
        public final String getName(){return name.get();}
        public final void setName(String value){
            name.set(value);}

        public boolean isRequired() {
            return isRequired.get();
        }
        public BooleanProperty isRequiredProperty() {
            return isRequired;
        }
        public void setRequired(final boolean required) {
            this.isRequired.set(required);
        }

        public ObservableBooleanValue effectiveRequiredProperty() {
            // Bindings with this work:
            // return isRequired;
            // with this not
            return isRequired.or(name.isEqualTo(SPECIAL_STRING));
        }
    }
}

在名称中输入 X 时,应选中行中的复选框。

在名称中输入 X 时,不会选中该行中的复选框。它从来没有被检查过,就像它根本没有绑定一样。

【问题讨论】:

  • 与其文档相反,checkBoxTableCell 需要布尔值作为属性才能显示状态

标签: javafx javafx-bindings


【解决方案1】:

CheckBoxXXCells 在绑定其选定状态时不符合其文档要求,f.i. (这里引用只是为了签名,即使没有明确设置):

公共最终回调&lt;Integer,​ObservableValue&lt;Boolean&gt;&gt;getSelectedStateCallback()

返回屏幕上显示的 CheckBox 所绑定的回调。

清楚地谈到了 ObservableValue,所以我们希望它至少 显示选择状态。

实际上,如果它不是一个属性,它的 updateItem 中的相关部分,则该实现什么也不做:

StringConverter<T> c = getConverter();

if (showLabel) {
    setText(c.toString(item));
}
setGraphic(checkBox);

if (booleanProperty instanceof BooleanProperty) {
    checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
}
ObservableValue<?> obsValue = getSelectedProperty();
if (obsValue instanceof BooleanProperty) {
    booleanProperty = (ObservableValue<Boolean>) obsValue;
    checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
}

checkBox.disableProperty().bind(Bindings.not(
        getTableView().editableProperty().and(
        getTableColumn().editableProperty()).and(
        editableProperty())
    ));

要解决此问题,请使用自定义单元格来更新其 updateItem 中的选定状态。我们需要禁用检查的触发以真正保持视觉与支持状态同步:

requiredColumn.setCellFactory(cc -> {
    TableCell<ViewModel, Boolean> cell = new TableCell<>() {
        CheckBox check = new CheckBox() {

            @Override
            public void fire() {
                // do nothing - visualizing read-only property
                // could do better, like actually changing the table's
                // selection
            }

        };
        {
            getStyleClass().add("check-box-table-cell");
            check.setOnAction(e -> {
                e.consume();
            });
        }

        @Override
        protected void updateItem(Boolean item, boolean empty) {
            super.updateItem(item, empty);
            if (empty || item == null) {
                setText(null);
                setGraphic(null);
            } else {
                check.setSelected(item);
                setGraphic(check);
            }
        }

    };
    return cell;
});

【讨论】:

  • 谢谢!我的想法是将我的 Binding 包装在 BooleanProperty 中,而不是挂钩到 CellFactory 并在这方面做一个解决方法,这样一切都可以从那里开始工作。我只需要扩展 BooleanProperty 并从/委托到 BooleanBinding。但我对 javafx 内部不是很熟悉 - 所以到目前为止没有成功。
  • 好吧,我认为您在错误的一端进行了调整:错误的不是您的数据/绑定,而是 CheckBoxTableCell - 您不会通过忽略该错误而获得任何收益(更糟糕的是:很可能会遇到其他问题; )使用自定义单元格并快乐。当然是你的决定。
猜你喜欢
  • 2016-04-02
  • 2021-08-02
  • 1970-01-01
  • 2019-03-28
  • 1970-01-01
  • 1970-01-01
  • 2018-12-08
  • 2011-11-01
  • 1970-01-01
相关资源
最近更新 更多