Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPanel doesn't repaint if repaint() is called within JFrame code

I have a class Forest and CellularJPanel, which extends JPanel and displays Forest. I wrote a primitive code to create JFrame, Forest, CellularJPanel and add CellularJPanel to the JFrame. Next is an infinite loop, which makes Forest update and CellularJPanel repaint.

    JFrame jFrame = new JFrame();          

    Forest forest = new Forest();
    CellularJPanel forestJPanel = new CellularJPanel(forest);

    jFrame.add(forestJPanel);

    jFrame.pack();
    //jFrame.setResizable(false);
    jFrame.setLocationRelativeTo(null);
    jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    jFrame.setVisible(true);

    while (true)
    {
        try
        {
            forestJPanel.repaint();
            forest.update();
            forest.sleep(); // calls Thread.sleep(...)
        }   
        catch (InterruptedException e)
        {

        }
    }

Here is a code of the CellularJPanel class:

public class CellularJPanel extends JPanel
{
    private CellularAutomata cellularAutomata;

    public CellularJPanel(CellularAutomata cellularAutomata)
    {
        super();
        this.cellularAutomata = cellularAutomata;
        setPreferredSize(this.cellularAutomata.getDimension());
    }

    @Override
    public void paintComponent(Graphics g)     
    {
        super.paintComponent(g);            
        Graphics2D graphics2D = (Graphics2D)g;
        cellularAutomata.draw(graphics2D);
    }
}

If I use above code within main() method, then everything works fine, CellularJPanel repaints, paintComponent() is called normally.

If I paste the same code to UI JFrame button click event method, then new JFrame shows and even displays initial state of the Forest, because paintComponent is once called, when jFrame.setVisible(true) is called. Then while loop is being executed, but CellularJPanel doesn't repaint, paintComponent is not called. I have no idea why, maybe I should use SwingUtilities.invokeLater(...) or java.awt.EventQueue.invokeLater somehow, but I've tried them and it didn't work, I'm doing something wrong.

Any suggestions?

P.S. My target is to display CellularJPanel within the same UI JFrame, from which button was clicked. But even if I add this panel to main UI JFrame, it doesn't work.

like image 233
Darko Avatar asked Jan 06 '23 15:01

Darko


2 Answers

Your problem is having a while(true) on the Event Dispatch Thread which will block anything related to UI because UI events aren't getting handled anymore.

The event dispatch thread (a single thread) works down a queue of UI event messages, until it handles the one where your while(true) loop is running. It then blocks any further processing because there's an infinite loop on it. Calling SwingUtilities.invokeLater from that loop won't help because it posts an event to the event dispatch thread, which is blocked in the while(true) loop.

So remove that loop, instead use a javax.swing.Timer to time your events. In the timer event, change the state of your UI and call for a repaint. The timer event will be synchronized with the UI thread, so changing state of UI components is allowed.

like image 134
TT. Avatar answered Jan 31 '23 06:01

TT.


There is a single UI thread that draws things - it's also the one that handles button clicks. In swing, this is called the event dispatch thread. If The UI thread is busy running a while loop, it can't paint.

You can quickly verify this by making your button click handler run only a single iteration of your loop (without the sleep): forest.update(); forestJpanel.repaint();

You can auto update from a separate thread (like Timer) that calls repaint/sleep in a loop.

like image 44
Krease Avatar answered Jan 31 '23 06:01

Krease