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

作为JTable单元格编辑器的JTextArea对话框丢失首个输入字符

解决JTable模态对话框CellEditor丢失首个输入字符的问题

我之前在给JTable做多行文本编辑的CellEditor时踩了不少坑:一开始用TableCell弹出框,结果弹窗会和右侧、下方单元格重叠,尤其是单元格在屏幕边角的时候问题特别多。后来改成用模态JDialog来编辑,虽然用户能移动对话框还能保存尺寸位置,但新问题来了——首次输入的字符总是传不到对话框里。

查了不少资料,发现很多人遇到过类似的问题,下面这个SSCCE(来自camickr的解决方案示例)就能复现第二列首次按键丢失的情况:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Frame;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

/**
 * Example taken from this answer
 *
 * @author camickr
 */
public class TablePopupEditor extends DefaultCellEditor {
    private PopupDialog popup;
    private String currentText = "";
    private JButton editorComponent1;

    public TablePopupEditor() {
        super( new JTextField() );
        setClickCountToStart( 2 );

        // Use a JButton as the editor component
        editorComponent1 = new JButton();
        editorComponent1.setBackground( Color.white );
        editorComponent1.setBorderPainted( false );
        editorComponent1.setContentAreaFilled( false );

        // Set up the dialog where we do the actual editing
        popup = new PopupDialog();
    }

    @Override
    public Object getCellEditorValue() {
        return currentText;
    }

    @Override
    public Component getTableCellEditorComponent( JTable table, Object value, boolean isSelected, int row, int column ) {
        SwingUtilities.invokeLater( new Runnable() {
            @Override
            public void run() {
                System.out.println( "run" );
                popup.setText( currentText );
                Point p = editorComponent1.getLocationOnScreen();
                popup.setLocation( p.x, p.y + editorComponent1.getSize().height );
                popup.setVisible( true );
                fireEditingStopped();
            }
        } );

        currentText = value.toString();
        editorComponent1.setText( currentText );
        return editorComponent1;
    }

    /*
     * Simple dialog containing the actual editing component
     */
    class PopupDialog extends JDialog implements ActionListener {
        private JTextArea textArea;

        public PopupDialog() {
            super( (Frame) null, "Change Description", true );
            textArea = new JTextArea( 5, 20 );
            textArea.setLineWrap( true );
            textArea.setWrapStyleWord( true );
            KeyStroke keyStroke = KeyStroke.getKeyStroke( "ENTER" );
            textArea.getInputMap().put( keyStroke, "none" );
            JScrollPane scrollPane = new JScrollPane( textArea );
            getContentPane().add( scrollPane );

            JButton cancel = new JButton( "Cancel" );
            cancel.addActionListener( this );
            JButton ok = new JButton( "Ok" );
            ok.setPreferredSize( cancel.getPreferredSize() );
            ok.addActionListener( this );

            JPanel buttons = new JPanel();
            buttons.add( ok );
            buttons.add( cancel );
            getContentPane().add( buttons, BorderLayout.SOUTH );
            pack();
            getRootPane().setDefaultButton( ok );
        }

        public void setText( String text ) {
            textArea.setText( text );
        }

        /*
         * Save the changed text before hiding the popup
         */
        @Override
        public void actionPerformed( ActionEvent e ) {
            if ( "Ok".equals( e.getActionCommand() ) ) {
                currentText = textArea.getText();
            }
            textArea.requestFocusInWindow();
            setVisible( false );
        }
    }

    public static void main( String[] args ) {
        String[] columnNames = { "Item", "Description" };
        Object[][] data = {
            { "Item 1", "Description of Item 1" },
            { "Item 2", "Description of Item 2" },
            { "Item 3", "Description of Item 3" }
        };

        JTable table = new JTable( data, columnNames );
        table.getColumnModel().getColumn( 1 ).setPreferredWidth( 300 );
        table.setPreferredScrollableViewportSize( table.getPreferredSize() );
        JScrollPane scrollPane = new JScrollPane( table );

        // Use the popup editor on the second column
        TablePopupEditor popupEditor = new TablePopupEditor();
        table.getColumnModel().getColumn( 1 ).setCellEditor( popupEditor );

        JFrame frame = new JFrame( "Popup Editor Test" );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.getContentPane().add( scrollPane );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible( true );
    }
}

问题根源与修复思路

这个问题的核心在于焦点传递的时机不对:当用户触发编辑(比如双击或按键)时,首个按键事件被JTable的单元格编辑器组件(这里是JButton)捕获了,而此时模态对话框还没完全显示并获取焦点,导致字符丢失。

修复的关键步骤:

  • 确保对话框显示后,立即让内部的JTextArea获取焦点,并且把首个按键事件转发给它
  • 调整编辑完成事件的触发时机,不要在对话框显示前就通知表格编辑结束

下面是修正后的核心代码部分:

  1. 在PopupDialog的构造函数中添加窗口监听,确保对话框打开时文本域获得焦点:
public PopupDialog() {
    // 原有代码...
    addWindowListener(new java.awt.event.WindowAdapter() {
        @Override
        public void windowOpened(java.awt.event.WindowEvent evt) {
            SwingUtilities.invokeLater(() -> {
                textArea.requestFocusInWindow();
                // 把光标定位到文本末尾,方便用户继续输入
                textArea.setCaretPosition(textArea.getText().length());
            });
        }
    });
}
  1. 修改getTableCellEditorComponent方法,延迟触发编辑完成事件:
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
    currentText = value.toString();
    editorComponent1.setText(currentText);
    
    SwingUtilities.invokeLater(() -> {
        popup.setText(currentText);
        Point p = editorComponent1.getLocationOnScreen();
        popup.setLocation(p.x, p.y + editorComponent1.getSize().height);
        popup.setVisible(true);
        // 移除这里的fireEditingStopped(),放到对话框关闭后
    });
    
    return editorComponent1;
}
  1. 在PopupDialog的actionPerformed方法中,对话框关闭后再通知编辑器编辑完成:
@Override
public void actionPerformed(ActionEvent e) {
    if ("Ok".equals(e.getActionCommand())) {
        currentText = textArea.getText();
    }
    setVisible(false);
    // 对话框关闭后才通知表格编辑完成
    fireEditingStopped();
}

这样调整后,焦点能正确传递到对话框的文本区域,首个输入的字符就不会丢失了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:04:18