【问题标题】:JPanel doesn't draw image until mouse clickJPanel 在鼠标单击之前不会绘制图像
【发布时间】:2025-11-26 09:50:01
【问题描述】:

我有一个小而简单的程序,我已经启动了一个奇怪的错误,我无法在网上找出或找到原因。

我的程序现在所做的就是构建一个 JFrame,构建一个 JPanel,然后从一个线程将图像绘制到 JPanel 上。问题是,除非我在框架中单击,否则图像不会显示,左键单击,右键单击,甚至用鼠标滚轮单击都会显示图像,但调整框架大小不会显示图像。我已经在面板和框架上尝试了 validate(),并且我尝试将 repaint() 放入线程调用的代码中,但是当 repaint() 在代码中时,图像会闪烁。

我在实际绘制图像的代码中放置了一个打印语句,但在我单击框架之前,该语句从未显示。我已经使用了调试器,但代码似乎被正确调用了。如果我在从主线程的 update() 函数调用的代码中的任何位置放置打印语句,则无需单击即可正确显示图像。

这是我的代码,任何建议将不胜感激:

框架

public class GameFrame extends JFrame{

  int w = 1300;
  int h = 740;

  public GameFrame() {

    this.setSize(w, h);
    setTitle("Frame");

    GamePanel panel = new GamePanel(w, h);
    this.add(panel);
    this.setVisible(true);
    this.setDefaultCloseOperation(EXIT_ON_CLOSE);
  }

  public static void main(String[] args) {
    GameFrame gameFrame = new GameFrame();
  }
}

小组

public class GamePanel extends JPanel implements Runnable{

//core classes
GameRenderer renderer;
GameManager manager;
GameLoader loader;

//graphics stuff
private Graphics dbg;
private Image dbImage = null;
private int panelWidth;
private int panelHeight;

//game updating stuff
Thread animator;
boolean running;

public GamePanel(int w, int h) {
    this.setSize(w, h);
    this.setVisible(true);
    this.setFocusable(true);
    this.panelWidth = w;
    this.panelHeight = h;

    renderer = new GameRenderer();
    manager = new GameManager();
    loader = new GameLoader();

    startGame();
}

private void startGame() {
    animator = new Thread(this, "Animator");
    animator.start();
    running = true;
}

public void run() {
    while(running) {
        gameAction();
    }
}

//everything that needs to be updated continuously
private void gameAction() {
    //updateGame(); // Need code here to update everything
    gameRender(); // Draw to the double buffer.
    paintScreen(); // Draw double buffer to screen.
}

/**
 * Draws the game image to the buffer.
 * 
 */
private void gameRender() {
    if(dbImage == null) {
        dbImage = createImage(this.panelWidth, this.panelHeight);
        return;
    }
    dbg = dbImage.getGraphics();
    renderer.draw((Graphics2D) dbg, panelWidth, panelHeight);
}

/**
 * Draws the game image to the screen by drawing the buffer.
 */
private void paintScreen() {
    Graphics g;
    try {
        g = this.getGraphics();
        if ((g != null) && (dbImage != null))  {
            g.drawImage(dbImage, 0, 0, null);
            g.dispose();
        }
    } catch (Exception e) { System.out.println("Graphics context error: " + e); }
}

}

图像渲染器

public class GameRenderer {

ImageManipulator im = new ImageManipulator();

/**
 * Actually draw everything that needs to be drawn
 * Layering also happens here 
 */
public void draw(Graphics2D g,int screenWidth, int screenHeight) {
    BufferedImage tileImg = im.loadImage("Images/tile.png");
    Tile tile = new Tile(tileImg, 30, 30);
    tile.draw(g);
}

}

类与我正在尝试绘制的图像

public class Tile {

BufferedImage tileImg;
//x and y values based on tile grid - z based on height up
int tileX;
int tileY;
int tileZ;
//actual coordinate of top left corner of tile *image*
int pixelX;
int pixelY;

public Tile(BufferedImage img, int pixelX, int pixelY) {
    this.pixelX = pixelX;
    this.pixelY = pixelY;
    this.tileImg = img;
}

public void draw(Graphics g) {
    g.drawImage(tileImg, pixelX, pixelY, null);
}

}

同样,任何建议或想法都会有所帮助。我不知道可能导致这种情况的原因。 另外,有人知道为什么这可能适用于包含但不是没有的打印语句吗?

【问题讨论】:

  • 我不是如何在后台管理图形的专家,但我曾经打电话给repaint()。 (您的问题的快速解决方法是将:update(g) 添加到 paintScreen 方法)。
  • 这有助于缩小范围。如果我将 update(g) 放在这个 if 语句中,我会得到相同的结果:if ((g != null) && (dbImage != null))。如果我把它紧跟在g = this.getGraphics(); 这一行之后,我会得到一些空指针异常(这是有道理的),但它显示正确。我猜这个 if 语句导致它无法呈现。
  • 不,使用 getGraphics() 方法完成后不会将图形放在屏幕上,AFAIK。 repaint() 是执行此操作的首选方法。
  • 另外,一个非常好的 Java 游戏循环资源是 Bonsai 游戏库:github.com/BonsaiDen/Bonsai-Game-Library/blob/master/src/org/…
  • getGraphics 不是 Swing 中的绘画方式,您应该在定义的绘画链中绘画(覆盖组件的paintComponent),它会告诉您何时应该完成绘画。如果你想控制绘制完成的时间,你应该使用 BufferedStrategy 来代替。问题是,当 RepaintManager 决定需要更新时,您使用 getGraphics 绘制的任何内容都可能(并且可能已经)被绘制

标签: java image jframe jpanel bufferedimage


【解决方案1】:

您误解了如何在 Swing 组件中进行自定义绘制。拨打this.getGraphics() 是不正确的。绘制 Swing 组件的正确方法是重写其paintComponent 方法。请注意,该方法的第一行必须调用super.paintComponent:

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    if (dbImage != null) {
        g.drawImage(dbImage, 0, 0, this);
    }
}

还请注意,您不得尝试释放 Graphics 对象,因为它不是您创建的,而且它在 Swing 的控制之下,而不是您的。

有关详细信息,请参阅Painting in AWT and Swing 教程。

当您想要重新绘制游戏时,只需调用面板的repaint 方法即可。因此,您的 gameAction 方法应将 paintScreen() 替换为 repaint(),这将自动调用面板的 paintComponent 方法(以及其他一些方法;正如该教程所解释的,绘画不是一件简单的事情)。

您需要在run() 方法的循环内放置一个简短的sleep 调用。如果您尝试不断更新和重新绘制游戏,程序将没有时间实际绘制任何东西。例如,如果您希望游戏每秒更新五次(即每 200 毫秒):

try {
    while (running) {
        gameAction();
        Thread.sleep(200);
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}

【讨论】:

    【解决方案2】:

    我有两个猜测。第一个是 GamePanel 中的 startGame 方法。把“running = true;”这一行在你的线程上调用“start()”之前。

    如果这不起作用,请在 GamePanel 的 run() 方法中尝试此操作:

    public void run() { gameAction(); }
    

    如果它渲染了,那么问题出在 while 循环上。

    【讨论】:

    • 嗯,看起来不像是其中任何一个。我什至在 run 函数中尝试了if(true) gameAction(); 来验证。