如何在焦点上启用提交对于 TableView/TreeTableView 丢失?

2022-09-03 14:06:01

有没有简单的方法可以让 TreeTableView(或 TableView)尝试在焦点丢失时提交值?

不幸的是,我没有成功使用javafx TableCellFactories的任何默认实现,这就是为什么我尝试了自己的TreeTableCell实现以及一些不同的tableCell实现,例如Graham Smith的实现,这似乎是最直接的,因为它已经实现了焦点丢失的钩子,但是该值从未提交并且用户更改被重置为原始值。

我的猜测是,每当焦点丢失时,受影响单元格的编辑属性总是已经是错误的,这导致单元格永远不会在focusLost上提交值。这里是原始(oracle-)TreeTableCell实现(8u20ea)的相关部分,这导致我的方法失败:

 @Override public void commitEdit(T newValue) {
        if (! isEditing()) return; // <-- here my approaches are blocked, because on focus lost its not editing anymore.

        final TreeTableView<S> table = getTreeTableView();
        if (table != null) {
            @SuppressWarnings("unchecked")
            TreeTablePosition<S,T> editingCell = (TreeTablePosition<S,T>) table.getEditingCell();

            // Inform the TableView of the edit being ready to be committed.
            CellEditEvent<S,T> editEvent = new CellEditEvent<S,T>(
                table,
                editingCell,
                TreeTableColumn.<S,T>editCommitEvent(),
                newValue
            );

            Event.fireEvent(getTableColumn(), editEvent);
        }

        // inform parent classes of the commit, so that they can switch us
        // out of the editing state.
        // This MUST come before the updateItem call below, otherwise it will
        // call cancelEdit(), resulting in both commit and cancel events being
        // fired (as identified in RT-29650)
        super.commitEdit(newValue);

        // update the item within this cell, so that it represents the new value
        updateItem(newValue, false);

        if (table != null) {
            // reset the editing cell on the TableView
            table.edit(-1, null);

            // request focus back onto the table, only if the current focus
            // owner has the table as a parent (otherwise the user might have
            // clicked out of the table entirely and given focus to something else.
            // It would be rude of us to request it back again.
            ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);
        }
    }

在调用原始 commitEdit() 方法之前,我成功地重写了此方法并“手动”提交值,但这会导致对键(如 enter)的提交提交值两次(在键上 +焦点丢失)。此外,我一点也不喜欢我的方法,所以我想知道,是否有其他人以“更好”的方式解决了这个问题?


答案 1

经过一番挖掘,发现罪魁祸首(又名:在textField失去焦点之前取消编辑的合作者)是TableCellBehaviour/Base处理鼠标Pressed:

  • 鼠标压制的呼叫simpleSelect(..)
  • 在检测到单击时,它会调用edit(-1, null)
  • 它在 TableView 上调用相同的方法
  • 这会将其编辑单元格属性设置为 null
  • tableCell 侦听该属性并通过取消其自己的编辑来做出反应

不幸的是,黑客攻击需要3个合作者

  • 具有附加 API 的表视图,用于终止编辑
  • 一个 TableCellBehaviour with overed,在调用 super 之前调用额外的 api(而不是 edit(-1..))simpleSelect(...)
  • 配置了扩展行为了解表的扩展属性的 TableCell

一些代码片段(完整代码):

// on XTableView:
public void terminateEdit() {
    if (!isEditing()) return;
    // terminatingCell is a property that supporting TableCells can listen to
    setTerminatingCell(getEditingCell());
    if (isEditing()) throw new IllegalStateException(
          "expected editing to be terminated but was " + getEditingCell());
    setTerminatingCell(null);
}

// on XTableCellBehaviour: override simpleSelect
@Override
protected void simpleSelect(MouseEvent e) {
    TableCell<S, T> cell = getControl();
    TableView<S> table = cell.getTableColumn().getTableView();
    if (table instanceof XTableView) {
        ((XTableView<S>) table).terminateEdit();
    }
    super.simpleSelect(e);
}

// on XTextFieldTableCell - this method is called from listener
// to table's terminatingCell property
protected void terminateEdit(TablePosition<S, ?> newPosition) {
    if (!isEditing() || !match(newPosition)) return;
    commitEdit();
}

protected void commitEdit() {
    T edited = getConverter().fromString(myTextField.getText());
    commitEdit(edited);
}

/**
 * Implemented to create XTableCellSkin which supports terminating edits.
 */
@Override
protected Skin<?> createDefaultSkin() {
    return new XTableCellSkin<S, T>(this);
}

注意:TableCellBehaviour的实现在jdk8u5和jdk8u20之间发生了巨大变化(黑客的乐趣 - 不适合生产用途;-) - 后者覆盖的方法是handleClicks(..)

顺便说一句:对JDK-8089514(在旧jira中是RT-18492)的大量投票可能会加快核心修复的速度。不幸的是,至少需要作者角色才能在新跟踪器中投票/评论错误。


答案 2

我还需要这个功能,并做了一些研究。我在上面提到的XTableView黑客攻击中遇到了一些稳定性问题。

由于问题似乎是 commitEdit() 在焦点丢失时不会生效,因此为什么不直接从 TableCell 调用自己的提交回调,如下所示:

public class SimpleEditingTextTableCell extends TableCell {
    private TextArea textArea;
    Callback commitChange;

    public SimpleEditingTextTableCell(Callback commitChange) {
        this.commitChange = commitChange;
    }

    @Override
    public void startEdit() {
         ...

        getTextArea().focusedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) {
                if (!arg2) {
                    //commitEdit is replaced with own callback
                    //commitEdit(getTextArea().getText());

                    //Update item now since otherwise, it won't get refreshed
                    setItem(getTextArea().getText());
                    //Example, provide TableRow and index to get Object of TableView in callback implementation
                    commitChange.call(new TableCellChangeInfo(getTableRow(), getTableRow().getIndex(), getTextArea().getText()));
                }
            }
        });
       ...
    }
    ...
}

在单元工厂中,您只需将提交的值存储到对象或执行任何必要的操作以使其永久化:

col.setCellFactory(new Callback<TableColumn<Object, String>, TableCell<Object, String>>() {
            @Override
            public TableCell<Object, String> call(TableColumn<Object, String> p) {
                return new SimpleEditingTextTableCell(cellChange -> {
                            TableCellChangeInfo changeInfo = (TableCellChangeInfo)cellChange;
                            Object obj = myTableView.getItems().get(changeInfo.getRowIndex());
                            //Save committed value to the object in tableview (and maybe to DB)
                            obj.field = changeInfo.getChangedObj().toString();
                            return true;
                        });
            }
        });

到目前为止,我还没有找到此解决方法的任何问题。另一方面,我还没有对此进行广泛的测试。

编辑:嗯,在一些测试注意到之后,解决方法是在表视图中处理大数据,但是空的表视图单元格在焦点丢失后不会更新,只有在再次双击它时才会更新。有办法刷新表格视图,但对我来说太多的黑客攻击......

EDIT2: Add setItem(getTextArea().getText());在调用回调之前 ->也适用于空表视图。