作为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获取焦点,并且把首个按键事件转发给它 - 调整编辑完成事件的触发时机,不要在对话框显示前就通知表格编辑结束
下面是修正后的核心代码部分:
- 在
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()); }); } }); }
- 修改
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; }
- 在
PopupDialog的actionPerformed方法中,对话框关闭后再通知编辑器编辑完成:
@Override public void actionPerformed(ActionEvent e) { if ("Ok".equals(e.getActionCommand())) { currentText = textArea.getText(); } setVisible(false); // 对话框关闭后才通知表格编辑完成 fireEditingStopped(); }
这样调整后,焦点能正确传递到对话框的文本区域,首个输入的字符就不会丢失了。
内容的提问来源于stack exchange,提问作者bobndrew
相关产品推荐
相关产品推荐

