我有一个表格单元工厂负责在JavaFX TableView中创建可编辑单元格。
我尝试在tableview中实现一些附加功能,以便当用户在可编辑单元格外部单击时进行提交(编辑后的文本将被保存,而不会根据默认的tableview行为进行丢弃。)< / p>
我添加了一个textField.focusedProperty()
事件处理程序,我从文本字段中提交了文本。但是,如果在当前单元格外部点击cancelEdit()
,则调用commitEdit(textField.getText());
无效。
我已经意识到,一旦cancelEdit()
被调用,TableCell.isEditing()
将返回false,因此提交永远不会发生。
如何在用户点击可编辑单元格外部时提交文字?
提交setOnEditCommit()
事件处理程序后,将处理验证和数据库逻辑。我还没有把它包含在这里,因为它很可能会使事情进一步复杂化。
// EditingCell - for editing capability in a TableCell
public static class EditingCell extends TableCell<Person, String> {
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<KeyEvent>() {
@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<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (!newValue) {
commitEdit(textField.getText());
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
答案 0 :(得分:5)
您可以通过覆盖方法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<S> table = getTableView();
if (table != null) {
TableColumn<S, T> column = getTableColumn();
CellEditEvent<S, T> event = new CellEditEvent<>(
table, new TablePosition<S,T>(table, getIndex(), column),
TableColumn.editCommitEvent(), item
);
Event.fireEvent(column, event);
}
}
super.commitEdit(item);
}
此解决方法来自https://gist.github.com/james-d/be5bbd6255a4640a5357#file-editcell-java-L109
答案 1 :(得分:3)
由于我找不到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<S,T> extends TableCell<S,T> {
/***************************************************************************
* *
* 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 <S> Callback<TableColumn<S,String>, TableCell<S,String>> 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 <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> forTableColumn(
final StringConverter<T> converter) {
return list -> new AcceptOnExitTableCell<S,T>(converter);
}
/***************************************************************************
* *
* Fields *
* *
**************************************************************************/
private TextField textField;
private boolean escapePressed=false;
private TablePosition<S, ?> 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<T> converter) {
this.getStyleClass().add("text-field-table-cell");
setConverter(converter);
}
/***************************************************************************
* *
* Properties *
* *
**************************************************************************/
// --- converter
private ObjectProperty<StringConverter<T>> converter =
new SimpleObjectProperty<StringConverter<T>>(this, "converter");
/**
* The {@link StringConverter} property.
*/
public final ObjectProperty<StringConverter<T>> converterProperty() {
return converter;
}
/**
* Sets the {@link StringConverter} to be used in this cell.
*/
public final void setConverter(StringConverter<T> value) {
converterProperty().set(value);
}
/**
* Returns the {@link StringConverter} used in this cell.
*/
public final StringConverter<T> 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<S> table = getTableView();
tablePos=table.getEditingCell();
}
}
/** {@inheritDoc} */
@Override public void commitEdit(T newValue) {
if (! isEditing())
return;
final TableView<S> 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();
}
}
答案 2 :(得分:0)
我是这样做的 - 我将textField文本属性与单元格的text属性绑定(双向)。
class EditingCell<S, T> extends TableCell<S, T> {
private final TextField mTextField;
public EditingCell() {
super();
mTextField = new TextField();
mTextField.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
if( event.getCode().equals(KeyCode.ENTER) )
commitEdit((T)mTextField.getText());
}
});
mTextField.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> 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());
}
}
}
}
}
答案 3 :(得分:0)
我创建了自己的解决方法(但对于JavaFX 2)。主要思想 - 将cancelEdit()转换为commitEdit()。通过验证器可以验证提交的文本。
/** Validator. */
public interface TextColumnValidator<T> {
boolean valid(T rowVal, String newVal);
}
/**
* Special table text field cell that commit its content on focus lost.
*/
public class TextFieldTableCellEx<S> extends TextFieldTableCell<S, String> {
/** */
private final TextColumnValidator<S> validator;
/** */
private boolean cancelling;
/** */
private boolean hardCancel;
/** */
private String curTxt = "";
/** Create cell factory. */
public static <S> Callback<TableColumn<S, String>, TableCell<S, String>>
cellFactory(final TextColumnValidator<S> validator) {
return new Callback<TableColumn<S, String>, TableCell<S, String>>() {
@Override public TableCell<S, String> call(TableColumn<S, String> col) {
return new TextFieldTableCellEx<>(validator);
}
};
}
/**
* Text field cell constructor.
*
* @param validator Input text validator.
*/
private TextFieldTableCellEx(TextColumnValidator<S> 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<String>() {
@Override public void changed(ObservableValue<? extends String> val, String oldVal, String newVal) {
curTxt = newVal;
}
});
tf.setOnKeyReleased(new EventHandler<KeyEvent>() {
@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<Boolean>() {
@Override public void changed(ObservableValue<? extends Boolean> 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&amp;下使用它。 Java8 / JavaFx8。
我测试过 - 它可以在两者下运行。