求助:Swing程序绘制3个JLabel而非1个的异常问题
问题根源与解决方案:重复显示棋子的原因
你遇到的问题核心在于每次Swing触发重绘时,你都在创建并添加新的JLabel棋子组件,导致多个组件叠加显示,看起来像是出现了多个兵。
为什么会重复出现?
Swing的paintComponent方法会被多次自动调用:比如窗口初始化、最小化后恢复、窗口被其他窗口覆盖后重新显示、甚至窗口移动时,都会触发重绘。而你的draw方法是在paintComponent里调用的,每次调用draw都会循环遍历Figure.figures,创建新的JLabel并add到GamePanel中。次数多了,面板里就堆积了多个相同的JLabel,自然就看到多个兵了。
另外你还存在一个小问题:棋子的位置计算错误。你直接用figure.position[0] +5作为像素位置,但position是棋盘的行列索引(0-7),需要乘以格子大小UNIT_SIZE才能对应到正确的棋盘格位置,这也是你觉得“位置随机”的原因之一。
解决方案
我给你两种可行的解决思路,你可以根据需求选择:
思路1:提前创建棋子组件,只更新位置
组件只需要创建并添加一次,重绘时只更新它们的位置即可:
import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.ArrayList; import java.util.List; public class GamePanel extends JPanel implements MouseListener { static final int SCREEN_EDGE = 800; static final int GAME_UNITS = 64; static final int UNIT_SIZE = 100; final int[] x = new int[GAME_UNITS]; final int[] y = new int[GAME_UNITS]; boolean running = false; // 新增:保存棋子组件的集合 private final List<JLabel> figureLabels = new ArrayList<>(); public GamePanel() { this.setPreferredSize(new Dimension(SCREEN_EDGE, SCREEN_EDGE)); this.setFocusable(true); this.addKeyListener(new MyKeyAdapter()); this.addMouseListener(this); // 别忘了注册鼠标监听器! startGame(); int[] position = {0,0}; new Pawn(position,-1); // 初始化棋子组件,只执行一次 initFigureLabels(); } private void startGame() {} private void initFigureLabels() { for (Figure figure : Figure.figures) { if (figure != null) { JLabel figureSprite = new JLabel(figure.image, JLabel.CENTER); figureSprite.setSize(90,90); updateFigurePosition(figureSprite, figure); this.add(figureSprite); figureLabels.add(figureSprite); } } } private void updateFigurePosition(JLabel label, Figure figure) { // 修正位置计算:行列索引 * 格子大小 + 偏移量 int pixelX = figure.position[0] * UNIT_SIZE + 5; int pixelY = figure.position[1] * UNIT_SIZE + 5; label.setLocation(pixelX, pixelY); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); draw(g); } public void draw(Graphics g){ // 绘制棋盘的代码保持不变 int counter = 1; for (int y = 0; y < SCREEN_EDGE/UNIT_SIZE; y++) { int color = (y % 2 == 0) ? 1 : 2; for (int x = 0; x < SCREEN_EDGE/UNIT_SIZE; x++) { g.setColor(color == 1 ? new Color(239,217, 181) : new Color(180, 136,98)); g.fillRect(x*UNIT_SIZE, y*UNIT_SIZE, UNIT_SIZE, UNIT_SIZE); color = color == 1 ? 2 : 1; } } // 重绘时只更新位置,不再创建新组件 int index = 0; for (Figure figure : Figure.figures) { if (figure != null && index < figureLabels.size()) { updateFigurePosition(figureLabels.get(index), figure); index++; } } } // ... 原有鼠标事件、KeyAdapter代码保持不变 }
思路2:直接用Graphics绘制棋子(更推荐游戏场景)
如果你担心JLabel的鼠标事件处理麻烦,直接用Graphics绘制图片是更简洁的方案,还能避免组件管理的问题:
public class GamePanel extends JPanel implements MouseListener { // ... 原有属性不变 public GamePanel() { this.setPreferredSize(new Dimension(SCREEN_EDGE, SCREEN_EDGE)); this.setFocusable(true); this.addKeyListener(new MyKeyAdapter()); this.addMouseListener(this); // 注册鼠标监听器 startGame(); int[] position = {0,0}; new Pawn(position,-1); } // ... 原有startGame、paintComponent代码不变 public void draw(Graphics g){ // 绘制棋盘的代码保持不变 // ... 棋盘绘制代码 // 直接用Graphics绘制棋子图片,无需JLabel for (Figure figure : Figure.figures) { if (figure != null && figure.image != null) { int pixelX = figure.position[0] * UNIT_SIZE + 5; int pixelY = figure.position[1] * UNIT_SIZE + 5; figure.image.paintIcon(this, g, pixelX, pixelY); } } } // 处理鼠标点击:判断点击的棋盘格是否有棋子 @Override public void mouseClicked(MouseEvent e) { // 计算点击的棋盘格行列 int gridX = e.getX() / UNIT_SIZE; int gridY = e.getY() / UNIT_SIZE; // 查找该位置的棋子 for (Figure figure : Figure.figures) { if (figure != null && figure.position[0] == gridX && figure.position[1] == gridY) { // 这里可以处理棋子的点击逻辑,比如选中、移动等 System.out.println("点击了位于(" + gridX + "," + gridY + ")的棋子"); break; } } } // ... 其他鼠标事件、KeyAdapter代码保持不变 }
另外建议把Figure类中的数组换成ArrayList,避免手动扩容的麻烦:
public abstract class Figure{ // ... 原有属性不变 // 替换数组为ArrayList protected static List<Figure> figures = new ArrayList<>(); public Figure(int value, int[] position, int[][] possibleMoves , int whiteOrBlack) { this.value = value; this.position = position; this.possibleMoves = possibleMoves; this.whiteOrBlack = whiteOrBlack; // 直接添加到列表,无需手动扩容 figures.add(this); } // ... 原有抽象方法不变 }
总结
重复显示棋子的核心原因是在重绘方法中重复添加组件,修正这个逻辑后就能解决问题。推荐使用第二种直接绘制图片的方式,更适合游戏开发的场景,鼠标事件处理也更灵活。
内容的提问来源于stack exchange,提问作者JIgnat
相关产品推荐
相关产品推荐

