Why won't repaint() always call paintComponent and why it doesn't always behave properly when it's called

10,905

First of all, if you haven't already done so, I'd have a read through Painting in AWT and Swing, which explains the painting scheme used by Swing (and AWT).

Secondly, you don't control the repaints, the repaint manager and the OS do, you just provide suggestions.

Thirdly, you could take a look at the JComponent.paintImmediately method, it will require to know the area you want to update, but might help

From the Java Docs

Paints the specified region in this component and all of its descendants that overlap the region, immediately.

It's rarely necessary to call this method. In most cases it's more efficient to call repaint, which defers the actual painting and can collapse redundant requests into a single paint call. This method is useful if one needs to update the display while the current event is being dispatched.

I may also be more prudent to render the game state to an off screen buffer and use this in the paintComponent method. You could place these on a queue and pop them off during the paint process, allowing to create more or less on demand (keeping a pool of a few and growing, shrinking the pool as you need)...

Share:
10,905
Admin
Author by

Admin

Updated on June 14, 2022

Comments

  • Admin
    Admin about 2 years

    I'm working on a Tetris clone in Java, and all seems to work properly until I want a full row to be cleared and everything above to be dropped. Although all my data properly represents the transformation, my paintComponent method seems to only clear the row, but leave everything displayed above as it was before the repaint() call. The new piece will fall through the phantom blocks and land on invisible blocks at the bottom row, where the above pieces would have fallen.

    Here's my paint component method:

    public void paintComponent(Graphics page)
    {
        super.paintComponent(page);
        Graphics2D page2D = (Graphics2D) page;
        for (int c = 0; c < 10; c++) 
        {
            for (int r = 0; r < 18; r++)
            {
                if (well[c][r] != null) //Well is a 2D array of Block objects that have Rectangle object, coordinates and color
                {
                    page2D.setColor(well[c][r].getColor());
                    page2D.fill(well[c][r].getSquare());
                    page2D.setColor(Color.gray);
                    page2D.draw(well[c][r].getSquare());
                }       
    
            }
        }
        for (int i = 0; i < 4; i++) //tetro = the player's tetris piece
        {
            page2D.setColor(tetro.getColor());
            page2D.fill(tetro.getBlock(i).getSquare());
            page2D.setColor(Color.GRAY);
            page2D.draw(tetro.getBlock(i).getSquare());
        }
    
    }
    

    This is the portion of my actionPerformed method in my Timer listener that detects/clears blocks and calls the repaint method.

            int count = 0;  //Number of occupied cells in well
            int clears = 0; //number of rows to be clear
            int lowestClear = -1; //Lowest row that was cleared, -1 if none
            for (int row = 0; row < 18; row++)
            {
                for (int col = 0; col < 10; col++)
                {
                    if (well[col][row] != null)
                    {
                        count++;
                    }
                }
                if (count == 10)
                {
                    clears++;
                    if (lowestClear < 0)
                    {
                        lowestClear = row;
                    }
                    for (int col = 0; col < 10; col++)
                    {
                        well[col][row] = null;
                    }
                }
                count = 0;
            }
            if (clears > 0)
            {
                repaint(); //Doesn't call paintComponent()
                for (int i = 1; i <= clears; i++)
                {
                    for (int r = 16; r >= 0; r--)
                    {
                        if (r > lowestClear)
                        {
                            break;
                        }
                        for (int c = 0; c < 10; c++)
                        {
                            if (well[c][r] != null)
                            {
                                well[c][r+1] = well[c][r];
                                well[c][r] = null;
                            }           
                        }
                    }
                }
                repaint(); //Does not call paintComponent()
            }       
            tetro.fall();
            repaint(); //DOES call paint component
    

    By the time the first repaint() method is called, the well array properly shows that the full row is now entirely null. I would like the repaint() method to update the panel to show this empty row, but paintComponent() doesn't seem to be called. This is also the case with the second repaint() method, where I would like it to update the frame to show the blocks in their new positions after clearing a row and dropping them down. Again, paintComponent() isn't called. For the last repaint() call, however, where I just want to update the position of the falling piece regardless of whatever updates it may or may not have needed to make before, repaint() DOES call paintComponent(). So: question number one is, why is paintComponent() only called at this instance of the repaint() call.

    However, when paintComponent() is called and it reaches the end of the method, I follow it in debug mode to see at what line does the panel reflect the changes. Once it reaches :"Repaintmanager.paintDirtyRegions(Map< Component,Rectangle >)" line:856, it has cleared the row and displays the new falling piece, but has invisible blocks and phantom blocks.

    So, my second question is, why is paintComponent() behaving in this way. Obviously I need to do a good bit of reading on Repaintmanager and Java painting in general, but I would greatly appreciate it if someone could explain this to me.

    Here's the main method if important:

    import javax.swing.JFrame;
    
    public class TetrisDriver 
    {
    
    
    public static void main(String[] args) 
    {
        JFrame frame = new JFrame("Tetris");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
        frame.getContentPane().add(new Matrix()); //Matrix = jPanel class
        frame.pack();
        frame.setVisible(true);
    }
    

    }

    I apologize if this is obnoxiously long.

  • Admin
    Admin over 11 years
    I'm going to read the article again, but from what I understand the problem must have something to do with repaint manager collapsing multiple requests, but I can't figure out why when executing paintComponent it would paint the brief state where the row is cleared and not the actual state at the time in which everything above the cleared row is shifted down one. I'm honestly not sure how I would implement an offscreen buffer or paintImmediately to prevent whatever is happening without first understanding what is happening. Do you have any ideas of what repaint manager is doing from my code?
  • Ungeheuer
    Ungeheuer about 9 years
    @sdasdadas why is it not a good idea to call paint()? I have heard this, but do not fully understand.
  • Ungeheuer
    Ungeheuer about 9 years
    How would you make a queue of GUIs, or what are you making a queue of? Would it be like an ArrayList or am I misreading what you wrote?
  • MadProgrammer
    MadProgrammer about 9 years
    @JohnnyCoder That would depend on what you are trying to do
  • MadProgrammer
    MadProgrammer about 9 years
    @user1726134 Remember, painting can occur at any time, for any reason, many times without your knowledge or control. It could be that you are hitting a repaint point between state updates, but it's hard to tell without some kind of runnable example, everything else is guess work
  • MadProgrammer
    MadProgrammer about 9 years
    Swing uses a passive rendering algorithm, this means that painting could occur and anytime for any reason, mostly without your knowledge or intervention. Attempting to paint in the way you have suggested could result in a NullPointerException or cause other painting artefacts as you fight the paint sub system. You should work within the API not against it. getGraphics is NEVER a good idea