如何启用对TableView / TreeTableView focusLost提交?

是否有任何简单的方法让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); } } 

我成功地重写了这个方法,并在调用原始的commitEdit()方法之前提交了“手动”的值,但是这会导致对像键入的提交提交两次(在失去焦点的key +上)。 而且,我真的不喜欢我的方法,所以我想知道,如果有人以“更好”的方式解决了这个问题呢?

经过一番挖掘,原来罪魁祸首(也就是在textField放弃焦点之前取消编辑的合作者)是TableCellBehaviour / Base在处理mousePressed时:

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

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

  • 一个带有附加api的TableView来终止一个编辑
  • 一个重载的simpleSelect(...)的TableCellBehaviour,在调用super()之前调用附加的api(而不是编辑(-1 ..))
  • 一个TableCell,configuration了扩展的行为知道表的扩展属性

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

 // 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 (老吉拉RT-18492)的大规模投票可能会加速核心修复。 不幸的是,至less需要作者angular色来投票/评论新追踪器中的错误。

我也需要这个function,并做了一些研究。 我遇到了上面提到的XTableView黑客的一些稳定性问题。

由于问题似乎是commitEdit()在焦点丢失时不起作用,为什么不只是从TableCell调用你自己的提交callback,如下所示:

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

到目前为止,我还没有能够find任何问题,这个解决方法。 另一方面,我还没有做过这方面的广泛testing。

编辑:好,经过一些testing注意到,解决方法是在tableview中的大数据工作正常,但与空tableview单元没有得到更新后焦点丢失,只有当双击它一次。 有办法刷新表视图,但对我来说太多黑客攻击…

编辑2:增加了setItem(getTextArea()。getText()); 之前调用callback – >与空tableview以及工作。

保留这是一个愚蠢的build议。 看起来太简单了 但是,为什么不重写TableCell#cancelEdit()并在调用时手动保存这些值? 当单元格失去焦点时,总是调用cancelEdit()来取消编辑。

 class EditableCell extends TableCell<ObservableList<StringProperty>, String> { private TextField textfield = new TextField(); private int colIndex; private String originalValue = null; public EditableCell(int colIndex) { this.colIndex = colIndex; textfield.prefHeightProperty().bind(heightProperty().subtract(2.0d)); this.setPadding(new Insets(0)); this.setAlignment(Pos.CENTER); textfield.setOnAction(e -> { cancelEdit(); }); textfield.setOnKeyPressed(e -> { if (e.getCode().equals(KeyCode.ESCAPE)) { textfield.setText(originalValue); } }); } @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (isEmpty()) { setText(null); setGraphic(null); } else { if (isEditing()) { textfield.setText(item); setGraphic(textfield); setText(null); } else { setText(item); setGraphic(null); } } } @Override public void startEdit() { super.startEdit(); originalValue = getItem(); textfield.setText(getItem()); setGraphic(textfield); setText(null); } @Override public void cancelEdit() { super.cancelEdit(); setGraphic(null); setText(textfield.getText()); ObservableList<StringProperty> row = getTableView().getItems().get(getIndex()); row.get(colIndex).set(getText()); } } 

我不知道。 也许我错过了一些东西。 但它似乎为我工作。

更新:添加取消编辑function。 您现在可以通过在对焦文本字段时按Esc键取消编辑。 同时添加,以便您可以保存编辑时按下Enter键,同时聚焦文本字段。

由于TextFieldTableCell遭受了主要的function损失(如https://bugs.openjdk.java.net/browse/JDK-8089514中计算),我计划在Java 9中进行修复,所以我决定采用另一种解决scheme。 请接受我的道歉,如果这是脱靶,但在这里:

主要的想法是忘记TextFieldTableCell,并在其中使用带有TextField的自定义TableCell类。

自定义TableCell:

 public class CommentCell extends TableCell<ListItem, String> { private final TextField comment = new TextField(); public CommentCell() { this.comment.setMaxWidth( Integer.MAX_VALUE ); this.comment.setDisable( true ); this.comment.focusedProperty().addListener( new ChangeListener<Boolean>() { @Override public void changed( ObservableValue<? extends Boolean> arg0, Boolean oldPropertyValue, Boolean newPropertyValue ) { if ( !newPropertyValue ) { // Binding the TextField text to the model MainController.getInstance().setComment( getTableRow().getIndex(), comment.getText() ); } } } ); this.setGraphic( this.comment ); } @Override protected void updateItem( String s, boolean empty ) { // Checking if the TextField should be editable (based on model condition) if ( MainController.getInstance().isDependency( getTableRow().getIndex() ) ) { this.comment.setDisable( false ); this.comment.setEditable( true ); } // Setting the model value as the text for the TextField if ( s != null && !s.isEmpty() ) { this.comment.setText( s ); } } } 

UI显示可能与TextFieldTableCell不同,但至less可以提供更好的可用性: UI显示

我find了一个简单的解决scheme,只需要提供特定于数据types的列的提交function:

 TableColumn msgstr = new TableColumn("msgstr"); msgstr.setMinWidth(100); msgstr.prefWidthProperty().bind(widthProperty().divide(3)); msgstr.setCellValueFactory( new PropertyValueFactory<>("msgstr") ); msgstr.setOnEditCommit(new EventHandler<CellEditEvent<PoEntry, String>>() { @Override public void handle(CellEditEvent<PoEntry, String> t) { ((PoEntry)t.getTableView().getItems().get(t.getTablePosition().getRow())).setMsgstr(t.getNewValue()); } });