【问题标题】:Updating ObservableList after editing cell in TableView在 TableView 中编辑单元格后更新 ObservableList
【发布时间】:2019-07-12 18:35:03
【问题描述】:

我正在尝试使用 Oracle 糟糕的教程创建可编辑单元格。我发现他们的EditCell 类仅在我单击我当前编辑的同一行或任何行之外时更新。如果我单击另一行,则会取消编辑。这是本教程的链接,在它的最后你可以找到EditCell 类,但这不是这个问题的重点:

https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm

此类创建TextField 用于编辑目的。单击另一行会启动cancel() 方法。还有这个代码行:

setText((String( getItem());

阻止编辑。我将其替换为:

setText((String) textField.getText());

现在编辑工作。但在编辑此单元格后,旧值再次加载到TextField。我猜ObservableList 在第一次编辑后没有更新。

这是 FXML 代码:

<GridPane fx:controller="sample.Controller"
      xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">

    <TableView GridPane.columnIndex="0" GridPane.rowIndex="1" items="${controller.data}" editable="true">
        <columns>
            <TableColumn fx:id="colName" text="name">
                <cellValueFactory>
                    <PropertyValueFactory property="Name"/>
                </cellValueFactory>
            </TableColumn>

            <TableColumn fx:id="colSurname" text="surname">
                <cellValueFactory>
                    <PropertyValueFactory property="Surname"/>
                </cellValueFactory>
            </TableColumn>
        </columns>
    </TableView>
</GridPane>

在控制器中我声明ObservableList:

public class Controller {

    @FXML
    private TableColumn<Person, String> colName;
    @FXML
    private TableColumn<Person, String> colSurname;

    @FXML
    private ObservableList<Person> data;

    public Controller(){
        data = FXCollections.observableArrayList(
                new Person("John", "S."),
                new Person("Jane", "S.")
        );
    }

    public TableColumn<Person, String> getColName() {
        return colName;
    }

    public void setColName(TableColumn<Person, String> colName) {
        this.colName = colName;
    }

    public TableColumn<Person, String> getColSurname() {
        return colSurname;
    }

    public void setColSurname(TableColumn<Person, String> colSurname) {
        this.colSurname = colSurname;
    }

    public ObservableList<Person> getData() {
        return data;
    }

    public void setData(ObservableList<Person> data) {
        this.data = data;
    }
}

Person.java 代码:

public class Person {

    private final SimpleStringProperty name;
    private final SimpleStringProperty surname;

    public Person(String name, String surname){
        this.name = new SimpleStringProperty(name);
        this.surname = new SimpleStringProperty(surname);
    }

    public String getName() {
        return name.get();
    }

    public SimpleStringProperty nameProperty() {
        return name;
    }

    public void setName(String name) {
        this.name.set(name);
    }

    public String getSurname() {
        return surname.get();
    }

    public SimpleStringProperty surnameProperty() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname.set(surname);
    }
}

Main我声明控制器和可编辑列:

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));

        Parent root = (Parent) loader.load();
        primaryStage.setScene(new Scene(root, 300, 275));

        Controller controller = loader.getController();
        TableColumn<Person, String> colName = controller.getColName();

        Callback<TableColumn<Person, String>, TableCell<Person, String>> cellFactory =
            (TableColumn<Person, String> p) -> new sample.EditCell();

        colName.setCellFactory(cellFactory);
        colName.setOnEditCommit(
                (TableColumn.CellEditEvent<Person, String> t) -> {
                    ((Person) t.getTableView().getItems().get(
                            t.getTablePosition().getRow())
                    ).setName(t.getNewValue());
                });


        primaryStage.show();
    }

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

我需要用ObservableList 绑定单元格吗?还是刷新一下?如何更新data 使 TextField 始终填充实际值?

这里是整个EditCell 类:

class EditCell extends TableCell<Person, String> {

    private TextField textField;

    public EditCell() {
    }

    @Override
    public void startEdit() {
        if (!isEmpty()) {
            super.startEdit();
            createTextField();
            setText(null);
            setGraphic(textField);
            textField.selectAll();
        }
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();

        setText((String) getItem());

        //setText((String) textField.getText());
        //This line updates cell, but textField keeps old value after next edit.

        setGraphic(null);
    }

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

        if (empty) {
            setText(null);
            setGraphic(null);
        } else {
            if (isEditing()) {
                if (textField != null) {
                    textField.setText(getString());
                }

                setText(null);
                setGraphic(textField);
            } else {
                setText(getString());
                setGraphic(null);
            }
        }
    }

    private void createTextField() {
        textField = new TextField(getString());
        textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
        textField.focusedProperty().addListener(
                (ObservableValue<? extends Boolean> arg0,
                 Boolean arg1, Boolean arg2) -> {
                    if (!arg2) {
                        commitEdit(textField.getText());
                    }
                });
    }

    private String getString() {
        return getItem() == null ? "" : getItem().toString();
    }
}

【问题讨论】:

  • 看看TableView doesn't commit values on focus lost event 是否有帮助。您可能还想考虑使用(或扩展)TextFieldTableCell,而不是自己动手(或从头开始)。
  • 您提到使用 TextFIeldTableCell,但是在我使用这行代码替换 CellFactory 之后:colName.setCellFactory(TextFieldTableCell.&lt;Person&gt;forTableColumn()); 编辑不会发生。这一步也在我链接的 Oracle 教程中。此外,还有“掌握 FXML”部分,不幸的是,技术作者不想描述仅使用 FXML 进行编辑。

标签: java javafx fxml


【解决方案1】:

编辑

编辑时,onEditCommit 处理程序会在提交编辑时收到通知(不出所料)。此处理程序负责将新值写入模型(在您的情况下为Person)。发生这种情况时,TableView 将自动更新以显示新值。

取消编辑时将Cell 的文本设置为TextField 的值的解决方案将不起作用。最终,一旦以某种方式触发更新,Cell 将刷新以显示模型提供的真实数据(由cellValueFactory 获得)。除此之外,您实际上并没有更新模型,因此所谓的编辑只是视觉上的事情。


关于教程

您链接到的tutorial 有问题。其中最重要的是它假设当TextField 失去焦点时,您可以成功提交新值。正如您所经历的那样,情况并非如此。通过查看以下问题,您可以看到许多其他人遇到过这个问题:TableView doesn't commit values on focus lost event。该问题的答案提供了许多hack解决问题的方法。有些人还指出错误报告,表明 no-commit-on-lost-focus 行为实际上是无意的;但是,这些错误在 JavaFX 11.0.2 中尚未修复。

这意味着:

textField.focusedProperty().addListener(
        (ObservableValue<? extends Boolean> arg0,
         Boolean arg1, Boolean arg2) -> {
            if (!arg2) {
                commitEdit(textField.getText());
            }
        });

永远不会提交编辑。您(但实际上是教程)没有提供提交新值的工作方法,因为在调用 if (!arg2) { commitEdit(...); } 时取消了编辑。由于取消了编辑,因此没有触发提交编辑事件,并且您的 TableColumn 无法将新值写入模型项。尽管这不能解决 no-commit-on-lost-focus 问题,但您可以做的是将onAction 处理程序添加到提交编辑的TextField。您可能还想提供一种通过键盘取消编辑的方法。这看起来像:

textField.setOnAction(event -> {
    commitEdit(textField.getText());
    event.consume();
}
textField.setOnKeyPressed(event -> {
    if (event.getCode() == KeyCode.ESCAPE) {
        cancelEdit();
        event.consume();
    }
}

这将在按下 Enter 键时提交编辑,并在按下 Esc 键时取消编辑。

请注意,TextFieldTableCell 已经提供了开箱即用的这种行为,无需推出您自己的 EditCell 实现。但是,如果您想在失去焦点时提交编辑,那么您必须查看TableView doesn't commit values on focus lost event 的答案(或其链接/相关问题)并尝试使用给定的解决方案之一(黑客)。

此外,如以下文档中所述,您不必提供自己的 onEditCommit 处理程序即可将新值写入模型 — TableColumn 默认情况下会这样做(假设 cellValueFactory 返回WritableValue)。


文档

也许阅读TableView 的文档会比你正在阅读的教程更有益,或者至少是对你正在阅读的教程的补充:

编辑

此控件支持值的内联编辑,本节试图概述可用的 API 以及您应该如何使用它们。

首先,单元格编辑通常需要与未编辑单元格时不同的用户界面。这是正在使用的Cell 实现的责任。对于TableView,强烈建议按TableColumn 而不是按行进行编辑,因为您通常希望用户以不同的方式编辑每个列值,并且这种方法允许特定于每个列的编辑器。您可以选择单元格是永久处于编辑状态(例如,这对于CheckBox 单元格来说很常见),还是在开始编辑时(例如,在单元格上收到双击时)切换到不同的 UI。

要知道何时请求对单元格进行编辑,只需重写 Cell.startEdit() 方法,并根据需要更新单元格文本和图形属性(例如,将文本设置为 null 并将图形设置为 TextField) .此外,您还应该覆盖 Cell.cancelEdit() 以在编辑结束时将 UI 重置为其原始视觉状态。在这两种情况下,您还必须确保调用super 方法以使单元格执行进入或退出其编辑模式必须执行的所有职责。

一旦您的单元格处于编辑状态,您最感兴趣的下一件事情就是如何提交或取消正在进行的编辑。这是您作为电池工厂供应商的责任。您的单元实现将根据用户输入(例如,当用户按下键盘上的 EnterESC 键时)知道编辑何时结束。发生这种情况时,您有责任酌情致电Cell.commitEdit(Object)Cell.cancelEdit()

当您调用Cell.commitEdit(Object) 时,会向TableView 触发一个事件,您可以通过TableColumn.setOnEditCommit(javafx.event.EventHandler) 添加一个EventHandler 来观察该事件。同样,您也可以观察编辑开始和编辑取消的编辑事件。

默认情况下,TableColumn 编辑提交处理程序是非空的,默认处理程序会尝试覆盖当前正在编辑的行中项目的属性值。它能够做到这一点,因为 Cell.commitEdit(Object) 方法传入了新值,并通过被触发的 CellEditEvent 传递给编辑提交处理程序。只需调用TableColumn.CellEditEvent.getNewValue() 即可检索此值。

请务必注意,如果您使用自己的 EventHandler 调用 TableColumn.setOnEditCommit(javafx.event.EventHandler),那么您将删除默认处理程序。除非您随后处理对属性(或相关数据源)的写回,否则不会发生任何事情。您可以通过使用TableColumnBase.addEventHandler(javafx.event.EventType, javafx.event.EventHandler) 方法来解决此问题,以添加TableColumn.editCommitEvent() EventType,并将所需的EventHandler 作为第二个参数。使用此方法,您不会替换默认实现,但会在发生编辑提交时收到通知。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-28
    • 2013-05-16
    • 2013-11-29
    • 1970-01-01
    • 2014-02-22
    • 2014-12-13
    • 2015-10-02
    • 1970-01-01
    相关资源
    最近更新 更多