You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
            }
        });
    }
}

关键修改说明

  1. 标签变量作用域调整:将humanPoints和machinePoints从构造函数局部变量改为类的实例变量,确保在按钮点击事件中可以直接访问并更新。

  2. 重构棋盘绘制逻辑:移除paintComponent中创建子组件的代码,改为直接在面板上绘制格子和棋子,彻底避免重复添加组件导致的布局崩溃。

  3. 新增分数更新方法:创建updateScoreLabels()方法统一处理两个标签的文本更新,简化代码逻辑。

  4. 同步数据与视图:在测试按钮点击事件中,先调用updateScoreLabels()更新标签,再调用boardPanel.repaint()重绘棋盘,保证数据与视图同步。

修改后,点击测试按钮时,标签会正确显示当前玩家和机器的棋子数量,棋盘也不会出现无限重复的问题。

内容的提问来源于stack exchange,提问作者Anton Blokhin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 18:57:08