Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Repainting a JPanel inside a frame

I have a JPanel inside a frame. The contents of the JPanel are supposed to be updated with each call to paintComponent (which is called by repaint()), but when I do it as below, I just see a white window. (Please excuse the mangled indentation, Eclipse does all kinds of weird stuff with tabs.)

private static void handleGUI() 
{       
    JFrame frame = new JFrame("Animation");
    frame.setPreferredSize(new Dimension(100, 100));

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    Board b = new Board();

    frame.getContentPane().add(b);

    frame.pack();
    frame.setVisible(true);

    while(true)
    {
        System.out.println("Repainting panel");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        b.repaint();
    }
}

public class Board extends JPanel
{
public Board() { t=0; }

    private int t;

public void paintComponent(Graphics g) 
{
    super.paintComponent(g);

    ++t;

    /* Variables snipped */

    g.setColor(Color.white);
    g.drawOval(0, 0, width, height);

    BufferedImage image = ImageIO.read(new File(imagePath));
    g.drawImage(image, x(t), y(t));
    /* There's some more image and rectangle drawing ahead */
}

}

like image 574
Joshua Avatar asked Jan 09 '11 19:01

Joshua


3 Answers

You have several problems with your code, one has been mentioned above (1+ rep -- though it looks to me as if you have code now in your paintComponent method that does do drawing), your paintComponent method is flawed, but not only that, you've got a while(true) loop and a Thread.sleep in the Swing main thread, the EDT, which will put Swing and your whole GUI to sleep. Better to use a Swing Timer instead. Also, you state,

The contents of the JPanel are supposed to be updated with each call to paintComponent (which is called by repaint()),

Are you sure that you want to put program logic inside of the paintComponent method? This is usually frowned upon since you the programmer do not have complete control regarding when this method is called. It may be called when you call repaint (but not always), and may be called due to messages from the OS when you don't expect it to be called.

Also, you never want to read a file inside of the paintComponent method as this will slow your painting to a cawl.

I'd recommend these changes:

1) Create a Swing Timer with a period of 1000, and in its ActionListener's actionPerformed method, read in your image (preferably in a background thread if the image has any significant size) and have the image read into a class field, say called image.

2) In the same Timer's actionPerformed method, increment t.

3) After the image has been read in, call repaint on the drawing JPanel and have the drawing JPanel use the image variable to paint the image with. Be careful if you are using a background thread to read in the image as you'll need to have this thread notify the GUI when the image has been completely read in. If using a SwingWorker to do this, you can add a PropertyChangeListener that listens to the SwingWorker's state value and triggers when this changes to StateValue.DONE.

If any of this is unclear or confusing, please ask for clarification.

edit: if the images aren't too big, you may wish to read them all in at one time, or do a batch read when a bunch are needed. There's no absolute need to read in the images immediately before using them.

like image 105
Hovercraft Full Of Eels Avatar answered Nov 14 '22 06:11

Hovercraft Full Of Eels


Don't make the thread sleep. Use a Swing Timer to set off the repaint events.

like image 2
jzd Avatar answered Nov 14 '22 07:11

jzd


your paintComponent just calls super.paintComponent. So the JPanel while paint it self, which will be light gray, or white or light brown or something depending on your look and feel.

like image 1
Jens Schauder Avatar answered Nov 14 '22 05:11

Jens Schauder