Java Swing开发黑白棋GUI:运行中更新JLabel异常求助
问题分析与解决方案
你的代码存在几个核心问题,导致标签无法正确更新或面板布局崩溃:
1. 在paintComponent中重复添加组件
每次调用panel.repaint()时,paintComponent方法都会执行,而你在这个方法里循环创建并添加新的JPanel tile到面板中,导致面板不断积累组件,最终布局彻底混乱。paintComponent仅负责绘制内容,绝对不能在这里添加或修改组件结构。
2. 标签变量作用域限制
humanPoints和machinePoints是构造函数内的局部变量,即便匿名内部类可以访问,你也没有在按钮点击时主动调用setText更新它们的内容。
3. 数据更新后未同步标签文本
测试按钮修改board数据后,只调用了panel.repaint()重绘棋盘,但没有同步更新两个标签的文本,所以标签始终显示初始值。
修正后的完整代码
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Arrays; import java.util.Objects; import static javax.swing.WindowConstants.EXIT_ON_CLOSE; public class ReversiGame { private JPanel boardPanel; private final int width = 60; private final int diameter = (int) Math.round(width * 0.85); private final int padding = (int) Math.round(width * 0.05); private String[][] board; private int SIZE = 8; // 将标签设为实例变量,方便跨方法访问 private JLabel humanPoints; private JLabel machinePoints; public ReversiGame() { board = newBoard(); JFrame frame = new JFrame(); frame.setTitle("Reversi"); frame.setLocationRelativeTo(null); frame.setLocation(450, 150); frame.setBackground(Color.BLACK); frame.setPreferredSize(new Dimension(width * 8, width * 9)); frame.setDefaultCloseOperation(EXIT_ON_CLOSE); frame.setResizable(false); JPanel bottom = new JPanel(); // 玩家棋子数量标签 humanPoints = new JLabel(); humanPoints.setText(String.valueOf(getNumberOfHumanTiles())); humanPoints.setForeground(Color.BLUE); humanPoints.setFont(new Font("Verdana", Font.BOLD, 20)); bottom.add(humanPoints); // 测试按钮:随机生成棋子组合 JButton testButton = new JButton("Test"); testButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { board = newBoard(); for (int i = 0; i < (int)(Math.random() * SIZE); i++) { board[(int)(Math.random() * SIZE)][(int)(Math.random() * SIZE)] = "X"; } for (int i = 0; i < (int)(Math.random() * SIZE); i++) { board[(int)(Math.random() * SIZE)][(int)(Math.random() * SIZE)] = "O"; } // 先更新标签文本 updateScoreLabels(); // 再重绘棋盘 boardPanel.repaint(); } }); bottom.add(testButton); // 机器棋子数量标签 machinePoints = new JLabel(); machinePoints.setText(String.valueOf(getNumberOfMachineTiles())); machinePoints.setForeground(Color.RED); machinePoints.setFont(new Font("Verdana", Font.BOLD, 20)); bottom.add(machinePoints); frame.add(bottom, BorderLayout.SOUTH); // 棋盘面板:直接绘制格子和棋子,不再添加子组件 boardPanel = new JPanel(new GridLayout(SIZE, SIZE)) { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 遍历绘制每个棋盘格子 for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { // 计算当前格子的坐标 int x = j * width; int y = i * width; // 绘制绿色格子和黑色边框 g.setColor(Color.GREEN); g.fillRect(x, y, width, width); g.setColor(Color.BLACK); g.drawRect(x, y, width, width); // 根据board值绘制对应颜色的棋子 switch (board[i][j]) { case "X": g.setColor(Color.BLUE); g.fillOval(x + padding, y + padding, diameter, diameter); g.setColor(Color.BLACK); g.drawOval(x + padding, y + padding, diameter, diameter); break; case "O": g.setColor(Color.RED); g.fillOval(x + padding, y + padding, diameter, diameter); g.setColor(Color.BLACK); g.drawOval(x + padding, y + padding, diameter, diameter); break; default: break; } } } } }; frame.add(boardPanel, BorderLayout.CENTER); frame.pack(); frame.setVisible(true); } // 创建初始棋盘布局 private String[][] newBoard() { String[][] newBoard = new String[SIZE][SIZE]; for (int i = 0; i < SIZE; i++) { Arrays.fill(newBoard[i], "."); } newBoard[SIZE / 2 - 1][SIZE / 2 - 1] = "X"; newBoard[SIZE / 2][SIZE / 2] = "X"; newBoard[SIZE / 2 - 1][SIZE / 2] = "O"; newBoard[SIZE / 2][SIZE / 2 - 1] = "O"; return newBoard; } // 统一更新分数标签的方法 private void updateScoreLabels() { humanPoints.setText(String.valueOf(getNumberOfHumanTiles())); machinePoints.setText(String.valueOf(getNumberOfMachineTiles())); } public int getNumberOfHumanTiles() { int count = 0; for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { if (Objects.equals(board[i][j], "X")) { count++; } } } return count; } public int getNumberOfMachineTiles() { int count = 0; for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { if (Objects.equals(board[i][j], "O")) { count++; } } } return count; } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new ReversiGame(); } }); } }
关键修改说明
标签变量作用域调整:将
humanPoints和machinePoints从构造函数局部变量改为类的实例变量,确保在按钮点击事件中可以直接访问并更新。重构棋盘绘制逻辑:移除
paintComponent中创建子组件的代码,改为直接在面板上绘制格子和棋子,彻底避免重复添加组件导致的布局崩溃。新增分数更新方法:创建
updateScoreLabels()方法统一处理两个标签的文本更新,简化代码逻辑。同步数据与视图:在测试按钮点击事件中,先调用
updateScoreLabels()更新标签,再调用boardPanel.repaint()重绘棋盘,保证数据与视图同步。
修改后,点击测试按钮时,标签会正确显示当前玩家和机器的棋子数量,棋盘也不会出现无限重复的问题。
内容的提问来源于stack exchange,提问作者Anton Blokhin
相关产品推荐
相关产品推荐

