【问题标题】:paintComponent not drawing everything until repaint() is called在调用 repaint() 之前,paintComponent 不会绘制所有内容
【发布时间】:2021-03-20 14:31:19
【问题描述】:

我正在开发一个国际象棋引擎,但我遇到了以下问题:除非调用repaint(),否则我的棋子不会被绘制。但是,正方形(= 国际象棋场)看起来很好。 我读到我应该避免在paintComponent 中使用repaint(),因为这会使函数被连续调用。我怎样才能避免打电话给repaint(),但仍然绘制我的图像? 这是代码:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class ChessBoard extends JPanel {

    ArrayList<Square> squares = new ArrayList<Square>();
    Piece piece = new Piece("B", 0);
    
    public ChessBoard() {
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                squares.add(new Square(i, j));
            }
        }
    }
    
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (Square square : squares) {
            square.draw(g);
        }
        piece.draw(g);
        //repaint(); //Image only appears if this is called
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ChessBoard chessboard = new ChessBoard();
        chessboard.createFrame();
    }
    
    public void createFrame() {
        JFrame f = new JFrame("ChessBoard");
        f.getContentPane().setPreferredSize(new Dimension(squareSize()*8, squareSize()*8));
        f.setResizable(false);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setTitle("Chessboard");
        f.setLocation((int)(screenSize().width-squareSize()*8)/2, (int)(screenSize().height-squareSize()*8)/2);
        f.add(this);
        f.pack();
        Frame.getFrames();
        f.setVisible(true);
    }
    
    public static Dimension screenSize() {
        return Toolkit.getDefaultToolkit().getScreenSize();
    }
    
    public static int squareSize() {
        return (int)screenSize().height/12;
    }

}


class Square {
    int start_x, start_y;
    int square_size;
    Color color;
    
    public Square(int x, int y) {
        // constructor
        start_x = x*ChessBoard.squareSize();
        start_y = y*ChessBoard.squareSize();
        square_size = ChessBoard.squareSize();
        if ((x+y)%2 == 0) {
            // Color of the white squares
            color = new Color(209, 192, 148);
        } else {
            // Color of the black squares
            color = new Color(110, 83, 43);
        }
    }

    public void draw(Graphics g) {
        g.setColor(this.color);
        g.fillRect(this.start_x, this.start_y, square_size, square_size);
    }
}

class Piece {
    String type;
    int coordinate, square_size, piece_size;
    int[] draw_coordinates = {7, 6, 5, 4, 3, 2, 1, 0};
    Image image;
    
    public Piece(String type, int coordinate) {
        this.type = type;
        this.coordinate = coordinate;
        this.square_size = ChessBoard.squareSize();
        this.piece_size = (int)ChessBoard.squareSize()*2/3;
        this.image = Toolkit.getDefaultToolkit().getImage("src/pieces/black_bishop.png");
    }
    
    public int co_x_board() {
        return coordinate - ((int)coordinate/8)*8;
    }
    
    public int co_y_board() {
        return (int)coordinate/8;
    }
    
    public int co_x_draw() {
        return co_x_board()*square_size+((int)square_size/6);
    }
    
    public int co_y_draw() {
        return draw_coordinates[co_y_board()]*square_size+((int)square_size/6);
    }
    
    public void draw(Graphics g) {
        g.drawString(type, co_x_draw(), co_y_draw()); // does work
        g.drawImage(image, co_x_draw(), co_y_draw(), piece_size, piece_size, null); // does not work
    }
}

提前谢谢你!

【问题讨论】:

  • 我不明白你的问题。当框架显示时,棋盘的原始状态被绘制。如果您对任何 ArrayList 进行更改,那么是的,您需要调用 repaint() 来绘制新状态。发布适当的minimal reproducible example 来演示问题。另请注意,您不应该设置框架的首选尺寸。您的计算没有考虑标题栏和边框的大小。相反,您应该设置正方形的首选大小,然后 pack() 框架。
  • 当我运行我的程序时,只显示正方形。但是,我希望在启动程序时显示这些片段。但是,如上所述,这些片段仅在调用 f.repaint() 后才显示出来(在这种情况下,当按下鼠标时)。我希望这可以解决问题。
  • 关于首选尺寸,我是故意这样做的,因为我事先不知道标题栏的尺寸(或者还有其他更简洁的方法可以让它工作吗?)。
  • 不,这并不能解决问题。您的绘画逻辑同时绘制正方形和碎片。如果这些片段没有出现,那么您有一个逻辑问题,最初没有正确设置这些片段。问题与绘画逻辑无关。
  • 这正是我不明白为什么它不起作用的原因。当我在绘图组件中调用 white_pieces.size() 时,我可以清楚地看到我的作品已创建。然而,在调用 f.repaint 之前,它们不会自己绘制。我还注意到 paintComponent 在任何交互之前执行了 2 次。

标签: java swing paintcomponent


【解决方案1】:

问题在于您没有提供drawImage 方法调用的ImageObserver 参数。

我可以重现您在 MRE 中看到的错误。

我曾通过Toolkit.getDefaultToolkit().getImage("...");(或Toolkit.getDefaultToolkit().createImage("...");)阅读图像,有时如果您不提供ImageObserver,或者之前没有在图像上调用getWidth(null),则有时它不起作用画它,以及其他症状,我不知道原因。但我所知道的是,如果您提供 ImageObserver 参数,那么它将起作用。

请记住,ComponentImageObserver...所以您需要将 ComponentGraphics 对象所属的对象)实际提供给 drawImage 最后一个参数。

因此,您可以更改您的 Piece#draw 方法以接受在其上绘制它的 Component,如下所示:

public void draw(Graphics g, final Component observer) {
    g.drawString(type, co_x_draw(), co_y_draw());
    g.drawImage(image, co_x_draw(), co_y_draw(), piece_size, piece_size, observer);
}

然后记得正确调用它,像这样更改ChessBoard#paintComponent

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    for (Square square : squares) {
        square.draw(g);
    }
    piece.draw(g, this);
}

即使将null 用作ImageObserver,通常更正错误的另一种方法是使用ImageIO#read 方法将图像读取为BufferedImage。同样,我不知道为什么会这样,但确实如此。我也没有用ImageIO#read 测试你的情况,但我仍然用Toolkit.getDefaultTooklit().getImage("..."); 测试它并且它有效(但你需要提供ImageObserver 参数)。

一些建议:

在我看来,用GridLayout(例如new GridLayout(0, 8);)在JPanel 中布置一些JLabels,然后将图像设置为Icons(通过@ 987654353@) 到JLabels...

【讨论】:

  • 感谢您的回答,这对我有用。
  • 没问题。您的 MRE 帮了大忙。
  • 是的,很抱歉没有立即包含它,下次我会记住它。感谢您的宝贵时间!
  • 获取 (MR) 示例图像的一种方法是热链接到在 this Q&A 中看到的图像。例如。 this answer 中的代码热链接到嵌入在this question 中的图像。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-03
  • 2017-01-03
  • 2020-05-22
  • 1970-01-01
  • 2015-03-30
  • 1970-01-01
相关资源
最近更新 更多