Painting in AWT
and Swing
Good Painting Code Is
the Key to App Performance
By Amy Fowler
In
a graphical system, a windowing toolkit is usually responsible
for providing a framework to make it relatively painless for a graphical
user interface (GUI) to render the right bits to the screen
at the right time.
Both the AWT (abstract windowing toolkit) and Swing provide such
a framework. But the APIs that implement it are not well understood
by some developers -- a problem that has led to programs not performing
as well as they could.
This article explains the AWT and Swing paint mechanisms in detail.
Its purpose is to help developers write correct and efficient GUI
painting code. While the article covers the general paint mechanism
(where and when to render), it does not tell how to
use Swing's graphics APIs to render a correct output. To learn how
to render nice graphics, visit the Java
2D Web site.
The main topics covered in this article are:
Evolution of the Swing Paint System
When the original AWT API was developed for JDK 1.0, only heavyweight
components existed ("heavyweight" means that the component
has it's own opaque native window). This allowed the AWT to rely
heavily on the paint subsystem in each native platform. This scheme
took care of details such as damage detection, clip calculation,
and z-ordering. With the introduction of lightweight components
in JDK 1.1 (a "lightweight" component is one that reuses
the native window of its closest heavyweight ancestor), the AWT
needed to implement the paint processing for lightweight components
in the shared Java code. Consequently, there are subtle differences
in how painting works for heavyweight and lightweight components.
After JDK 1.1, when the Swing toolkit was released, it introduced
its own spin on painting components. For the most part, the Swing
painting mechanism resembles and relies on the AWT's. But it also
introduces some differences in the mechanism, as well as new APIs
that make it easier for applications to customize how painting works.
Painting in AWT
To understand how AWT's painting API works, helps to know what
triggers a paint operation in a windowing environment. In AWT, there
are two kinds of painting operations: system-triggered painting,
and application-triggered painting.
System-triggered Painting
In a system-triggered painting operation, the system requests a
component to render its contents, usually for one of the following
reasons:
- The component is first made visible on the screen.
- The component is resized.
- The component has damaged that needs to be repaired. (For example,
something that previously obscured the component has moved, and
a previously obscured portion of the component has become exposed).
App-triggered Painting
In an application-triggered painting operation, the component decides
it needs to update its contents because its internal state has changed.
(For example,. a button detects that a mouse button has been pressed
and determines that it needs to paint a "depressed" button
visual).
Regardless of how a paint request is triggered, the AWT uses a
"callback" mechanism for painting, and this mechanism
is the same for both heavyweight and lightweight components. This
means that a program should place the component's rendering code
inside a particular overridden method, and the toolkit will invoke
this method when it's time to paint. The method to be overridden
is in java.awt.Component :
public void paint(Graphics g)
When AWT invokes this method, the Graphics object
parameter is pre-configured with the appropriate state for drawing
on this particular component:
- The
Graphics object's color is set to the
component's foreground property.
- The
Graphics object's font is set to the
component's font property.
- The
Graphics object's translation is set
such that the coordinate (0,0) represents the upper left corner
of the component.
- The
Graphics object's clip rectangle is
set to the area of the component that is in need of repainting.
Programs must use this Graphics object (or one derived
from it) to render output. They are free to change the state of
the Graphics object as necessary.
Here is a simple example of a paint callback which renders a filled
circle in the bounds of a component:
public void paint(Graphics g) {
// Dynamically calculate size information
Dimension size = getSize();
// diameter
int d = Math.min(size.width, size.height);
int x = (size.width - d)/2;
int y = (size.height - d)/2;
// draw circle (color already set to foreground)
g.fillOval(x, y, d, d);
g.setColor(Color.black);
g.drawOval(x, y, d, d);
}
Developers who are new to AWT might want to take a peek at the
PaintDemo example, which provides a
runnable program example of how to use the paint callback in an
AWT program.
In general, programs should avoid placing rendering code at any
point where it might be invoked outside the scope of the paint callback.
Why? Because such code may be invoked at times when it is not appropriate
to paint -- for instance, before the component is visible or has
access to a valid Graphics object. It is not recommended
that programs invoke paint() directly.
To enable app-triggered painting, the AWT provides the following
java.awt.Component methods to allow programs to asynchronously
request a paint operation:
public void repaint()
public void repaint(long tm)
public void repaint(int x, int y, int width, int height)
public void repaint(long tm, int x, int y,
int width, int height)
The following code shows a simple example of a mouse listener that
uses repaint() to trigger updates on a theoretical
button component when the mouse is pressed and released:
MouseListener l = new MouseAdapter() {
public void mousePressed(MouseEvent e) {
MyButton b = (MyButton)e.getSource();
b.setSelected(true);
b.repaint();
}
public void mouseReleased(MouseEvent e) {
MyButton b = (MyButton)e.getSource();
b.setSelected(false);
b.repaint();
}
};
Components that render complex output should invoke repaint()
with arguments defining only the region that requires updating.
A common mistake is to always invoke the no-arg version, which causes
a repaint of the entire component, often resulting in unnecessary
paint processing.
paint() vs. update()
Why do we make a distinction between "system-triggered"
and. "app-triggered" painting? Because AWT treats each
of these cases slightly differently for heavyweight components (the
lightweight case will be discussed later), which is unfortunately
a source of great confusion.
For heavyweight components, these two types of painting happen
in the two distinct ways, depending on whether a painting operation
is system-triggered or app-triggered.
System-triggered painting
This is how a system-triggered painting operation takes place:
- The AWT determines that either part or all of a component
needs to be painted.
- The AWT causes the event dispatching thread to invoke
paint()
on the component.
App-triggered painting
An app-triggered painting operation takes place as follows:
- The program determines that either part or all of a component
needs to be repainted in response to some internal state change.
- The program invokes
repaint() on the component,
which registers an asynchronous request to the AWT that this component
needs to be repainted.
- The AWT causes the event dispatching thread to invoke
update()
on the component.
NOTE: If multiple calls to repaint()
occur on a component before the initial repaint request is processed,
the multiple requests may be collapsed into a single call to
update() . The algorithm for determining when multiple
requests should be collapsed is implementation-dependent. If
multiple requests are collapsed, the resulting update rectangle
will be equal to the union of the rectangles contained in the
collapsed requests.
- If the component did not override
update() , the
default implementation of update() clears the component's
background (if it's not a lightweight component) and simply calls
paint() .
Since by default, the final result is the same (paint()
is called), many people don't understand the purpose of having a
separate update() method at all. While it's true that
the default implementation of update() turns
around and calls paint() , this update "hook"
enables a program to handle the app-triggered painting case differently,
if desired. A program must assume that a call to paint()
implies that the area defined by the graphic's clip rectangle is
"damaged" and must be completely repainted, however a
call to update() does not imply this, which enables
a program to do incremental painting.
Incremental painting is useful if a program wishes to layer additional
rendering on top of the existing bits of that component. The UpdateDemo
example demonstrates a program which benefits from using update()
to do incremental painting.
In truth, the majority of GUI components do not need to do incremental
drawing, so most programs can ignore the update() method
and simply override paint() to render the component
in it's current state. This means that both system-triggered and
app-triggered rendering will essentially be equivelent for most
component implementations.
Painting & Lightweight Components
From an application developer's perspective, the paint API is basically
the same for lightweights as it is for heavyweights (that is, you
just override paint() and invoke repaint()
to trigger updates). However, since AWT's lightweight component
framework is written entirely in common Java code, there are some
subtle differences in the way the mechanism is implemented for lightweights.
How Lightweights Get Painted
For a lightweight to exist, it needs a heavyweight somewhere up
the containment hierarchy in order to have a place to paint. When
this heavyweight ancestor is told to paint its window, it must translate
that paint call to paint calls on all of its lightweight descendents.
This is handled by java.awt.Container 's paint()
method , which calls paint() on any of its visible,
lightweight children which intersect with the rectangle to be painted.
So it's critical for all Container subclasses (lightweight or heavyweight)
that override paint() to do the following:
public class MyContainer extends Container {
public void paint(Graphics g) {
// paint my contents first...
// then, make sure lightweight children paint
super.paint(g);
}
}
If the call to super.paint() is missing, then the
container's lightweight descendents won't show up (a very common
problem when JDK 1.1 first introduced lightweights).
It's worth noting that the default implementation of Container.update()
does not use recursion to invoke update() or
paint() on lightweight descendents. This means that
any heavyweight Container subclass that uses update()
to do incremental painting must ensure that lightweight descendents
are recursively repainted if necessary. Fortunately, few heavyweight
container components need incremental painting, so this issue doesn't
affect most programs.
Lightweights & System-triggered Painting
The lightweight framework code that implements the windowing behaviors
(showing, hiding, moving, resizing, etc.) for lightweight components
is written entirely in Java. Often, within the Java implementation
of these functions, the AWT must explicitly tell various lightweight
components to paint (essentially system-triggered painting, even
though it's no longer originating from the native system).
However, the lightweight framework uses repaint() to
tell components to paint, which we previously explained results
in a call to update() instead of a direct call
to paint() . Therefore, for lightweights, system-triggered
painting can follow two paths:
- The system-triggered paint request originates from the native
system (i.e. the lightweight's heavyweight ancestor is first
shown), which results in a direct call to
paint() .
- The system-triggered paint request originates from the lightweight
framework (i.e., the lightweight is resized), which
results in a call to
update() , which by default is
forwarded to paint() .
In a nutshell, this means that for lightweight components there
is no real distinction between update() and paint() ,
which further implies that the incremental painting technique should
not be used for lightweight components.
Lightweights and Transparency
Since lightweight components "borrow" the screen real
estate of a heavyweight ancestor, they support the feature of transparency.
This works because lightweight components are painted from back
to front and therefore if a lightweight component leaves some or
all of its associated bits unpainted, the underlying component will
"show through." This is also the reason that the default
implementation of update() will not clear the background
if the component is lightweight.
The LightweightDemo sample
program demonstrates the transparency feature of lightweight components.
"Smart" Painting
While the AWT attempts to make the process of rendering components
as efficient as possible, a component's paint() implementation
itself can have a significant impact on overall performance. Two
key areas that can affect this process are:
- Using the clip region to narrow the scope of what is rendered.
- Using internal knowledge of the layout to narrow the scope of
what children are painted (lightweights only).
If your component is simple -- for example, if it's a pushbutton
-- then it's not worth the effort to factor the rendering in order
to only paint the portion that intersects the clip rectangle; it's
preferable to just paint the entire component and let the graphics
clip appropriately. However, if you've created a component that
renders complex output, like a text component, then it's critical
that your code use the clip information to narrow the amount of
rendering.
Further, if you're writing a complex lightweight container that
houses numerous components, where the component and/or its layout
manager has information about the layout, then it's worth using
that layout knowledge to be smarter about determining which of the
children must be painted. The default implementation of Container.paint()
simply looks through the children sequentially and tests for visibility
and intersection -- an operation that may be unnecessarily inefficient
with certain layouts. For example, if a container layed out the
components in a 100x100 grid, then that grid information could be
used to determine more quickly which of those 10,000 components
intersect the clip rectangle and actually need to be painted.
AWT Painting Guidelines
The AWT provides a simple callback API for painting components.
When you use it, the following guidelines apply:
- For most programs, all client paint code should be placed within
the scope of the component's
paint() method.
- Programs may trigger a future call to
paint() by
invoking repaint() , but shouldn't call paint()
directly.
- On components with complex output,
repaint() should
be invoked with arguments which define only the rectangle that
needs updating, rather than the no-arg version, which causes the
entire component to be repainted.
- Since a call to
repaint() results first in a call
to update() , which is forwarded to paint()
by default, heavyweight components may override update()
to do incremental drawing if desired (lightweights do not support
incremental drawing)
- Extensions of
java.awt.Container which override
paint() should always invoke super.paint()
to ensure children are painted.
- Components which render complex output should make smart use
of the clip rectangle to narrow the drawing operations to those
which intersects with the clip area.
Painting in Swing
Swing starts with AWT's basic painting model and extends it further
in order to maximize performance and improve extensibility. Like
AWT, Swing supports the paint callback and the use of repaint()
to trigger updates. Additionally, Swing provides built-in support
for double-buffering as well as changes to support Swing's additional
structure (like borders and the UI delegate). And finally, Swing
provides the RepaintManager API for those programs
who want to customize the paint mechanism further.
Double Buffering Support
One of the most notable features of Swing is that it builds support
for double-buffering right into the toolkit. It does the by providing
a "doubleBuffered" property on javax.swing.JComponent :
public boolean isDoubleBuffered()
public void setDoubleBuffered(boolean o)
Swing's double buffer mechanism uses a single offscreen buffer
per containment hierarchy (usually per top-level window) where double-buffering
has been enabled. And although this property can be set on a per-component
basis, the result of setting it on a particular container will have
the effect of causing all lightweight components underneath that
container to be rendered into the offscreen buffer, regardless of
their individual "doubleBuffered" property values.
By default, this property is set to true for all
Swing components. But the setting that really matters is on JRootPane ,
because that setting effectively turns on double-buffering for everything
underneath the top-level Swing component. For the most part, Swing
programs don't need to do anything special to deal with double-buffering,
except to decide whether it should be on or off (and for smooth
GUI rendering, you'll want it on!). Swing ensures that the appropriate
type of Graphics object (offscreen image Graphics
for double-buffering, regular Graphics otherwise) is
passed to the component's paint callback, so all the component needs
to do is draw with it. This mechanism is explained in greater detail
later in this article, in the section on Paint
Processing.
Additional Paint Properties
Swing introduces a couple of additional properties on JComponent
in order to improve the efficiency of the internal paint algorithms.
These properties were introduced in order to deal with the following
two issues, which can make painting lightweight components an expensive
operation:
- Transparency: If a lightweight component is painted,
it's possible that the component will not paint all of
its associated bits if partially or totally transparent; this
means that whenever it is repainted, whatever lies underneath
it must be repainted first. This requires the system to walk up
the containment hierarchy to find the first underlying heavyweight
ancestor from which to begin the back-to-front paint operation.
- Overlapping components: If a lightweight component is
painted, its possible that some other lightweight component
partially overlaps it; this means that whenever the original lightweight
component is painted, any components which overlap the original
component (where the clip rectangle intersects with the overlapping
area) the overlapping component must also be partially repainted.
This requires the system to traverse much of the containment hierarchy,
checking for overlapping components on each paint operation.
Opacity
To improve performance in the common case of opaque components,
Swing adds a read-write opaque property to javax.swing.JComponent :
public boolean isOpaque()
public void setOpaque(boolean o)
The settings
are:
true : The component agrees to paint all of the
bits contained within its rectangular bounds.
false : The component makes no guarantees about
painting all the bits within its rectangular bounds.
The opaque property
allows Swing's paint system to detect whether a repaint request
on a particular component will require the additional repainting
of underlying ancestors or not. The default value of the opaque
property for each standard Swing component is set by the current
look and feel UI object. The value is true for most
components.
One of the most common mistakes component implementations make
is that they allow the opaque property to default to
true , yet they do not completely render the area defined
by their bounds, the result is occasional screen garbage in the
unrendered areas. When a component is designed, careful thought
should be given to its handling of the opaque property,
both to ensure that transparency is used wisely, since it costs
more at paint time, and that the contract with the paint system
is honored.
The meaning of the opaque property is often misunderstood.
Sometimes it is taken to mean, "Make the component's background
transparent." However, this is not Swing's strict interpretation
of opacity. Some components, such as a pushbutton, may set the opaque
property to false in order to give the component a non-rectangular
shape, or to leave room around the component for transient visuals,
such as a focus indicator. In these cases, the component is not
opaque, but a major portion of its background is still filled in.
As defined previously, the opaque
property is primarily a contract with the repaint system. If a component
also uses the opaque property to define how transparency
is applied to a component's visuals, then this use of the property
should be documented. (It may be preferable for some components
to define additional properties to control the visual aspects of
how transparency is applied. For example, javax.swing.AbstractButton
provides the ContentAreaFilled property for this purpose.)
Another issue worth noting is how opacity relates to a Swing component's
border property. The area rendered by a Border
object set on a component is still considered to be part of that
component's geometry. This means that if a component is opaque,
it is still responsible for filling the area occupied by the border.
(The border then just layers its rendering on top of the opaque
component).
If you want a component to allow the underlying component to show
through its border area -- that is, if the border supports transparency
via isBorderOpaque() returning false --
then the component must define itself to be non-opaque and ensure
it leaves the border area unpainted.
"Optimized" Drawing
The overlapping component issue is more tricky. Even if none of
a component's immediate siblings overlaps the component, it's always
possible that a non-ancestor relative (such as a "cousin"
or "aunt") could overlap it. In such a case the repainting
of a single component within a complex hierarchy could require a
lot of treewalking to ensure 'correct' painting occurs. To reduce
unnecessary traversal, Swing adds a read-only isOptimizedDrawingEnabled
property to javax.swing.JComponent :
public boolean isOptimizedDrawingEnabled()
The settings
are:
true : The component indicates that none of its
immediate children overlap.
false : The component makes no guarantees about
whether or not its immediate children overlap
By checking the isOptimizedDrawingEnabled property,
Swing can quickly narrow its search for overlapping components at
repaint time.
Since the isOptimizedDrawingEnabled property is read-only,
so the only way components can change the default value is to subclass
and override this method to return the desired value. All standard
Swing components return true for this property, except
for JLayeredPane, JDesktopPane , and JViewPort .
The Paint Methods
The rules that apply to AWT's lightweight components also apply
to Swing components -- for instance,
paint() gets called when it's time to render -- except that
Swing further factors the paint() call into three separate
methods, which are invoked in the following order:
protected void paintComponent(Graphics g)
protected void paintBorder(Graphics g)
protected void paintChildren(Graphics g)
Swing programs should override paintComponent() instead
of overriding paint() . Although the API allows
it, there is generally no reason to override paintBorder()
or paintComponents() (and if you do, make sure you
know what you're doing!). This factoring makes it easier for programs
to override only the portion of the painting which they need to
extend. For example, this solves the AWT problem mentioned previously
where a failure to invoke super.paint() prevented any
lightweight children from appearing.
The SwingPaintDemo sample program
demonstrates the simple use of Swing's paintComponent()
callback.
Painting and the UI Delegate
Most of the standard Swing components have their look and feel
implemented by separate look-and-feel objects (called "UI delegates")
for Swing's Pluggable look and feel feature. This means that most
or all of the painting for the standard components is delegated
to the UI delegate and this occurs in the following way:
paint() invokes paintComponent() .
- If the
ui property is non-null, paintComponent()
invokes ui.update().
- If the component's
opaque property is true, ui.udpate()
fills the component's background with the background color and
invokes ui.paint() .
ui.paint() renders the content of the component.
This means that subclasses of Swing components which have a UI
delegate (vs. direct subclasses of JComponent ), should
invoke super.paintComponent() within their paintComponent
override:
public class MyPanel extends JPanel {
protected void paintComponent(Graphics g) {
// Let UI delegate paint first
// (including background filling, if I'm opaque)
super.paintComponent(g);
// paint my contents next....
}
}
If for some reason the component
extension does not want to allow the UI delegate to paint (if, for
example, it is completely replacing the component's visuals), it
may skip calling super.paintComponent() , but it must
be responsible for filling in its own background if the opaque
property is true , as discussed in the section on the
opaque property.
Paint Processing
Swing processes "repaint" requests in a slightly different
way from the AWT, although the final result for the application
programmer is essentially the same -- paint() is invoked.
Swing doesthis to support its RepaintManager API (discussed
later), as well as to improve paint performance. In Swing, painting
can follow two paths, as described below:
(A) The paint request originates on the first heavyweight ancestor
(usually JFrame, JDialog, JWindow, or JApplet ):
- the event dispatching thread invokes
paint() on
that ancestor
- The default implementation of
Container.paint()
recursively calls paint() on any lightweight descendents
- When the first Swing component is reached, the default
implementation of
JComponent.paint() does the following:
- if the component's
doubleBuffered property
is true and double-buffering is enabled on the
component's RepaintManager , will convert the
Graphics object to an appropriate offscreen graphics.
- invokes
paintComponent() (passing in offscreen
graphics if doubled-buffered)
- invokes
paintBorder() (passing in offscreen
graphics if doubled-buffered)
- invokes
paintChildren() (passing in offscreen
graphics if doubled-buffered), which uses the clip and the
opaque and optimizedDrawingEnabled
properties to determine exactly which descendents to recursively
invoke paint() on.
- if the component's
doubleBuffered property
is true and double-buffering is enabled on the
component's RepaintManager , copies the offscreen
image to the component using the original on-screen Graphics
object.
Note: the JComponent.paint() steps #1 and #5
are skipped in the recursive calls to paint()
(from paintChildren() , described in step#4) because
all the lightweight components within a Swing window hierarchy
will share the same offscreen image for double-buffering.
(B) The paint request originates from a call to repaint()
on an extension of javax.swing.JComponent :
JComponent.repaint() registers an asynchronous
repaint request to the component's RepaintManager ,
which uses invokeLater() to queue a Runnable
to later process the request on the event dispatching thread.
- The runnable executes on the event dispatching thread and causes
the component's
RepaintManager to invoke paintImmediately()
on the component, which does the following:
- uses the clip rectangle and the
opaque and
optimizedDrawingEnabled properties to determine
the 'root' component from which the paint operation must begin
(to deal with transparency and potentially overlapping components).
- if the root component's
doubleBuffered property
is true , and double-buffering is enabled on the
root's RepaintManager , will convert the Graphics
object to an appropriate offscreen graphics.
- invokes
paint() on the root component (which
executes (A)'s JComponent.paint() steps #2-4
above), causing everything under the root which intersects
with the clip rectangle to be painted.
- if the root component's
doubleBuffered property
is true and double-buffering is enabled on the
root's RepaintManager , copies the offscreen image
to the component using the original on-screen Graphics
object.
NOTE: if multiple calls to repaint() occur
on a component or any of its Swing ancestors before the repaint
request is processed, those multiple requests may be collapsed
into a single call back to paintImmediately() on
the topmost Swing component on which repaint()
was invoked. For example, if a JTabbedPane contains
a JTable and both issue calls to repaint()
before any pending repaint requests on that hierarchy are processed,
the result will be a single call to paintImmediately()
on the JTabbedPane , which will cause paint()
to be executed on both components.
This means that for Swing components, update() is
never invoked.
Although repaint() results in a call to paintImmediately() ,
it is not considered the paint "callback", and client
paint code should not be placed inside of a paintImmediately() .
In fact, there is no common reason to override paintImmediately()
at all.
Synchronous Painting
As described in the previous section, paintImmediately()
acts as the entry point for telling a single Swing component to
paint itself, making sure that all the required painting occurs
appropriately. This method may also be used for making synchronous
paint requests, as its name implies, which is sometimes required
by components which need to ensure their visual appearance 'keeps
up' in real time with their internal state (e.g. this is true for
the JScrollPane during a scroll operation).
Programs should not invoke this method directly unless there is
a valid need for real-time painting. This is because the asynchronous
repaint() will cause multiple overlapping requests
to be collapsed efficiently, whereas direct calls to paintImmediately()
will not. Additionally, the rule for invoking this method is that
it must be invoked from the event dispatching thread; it's
not an api designed for multi-threading your paint code!. For more
details on Swing's single-threaded model, see the archived article
"Threads
and Swing."
The RepaintManager
The purpose of Swing's RepaintManager class is to
maximize the efficiency of repaint processing on a Swing containment
hierarchy, and also to implement Swing's 'revalidation' mechanism
(the latter will be a subject for a separate article). It implements
the repaint mechanism by intercepting all repaint requests on Swing
components (so they are no longer processed by the AWT) and maintaining
its own state on what needs to be updated (known as "dirty
regions"). Finally, it uses invokeLater() to process
the pending requests on the event dispatching thread, as described
in the section on "Repaint Processing" (option B).
For most programs, the RepaintManager can be viewed
as part of Swing's internal system and can virtually be ignored.
However, its API provides programs the option of gaining finer control
over certain aspects of painting.
The "Current" RepaintManager
The RepaintManager is designed to be dynamically plugged,
although by default there is a single instance. The following static
methods allow programs to get and set the "current" RepaintManager :
public static RepaintManager currentManager(Component c)
public static RepaintManager currentManager(JComponent c)
public static void
setCurrentManager(RepaintManager aRepaintManager)
Replacing The "Current" RepaintManager
A program would extend and replace the RepaintManager
globally by doing the following:
RepaintManager.setCurrentManager(new MyRepaintManager());
You can also see RepaintManagerDemo
for a simple running example of installing a RepaintManager
which prints out information about what is being repainted.
A more interesting reason for extending and replacing the RepaintManager
would be to change how it processes repaint requests. Currently
the internal state used by the default implementation to track dirty
regions is package private and therefore not accessible by subclasses.
However, programs may implement their own mechanisms for tracking
dirty regions and for collapsing requests by overriding the following
methods:
public synchronized void
addDirtyRegion(JComponent c, int x, int y, int w, int h)
public Rectangle getDirtyRegion(JComponent aComponent)
public void markCompletelyDirty(JComponent aComponent)
public void markCompletelyClean(JComponent aComponent) {
The addDirtyRegion() method is the one which is invoked
when repaint() is called on a Swing component, and
thus can be hooked to catch all repaint requests. If a program overrides
this method (and does not call super.addDirtyRegion() )
then it becomes its responsibility to use invokeLater()
to place a Runnable on the EventQueue
which will invoke paintImmediately() on an appropriate
component (translation: not for the faint of heart).
Global Control Over Double-Buffering
The RepaintManager provides an API for globally enabling
and disabling double-buffering:
public void setDoubleBufferingEnabled(boolean aFlag)
public boolean isDoubleBufferingEnabled()
This property is checked inside of JComponent during
the processing of a paint operation in order to determine whether
to use the offscreen buffer for rendering. This property defaults
to true , but programs wishing to globally disable double-buffering
for all Swing components can do the following:
RepaintManager.currentManager(mycomponent).
setDoubleBufferingEnabled(false);
Note: since Swing's default implementation instantiates a single
RepaintManager instance, the mycomponent
argument is irrelevant.
Swing Painting Guidelines
Swing programs should understand these guidelines when writing
paint code:
- For Swing components,
paint() is always invoked
as a result of both system-triggered and app-triggered paint requests;update()
is never invoked on Swing components.
- Programs may trigger a future call to
paint() by
invoking repaint() , but shouldn't call paint()
directly.
- On components with complex output,
repaint() should
be invoked with arguments which define only the rectangle that
needs updating, rather than the no-arg version, which causes the
entire component to be repainted.
- Swing's implementation of
paint() factors the call
into 3 separate callbacks:
paintComponent()
paintBorder()
paintChildren()
Extensions of Swing components which wish to implement their own
paint code should place this code within the scope of the paintComponent()
method (not within paint() ).
- Swing introduces two properties to maximize painting efficiency:
opaque : will the component paint all its bits
or not?
optimizedDrawingEnabled : may any of this component's
children overlap?
- If a Swing component's
opaque property is set to
true , then it is agreeing to paint all of the bits
contained within its bounds (this includes clearing it's own background
within paintComponent() ), otherwise screen garbage
may result.
-
Setting either the opaque or optimizedDrawingEnabled
properties to false on a component will cause more
processing on each paint operation, therefore we recommend judicious
use of both transparency and overlapping components.
-
Extensions of Swing components which have UI delegates (including
JPanel ), should typically invoke super.paintComponent()
within their own paintComponent() implementation.
Since the UI delegate will take responsibility for clearing
the background on opaque components, this will take care of
#5.
-
Swing supports built-in double-buffering via the JComponent
doubleBuffered property, and it defaults to true
for all Swing components, however setting it to true
on a Swing container has the general effect of turning it on
for all lightweight descendents of that container, regardless
of their individual property settings.
-
It is strongly recommended that double-buffering be enabled
for all Swing components.
-
Components which render complex output should make smart use
of the clip rectangle to narrow the drawing operations to those
which intersect with the clip area.
Summary
Both the AWT and Swing provide APIs to make it easy for programs to
render content to the screen correctly. Although we recommend that
developers use Swing for most GUI needs, it is useful to understand
AWT's paint mechanism because Swing's is built on top of it.
To get the best performance from these APIs, application programs
must also take responsibility for writing programs which use the
guidelines outlined in this document.
|