Обновление ObservableList после редактирования ячейки в TableView - PullRequest
0 голосов
/ 19 февраля 2019

Я пытаюсь создать редактируемые ячейки, используя плохой учебник 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 голосов
/ 21 февраля 2019

Редактирование

При редактировании обработчик onEditCommit уведомляется, когда редактирование было совершено (что неудивительно).Этот обработчик отвечает за запись нового значения в модель (в вашем случае, Person).Когда это происходит, TableView автоматически обновляется для отображения нового значения.

Ваше решение установить текст Cell равным значению TextField при отмене редактирования не будет работать,В конце концов, когда обновление каким-либо образом инициируется, Cell обновится, чтобы отобразить реальные данные, предоставленные моделью (полученные cellValueFactory).Кроме того, вы фактически не обновляли модель, и поэтому предполагаемое редактирование - это просто визуальная вещь.


Об учебнике

Учебник , на который вы ссылаетесьимеет проблемы.Самым большим из них является то, что предполагается, что когда TextField теряет фокус, вы можете успешно зафиксировать новое значение.Как вы испытываете, это не так.Вы можете увидеть, что многие другие столкнулись с этой проблемой, взглянув на этот вопрос: TableView не фиксирует значения для события потери фокуса .Ответы на этот вопрос предоставляют множество способов взломать вокруг проблемы.Некоторые также указывают на сообщения об ошибках, указывающие на то, что поведение 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 не может записать новое значение в элемент модели.Что вы можете сделать, хотя это не решит проблему «без фиксации при потере фокуса», это добавить обработчик 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 не фиксирует значения для события фокуса потерянного (или его связанных / связанных вопросов) ипопытайтесь использовать одно из указанных решений (хаков).

Кроме того, как отмечено в приведенной ниже документации, вам не нужно предоставлять собственный обработчик onEditCommit, чтобы записать новое значение в модель.- TableColumn делает это по умолчанию (при условии, что cellValueFactory возвращает WritableValue).


Документация

Возможно чтение документации TableViewбудет более полезным, чем учебное пособие, которое вы читаете, или, по крайней мере, будет дополнять его:

Редактирование

Этот элемент управления поддерживает встроенное редактирование значений, иВ этом разделе делается попытка дать обзор доступных API и способов их использования.

Во-первых, для редактирования ячеек чаще всего требуется другой пользовательский интерфейс, чем когда ячейка не редактируется.Это ответственность используемой реализации Cell.Для TableView настоятельно рекомендуется, чтобы редактирование выполнялось по-1075 *, а не по строке, поскольку чаще всего вы хотите, чтобы пользователи редактировали каждое значение столбца по-разному, и этот подход позволяет использовать редакторы, специфичные для каждого столбца.Вы можете выбрать, находится ли ячейка постоянно в состоянии редактирования (например, это характерно для CheckBox ячеек), или переключиться на другой пользовательский интерфейс, когда начинается редактирование (например, при получении двойного щелчка по ячейке).

Чтобы узнать, когда для ячейки было запрошено редактирование, просто переопределите метод Cell.startEdit() и обновите текст и графические свойства ячейки соответствующим образом (например, установите для текста значение null и установите для графики значение TextField),Кроме того, вы также должны переопределить Cell.cancelEdit(), чтобы вернуть пользовательский интерфейс в исходное визуальное состояние после завершения редактирования.В обоих случаях важно также убедиться, что вы вызываете метод super, чтобы ячейка выполняла все обязанности, которые она должна выполнять для входа или выхода из режима редактирования.

Как только ваша ячейка находится в состоянии редактированияСледующее, что вас, скорее всего, заинтересует, это как зафиксировать или отменить происходящее редактирование.Это ваша ответственность как поставщика клеточной фабрики.Ваша реализация ячейки будет знать, когда редактирование закончится, на основе пользовательского ввода (например, когда пользователь нажимает клавиши Enter или ESC на своей клавиатуре).Когда это происходит, вы обязаны позвонить Cell.commitEdit(Object) или Cell.cancelEdit(), в зависимости от ситуации.

Когда вы звоните Cell.commitEdit(Object), событие вызывается на TableView, что можно наблюдать, добавивEventHandler через 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() EventType с желаемым EventHandler в качестве второго аргумента.Используя этот метод, вы не замените реализацию по умолчанию, но вы будете уведомлены, когда произошла фиксация редактирования.

...