Java Swing JFrame内JPanel绘制Image图片不显示问题
问题原因
- 图片加载逻辑不可靠:
Toolkit.getDefaultToolkit().getImage()是异步非阻塞方法,调用后会立刻返回,不会等待图片实际加载完成,且代码中加载图片、字体时的异常被空捕获,一旦文件路径错误、加载失败,只会返回null,没有任何报错提示,完全无法定位问题。同时你把图片加载逻辑放在paint方法内,每次界面重绘都会重新触发加载,性能差且极易加载失败。 - Swing绘制写法不规范:直接重写
JPanel的paint方法且未调用super.paint(g),破坏了Swing原生的组件绘制链路,很容易出现渲染异常、元素不显示的问题。Swing自定义绘制标准写法是重写paintComponent方法,且第一行必须调用父类方法完成基础绘制。 - 窗口初始化顺序错误:你在往
JFrame添加自定义棋盘面板之前就调用了setVisible(true),还重复调用了两次该方法,会导致首次界面渲染时组件层级未初始化完成。另外手动给面板设置bounds的写法不符合Swing布局管理器逻辑,且绘制格子尺寸时直接取JFrame的宽高,没有排除窗口边框、标题栏占用的像素,坐标计算容易偏移。 - 绘制逻辑存在小bug:绘制左上角棋子时y方向坐标用了
screen.getWidth()/8而非高度值计算,虽然y=0时暂时看不出问题,但属于逻辑隐患。
修复方案
- 替换图片加载方式,提前缓存图片资源
用ImageIO.read()做同步阻塞加载,启动时就把棋子图片加载到内存缓存,加载失败直接打印异常栈方便排查,资源读取用类加载器路径,避免工作目录变化导致找不到文件:public static final class Rook extends Piece{ private static BufferedImage whiteRook; private static BufferedImage blackRook; // 类加载时就初始化图片,只加载一次 static { try { // 把两张棋子png放到项目src根目录,编译后会自动输出到类路径根目录 whiteRook = ImageIO.read(Rook.class.getResourceAsStream("/75_px_white_rook.png")); blackRook = ImageIO.read(Rook.class.getResourceAsStream("/75_px_black_rook.png")); } catch (IOException e) { // 禁止空捕获异常,打印堆栈方便定位路径、加载错误 e.printStackTrace(); } } public static BufferedImage getWhiteRook(){ return whiteRook; } public BufferedImage getBlackRook(){ return blackRook; } } - 调整窗口初始化顺序,所有组件配置完成后再显示窗口
删掉createNewBoard方法里提前写的两次setVisible(true)调用,等所有组件添加、参数配置完成后,最后再调用pack()和setVisible(true)。删掉手动给棋盘面板设置bounds的代码,改用setPreferredSize指定面板尺寸,交给布局管理器自动适配。 - 修正自定义面板的绘制逻辑
把重写paint改成重写paintComponent,第一行调用super.paintComponent(graphic),格子尺寸用面板自身的宽高计算(不要用JFrame的宽高),提前在面板初始化时拿到棋子图片引用,不要在绘制循环里反复加载:JPanel board = new JPanel(){ boolean color = true; private final Image whiteRook = Piece.Rook.getWhiteRook(); @Override protected void paintComponent(Graphics graphic) { super.paintComponent(graphic); graphic.setFont(new Font("AlphaRomaineFont.ttf", Font.PLAIN, 18)); // 基于当前面板尺寸计算格子大小,避免边框、标题栏导致尺寸偏移 int cellWidth = getWidth() / 8; int cellHeight = getHeight() / 8; for(int y = 0; y < 8; y++){ for(int x = 0; x < 8; x++){ graphic.setColor(color ? Color.decode("#f5f1e6") : Color.decode("#449e48")); graphic.fillRect(x*cellWidth, y*cellHeight, cellWidth, cellHeight); if(y == 7){ graphic.setColor(Color.BLACK); graphic.drawString(String.valueOf(x_AxisMap.get(x)), x*cellWidth+3, y*cellHeight+87); graphic.setColor(color ? Color.decode("#f5f1e6") : Color.decode("#449e48")); } if(x == 7){ graphic.setColor(Color.BLACK); graphic.drawString(String.valueOf(y_AxisMap.get(y)), x*cellWidth+80, y*cellHeight+15); graphic.setColor(color ? Color.decode("#f5f1e6") : Color.decode("#449e48")); } // 绘制左上角白车,判空避免图片加载失败时报错 if(x == 0 && y == 0 && whiteRook != null){ graphic.drawImage(whiteRook, x*cellWidth, y*cellHeight, cellWidth, cellHeight, this); } color = !color; } color = !color; } } }; board.setPreferredSize(new Dimension(720,720)); screen.add(board); // 所有配置完成后再调整窗口位置、大小、显示 screen.pack(); screen.setLocationRelativeTo(null); screen.setVisible(true);
额外注意事项
- 所有绘制相关的方法(
paint/paintComponent)会在窗口移动、缩放、重绘时被频繁调用,不要在这些方法里写加载文件、创建对象这类重IO/重内存逻辑,否则会导致界面卡顿、资源加载异常。 - 自定义字体也建议和图片一样,启动时一次性加载完成缓存,不要每次设置字体时重新读取文件。
内容的提问来源于stack exchange,提问作者ToyotaCorolla
相关产品推荐
相关产品推荐

