从一个单元格编辑到另一个单元格时触发有效取消 CellEditEvent 的问题

问题描述 投票:0回答:2

我正在实现一个可编辑的

TableView
,它依赖于
CellEditEvents
来取消、开始和提交事件。

在下面的例子中,城市栏是可编辑的,并且在以下情况下会触发相应的事件:

  • 取消:在文本字段中按转义键或当焦点从文本字段中丢失时。
  • 提交:在文本字段中按 Enter 键。

当我从编辑单元格移动到

RadioButton
时,开始和取消事件正确触发。但从一个单元格遍历到另一个单元格时会抛出错误。

请检查下面的 gif(步骤)和控制台输出。

On City edit start :: TableDataObj{firstName=First Name 0, lastName=Last Name 0, city=City 0}
On City edit cancel :: TableDataObj{firstName=First Name 0, lastName=Last Name 0, city=City 0}
On City edit start :: TableDataObj{firstName=First Name 1, lastName=Last Name 1, city=City 1}
On City edit cancel :: TableDataObj{firstName=First Name 1, lastName=Last Name 1, city=City 1}
On City edit start :: TableDataObj{firstName=First Name 0, lastName=Last Name 0, city=City 0}
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at javafx.scene.control.TableColumn$CellEditEvent.getTableView(TableColumn.java:772)
    at javafx.scene.control.TableColumn$CellEditEvent.getRowValue(TableColumn.java:829)
    at com.thales.javafx.tableview.CancelTableEditDemo.lambda$buildTable$7(CancelTableEditDemo.java:84)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.control.TableCell.cancelEdit(TableCell.java:400)
    at com.thales.javafx.tableview.CancelTableEditDemo$EditingCell.cancelEdit(CancelTableEditDemo.java:105)
    at javafx.scene.control.TableCell.updateEditing(TableCell.java:565)
    at javafx.scene.control.TableCell.lambda$new$26(TableCell.java:142)
    at javafx.beans.WeakInvalidationListener.invalidated(WeakInvalidationListener.java:83)
    at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:349)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.beans.property.ReadOnlyObjectWrapper$ReadOnlyPropertyImpl.fireValueChangedEvent(ReadOnlyObjectWrapper.java:176)
    at javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:142)
    at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
    at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
    at javafx.scene.control.TableView.setEditingCell(TableView.java:1145)
    at javafx.scene.control.TableView.edit(TableView.java:1457)
    at com.sun.javafx.scene.control.behavior.TableCellBehavior.edit(TableCellBehavior.java:106)
    at com.sun.javafx.scene.control.behavior.TableCellBehavior.edit(TableCellBehavior.java:38)
    at com.sun.javafx.scene.control.behavior.CellBehaviorBase.handleClicks(CellBehaviorBase.java:269)
    at com.sun.javafx.scene.control.behavior.TableCellBehaviorBase.simpleSelect(TableCellBehaviorBase.java:218)
    at com.sun.javafx.scene.control.behavior.TableCellBehaviorBase.doSelect(TableCellBehaviorBase.java:148)
    at com.sun.javafx.scene.control.behavior.CellBehaviorBase.mousePressed(CellBehaviorBase.java:150)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:95)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:352)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$355(GlassViewEventHandler.java:388)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:387)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$149(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)

我期望的是:当从 Cell-0 遍历到 Cell-1 时,它必须在开始编辑 Cell-1 之前为 Cell-0 触发有效的取消事件。

你们中的任何人都可以帮我找出我缺少的地方/什么吗?

以下是该问题的完整工作代码:

import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class CancelTableEditDemo extends Application {
    public static void main(String... a) {
        Application.launch(a);
    }

    @Override
    public void start(final Stage primaryStage) throws Exception {
        final ObservableList<TableDataObj> items = FXCollections.observableArrayList();
        final int no = 2;
        for (int i = 0; i < no; i++) {
            final String firstName = "First Name " + i;
            final String lastName = "Last Name " + i;
            final String city = "City " + i;
            items.add(new TableDataObj(i, firstName, lastName, city));
        }

        final TableView<TableDataObj> table = buildTable();
        table.setItems(items);

        final VBox root = new VBox(new RadioButton("Use this for focus changing"), table);
        root.setSpacing(10);
        root.setPadding(new Insets(10));
        VBox.setVgrow(table, Priority.ALWAYS);

        final Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Cancel Table Edit Demo");
        primaryStage.show();
    }

    @SuppressWarnings("unchecked")
    private TableView<TableDataObj> buildTable() {
        final TableView<TableDataObj> tableView = new TableView<>();
        tableView.setEditable(true);
        final TableColumn<TableDataObj, Integer> idCol = new TableColumn<>();
        idCol.setText("Id");
        idCol.setCellValueFactory(param -> param.getValue().idProperty().asObject());

        final TableColumn<TableDataObj, String> fnCol = new TableColumn<>();
        fnCol.setText("First Name");
        fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
        fnCol.setPrefWidth(150);

        final TableColumn<TableDataObj, String> lnCol = new TableColumn<>();
        lnCol.setText("Last Name");
        lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());
        lnCol.setPrefWidth(150);

        final TableColumn<TableDataObj, String> cityCol = new TableColumn<>();
        cityCol.setEditable(true);
        cityCol.setText("City");
        cityCol.setCellValueFactory(param -> param.getValue().cityProperty());
        cityCol.setPrefWidth(150);
        cityCol.setCellFactory(param -> {
            final EditingCell<TableDataObj, String> cell = new EditingCell<>();
            cell.setOnMouseClicked(e -> {
                tableView.edit(cell.getTableRow().getIndex(), cityCol);
            });
            return cell;
        });
        cityCol.setOnEditStart(e -> {
            System.out.println("On City edit start :: " + e.getRowValue());
        });
        cityCol.setOnEditCancel(e -> {
            System.out.println("On City edit cancel :: " + e.getRowValue());
        });
        cityCol.setOnEditCommit(e -> {
            System.out.println("On City edit commit :: val : " + e.getNewValue() + " :: " + e.getRowValue());
            e.getRowValue().setCity(e.getNewValue());
        });
        tableView.getColumns().addAll(idCol, fnCol, lnCol, cityCol);
        return tableView;
    }

    /**
     * Editing Cell
     */
    class EditingCell<T, S> extends TableCell<T, S> {

        private TextField textField;

        @Override
        public void cancelEdit() {
            super.cancelEdit();
            updateItem(getItem(), getItem() == null);
        }

        @Override
        public void commitEdit(final S newValue) {
            super.commitEdit(newValue);
        }

        @Override
        public void startEdit() {
            super.startEdit();
            updateItem(getItem(), getItem() == null);
            textField.selectAll();
            textField.requestFocus();
        }

        @Override
        public void updateItem(final S item, final boolean empty) {
            super.updateItem(item, empty);
            if (empty) {
                setText(null);
                setGraphic(textField);
            } else {
                if (isEditing()) {
                    if (textField == null) {
                        createTextField();
                    }
                    textField.setText(getString());
                    setGraphic(textField);
                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                } else {
                    setText(item != null ? item.toString() : "");
                    setContentDisplay(ContentDisplay.TEXT_ONLY);
                }
            }
        }

        private void createTextField() {
            textField = new TextField(getString());
            textField.setMinWidth(getWidth() - getGraphicTextGap() * 2);

            textField.setOnKeyPressed(keyEvent -> {
                if (keyEvent.getCode() == KeyCode.ESCAPE) {
                    cancelEdit();
                    keyEvent.consume();
                } else if (keyEvent.getCode() == KeyCode.ENTER) {
                    commitEdit((S) textField.getText()); // For now casting directly for testing
                    keyEvent.consume();
                }
            });

            /* Cancel edit when loosing focus. */
            textField.focusedProperty().addListener((obs, prevFocus, focused) -> {
                if (!focused) {
                    cancelEdit();
                }
            });
        }

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

    /**
     * Data object.
     */
    class TableDataObj {
        private final IntegerProperty id = new SimpleIntegerProperty();
        private final StringProperty firstName = new SimpleStringProperty();
        private final StringProperty lastName = new SimpleStringProperty();
        private final StringProperty city = new SimpleStringProperty();

        public TableDataObj(final int i, final String fn, final String ln, final String cty) {
            setId(i);
            setFirstName(fn);
            setLastName(ln);
            setCity(cty);
        }

        public StringProperty cityProperty() {
            return city;
        }

        public StringProperty firstNameProperty() {
            return firstName;
        }

        public String getCity() {
            return city.get();
        }

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

        public int getId() {
            return id.get();
        }

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

        public IntegerProperty idProperty() {
            return id;
        }

        public StringProperty lastNameProperty() {
            return lastName;
        }

        public void setCity(final String city1) {
            city.set(city1);
        }

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

        public void setId(final int idA) {
            id.set(idA);
        }

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

        @Override
        public String toString() {
            return "TableDataObj{" +
                    "firstName=" + firstName.get() +
                    ", lastName=" + lastName.get() +
                    ", city=" + city.get() +
                    '}';
        }
    }
}
javafx tableview javafx-8
2个回答
0
投票

好吧..因为我必须寻找解决方法,直到升级到 JavaFX 17,以下是我提出的更改(针对 JavaFX 8):

首先,在 onCancelEdit 事件处理程序中添加对 TablePosition 的空检查,以确保不会因内部错误而引发错误。

cityCol.setOnEditCancel(e -> {
    if (e.getTablePosition() != null) {
        System.out.println("On City edit cancel :: " + e.getRowValue());
    }
});

其次,为了触发正确的取消事件,我在条件不正确时显式地触发取消事件。

@Override
public void cancelEdit() {
    TablePosition<T, ?> editingCell = getTableView().getEditingCell();
    super.cancelEdit();
    // If the editingCell is null, then the editCancelEvent fired in super method has no impact. So explicitly firing a valid editCancelEvent.
    if (editingCell == null) {
        final TablePosition<T, S> pos = new TablePosition<>(getTableView(), getTableRow().getIndex(), getTableColumn());
        Event.fireEvent(getTableColumn(), new TableColumn.CellEditEvent<>(getTableView(), pos, TableColumn.editCancelEvent(), null));
    }
    setText(getItem() != null ? getItem().toString() : "");
    setContentDisplay(ContentDisplay.TEXT_ONLY);
}

包含更改的完整工作演示如下:

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class CancelTableEditDemo extends Application {
    public static void main(String... a) {
        Application.launch(a);
    }

    @Override
    public void start(final Stage primaryStage) throws Exception {
        final ObservableList<TableDataObj> items = FXCollections.observableArrayList();
        final int no = 2;
        for (int i = 0; i < no; i++) {
            final String firstName = "First Name " + i;
            final String lastName = "Last Name " + i;
            final String city = "City " + i;
            items.add(new TableDataObj(i, firstName, lastName, city));
        }

        final TableView<TableDataObj> table = buildTable();
        table.setItems(items);

        final VBox root = new VBox(new RadioButton("Use this for focus changing"), table);
        root.setSpacing(10);
        root.setPadding(new Insets(10));
        VBox.setVgrow(table, Priority.ALWAYS);

        final Scene sc = new Scene(root);
        primaryStage.setScene(sc);
        primaryStage.setTitle("Cancel Table Edit Demo");
        primaryStage.show();
    }

    @SuppressWarnings("unchecked")
    private TableView<TableDataObj> buildTable() {
        final TableView<TableDataObj> tableView = new TableView<>();
        tableView.setEditable(true);
        final TableColumn<TableDataObj, Integer> idCol = new TableColumn<>();
        idCol.setText("Id");
        idCol.setCellValueFactory(param -> param.getValue().idProperty().asObject());

        final TableColumn<TableDataObj, String> fnCol = new TableColumn<>();
        fnCol.setText("First Name");
        fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
        fnCol.setPrefWidth(150);

        final TableColumn<TableDataObj, String> lnCol = new TableColumn<>();
        lnCol.setText("Last Name");
        lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());
        lnCol.setPrefWidth(150);

        final TableColumn<TableDataObj, String> cityCol = new TableColumn<>();
        cityCol.setEditable(true);
        cityCol.setText("City");
        cityCol.setCellValueFactory(param -> param.getValue().cityProperty());
        cityCol.setPrefWidth(150);
        cityCol.setCellFactory(param -> {
            final EditingCell<TableDataObj, String> cell = new EditingCell<>();
            cell.setOnMouseClicked(e -> {
                tableView.edit(cell.getTableRow().getIndex(), cityCol);
            });
            return cell;
        });
        cityCol.setOnEditStart(e -> {
            System.out.println("On City edit start :: " + e.getRowValue());
        });
        cityCol.setOnEditCancel(e -> {
            if (e.getTablePosition() != null) {
                System.out.println("On City edit cancel :: " + e.getRowValue());
            }
        });
        cityCol.setOnEditCommit(e -> {
            System.out.println("On City edit commit :: val : " + e.getNewValue() + " :: " + e.getRowValue());
            e.getRowValue().setCity(e.getNewValue());
        });
        tableView.getColumns().addAll(idCol, fnCol, lnCol, cityCol);
        return tableView;
    }

    /**
     * Editing Cell
     */
    class EditingCell<T, S> extends TableCell<T, S> {

        private TextField textField;

        @Override
        public void cancelEdit() {
            TablePosition<T, ?> editingCell = getTableView().getEditingCell();
            super.cancelEdit();
            // If the editingCell is null, then the editCancelEvent fired in super method has no impact. So explicitly firing a valid editCancelEvent.
            if (editingCell == null) {
                final TablePosition<T, S> pos = new TablePosition<>(getTableView(), getTableRow().getIndex(), getTableColumn());
                Event.fireEvent(getTableColumn(), new TableColumn.CellEditEvent<>(getTableView(), pos, TableColumn.editCancelEvent(), null));
            }
            setText(getItem() != null ? getItem().toString() : "");
            setContentDisplay(ContentDisplay.TEXT_ONLY);
        }

        @Override
        public void startEdit() {
            super.startEdit();
            if (textField == null) {
                createTextField();
            }
            textField.setText(getString());
            setGraphic(textField);
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            textField.selectAll();
            textField.requestFocus();
        }

        @Override
        public void updateItem(final S item, final boolean empty) {
            super.updateItem(item, empty);
            setGraphic(null);
            if (empty) {
                setText(null);
            } else {
                setText(item != null ? item.toString() : "");
                setContentDisplay(ContentDisplay.TEXT_ONLY);
            }
        }

        private void createTextField() {
            textField = new TextField(getString());
            textField.setMinWidth(getWidth() - getGraphicTextGap() * 2);

            textField.setOnKeyPressed(keyEvent -> {
                if (keyEvent.getCode() == KeyCode.ESCAPE) {
                    cancelEdit();
                    keyEvent.consume();
                } else if (keyEvent.getCode() == KeyCode.ENTER) {
                    commitEdit((S) textField.getText()); // For now casting directly for testing
                    keyEvent.consume();
                }
            });

            /* Cancel edit when loosing focus. */
            textField.focusedProperty().addListener((obs, prevFocus, focused) -> {
                if (!focused && isEditing()) {
                    cancelEdit();
                }
            });
        }

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

    /**
     * Data object.
     */
    class TableDataObj {
        private final IntegerProperty id = new SimpleIntegerProperty();
        private final StringProperty firstName = new SimpleStringProperty();
        private final StringProperty lastName = new SimpleStringProperty();
        private final StringProperty city = new SimpleStringProperty();

        public TableDataObj(final int i, final String fn, final String ln, final String cty) {
            setId(i);
            setFirstName(fn);
            setLastName(ln);
            setCity(cty);
        }

        public StringProperty cityProperty() {
            return city;
        }

        public StringProperty firstNameProperty() {
            return firstName;
        }

        public String getCity() {
            return city.get();
        }

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

        public int getId() {
            return id.get();
        }

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

        public IntegerProperty idProperty() {
            return id;
        }

        public StringProperty lastNameProperty() {
            return lastName;
        }

        public void setCity(final String city1) {
            city.set(city1);
        }

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

        public void setId(final int idA) {
            id.set(idA);
        }

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

        @Override
        public String toString() {
            return "TableDataObj{" +
                    "firstName=" + firstName.get() +
                    ", lastName=" + lastName.get() +
                    ", city=" + city.get() +
                    '}';
        }
    }
}

0
投票

1。避免使用 Platform.runLater(()-> "calls") !

如果单元格方法未触发,请重新考虑使用 table/tree.getSelectionModel().clearSelection()。我遇到过一种情况,删除这个调用被证明是有益的。在我的场景中,我向模型添加了一个新的 TreeItem 并希望立即对其进行编辑。虽然设置焦点和选择索引是必要的,但在这种情况下省略clearSelection()调用是有效的。

2。确保 table/tree.layout() 和 edit() 之间没有其他语句,例如刷新()或类似函数。将布局()放在编辑()之前的行上以避免任何干扰!

为了正确排序,请记住在添加新项目之前调用clearSelection();否则排序后可能选择的索引不正确。但是,包含此调用会导致 edit() 未按预期启动编辑模式。但这并不是我的案例没有启动的真正原因。这是因为我有一个侦听器来监听 TreeView 选择的变化,从而将新的选择记录到选择历史记录中。反过来,该历史记录有一个监听列表中更改的监听器,然后它会调用 treeView.requestFocus(),突然结束编辑模式。由于我没有收到任何错误消息,因此很难识别。

3.因此,请留意当选择发生变化时操纵焦点的听众,尤其是通过添加或删除项目!

© www.soinside.com 2019 - 2024. All rights reserved.