在TableView中编辑单元格后更新ObservableList

时间:2019-02-19 09:45:54

标签: java javafx fxml

我正尝试使用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();
    }
}

1 个答案:

答案 0 :(得分:1)

编辑

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

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


关于教程

您链接到的tutorial出现问题。其中最大的假设是,当TextField失去焦点时,您可以成功提交新值。在您遇到的情况并非如此。通过查看以下问题,您可以看到许多其他人都遇到了此问题:TableView doesn't commit values on focus lost event。该问题的答案提供了许多方法来 hack 解决该问题。有些人还指出了错误报告,表明“失去焦点时不提交”行为实际上是意料之外的。但是,从JavaFX 11.0.2开始,这些错误尚未得到修复。

这是什么意思:

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

永远不会提交修改。您(但实际上是本教程)没有提供任何工作方式来提交新值,因为在调用if (!arg2) { commitEdit(...); }时取消了编辑。由于取消了编辑,因此不会触发提交编辑事件,并且您的TableColumn无法将新值写入模型项。尽管无法解决失去焦点时没有提交的问题,但是您可以做的是在您的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默认情况下会这样做(假设{ {1}}返回cellValueFactory)。


文档

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

  

编辑

     

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

     

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

     

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

     

单元格处于编辑状态后,您最可能感兴趣的下一件事是如何提交或取消正在进行的编辑。作为电池工厂提供者,这是您的责任。您的单元实现将基于用户输入(例如,当用户按下键盘上的 Enter ESC 键时)知道编辑何时结束。发生这种情况时,您有责任视情况致电Cell.cancelEdit()super

     

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

     

默认情况下,EventHandler编辑提交处理程序为非null,默认处理程序尝试覆盖当前正在编辑的行中项目的属性值。之所以能够做到这一点,是因为在新值中传递了TableColumn.setOnEditCommit(javafx.event.EventHandler)方法,并通过触发的TableColumn将其传递给了编辑提交处理程序。只需调用Cell.commitEdit(Object)来检索此值即可。

     

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