user1726134
user1726134

Reputation: 31

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

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.

Upvotes: 3

Views: 11018

Answers (3)

Piotr M&#252;ller
Piotr M&#252;ller

Reputation: 5558

repaint() don't call paintComponent() and this is right working scenerio for repaint() method.

It only marks a state that component should be repainted and paintComponent() is called further by Swing itself.

Many repaint() calls can cause only single paintComponent() call and this is valid behaviour.

Upvotes: -1

Spino Prime
Spino Prime

Reputation: 118

Instead of using repaint() you could try directly calling paint() yourself.

Graphics g;
g = getGraphics();
paint(g);

This way it will paint immediately whenever you want to make a change. Assign this to another function and call it instead when you want to see your changes right away.

Upvotes: -3

MadProgrammer
MadProgrammer

Reputation: 347334

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)...

Upvotes: 1

Related Questions