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

Java Swing象棋棋盘拖拽与棋子移动问题(repaint()无效)

Java Swing象棋拖拽移动失效问题解决

问题描述

我用Java Swing开发象棋GUI,基于JPanel实现了Board类负责绘制棋盘和棋子,同时通过MouseListener和MouseMotionListener实现鼠标拖拽移动棋子的逻辑。现在遇到的问题是:控制台显示棋子坐标已经更新,但界面上看不到棋子移动,repaint()调用后画面没有变化。

问题代码

Board类代码

public class Board extends JPanel implements MouseMotionListener, MouseListener {

    private final PieceRender pieceRender = new PieceRender();
    private final int cellSize = 600 / 8;
    private final int initialX = 45;
    private final int initialY = 55;
    private Point movingPiece;
    private int pieceLocationIndex;

    public Board() {
        addMouseListener(this);
        addMouseMotionListener(this);
    }

    @Override
    protected void paintComponent(Graphics g){
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;
        drawBoard(g2);
        drawLabel(g2);

        pieceRender.pieceByDefault(g2,initialX,initialY,cellSize);
    }

    private void drawBoard(Graphics2D g2) {
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                drawSquare(g2,2 * j, 2 * i, true);
                drawSquare(g2,1 + 2 * j, 1 + 2 * i, true);

                drawSquare(g2,1 + 2 * j, 2 * i, false);
                drawSquare(g2,2 * j, 1+ 2 * i, false);
            }
        }
    }

    private void drawSquare(Graphics2D g, int x, int y, boolean color){
        g.setColor(color ? Color.WHITE : Color.darkGray);
        g.fillRect(initialX + x * cellSize, initialY + y * cellSize, cellSize, cellSize);
    }

    private void drawLabel(Graphics2D g2){
        char[] litere = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'};
        char[] cifre = {'1', '2', '3', '4', '5', '6', '7', '8'};

        Font font = new Font("default", Font.BOLD, 20);
        g2.setFont(font);

        var pozitionX = initialX;
        var pozitionY = initialY;

        for(int i = 0; i < litere.length; i++){
            g2.drawChars(litere, i, 1, pozitionX + 30, pozitionY - 10);
            pozitionX += 75;
        }
        for(int i = 0; i < cifre.length; i++){
            g2.drawChars(cifre, i, 1, pozitionX - 625, pozitionY + 45);
            pozitionY += 75;
        }
    }

    @Override
    public void mouseClicked(MouseEvent e) {}

    @Override
    public void mousePressed(MouseEvent e) {
        var actualX = (e.getPoint().x - initialX) / cellSize;
        var actualY = (e.getPoint().y - initialY) / cellSize;

        for (int i = 0; i < pieceRender.piecesBox.size(); i++) {
            if (pieceRender.piecesBox.get(i).getCoordinates().getPos_X() == actualX
                   && pieceRender.piecesBox.get(i).getCoordinates().getPos_Y() == actualY){
                    pieceLocationIndex = i;
           }
        }
        movingPiece = e.getPoint();
        repaint();
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        var actualX = (e.getPoint().x - initialX) / cellSize;
        var actualY = (e.getPoint().y - initialY) / cellSize;

        pieceRender.piecesBox.get(pieceLocationIndex).getCoordinates().setPos_X(actualX);
        pieceRender.piecesBox.get(pieceLocationIndex).getCoordinates().setPos_Y(actualY);

        movingPiece = null;
        repaint();
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        movingPiece = e.getPoint();
        repaint();
    }
    
    @Override
    public void mouseEntered(MouseEvent e) {}
    @Override
    public void mouseExited(MouseEvent e) {}
    @Override
    public void mouseMoved(MouseEvent e) {}
    
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            Board board = new Board();
            board.addMouseListener(board);
            board.addMouseMotionListener(board);

            JFrame frame = new JFrame("Chess Game");
            frame.add(board);
            frame.setSize(800, 800);
            frame.setVisible(true);
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        });
        t1.start();
    }
}

PieceRender类绘制代码

public void pieceByDefault (Graphics2D g2, int initialX, int initialY, int cellSize) {
        PieceFactory pieceFactory = new PieceFactory();

        char[][] defaultPositions = {
                {'R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R'},
                {'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'},
                {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
                {'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'},
                {'r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'}
        };

        for (int y = 0; y < 8; y++) {
            for (int x = 0; x < 8; x++) {
                char pieceType = defaultPositions[y][x];
                if (pieceType != ' ') {
                    ChessPiece piece = pieceFactory.create(pieceType, new Coordinates(x, y));
                    piecesBox.add(piece);
                    drawPiece(g2, piece.getIndex(), piece
                     .getCoordinates().getPos_X(), piece.getCoordinates()
                     .getPos_Y(),initialX, initialY, cellSize);
                }
            }
        }
    }

 public void drawPiece(Graphics2D g2, int index, 
     int initialX, int initialY,int primeX, int primeY, int  cellSize){
        drawImage(g2, index, initialX, initialY,primeX, primeY, cellSize);
    }

问题根源及修复方案

1. 核心问题:每次重绘都重新初始化棋子

pieceByDefault方法在paintComponent中被调用,意味着每次重绘都会重新创建所有棋子并添加到piecesBox,直接覆盖了之前修改的坐标,同时导致棋子列表无限膨胀。

修复:
将棋子初始化逻辑从绘制方法中分离,只在Board构造时执行一次:

// Board类构造方法修改
public Board() {
    addMouseListener(this);
    addMouseMotionListener(this);
    // 初始化棋子,仅执行一次
    pieceRender.initPieces();
}

2. 绘制逻辑错误:未基于实时坐标绘制

原pieceByDefault是基于默认位置绘制,而非遍历现有棋子的实时坐标。需要改为根据piecesBox中棋子的当前状态绘制。

修复:
修改PieceRender类,拆分初始化和绘制逻辑:

public class PieceRender {
    public List<ChessPiece> piecesBox = new ArrayList<>();

    // 初始化棋子,仅调用一次
    public void initPieces() {
        PieceFactory pieceFactory = new PieceFactory();
        char[][] defaultPositions = {
                {'R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R'},
                {'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'},
                {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
                {'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'},
                {'r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'}
        };

        piecesBox.clear();
        for (int y = 0; y < 8; y++) {
            for (int x = 0; x < 8; x++) {
                char pieceType = defaultPositions[y][x];
                if (pieceType != ' ') {
                    ChessPiece piece = pieceFactory.create(pieceType, new Coordinates(x, y));
                    piecesBox.add(piece);
                }
            }
        }
    }

    // 根据实时坐标绘制所有棋子
    public void drawAllPieces(Graphics2D g2, int initialX, int initialY, int cellSize) {
        for (ChessPiece piece : piecesBox) {
            Coordinates coord = piece.getCoordinates();
            int drawX = initialX + coord.getPos_X() * cellSize;
            int drawY = initialY + coord.getPos_Y() * cellSize;
            drawPiece(g2, piece.getIndex(), drawX, drawY, initialX, initialY, cellSize);
        }
    }

    // 原有drawPiece、drawImage方法保持不变
}

3. 拖拽时的实时绘制处理

拖拽过程中,需要单独绘制跟随鼠标的棋子,避免和其他棋子的绘制逻辑冲突。

修复:
修改Board类的paintComponent方法:

@Override
protected void paintComponent(Graphics g){
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    drawBoard(g2);
    drawLabel(g2);

    // 绘制所有非拖拽状态的棋子
    pieceRender.drawAllPieces(g2, initialX, initialY, cellSize);
    
    // 绘制正在拖拽的棋子
    if (movingPiece != null) {
        ChessPiece moving = pieceRender.piecesBox.get(pieceLocationIndex);
        // 让棋子中心跟随鼠标
        int drawX = movingPiece.x - cellSize / 2;
        int drawY = movingPiece.y - cellSize / 2;
        pieceRender.drawPiece(g2, moving.getIndex(), drawX, drawY, initialX, initialY, cellSize);
    }
}

4. 其他冗余问题修复

  • 移除main方法中重复注册的鼠标监听器(Board构造中已注册)
  • 使用SwingUtilities.invokeLater代替自定义线程,保证Swing组件在事件调度线程(EDT)中创建:
public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> {
        Board board = new Board();
        JFrame frame = new JFrame("Chess Game");
        frame.add(board);
        frame.setSize(800, 800);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 14:15:55