[an error occurred while processing this directive]
The Morgue Commentary PLAF Papers Friends Tech Topics Swing Text The Web Tech Topics What's Swing?
Index Archive Call 911 PLAF Papers Friends Tech Topics Swing Text Swing & the Web IDE Roundup Special Report Databank Page 2 Page One What's Swing?
JDK Docs Download JDK Swing API Docs Download Swing Java Tutorial
The Swing Connection The Archive Tech Topics
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.

Great Wall imageOnce 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:


Standard Swing borders

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));

AWT insets and the border property

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.


Sharing border instances

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.


Creating nested borders

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)));

Borders with look-and-feel defaults

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!"


Example: Crating your own 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).
[an error occurred while processing this directive]