在JavaFX中单击外部可编辑的TableView单元格时如何提交?

我有一个表格单元工厂负责在JavaFX TableView中创建可编辑单元格。

我正在尝试为tableview实现一些附加function,以便当用户在可编辑单元格外部单击时进行提交(编辑的文本将被保存,而不会根据默认的tableview行为进行丢弃。)

我添加了一个textField.focusedProperty()事件处理程序,我从文本字段提交文本。 但是,当在当前单元cancelEdit()单击时,将调用cancelEdit()并调用commitEdit(textField.getText()); 没有效果。

我已经意识到,一旦调用了cancelEdit()TableCell.isEditing()将返回false,因此提交永远不会发生。

如何在用户点击可编辑单元格外部时提交文本?

提交setOnEditCommit()事件处理程序后,将处理validation和数据库逻辑。 我没有把它包括在这里,因为它很可能会使事情进一步复杂化。

 // EditingCell - for editing capability in a TableCell public static class EditingCell extends TableCell { private TextField textField; public EditingCell() { } @Override public void startEdit() { super.startEdit(); if (textField == null) { createTextField(); } setText(null); setGraphic(textField); 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); textField.setOnKeyReleased(new EventHandler() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.ENTER) { commitEdit(textField.getText()); } else if (t.getCode() == KeyCode.ESCAPE) { cancelEdit(); } } }); textField.focusedProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { if (!newValue) { commitEdit(textField.getText()); } } }); } private String getString() { return getItem() == null ? "" : getItem().toString(); } } 

你可以通过覆盖下一个方法commitEdit来做到这一点:

 @Override public void commitEdit(T item) { // This block is necessary to support commit on losing focus, because // the baked-in mechanism sets our editing state to false before we can // intercept the loss of focus. The default commitEdit(...) method // simply bails if we are not editing... if (!isEditing() && !item.equals(getItem())) { TableView table = getTableView(); if (table != null) { TableColumn column = getTableColumn(); CellEditEvent event = new CellEditEvent<>( table, new TablePosition(table, getIndex(), column), TableColumn.editCommitEvent(), item ); Event.fireEvent(column, event); } } super.commitEdit(item); } 

此解决方法来自https://gist.github.com/james-d/be5bbd6255a4640a5357#file-editcell-java-L109

由于我找不到kuaw26的源代码(死链接),我为java 8开发了自己的解决方案。我发现上面代码中的TextField从未收到esc-key的keyReleased事件,因此他的代码不起作用。

不幸的是,我需要从TextFieldTableCell和CellUtils复制代码并对其进行调整,因为TextFieldTableCell使用私有TextField而CellUtils受包保护。 这可能不是最好的OO方式。

这是我的解决方案:

 // package yourLib; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.event.Event; 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.input.KeyCode; import javafx.util.Callback; import javafx.util.StringConverter; import javafx.util.converter.DefaultStringConverter; /** * A class containing a {@link TableCell} implementation that draws a * {@link TextField} node inside the cell. If the TextField is * left, the value is commited. * */ public class AcceptOnExitTableCell extends TableCell { /*************************************************************************** * * * Static cell factories * * * **************************************************************************/ /** * Provides a {@link TextField} that allows editing of the cell content when * the cell is double-clicked, or when * {@link TableView#edit(int, javafx.scene.control.TableColumn)} is called. * This method will only work on {@link TableColumn} instances which are of * type String. * * @return A {@link Callback} that can be inserted into the * {@link TableColumn#cellFactoryProperty() cell factory property} of a * TableColumn, that enables textual editing of the content. */ public static  Callback, TableCell> forTableColumn() { return forTableColumn(new DefaultStringConverter()); } /** * Provides a {@link TextField} that allows editing of the cell content when * the cell is double-clicked, or when * {@link TableView#edit(int, javafx.scene.control.TableColumn) } is called. * This method will work on any {@link TableColumn} instance, regardless of * its generic type. However, to enable this, a {@link StringConverter} must * be provided that will convert the given String (from what the user typed * in) into an instance of type T. This item will then be passed along to the * {@link TableColumn#onEditCommitProperty()} callback. * * @param converter A {@link StringConverter} that can convert the given String * (from what the user typed in) into an instance of type T. * @return A {@link Callback} that can be inserted into the * {@link TableColumn#cellFactoryProperty() cell factory property} of a * TableColumn, that enables textual editing of the content. */ public static  Callback, TableCell> forTableColumn( final StringConverter converter) { return list -> new AcceptOnExitTableCell(converter); } /*************************************************************************** * * * Fields * * * **************************************************************************/ private TextField textField; private boolean escapePressed=false; private TablePosition tablePos=null; /*************************************************************************** * * * Constructors * * * **************************************************************************/ /** * Creates a default TextFieldTableCell with a null converter. Without a * {@link StringConverter} specified, this cell will not be able to accept * input from the TextField (as it will not know how to convert this back * to the domain object). It is therefore strongly encouraged to not use * this constructor unless you intend to set the converter separately. */ public AcceptOnExitTableCell() { this(null); } /** * Creates a TextFieldTableCell that provides a {@link TextField} when put * into editing mode that allows editing of the cell content. This method * will work on any TableColumn instance, regardless of its generic type. * However, to enable this, a {@link StringConverter} must be provided that * will convert the given String (from what the user typed in) into an * instance of type T. This item will then be passed along to the * {@link TableColumn#onEditCommitProperty()} callback. * * @param converter A {@link StringConverter converter} that can convert * the given String (from what the user typed in) into an instance of * type T. */ public AcceptOnExitTableCell(StringConverter converter) { this.getStyleClass().add("text-field-table-cell"); setConverter(converter); } /*************************************************************************** * * * Properties * * * **************************************************************************/ // --- converter private ObjectProperty> converter = new SimpleObjectProperty>(this, "converter"); /** * The {@link StringConverter} property. */ public final ObjectProperty> converterProperty() { return converter; } /** * Sets the {@link StringConverter} to be used in this cell. */ public final void setConverter(StringConverter value) { converterProperty().set(value); } /** * Returns the {@link StringConverter} used in this cell. */ public final StringConverter getConverter() { return converterProperty().get(); } /*************************************************************************** * * * Public API * * * **************************************************************************/ /** {@inheritDoc} */ @Override public void startEdit() { if (! isEditable() || ! getTableView().isEditable() || ! getTableColumn().isEditable()) { return; } super.startEdit(); if (isEditing()) { if (textField == null) { textField = getTextField(); } escapePressed=false; startEdit(textField); final TableView table = getTableView(); tablePos=table.getEditingCell(); } } /** {@inheritDoc} */ @Override public void commitEdit(T newValue) { if (! isEditing()) return; final TableView table = getTableView(); if (table != null) { // Inform the TableView of the edit being ready to be committed. CellEditEvent editEvent = new CellEditEvent( table, tablePos, TableColumn.editCommitEvent(), newValue ); Event.fireEvent(getTableColumn(), editEvent); } // we need to setEditing(false): super.cancelEdit(); // this fires an invalid EditCancelEvent. // 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. // requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table); } } /** {@inheritDoc} */ @Override public void cancelEdit() { if(escapePressed) { // this is a cancel event after escape key super.cancelEdit(); setText(getItemText()); // restore the original text in the view } else { // this is not a cancel event after escape key // we interpret it as commit. String newText=textField.getText(); // get the new text from the view this.commitEdit(getConverter().fromString(newText)); // commit the new text to the model } setGraphic(null); // stop editing with TextField } /** {@inheritDoc} */ @Override public void updateItem(T item, boolean empty) { super.updateItem(item, empty); updateItem(); } /*************************************************************************** * * * // djw code taken and adapted from package protected CellUtils. * * * **************************************************************************/ private TextField getTextField() { final TextField textField = new TextField(getItemText()); // Use onAction here rather than onKeyReleased (with check for Enter), // as otherwise we encounter RT-34685 textField.setOnAction(event -> { if (converter == null) { throw new IllegalStateException( "Attempting to convert text input into Object, but provided " + "StringConverter is null. Be sure to set a StringConverter " + "in your cell factory."); } this.commitEdit(getConverter().fromString(textField.getText())); event.consume(); }); textField.setOnKeyPressed(t -> { if (t.getCode() == KeyCode.ESCAPE) escapePressed = true; else escapePressed = false; }); textField.setOnKeyReleased(t -> { if (t.getCode() == KeyCode.ESCAPE) { // djw the code may depend on java version / expose incompatibilities: throw new IllegalArgumentException("did not expect esc key releases here."); } }); return textField; } private String getItemText() { return getConverter() == null ? getItem() == null ? "" : getItem().toString() : getConverter().toString(getItem()); } private void updateItem() { if (isEmpty()) { setText(null); setGraphic(null); } else { if (isEditing()) { if (textField != null) { textField.setText(getItemText()); } setText(null); setGraphic(textField); } else { setText(getItemText()); setGraphic(null); } } } private void startEdit(final TextField textField) { if (textField != null) { textField.setText(getItemText()); } setText(null); setGraphic(textField); textField.selectAll(); // requesting focus so that key input can immediately go into the // TextField (see RT-28132) textField.requestFocus(); } } 

这是我如何做到的 – 我将textField的text属性与单元格的text属性(双向)绑定在一起。

 class EditingCell extends TableCell { private final TextField mTextField; public EditingCell() { super(); mTextField = new TextField(); mTextField.setOnKeyPressed(new EventHandler() { @Override public void handle(KeyEvent event) { if( event.getCode().equals(KeyCode.ENTER) ) commitEdit((T)mTextField.getText()); } }); mTextField.focusedProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { if( !newValue ) commitEdit((T)mTextField.getText()); } }); mTextField.textProperty().bindBidirectional(textProperty()); } @Override public void startEdit() { super.startEdit(); setGraphic(mTextField); } @Override public void cancelEdit() { super.cancelEdit(); setGraphic(null); } @Override public void updateItem(final T item, final boolean empty) { super.updateItem(item, empty); if( empty ) { setText(null); setGraphic(null); } else { if( item == null ) { setGraphic(null); } else { if( isEditing() ) { setGraphic(mTextField); setText((String)getItem()); } else { setGraphic(null); setText((String)getItem()); } } } } } 

我创建了自己的解决方法(但对于JavaFX 2)。 主要思想 – 将cancelEdit()转换为commitEdit()。 通过validation器可以validation提交的文本。

 /** Validator. */ public interface TextColumnValidator { boolean valid(T rowVal, String newVal); } /** * Special table text field cell that commit its content on focus lost. */ public class TextFieldTableCellEx extends TextFieldTableCell { /** */ private final TextColumnValidator validator; /** */ private boolean cancelling; /** */ private boolean hardCancel; /** */ private String curTxt = ""; /** Create cell factory. */ public static  Callback, TableCell> cellFactory(final TextColumnValidator validator) { return new Callback, TableCell>() { @Override public TableCell call(TableColumn col) { return new TextFieldTableCellEx<>(validator); } }; } /** * Text field cell constructor. * * @param validator Input text validator. */ private TextFieldTableCellEx(TextColumnValidator validator) { this.validator = validator; } /** {@inheritDoc} */ @Override public void startEdit() { super.startEdit(); curTxt = ""; hardCancel = false; Node g = getGraphic(); if (g != null) { final TextField tf = (TextField)g; tf.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue val, String oldVal, String newVal) { curTxt = newVal; } }); tf.setOnKeyReleased(new EventHandler() { @Override public void handle(KeyEvent evt) { if (KeyCode.ENTER == evt.getCode()) cancelEdit(); else if (KeyCode.ESCAPE == evt.getCode()) { hardCancel = true; cancelEdit(); } } }); // Special hack for editable TextFieldTableCell. // Cancel edit when focus lost from text field, but do not cancel if focus lost to VirtualFlow. tf.focusedProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue val, Boolean oldVal, Boolean newVal) { Node fo = getScene().getFocusOwner(); if (!newVal) { if (fo instanceof VirtualFlow) { if (fo.getParent().getParent() != getTableView()) cancelEdit(); } else cancelEdit(); } } }); Platform.runLater(new Runnable() { @Override public void run() { tf.requestFocus(); } }); } } /** {@inheritDoc} */ @Override public void cancelEdit() { if (cancelling) super.cancelEdit(); else try { cancelling = true; if (hardCancel || curTxt.trim().isEmpty()) super.cancelEdit(); else if (validator.valid(getTableView().getSelectionModel().getSelectedItem(), curTxt)) commitEdit(curTxt); else super.cancelEdit(); } finally { cancelling = false; } } } 

更新:此代码是作为Apache Ignite Schema Import GUI Utility的一部分编写的。 查看TableCell代码的完整版: https : //github.com/apache/ignite/blob/ignite-1.9/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/Controls.java

你也可以构建这个实用程序(这是一个非常简单的实用程序,有2个屏幕),并在Java7 / javaFx2和Java8 / JavaFx8下使用它。

我测试过 – 它在两者下运行。