| Home > Core Java FAQ
> Drawing FAQ |
| Drawing |
| Drawing
AWT Components (10) * Loading
and Drawing Images (17) * Images
(11) |
| |
|
Q . What is the paint method for, when is it invoked, and by whom?
|
Ans :
The paint
method is usually invoked by the Abstract Window Toolkit (AWT),
via the update method, in order to
provide an up-to-date image on screen of the component.
The
paint method is the fundamental means by which an AWT
component draws itself on the screen. You need to define paint
in your own code if you subclass from a Component
class that does not already know how to draw itself—most
commonly Canvas, Panel, or Applet.
Other Component subclasses, such as Button
and TextField, know how to draw themselves, and need
no further help from you.
Typically, paint is
invoked for you by update; but it can also be invoked
directly, bypassing update, such as when a Component
is scrolled or resized, or when part of a window is exposed after
being covered by another window.
Note: In the JDK 1.0.2 and
1.1, all Component painting is run by a central AWT
thread. If a Component's paint method
hogs time or blocks completely, it can stop the entire application
from updating its appearance
|
|
Q . What should I put in my paint method?
|
Ans :
Put the
minimum necessary to paint
your Component completely and
quickly.
The
purpose of paint is to provide a quick snapshot of
what's happening in a program, rather than to make part of the
action happen. In other words, paint should show
state, not compute it. An applet displaying a bouncing ball, for
example, should calculate the new ball position, spin, and so on
outside of the paint method (hence, in a separate
thread from paint); paint can then
simply read the currrent coordinates for the ball and draw it
accordingly.
A well-written paint
method should execute quickly and should fully represent the
component's visible state at any arbitrary time. There are handy
tests for these requirements. First, watch how your component
behaves when you scroll it into view. A speedy paint
method allows a component to be repainted many times with little
noticeable delay. Some existing applets fail this test
spectacularly, often because they include lengthy state
calculations in their paint methods. Second, cover
your application's window and then re-expose it. Your components
should show everything you want them to show.
|
|
Q . What is repaint for, when is it invoked, and by whom?
|
Ans :
You invoke repaint
on a component whenever you want the component to be redrawn.
The
repaint method issues a request that a component be
redrawn within a specified time, or as soon as possible if the
time is left unspecified. The JDK (1.0.2 and 1.1) organizes all
drawing requests via a central drawing authority, run in a single
thread. This ensures that different drawing requests don't
interrupt each other and leave the screen in an inconsistent
state. The central AWT thread responds to repaint
requests by invoking update as needed on the
components requesting a repaint.
You normally invoke repaint
on a component when you know it no longer accurately reflects the
internal state of your program. For example, if your program
contains several graphing components connected to a data set, you
can keep the graphs current by invoking repaint on
each of them after any change to the data set.
For more complex programs, you need
to be aware of the threaded implementation of repaint.
First, repaint requests are asynchronous, which means
that an invocation of repaint returns immediately
rather than waiting for the drawing to complete. Second, if a Component
has two or more unfulfilled repaint requests waiting
in the drawing queue, the AWT may merge them into a single
request. Your code should not make assumptions about the number of
actual paint or update calls that will
occur in response to your repaint requests.
|
|
Q . Why do my repeated calls to repaint not have any effect?
|
Ans :
The repaint
method issues drawing requests, but your code may be keeping the
system too busy to fulfill those requests.
The repaint
method issues a request for the component to be visually updated
but relies on other methods to carry out the actual work. The
underlying question is why these other methods are not doing their
work.
The repaint method is
asynchronous: it issues a request to the central drawing thread
and returns immediately without waiting for the drawing work to
finish. A common mistake is to keep the system (CPU) fully
occupied in a tight loop by giving it nonstop repeated repaint
requests. If the virtual machine implementation in your platform
doesn't provide time-slicing for threads, the drawing thread may
never get a turn to run. You can help the drawing thread by
putting some breathing space in your repaint loop, by
means of Thread's sleep or yield method.
|
|
Q . What is update for, when is it invoked, and by whom?
|
Ans :
In response
to repaint requests for a component, the AWT invokes update
to make the component bring its appearance up to date.
The
update method works together with Component's
paint and repaint methods to keep a
program's appearance up to date with any changes in its internal
state. The key difference between paint and update
is that paint must be able to draw the entire
component from scratch, but update can be used for
more selective and efficient drawing. Your update
method needs to draw only enough to bring the Component's
appearance up to date from the last time it was drawn. For
example, if a textual object deleted its last line, update
could merely cover the space for the last line with the background
color, whereas paint should redraw the entire object,
unchanged and changed parts alike.
The AWT invokes update
on Components when it wants them to redraw
themselves, typically in response to repaint calls
made by your program. Note that the default implementation of update
in the JDK (1.0.2 and 1.1) has no special knowledge of how to
redraw the component selectively, so it does the safe thing and
redraws the whole component from the ground up:
/* in Component.java (JDK 1.0.2 and 1.1): */
public void update(Graphics g) {
g.setColor(getBackground());
g.fillRect(0, 0, width, height);
g.setColor(getForeground());
paint(g);
}
In
cases where you know more about how to optimize your drawing, you
can, and should, override update to redraw only as
much as needed. Some applets, for example, redefine update
so that it simply paints on top of what's already present:
public void update(Graphics g) {
g.setColor(getForeground());
paint(g);
}
This step helps reduce
flicker in simple animations where the applet redraws its entire
area. Note that full-scale animation with moving parts over a
constant background almost always requires a more sophisticated update
method and double buffering.
|
|
Q . What drawing occurs if my applet or other component reappears after being covered by some other window?
|
Ans :
The
background is filled, and paint
is invoked.
When
an applet or other component is re-exposed after lying behind
another window, your program must be able to redraw the re-exposed
or "damaged" area. First, the damaged area is filled
with the background color (often unpreventably by native platform
itself); then the AWT invokes paint on the affected
components. Window re-exposure is one of a few cases in which your
program receives a paint call directly without first
going through the update method. Two other cases are
when a component is first put on screen and when a component is
scrolled.
|
|
Q . Can I implement an invisible or partly transparent component?
|
Ans :
In the JDK
1.0.2 you can't, but the JDK 1.1 lays the groundwork to enable
"lightweight" components that can be partly or totally
transparent.
In the JDK 1.0.2,
all components cover their space on screen with an opaque
background color, on top of which you can draw. There is no way in
the JDK 1.0.2 to have all or part of an AWT component be
transparent. Starting with the JDK 1.1, the AWT supports
lightweight, peerless components that can draw themselves without
the use of a native-platform peer. Peerless components can draw
themselves as much or as little as they like, and whatever parts
of their area they don't draw on remain transparent.
To define transparent Component
subclasses, extend either Component or Container, and
then define your subclass pretty much the same as you would any
other Component:
/* using JDK 1.1: */
public class MyComponent extends Component {
// ...
public void paint(Graphics g) {
// ... code to draw the component
}
}
The code you put in paint
can be just like paint code for nontransparent
components. The difference is simply that the background is not
automatically filled in. Any part of the component not covered by
its paint method will be transparent.
Another key point about using
lightweight components is to make sure that the parent of any
lightweight component has an appropriate paint method
itself that will invoke paint on the lightweight
component. The default paint method inherited from
Container manages this for you, so you get it for free if your
container class doesn't override paint. If your
container class must override paint, though, just be
sure to include an invocation of super.paint in your
class's paint method:
/* overriding paint in a container class: */
public void paint(Graphics g) {
// ... do this class's special paint processing
super.paint(g); // invoke default paint to handle
// lightweight components
}
|
|
Q .
How does the XOR drawing mode work?
|
Ans
:
The XOR
drawing mode flips pixel colors in a reversible fashion.
The
Graphics class's setXORMode method
allows your program to draw in a two-way reversible fashion.
Instead of irrevocably covering previous colors with the Graphics
instance's current color, setXORMode specifies a
toggle color that will trade places with the current color on each
drawing operation. XOR drawing has the following properties:
- performing the
same draw twice restores all original colors
- the
Graphics
instance's current color switches exactly with the toggle
color on each draw, and vice versa
- other color
changes depend on the color model of the screen, but are also
reversible
If you want to use XOR
drawing, you must specifically request it. Otherwise, the Graphics
class draws in its default "paint mode,"
which simply covers any pre-existing color with the current color.
|
|
Q . When I initialize a component,I call MyComponent.getImage() to get its image. createImage() returns null! I know the image works elsewhere. What's wrong?
|
Ans
:
A
peer component needs to exist for your component before you can
get its image. This is done by the method addNotify() (surely one
of the most poorly named methods in all Java -- it doesn't
mean "add a Notify". It means "Notify that
the Component has been added to a Container". It tells the
system, "you need to create the peer for this Component
now"). addNotify will be called for you when you add your
component to a container.
Javasoft
notes that most applications do not call addNotify() directly. It
is called for you when you add the component to a container. If
you have any code that requires peer resources, you can move it
into a thread that is started from a conditional branch of
the paint() or update() method. That way the peer will definitely
exist when the code is executed.
A
common reason for seeming to require peer resources in a
constructor, or alternatively in the getPreferredSize()
method, (which is also usually before the peer is created)
is to measure the area required for your window, in terms of
font and image sizes. Font sizes may be obtained by
calling Toolkit.getDefaultToolkit().getFontMetrics(somefont).
This does not require a peer. Image sizes may be obtained by
waiting for the relevant Image to load from the ImageProducer by
using an ImageObserver, or a MediaTracker, also without
requiring a peer. If you override addNotify(), don't forget
to call super.addNotify() in your overriding version.
|
|
Q . How can I force a reload a fresh version of an image into my applet? My image file is changed periodically, and I want the applet to go and retrieve it, not cache it.
|
Ans
: You need to turn off caching for the URL.
URL url = null;
URLConnection con;
try {
url = new URL(getDocumentBase(),"image.jpg");
con = url.openConnection();
con.setUseCaches(false);
} catch (MalformedURLException e1) {
System.err.println(e1.getMessage());}
catch (IOException e2) {
System.err.println(e2.getMessage());}
Note: Some programmers have reported that it caches anyway, even if they do this. That is a browser bug.
One programmer reported that even after turning off caching and calling
image.flush() before getImage(..), he was still seeing the same picture
even though it had been changed on the server.
He worked out a solution: access the image via a cgi script that returned a URL. This redirects the browser, and he put in an Expires:
header as well to force the reload. Painful and laborious, but it
go the result.
|
|
|
|