如何为TableView/TreeTableView的focusLost启用提交?

cru*_*sam 13 java focus tableview javafx-8 treetableview

是否有任何简单的方法让TreeTableView(或TableView)尝试在焦点丢失时提交值?

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

我的猜测是,每当焦点丢失时,受影响的Cell的editingProperty总是为false,这导致Cell永远不会在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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

我成功地覆盖了这个方法,并在调用原始的commitEdit()方法之前"手动"提交值,但是这会导致像key这样的键提交两次提交值(在键+上丢失焦点).此外,我根本不喜欢我的方法,所以我想,如果有其他人以"更好"的方式解决了这个问题?

kle*_*tra 12

经过一番挖掘,结果证明罪魁祸首(又名:在textField失去焦点之前取消编辑的合作者)是在处理mousePressed时的TableCellBehaviour/Base:

  • mousePressed调用 simpleSelect(..)
  • 检测到它所调用的单击 edit(-1, null)
  • 它在TableView上调用相同的方法
  • 将其editingCell属性设置为null
  • tableCell监听该属性并通过取消自己的编辑来做出反应

不幸的是,hackaround需要3个合作者

  • 带有额外api的TableView可以终止编辑
  • 一个带有重写的TableCellBehaviour,simpleSelect(...)在调用super之前调用额外的api(而不是edit(-1 ..))
  • 一个配置了扩展行为并且知道表的扩展属性的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);
}
Run Code Online (Sandbox Code Playgroud)

注意:TableCellBehaviour的实现在jdk8u5和jdk8u20之间大量改变(黑客的喜悦 - 不适合生产使用;-) - 后者覆盖的方法是 handleClicks(..)

BTW:JDK-8089514的大量投票(旧版jira中的RT-18492)可能会加速核心修复.不幸的是,至少需要作者角色来对新跟踪器中的错误进行投票/评论.


key*_*eyo 5

I also needed this functionality and did some study. I faced some stability issues with XTableView hacking mentioned above.

As problem seems to be commitEdit() won't take effect when focus is lost, why you don't just call your own commit callback from TableCell as follows:

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()));
                }
            }
        });
       ...
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

In cell factory, you just store committed value to the object or do whatever necessary to make it permanent:

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;
                        });
            }
        });
Run Code Online (Sandbox Code Playgroud)

So far, I have not been able to find any problems with this workaround. On the other hand, I haven't been yet done extensive testing on this either.

EDIT: Well, after some testing noticed, the workaround was working well with big data in tableview but with empty tableview cell was not getting updated after focus lost, only when double clicking it again. There would be ways to refresh table view but that too much hacking for me...

EDIT2: Added setItem(getTextArea().getText()); before calling callback -> works with empty tableview as well.