Building Reusable Java Widgets

An introduction to writing pluggable do-it-yourself widgets for the Java programmer.
Widget Examples

For the sake of clarity in the examples, I have kept the graphics to a minimum and focused on the “nuts and bolts” of creating a reusable widget.

A note about style: I precede all of my instance variables with an underscore. Variables in upper case are class variables (static variables), and variables in lower case are generally temporary variables that have either been passed into the method or defined within the current block. Class and interface names always start with an upper case letter. Event classes always use “Event” as a suffix, and listener interfaces always have “Listener” for a suffix.

Example 1. Vertical Separator

The first widget is a simple vertical separator. A separator should be familiar to HTML users; the <hr> tag creates a horizontal rule. Separators are used to separate groups of components. The vertical separator is a very simple widget. It does not need to interact with any other widgets or objects. It is simply a visual component. After a little thought I came to the conclusion that this widget should be created by specialization, specifically by subclassing the Canvas class. Canvas is a good choice here since the only duty of the vertical separator is to render itself on the screen. Separators generally have an etched look to them, as if they were carved into the screen.

1. Rendering an Etched Line

To create the 3D effect of etching, draw two lines next to each other, one darker than the background, the other lighter. Here is a simple Java code fragment to create two vertical lines—one will appear etched, the other one raised. See Figure 5 for a picture of the two lines.

public void paint( Graphics g ) {
        // draw a raised line
        g.setColor( _light ) ;
        g.drawLine( 5, 10, 5, 40 ) ;
        g.setColor( _dark ) ;
        g.drawLine( 6, 10, 6, 40 ) ;
        // draw an etched line
        g.setColor( _dark ) ;
        g.drawLine( 25, 10, 25, 40) ;
        g.setColor( _light ) ;
        g.drawLine( 26, 10, 26, 40) ;

Figure 5. Etched and Raised Lines

Here is how I set the values of the two instance variables _light and _dark :

_light = getBackground().brighter().brighter() ;
_dark  = getBackground().darker().darker() ;

Setting the values relative to the background color rather than a hard-coded color makes the code more general. These lines will appear etched and raised regardless of the background color of the region in which they appear.

1.2. Sizing the Separator

The vertical separator should fill its allocated space vertically and center in its space horizontally. Within the paint method you can determine the space that has been allotted and calculate its dimensions. Here is how I did it:

size = size() ;
int length = size.height ;
int yPosition = ( size.width )/2 ;
g.setColor( dark ) ;
g.drawLine( 0, yPosition, length, yPosition ) ;
g.setColor( light ) ;
g.drawLine( 0, yPosition+1, length, yPosition+1 );

Now, there is one more critical method we need to override. Remember that I have chosen to subclass Canvas. The Canvas class provides a default size of 0x0. This means that if the widget is laid out using its default size, it will not show up. To achieve a meaningful default size, you need to override the getPrefferedSize and getMinimumSize methods. I have chosen a region of 4x8 pixels for both its preferred size and its minimum size. Why did I choose 4x8? The separator has an actual width of 2 pixels. Setting its preferred width to 4 gives it a 1 pixel buffer on each side. The preferred height of 8 pixels is somewhat arbitrary—any value greater than 0 is acceptable, just so it is visible. Remember if the separator is used properly, the layout manager will grow its height to an appropriate value regardless of the preferred height.

You can see the completed VerticalSeparator class in Listing 1. Remember, we built the vertical separator to fill the vertical space that is allocated, so be sure to place it appropriately. The east and west portions of the border layout are guaranteed to be vertical regions. If the separator is placed in either of those regions, it will grow to fill the vertical region. If you place it in the north or south regions, it will be sized to its preferred height of 8 pixels, which may not be what you want. I recommend you read up on layout managers and how they respond to a widget's need to be sized. Some completely disregard preferred sizes. You can see an applet that uses the vertical separator in Figure 6.

Figure 6. The VerticalSeparator in Action

In this example I have introduced some fundamental concepts of drawing and sizing. The goal of the vertical widget is simply to make your GUI look better. This is a noble goal, but in the next few examples we are going to look at some harder working widgets that really earn their keep. Before looking at those widgets, here are a couple of challenges.