TableView 不会在焦点丢失事件上提交值

2024-03-14

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

  • 按键编辑
  • 输入键 = 下一行
  • Tab 键 = 下一列
  • 退出键 = 取消编辑

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

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

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", "[email protected] /cdn-cgi/l/email-protection"),
            new Person("Isabella", "Johnson", "[email protected] /cdn-cgi/l/email-protection"),
            new Person("Ethan", "Williams", "[email protected] /cdn-cgi/l/email-protection"),
            new Person("Emma", "Jones", "[email protected] /cdn-cgi/l/email-protection"),
            new Person("Michael", "Brown", "[email protected] /cdn-cgi/l/email-protection"));

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

}

非常感谢!

Edit:我已经缩小范围了。问题似乎是当焦点改变时,JavaFX 代码取消了编辑模式。那很糟。

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 中一个众所周知的错误的问题。

背景

你打电话时commitEdit(textField.getText()),它所做的第一件事是检查isEditing()如果值为false,无需承诺。

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

    ... // Rest of the things
}

为什么会返回 false?

正如您可能已经发现的那样,一旦您按下TAB or ENTER更改您的选择,cancelEdit()被称为设置TableCell.isEditing()为假。到时候commitEdit()在 textField 的 focus 属性监听器内部被调用,isEditing()已经返回false.

解决方案/黑客

JavaFX 社区对此主题的讨论一直在进行。里面的人已经发帖了hacks,非常欢迎您查看。

  • TableView、TreeView、ListView - 单击编辑的单元格、节点或条目外部应提交值 https://bugs.openjdk.java.net/browse/JDK-8089514
  • TableCell - 提交焦点丢失并非在每种情况下都是不可能的 https://bugs.openjdk.java.net/browse/JDK-8089311

有一个 hack 显示在一个SO线程 https://stackoverflow.com/a/25291137/1759128,这似乎完成了工作,尽管我还没有尝试过。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TableView 不会在焦点丢失事件上提交值 的相关文章

  • 无法将 intellij 的 scenebuilder 与 jfoenix 一起使用

    我使用 java 9 0 4 创建了一个 javafx 项目 该代码可以编译并运行 我可以使用内部场景构建器 但是当我从 Jfoenix 9 0 1 库导入元素时 我无法再使用 sceneBuilder 我收到此错误 java lang U
  • 仅当用户开始输入时清除 JavaFX TextField 中的提示文本

    默认行为是当字段获得焦点时 字段中的提示文本将被删除 那是标记在场上的时候 是否可以配置文本字段 以便仅在用户开始输入时删除提示文本 否则 我需要在每个文本字段旁边 上方添加一个标签 以描述其中的值 我知道它有点旧 但我自己也需要它 这仍然
  • 如何在JavaFX中为TextArea设置圆角?

    我需要在 TextArea 上有圆角 但它看起来有点奇怪 看起来 有些内层也应该有相同半径的圆角 但是哪一个呢 我使用这个CSS text area fx background color dbb1b1 fff0f0 fx backgrou
  • Mac 上的 JavaFX WebView 字体问题

    有些网站显示乱码而不是正确的文本 它只发生在 Mac 上 For example with GMapsFX 可能与 OS X 10 11 或 10 12 有关 我用Java 1 8 0 121测试了它 此问题有任何修复或解决方法吗 就我而言
  • 如何调整Javafx未装饰阶段的大小?

    我是JavaFX技术的新手 目前我正在开发javafx应用程序 其中有未装饰的阶段 我能够使用下面的代码将其移动到屏幕上 但我是无法从右下角调整此窗口的大小 任何人都可以建议我解决方案 public void loadPanel final
  • didSelectRowAtIndexPath 与点击手势识别器冲突

    我通过情节提要在 ViewController 中设置了点击手势识别器 因此 如果显示此视图中的所有点击 将隐藏键盘 问题是 现在 我在这个视图中添加了一个 TableView 当我点击一个单元格时 使用点击手势识别器设置的方法是调用 而不
  • JavaFX 拖放无法正常工作

    我在网格窗格中使用两个 Imageview 进行拖放测试 我的问题是 当我完成拖放并将目标图像视图移动到源图像视图并释放鼠标时 我错误地最终显示了 img2 中的图片 而不是 img1 中的图片 当我注释掉 setOnDragExited
  • JavaFX Integer Spinner (IntegerSpinnerValueFactory) 不会将值回绕到最小值

    我创建了一个带有值的整数微调器 min 5 max 15 and initialValue 12 and wrapAround true 一旦旋转器到达max 15 增量期间的值 而不是将值重置为min 5 正如它所说文档 https op
  • 按钮悬停和按下效果 CSS Javafx

    我是 CSS 新手 为按钮定义了以下 CSS 样式 其中id并且应用了自定义样式 但不应用悬停和按下效果 bevel grey fx background color linear gradient f2f2f2 d6d6d6 linear
  • Mac OS X 下的 JavaFX:系统菜单和模态窗口

    我目前面临 Mac OS X 下的 JavaFX 菜单和模式对话框的问题 我正在使用该方法 MenuBar setUseSystemMenuBar true 为了将系统菜单栏用于我的应用程序菜单 这工作正常 但如果模式对话框打开 菜单不会被
  • 绘制圆和连接这些圆的曲线

    我需要绘制一些圆圈和连接这些圆圈的曲线 圆圈必须以行和列的方式绘制 所以我想使用 GridPane 将是一个不错的选择 但是 绘制完所有圆圈后 如何添加曲线来连接属于此 GridPane 上不同列的圆圈 我无法将 GridPane add
  • 如何创建一个显示 Spinners 的 x 和 y 值的表格?

    我想创建一个位于图表右侧的表格 其中显示 2 列 x 和 y 值已输入到xSpin and ySpin旋转器 我已经画了一张我想要桌子放置的位置的图 我尝试过在网格窗格布局中使用文本框来创建表格并将值直接输入到文本框网格中 但是我无法将它们
  • Javafx 拖放 TabPane

    我 在 JavaFx 应用程序中 有一个带有不同选项卡的选项卡 我想实现拖放功能以将选项卡拖到舞台之外 这样它就可以生成一个新窗口 就像在 Google Chrome 中一样 谢谢您的帮助 您应该检查 Tom Schindl 在他的网站上显
  • Raspberry PI 上的 JavaFX:加载库存着色器时出错

    目前我正在尝试部署我的 JavaFX 应用程序 该应用程序可以在 Windows 上的 Raspberry Model B v1 2 上顺利运行 由于 JavaFX 不能直接在 Raspi 上使用 我已经按照此处所述使用 Gluon 进行了
  • 使用 PropertyEditor (ControlsFX) 的属性表示例

    我一直在寻找使用 ControlsFX 属性表的任何好例子 但除了这个之外找不到任何东西 在此示例中 包含 NameItem 对象的 ObservableList 项被添加到其构造函数中的 PropertySheet 对象中 就像文档所述一
  • JavaFX:在 WebView img 标签中未加载本地图像

    以下是我的代码 一切安好 我可以加载远程页面 我可以放置 HTML 内容 但我的img标签显示一个X标志表示无法加载图像 Note 我的图像与类位于同一个包中JavaFX在 Smiley 文件夹中 我可以列出所有图像 这意味着路径没有问题
  • 如何设置菜单按钮和菜单项的样式

    我尝试更改菜单按钮中的样式 我可以更改菜单按钮样式 但不能更改其菜单项 无论我尝试什么 菜单按钮内的菜单项都保持不变 menu button fx background color black menu button label fx ba
  • TableView,设置可编辑单元格

    我尝试使表格单元格可编辑 我设法使用其中包含字符串值的两个列来执行此操作 但我无法使用表示整数值的列来执行此操作 带有 X 的地方是编译器收到错误的地方 The method setCellFactory Callback
  • JavaFX 图表自动缩放错误且数字较低

    我正在使用 JavaFX 构建 StackedBarChart 随着新数据的进入 图表将发生变化 我使用更新图表的按钮来模拟这一点 它大部分工作正常 但我注意到 当我第一次更新图表时 如果值较低 值小于 100 Y 轴标签似乎有点偏离 更奇
  • JavaFx 无法正确渲染

    我的代码由两个类组成 一是 MainGUI java 二是 Screen java 我打算为不同的屏幕创建不同的类并在需要时渲染它们 这是我当前的代码MainGUI java import javafx application Applic

随机推荐