Understanding
Borders
Plain
or Fancy Decor Not a Problem with Swing
By Amy Fowler
It's easy to design eye-catching borders for Swing
components, thanks to a generic border
property that all Swing components inherit.
Once
you know how Swing's border property
works, you can create many different kinds of plain or decorative
borders, such as beveled borders for buttons, labeled borders for
panels, and borders that are just simple lines.
With equal ease, you can use the border
property to surround a component with nothing but a specified amount
of white space -- a variety of border that is particularly suitable
for text margins.
In Version 0.7 of Swing, a new Border interface provides
a set of standard Border objects and a generous collection of methods
for painting borders, getting border insets (which set the thickness
of each of a border's four edges), and determining whether borders
are opaque. Swing's Border interface is defined in the swing.border
package, along with a set of standard border classes that implement
the interface.
The Border interface also determines the data types
that are used for various kinds of borders.
You can get or set a component's border
property by calling the following methods, which are inherited
from the JComponent class:
public void setBorder(Border border)
public Border getBorder()
This article explains how Swing's border classes work,
and provides some handy tips for creating, implementing, and managing
borders in Swing applications. The following major topics are introduced
in this article:
Swing provides a number of standard borders, and also
lets you create your own borders. These are the standard borders
that Swing provides:
- EmptyBorder: Provides white space (or padding) that
you can use to surround a component.
- LineBorder: Provides a colored border of a single
thickness.
- BevelBorder: Provides a 3D raised or lowered beveled
border.
- EtchedBorder: Provides a 3D etched-in border.
- MatteBorder: Provides a colored or tiled-image border.
- TitledBorder: Provides a border containing a title
with a specified position and justification.
- CompoundBorder: Enables the nesting of an outer and
an inner border.
If none of these predefined borders meets your needs,
you can create your own. For example, to place a 3-pixel blue border
around the edge of a label, you could do the following:
JLabel label = new
JLabel("I'm blue around the edges");
label.setBorder
(new LineBorder(Color.blue, 3));
As AWT programmers know, the java.awt.Container
package defines an insets property
that you can use to create padding around the four edges of a container,
an area in which a container's children are not allowed to overlap.
In Swing, it's still legal to use AWT-style insets, but Swing's
border property provides a more
streamlined mechanism for placing padding around a component's edges.
In Swing, the AWT insets
property is superseded by the insets
property in a very simple way: JComponent's getInsets()
method simply delegates its operations to a border object's getBorderInsets()
method.
In a Swing application, you should not set the insets
property directly. Instead, just create an EmptyBorder object, and
then set its border property to
display whatever amount of white space you want your component to
have.
In Swing, it's perfectly legal to invoke getInsets()
to determine the amount of space that will be occupied by a border
-- a determination that all legal LayoutManager objects should make.
But in a Swing program, you should not override getInsets()
to return any specific values for your JComponent subclass. Instead,
call your border object's getBorderInsets()
method.
Swing's Border classes are designed in such a way
that a single Border instance can be shared across components; for
example, all instantiated JButtons that are equipped with bevel
borders can use the same Border object for their bevel.
This strategy works in Swing because the paintBorder()
and getBorderInsets() methods
take a java.awt.Component as a parameter. This means that
a border object can compute any component-specific information on
the fly. For example, a border object can dynamically highlight
a color property that's based on its parent component's background
color.
The BorderFactory class
To facilitate the sharing of Border instances across
components, the Swing set provides a special factory class named
BorderFactory. The BorderFactory class defines a number of static
methods that return shared instances of the various standard border
types (whenever sharing is feasible, of course). Because the BorderFactory
class is implemented in the swing package, you can set
borders without importing the swing.border package.
For example, to set a lowered bevel border for a panel,
you could execute the following statements:
JPanel panel = new JPanel();
panel.setBorder
(BorderFactory. \
createLoweredBevelBorder());
We highly recommend using the BorderFactory methods
to obtain Border instances -- particularly when you're working with
borders that don't have individual properties (such as BevelBorder
and EtchedBorder) -- because the BorderFactory methods
are designed to optimize the sharing of such objects.
You can get some interesting visual effects by placing
multiple border decorations around a single component -- for instance,
you might want to find out what happens when you surround a button
with both a bevel and some white-space padding around its label.
To help you nest multiple borders inside each other
in this fashion, Swing's Border interface provides a CompoundBorder
class. The CompoundBorder class takes two Border parameters in its
constructor -- one for the outer border and one for the inner border.
For example, the following code fragment creates a
label that is surrounded not only by four 10-pixel margins, but
also by a surrounding raised-bevel border:
JLabel label = new JLabel
("I'm floating");
label.setBorder(new CompoundBorder
(BorderFactory.createRaisedBevelBorder(),
new EmptyBorder(10,10,10,10)));
Some Swing components are equipped with look-and-feel
mechanisms that install default borders with appearances which match
the L&F characteristics of the platform for which they are designed.
Most buttons fall into this category. So do a number of other kinds
of classes, including JToolBar, JInternalFrame, and so on.
When a component has a set of default L&F-specific
border settings, the component's UI object sets up the component's
border when the UI is first installed. This typically happens either
when the component is constructed or when the UI is dynamically
changed.
But a component's UI will not install its default
border if it detects that a non-UI-default border has already been
installed. Consequently, when you create a component, you can easily
override the installation of a UI-default border for the component
by simply setting your own Border object using the setBorder()
method.
If you decide to take this approach, though, remember
that indiscriminately switching non-UI-default borders for default
borders can have detrimental effects on some components' UIs. For
example, every Swing button has a UI-default border that can detect
the state of the button, and an application can use this ability
to start or stop a button animation when the button is pressed.
None of Swing's standard Border classes have this ability, so replacing
a button's UI-default border with a non-UI-default border can destroy
the button's built-in ability to detect and control animations.
Another potentially undesirable side effect stems
from the fact that Swing's button and text components provide margins
as a component property (for more details, see the getMargins()
and setMargins() methods
in the appropriate APIs). By calling these methods, a clever Swing
programmer can easily get and set the margins of a button component
or a text component without bothering with any Border operations
at all.
But before you give in to such a temptation, beware:
Once again, if a component has a "smart" border and
you replace that border with standard Border object, one not smart
enough to calculate the component's margins, the margin settings
will be lost.
That's one reason that we recommend implementing margins
using Border objects. When you follow this advice, you know that
your component's UI object will automatically create an appropriate
border object (or, alternatively, a CompoundBorder object) that
takes the margins property value
into account when it interacts with other kinds of objects.
So the moral of this section could be summed up as
follows:
"Beware of changing borders on components that
have UI-defined borders!"
If none of the standard Border classes meets your
needs, don't fret: Swing makes it relatively easy to create your
own Border classes. The Swing set does that by providing a base
class named AbstractBorder. The AbstractBorder class can be a good
starting point for creating customized borders for Swing components.
The following code fragment, for example, defines
a custom Border class that paints a double-line border of a specified
color:
import com.sun.java.swing.border.*;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Color;
import java.awt.Component;
public class DoubleLineBorder
extends AbstractBorder
{
protected Color lineColor;
public DoubleLineBorder(Color color) {
lineColor = color;
}
public void paintBorder(Component c,
Graphics g, int x, int y, int width,
int height) {
g.setColor(lineColor);
g.drawRect(x, y, width-1, height-1);
g.drawRect(x+2, y+2,
width-5, height-5);
}
public Insets getBorderInsets
(Component c) {
return new Insets(3,3,3,3);
}
public boolean isBorderOpaque() {
return false;
}
}
How it works
Here are some notes on how the preceding code works:
- In the paintBorder() method, if you change any element of the Graphics
object's state except for the color or the font, you
should be careful to save and restore the object's original
state. This is because JComponent does not create a new Graphics
child before calling paintBorder(). (In the preceding example, because
we are setting only the color, we do not need to perform any
specific state management).
- The isBorderOpaque()
method should return true only if your paintBorder()
method paints all the pixels it defines in its insets. (In the
example above, we leave a blank pixel line between the opaque
lines, so the border is not opaque).
|