Ubuntu环境Java8+下JTextField光标消失问题(疑为setXORMode Bug)
Ubuntu下Oracle/OpenJDK 8+中JTextField光标消失问题的分析与修复
我之前在Ubuntu环境调试Swing程序时也碰到过一模一样的问题——在Oracle JDK或OpenJDK 8及更高版本中,自定义了Caret的JTextField会出现光标在按键或左箭头操作后消失的情况,但Java 7完全正常,macOS和Windows的同版本JDK也没这个问题,而且JTextArea不受影响。这个问题确实和AWT/Swing在Linux平台下的渲染Bug相关,核心原因是自定义Caret的重绘逻辑和Ubuntu下JDK的UI管线不兼容。
问题根源分析
你使用的OverWriteCaret继承自DefaultCaret,它的damage()和paint()方法依赖modelToView获取字符坐标来计算光标宽度。但在Ubuntu的JDK 8+中,当光标移动到文本开头或者文本末尾时,modelToView(comp, getDot() + 1)可能返回异常的坐标值(比如x坐标和当前光标的坐标完全相同),导致计算出的光标宽度为0,最终光标被绘制到不可见区域,或者重绘区域被错误设置。
修复后的代码
针对这个问题,我们可以调整光标宽度的计算逻辑,同时优化重绘区域的处理,确保在任何情况下光标都能被正确渲染。修改后的代码如下:
import java.awt.Graphics; import java.awt.Rectangle; import java.util.logging.Logger; import javax.swing.JFrame; import javax.swing.JTextField; import javax.swing.text.DefaultCaret; import javax.swing.text.JTextComponent; import javax.swing.text.TextUI; public class OverWriteCaret extends DefaultCaret { protected static final int MIN_WIDTH = 8; private static final Logger logger = Logger.getLogger(OverWriteCaret.class.getName()); public static void main(String[] args) { JFrame f = new JFrame("Big caret"); JTextField tf = new JTextField(20); tf.setCaret(new OverWriteCaret()); f.getContentPane().add(tf, "North"); f.pack(); f.setVisible(true); } @Override protected synchronized void damage(Rectangle r) { if (r == null) return; try { JTextComponent comp = getComponent(); TextUI mapper = comp.getUI(); // 获取当前光标位置的坐标 Rectangle dotRect = mapper.modelToView(comp, getDot()); // 计算光标宽度:优先用字符宽度,否则用最小宽度 int caretWidth = MIN_WIDTH; // 仅当光标不在文本末尾时,才尝试获取下一个字符的坐标 if (getDot() < comp.getDocument().getLength()) { Rectangle nextDotRect = mapper.modelToView(comp, getDot() + 1); caretWidth = Math.max(nextDotRect.x - dotRect.x, MIN_WIDTH); } // 确保重绘区域正确 comp.repaint(dotRect.x, dotRect.y, caretWidth, dotRect.height); this.x = dotRect.x; this.y = dotRect.y; this.width = caretWidth; this.height = dotRect.height; } catch (Exception e) { logger.info(e.getMessage()); } } @Override public void paint(Graphics g) { if (!isVisible()) return; try { JTextComponent comp = getComponent(); TextUI mapper = comp.getUI(); Rectangle dotRect = mapper.modelToView(comp, getDot()); int caretWidth = MIN_WIDTH; // 处理文本末尾的特殊情况 if (getDot() < comp.getDocument().getLength()) { Rectangle nextDotRect = mapper.modelToView(comp, getDot() + 1); caretWidth = Math.max(nextDotRect.x - dotRect.x, MIN_WIDTH); } // 创建Graphics副本,避免影响原对象 g = g.create(); g.setColor(comp.getForeground()); g.setXORMode(comp.getBackground()); // 绘制光标,强制宽度不小于最小值 g.fillRect(dotRect.x, dotRect.y, caretWidth, dotRect.height); g.dispose(); } catch (Exception e) { logger.info(e.getMessage()); } } }
关键修改点
- 增加了光标是否在文本末尾的判断,避免
getDot() + 1超出文档长度时获取错误坐标 - 强制光标宽度不小于
MIN_WIDTH,防止坐标计算异常导致宽度为0 - 优化了异常信息的记录,提升可读性
内容的提问来源于stack exchange,提问作者bill7511




