[ Team LiB ] Previous Section Next Section

15.2 A Simple Bean

As noted earlier, Swing and AWT components can all function as beans. When you write a custom GUI component, it is not difficult to make it function as a bean as well. Example 15-1 shows the definition of a custom JavaBeans component, MultiLineLabel, that displays one or more lines of static text.

What makes this component a bean is that all its properties have get and set accessor methods. Because MultiLineLabel doesn't respond to user input in any way, it doesn't define any events, so no event-listener registration methods are required. MultiLineLabel also defines a no-argument constructor, so that it can be easily instantiated by beanboxes.

Example 15-1. MultiLineLabel.java
package je3.beans;
import java.awt.*;
import javax.swing.*;
import java.util.StringTokenizer;

/**
 * A custom component that displays multiple lines of text with specified
 * margins and alignment.
 **/
public class MultiLineLabel extends JComponent {
    // User-specified properties
    protected String label;             // The label, not broken into lines
    protected int margin_width;         // Left and right margins
    protected int margin_height;        // Top and bottom margins
    protected Alignment alignment;      // The alignment of the text.

    // Computed state values
    protected int num_lines;            // The number of lines
    protected String[  ] lines;           // The label, broken into lines
    protected int[  ] line_widths;        // How wide each line is
    protected int max_width;            // The width of the widest line
    protected int line_height;          // Total height of the font
    protected int line_ascent;          // Font height above baseline
    protected boolean measured = false; // Have the lines been measured?
    
    // Here are five versions of the constructor.
    public MultiLineLabel(String label, int margin_width,
                          int margin_height, Alignment alignment) {
        this.label = label;                 // Remember all the properties.
        this.margin_width = margin_width;
        this.margin_height = margin_height;
        this.alignment = alignment;
        newLabel( );                         // Break the label up into lines.
    }

    public MultiLineLabel(String label, int margin_width, int margin_height) {
        this(label, margin_width, margin_height, Alignment.LEFT);
    }

    public MultiLineLabel(String label, Alignment alignment) {
        this(label, 10, 10, alignment);
    }

    public MultiLineLabel(String label) { this(label, 10, 10, Alignment.LEFT);}

    public MultiLineLabel( ) { this(""); }
    
    // Methods to set and query the various attributes of the component.
    // Note that some query methods are inherited from the superclass.
    public void setLabel(String label) {
        this.label = label;
        newLabel( );               // Break the label into lines.
        repaint( );                // Request a redraw.
        measured = false;         // Note that we need to measure lines.
        invalidate( );             // Tell our containers about this
    }
    public void setAlignment(Alignment a) { alignment = a; repaint( ); }
    public void setMarginWidth(int mw) { margin_width = mw; repaint( ); }
    public void setMarginHeight(int mh) { margin_height = mh; repaint( ); }

    // Override this property setter method because we need to remeasure
    public void setFont(Font f) {
        super.setFont(f);         // Tell our superclass about the new font.
        repaint( );                // Request a redraw.
        measured = false;         // Note that we need to remeasure lines.
        invalidate( );             // Tell our containers about new size
    }

    // Property getter methods.
    public String getLabel( ) { return label; }
    public Alignment getAlignment( ) { return alignment; }
    public int getMarginWidth( ) { return margin_width; }
    public int getMarginHeight( ) { return margin_height; }
    
    /**
     * This method is called by a layout manager when it wants to
     * know how big we'd like to be.
     */
    public Dimension getPreferredSize( ) {
        if (!measured) measure( );
        return new Dimension(max_width + 2*margin_width,
                             num_lines * line_height + 2*margin_height);
    }

    /**
     * This method is called when the layout manager wants to know
     * the bare minimum amount of space we need to get by.
     */
    public Dimension getMinimumSize( ) { return getPreferredSize( ); }
    
    /**
     * This method draws the component.
     * Note that it handles the margins and the alignment, but that
     * it doesn't have to worry about the color or font--the superclass
     * takes care of setting those in the Graphics object we're passed.
     **/
    public void paintComponent(Graphics g) {
        int x, y;
        Dimension size = this.getSize( );
        if (!measured) measure( );
        y = line_ascent + (size.height - num_lines * line_height)/2;
        for(int i = 0; i < num_lines; i++, y += line_height) {
            if (alignment == Alignment.LEFT) x = margin_width;
            else if (alignment == Alignment.CENTER) 
                x = (size.width - line_widths[i])/2; 
            else x = size.width - margin_width - line_widths[i];
            g.drawString(lines[i], x, y);
        }
    }

    /**
     * This internal method breaks a specified label up into an array of lines.
     * It uses the StringTokenizer utility class.
     **/
    protected synchronized void newLabel( ) {
        StringTokenizer t = new StringTokenizer(label, "\n");
        num_lines = t.countTokens( );
        lines = new String[num_lines];
        line_widths = new int[num_lines];
        for(int i = 0; i < num_lines; i++) lines[i] = t.nextToken( );
    }
    
    /**
     * This internal method figures out how the font is, and how wide each
     * line of the label is, and how wide the widest line is.
     **/
    protected synchronized void measure( ) {
        FontMetrics fm = this.getFontMetrics(this.getFont( ));
        line_height = fm.getHeight( );
        line_ascent = fm.getAscent( );
        max_width = 0;
        for(int i = 0; i < num_lines; i++) {
            line_widths[i] = fm.stringWidth(lines[i]);
            if (line_widths[i] > max_width) max_width = line_widths[i];
        }
        measured = true;
    }
}

15.2.1 The Alignment Class

MultiLineLabel uses an auxiliary class named Alignment to define three alignment constants. The definition of this class is shown in Example 15-2. The class defines three constants that hold three instances of itself, and declares its constructor private so that no other instances can be created. In this way, Alignment effectively creates an enumerated type. This is a useful technique that is not at all specific to JavaBeans.

Example 15-2. Alignment.java
package je3.beans;

/** This class defines an enumerated type with three values */
public class Alignment {
    /** This private constructor prevents anyone from instantiating us */
    private Alignment( ) {  };
    // The following three constants are the only instances of this class
    public static final Alignment LEFT = new Alignment( );
    public static final Alignment CENTER = new Alignment( );
    public static final Alignment RIGHT = new Alignment( );
}

15.2.2 Packaging a Bean

To prepare a bean for use in a beanbox, you must package it in a JAR file, along with any other classes or resource files it requires. (JAR files are "Java archives"; you can read about the jar tool in Java in a Nutshell.) Because a single bean can have many auxiliary files, and because a JAR file can contain multiple beans, the manifest of the JAR file must define which JAR file entries are beans. You create a JAR file with the c option to the jar command. When you use the m option in conjunction with c, it tells jar to read a partial manifest file that you specify. jar uses the information in your partially specified manifest file when creating the complete manifest for the JAR file. To identify a class file as a bean, you simply add the following line to the file's manifest entry:

Java-Bean: true

To package the MultiLineLabel class in a JAR file, first create a manifest "stub" file. Create a file, perhaps named manifest.stub, with these contents:

Name: je3/beans/MultiLineLabel.class
Java-Bean: true

Note that the forward slashes in the manifest file shouldn't be changed to backward slashes on Windows systems. The format of the JAR manifest file requires forward slashes to separate directories, regardless of the platform. Having created this partial manifest file, you can now create the JAR file:

% jar cfm MultiLineLabel.jar manifest.stub 
  je3/beans/MultiLineLabel.class je3/beans/Alignment.class

Note that this is a single long command line that has been broken onto two lines. Also, on a Windows system, you do need to replace forward slashes with backslashes in this command line. If this bean required auxiliary files, you would specify them at the end of the jar command line, along with the class files for the bean.

15.2.3 Installing a Bean

The procedure for installing a bean depends on the beanbox tool you use. For Sun's demonstration Bean Builder tool, for example, use the Load Jar... command in the File menu to make your beans appear on the palette of available beans. For our own ShowBean tool, you simply need to ensure that the JAR file is in your classpath. In fact, you don't need to create the JAR file at all to use ShowBean: you can just ensure that the bean development directory is in your classpath.

    [ Team LiB ] Previous Section Next Section