Previous | Next | Trail Map | Creating a GUI with JFC/Swing | Working with Graphics

Overview of Custom Painting

If you haven't read the Painting(in the Creating a User Interface trail) section, please do so right now. That section describes how Swing components are painted -- essential information if you're going to write custom painting code.

Before you implement a component that performs custom painting, first make sure that you really need to do so. You might be able to use the text and image capabilities of labels(in the Creating a User Interface trail), buttons(in the Creating a User Interface trail), or text components(in the Creating a User Interface trail) instead. And remember, you can use borders(in the Creating a User Interface trail) to customize the outside edges of a component.

If you really need to perform custom painting, then you need to decide which superclass to use. We recommend that you extend either JPanel or a more specialized Swing component class. For example, if you're creating a custom button class, you should probably implement it by extending a button class such as JButton or JToggleButton. That way you'll inherit the state management provided by those classes. If you're creating a component that paints on top of an image, you might want to create a JLabel subclass. On the other hand, if you're implementing a component that generates and displays a graph on top of a blank or transparent background, then you might want to use a JPanel subclass.

When implementing custom painting code, keep two things in mind:

An Example of Custom Painting

The following code gives an example of custom painting. It shows an image twice, once at its natural size and once very wide.
class ImagePanel extends JPanel {
    ...
    public void paintComponent(Graphics g) {
        super.paintComponent(g); //paint background
    
        //Draw image at its natural size first.
        g.drawImage(image, 0, 0, this); //85x62 image
    
        //Now draw the image scaled.
        g.drawImage(image, 90, 0, 300, 62, this);
    }
}
Here is the result:
Click this figure to run the applet.
This is a picture of the applet's GUI. To run the applet, click the picture. The applet will appear in a new browser window.

The example code is from ImageDisplayer.java, which is further discussed in Displaying Images. The example demonstrates a few rules that apply to all components that perform custom painting:

One thing this component does not do is take borders into account. Not only does it not use a border, but it also doesn't adjust its painting coordinates to take a border into account. A production-quality component would adjust to borders as described in the next subsection.

The Coordinate System

Each component has its own integer coordinate system, ranging from (0, 0) to (width - 1, height - 1), with each unit representing the size of one pixel. As the following figure shows, the upper left corner of a component's painting area is (0, 0). The X coordinate increases to the right, and the Y coordinate increases downward.

When painting a component, you must take into account not only the component's size but also the size of the component's border, if any. For example, a border that paints a one-pixel line around a component changes the top leftmost corner from (0,0) to (1,1) and reduces the width and the height of the painting area by two pixels each (one pixel per side). The following figure demonstrates this:

(0,0)
  \ (1, 1)
   v /
   =v===========
   =...........=
   =...........=
   =...........=
   =...........<- (width - 2, height 2)
   =============
                ^
                 \
         (width - 1, height - 1)
[PENDING: convert this figure from ASCII to a real figure]
[PENDING: write a program with such a border.]
You get the width and height of a component using its getWidth and getHeight methods. To determine the border size, use the getInsets method. Here is some code that a component might use to determine the width and height available for custom painting:
public void paintComponent(Graphics g) {
    ...
    Insets insets = getInsets();
    int currentWidth = getWidth() - insets.left - insets.right;
    int currentHeight = getHeight() - insets.top - insets.bottom;
    ...
    .../* First painting occurs at (x,y), where x is at least
      insets.left, and y is at least insets.height. */...
}

To familiarize yourself with the coordinate system, you can play with the following applet. Wherever you click on or inside the framed area, a dot is painted, and the label below lists the click's coordinates. The dot is obscured if you click on the border because the component's border is painted after the component performs its custom painting. If we didn't want this effect, an easy solution would be to move the border from the component into a new JPanel object that contains the component.

Click this figure to run the applet.
This is a picture of the applet's GUI. To run the applet, click the picture. The applet will appear in a new browser window.
The program is implemented in CoordinatesDemo.java. Although we don't discuss this example's code anywhere, it's very similar to the code in the RectangleDemo program, which is discussed a little later in Painting Shapes.

Arguments to the repaint Method

Remember that calling a component's repaint method requests that the component be scheduled to paint itself. When the painting system is unable to keep up with the pace of repaint requests, it might combine multiple requests into a single paint request to the component.

The repaint method has two useful forms:

void repaint()
Requests that the entire component be repainted.
void repaint(int, int, int, int)
Requests that only the specified part of the component be repainted. The arguments specify first the X and Y coordinates at the upper left of the area to be repainted, and then the area's width and height.

Although using the four-argument form of repaint method often isn't practical, it can help painting performance significantly. The program in the following picture uses the four-argument repaint method when requesting frequent repaints to display the user's current selection area. Doing so avoids repainting the parts of the component that haven't changed since the last painting operation.

Click this figure to run the applet.
This is a picture of the applet's GUI. To run the applet, click the picture. The applet will appear in a new browser window.
The program is implemented in SelectionDemo.java. Here is the code that calculates the area to be repainted and then paints it:
class SelectionArea extends JLabel {
    ...
    public SelectionArea(ImageIcon image, ...) {
        super(image); //Makes this component display an image.
        ...
    }

    ...//In a mouse-dragged event handler:
        Rectangle totalRepaint = rectToDraw.union(previousRectDrawn);
        repaint(totalRepaint.x, totalRepaint.y,
                totalRepaint.width, totalRepaint.height);
    ...
    public void paintComponent(Graphics g) {
        super.paintComponent(g); //paints the background and image
        ...
        //Paint a rectangle on top of the image.
        g.setXORMode(Color.white); //Color of line varies
                                   //depending on image colors
        g.drawRect(rectToDraw.x, rectToDraw.y,
                   rectToDraw.width - 1, rectToDraw.height - 1);
        ...
    }
    ...
}
As you can see, the custom component extends JLabel so that it inherits the ability to display an image. The user can select a rectangular area by dragging the mouse. The component continuously displays a rectangle indicating the size of the current selection. To improve rendering speed, the component's mouse-dragged event handler specifies a painting area to the repaint method.

[PENDING: checking. Even for a large image, does painting only part of it help with Swing components?]
By limiting the area to be repainted, the event handlers avoid unnecessarily repainting the image outside of that area. For this small image [PENDING: or "For images"?, there's no noticeable performance benefit to this strategy. However, for a large image there might be a real benefit [PENDING: true?]. If instead of painting an image from a file, you had to compute what to paint under the rectangle -- for example, computing shapes in a draw program -- then using knowledge of the paint area to limit the computation you perform might improve performance significantly. [PENDING: check]

The area specified to repaint must include not only the area to be painted, but also any area that needs to be erased. Otherwise, old painting remains visible until it happens to be erased by other painting. The preceding code calculates the total area to be repainted by taking the union of the rectangle to be painted with the rectangle that was previously painted.

The painting area specified to repaint is reflected in the Graphics object passed into the paintComponent method. You can use the getClipBounds method to determine which rectangular area to paint. Here is an example of painting as small a background rectangle as possible:

[PENDING: CHECK: can clipRect be null? Chan's book says yes, but the spec doesn't say.]

public void paintComponent(Graphics g) {

    //Paint the background
    Rectangle clipRect = g.getClipBounds();
    g.setColor(getBackground()); 
    g.fillRect(clipRect.x, clipRect.y,
               clipRect.width, clipRect.height);

    ...//Perform custom painting...
}

Note: You might have noticed that the last two arguments to fillRect were the desired width and height, while the last two arguments to drawRect were each one less -- desiredWidth - 1 and desiredHeight - 1. This is because the painting system treats lines and fills differently. Lines are painted underneath the bounding coordinates, making them one pixel wider and taller than the specified width and height. Fills, on the other hand, are painted between the bounding coordinates, so they are precisely the requested height and width. We'll discuss this a bit more in Painting Shapes.

The Graphics Object

The Graphics(in the API reference documentation) object passed into the paintComponent method provides both a context for painting and methods for performing the painting. The methods, which we discuss in detail a little later, have names such as drawImage, drawString, drawRect, and fillRect.

The graphics context consists of state such as the current output device (for example, the screen or offscreen buffer) [PENDING: check this; the API doc doesn't refer to this], the current painting color, the current font, and (as you've already seen) the current painting area, The color and font are initialized to the foreground color and font of the component just before the invocation of paintComponent. You can get them using the getColor and getFont methods, and set them using the setColor and setFont methods.

You can safely ignore the current painting area, if you like. It has no effect on the component's coordinate system, and any painting outside the area is ignored. However, if your painting code involves complex operations that can be simplified if the painting area is reduced, then you should use your knowledge of the painting area to help you improve painting performance. As shown by the previous code example, you get the painting area's rectangular bounds from the Graphics object by invoking the getClipBounds method.

You can reduce the painting area in two ways. The first is to specify repaint with arguments whenever possible. The other is to implement paintComponent so that it invokes the Graphics object's setClip method. If you use setClip, be sure to restore the original painting area before returning. Otherwise, the component could be painted improperly. Here's an example of reducing and then restoring the painting area:

Rectangle oldClipBounds = g.getClipBounds();
Rectangle clipBounds = new Rectangle(...);
g.setClip(clipBounds);

...//Perform custom painting...

g.setClip(oldClipBounds);

When writing your painting code, keep in mind that you can't depend on any graphics context except what's provided by the Graphics object. For example, you can't rely on the painting area you specify with repaint being exactly the same as the painting area used in the subsequent call to paintComponent. For one thing, multiple repaint requests can be coalesced into a single paintComponent call, with the painting area adjusted accordingly. For another, the AWT painting system occasionally calls paintComponent on its own, without any repaint request from your program. As an example, the painting system invokes a component's paintComponent method when it first shows the component's GUI. Also, when the GUI is covered by another window and then becomes uncovered, the painting system invokes the paintComponent method with the painting area equal to the newly uncovered area.

The Swing Painting Methods

The paintComponent method is one of three methods that JComponent objects use to paint themselves. The three methods are invoked in this order:
  1. paintComponent -- The main method for painting. By default, it first paints the background if the component is opaque. Then it performs any custom painting.
  2. paintBorder -- Tells the component's border (if any) to paint. Do not invoke or override this method.
  3. paintChildren -- Tells any components contained by this component to paint themselves. Do not invoke or override this method.


    Note: Don't override or invoke the method that calls the paintXxx methods: the paint method, Although overriding paint was legitimate in pre-Swing components, it's generally not a good thing to do in components that descend from JComponent. Unless you're careful, overriding paint would likely confuse the painting system, which relies on the JComponent implementation of the paint method for correct painting, performance enhancements, and features such as double buffering.

    The standard Swing components delegate their look-and-feel-specific painting to an object called a UI delegate. When such a component's paintComponent method is called, the method asks the UI delegate to paint the component. Generally, the UI delegate first checks whether the component is opaque and, if so, paints the entire background of the component. Then the UI delegate performs any look-and-feel-specific painting.

    The reason that we recommend extending JPanel instead of JComponent is that the JComponent class doesn't currently set up a UI delegate -- only its subclasses do. This means that if you extend JComponent, your component's background won't be painted unless you paint it yourself. When you extend JPanel and invoke super.paintComponent at the top of your paintComponent method, however, then the panel's UI delegate paints the component's background if the component is opaque.

    If you need more information about painting, see Painting in AWT and Swing. It's an article in The Swing Connection that discusses in depth the intricacies of painting.


Previous | Next | Trail Map | Creating a GUI with JFC/Swing | Working with Graphics