This week's book giveaway is in the Jython/Python forum.
We're giving away four copies of Murach's Python Programming and have Michael Urban and Joel Murach on-line!
See this thread for details.
Win a copy of Murach's Python Programming this week in the Jython/Python forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic

call repaint() 200 times in a loop and it only calls paintComponent once - how does it know?  RSS feed

 
Marshall Crenshaw
Greenhorn
Posts: 6
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I have been baffled by the functioning of repaint() - and the SwingPaintDemo3 with the moving square seems mysterious - you call repaint(x,y,w,h) twice and the first time it clears the clip area and the 2nd time it paints the red box. The code in paintComponent tells it to paint the box both times, yet somehow it ignores the initial box and only paints the 2nd one.

I've been writing code to bounce some balls in a box to try and understand the behavior. I set up an array of ball objects, loop through them, move them adjusting for collisions with walls and each other, then repaint(). I call repaint x2 for each ball, just like in the example. In my paintComponenet code, if I try to just paint the current ball only one ball will move, even if I send a different ball object each time. The only way to get all the balls to show up is to put a loop in paintComponenet that goes through all 100 balls every time I call it. I was worried that to move 100 balls I was painting 100x100 times.

So I put some System.out.println commands in my ball move loop, inside my object ball draw commands, and inside the paint component.


In my ball object I have the code to draw a ball


With 100 balls I get the following output:

Entering calculateMoveBall [ 1]
Calling drawBall
Entering drawBall [ 1]
b: ( 102, 49) - thisBall: ( 102, 49) call repaint
b: ( 103, 53) - thisBall: ( 103, 53) call repaint
Leaving drawBall
Exiting calculateMoveBall

Entering calculateMoveBall [ 2]
Calling drawBall
Entering drawBall [ 2]
b: ( 212, 125) - thisBall: ( 212, 125) call repaint
b: ( 213, 121) - thisBall: ( 213, 121) call repaint
Leaving drawBall
Exiting calculateMoveBall

Entering calculateMoveBall [ 3]
Calling drawBall
Entering drawBall [ 3]
b: ( 220, 202) - thisBall: ( 220, 202) call repaint
b: ( 216, 202) - thisBall: ( 216, 202) call repaint
Leaving drawBall
Exiting calculateMoveBall

Entering calculateMoveBall [ 4]
Calling drawBall
Entering drawBall [ 4]
b: ( 354, 136) - thisBall: ( 354, 136) call repaint
b: ( 351, 133) - thisBall: ( 351, 133) call repaint
Leaving drawBall
Exiting calculateMoveBall

Entering calculateMoveBall [ 5]
etc.
.............
Entering calculateMoveBall [98]
Calling drawBall
Entering drawBall [98]
b: ( 266, 946) - thisBall: ( 266, 946) call repaint
b: ( 270, 945) - thisBall: ( 270, 945) call repaint
Leaving drawBall
Exiting calculateMoveBall

Entering calculateMoveBall [99]
Calling drawBall
Entering drawBall [99]
b: ( 748, 929) - thisBall: ( 748, 929) call repaint
b: ( 750, 926) - thisBall: ( 750, 926) call repaint
Leaving drawBall
Exiting calculateMoveBall

Entering calculateMoveBall [100]
Calling drawBall
Entering drawBall [100]
b: ( 978, 830) - thisBall: ( 978, 830) call repaint
b: ( 979, 826) - thisBall: ( 979, 826) call repaint
Leaving drawBall
Exiting calculateMoveBall
Entering paintComponent with [100] ( 979, 826)
Entering paintBalls [ 1] ( 73, 878)
Exiting paintBalls
Entering paintBalls [ 2] ( 253, 454)
Exiting paintBalls
Entering paintBalls [ 3] ( 431, 827)
Exiting paintBalls
Entering paintBalls [ 4] ( 420, 854)
Exiting paintBalls
Entering paintBalls [ 5] ( 772, 270)
Exiting paintBalls
Entering paintBalls [ 6] ( 544, 315)
Exiting paintBalls
Entering paintBalls [ 7] ( 283, 402)
Exiting paintBalls
Entering paintBalls [ 8] ( 889, 975)
Exiting paintBalls
Entering paintBalls [ 9] ( 787, 796)
Exiting paintBalls
Entering paintBalls [10] ( 182, 294)
Exiting paintBalls
etc...

Entering paintBalls [97] ( 76, 982)
Exiting paintBalls
Entering paintBalls [98] ( 270, 945)
Exiting paintBalls
Entering paintBalls [99] ( 750, 926)
Exiting paintBalls
Entering paintBalls [100] ( 979, 826)
Exiting paintBalls
Exiting paintComponent


So even though I called repaint() 200 times (twice for each ball), repaint was actually only called once, drew all the balls at once, and then went back. And I just noticed it appears to have waited until I exited the calculateMoveBall loop to go into paintComponent!
The spooky things is how does it know to do that? Does the Java machine 'see' that it is inside of a loop, and perhaps also sees the loop inside of paintComponent, and somehow correctly guesses that it doesn't have to do it 200 times, but can wait and do it once?
If I attempt to do the same thing in code, take the loop out of paintComponent() and call repaint() with the current ball, expecting the machine to do exactly what I tell it, it refuses and does it's own thing, waiting to call paintComponent on the 100th ball, drawing only the last ball (so I guess the loop inside paintComponent is not in the logic). So a call to repaint() is a request for a higher being to decide if it has the time or energy to repaint the clip. If not, it ignores the call, or stacks them up for later (maybe I should try a million and see if it has room for that!) - well so far up to 4000 it behaves the same.
This is helpful if you are happy with "this is how it works so use it that way". However I really don't like having some kind of hidden logic that I have to trust to work the right way. If I don't want it to wait until later I'm not sure what to do. And I don't trust the machine to do whatever whenever. How do you debug that???

I guess I wanted to show what I found and ask for comments.

Questions:
Is there documentation to know what repaint() will do or how it decides when to call paintComponent? The Swing tutorial gives the example but not the why.
"By now you know that the paintComponent method is where all of your painting code should be placed. It is true that this method will be invoked when it is time to paint"
"An important point worth noting is that although we have invoked repaint twice in a row in the same event handler, Swing is smart enough to take that information and repaint those sections of the screen all in one single paint operation. In other words, Swing will not repaint the component twice in a row, even if that is what the code appears to be doing." (What the code appears to be doing - now we have to guess what it is doing)

Is there a way to force repaint() to call paintComponent on a clip rectangle (not just on the whole thing?) I would think invalidate() would force repainting of the whole componenet.

Perhaps this is when you draw to a bitmap in memory and paint the whole thing on the screen...
 
Rob Camick
Ranch Hand
Posts: 2752
11
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
- you call repaint(x,y,w,h) twice and the first time it clears the clip area and the 2nd time it paints the red box.


It is not efficient to paint components individually. The repaint(...) method makes a request to the RepaintManager. The RepaintManager then gathers all the requests an determines the area affected by all the requests and then after a certain period of time will make a single call to the paint() method which in turn invoked the paintComponent() method of the component.

So for example if you make two repaint requests like the following:



the RepaintManager would combine the two requests into the smallest area possible to honour both requests and basically invoke the paint() method with something like:



I would think invalidate() would force repainting of the whole componenet.


invalidate() has nothing to do with painting. It has to do with the layout of component and invoking the layout managers. Basically when you add/remove components from a panel on a visible GUI you need to invoke revalidate() to make sure the layout manager is invoked and components are positioned properly on the panel. Then you invoke repaint() on the panel to make sure the components get painted.
 
Brian Cole
Author
Ranch Hand
Posts: 932
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Marshall Crenshaw wrote:I have been baffled by the functioning of repaint() - and the SwingPaintDemo3 with the moving square seems mysterious - you call repaint(x,y,w,h) twice and the first time it clears the clip area and the 2nd time it paints the red box. The code in paintComponent tells it to paint the box both times, yet somehow it ignores the initial box and only paints the 2nd one.


What MyPanel.paintComponent() does in SwingPaintDemo3 is clear everything (by painting with the background color) and then draw the red square anew. (That's why it keeps track of the square's location with the private ints. It doesn't know how many times it will be called on to paint it.) The method could presumably call g.getClipBounds() to examine what its clipping region actually is, but that would be weird. It usually shouldn't care.

The first call to repaint() is to mark the square's old location dirty. The second call to repaint() is to mark the square's new location dirty. There may be other dirty regions also, for example the OS may have popped up and dismissed a dialog box over SwingPaintDemo3. The two repaint() calls will probably be coalesced into a single call to paintComponent(), which is good for efficiency.


Questions:
Is there documentation to know what repaint() will do or how it decides when to call paintComponent? The Swing tutorial gives the example but not the why.
"By now you know that the paintComponent method is where all of your painting code should be placed. It is true that this method will be invoked when it is time to paint"
"An important point worth noting is that although we have invoked repaint twice in a row in the same event handler, Swing is smart enough to take that information and repaint those sections of the screen all in one single paint operation. In other words, Swing will not repaint the component twice in a row, even if that is what the code appears to be doing." (What the code appears to be doing - now we have to guess what it is doing)


There's no way to know precisely when paintComponent() will be called. Or rather, you could probably track down all the source code and eventually figure it out, but the point is that you're not supposed to know. The system will call paintComponent() whenever it thinks its necessary, which can depend on all sorts of stuff external to the program. If you call the other version of repaint() in which the first argument is a long number of milliseconds, it will do its best to call paintComponent() sometime within that timeframe, but there is no guarantee.


Is there a way to force repaint() to call paintComponent on a clip rectangle (not just on the whole thing?)


No. You can give it a hint by providing coordinate arguments to repaint(), but it could ignore them and decide to repaint the whole thing (or any region that contains what you specify). On most operating systems, if you provide coordinates and no other regions are marked dirty, it will usually repaint only the region that you specify. But, again, there is no guarantee. And if other regions are marked dirty for some reason, then obviously they will have to be repainted too.
 
Campbell Ritchie
Sheriff
Posts: 53750
127
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Another thing: repainting take s a certain time, maybe a few milliseconds. Your loop will run in more like 0.01μs, so the painting manager object may be unable to keep up with the loop.

And since my answer is so much less than the preceding two answers. and I think they are bl**d* good answers, I think I shall have to fling some cows around.

[edit]I would be embarrassed to say what the spelling correction was [/edit]
 
Tim Holloway
Bartender
Posts: 18531
61
Android Eclipse IDE Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Short version: repaint doesn't call the render routines, it tells the regularly-scheduled render routines that repainting is required. You typically do this by invalidating sections of the display (marking them as needing repainting).

In many UIs - including Swing, the actual painting is done by a completely different thread.
 
It is sorta covered in the JavaRanch Style Guide.
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!