Good Painting Code Is the Key to App Performance
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. - By Amy Fowler
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.
The main topics covered in this article are:
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.
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.
In a system-triggered painting operation, the system requests a component to render its contents, usually for one of the following reasons:
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:
Graphics
object's color is set to the component's foreground
property.Graphics
object's font is set to the component's font
property.Graphics
object's translation is set such that the coordinate (0,0) represents the upper left corner of the component.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:
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.
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:
paint()
.
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.
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:
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.
The AWT provides a simple callback API for painting components. When you use it, the following guidelines apply:
paint()
method.
paint()
by invoking repaint()
, but shouldn't call paint()
directly.
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.
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)
java.awt.Container
which override paint()
should always invoke super.paint()
to ensure children are painted.
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.
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.
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:
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 overlapBy 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 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()
.ui
property is non-null, paintComponent()
invokes ui.update().
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 compone nt 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.
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
):
paint()
on that ancestor
Container.paint()
recursively calls paint()
on any lightweight descendents
JComponent.paint()
does the following:
doubleBuffered
property is true
and double-buffering is enabled on the component's RepaintManager
, will convert the Graphics
object to an appropriate offscreen graphics.paintComponent()
(passing in offscreen graphics if doubled-buffered)paintBorder()
(passing in offscreen graphics if doubled-buffered)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.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.
RepaintManager
to invoke paintImmediately()
on the component, which does the following:
opaque
and optimizedDrawingEnabled
properties to determine the 'root' component from which the paint operation must begin (to deal with transparency and potentially overlapping components).doubleBuffered
property is true
, and double-buffering is enabled on the root's RepaintManager
, will convert the Graphics
object to an appropriate offscreen graphics.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.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 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 programs should understand these guidelines when writing paint code:
paint()
is always invoked as a result of both system-triggered and app-triggered paint requests; update()
is never invoked on Swing components.
paint()
by invoking repaint()
, but shouldn't call paint()
directly.
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.
paint()
factors the call into 3 separate callbacks:
paintComponent()
paintBorder()
paintChildren()
paintComponent()
method ( not within paint()
).
opaque
: will the component paint all its bits or not?optimizedDrawingEnabled
: may any of this component's children overlap?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.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.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.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.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.