Search code examples
javaswingpaintcomponentrepaintpaintevent

Java repaint() not calling paintComponent()


In an attempt to make a very simple bullet-hell game to learn about java, I ran into a roadblock: repaint() wasn't calling paintComponent().

Here is the entire program, which for now simply draws an image I created 50 times per second onto a JPanel, which rests on a JFrame.

/*
 * Bullet hell, by Nematodes
 */

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class bulletHell extends JFrame
{
    private static final long serialVersionUID = 0L;
    JPanel gamePanel = new JPanel();
    int gameTimerDelay = 20;
    int x, y = 0;
    BufferedImage lightOrb;
    javax.swing.Timer gameTimer;

    public static void main(String[] args)
    {
        bulletHell createFrame = new bulletHell();
        createFrame.frameConstructor();
    }

    public void frameConstructor()
    {
        // Construct frame and frame components
        setTitle("Bullet hell");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        getContentPane().setLayout(new GridBagLayout());
        setVisible(true);

        GridBagConstraints gridConstraints;

        gridConstraints = new GridBagConstraints();
        gridConstraints.gridx = 0;
        gridConstraints.gridy = 0;
        gamePanel.setBackground(Color.BLACK);
        gamePanel.setPreferredSize(new Dimension(700, 700));
        getContentPane().add(gamePanel, gridConstraints);

        pack();

        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        setBounds((int) (0.5 * (screenSize.width - getWidth())),
                 (int) (0.5 * (screenSize.height - getHeight())), getWidth(), getHeight());

        try
        {
            lightOrb = ImageIO.read(new File("C:/Users/Owner/Downloads/orb.bmp"));
        }
        catch(IOException e)
        {
            System.out.println("An issue occurred while trying to read orb.bmp");
        }

        // Start timer that draws game objects 50 times per second (50 FPS)
        gameTimer = new javax.swing.Timer(gameTimerDelay, gameTimerAction);
        gameTimer.setInitialDelay(0);
        gameTimer.start();
    }

    ActionListener gameTimerAction = new ActionListener()
    {
        public void actionPerformed(ActionEvent e)
        {
            repaint();
        }
    };

    class GraphicsPanel extends JPanel
    {
        public GraphicsPanel()
        {

        }

        // Draw all of the components
        @Override
        public void paintComponent(Graphics g)
        {
            Graphics2D g2D = (Graphics2D) g;
            super.paintComponent(g2D);
            g2D.drawImage(lightOrb, x, y, this);
            g2D.dispose();
        }
    }
}

After some debugging with breakpoints and println methods, I can confirm that the correct image is being read, the timer in gameTimerAction is being called 50 times per second, and repaint() is not invoking paintComponent() at all.

I am somewhat new to Java programming, and might just be missing something simple.

Edit: Problem has been solved by changing gamePanel to a GraphicsPanel object. Unfortunately, this also means that my much larger pong project (which this project's flawed drawing logic was essentially copied from) only worked by a miracle, and might be unstable with certain code additions.


Solution

  • I can immediately see several problems:

    • Most Important: You never instantiate a GraphicsPanel object, nor do you add it to anything. The paintComponent(...) method will never be called on a JPanel that is neither rendered nor created. Why not make your gamePanel variable a GraphicsPanel object and not a JPanel object?
    • You never change x and y in your Timer, and so without change, no animation will occur.
    • Also you're calling dispose on a Graphics object given to you by the JVM, something you should never do. This breaks the Swing painting chain making the graphics of your GUI unstable.

    So keep at it, you'll get there.

    For example:

    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.net.URL;
    
    import javax.imageio.ImageIO;
    import javax.swing.*;
    
    public class BulletExample extends JPanel {
       public static final String IMG_PATH = "http://www.i2clipart.com/cliparts/f/0/5/8/clipart-blue-circle-f058.png";
       private static final int PREF_W = 700;
       private static final int PREF_H = PREF_W;
       private static final int TIMER_DELAY = 20;
       private BufferedImage bullet;
       private int bulletX;
       private int bulletY;
    
       public BulletExample() throws IOException {
          URL imgUrl = new URL(IMG_PATH);
          bullet = ImageIO.read(imgUrl);
          new Timer(TIMER_DELAY, new BulletListener()).start();
       }
    
       @Override
       protected void paintComponent(Graphics g) {
          super.paintComponent(g);
          if (bullet != null) {
             g.drawImage(bullet, bulletX, bulletY, this);
          }
       }
    
       @Override
       public Dimension getPreferredSize() {
          if (isPreferredSizeSet()) {
             return super.getPreferredSize();
          }
          return new Dimension(PREF_W, PREF_H);
       }
    
       private class BulletListener implements ActionListener {
          @Override
          public void actionPerformed(ActionEvent e) {
             bulletX++;
             bulletY++;
             repaint();
          }
       }
    
       private static void createAndShowGui() throws IOException {
    
          // create the drawing JPanel
          BulletExample mainPanel = new BulletExample();
    
          JFrame frame = new JFrame("BulletExample");
          frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    
          // add it to the JFrame
          frame.getContentPane().add(mainPanel);
          frame.pack();
          frame.setLocationRelativeTo(null);
          frame.setVisible(true);
       }
    
       public static void main(String[] args) {
          SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                try {
                   createAndShowGui();
                } catch (IOException e) {
                   e.printStackTrace();
                }
             }
          });
       }
    }