You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动