• Post Reply Bookmark Topic Watch Topic
  • New Topic

The Java Tutorials, Swing paint demo 3

 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi,

Just wondering if anyone would mind expanding upon the explanation of a swing demo in the java tutorials please, I think I've got slightly confused:

Source
Because we are manually setting the clip, our moveSquare method invokes the repaint method not once, but twice. The first invocation tells Swing to repaint the area of the component where the square previously was (the inherited behavior uses the UI Delegate to fill that area with the current background color.) The second invocation paints the area of the component where the square currently is


relevant code snippet:



I don't really understand - how can that first call to repaint tell Swing to paint the component where the square was? at that point in time moveSquare has been called with the arguments of X and Y for the mouse. as far as i can tell, the sqaure is moved in the two lines that follow, not in the preceeding 1st call to repaint.

I'm probably misunderstanding something simple!

Thanks,

Nick

full code
 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The repaint() method just makes a painting request to the RepaintManager. For efficiency the RepaintManager will combine multiple repaint requests into a single paint request.

In the example the square has a width/height of 20 and a location of (50, 50);

If the square is moved to (75, 75) the two repaint requests would be:

(50, 50, 70, 70) and (75, 75, 95, 95)

The RepaintManager will then combine the two requests into a single rectangular area that includes both areas:

(50, 50, 95, 95)

So the area repainted is larger than if each area was painted separately. However by combining the two requests into one it is more efficient.

So when the paintComponent() method is invoked the squarex/y values will contain the squares new location.

The super.paintComponent() will clear the background of the area. Then the square will be painted at its new location.

You can verify this by adding:



at the start of the paintComponent() method.
 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
ok, i think i get it, but it seems like a slightly misleading way for oracle to phrase that statement.

also, it seems strange that the 1st repaint invocation needs to come before the change of the squareX and squareY coordinates if swing already knows it's going to combine the two requests.

i guess my question is, what is it, exactly, that that first repaint is doing? is it just setting (some of) the bounds for the repaint? if that's true i again don't quite follow why the coordination change has to come between the two repaints.

sorry if i'm being a bit slow with this, just started with the graphics side of things properly.

thanks for the quick response,

nick
 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
i guess my question is, what is it, exactly, that that first repaint is doing?


Remove the first repaint(...) statement and see what happens.

Take it click at different locations a few times and see what happens. Then reread my answer to see if it makes more sense.

Then try resizing the entire frame and see what happens.

For one more test you can also do:




Don't forget to look at the clip bounds to see what is being painted each time.
 
Steffe Wilson
Ranch Hand
Posts: 165
12
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The following describes how computer animation works in very broad terms. If I'm understanding your question correctly then this should help.
You don't see that loop in the oracle demo code because its managed inside AWT but the two calls to repaint() that occur within moveSquare() tell AWT which two specific areas of the screen have changed and need to be redrawn, ie the area where the square was and the area where the square is. The squareX, squareY variables save the new position of the object in preparation for the next time the background needs to be redrawn, ie the next time moveSquare() is called, which happens next time the mouse is dragged or clicked.
 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
tell AWT which two specific areas of the screen have changed and need to be redrawn, ie the area where the square was and the area where the square is.


No that is not accurate.

It does not track the specific areas that need to be withdrawn.

It tracks the smallest rectangular area that contains the two areas to be drawn and then repaints that area in one paint request.

Reread my answer and look at the calculation for this area to be redrawn.
 
Steffe Wilson
Ranch Hand
Posts: 165
12
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Rob Camick wrote:
tell AWT which two specific areas of the screen have changed and need to be redrawn, ie the area where the square was and the area where the square is.


No that is not accurate.

It does not track the specific areas that need to be withdrawn.

It tracks the smallest rectangular area that contains the two areas to be drawn and then repaints that area in one paint request.

Reread my answer and look at the calculation for this area to be redrawn.

Which part of my post is inaccurate and incompatible with yours?
 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
AWT which two specific areas of the screen have changed and need to be redrawn,


It does NOT redraw two specific areas of the screen.

It only redraws one larger area of the screen.

If you have the original square at (0, 0) and click at (100, 100), then it will redraw an Rectangle on (0, 0, 120, 120). So redraws a large rectangle, not the specific areas.

So if the areas are close to one another only a small area is painted. If the areas are far apart then a large area is repainted.
 
Steffe Wilson
Ranch Hand
Posts: 165
12
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Rob Camick wrote:
AWT which two specific areas of the screen have changed and need to be redrawn,


It does NOT redraw two specific areas of the screen.

It only redraws one larger area of the screen.

If you have the original square at (0, 0) and click at (100, 100), then it will redraw an Rectangle on (0, 0, 120, 120). So redraws a large rectangle, not the specific areas.

So if the areas are close to one another only a small area is painted. If the areas are far apart then a large area is repainted.

Rob, you are leaving out a key part of my statement and then saying my post is inaccurate!

This is what I said:
the two calls to repaint() that occur within moveSquare() tell AWT which two specific areas of the screen have changed and need to be redrawn

I worded my post carefully. I didn't say anything about how it was drawn by AWT, you had already covered that in detail and I was trying to clarify why repaint() was called twice which is the question that nick originally posted and nick's second post indicated he still didn't follow why the co-ordinates changed between the two calls so that is what I focused on in my post.

 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Again you state: "tell AWT which two specific areas of the screen have changed and need to be redrawn"

The wording is not careful, it implies each specific area is redrawn separately.

All the paintComponent() method does is paint a square, so if you call it twice separately it implies that two square should be drawn.

So the question is why does the first square get removed?

It is because only one large area (the intersection of the two areas) is painted. First the painting method clears the entire area and then second it paint the square at its new location.

You also talk about an animation loop. Well there is no loop. Again I find that confusing. There is no loop managed inside AWT.
 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
@Nick,

My answer was meant to be simple and stress that multiple repaint(...) method calls for a given area results in a single paint() request which results in the paintComponent() method being invoked for a larger area containing all the individual areas.

Then the paintComponent() method will simple clear the entire area (which removes the old square) and then paints the new square.

This is verified by adding some debug code into the paintComponent() method to display the size of area to be painted.

Then by commenting out the various repaint() statements you can see different results. This type of experimentation should help you better understand the painting process.



 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
thanks rob, the clip bounds stuff does make it a little more understandable

- taking out the first call seems to make the area repainted the height, width and position of the square at its second position only, because where it originally was is not being used to set the bounds of what is repainted.

- taking out both does nothing when clicking, no area is repainted

- resizing repaints the whole screen (i think), but does move the square occasionally, which i can't explain.

i still do think that the order of repaint, then move the squares, then repaint is a bit strange, but i definitely have a better understanding now, thanks


Steffe - the idea that the loop is essentially hidden was useful, thanks. i think you spotted that i was confusing the two calls to repaint as fullfiling the same function, (which is why the order of the steps was bothering me), when obviously they don't!

Regards,

Nick
 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
i still do think that the order of repaint, then move the squares, then repaint is a bit strange,


You don't need to do it that way you could just do:



Then every time you click the mouse the entire panel will be repainted. This is the simplest way to code it and you will never notice a difference performance wise because all you are doing is clearing the entire panel and then painting the square.

The demo code is just showing how you can be extremely particular about the area that is being repainted. Think of a more complicated component like a JTextPane or JTable. You don't want to repaint the entire component every time something changes, (as the painting or logic to determine what to paint will be far more complicated) only the row that changes. So even in the case of a JTable you might have a selected row and then you click on another row. So you need to repaint the selected row so it is unselected and then repaint the newly selected row. So again you (potentially) have the concept of two repaint() requests combined into one. Again the concept is the same you need to remove the old painting and add the new painting.
 
Brian Cole
Author
Ranch Hand
Posts: 920
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Steffe Wilson wrote:the two calls to repaint() that occur within moveSquare() tell AWT which two specific areas of the screen have changed and need to be redrawn

Rob Camick wrote:The wording is not careful, it implies each specific area is redrawn separately.

I don't see how Mr. Wilson's statement implies that the two specific regions will be redrawn separately.

The first call to repaint() does inform the repaint manager that one specific area should be redrawn. The second call to repaint() does inform the repaint manager that a second specific area should be redrawn. The repaint manager can handle this information any way it likes. Typically it will decide to call paint() only once (with a region containing the union of the two specific areas) but you should not depend on this being the case.
 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I don't see how Mr. Wilson's statement implies that the two specific regions will be redrawn separately.


Maybe it is my (mis?)understanding of the English language. When I see the word "and" used in a sentence I assume the context of the sentence is:

1) two specific areas of the screen have changed
2) two specific areas of the screen need to be redrawn

It doesn't say one area containing the two changed areas will be painted. It says two areas will be painted.

The repaint manager can handle this information any way it likes.


And that is my point.

When you see the repaint() method being invoked the logical assumption is that the painting is done right away since Swing is designed to be single Threaded and all events are executed in the order in which they are received.

You need to understand that the repaint() method just forwards a request to the RepaintManager. In turn the RepaintManager will consolidate the two requests into one.

If the repaint manager treats each request separately, then the code will NOT work.

In the context of explaining how this painting example works, it is necessary for the RepaintManager to behave normally, which IS to combine the two requests into one.

If you want to bypass the RepaintManager you can replace the repaint(...) statements with paintImmediately(...) statements.

If you do this then painting code will NOT WORK. That is, the original square will not be removed.

I did not want there to be any ambiguity in the wording:

This code will ONLY work because the RepaintManager consolidates the two requests into one.

 
Brian Cole
Author
Ranch Hand
Posts: 920
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Rob Camick wrote:
You need to understand that the repaint() method just forwards a request to the RepaintManager. In turn the RepaintManager will consolidate the two requests into one. If the repaint manager treats each request separately, then the code will NOT work.

Well I must admit that you've lost me. Why wouldn't it work? It seems to me it should work fine. Whether it does the painting in one call or two is an implementation detail, not a correctness issue.
 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Why wouldn't it work? It seems to me it should work fine


Well, if the RepaintManager isn't going to consolidate repaint() requests then you would expect it to do the painting as soon as the request is received.

So to simulate this I suggested you try using paintImmediately() and you will see the painting behaviour doesn't work as expected. That is the original square is not removed.

But you are correct. If each repaint request is queued up and appended to the end of the EDT, then it doesn't matter if each individual area is repainted separately.

If you add debug code to the paintComponent() method you will see the method is only invoked once, not twice.

I was just trying to explain what was happening in the context of the sample program, which is why I was trying to highlight that only one area is repainted, NOT two separate areas.

 
Brian Cole
Author
Ranch Hand
Posts: 920
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Rob Camick wrote:
So to simulate this I suggested you try using paintImmediately() and you will see the painting behaviour doesn't work as expected. That is the original square is not removed.

This is not because of any limitation/flaw of paintImmediately() or of the repaint manager, but is simply because of how moveSquare()'s implementation is structured. The fields squareX and squareY aren't updated until after the first call to repaint(). This causes no problems with repaint() because there will be a delay before the repaint manager calls paint(), but not so with paintImmediately().

However if we were to make a minor change to the implementation of moveSquare(), like this...

...then it will indeed remove the original square and the demo will work fine. It doesn't matter whether the calls to paint() are coalesced or not.

NOTE TO THOSE LEARNING SWING: It doesn't make sense to call paintImmediately() here. Please call repaint() instead, which allows the repaint manager optimize things for you, though you may keep this modified structure using oldX/oldY if you wish. I'm simply trying to prove a point that the demo can still work even if we do call paintImmediately().
 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
This is not because of any limitation/flaw of paintImmediately() or of the repaint manager, but is simply because of how moveSquare()'s implementation is structured.


Exactly. This question is about how does the demo code work. So I explained how two repaint() calls resulted in a single paint request.

It doesn't make sense to call paintImmediately() here. Please call repaint() instead, which allows the repaint manager optimize things for you,


Agreed. And that was the whole point of my comment, to clarify that the RepaintManager does optimize things for you and only a single painting call will be made, not two as implied by the statement I questioned.

I was not trying to guess why might happen if the RepaintManager worked differently or how you might rewrite the painting code. I was only stating what DOES happen in the demo code as presented in the tutorial.
 
Brian Cole
Author
Ranch Hand
Posts: 920
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
It's interesting that you and I see this so differently.

I disagreed that Mr. Wilson's statement implied that the two specific areas will be redrawn separately. But that's ok. Reasonable people can disagree on English semantics.

Rob Camick wrote:
This code will ONLY work because the RepaintManager consolidates the two requests into one.

But I really disagree with this. Whether the repaint manager consolidates them is irrelevant. All that matters is that the regions that have been flagged as dirty do eventually get painted, and that the object's internal state is correct when paint() is called. And whether the repaint manager calls paint() once, or twice (in either order), or even more than twice (perhaps by partitioning dirty regions for some reason) has no bearing on the object's internal state.

So it would be ok to say that the original moveSquare() code will ONLY work because the repaint manager will not call paint() until after the object's internal state has been set correctly. But, again, this has nothing to do with whether the two dirty regions are consolidated or not. The consolidation issue is a red herring that does not help in understanding what SwingPaintDemo3 is doing.
 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I have already regretted that statement as it has gotten you off topic with my original statement. However, I have also already taken back that statement when I replied:

But you are correct. If each repaint request is queued up and appended to the end of the EDT, then it doesn't matter if each individual area is repainted separately.

For the nth time I am just explaining what currently happens with the code as written in the tutorial given the current implementation of the RepaintManager:

1. Multiple repaint() requests will be consolidated in to a single paint request of one larger area.
2. A single paint request will be added to the end of the EDT.

I wanted to remove any doubt that two paint requests will ever be made in case your "English semantics" are the same as mine.

Obviously with a different implementation of the RepaintManager or listener code or painting code anything is possible. But this question is about the tutorial code, not some hypothetical code.

The consolidation issue is a red herring that does not help in understanding what SwingPaintDemo3 is doing.


Well I think it is relevant.

Many people think painting is done as soon is you invoke the repaint() request. How many times have you ever seen a question asking why the painting is not done until after a loop is finished?

Introducing the concept that the RepaintManager consolidates multiple requests in to one and therefore only repaints the final state (not intermediates states) of a component is important to understand.

It helps explain why the painting works even though the state of the component is changed between the two repaint calls.
 
Brian Cole
Author
Ranch Hand
Posts: 920
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Rob Camick wrote:
I wanted to remove any doubt that two paint requests will ever be made [...]

Obviously with a different implementation of the RepaintManager or listener code or painting code anything is possible. But this question is about the tutorial code, not some hypothetical code.


Ok, I reluctantly agree that in this particular tutorial code, if run on a JDK/JRE that exists today, a single call to moveSquare() will cause no more than one paint request to be scheduled. But I don't think this is guaranteed by the API and I would consider it incorrect to write code that somehow relies on this being the case.

If the moveSquare() method were somehow called from a thread other than the EDT, which I agree this tutorial does not do, then it would actually be possible for one call to moveSquare() to result in multiple calls to paint().
 
Brian Cole
Author
Ranch Hand
Posts: 920
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I don't think the following bit was there when I started writing my first reply, hence this follow-up.

Rob Camick wrote:
The consolidation issue is a red herring that does not help in understanding what SwingPaintDemo3 is doing.


Well I think it is relevant.

Many people think painting is done as soon is you invoke the repaint() request. How many times have you ever seen a question asking why the painting is not done until after a loop is finished?

Introducing the concept that the RepaintManager consolidates multiple requests in to one and therefore only repaints the final state (not intermediates states) of a component is important to understand.

It helps explain why the painting works even though the state of the component is changed between the two repaint calls.

This message was edited 3 times.


Ah, but it's not the consolidation of multiple dirty regions that is responsible for the delay between repaint() and paint(). The delay is simply because calling repaint() causes a paint request to be inserted into the event queue, which of course will be handled not immediately but later. (If you think that the consolidation is what's responsible for the delay, then I can understand why you wrote some of these things above with which I have taken issue. But I stand by my comment that consolidation is irrelevant, a red herring. Perhaps this will have to be another place where reasonable people will disagree.)

You are correct that many people have misconceptions about how painting works. They often think painting happens immediately (false). They often think that what a component has already painted is somehow (backing store) being "remembered" by the system (false). Understanding that repaint() and paint() are decoupled is important. Once that is understood, I'm not sure knowing explicitly about RepaintManager is necessary, but no harm in knowing.

What I teach is not that intermediate states will be skipped, but that they may be skipped, and no assumptions should be made about when paint() may or may not be called by the system. It may be that many calls to repaint() get consolidated into a single paint() call. It may be that even with zero calls to repaint() that the system will make many calls to paint(). The paint() method should not care why or how it is being called but should just do its job of painting the current state of the component.
 
Rob Camick
Ranch Hand
Posts: 2699
10
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
But I stand by my comment that consolidation is irrelevant,


Once again I stress that my answer/comments are about trying to explain how the posted code works as simple as possible without getting into the complex details.

I had suggested the OP add debug code to the paintComponent() method to see:

1) how often paintComponent() is invoked
2) what the size of the clip area is

If this was done the OP would see that only a single call is made.

So it is relevant in explaining why paintComponent() is only called once and understanding that the area can both be cleared and painted in the single call.

I tried to keep the answer simple.
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!