Painting in Swing

Although the Swing component set is quite powerful, you are not limited to using it because Swing also lets you write directly into the display area of a frame, panel, or one of Swing’s other components, such as JLabel.

Painting
in Swing

Although the Swing component
set is quite powerful, you are not limited to using it because Swing also lets
you write directly into the display area of a frame, panel, or one of Swing’s
other components, such as JLabel.
Although many (perhaps most) uses of Swing will not involve drawing directly to the surface of a component, it is
available for those applications that need this capability. To write output
directly to the surface of a component, you will use one or more drawing
methods defined by the AWT, such as drawLine(
) or drawRect( ). Thus, most of
the techniques and methods described in Chapter 25 also apply to Swing.
However, there are also some very important differences, and the process is
discussed in detail in this section.

Painting
Fundamentals

Swing’s approach to painting
is built on the original AWT-based mechanism, but Swing’s implementation offers
more finally grained control. Before examining the specifics of Swing-based
painting, it is useful to review the AWT-based mechanism that underlies it.

The AWT class Component defines a method called paint( ) that is used to draw output
directly to the surface of a component. For the most part, paint( ) is not called by your program. (In fact, only in the most
unusual cases should it ever be called by your program.) Rather, paint( ) is called by the run-time
system whenever a component must be rendered. This situation can occur for
several reasons. For example, the window in which the component is displayed
can be overwritten by another window and then uncovered. Or, the window might
be minimized and then restored. The paint(
) method is also called when a program begins running. When writing
AWT-based code, an application will override paint( ) when it needs

to write output directly to
the surface of the component.

Because JComponent inherits Component,
all Swing’s lightweight components inherit the paint( ) method. However, you will
not override it to paint directly to the surface of a component. The reason
is that Swing uses a bit more sophisticated approach to painting that involves
three distinct methods: paintComponent(
), paintBorder( ), and paintChildren( ). These methods paint
the indicated portion of a component and divide the painting process into its
three distinct, logical actions. In a lightweight component, the original AWT
method paint( ) simply executes
calls to these methods, in the order just shown.

To paint to the surface of a
Swing component, you will create a subclass of the component and then override
its paintComponent( ) method. This
is the method that paints the interior of the component. You will not normally
override the other two painting methods. When overriding paintComponent( ), the first thing you must do is call super.paintComponent( ), so that the
superclass portion of the painting process takes place. (The only time this is
not required is when you are taking complete, manual control over how a
component is displayed.) After that, write the output that you want to display.
The paintComponent( ) method is
shown here:

protected void
paintComponent(Graphics g)

The parameter g is the graphics context to which
output is written.

To cause a component to be
painted under program control, call repaint(
). It works in Swing just as it does for the AWT. The repaint( ) method is defined by Component. Calling it causes the system to call paint( ) as soon as it is possible to
do so. Because painting is a time-consuming operation, this mechanism allows
the run-time system to defer painting momentarily until some higher-priority
task has completed, for example. Of course, in Swing the call to paint( ) results in a call to paintComponent( ). Therefore, to output
to the surface of a component, your program will store the output until paintComponent( ) is called. Inside the
overridden paintComponent( ), you
will draw the stored output.

Compute
the Paintable Area

When drawing to the surface
of a component, you must be careful to restrict your output to the area that is
inside the border. Although Swing automatically clips any output that will
exceed the boundaries of a component, it is still possible to paint into the
border, which will then get overwritten when the border is drawn. To avoid
this, you must compute the paintablearea of the component. This is the area
defined by the current size of the component minusthe space used by the border. Therefore, before you paint to a
component, you must obtain the width of the border and then adjust your drawing
accordingly.

This method is defined by Container and overridden by JComponent. It returns an Insets object that contains the
dimensions of the border. The inset values can be obtained by using these
fields:

int top; int bottom; int
left;

int right;

These values are then used to
compute the drawing area given the width and the height of the component. You
can obtain the width and height of the component by calling getWidth( ) and getHeight( ) on the component. They are shown here:

int getWidth( ) int
getHeight( )

By subtracting the value of
the insets, you can compute the usable width and height of the component.

A
Paint Example

Here is a program that puts
into action the preceding discussion. It creates a class called PaintPanel that extends JPanel. The program then uses an
object of that class to displaylines
whose endpoints have been generated randomly. Sample output is shown in Figure
31-4.

Let’s examine this program
closely. The PaintPanel class
extends JPanel. JPanel is one of Swing’s lightweight containers, which means that
it is a component that can be added to the content pane of a JFrame. To handle painting, PaintPanel overrides the paintComponent( ) method. This enables PaintPanel to write directly to the
surface of the component when painting takes place. The size of the panel is
not specified because the program uses the default border layout and the panel
is added to the center. This results in the panel being sized to fill the
center. If you change the size of the window, the size of the panel will be
adjusted accordingly.

Notice that the constructor
also specifies a 5-pixel wide, red border. This is accomplished by setting the
border by using the setBorder( )
method, shown here:

void setBorder(Border border)

Border is the Swing interface that encapsulates a border. You can obtain a
border by callingone of the factory
methods defined by the BorderFactory
class. The one used in the program is createLineBorder(
), which creates a simple line border. It is shown here:

static Border
createLineBorder(Color clr, int width)

Here, clr specifies the color of the border and width specifies its width in pixels.

Inside the override of paintComponent( ), notice that it first
calls super.paintComponent( ). As
explained, this is necessary to ensure that the component is properly drawn.
Next, the width and height of the panel are obtained along with the insets.
These values are used to ensure the lines lie within the drawing area of the
panel. The drawing area is the overall width and height of a component less the
border width. The computations are designed to work with differently sized PaintPanels and borders. To prove this,
try changing the size of the window. The lines will still all lie within the
borders of the panel.

The PaintDemo class creates a PaintPanel
and then adds the panel to the content pane. When the application is first
displayed, the overridden paintComponent(
) method is called, and the lines are drawn. Each time you resize or hide
and restore the window, a new set of lines are drawn. In all cases, the lines
fall within the paintable area.