最近我们开始从 Java 8 - 21 迁移,其中一部分是 JavaFX。我们的可编辑表格遇到了一个重大问题(我们有很多控件都存在这个问题)。功能应如下所示 - 当用户使用 Tab 键进入单元格,然后开始键入时,该字段应立即开始编辑。这在 Java FX8 中工作得很好,但在 21 中它失去了第一次击键 - 我已经尝试了几种方法来修复它,但没有一个能够一致地工作我很好奇是否有人解决了这个问题?我了解此问题是由于 Java 11 中事件流的更改引起的。这对我的应用程序来说是一个重大更改,在找到解决方案之前我无法迁移。
感谢您的任何建议。
public class EditableLongTableCell<T> extends TableCell<T,Long>{
private TextField textField;
private final Pattern intPattern = Pattern.compile("-?\\d+");
private ChangeListener<? super Boolean> changeListener = (obs, ov, nv) -> {
if (!nv) {
if((intPattern.matcher(textField.getText()).matches())) {
commitEdit(Long.parseLong((textField.getText())));
}
}
};
@Override
public void startEdit() {
if(editableProperty().get()){
if (!isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.requestFocus();
}
}
}
@Override
public void cancelEdit() {
super.cancelEdit();
if(getItem()!=null && !StringUtility.isBlankOrNull(getItem().toString())) {
setText(getItem().toString());
} else {
setText("");
}
setGraphic(null);
}
@Override
public void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(item.toString());
textField.selectAll();
}
setText(null);
setGraphic(textField);
} else {
if(item != null && !StringUtility.isBlankOrNull(item.toString())) {
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumFractionDigits(0);
nf.setMaximumFractionDigits(0);
setText(nf.format(item));
}else {
setText("");
}
setGraphic(null);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.addEventFilter(KeyEvent.KEY_TYPED, new TextFieldNumberFilter());
textField.setOnAction(evt -> {
Long item = null;
if(!StringUtility.isBlankOrNull(textField.getText())){
item = Long.parseLong((textField.getText()));
}
commitEdit(item);
});
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.focusedProperty().addListener(changeListener);
textField.setOnKeyPressed((ke) -> {
if (ke.getCode().equals(KeyCode.ESCAPE)) {
textField.focusedProperty().removeListener(changeListener);
cancelEdit();
}
if (ke.getCode().equals(KeyCode.TAB)) {
if(textField.getText() != null && !StringUtility.isBlankOrNull(textField.getText())) {
commitEdit(Long.parseLong(textField.getText()));
}else {
commitEdit(null);
}
}
if(ke.getCode().isLetterKey()){
ke.consume();
}
});
}
@Override
public void commitEdit(Long item) {
textField.focusedProperty().removeListener(changeListener);
if (isEditing()) {
super.commitEdit(item);
} else {
final TableView<T> table = getTableView();
if (table != null) {
TablePosition<T, Long> position = new TablePosition<T, Long>(getTableView(),
getTableRow().getIndex(), getTableColumn());
CellEditEvent<T, Long> editEvent = new CellEditEvent<T, Long>(table, position,
TableColumn.editCommitEvent(), item);
Event.fireEvent(getTableColumn(), editEvent);
}
updateItem(item, false);
if (table != null) {
table.edit(-1, null);
}
}
}
protected String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
这里有一个建议,它可以减少(但不能完全消除)文本字段上的低级事件处理量。这显着降低了事件处理与文本字段和单元格的内部事件处理交互的程度,因此应该减少未来发生重大更改的可能性。这只是一个建议,并不是完整的解决方案,但它可能足以让您继续前进。
这里实施了两种策略:
TextFormatter
(JavaFX 8u40 中引入) 实现数字文本字段。除了作为一种 API 支持的方式来过滤文本字段中的输入之外,格式化程序的值也会在文本字段上触发操作或文本字段失去焦点时提交,这提供了一种更自然的方式来触发更改底层模型。这是使用这种方法的单元实现。我还更改了此设置以延迟创建文本字段(而不是每次需要时创建一个新字段)并使用了一些更现代的 Java 习惯用法
public class EditableLongEditingCell<T> extends TableCell<T, Long> {
private TextField textField ;
private Pattern intPattern = Pattern.compile("^-?\\d*$");
private TextFormatter<Long> formatter;
private TextField getTextField() {
if (textField == null) {
textField = new TextField();
// allow only integer input:
formatter = new TextFormatter<>(
new StringConverter<>() {
@Override
public String toString(Long value) {
return value.toString();
}
@Override
public Long fromString(String s) {
return s.isBlank() ? 0 : Long.parseLong(s);
}
},
0L,
change -> {
if (intPattern.matcher(change.getControlNewText()).matches()) {
return change;
}
return null;
}
);
textField.setTextFormatter(formatter);
// commit change when formatter's value changes:
formatter.valueProperty().addListener((_, old, value) -> {
if (getTableColumn().getCellObservableValue(getTableRow().getItem()) instanceof Property<Long> p) {
p.setValue(value);
};
});
// revert text when "escape" is pressed:
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ESCAPE) {
textField.setText(formatter.getValueConverter().toString(formatter.getValue()));
}
});
}
return textField;
}
@Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(getTextField());
formatter.setValue(item);
}
}
}
为了解决外观上的变化,如果需要,请考虑在文本字段未聚焦时将其样式设置为标签。以下只是其原型(它无法与表中选定的行正确交互,但这应该是可以修复的)。
为了简单起见(这样你就不必在自己的 CSS 中复制文本字段的样式),向文本字段引入一个“unfocused”伪类:
// in the cell implementation:
private final PseudoClass unfocused = PseudoClass.getPseudoClass("unfocused");
然后在
getTextField()
方法中,当创建文本字段时,执行
// update "unfocused" CSS psuedoclass:
textField.focusedProperty().subscribe(focused ->
textField.pseudoClassStateChanged(unfocused, !focused)
);
最后,将以下内容添加到样式表中。请注意,如果您需要将其保留在代码中,您实际上可以将样式表添加到单元格中:
.table-cell .text-field:unfocused {
-fx-text-fill: -fx-text-background-color;
-fx-background-color: -fx-control-inner-background;
-fx-background-insets: 0;
-fx-background-radius: 0;
-fx-cursor: inherit;
-fx-padding: 0;
}