Why won't repaint() always call paintComponent and why it doesn't always behave properly when it's called
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)...
Admin
Updated on June 14, 2022Comments
-
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 over 11 yearsI'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 about 9 years@sdasdadas why is it not a good idea to call paint()? I have heard this, but do not fully understand.
-
Ungeheuer about 9 yearsHow 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 about 9 years@JohnnyCoder That would depend on what you are trying to do
-
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 about 9 yearsSwing 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