TableView 不会在焦点丢失事件上提交值 [英] TableView doesn't commit values on focus lost event

查看:24
本文介绍了TableView 不会在焦点丢失事件上提交值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'd like to create a table with the following features:

  • 按键编辑
  • 回车键 = 下一行
  • Tab 键 = 下一列
  • 退出键 = 取消编辑

下面是实现这些功能的代码.这些价值观应该致力于失去焦点.问题:他们没有承诺.焦点更改事件被触发,根据控制台输出的值将是正确的,但最终表格单元格中的值是旧的.

Below is a code which implements these features. The values should be committed on focus lost. Problem: They aren't committed. The focus change event is fired, the values would be correct according to the console output, but in the end the values in the table cells are the old ones.

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

Does anyone know how to prevent this and how do you get the current EditingCell object so that I can invoke commit manually? After all there should be some kind of verifier invoked which prevents changing the focus if the values aren't correct.

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 代码取消了编辑模式.这很糟糕.

I've narrowed it down. It seems the problem is that the JavaFX code cancels the edit mode when the focus changes. That's bad.

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);
}

推荐答案

我很好奇,做了一些背景调查.

您正面临 JavaFX 中一个众所周知的错误的问题.

You are facing the problem of a well-known bug in the JavaFX.

当你调用 commitEdit(textField.getText()) 时,它做的第一件事就是检查 isEditing() 的值,如果值为 false,不提交.

When you call commitEdit(textField.getText()), the first thing it does is to check the value of isEditing() and returns if the value is false, without committing.

public void commitEdit(T newValue) {
    if (! isEditing()) return;

    ... // Rest of the things
}

为什么返回false?

您可能已经发现,只要您按下 TABENTER 来更改您的选择,cancelEdit() 就会被调用将 TableCell.isEditing() 设置为 false.当 textField 的焦点属性侦听器中的 commitEdit() 被调用时,isEditing() 已经返回 false.

As you have probably found out, as soon as you press TAB or ENTER to change your selection, cancelEdit() is called which sets the TableCell.isEditing() to false. By the time the commitEdit() inside textField's focus property listener is called, isEditing() is already returning false.

JavaFX 社区一直在讨论该主题.那里的人已经发布了hacks,欢迎您查看.

There have been on going discussion on the Topic in JavaFX community. People in there have posted hacks, which you are most welcome to look at.

一个 SO 线程 中显示了一个 hack,它似乎完成了工作,尽管我还没有试过了(还).

There is a hack shown in a SO thread, which seems to get the job done, although I haven't tried it (yet).

这篇关于TableView 不会在焦点丢失事件上提交值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆