JComponent repaint nightmare

R

Roger Davis

I am trying to draw a complicated image into an on-screen window
and make frequent, minor changes to that image, usually in
response to user input, e.g., draw a rubberband bounding box, etc.
I want to make these changes without having to completely
redraw the image , i.e., I want to redraw only the small part
of the image which has changed. Unfortunately every approach
I have tried results in failure -- when I try to redraw some
small part of my image, the unchanged (and un-redrawn) portion
of the image disappears from the display. I have been
stumbling lost through repaint() hell for three days now with
no end in sight, and am hoping that someone out there can
tell me how to do this. I am using Java 2 SE 1.4.2.

I am an X11 programmer who is new to Java, so I may be going
about this completely wrong, but here is what I am doing (based
largely on suggestions from various Java books). My drawing
object, called a Drawable, is an instantiation of my own direct
subclass of JComponent. This Drawable object is encapsulated in a
JScrollPane which has been stuffed into the content pane of a JFrame,
which also has a JMenuBar. I am doing my drawing into an off-screen
BufferedImage which I have allocated myself. Every time I perform
a drawing operation on this BufferedImage, e.g., adjusting a rubberband
bounding box, I create a Rectangle which bounds the affected area and
place it onto a queue, and call the JComponent.repaint() method. I
have overridden the JComponent.paint() method in my Drawable
class to examine this queue and transfer the appropriate subsections
of the BufferedImage into the Drawable. When my program starts
and creates the initial large, complicated image and transfers the
whole thing into the Drawable this works fine. What does *not*
work is when I subsequently redraw some small piece of the image
and then call repaint(), which forces a call to paint() -- when this
happens the entire contents of the on-screen Drawable are wiped out,
and all I see is my small updated area. Some agent external to my
Drawable's paint() method is clearing the Drawable before that
paint() method is called.

I tried overriding JComponent's update() method as suggested in
various books, but then found out that Swing does not even call
JComponent.update(), which I confirmed empirically by putting print
statements in my own overriding Drawable.update() method.

I then discovered documentation in various places which mention some
sneaky ComponentUI.update() method which may be clearing my JComponent.
However, Sun's Javadoc says that this should happen only if the
JComponent is opaque, and mine is not (at least according to isOpaque()
at the time of creation of the JComponent -- I have not explicitly set
it either way). Also, I've found other documentation that implies
that this does not happen anyway if you are working with your own
direct subclass of JComponent and have not explicitly created your
own UI delegate. (I *am* using my own direct subclass of JComponent and
I have *not* explicitly created any UI delegate. I don't even have a
clue of what a UI delegate is.)

After all of the above failed to get me anywhere, I scrapped the whole
BufferedImage approach and tried to use Swing's own double-buffering.
I have no idea if this is even possible and was not able to find any
programming examples, so I may have gone about it completely wrong,
but the bottom line is that failed also. I did a setDoubleBuffered()
on my JComponent and did all of my draws to the JComponent, then called
repaint(rect) after each drawing operation, where rect is the smallest
bounding Rectangle that contained the affected area. I did not override
JComponent.paint() in this attempt. This basically did the
same thing as my BufferedImage approach, only with a lot more flicker.

There has *got* to be a way to do this. I hope someone out there can
tell me how!

Thanks.
 
H

Harald Hein

Roger Davis said:
I am an X11 programmer who is new to Java,

Then I would ugently suggest that you get the Painting article from sun
and read it a few times.

http://java.sun.com/products/jfc/tsc/articles/painting/index.html

Then I would suggest to continue with

http://java.sun.com/docs/books/tutorial/uiswing/14painting/index.html

and

http://java.sun.com/docs/books/tutorial/2d/index.html
largely on suggestions from various Java books).

It doesn't sound as if these books are too great.
I am doing my drawing into an
off-screen BufferedImage which I have allocated myself.

If you are only doing this for double buffering, this is not necessary.
Swing does double-buffering by itself. Consider changing your algorithm
so you don't need to buffer yourself. If you buffer yourself, turn at
least Swing's double-buffering off.
I perform a drawing operation on this BufferedImage, e.g.,
adjusting a rubberband bounding box,

Rubberbands etc. are maybe the only things where I would consider using
paintImmediately, instead of going through the repaint/update/paint
mechanism.
I create a Rectangle which
bounds the affected area and place it onto a queue,

Which queue? You just claimed you use a BufferedImage. Please post
complete, small example code. Code that can be compiled and runs as-is.
and call the
JComponent.repaint() method. I have overridden the
JComponent.paint()

Override paintComponent(), not paint(). See the mentioned Painting
article.
when
this happens the entire contents of the on-screen Drawable are
wiped out, and all I see is my small updated area.

Use a debugger. Pay special attention to what is going on in the
Superclass. You do call super.paint[Component](), don't you? You know
that the clip rectangle and origin can be different in calls to
paint[Component]()? Also use the debugger to check if and how much you
copy from your BufferedImage to the Graphics object. Again, check the
mentioned article.
Some agent
external to my Drawable's paint() method is clearing the Drawable
before that paint() method is called.

There are no such agents. Yet again, learn more about the Swing/AWT
painting mechanism.
I tried overriding JComponent's update() method as suggested in
various books,

This is bullshit, because, as you have found out:
Swing does not even call
JComponent.update(), which I confirmed empirically by putting
print statements in my own overriding Drawable.update() method.

You wouldn' have to, if you would have read the ...

Throw that book away.
I then discovered documentation in various places which mention
some sneaky ComponentUI.update() method which may be clearing my
JComponent.

ComponentUI.update() is only called by the paintComponent() method, if
(a) there is a ui delegate, and (b) opaque is true.

JPanes has a ui delegate, and yes, it fills (not cleans) the background
with the background color, if opaque is true. JComponent doesn't have
one. opaque is all about filling.

If you overrider paintComponent(), you can decide for yourself what to
do. If your superclass has a ui delegate, you can use it by just
calling super.paintComponent() first in the method. If you don't have a
ui delegate, you better make sure that you satisfy the opaque flag
yourself.

However, since, you are buffering your own image, you can satisfy the
opaque flag by always at least copying as much data to the screen as
indicated via the clip region (now you know why I suggested to use a
debugger and check what's going on in the superclass and how the clip
region is set).
I don't even have a clue of what a UI
delegate is.)

Then get your ass up, and read about it.
I did a setDoubleBuffered()

It is on by default.
on my JComponent and did all of
my draws to the JComponent,

Using getGraphics()? This will never work.
There has *got* to be a way to do this. I hope someone out there
can tell me how!

Stop whining, and start learning about the paint mechanism and how to
use a debugger.
 
A

Alex Hunsley

Roger Davis wrote:
[snip]
I then discovered documentation in various places which mention some
sneaky ComponentUI.update() method which may be clearing my JComponent.
However, Sun's Javadoc says that this should happen only if the
JComponent is opaque, and mine is not (at least according to isOpaque()
at the time of creation of the JComponent -- I have not explicitly set
it either way). Also, I've found other documentation that implies
that this does not happen anyway if you are working with your own
direct subclass of JComponent and have not explicitly created your
own UI delegate. (I *am* using my own direct subclass of JComponent and
I have *not* explicitly created any UI delegate. I don't even have a
clue of what a UI delegate is.)

For starters, you should be doing your own painting in paintComponent,
and not paint (if that's what you're doing).
The update method, which you should leave well alone, calls paint, which
you should also leave well alone.
Paint calls paintComponent, paintBorder, and paintChildren. Hence, if
you override paintComponent in the right way, you should be ok.

If you don't call super.paintComponent(..) in your own paintComponent
method, swing won't do it's default blanking of the drawing area, which
is what it sounds like is happening.
You might also be interested in looking at Graphics.setClip if you want
to clip what gets drawn to a specific region.

I'm getting around, at last, to doing a web page about this very subject
(painting in swing + double buffering), I really need to finish it as it
sounds like it would be of help in your situation!

After all of the above failed to get me anywhere, I scrapped the whole
BufferedImage approach and tried to use Swing's own double-buffering.
I have no idea if this is even possible and was not able to find any
programming examples, so I may have gone about it completely wrong,
but the bottom line is that failed also. I did a setDoubleBuffered()
on my JComponent and did all of my draws to the JComponent, then called
repaint(rect) after each drawing operation, where rect is the smallest
bounding Rectangle that contained the affected area. I did not override
JComponent.paint() in this attempt. This basically did the
same thing as my BufferedImage approach, only with a lot more flicker.

There has *got* to be a way to do this. I hope someone out there can
tell me how!

Don't draw individual bits and then call repaint() after each bit. As
long as you override paintComponent, you can control what gets painted.
Call repaint() when you want to force paintComponent to get called.

I think doing your own double buffering sounds ideal in this situation.
 
A

Alex Hunsley

Roger said:
I am trying to draw a complicated image into an on-screen window
and make frequent, minor changes to that image, usually in
[snip]

P.S. posting code would help a lot. If not to the group, to me, at
(e-mail address removed) (remove the tooth from the email address to
get correct address).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top