The JavaTM Tutorial

| Start of Tutorial > Start of Trail > Start of Lesson | Search

Trail: Creating a GUI with JFC/Swing
Lesson: Working with Graphics

## Overview of Custom Painting

> If you haven't read the Painting 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, buttons, or text components instead. And remember, you can use borders 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:

> - Your custom painting code belongs in a method named `paintComponent`.
> - You can -- and probably should -- use a border to paint the outside edges of your component.

### 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:

> 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:

> - The painting code does something that no standard Swing component does. If we just wanted to display the figure once, at its natural size, we would have used a `JLabel` object instead of the custom component.
> - The custom component is a `JPanel` subclass. This is a common superclass for custom components.
> - All the custom painting code is in a method called `paintComponent`.
> - Before performing any custom painting, the component paints its background by invoking `super.paintComponent`. If we remove that call, either our custom painting code must paint the component's background or we must invoke `setOpaque(false)` on the component. Doing the latter would inform the Swing painting system that the components behind the non-opaque component might be visible, and thus should be painted.

> 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:

> 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.

> 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.

> 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.setColor(Color.white);
> >             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.

> By limiting the area to be repainted, the event handlers avoid unnecessarily repainting the image outside of that area. For this small image there's no noticeable performance benefit to this strategy. However, for a large image there might be a real benefit. And 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.

> 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 using the clip bounds:

> >     public void paintComponent(Graphics g) {
> >         Rectangle clipRect = g.getClipBounds();
> >         if (clipRect != null) {
> >     	//If it's more efficient, draw only the area
> >     	//specified by clipRect.
> >     	//Top-leftmost point = (clipRect.x, clipRect.y)
> >     	//Width, height = clipRect.width, clipRect.height
> >         } else {
> >     	//Paint the entire component.
> >         }
> >     }

### The Graphics Object

> The `Graphics` 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 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 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.

| Start of Tutorial > Start of Trail > Start of Lesson | Search