Javafx TableView没有';t提交焦点丢失事件上的值

Javafx TableView没有';t提交焦点丢失事件上的值,javafx,tableview,Javafx,Tableview,我想创建一个具有以下功能的表: 按键编辑 输入key=下一行 Tab键=下一列 退出键=取消编辑 下面是实现这些功能的代码。这些值应在焦点丢失时提交。问题:他们没有承诺。触发焦点更改事件后,根据控制台输出,值将是正确的,但最终表单元格中的值是旧值 是否有人知道如何防止这种情况发生,以及如何获取当前的EditingCell对象以便我可以手动调用commit?毕竟,应该调用某种类型的验证器,以防止在值不正确时更改焦点 import javafx.application.Application;

我想创建一个具有以下功能的表:

  • 按键编辑
  • 输入key=下一行
  • Tab键=下一列
  • 退出键=取消编辑
下面是实现这些功能的代码。这些值应在焦点丢失时提交。问题:他们没有承诺。触发焦点更改事件后,根据控制台输出,值将是正确的,但最终表单元格中的值是旧值

是否有人知道如何防止这种情况发生,以及如何获取当前的EditingCell对象以便我可以手动调用commit?毕竟,应该调用某种类型的验证器,以防止在值不正确时更改焦点

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TableViewInlineEditDemo extends Application {

    private final TableView<Person> table = new TableView<>();
    private final ObservableList<Person> data =
            FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
            new Person("Ethan", "Williams", "ethan.williams@example.com"),
            new Person("Emma", "Jones", "emma.jones@example.com"),
            new Person("Michael", "Brown", "michael.brown@example.com"));

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

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setWidth(450);
        stage.setHeight(550);

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));

        table.setEditable(true);

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

        TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
        TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
        TableColumn<Person, String> emailCol = new TableColumn<>("Email");

        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(new PropertyValueFactory<>("firstName"));
        firstNameCol.setCellFactory(cellFactory);
        firstNameCol.setOnEditCommit((CellEditEvent<Person, String> t) -> {
            ((Person) t.getTableView().getItems().get(t.getTablePosition().getRow())).setFirstName(t.getNewValue());
        });

        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(new PropertyValueFactory<>("lastName"));
        lastNameCol.setCellFactory(cellFactory);
        lastNameCol.setOnEditCommit((CellEditEvent<Person, String> t) -> {
            ((Person) t.getTableView().getItems().get(t.getTablePosition().getRow())).setLastName(t.getNewValue());
        });

        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(new PropertyValueFactory<>("email"));
        emailCol.setCellFactory(cellFactory);
        emailCol.setOnEditCommit((CellEditEvent<Person, String> t) -> {
            ((Person) t.getTableView().getItems().get(t.getTablePosition().getRow())).setEmail(t.getNewValue());
        });

        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);


        // edit mode on keypress
        table.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent e) {

                if( e.getCode() == KeyCode.TAB) { // commit should be performed implicitly via focusedProperty, but isn't
                    table.getSelectionModel().selectNext();
                    e.consume();
                    return;
                }
                else if( e.getCode() == KeyCode.ENTER) { // commit should be performed implicitly via focusedProperty, but isn't
                    table.getSelectionModel().selectBelowCell();
                    e.consume();
                    return;
                }

                // switch to edit mode on keypress, but only if we aren't already in edit mode
                if( table.getEditingCell() == null) {
                    if( e.getCode().isLetterKey() || e.getCode().isDigitKey()) {  

                        TablePosition focusedCellPosition = table.getFocusModel().getFocusedCell();
                        table.edit(focusedCellPosition.getRow(), focusedCellPosition.getTableColumn());

                    }
                }

            }
        });

        // single cell selection mode
        table.getSelectionModel().setCellSelectionEnabled(true);
        table.getSelectionModel().selectFirst();


        final VBox vbox = new VBox();
        vbox.getChildren().addAll(label, table);

        ((Group) scene.getRoot()).getChildren().addAll(vbox);

        stage.setScene(scene);
        stage.show();
    }


    class EditingCell extends TableCell<Person, String> {

        private TextField textField;

        public EditingCell() {
        }

        @Override
        public void startEdit() {
            if (!isEmpty()) {
                super.startEdit();
                createTextField();
                setText(null);
                setGraphic(textField);
                textField.requestFocus(); // must be before selectAll() or the caret would be in wrong position
                textField.selectAll();
            } 
        }

        @Override
        public void cancelEdit() {
            super.cancelEdit();
            setText((String) getItem());
            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);

            // commit on focus lost
            textField.focusedProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {

                if( oldValue = true && newValue == false) {

                    System.out.println( "Focus lost, current value: " + textField.getText());

                    commitEdit();

                }
            });

            // cancel edit on ESC
            textField.addEventFilter(KeyEvent.KEY_RELEASED, e -> {

                if( e.getCode() == KeyCode.ESCAPE) {
                    cancelEdit();
                }

            });

        }

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

        private boolean commitEdit() {
            super.commitEdit(textField.getText());
            return true; // TODO: add verifier and check if commit was possible
        }
    }

    public static class Person {

        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;

        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }

        public String getFirstName() {
            return firstName.get();
        }

        public void setFirstName(String fName) {
            firstName.set(fName);
        }

        public String getLastName() {
            return lastName.get();
        }

        public void setLastName(String fName) {
            lastName.set(fName);
        }

        public String getEmail() {
            return email.get();
        }

        public void setEmail(String fName) {
            email.set(fName);
        }
    }

}
导入javafx.application.application;
导入javafx.beans.property.SimpleStringProperty;
导入javafx.beans.value.observeValue;
导入javafx.collections.FXCollections;
导入javafx.collections.ObservableList;
导入javafx.event.EventHandler;
导入javafx.scene.Group;
导入javafx.scene.scene;
导入javafx.scene.control.Label;
导入javafx.scene.control.TableCell;
导入javafx.scene.control.TableColumn;
导入javafx.scene.control.TableColumn.CellEditEvent;
导入javafx.scene.control.TablePosition;
导入javafx.scene.control.TableView;
导入javafx.scene.control.TextField;
导入javafx.scene.control.cell.PropertyValueFactory;
导入javafx.scene.input.KeyCode;
导入javafx.scene.input.KeyEvent;
导入javafx.scene.layout.VBox;
导入javafx.scene.text.Font;
导入javafx.stage.stage;
导入javafx.util.Callback;
公共类TableViewInlineEditDemo扩展了应用程序{
private final TableView table=new TableView();
私有最终可观测列表数据=
FXCollections.observableArrayList(
新人(“雅各布”、“史密斯”、“雅各布”。smith@example.com"),
新人(“伊莎贝拉”、“约翰逊”、“伊莎贝拉”。johnson@example.com"),
新人(“伊桑”、“威廉姆斯”、“伊桑”。williams@example.com"),
新人(“艾玛”、“琼斯”、“艾玛”。jones@example.com"),
新人(“迈克尔”、“布朗”、“迈克尔”。brown@example.com"));
公共静态void main(字符串[]args){
发射(args);
}
@凌驾
公众假期开始(阶段){
场景=新场景(新组());
舞台布景宽度(450);
舞台设置高度(550);
最终标签=新标签(“地址簿”);
label.setFont(新字体(“Arial”,20));
table.setEditable(true);
Callback cellFactory=(TableColumn p)->new EditingCell();
TableColumn firstNameCol=新的TableColumn(“名字”);
TableColumn lastNameCol=新的TableColumn(“姓氏”);
TableColumn emailCol=新的TableColumn(“电子邮件”);
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(新属性ValueFactory(“firstName”));
firstNameCol.setCellFactory(cellFactory);
firstNameCol.setOnEditCommit((CellEditEvent t)->{
((Person)t.getTableView().getItems().get(t.getTablePosition().getRow()).setFirstName(t.getNewValue());
});
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(新属性ValueFactory(“lastName”));
lastNameCol.setCellFactory(cellFactory);
lastNameCol.setOnEditCommit((CellEditEvent t)->{
((Person)t.getTableView().getItems().get(t.getTablePosition().getRow()).setLastName(t.getNewValue());
});
设置最小宽度(200);
emailCol.setCellValueFactory(新属性ValueFactory(“电子邮件”);
emailCol.setCellFactory(cellFactory);
emailCol.setOnEditCommit((CellEditEvent t)->{
((Person)t.getTableView().getItems().get(t.getTablePosition().getRow()).setEmail(t.getNewValue());
});
表2.设置项目(数据);
table.getColumns().addAll(firstNameCol、lastNameCol、emailCol);
//按键编辑模式
table.addEventFilter(按下KeyEvent.KEY_,新建EventHandler()){
@凌驾
公共无效句柄(KeyEvent e){
如果(e.getCode()==KeyCode.TAB){//commit应该通过focusedProperty隐式执行,但不是
table.getSelectionModel().selectNext();
e、 消费();
回来
}
else if(e.getCode()==KeyCode.ENTER){//commit应该通过focusedProperty隐式执行,但不是
table.getSelectionModel().selectBelowCell();
e、 消费();
回来
}
//在按键时切换到编辑模式,但前提是我们尚未处于编辑模式
if(table.getEditingCell()==null){
如果(e.getCode().IsleterKey()| | e.getCode().isDigitKey()){
TablePosition focusedCellPosition=table.getFocusModel().getFocusedCell();
编辑(focusedCellPosition.getRow(),focusedCellPosition.getTableColumn());
}
}
}
});
//单细胞选择模式
table.getSelectionModel().setCellSelectionEnabled(true);
table.getSelectionModel().selectFirst();
最终VBox VBox=新的VBox();
vbox.getChildren().addAll(标签、表格);
((组)scene.getRoot()).getChildren().addAll(vbox);
舞台场景;
stage.show();
}
类EditingCell扩展了TableCell{
私有文本字段文本字段;
公共编辑单元(){
}
@凌驾
公开作废已启动IT(){
如果(!isEmpty()){
super.startEdit();
createTextField();
setText(空);
设置图形(文本字段);
textField.requestFocus();//必须在selectAll()之前,否则插入符号将位于错误的位置
public Cell() {
    setText(null); // default to null text, to match the null item
    // focusTraversable is styleable through css. Calling setFocusTraversable
    // makes it look to css like the user set the value and css will not 
    // override. Initializing focusTraversable by calling set on the 
    // CssMetaData ensures that css will be able to override the value.
    ((StyleableProperty<Boolean>)(WritableValue<Boolean>)focusTraversableProperty()).applyStyle(null, Boolean.FALSE);
    getStyleClass().addAll(DEFAULT_STYLE_CLASS);

    /**
     * Indicates whether or not this cell has focus. For example, a
     * ListView defines zero or one cell as being the "focused" cell. This cell
     * would have focused set to true.
     */
    super.focusedProperty().addListener(new InvalidationListener() {
        @Override public void invalidated(Observable property) {
            pseudoClassStateChanged(PSEUDO_CLASS_FOCUSED, isFocused()); // TODO is this necessary??

            // The user has shifted focus, so we should cancel the editing on this cell
            if (!isFocused() && isEditing()) {
                cancelEdit();
            }
        }
    });

    // initialize default pseudo-class state
    pseudoClassStateChanged(PSEUDO_CLASS_EMPTY, true);
}
public void commitEdit(T newValue) {
    if (! isEditing()) return;

    ... // Rest of the things
}
import javafx.beans.binding.Bindings;
import javafx.scene.Node;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

public abstract class AutoCommitTableCell<S,T> extends TableCell<S,T>
{
    private Node field;
    private boolean startEditing;
    private T defaultValue;


    /** @return a newly created input field. */
    protected abstract Node newInputField();

    /** @return the current value of the input field. */
    protected abstract T getInputValue();

    /** Sets given value to the input field. */
    protected abstract void setInputValue(T value);

    /** @return the default in case item is null, must be never null, else cell will not be editable. */
    protected abstract T getDefaultValue();

    /** @return converts the given value to a string, being the cell-renderer representation. */
    protected abstract String inputValueToText(T value);


    @Override
    public void startEdit() {
        try {
            startEditing = true;

            super.startEdit();  // updateItem() will be called

            setInputValue(getItem());
        }
        finally {
            startEditing = false;
        }
    }

    /** Redirects to commitEdit(). Leaving the cell should commit, just ESCAPE should cancel. */
    @Override
    public void cancelEdit() {
        // avoid JavaFX NullPointerException when calling commitEdit()
        getTableView().edit(getIndex(), getTableColumn());

        commitEdit(getInputValue());
    }

    private void cancelOnEscape() {
        if (defaultValue != null)    {   // canceling default means writing null
            setItem(defaultValue = null);
            setText(null);
            setInputValue(null);
        }
        super.cancelEdit();
    }

    @Override
    protected void updateItem(T newValue, boolean empty) {
        if (startEditing && newValue == null)
            newValue = (defaultValue = getDefaultValue());

        super.updateItem(newValue, empty);

        if (empty || newValue == null) {
            setText(null);
            setGraphic(null);
        }
        else {
            setText(inputValueToText(newValue));
            setGraphic(startEditing || isEditing() ? getInputField() : null);
        }
    }

    protected final Node getInputField()    {
        if (field == null)    {
            field = newInputField();

            // a cell-editor won't be committed or canceled automatically by JFX
            field.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
                if (event.getCode() == KeyCode.ENTER || event.getCode() == KeyCode.TAB)
                    commitEdit(getInputValue());
                else if (event.getCode() == KeyCode.ESCAPE)
                    cancelOnEscape();
            });

            contentDisplayProperty().bind(
                    Bindings.when(editingProperty())
                        .then(ContentDisplay.GRAPHIC_ONLY)
                        .otherwise(ContentDisplay.TEXT_ONLY)
                );
        }
        return field;
    }
}
import javafx.scene.Node;
import javafx.scene.control.TextField;
import jfx.examples.tablebinding.PersonsModel.Person;

public class StringTableCell extends AutoCommitTableCell<Person,String>
{
    @Override
    protected String getInputValue() {
        return ((TextField) getInputField()).getText();
    }

    @Override
    protected void setInputValue(String value) {
        ((TextField) getInputField()).setText(value);
    }

    @Override
    protected String getDefaultValue() {
        return "";
    }

    @Override
    protected Node newInputField() {
        return new TextField();
    }

   @Override
    protected String inputValueToText(String newValue) {
        return newValue;
    }
}
final TableColumn<Person,String> nameColumn = new TableColumn<Person,String>("Name");
nameColumn.setCellValueFactory(
        cellDataFeatures -> cellDataFeatures.getValue().nameProperty());
nameColumn.setCellFactory(
        cellDataFeatures -> new StringTableCell());
public class EditCell<S, T> extends TableCell<S, T> {
    private final TextField textField = new TextField();

    // Converter for converting the text in the text field to the user type, and vice-versa:
    private final StringConverter<T> converter;

    /**
     * Creates and initializes an edit cell object.
     * 
     * @param converter
     *            the converter to convert from and to strings
     */
    public EditCell(StringConverter<T> converter) {
        this.converter = converter;

        itemProperty().addListener((obx, oldItem, newItem) -> {
            setText(newItem != null ? this.converter.toString(newItem) : null);
        });

        setGraphic(this.textField);
        setContentDisplay(ContentDisplay.TEXT_ONLY);

        this.textField.setOnAction(evt -> {
            commitEdit(this.converter.fromString(this.textField.getText()));
        });
        this.textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
            if (!isNowFocused) {
                commitEdit(this.converter.fromString(this.textField.getText()));
            }
        });
        this.textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
            if (event.getCode() == KeyCode.ESCAPE) {
                this.textField.setText(this.converter.toString(getItem()));
                cancelEdit();
                event.consume();
            } else if (event.getCode() == KeyCode.TAB) {
                commitEdit(this.converter.fromString(this.textField.getText()));
                TableColumn<S, ?> nextColumn = getNextColumn(!event.isShiftDown());
                if (nextColumn != null) {
                    getTableView().getSelectionModel().clearAndSelect(getTableRow().getIndex(), nextColumn);
                    getTableView().edit(getTableRow().getIndex(), nextColumn);
                }
            }
        });
    }

    /**
     * Convenience converter that does nothing (converts Strings to themselves and vice-versa...).
     */
    public static final StringConverter<String> IDENTITY_CONVERTER = new StringConverter<String>() {

        @Override
        public String toString(String object) {
            return object;
        }

        @Override
        public String fromString(String string) {
            return string;
        }

    };

    /**
     * Convenience method for creating an EditCell for a String value.
     * 
     * @return the edit cell
     */
    public static <S> EditCell<S, String> createStringEditCell() {
        return new EditCell<S, String>(IDENTITY_CONVERTER);
    }

    // set the text of the text field and display the graphic
    @Override
    public void startEdit() {
        super.startEdit();
        this.textField.setText(this.converter.toString(getItem()));
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        this.textField.requestFocus();
    }

    // revert to text display
    @Override
    public void cancelEdit() {
        super.cancelEdit();
        setContentDisplay(ContentDisplay.TEXT_ONLY);
    }

    // commits the edit. Update property if possible and revert to text display
    @Override
    public void commitEdit(T item) {
        // This block is necessary to support commit on losing focus, because the baked-in mechanism
        // sets our editing state to false before we can intercept the loss of focus.
        // The default commitEdit(...) method simply bails if we are not editing...
        if (!isEditing() && !item.equals(getItem())) {
            TableView<S> table = getTableView();
            if (table != null) {
                TableColumn<S, T> column = getTableColumn();
                CellEditEvent<S, T> event = new CellEditEvent<>(table,
                        new TablePosition<S, T>(table, getIndex(), column),
                        TableColumn.editCommitEvent(), item);
                Event.fireEvent(column, event);
            }
        }

        super.commitEdit(item);

        setContentDisplay(ContentDisplay.TEXT_ONLY);
    }

    /**
     * Finds and returns the next editable column.
     * 
     * @param forward
     *            indicates whether to search forward or backward from the current column
     * @return the next editable column or {@code null} if there is no next column available
     */
    private TableColumn<S, ?> getNextColumn(boolean forward) {
        List<TableColumn<S, ?>> columns = new ArrayList<>();
        for (TableColumn<S, ?> column : getTableView().getColumns()) {
            columns.addAll(getEditableColumns(column));
        }
        // There is no other column that supports editing.
        if (columns.size() < 2) { return null; }
        int currentIndex = columns.indexOf(getTableColumn());
        int nextIndex = currentIndex;
        if (forward) {
            nextIndex++;
            if (nextIndex > columns.size() - 1) {
                nextIndex = 0;
            }
        } else {
            nextIndex--;
            if (nextIndex < 0) {
                nextIndex = columns.size() - 1;
            }
        }
        return columns.get(nextIndex);
    }

    /**
     * Returns all editable columns of a table column (supports nested columns).
     * 
     * @param root
     *            the table column to check for editable columns
     * @return a list of table columns which are editable
     */
    private List<TableColumn<S, ?>> getEditableColumns(TableColumn<S, ?> root) {
        List<TableColumn<S, ?>> columns = new ArrayList<>();
        if (root.getColumns().isEmpty()) {
            // We only want the leaves that are editable.
            if (root.isEditable()) {
                columns.add(root);
            }
            return columns;
        } else {
            for (TableColumn<S, ?> column : root.getColumns()) {
                columns.addAll(getEditableColumns(column));
            }
            return columns;
        }
    }
}
    @FXML
    private void initialize() {
        table.getSelectionModel().setCellSelectionEnabled(true);
        table.setEditable(true);

        table.getColumns().add(createColumn("First Name", Person::firstNameProperty));
        table.getColumns().add(createColumn("Last Name", Person::lastNameProperty));
        table.getColumns().add(createColumn("Email", Person::emailProperty));

        table.getItems().addAll(
                new Person("Jacob", "Smith", "jacob.smith@example.com"),
                new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
                new Person("Ethan", "Williams", "ethan.williams@example.com"),
                new Person("Emma", "Jones", "emma.jones@example.com"),
                new Person("Michael", "Brown", "michael.brown@example.com")
        );

        table.setOnKeyPressed(event -> {
            TablePosition<Person, ?> pos = table.getFocusModel().getFocusedCell() ;
            if (pos != null && event.getCode().isLetterKey()) {
                table.edit(pos.getRow(), pos.getTableColumn());
            }
        });
    }

    private <T> TableColumn<T, String> createColumn(String title, Function<T, StringProperty> property) {
        TableColumn<T, String> col = new TableColumn<>(title);
        col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));

        col.setCellFactory(column -> EditCell.createStringEditCell());
        return col;
    }
abstract class EditingTextCell<T, V> extends TableCell<T, V> {
    protected TextField textField;
    private T editedItem;

    @Override
    public void startEdit() {
        ...
        textField.focusedProperty().addListener((t, oldval, newval) -> {
            if (!newval) {
                setItemValue(editedItem, textField.getText());
            }
        });

        editedItem = (T) getTableRow().getItem();
    }
    public abstract void setItemValue(T item, String text);
    ...
}