You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX从8迁移到21时表格单元格编辑丢失首击键的修复方案

问题:JavaFX 21可编辑表格首击键丢失(Java 8迁移后)

我们正从Java 8迁移至Java 21(包含JavaFX组件),迁移后可编辑表格出现严重问题:用户按Tab键进入单元格后开始输入,首击键会丢失,该功能在JavaFX 8中可正常工作。已知问题源于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(); 
} 

修复建议

  • 延迟焦点请求,避免事件抢占:JavaFX 11+中,Tab切换单元格的事件流会优先处理,直接调用textField.requestFocus()可能无法让TextField真正捕获后续的输入事件。修改startEdit方法中的焦点请求代码,用Platform.runLater包裹:

    // 替换原textField.requestFocus();
    Platform.runLater(() -> {
        textField.requestFocus();
        textField.selectAll(); // 确保文本被选中,首击键直接替换内容
    });
    
  • 检查事件过滤器是否消费了首击键:TextFieldNumberFilter作为KEY_TYPED事件过滤器,可能误消费了首击的数字键。确保过滤器只消费非法输入,比如:

    class TextFieldNumberFilter implements EventHandler<KeyEvent> {
        @Override
        public void handle(KeyEvent event) {
            char c = event.getCharacter().charAt(0);
            // 只消费非数字、非负号的输入(负号只能在开头)
            if (!Character.isDigit(c) && !(c == '-' && textField.getText().isEmpty())) {
                event.consume();
            }
        }
    }
    
  • 调整Tab键的处理逻辑:原代码中Tab键按下时直接提交编辑,这会干扰正常的焦点流转。改为在Tab键释放时提交,或者让焦点自然转移后再处理提交:

    // 替换原Tab键的KeyPressed处理,改用KeyReleased
    textField.setOnKeyReleased((ke) -> {
        if (ke.getCode().equals(KeyCode.TAB)) {
            if(textField.getText() != null && !StringUtility.isBlankOrNull(textField.getText())) {
                commitEdit(Long.parseLong(textField.getText()));
            }else {
                commitEdit(null);
            }
        }
    });
    
  • 确保编辑状态与TextField状态同步:在updateItem方法中,当处于编辑状态时,确保TextField的文本和选择状态正确设置,避免UI更新导致的焦点或选择丢失。

内容的提问来源于stack exchange,提问作者purring pigeon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 12:40:58